flush
authorMichael Wallner <mike@php.net>
Tue, 8 Sep 2020 11:44:03 +0000 (13:44 +0200)
committerMichael Wallner <mike@php.net>
Tue, 8 Sep 2020 11:44:03 +0000 (13:44 +0200)
42 files changed:
testing/CMakeLists.txt
testing/hashkit/basic.cpp [deleted file]
testing/lib.cpp [deleted file]
testing/lib/Cluster.cpp
testing/lib/Cluster.hpp
testing/lib/MemcachedCluster.cpp
testing/lib/MemcachedCluster.hpp
testing/lib/Retry.cpp
testing/lib/Retry.hpp
testing/lib/Server.cpp
testing/lib/Server.hpp
testing/lib/common.cpp
testing/lib/common.hpp
testing/lib/random.cpp [new file with mode: 0644]
testing/lib/random.hpp [new file with mode: 0644]
testing/memcached/basic.cpp [deleted file]
testing/memcached/callbacks.cpp [deleted file]
testing/memcached/dump.cpp [deleted file]
testing/memcached/encoding_key.cpp [deleted file]
testing/memcached/exist.cpp [deleted file]
testing/memcached/haldenbrand.cpp [deleted file]
testing/memcached/servers.cpp [deleted file]
testing/tests/hashkit/basic.cpp [new file with mode: 0644]
testing/tests/lib.cpp [new file with mode: 0644]
testing/tests/memcached/append.cpp [new file with mode: 0644]
testing/tests/memcached/basic.cpp [new file with mode: 0644]
testing/tests/memcached/callbacks.cpp [new file with mode: 0644]
testing/tests/memcached/cas.cpp [new file with mode: 0644]
testing/tests/memcached/dump.cpp [new file with mode: 0644]
testing/tests/memcached/encoding_key.cpp [new file with mode: 0644]
testing/tests/memcached/exist.cpp [new file with mode: 0644]
testing/tests/memcached/haldenbrand.cpp [new file with mode: 0644]
testing/tests/memcached/inc_dec.cpp [new file with mode: 0644]
testing/tests/memcached/ketama.cpp [new file with mode: 0644]
testing/tests/memcached/ketama_test_cases.h [new file with mode: 0644]
testing/tests/memcached/ketama_test_cases_spy.h [new file with mode: 0644]
testing/tests/memcached/prepend.cpp [new file with mode: 0644]
testing/tests/memcached/regression/binary_block_add.cpp [new file with mode: 0644]
testing/tests/memcached/servers.cpp [new file with mode: 0644]
testing/tests/memcached/simple.cpp [new file with mode: 0644]
testing/tests/noreply.cpp [new file with mode: 0644]
tests/output_plus.res [deleted file]

index 5f1532f8c718bf3baf785ddc595488a9832fa3ed..b019302e0dda586fe07b163c6b579b1268bb075f 100644 (file)
@@ -1,28 +1,6 @@
 
-add_executable(catch_main
-        main.cpp
-
-        lib/common.cpp
-
-        lib/Cluster.cpp
-        lib/Connection.cpp
-        lib/ForkAndExec.cpp
-        lib/MemcachedCluster.cpp
-        lib/Server.cpp
-        lib/Retry.cpp
-
-        lib.cpp
-        hashkit/basic.cpp
-        memcached/basic.cpp
-        memcached/callbacks.cpp
-        memcached/servers.cpp
-        memcached/dump.cpp
-        memcached/encoding_key.cpp
-        memcached/exist.cpp
-        memcached/haldenbrand.cpp
-        )
-
-set_target_properties(catch_main PROPERTIES
-        CXX_STANDARD 17)
+file(GLOB_RECURSE TESTING_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
 
+add_executable(catch_main ${TESTING_SRC})
+set_target_properties(catch_main PROPERTIES CXX_STANDARD 17)
 target_link_libraries(catch_main libhashkit libmemcached)
diff --git a/testing/hashkit/basic.cpp b/testing/hashkit/basic.cpp
deleted file mode 100644 (file)
index 8393266..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-#include "../lib/common.hpp"
-
-#include "libhashkit-1.0/hashkit.hpp"
-
-static const char *input[] = {
-    "apple",
-    "beat",
-    "carrot",
-    "daikon",
-    "eggplant",
-    "flower",
-    "green",
-    "hide",
-    "ick",
-    "jack",
-    "kick",
-    "lime",
-    "mushrooms",
-    "nectarine",
-    "orange",
-    "peach",
-    "quant",
-    "ripen",
-    "strawberry",
-    "tang",
-    "up",
-    "volumne",
-    "when",
-    "yellow",
-    "zip",
-};
-
-static const uint32_t output[][sizeof(input)/sizeof(*input)] = {
-          // one_at_a_time
-          { 2297466611U, 3902465932U, 469785835U, 1937308741U,
-          261917617U, 3785641677U, 1439605128U, 1649152283U,
-          1493851484U, 1246520657U, 2221159044U, 1973511823U,
-          384136800U, 214358653U, 2379473940U, 4269788650U,
-          2864377005U, 2638630052U, 427683330U, 990491717U,
-          1747111141U, 792127364U, 2599214128U, 2553037199U,
-          2509838425U },
-
-          // md5
-          { 3195025439U, 2556848621U, 3724893440U, 3332385401U,
-          245758794U, 2550894432U, 121710495U, 3053817768U,
-          1250994555U, 1862072655U, 2631955953U, 2951528551U,
-          1451250070U, 2820856945U, 2060845566U, 3646985608U,
-          2138080750U, 217675895U, 2230934345U, 1234361223U,
-          3968582726U, 2455685270U, 1293568479U, 199067604U,
-          2042482093U },
-
-          // crc
-          { 10542U, 22009U, 14526U, 19510U, 19432U, 10199U, 20634U,
-          9369U, 11511U, 10362U, 7893U, 31289U, 11313U, 9354U,
-          7621U, 30628U, 15218U, 25967U, 2695U, 9380U,
-          17300U, 28156U, 9192U, 20484U, 16925U },
-
-          // fnv1_64
-          { 473199127U, 4148981457U, 3971873300U, 3257986707U,
-          1722477987U, 2991193800U, 4147007314U, 3633179701U,
-          1805162104U, 3503289120U, 3395702895U, 3325073042U,
-          2345265314U, 3340346032U, 2722964135U, 1173398992U,
-          2815549194U, 2562818319U, 224996066U, 2680194749U,
-          3035305390U, 246890365U, 2395624193U, 4145193337U,
-          1801941682U },
-
-          // fnv1a_64
-          { 1488911807U, 2500855813U, 1510099634U, 1390325195U,
-          3647689787U, 3241528582U, 1669328060U, 2604311949U,
-          734810122U, 1516407546U, 560948863U, 1767346780U,
-          561034892U, 4156330026U, 3716417003U, 3475297030U,
-          1518272172U, 227211583U, 3938128828U, 126112909U,
-          3043416448U, 3131561933U, 1328739897U, 2455664041U,
-          2272238452U },
-
-          // fnv1_32
-          { 67176023U, 1190179409U, 2043204404U, 3221866419U,
-          2567703427U, 3787535528U, 4147287986U, 3500475733U,
-          344481048U, 3865235296U, 2181839183U, 119581266U,
-          510234242U, 4248244304U, 1362796839U, 103389328U,
-          1449620010U, 182962511U, 3554262370U, 3206747549U,
-          1551306158U, 4127558461U, 1889140833U, 2774173721U,
-          1180552018U },
-
-          // fnv1a_32
-          { 280767167U, 2421315013U, 3072375666U, 855001899U,
-          459261019U, 3521085446U, 18738364U, 1625305005U,
-          2162232970U, 777243802U, 3323728671U, 132336572U,
-          3654473228U, 260679466U, 1169454059U, 2698319462U,
-          1062177260U, 235516991U, 2218399068U, 405302637U,
-          1128467232U, 3579622413U, 2138539289U, 96429129U,
-          2877453236U },
-
-          // hsieh
-#ifdef HAVE_HSIEH_HASH
-          { 3738850110U, 3636226060U, 3821074029U, 3489929160U, 3485772682U, 80540287U,
-          1805464076U, 1895033657U, 409795758U, 979934958U, 3634096985U, 1284445480U,
-          2265380744U, 707972988U, 353823508U, 1549198350U, 1327930172U, 9304163U,
-          4220749037U, 2493964934U, 2777873870U, 2057831732U, 1510213931U, 2027828987U,
-          3395453351U },
-#else
-          {  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
-#endif
-
-          // murmur
-#ifdef HAVE_MURMUR_HASH
-          // murmur
-          { 4142305122U, 734504955U, 3802834688U, 4076891445U,
-          387802650U, 560515427U, 3274673488U, 3150339524U,
-          1527441970U, 2728642900U, 3613992239U, 2938419259U,
-          2321988328U, 1145154116U, 4038540960U, 2224541613U,
-          264013145U, 3995512858U, 2400956718U, 2346666219U,
-          926327338U, 442757446U, 1770805201U, 560483147U,
-          3902279934U },
-          // murmur3
-          { 1120212521U, 1448785489U, 4186307405U, 2686268514U,
-          444808887U, 221750260U, 3074673162U, 1946933257U,
-          2826416675U, 2430719166U, 3200429559U, 297894347U,
-          732888124U, 4050076964U, 3298336176U, 1336207361U,
-          810553576U, 3748182674U, 3860119212U, 3439537197U,
-          3044240981U, 1464271804U, 3896193724U, 2915115798U,
-          1702843840U },
-#else
-          { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
-          { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
-#endif
-
-          // jenkins
-          { 1442444624U, 4253821186U, 1885058256U, 2120131735U,
-          3261968576U, 3515188778U, 4232909173U, 4288625128U,
-          1812047395U, 3689182164U, 2502979932U, 1214050606U,
-          2415988847U, 1494268927U, 1025545760U, 3920481083U,
-          4153263658U, 3824871822U, 3072759809U, 798622255U,
-          3065432577U, 1453328165U, 2691550971U, 3408888387U,
-          2629893356U }
-};
-
-TEST_CASE("hashkit") {
-  hashkit_st st, *hp = hashkit_create(nullptr);
-  Hashkit stack;
-  Hashkit *heap = new Hashkit;
-
-  REQUIRE(hashkit_create(&st));
-  REQUIRE(hp);
-
-  SECTION("can copy") {
-    Hashkit stack_copy(stack);
-    Hashkit *heap_copy(heap);
-    hashkit_st st_copy, *st_ptr;
-
-    (void) stack_copy;
-    (void) heap_copy;
-
-    st_ptr = hashkit_clone(&st_copy, &st);
-    REQUIRE(st_ptr == &st_copy);
-    REQUIRE(hashkit_compare(st_ptr, &st_copy));
-
-    SUCCEED("OK");
-  }
-
-  SECTION("can assign") {
-    Hashkit stack_copy;
-
-    stack_copy = stack;
-    (void) stack_copy;
-
-    SUCCEED("OK");
-  }
-
-  SECTION("can digest default") {
-    REQUIRE(2297466611U == stack.digest(S("apple")));
-    REQUIRE(2297466611U == hashkit_digest(&st, S("apple")));
-  }
-
-  SECTION("can set hash function") {
-    for (int f = HASHKIT_HASH_DEFAULT; f < HASHKIT_HASH_MAX; ++f) {
-      auto h = static_cast<hashkit_hash_algorithm_t>(f);
-
-      if (h == HASHKIT_HASH_CUSTOM) {
-        continue;
-      }
-      if (!libhashkit_has_algorithm(h)) {
-        WARN("hashkit algorithm not enabled: " << libhashkit_string_hash(h) << " (" << f << ")");
-        continue;
-      }
-
-      REQUIRE(HASHKIT_SUCCESS == stack.set_function(h));
-      REQUIRE(HASHKIT_SUCCESS == hashkit_set_function(&st, h));
-
-      SECTION("can digest set hash function") {
-        auto n = 0;
-
-        for (auto i : input) {
-          CHECK(output[f][n] == stack.digest(S(i)));
-          CHECK(output[f][n] == hashkit_digest(&st, S(i)));
-          CHECK(output[f][n] == libhashkit_digest(S(i), h));
-          ++n;
-        }
-      }
-    }
-  }
-
-  SECTION("is comparable") {
-    REQUIRE(*heap == stack);
-    REQUIRE(hashkit_compare(&st, hp));
-
-    stack.set_function(HASHKIT_HASH_MD5);
-    hashkit_set_function(&st, HASHKIT_HASH_MD5);
-
-    REQUIRE_FALSE(*heap == stack);
-    REQUIRE_FALSE(hashkit_compare(&st, hp));
-  }
-
-  delete heap;
-  hashkit_free(&st);
-  hashkit_free(hp);
-}
diff --git a/testing/lib.cpp b/testing/lib.cpp
deleted file mode 100644 (file)
index 571b56b..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-#include "lib/common.hpp"
-#include "lib/Cluster.hpp"
-#include "lib/Retry.hpp"
-#include "lib/Server.hpp"
-
-TEST_CASE("lib/Server") {
-  Server server{"memcached"};
-
-  SECTION("starts and listens") {
-
-    REQUIRE(server.start().has_value());
-
-    Retry server_is_listening{[&server] {
-      return server.isListening();
-    }};
-    REQUIRE(server_is_listening());
-
-    SECTION("stops") {
-
-      REQUIRE(server.stop());
-
-      SECTION("is waitable") {
-
-        REQUIRE(server.wait());
-
-        SECTION("stopped") {
-
-          REQUIRE_FALSE(server.check());
-        }
-      }
-    }
-  }
-}
-
-TEST_CASE("lib/Cluster") {
-  Cluster cluster{Server{"memcached", {
-    random_socket_or_port_arg(),
-  }}};
-
-  SECTION("starts and listens") {
-
-    REQUIRE(cluster.start());
-
-    Retry cluster_is_listening{[&cluster] {
-      return cluster.isListening();
-    }};
-    REQUIRE(cluster_is_listening());
-
-    SECTION("stops") {
-
-      cluster.stop();
-      cluster.wait();
-
-      SECTION("stopped") {
-
-        REQUIRE(cluster.isStopped());
-      }
-    }
-  }
-}
index 5d2ab42a7eb3e5f42fbf18078b3099e086d99bb1..4c6ea52c258f49c256d4987d8ce5336666bc64c3 100644 (file)
@@ -1,10 +1,11 @@
 #include "Cluster.hpp"
+#include "Retry.hpp"
 
 #include <sys/wait.h>
 
-Cluster::Cluster(Server &&serv, uint16_t cnt)
+Cluster::Cluster(Server serv, uint16_t cnt)
 : count{cnt}
-, proto{forward<Server>(serv)}
+, proto{move(serv)}
 {
   if (!cnt) {
     count = thread::hardware_concurrency()/2 ?: 4;
@@ -33,10 +34,7 @@ bool Cluster::start() {
   bool started = true;
 
   for (auto &server : cluster) {
-    auto pid = server.start();
-    if (pid.has_value()) {
-      pids[*pid] = &server;
-    } else {
+    if (!startServer(server)) {
       started = false;
     }
   }
@@ -62,23 +60,39 @@ bool Cluster::isStopped() {
 
 bool Cluster::isListening() {
   for (auto &server : cluster) {
-    if (!server.isListening()) {
-      // zombie?
-      auto old_pid = server.getPid();
-      if (server.tryWait()) {
-        pids.erase(old_pid);
-        auto pid = server.start();
-        if (pid.has_value()) {
-          pids[*pid] = &server;
+    Retry server_is_listening{[&] {
+      if (!server.isListening()) {
+        // zombie?
+        auto old_pid = server.getPid();
+        if (server.tryWait()) {
+          cerr << "zombie collected (old pid=" << old_pid << "): " << server << "\n";
+          pids.erase(old_pid);
+          // restart
+          startServer(server);
+        }
+        if (!server.isListening()) {
+          return false;
         }
       }
-      return server.isListening();
+      return true;
+    }};
+    if (!server_is_listening()) {
+      return false;
     }
   }
 
   return true;
 }
 
+bool Cluster::startServer(Server &server) {
+  auto pid = server.start();
+  if (pid.has_value()) {
+    pids[*pid] = &server;
+    return true;
+  }
+  return false;
+}
+
 void Cluster::wait() {
   siginfo_t inf;
 
@@ -94,4 +108,3 @@ void Cluster::wait() {
     }
   }
 }
-
index efc5eefdd93d15ba45fefbf9ee37f547536abf30..1bbc9d22325568a7c64369b068b1e64175377c26 100644 (file)
@@ -6,7 +6,7 @@
 class Cluster {
 public:
   explicit
-  Cluster(Server &&serv, uint16_t cnt = 0);
+  Cluster(Server serv, uint16_t cnt = 0);
 
   ~Cluster();
 
@@ -40,4 +40,6 @@ private:
   Server proto;
   vector<Server> cluster;
   map<pid_t, Server *> pids;
+
+  bool startServer(Server &server);
 };
index 40f2a963e1f1be0b68e25f89d966fc441e1d2169..9da89a960075b13b9df86397b062e4b08719506d 100644 (file)
@@ -69,3 +69,43 @@ void MemcachedCluster::enableBinaryProto(bool enable) {
   REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(&memc,
       MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, enable));
 }
+
+MemcachedCluster::MemcachedCluster(MemcachedCluster &&mc)
+    : cluster{Server{}}
+{
+  *this = move(mc);
+}
+
+MemcachedCluster &MemcachedCluster::operator=(MemcachedCluster &&mc) {
+  cluster = move(mc.cluster);
+  memcached_clone(&memc, &mc.memc);
+  returns = ReturnMatcher{&memc};
+  return *this;
+}
+
+ReturnMatcher &ReturnMatcher::operator=(ReturnMatcher &&rm) {
+  memc = exchange(rm.memc, nullptr);
+  expected = rm.expected;
+  return *this;
+}
+
+bool ReturnMatcher::match(const memcached_return_t &arg) const {
+  return arg == expected;
+}
+
+ReturnMatcher ReturnMatcher::success() {
+  return ReturnMatcher{memc};
+}
+
+ReturnMatcher ReturnMatcher::operator()(memcached_return_t expected_) {
+  return ReturnMatcher{memc, expected_};
+}
+
+string ReturnMatcher::describe() const {
+  return "is " + to_string(expected)
+         + "\n  actual: " + memcached_last_error_message(memc);
+}
+
+ReturnMatcher::ReturnMatcher(ReturnMatcher &&rm) {
+  *this = move(rm);
+}
index 685cf6fbef22564c1ad4625bbb2fc6ed8a4169d2..7286e69f9424c0141e549dcdac815347e5e45c09 100644 (file)
@@ -13,39 +13,26 @@ public:
   ReturnMatcher(const ReturnMatcher &) = default;
   ReturnMatcher &operator = (const ReturnMatcher &) = default;
 
-  ReturnMatcher(ReturnMatcher &&rm) {
-    *this = move(rm);
-  }
-  ReturnMatcher &operator = (ReturnMatcher &&rm) {
-    memc = exchange(rm.memc, nullptr);
-    expected = rm.expected;
-    return *this;
-  }
-
-  bool match(const memcached_return_t &arg) const override {
-    return arg == expected;
-  }
-
-  ReturnMatcher success() {
-    return ReturnMatcher{memc};
-  }
-
-  ReturnMatcher operator () (memcached_return_t expected_) {
-    return ReturnMatcher{memc, expected_};
-  }
+  ReturnMatcher(ReturnMatcher &&rm);
+  ReturnMatcher &operator = (ReturnMatcher &&rm);
+
+  bool match(const memcached_return_t &arg) const override;
+  ReturnMatcher success();
+  ReturnMatcher operator () (memcached_return_t expected_);
 
 protected:
-  string describe() const override {
-    return string{"is "}
-           + to_string(expected)
-           + " (" + memcached_strerror(memc, expected) + ") "
-           + "\n\tlast error: "
-      + memcached_last_error_message(memc);
-  }
+  string describe() const override;
 
 private:
   const memcached_st *memc;
-  memcached_return_t expected;
+  memcached_return_t expected{MEMCACHED_SUCCESS};
+};
+
+class LoneReturnMatcher {
+public:
+  ReturnMatcher returns;
+  explicit LoneReturnMatcher(const memcached_st *memc) : returns{memc}
+  {}
 };
 
 class MemcachedCluster {
@@ -62,18 +49,8 @@ public:
   MemcachedCluster(const MemcachedCluster &) = delete;
   MemcachedCluster &operator=(const MemcachedCluster &) = delete;
 
-  MemcachedCluster(MemcachedCluster &&mc)
-  : cluster{Server{}}
-  {
-    *this = move(mc);
-  };
-  MemcachedCluster &operator=(MemcachedCluster &&mc)
-  {
-    cluster = move(mc.cluster);
-    memcached_clone(&memc, &mc.memc);
-    returns = ReturnMatcher{&memc};
-    return *this;
-  }
+  MemcachedCluster(MemcachedCluster &&mc);;
+  MemcachedCluster &operator=(MemcachedCluster &&mc);
 
   void enableBinaryProto(bool enable = true);
   void flush();
index dd6b83c1feb804d92799330710655e4663129c1f..3630089f615c74fa49e4216df47f13dc9c6ad5b7 100644 (file)
@@ -1,9 +1,9 @@
 #include "Retry.hpp"
 
-Retry::Retry(predicate &&pred_, unsigned int max_, chrono::milliseconds sleep_for_)
+Retry::Retry(predicate pred_, unsigned int max_, chrono::milliseconds sleep_for_)
 : max{max_}
 , sleep_for{sleep_for_}
-, pred{forward<predicate>(pred_)}
+, pred{move(pred_)}
 {}
 
 bool Retry::operator()() {
index 26924b3abb2837f7c956b1d1911e3976ffe33d04..04c0466da384be1ef1312b1e03d7cc2d38f7d121 100644 (file)
@@ -7,7 +7,7 @@ public:
 
   using predicate = function<bool()>;
 
-  explicit Retry(predicate &&pred_, unsigned max_ = 10, chrono::milliseconds sleep_for_ = 20ms);
+  explicit Retry(predicate pred_, unsigned max_ = 10, chrono::milliseconds sleep_for_ = 100ms);
 
   bool operator () ();
 
index b3d0118baf9a51f14dbab99101e8f85355ef79f5..e5ef5ead68b12e4ddafb4e510941763f750c226d 100644 (file)
@@ -5,9 +5,9 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-Server::Server(string &&binary_, Server::argv_t &&args_)
-    : binary{forward<string>(binary_)}
-    , args{forward<argv_t>(args_)}
+Server::Server(string binary_, Server::argv_t args_)
+    : binary{move(binary_)}
+    , args{move(args_)}
 {}
 
 Server::~Server() {
index 5891dea27fb83dc3b0377e977c16aee1bfe924bf..bb4bfa7f9bfea4410fbfaf275d2cdca2b96891ae 100644 (file)
@@ -15,7 +15,7 @@ public:
   using argv_t = vector<variant<arg_t, arg_pair_t>>;
 
   explicit
-  Server(string &&binary_ = "false", argv_t && args_ = {});
+  Server(string binary_ = "false", argv_t args_ = {});
 
   ~Server();
 
@@ -65,3 +65,17 @@ private:
   vector<char *> createArgv();
   optional<string> handleArg(vector<char *> &arr, const string &arg, const arg_func_t &next_arg);
 };
+
+inline ostream &operator << (ostream &out, const socket_or_port_t sop) {
+  if (holds_alternative<string>(sop)) {
+    out << get<string>(sop);
+  } else {
+    out << get<int>(sop);
+  }
+  return out;
+}
+
+inline ostream &operator << (ostream &out, const Server &server) {
+  out << "Server{binary=" << server.getBinary() << ",pid=" << server.getPid() << ",conn=" << server.getSocketOrPort() << "}";
+  return out;
+}
index 16f5e186b0f3b56a6e6368fc872fbe5eeeb90407..04dd79d650dfe66633068e34ee2156b3ab9f24d5 100644 (file)
@@ -2,45 +2,6 @@
 #include "Connection.hpp"
 
 #include <cstdlib>
-#include <sys/random.h>
-#include <unistd.h>
-
-unsigned random_num(unsigned min, unsigned max) {
-  unsigned p;
-  getrandom(&p, sizeof(p), 0);
-  return (p % (max - min + 1)) + min;
-}
-
-unsigned random_port() {
-  retry:
-  int port = random_num(2<<9, 2<<15);
-  Connection conn(port);
-
-  if (!conn.open()) {
-    return port;
-  }
-  if (!conn.isOpen()) {
-    return port;
-  }
-  goto retry;
-}
-
-string random_socket() {
-  return "/tmp/libmc." + to_string(random_num(1, UINT32_MAX)) + "@" + to_string(getpid()) + ".sock";
-}
-
-string random_socket_or_port_string(const string &what) {
-  if (what == "-s") {
-    return random_socket();
-  }
-
-  return to_string(random_port());
-}
-
-string random_socket_or_port_flag(const string &binary) {
-  (void) binary;
-  return random_num(0, 1) ? "-p" : "-s";
-}
 
 const char *getenv_else(const char *var, const char *defval) {
   auto val = getenv(var);
index 6daa357beb07dbec1f2aa91478adb427d375db96..c1ef43ae9f27394b6acceb79b13e7196a0bca221 100644 (file)
@@ -11,7 +11,8 @@
 #include <variant>
 #include <vector>
 
-#include "../lib/catch.hpp"
+#include "testing/lib/catch.hpp"
+#include "testing/lib/random.hpp"
 
 #include "libmemcached/memcached.h"
 
@@ -22,29 +23,25 @@ using socket_or_port_t = variant<string, int>;
  * Useful macros for testing
  */
 #define S(s) (s),strlen(s)
+#define DECLARE_STREQUAL static auto strequal = equal_to<string>();
 #define LOOPED_SECTION(tests) \
   for (auto &[name, test] : tests) DYNAMIC_SECTION("test " << name)
-#define REQUIRE_SUCCESS(rc) REQUIRE_THAT(rc, test.returns.success())
-#define REQUIRE_RC(rc, call) REQUIRE_THAT(call, test.returns(rc))
-
+#define REQUIRE_SUCCESS(rc) do { \
+    INFO("expected: SUCCESS");   \
+    REQUIRE_THAT(rc, test.returns.success()); \
+  } while(0)
+#define REQUIRE_RC(rc, call) do { \
+    INFO("expected: " << memcached_strerror(nullptr, rc)); \
+    REQUIRE_THAT(call, test.returns(rc));                  \
+  } while(0)
 
 const char *getenv_else(const char *var, const char *defval);
-unsigned random_num(unsigned min, unsigned max);
-unsigned random_port();
-string random_socket();
-string random_socket_or_port_string(const string &what);
-string random_socket_or_port_flag(const string &binary);
-
-inline auto random_socket_or_port_arg() {
-  return make_pair(&random_socket_or_port_flag, &random_socket_or_port_string);
-}
 
 inline memcached_return_t fetch_all_results(memcached_st *memc, unsigned int &keys_returned, memcached_return_t &rc) {
   keys_returned = 0;
 
   memcached_result_st *result = nullptr;
-  while ((result = memcached_fetch_result(memc, result, &rc)))
-  {
+  while ((result = memcached_fetch_result(memc, result, &rc))) {
     REQUIRE(MEMCACHED_SUCCESS == rc);
     keys_returned += 1;
   }
@@ -78,16 +75,18 @@ public:
   }
 };
 
+template<class T>
 class Malloced {
-  void *ptr;
+  T *ptr;
 public:
-  Malloced(void *ptr_)
+  Malloced(T *ptr_)
   : ptr{ptr_}
   {}
   ~Malloced() {
-    free(ptr);
+    if(ptr)
+      free(ptr);
   }
-  void *operator *() {
+  auto operator *() {
     return ptr;
   }
 };
diff --git a/testing/lib/random.cpp b/testing/lib/random.cpp
new file mode 100644 (file)
index 0000000..a238c74
--- /dev/null
@@ -0,0 +1,77 @@
+#include "testing/lib/random.hpp"
+#include "testing/lib/Connection.hpp"
+
+#include <chrono>
+#include <random>
+
+#include <unistd.h> // getpid()
+
+
+template<typename T>
+enable_if_t<is_integral_v<T>, T> random_num(T min, T max) {
+  using namespace chrono;
+  using rnd = mt19937;
+  using dst = uniform_int_distribution<T>;
+
+  auto time = duration_cast<microseconds>(system_clock::now().time_since_epoch());
+  auto seed = static_cast<rnd::result_type>(time.count() % numeric_limits<T>::max());
+  auto rgen = rnd{seed};
+  return dst(min, max)(rgen);
+}
+
+unsigned random_port() {
+  retry:
+  auto port = random_num(2<<9, 2<<15);
+  Connection conn(port);
+
+  if (!conn.open()) {
+    return port;
+  }
+  if (!conn.isOpen()) {
+    return port;
+  }
+  goto retry;
+}
+
+string random_socket(const string &prefix) {
+  return prefix + to_string(random_num(1U, UINT32_MAX)) + "@" + to_string(getpid()) + ".sock";
+}
+
+string random_socket_or_port_string(const string &what) {
+  if (what == "-s") {
+    return random_socket();
+  }
+
+  return to_string(random_port());
+}
+
+string random_socket_or_port_flag(const string &binary) {
+  (void) binary;
+  return random_num(0, 1) ? "-p" : "-s";
+}
+
+char random_ascii(char min, char max) {
+  return static_cast<char>(random_num(min, max));
+}
+
+string random_ascii_string(size_t len, char min, char max) {
+  string s;
+  s.reserve(len + 1);
+
+  for (size_t rem = 0; rem < len; ++rem) {
+    s += random_ascii(min, max);
+  }
+
+  s[len] = '\0';
+  assert(strlen(s.c_str()) == s.size());
+
+  return s;
+}
+
+pair<string, string> random_ascii_pair(size_t minlen, size_t maxlen) {
+  return {
+      random_ascii_string(random_num(minlen, maxlen)),
+      random_ascii_string(random_num(minlen, maxlen))
+  };
+}
+
diff --git a/testing/lib/random.hpp b/testing/lib/random.hpp
new file mode 100644 (file)
index 0000000..208b09f
--- /dev/null
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstddef>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+using namespace std;
+
+using kv_pair = pair<string, string>;
+
+template<typename T>
+enable_if_t<is_integral_v<T>, T> random_num(T min, T max);
+
+unsigned random_port();
+
+char random_ascii(char min = '!', char max = '~');
+string random_ascii_string(size_t len, char min = '!', char max = '~');
+kv_pair random_ascii_pair(size_t minlen = 1<<2, size_t maxlen = 1<<10);
+
+template<template <typename> class Container>
+auto random_ascii_pairs(size_t count, size_t minlen = 1<<2, size_t maxlen = 1<<10) {
+  Container<kv_pair> v;
+
+  v.reserve(count);
+  for (size_t i = 0; i < count; ++i) {
+    v.emplace_back(random_ascii_pair(minlen, maxlen));
+  }
+
+  return v;
+}
+
+string random_socket(const string &prefix = "/tmp/libmc.");
+string random_socket_or_port_string(const string &what);
+string random_socket_or_port_flag(const string &binary);
+
+inline auto random_socket_or_port_arg() {
+  return make_pair(&random_socket_or_port_flag, &random_socket_or_port_string);
+}
diff --git a/testing/memcached/basic.cpp b/testing/memcached/basic.cpp
deleted file mode 100644 (file)
index 8e6cced..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "../lib/common.hpp"
-
-#include "libmemcached/is.h"
-
-TEST_CASE("memcached basic") {
-  memcached_st memc, *memc_ptr;
-
-  memc_ptr = memcached_create(&memc);
-
-  REQUIRE(memc_ptr);
-  REQUIRE(memc_ptr == &memc);
-
-  SECTION("can be cloned") {
-    memc_ptr = memcached_clone(nullptr, &memc);
-    REQUIRE(memc_ptr);
-    REQUIRE(memcached_is_allocated(memc_ptr));
-    memcached_free(memc_ptr);
-  }
-
-  SECTION("can be reset") {
-    memc_ptr = memcached_clone(nullptr, &memc);
-    REQUIRE(MEMCACHED_SUCCESS == memcached_reset(&memc));
-    REQUIRE_FALSE(memcached_is_allocated(&memc));
-    REQUIRE(MEMCACHED_SUCCESS == memcached_reset(memc_ptr));
-    REQUIRE(memcached_is_allocated(memc_ptr));
-    memcached_free(memc_ptr);
-  }
-}
diff --git a/testing/memcached/callbacks.cpp b/testing/memcached/callbacks.cpp
deleted file mode 100644 (file)
index 63cc0ed..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "../lib/common.hpp"
-
-static memcached_return_t delete_trigger(memcached_st *, const char *, size_t) {
-  return MEMCACHED_SUCCESS;
-}
-
-TEST_CASE("memcached callbacks") {
-  void *fptr = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(&delete_trigger));
-  MemcachedPtr memc;
-
-  SECTION("set delete trigger") {
-    REQUIRE(MEMCACHED_SUCCESS == memcached_callback_set(*memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, fptr));
-  }
-
-  SECTION("set delete trigger fails w/ NOREPLY") {
-    REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(*memc, MEMCACHED_BEHAVIOR_NOREPLY, true));
-    REQUIRE_FALSE(MEMCACHED_SUCCESS == memcached_callback_set(*memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, fptr));
-  }
-}
diff --git a/testing/memcached/dump.cpp b/testing/memcached/dump.cpp
deleted file mode 100644 (file)
index 6add566..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#include "../lib/common.hpp"
-#include "../lib/MemcachedCluster.hpp"
-
-memcached_return_t dump_cb(const memcached_st *, const char *, size_t, void *ctx) {
-  auto *c = reinterpret_cast<size_t *>(ctx);
-  ++(*c);
-  return MEMCACHED_SUCCESS;
-}
-
-TEST_CASE("memcached dump") {
-  pair<string, MemcachedCluster> tests[]{
-    {"mixed", MemcachedCluster::mixed()},
-    {"network", MemcachedCluster::network()},
-    {"socket", MemcachedCluster::socket()}
-  };
-
-  LOOPED_SECTION(tests) {
-    auto memc = &test.memc;
-
-    SECTION("prepared with 64 KVs") {
-      for (int i = 0; i < 64; ++i) {
-        char key[8];
-        int len = snprintf(key, sizeof(key) - 1, "k_%d", i);
-
-        CHECKED_IF(len) {
-          REQUIRE_SUCCESS(memcached_set(memc, key, len, key, len, 0, 0));
-        }
-      }
-
-      memcached_quit(memc);
-
-      // let memcached sort itself
-      using namespace chrono_literals;
-      this_thread::sleep_for(3s);
-
-      SECTION("dumps 64 KVs") {
-        size_t counter = 0;
-        memcached_dump_fn fn[] = {dump_cb};
-
-        REQUIRE_SUCCESS(memcached_dump(memc, fn, &counter, 1));
-        REQUIRE(counter == 64);
-      }
-    }
-  }
-}
diff --git a/testing/memcached/encoding_key.cpp b/testing/memcached/encoding_key.cpp
deleted file mode 100644 (file)
index a924b4a..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "../lib/common.hpp"
-#include "../lib/MemcachedCluster.hpp"
-
-#define TEST_KEY S("test")
-#define INITIAL_VAL S("initial")
-#define REPLACED_VAL S("replaced")
-
-static inline void check(memcached_st *enc, memcached_st *raw, const char *val, size_t len) {
-  memcached_return_t enc_rc, raw_rc;
-  size_t enc_length, raw_length;
-  Malloced enc_value(memcached_get(enc, TEST_KEY, &enc_length, nullptr, &enc_rc));
-  Malloced raw_value(memcached_get(raw, TEST_KEY, &raw_length, nullptr, &raw_rc));
-
-  REQUIRE(enc_rc == MEMCACHED_SUCCESS);
-  REQUIRE(raw_rc == MEMCACHED_SUCCESS);
-  REQUIRE_FALSE(enc_length == raw_length);
-  REQUIRE(memcmp(*raw_value, *enc_value, raw_length));
-  REQUIRE(enc_length == len);
-  REQUIRE_FALSE(memcmp(val, *enc_value, enc_length));
-}
-
-TEST_CASE("memcached encoding_key") {
-  pair<string, MemcachedCluster> tests[]{
-    {"network", MemcachedCluster::network()},
-    {"socket", MemcachedCluster::socket()}
-  };
-
-  LOOPED_SECTION(tests) {
-    auto memc = &test.memc;
-
-    SECTION("accepts encoding key") {
-      MemcachedPtr copy(memc);
-
-      REQUIRE_SUCCESS(memcached_set_encoding_key(memc, S(__func__)));
-
-      SECTION("sets encoded value") {
-        REQUIRE_SUCCESS(memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
-
-        SECTION("gets encoded value") {
-          check(memc, &copy.memc, INITIAL_VAL);
-        }
-
-        SECTION("cloned gets encoded value") {
-          MemcachedPtr dupe(memc);
-
-          check(&dupe.memc, &copy.memc, INITIAL_VAL);
-        }
-      }
-
-      SECTION("adds encoded value") {
-
-        REQUIRE_SUCCESS(memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
-        REQUIRE_RC(MEMCACHED_NOTSTORED, memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0));
-
-        check(memc, &copy.memc, INITIAL_VAL);
-
-        test.flush();
-
-        REQUIRE_SUCCESS(memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0));
-
-        SECTION("gets encoded value") {
-          check(memc, &copy.memc, REPLACED_VAL);
-        }
-      }
-
-      SECTION("replaces encoded value") {
-        REQUIRE_SUCCESS(memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
-
-        check(memc, &copy.memc, INITIAL_VAL);
-
-        REQUIRE_SUCCESS(memcached_replace(memc, TEST_KEY, REPLACED_VAL, 0, 0));
-
-        SECTION("gets encoded value") {
-          check(memc, &copy.memc, REPLACED_VAL);
-        }
-      }
-
-      SECTION("unsupported") {
-        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_increment(memc, TEST_KEY, 0, nullptr));
-        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_decrement(memc, TEST_KEY, 0, nullptr));
-        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_increment_with_initial(memc, TEST_KEY, 0, 0, 0, nullptr));
-        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_decrement_with_initial(memc, TEST_KEY, 0, 0, 0, nullptr));
-        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_append(memc, TEST_KEY, REPLACED_VAL, 0, 0));
-        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_prepend(memc, TEST_KEY, REPLACED_VAL, 0, 0));
-      }
-    }
-  }
-}
diff --git a/testing/memcached/exist.cpp b/testing/memcached/exist.cpp
deleted file mode 100644 (file)
index 02ec0de..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#include "../lib/common.hpp"
-#include "../lib/MemcachedCluster.hpp"
-
-TEST_CASE("memcached exist") {
-  pair<string, MemcachedCluster> tests[]{
-    {"bin_mixed", MemcachedCluster::mixed()},
-    {"network", MemcachedCluster::network()},
-    {"socket", MemcachedCluster::socket()}
-  };
-
-  tests[0].second.enableBinaryProto();
-
-  LOOPED_SECTION(tests) {
-    auto memc = &test.memc;
-
-    SECTION("initial not found") {
-      REQUIRE_RC(MEMCACHED_NOTFOUND,memcached_exist(memc, S("frog")));
-    }
-
-    SECTION("set found") {
-        REQUIRE_SUCCESS(memcached_set(memc, S("frog"), S("frog"), 0, 0));
-        REQUIRE_SUCCESS(memcached_exist(memc, S("frog")));
-
-        SECTION("deleted not found") {
-          REQUIRE_SUCCESS(memcached_delete(memc, S("frog"), 0));
-          REQUIRE_RC(MEMCACHED_NOTFOUND, memcached_exist(memc, S("frog")));
-        }
-    }
-
-    SECTION("by key") {
-      SECTION("initial not found") {
-        REQUIRE_RC(MEMCACHED_NOTFOUND, memcached_exist_by_key(memc, S("master"), S("frog")));
-      }
-
-      SECTION("set found") {
-        REQUIRE_SUCCESS(memcached_set_by_key(memc, S("master"), S("frog"), S("frog"), 0, 0));
-        REQUIRE_SUCCESS(memcached_exist_by_key(memc, S("master"), S("frog")));
-
-        SECTION("deleted not found") {
-          REQUIRE_SUCCESS(memcached_delete_by_key(memc, S("master"), S("frog"), 0));
-          REQUIRE_RC(MEMCACHED_NOTFOUND, memcached_exist_by_key(memc, S("master"), S("frog")));
-        }
-      }
-    }
-  }
-
-}
diff --git a/testing/memcached/haldenbrand.cpp b/testing/memcached/haldenbrand.cpp
deleted file mode 100644 (file)
index 44abf1e..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-#include "../lib/common.hpp"
-#include "../lib/MemcachedCluster.hpp"
-
-/* Test case provided by Cal Haldenbrand */
-
-#define HALDENBRAND_KEY_COUNT 3000U // * 1024576
-#define HALDENBRAND_FLAG_KEY 99 // * 1024576
-
-#include <cstdlib>
-
-TEST_CASE("memcached haldenbrand nblock_tcp_ndelay") {
-  pair<string, MemcachedCluster> tests[] = {
-      {"network", MemcachedCluster::network()},
-  };
-  
-  for (auto &[name, test] : tests) {
-    REQUIRE_SUCCESS(memcached_behavior_set(&test.memc, MEMCACHED_BEHAVIOR_NO_BLOCK, true));
-    REQUIRE_SUCCESS(memcached_behavior_set(&test.memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, true));
-  }
-  
-  LOOPED_SECTION(tests) {
-    auto memc = &test.memc;
-
-    /* We just keep looking at the same values over and over */
-    srandom(10);
-
-    /* add key */
-    unsigned long long total = 0;
-    for (uint32_t x = 0; total < 20 * 1024576; x++) {
-      uint32_t size = (uint32_t) (rand() % (5 * 1024)) + 400;
-      char randomstuff[6 * 1024];
-      memset(randomstuff, 0, 6 * 1024);
-      REQUIRE(size < 6 * 1024); /* Being safe here */
-
-      for (uint32_t j = 0; j < size; j++) {
-        randomstuff[j] = (signed char) ((rand() % 26) + 97);
-      }
-
-      total += size;
-      char key[MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1];
-      int key_length = snprintf(key, sizeof(key), "%u", x);
-      REQUIRE_SUCCESS(memcached_set(memc, key, key_length,
-          randomstuff, strlen(randomstuff),
-          time_t(0), HALDENBRAND_FLAG_KEY
-      ));
-    }
-    REQUIRE(total > HALDENBRAND_KEY_COUNT);
-
-    size_t total_value_length = 0;
-    for (uint32_t x = 0, errors = 0; total_value_length < 24576; x++) {
-      uint32_t flags = 0;
-      size_t val_len = 0;
-
-      char key[MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1];
-      int key_length = snprintf(key, sizeof(key), "%u", x);
-
-      memcached_return_t rc;
-      char *getval = memcached_get(memc, key, key_length, &val_len, &flags, &rc);
-      if (memcached_failed(rc)) {
-        if (rc == MEMCACHED_NOTFOUND) {
-          errors++;
-        } else {
-          REQUIRE(rc);
-        }
-
-        continue;
-      }
-      REQUIRE(uint32_t(HALDENBRAND_FLAG_KEY) == flags);
-      REQUIRE(getval);
-
-      total_value_length += val_len;
-      errors = 0;
-      ::free(getval);
-    }
-
-
-    std::vector<size_t> key_lengths;
-    key_lengths.resize(HALDENBRAND_KEY_COUNT);
-    std::vector<char *> keys;
-    keys.resize(key_lengths.size());
-    for (uint32_t x = 0; x < key_lengths.size(); x++) {
-      char key[MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1];
-      int key_length = snprintf(key, sizeof(key), "%u", x);
-      REQUIRE(key_length > 0);
-      REQUIRE(key_length < MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1);
-      keys[x] = strdup(key);
-      key_lengths[x] = key_length;
-    }
-
-    REQUIRE_SUCCESS(memcached_mget(memc, &keys[0], &key_lengths[0], key_lengths.size()));
-
-    unsigned int keys_returned;
-    REQUIRE(memcached_success(fetch_all_results(memc, keys_returned)));
-    REQUIRE(HALDENBRAND_KEY_COUNT == keys_returned);
-
-    for (auto key : keys) {
-      free(key);
-    }
-  }
-}
diff --git a/testing/memcached/servers.cpp b/testing/memcached/servers.cpp
deleted file mode 100644 (file)
index 9c32416..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#include "../lib/common.hpp"
-
-TEST_CASE("memcached servers") {
-  SECTION("memcached_servers_parse") {
-    SECTION("does not leak memory") {
-      memcached_server_st *s = memcached_servers_parse("1.2.3.4:1234");
-      REQUIRE(s);
-      memcached_server_free(s);
-    }
-  }
-
-  SECTION("memcached_server_list") {
-    SECTION("append with weight - all zeros") {
-      memcached_server_st *sl = memcached_server_list_append_with_weight(
-          nullptr, nullptr, 0, 0, 0);
-      REQUIRE(sl);
-      memcached_server_list_free(sl);
-    }
-    SECTION("append with weight - host set only") {
-      memcached_server_st *sl = memcached_server_list_append_with_weight(
-          nullptr, "localhost", 0, 0, 0);
-      REQUIRE(sl);
-      memcached_server_list_free(sl);
-    }
-    SECTION("append with weight - error set only") {
-      memcached_return_t rc;
-      memcached_server_st *sl = memcached_server_list_append_with_weight(
-          nullptr, nullptr, 0, 0, &rc);
-      REQUIRE(sl);
-      REQUIRE(MEMCACHED_SUCCESS == rc);
-      memcached_server_list_free(sl);
-    }
-  }
-
-  SECTION("no configured servers") {
-    MemcachedPtr memc;
-
-    REQUIRE(MEMCACHED_NO_SERVERS == memcached_increment(*memc, S("key"), 1, nullptr));
-  }
-}
diff --git a/testing/tests/hashkit/basic.cpp b/testing/tests/hashkit/basic.cpp
new file mode 100644 (file)
index 0000000..a5a4e8f
--- /dev/null
@@ -0,0 +1,217 @@
+#include "testing/lib/common.hpp"
+
+#include "libhashkit-1.0/hashkit.hpp"
+
+static const char *input[] = {
+    "apple",
+    "beat",
+    "carrot",
+    "daikon",
+    "eggplant",
+    "flower",
+    "green",
+    "hide",
+    "ick",
+    "jack",
+    "kick",
+    "lime",
+    "mushrooms",
+    "nectarine",
+    "orange",
+    "peach",
+    "quant",
+    "ripen",
+    "strawberry",
+    "tang",
+    "up",
+    "volumne",
+    "when",
+    "yellow",
+    "zip",
+};
+
+static const uint32_t output[][sizeof(input)/sizeof(*input)] = {
+          // one_at_a_time
+          { 2297466611U, 3902465932U, 469785835U, 1937308741U,
+          261917617U, 3785641677U, 1439605128U, 1649152283U,
+          1493851484U, 1246520657U, 2221159044U, 1973511823U,
+          384136800U, 214358653U, 2379473940U, 4269788650U,
+          2864377005U, 2638630052U, 427683330U, 990491717U,
+          1747111141U, 792127364U, 2599214128U, 2553037199U,
+          2509838425U },
+
+          // md5
+          { 3195025439U, 2556848621U, 3724893440U, 3332385401U,
+          245758794U, 2550894432U, 121710495U, 3053817768U,
+          1250994555U, 1862072655U, 2631955953U, 2951528551U,
+          1451250070U, 2820856945U, 2060845566U, 3646985608U,
+          2138080750U, 217675895U, 2230934345U, 1234361223U,
+          3968582726U, 2455685270U, 1293568479U, 199067604U,
+          2042482093U },
+
+          // crc
+          { 10542U, 22009U, 14526U, 19510U, 19432U, 10199U, 20634U,
+          9369U, 11511U, 10362U, 7893U, 31289U, 11313U, 9354U,
+          7621U, 30628U, 15218U, 25967U, 2695U, 9380U,
+          17300U, 28156U, 9192U, 20484U, 16925U },
+
+          // fnv1_64
+          { 473199127U, 4148981457U, 3971873300U, 3257986707U,
+          1722477987U, 2991193800U, 4147007314U, 3633179701U,
+          1805162104U, 3503289120U, 3395702895U, 3325073042U,
+          2345265314U, 3340346032U, 2722964135U, 1173398992U,
+          2815549194U, 2562818319U, 224996066U, 2680194749U,
+          3035305390U, 246890365U, 2395624193U, 4145193337U,
+          1801941682U },
+
+          // fnv1a_64
+          { 1488911807U, 2500855813U, 1510099634U, 1390325195U,
+          3647689787U, 3241528582U, 1669328060U, 2604311949U,
+          734810122U, 1516407546U, 560948863U, 1767346780U,
+          561034892U, 4156330026U, 3716417003U, 3475297030U,
+          1518272172U, 227211583U, 3938128828U, 126112909U,
+          3043416448U, 3131561933U, 1328739897U, 2455664041U,
+          2272238452U },
+
+          // fnv1_32
+          { 67176023U, 1190179409U, 2043204404U, 3221866419U,
+          2567703427U, 3787535528U, 4147287986U, 3500475733U,
+          344481048U, 3865235296U, 2181839183U, 119581266U,
+          510234242U, 4248244304U, 1362796839U, 103389328U,
+          1449620010U, 182962511U, 3554262370U, 3206747549U,
+          1551306158U, 4127558461U, 1889140833U, 2774173721U,
+          1180552018U },
+
+          // fnv1a_32
+          { 280767167U, 2421315013U, 3072375666U, 855001899U,
+          459261019U, 3521085446U, 18738364U, 1625305005U,
+          2162232970U, 777243802U, 3323728671U, 132336572U,
+          3654473228U, 260679466U, 1169454059U, 2698319462U,
+          1062177260U, 235516991U, 2218399068U, 405302637U,
+          1128467232U, 3579622413U, 2138539289U, 96429129U,
+          2877453236U },
+
+          // hsieh
+#ifdef HAVE_HSIEH_HASH
+          { 3738850110U, 3636226060U, 3821074029U, 3489929160U, 3485772682U, 80540287U,
+          1805464076U, 1895033657U, 409795758U, 979934958U, 3634096985U, 1284445480U,
+          2265380744U, 707972988U, 353823508U, 1549198350U, 1327930172U, 9304163U,
+          4220749037U, 2493964934U, 2777873870U, 2057831732U, 1510213931U, 2027828987U,
+          3395453351U },
+#else
+          {  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+#endif
+
+          // murmur
+#ifdef HAVE_MURMUR_HASH
+          // murmur
+          { 4142305122U, 734504955U, 3802834688U, 4076891445U,
+          387802650U, 560515427U, 3274673488U, 3150339524U,
+          1527441970U, 2728642900U, 3613992239U, 2938419259U,
+          2321988328U, 1145154116U, 4038540960U, 2224541613U,
+          264013145U, 3995512858U, 2400956718U, 2346666219U,
+          926327338U, 442757446U, 1770805201U, 560483147U,
+          3902279934U },
+          // murmur3
+          { 1120212521U, 1448785489U, 4186307405U, 2686268514U,
+          444808887U, 221750260U, 3074673162U, 1946933257U,
+          2826416675U, 2430719166U, 3200429559U, 297894347U,
+          732888124U, 4050076964U, 3298336176U, 1336207361U,
+          810553576U, 3748182674U, 3860119212U, 3439537197U,
+          3044240981U, 1464271804U, 3896193724U, 2915115798U,
+          1702843840U },
+#else
+          { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+          { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+#endif
+
+          // jenkins
+          { 1442444624U, 4253821186U, 1885058256U, 2120131735U,
+          3261968576U, 3515188778U, 4232909173U, 4288625128U,
+          1812047395U, 3689182164U, 2502979932U, 1214050606U,
+          2415988847U, 1494268927U, 1025545760U, 3920481083U,
+          4153263658U, 3824871822U, 3072759809U, 798622255U,
+          3065432577U, 1453328165U, 2691550971U, 3408888387U,
+          2629893356U }
+};
+
+TEST_CASE("hashkit") {
+  hashkit_st st, *hp = hashkit_create(nullptr);
+  Hashkit stack;
+  Hashkit *heap = new Hashkit;
+
+  REQUIRE(hashkit_create(&st));
+  REQUIRE(hp);
+
+  SECTION("can copy") {
+    Hashkit stack_copy(stack);
+    Hashkit *heap_copy(heap);
+    hashkit_st st_copy, *st_ptr;
+
+    (void) stack_copy;
+    (void) heap_copy;
+
+    st_ptr = hashkit_clone(&st_copy, &st);
+    REQUIRE(st_ptr == &st_copy);
+    REQUIRE(hashkit_compare(st_ptr, &st_copy));
+
+    SUCCEED("OK");
+  }
+
+  SECTION("can assign") {
+    Hashkit stack_copy;
+
+    stack_copy = stack;
+    (void) stack_copy;
+
+    SUCCEED("OK");
+  }
+
+  SECTION("can digest default") {
+    REQUIRE(2297466611U == stack.digest(S("apple")));
+    REQUIRE(2297466611U == hashkit_digest(&st, S("apple")));
+  }
+
+  SECTION("can set hash function") {
+    for (int f = HASHKIT_HASH_DEFAULT; f < HASHKIT_HASH_MAX; ++f) {
+      auto h = static_cast<hashkit_hash_algorithm_t>(f);
+
+      if (h == HASHKIT_HASH_CUSTOM) {
+        continue;
+      }
+      if (!libhashkit_has_algorithm(h)) {
+        WARN("hashkit algorithm not enabled: " << libhashkit_string_hash(h) << " (" << f << ")");
+        continue;
+      }
+
+      REQUIRE(HASHKIT_SUCCESS == stack.set_function(h));
+      REQUIRE(HASHKIT_SUCCESS == hashkit_set_function(&st, h));
+
+      SECTION("can digest set hash function") {
+        auto n = 0;
+
+        for (auto i : input) {
+          CHECK(output[f][n] == stack.digest(S(i)));
+          CHECK(output[f][n] == hashkit_digest(&st, S(i)));
+          CHECK(output[f][n] == libhashkit_digest(S(i), h));
+          ++n;
+        }
+      }
+    }
+  }
+
+  SECTION("is comparable") {
+    REQUIRE(*heap == stack);
+    REQUIRE(hashkit_compare(&st, hp));
+
+    stack.set_function(HASHKIT_HASH_MD5);
+    hashkit_set_function(&st, HASHKIT_HASH_MD5);
+
+    REQUIRE_FALSE(*heap == stack);
+    REQUIRE_FALSE(hashkit_compare(&st, hp));
+  }
+
+  delete heap;
+  hashkit_free(&st);
+  hashkit_free(hp);
+}
diff --git a/testing/tests/lib.cpp b/testing/tests/lib.cpp
new file mode 100644 (file)
index 0000000..12e6665
--- /dev/null
@@ -0,0 +1,60 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/Cluster.hpp"
+#include "testing/lib/Retry.hpp"
+#include "testing/lib/Server.hpp"
+
+TEST_CASE("lib/Server") {
+  Server server{"memcached"};
+
+  SECTION("starts and listens") {
+
+    REQUIRE(server.start().has_value());
+
+    Retry server_is_listening{[&server] {
+      return server.isListening();
+    }};
+    REQUIRE(server_is_listening());
+
+    SECTION("stops") {
+
+      REQUIRE(server.stop());
+
+      SECTION("is waitable") {
+
+        REQUIRE(server.wait());
+
+        SECTION("stopped") {
+
+          REQUIRE_FALSE(server.check());
+        }
+      }
+    }
+  }
+}
+
+TEST_CASE("lib/Cluster") {
+  Cluster cluster{Server{"memcached", {
+    random_socket_or_port_arg(),
+  }}};
+
+  SECTION("starts and listens") {
+
+    REQUIRE(cluster.start());
+
+    Retry cluster_is_listening{[&cluster] {
+      return cluster.isListening();
+    }};
+    REQUIRE(cluster_is_listening());
+
+    SECTION("stops") {
+
+      cluster.stop();
+      cluster.wait();
+
+      SECTION("stopped") {
+
+        REQUIRE(cluster.isStopped());
+      }
+    }
+  }
+}
diff --git a/testing/tests/memcached/append.cpp b/testing/tests/memcached/append.cpp
new file mode 100644 (file)
index 0000000..96244df
--- /dev/null
@@ -0,0 +1,63 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+DECLARE_STREQUAL;
+
+TEST_CASE("memcached append") {
+  pair<string, MemcachedCluster> tests[] = {
+      {"bin_mixed", MemcachedCluster::mixed()},
+      {"mixed", MemcachedCluster::mixed()}
+  };
+
+  tests[0].second.enableBinaryProto();
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    SECTION("text") {
+      const char *values[] = {
+          "one", "two", "three", "four"
+      };
+
+      for (auto key : values) {
+        string cmp{key};
+
+        REQUIRE_SUCCESS(memcached_set(memc, S(key), S(key), 0, 0));
+        for (auto value : values) {
+          REQUIRE_SUCCESS(memcached_append(memc, S(key), S(value), 0, 0));
+          cmp += value;
+        }
+
+        memcached_return_t rc;
+        size_t len;
+        uint32_t flags;
+        Malloced got(memcached_get(memc, S(key), &len, &flags, &rc));
+
+        REQUIRE(strequal(cmp, *got));
+        REQUIRE_SUCCESS(rc);
+      }
+    }
+
+    SECTION("bytes") {
+        vector<uint32_t> store_list{ 23, 56, 499, 98, 32847, 0 };
+        const size_t raw_len = sizeof(decltype(store_list)::value_type) * store_list.size();
+        const char *raw_ptr = reinterpret_cast<char *>(store_list.data());
+
+        REQUIRE_SUCCESS(memcached_set(memc, S(__func__), nullptr, 0, 0, 0));
+
+        for (auto item : store_list) {
+          auto val = reinterpret_cast<char *>(&item);
+          REQUIRE_SUCCESS(memcached_append(memc, S(__func__), val, sizeof(item), 0, 0));
+        }
+
+        memcached_return_t rc;
+        size_t len;
+        uint32_t flags;
+        Malloced got(memcached_get(memc, S(__func__), &len, &flags, &rc));
+
+        REQUIRE(len == raw_len);
+        REQUIRE(strequal({raw_ptr, raw_len}, string{*got, len}));
+        REQUIRE_SUCCESS(rc);
+    }
+  }
+}
diff --git a/testing/tests/memcached/basic.cpp b/testing/tests/memcached/basic.cpp
new file mode 100644 (file)
index 0000000..687ee9e
--- /dev/null
@@ -0,0 +1,33 @@
+#include "testing/lib/common.hpp"
+
+#include "libmemcached/is.h"
+
+TEST_CASE("memcached basic") {
+  SECTION("library") {
+    REQUIRE(string(LIBMEMCACHED_VERSION_STRING) == memcached_lib_version());
+  }
+  SECTION("memcached_st") {
+    memcached_st memc, *memc_ptr;
+
+    memc_ptr = memcached_create(&memc);
+
+    REQUIRE(memc_ptr);
+    REQUIRE(memc_ptr == &memc);
+
+    SECTION("can be cloned") {
+      memc_ptr = memcached_clone(nullptr, &memc);
+      REQUIRE(memc_ptr);
+      REQUIRE(memcached_is_allocated(memc_ptr));
+      memcached_free(memc_ptr);
+    }
+
+    SECTION("can be reset") {
+      memc_ptr = memcached_clone(nullptr, &memc);
+      REQUIRE(MEMCACHED_SUCCESS == memcached_reset(&memc));
+      REQUIRE_FALSE(memcached_is_allocated(&memc));
+      REQUIRE(MEMCACHED_SUCCESS == memcached_reset(memc_ptr));
+      REQUIRE(memcached_is_allocated(memc_ptr));
+      memcached_free(memc_ptr);
+    }
+  }
+}
diff --git a/testing/tests/memcached/callbacks.cpp b/testing/tests/memcached/callbacks.cpp
new file mode 100644 (file)
index 0000000..cfa1f4f
--- /dev/null
@@ -0,0 +1,118 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+static memcached_return_t delete_trigger(memcached_st *, const char *, size_t) {
+  return MEMCACHED_SUCCESS;
+}
+
+static memcached_return_t read_through_trigger(memcached_st *, char *, size_t, memcached_result_st *result) {
+  return memcached_result_set_value(result, S("updated by read through trigger"));
+}
+
+static memcached_return_t clone_callback(memcached_st *, memcached_st *) {
+  return MEMCACHED_SUCCESS;
+}
+
+static memcached_return_t cleanup_callback(memcached_st *) {
+  return MEMCACHED_SUCCESS;
+}
+
+TEST_CASE("memcached callbacks") {
+  auto test{MemcachedCluster::mixed()};
+  auto memc = &test.memc;
+  memcached_return_t rc;
+
+  SECTION("user data") {
+    int temp = 123;
+    void *data = &temp;
+
+    REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_USER_DATA, data));
+    REQUIRE(data == memcached_callback_get(memc, MEMCACHED_CALLBACK_USER_DATA, &rc));
+    REQUIRE_SUCCESS(rc);
+  }
+
+  SECTION("delete callback") {
+    void *dptr = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(&delete_trigger));
+
+    REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, dptr));
+    REQUIRE(memcached_callback_get(memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, &rc) == dptr);
+    REQUIRE_SUCCESS(rc);
+
+    SECTION("fails w/ NOREPLY") {
+      REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, true));
+      REQUIRE_RC(MEMCACHED_INVALID_ARGUMENTS, memcached_callback_set(memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, dptr));
+    }
+  }
+
+  SECTION("get_failure callback") {
+    void *gptr = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(&read_through_trigger));
+    Malloced empty(memcached_get(memc, S(__func__), nullptr, nullptr, &rc));
+    REQUIRE_FALSE(*empty);
+    REQUIRE_RC(MEMCACHED_NOTFOUND, rc);
+
+    REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_GET_FAILURE, gptr));
+    REQUIRE(gptr == memcached_callback_get(memc, MEMCACHED_CALLBACK_GET_FAILURE, &rc));
+    REQUIRE_SUCCESS(rc);
+
+    for (int twice = 0; twice < 2; ++twice) {
+      uint32_t flags;
+      size_t len;
+      Malloced val(memcached_get(memc, S(__func__), &len, &flags, &rc));
+
+      REQUIRE_SUCCESS(rc);
+      REQUIRE(string("updated by read through trigger") == string(*val, len));
+      REQUIRE_FALSE((*val)[len]);
+    }
+
+    SECTION("clone callback") {
+      void *cptr = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(&clone_callback));
+
+      REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_CLONE_FUNCTION, cptr));
+      REQUIRE(cptr == memcached_callback_get(memc, MEMCACHED_CALLBACK_CLONE_FUNCTION, &rc));
+      REQUIRE_SUCCESS(rc);
+    }
+
+    SECTION("cleanup callback") {
+      void *cptr = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(cleanup_callback));
+
+      REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_CLEANUP_FUNCTION, cptr));
+      REQUIRE(cptr == memcached_callback_get(memc, MEMCACHED_CALLBACK_CLEANUP_FUNCTION, &rc));
+      REQUIRE_SUCCESS(rc);
+    }
+
+    SECTION("namespace") {
+      void *ns;
+
+      REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, "ns"));
+      ns = memcached_callback_get(memc, MEMCACHED_CALLBACK_NAMESPACE, &rc);
+      REQUIRE_SUCCESS(rc);
+      REQUIRE("ns"s == static_cast<char *>(ns));
+
+      REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, nullptr));
+      ns = memcached_callback_get(memc, MEMCACHED_CALLBACK_NAMESPACE, &rc);
+      REQUIRE_SUCCESS(rc);
+      REQUIRE(nullptr == ns);
+
+
+      uint64_t binary = GENERATE(0, 1);
+      REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, binary));
+
+      DYNAMIC_SECTION("too long (binary=" << binary << ")") {
+        string blob;
+
+        blob = random_ascii_string(MEMCACHED_MAX_NAMESPACE-1, '@', 'Z');
+        REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, blob.c_str()));
+
+        blob = random_ascii_string(MEMCACHED_MAX_NAMESPACE, '@', 'Z');
+        REQUIRE_RC(MEMCACHED_KEY_TOO_BIG,memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, blob.c_str()));
+      }
+
+      DYNAMIC_SECTION("verify key (binary=" << binary << ")") {
+        REQUIRE_RC(binary ? MEMCACHED_INVALID_ARGUMENTS : MEMCACHED_SUCCESS,
+            memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_VERIFY_KEY, 1));
+        REQUIRE_RC(binary ? MEMCACHED_SUCCESS : MEMCACHED_BAD_KEY_PROVIDED,
+            memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, "with spaces"));
+      }
+    }
+  }
+}
diff --git a/testing/tests/memcached/cas.cpp b/testing/tests/memcached/cas.cpp
new file mode 100644 (file)
index 0000000..7335878
--- /dev/null
@@ -0,0 +1,41 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached cas") {
+  pair<string, MemcachedCluster> tests[] = {
+      {"network", MemcachedCluster::network()},
+      {"socket", MemcachedCluster::socket()}
+  };
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+    const char *keys[2] = {__func__, NULL};
+    size_t keylengths[2] = {strlen(__func__), 0};
+
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, true));
+    REQUIRE_SUCCESS(memcached_set(memc, S(__func__), S("we the people"), (time_t) 0, (uint32_t) 0));
+    REQUIRE_SUCCESS(memcached_mget(memc, keys, keylengths, 1));
+
+    memcached_result_st *results = memcached_result_create(memc, nullptr);
+    REQUIRE(results);
+
+    memcached_return_t rc;
+    results = memcached_fetch_result(memc, results, &rc);
+    REQUIRE(results);
+    REQUIRE_SUCCESS(rc);
+
+    REQUIRE(memcached_result_cas(results));
+    REQUIRE("we the people"s == string(memcached_result_value(results), memcached_result_length(results)));
+
+    uint64_t cas = memcached_result_cas(results);
+    REQUIRE(memcached_success(memcached_cas(memc, S(__func__), S("change the value"), 0, 0, cas)));
+
+    /*
+     * The item will have a new cas value, so try to set it again with the old
+     * value. This should fail!
+     */
+    REQUIRE_RC(MEMCACHED_DATA_EXISTS, memcached_cas(memc, S(__func__), S("change the value"), 0, 0, cas));
+
+    memcached_result_free(results);
+  }
+}
diff --git a/testing/tests/memcached/dump.cpp b/testing/tests/memcached/dump.cpp
new file mode 100644 (file)
index 0000000..9956b22
--- /dev/null
@@ -0,0 +1,45 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+memcached_return_t dump_cb(const memcached_st *, const char *, size_t, void *ctx) {
+  auto *c = reinterpret_cast<size_t *>(ctx);
+  ++(*c);
+  return MEMCACHED_SUCCESS;
+}
+
+TEST_CASE("memcached dump") {
+  pair<string, MemcachedCluster> tests[]{
+    {"mixed", MemcachedCluster::mixed()},
+    {"network", MemcachedCluster::network()},
+    {"socket", MemcachedCluster::socket()}
+  };
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    SECTION("prepared with 64 KVs") {
+      for (int i = 0; i < 64; ++i) {
+        char key[8];
+        int len = snprintf(key, sizeof(key) - 1, "k_%d", i);
+
+        CHECKED_IF(len) {
+          REQUIRE_SUCCESS(memcached_set(memc, key, len, key, len, 0, 0));
+        }
+      }
+
+      memcached_quit(memc);
+
+      // let memcached sort itself
+      using namespace chrono_literals;
+      this_thread::sleep_for(3s);
+
+      SECTION("dumps 64 KVs") {
+        size_t counter = 0;
+        memcached_dump_fn fn[] = {dump_cb};
+
+        REQUIRE_SUCCESS(memcached_dump(memc, fn, &counter, 1));
+        REQUIRE(counter == 64);
+      }
+    }
+  }
+}
diff --git a/testing/tests/memcached/encoding_key.cpp b/testing/tests/memcached/encoding_key.cpp
new file mode 100644 (file)
index 0000000..825bfa6
--- /dev/null
@@ -0,0 +1,88 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+#define TEST_KEY S("test")
+#define INITIAL_VAL S("initial")
+#define REPLACED_VAL S("replaced")
+
+static inline void check(memcached_st *enc, memcached_st *raw, const char *val, size_t len) {
+  memcached_return_t enc_rc, raw_rc;
+  size_t enc_length, raw_length;
+  Malloced enc_value(memcached_get(enc, TEST_KEY, &enc_length, nullptr, &enc_rc));
+  Malloced raw_value(memcached_get(raw, TEST_KEY, &raw_length, nullptr, &raw_rc));
+
+  REQUIRE(enc_rc == MEMCACHED_SUCCESS);
+  REQUIRE(raw_rc == MEMCACHED_SUCCESS);
+  REQUIRE_FALSE(enc_length == raw_length);
+  REQUIRE(memcmp(*raw_value, *enc_value, raw_length));
+  REQUIRE(enc_length == len);
+  REQUIRE_FALSE(memcmp(val, *enc_value, enc_length));
+}
+
+TEST_CASE("memcached encoding_key") {
+  pair<string, MemcachedCluster> tests[]{
+    {"network", MemcachedCluster::network()},
+    {"socket", MemcachedCluster::socket()}
+  };
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    SECTION("accepts encoding key") {
+      MemcachedPtr copy(memc);
+
+      REQUIRE_SUCCESS(memcached_set_encoding_key(memc, S(__func__)));
+
+      SECTION("sets encoded value") {
+        REQUIRE_SUCCESS(memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
+
+        SECTION("gets encoded value") {
+          check(memc, &copy.memc, INITIAL_VAL);
+        }
+
+        SECTION("cloned gets encoded value") {
+          MemcachedPtr dupe(memc);
+
+          check(&dupe.memc, &copy.memc, INITIAL_VAL);
+        }
+      }
+
+      SECTION("adds encoded value") {
+
+        REQUIRE_SUCCESS(memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
+        REQUIRE_RC(MEMCACHED_NOTSTORED, memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+
+        check(memc, &copy.memc, INITIAL_VAL);
+
+        test.flush();
+
+        REQUIRE_SUCCESS(memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+
+        SECTION("gets encoded value") {
+          check(memc, &copy.memc, REPLACED_VAL);
+        }
+      }
+
+      SECTION("replaces encoded value") {
+        REQUIRE_SUCCESS(memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
+
+        check(memc, &copy.memc, INITIAL_VAL);
+
+        REQUIRE_SUCCESS(memcached_replace(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+
+        SECTION("gets encoded value") {
+          check(memc, &copy.memc, REPLACED_VAL);
+        }
+      }
+
+      SECTION("unsupported") {
+        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_increment(memc, TEST_KEY, 0, nullptr));
+        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_decrement(memc, TEST_KEY, 0, nullptr));
+        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_increment_with_initial(memc, TEST_KEY, 0, 0, 0, nullptr));
+        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_decrement_with_initial(memc, TEST_KEY, 0, 0, 0, nullptr));
+        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_append(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+        REQUIRE_RC(MEMCACHED_NOT_SUPPORTED, memcached_prepend(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+      }
+    }
+  }
+}
diff --git a/testing/tests/memcached/exist.cpp b/testing/tests/memcached/exist.cpp
new file mode 100644 (file)
index 0000000..a0eeede
--- /dev/null
@@ -0,0 +1,47 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached exist") {
+  pair<string, MemcachedCluster> tests[]{
+    {"bin_mixed", MemcachedCluster::mixed()},
+    {"network", MemcachedCluster::network()},
+    {"socket", MemcachedCluster::socket()}
+  };
+
+  tests[0].second.enableBinaryProto();
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    SECTION("initial not found") {
+      REQUIRE_RC(MEMCACHED_NOTFOUND,memcached_exist(memc, S("frog")));
+    }
+
+    SECTION("set found") {
+        REQUIRE_SUCCESS(memcached_set(memc, S("frog"), S("frog"), 0, 0));
+        REQUIRE_SUCCESS(memcached_exist(memc, S("frog")));
+
+        SECTION("deleted not found") {
+          REQUIRE_SUCCESS(memcached_delete(memc, S("frog"), 0));
+          REQUIRE_RC(MEMCACHED_NOTFOUND, memcached_exist(memc, S("frog")));
+        }
+    }
+
+    SECTION("by key") {
+      SECTION("initial not found") {
+        REQUIRE_RC(MEMCACHED_NOTFOUND, memcached_exist_by_key(memc, S("master"), S("frog")));
+      }
+
+      SECTION("set found") {
+        REQUIRE_SUCCESS(memcached_set_by_key(memc, S("master"), S("frog"), S("frog"), 0, 0));
+        REQUIRE_SUCCESS(memcached_exist_by_key(memc, S("master"), S("frog")));
+
+        SECTION("deleted not found") {
+          REQUIRE_SUCCESS(memcached_delete_by_key(memc, S("master"), S("frog"), 0));
+          REQUIRE_RC(MEMCACHED_NOTFOUND, memcached_exist_by_key(memc, S("master"), S("frog")));
+        }
+      }
+    }
+  }
+
+}
diff --git a/testing/tests/memcached/haldenbrand.cpp b/testing/tests/memcached/haldenbrand.cpp
new file mode 100644 (file)
index 0000000..8dedcdf
--- /dev/null
@@ -0,0 +1,100 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+/* Test case provided by Cal Haldenbrand */
+
+#define HALDENBRAND_KEY_COUNT 3000U // * 1024576
+#define HALDENBRAND_FLAG_KEY 99 // * 1024576
+
+#include <cstdlib>
+
+TEST_CASE("memcached haldenbrand nblock_tcp_ndelay") {
+  pair<string, MemcachedCluster> tests[] = {
+      {"network", MemcachedCluster::network()},
+  };
+  
+  for (auto &[name, test] : tests) {
+    REQUIRE_SUCCESS(memcached_behavior_set(&test.memc, MEMCACHED_BEHAVIOR_NO_BLOCK, true));
+    REQUIRE_SUCCESS(memcached_behavior_set(&test.memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, true));
+  }
+  
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    /* We just keep looking at the same values over and over */
+    srandom(10);
+
+    /* add key */
+    unsigned long long total = 0;
+    for (uint32_t x = 0; total < 20 * 1024576; x++) {
+      uint32_t size = (uint32_t) (rand() % (5 * 1024)) + 400;
+      char randomstuff[6 * 1024];
+      memset(randomstuff, 0, 6 * 1024);
+      REQUIRE(size < 6 * 1024); /* Being safe here */
+
+      for (uint32_t j = 0; j < size; j++) {
+        randomstuff[j] = (signed char) ((rand() % 26) + 97);
+      }
+
+      total += size;
+      char key[MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1];
+      int key_length = snprintf(key, sizeof(key), "%u", x);
+      REQUIRE_SUCCESS(memcached_set(memc, key, key_length,
+          randomstuff, strlen(randomstuff),
+          time_t(0), HALDENBRAND_FLAG_KEY
+      ));
+    }
+    REQUIRE(total > HALDENBRAND_KEY_COUNT);
+
+    size_t total_value_length = 0;
+    for (uint32_t x = 0, errors = 0; total_value_length < 24576; x++) {
+      uint32_t flags = 0;
+      size_t val_len = 0;
+
+      char key[MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1];
+      int key_length = snprintf(key, sizeof(key), "%u", x);
+
+      memcached_return_t rc;
+      char *getval = memcached_get(memc, key, key_length, &val_len, &flags, &rc);
+      if (memcached_failed(rc)) {
+        if (rc == MEMCACHED_NOTFOUND) {
+          errors++;
+        } else {
+          REQUIRE(rc);
+        }
+
+        continue;
+      }
+      REQUIRE(uint32_t(HALDENBRAND_FLAG_KEY) == flags);
+      REQUIRE(getval);
+
+      total_value_length += val_len;
+      errors = 0;
+      ::free(getval);
+    }
+
+
+    std::vector<size_t> key_lengths;
+    key_lengths.resize(HALDENBRAND_KEY_COUNT);
+    std::vector<char *> keys;
+    keys.resize(key_lengths.size());
+    for (uint32_t x = 0; x < key_lengths.size(); x++) {
+      char key[MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1];
+      int key_length = snprintf(key, sizeof(key), "%u", x);
+      REQUIRE(key_length > 0);
+      REQUIRE(key_length < MEMCACHED_MAXIMUM_INTEGER_DISPLAY_LENGTH + 1);
+      keys[x] = strdup(key);
+      key_lengths[x] = key_length;
+    }
+
+    REQUIRE_SUCCESS(memcached_mget(memc, &keys[0], &key_lengths[0], key_lengths.size()));
+
+    unsigned int keys_returned;
+    REQUIRE(memcached_success(fetch_all_results(memc, keys_returned)));
+    REQUIRE(HALDENBRAND_KEY_COUNT == keys_returned);
+
+    for (auto key : keys) {
+      free(key);
+    }
+  }
+}
diff --git a/testing/tests/memcached/inc_dec.cpp b/testing/tests/memcached/inc_dec.cpp
new file mode 100644 (file)
index 0000000..e7dfb4a
--- /dev/null
@@ -0,0 +1,120 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached inc_dec") {
+  pair<string, MemcachedCluster> tests[] = {
+      {"mixed", MemcachedCluster::mixed()},
+  };
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+    uint64_t binary = GENERATE(0, 1);
+    void *prefix = GENERATE(as<void *>{}, nullptr, "namespace:");
+
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, binary));
+    if (prefix) {
+      REQUIRE_SUCCESS(memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, prefix));
+    }
+
+    DYNAMIC_SECTION("increment (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+
+      REQUIRE_SUCCESS(memcached_set(memc, S("number"), S("0"), 0, 0));
+
+      for (auto i = 1; i <= 10; ++i) {
+        REQUIRE_SUCCESS(memcached_increment(memc, S("number"), 1, &number));
+        REQUIRE(number == static_cast<uint64_t>(i));
+      }
+    }
+
+    DYNAMIC_SECTION("increment by_key (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+
+      REQUIRE_SUCCESS(memcached_set_by_key(memc, S("key"), S("number"), S("0"), 0, 0));
+
+      for (auto i = 1; i <= 10; ++i) {
+        REQUIRE_SUCCESS(memcached_increment_by_key(memc, S("key"), S("number"), 1, &number));
+        REQUIRE(number == static_cast<uint64_t>(i));
+      }
+    }
+
+    DYNAMIC_SECTION("increment with initial (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+      uint64_t initial = GENERATE(0, 456);
+
+      if (!binary) {
+        REQUIRE_RC(MEMCACHED_INVALID_ARGUMENTS,
+            memcached_increment_with_initial(memc, S("number"), 1, initial, 0, &number));
+      } else {
+        REQUIRE_SUCCESS(memcached_increment_with_initial(memc, S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial);
+        REQUIRE_SUCCESS(memcached_increment_with_initial(memc, S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial + 123);
+      }
+    }
+
+    DYNAMIC_SECTION("increment with initial by_key (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+      uint64_t initial = GENERATE(0, 456);
+
+      if (!binary) {
+        REQUIRE_RC(MEMCACHED_INVALID_ARGUMENTS,
+            memcached_increment_with_initial_by_key(memc, S("key"), S("number"), 1, initial, 0, &number));
+      } else {
+        REQUIRE_SUCCESS(memcached_increment_with_initial_by_key(memc, S("key"), S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial);
+        REQUIRE_SUCCESS(memcached_increment_with_initial_by_key(memc, S("key"), S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial + 123);
+      }
+    }
+
+    DYNAMIC_SECTION("decrement (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+
+      REQUIRE_SUCCESS(memcached_set(memc, S("number"), S("10"), 0, 0));
+
+      for (auto i = 9; i >= 0; --i) {
+        REQUIRE_SUCCESS(memcached_decrement(memc, S("number"), 1, &number));
+        REQUIRE(number == static_cast<uint64_t>(i));
+      }
+    }
+    DYNAMIC_SECTION("decrement by_key (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+
+      REQUIRE_SUCCESS(memcached_set_by_key(memc, S("key"), S("number"), S("10"), 0, 0));
+
+      for (auto i = 9; i >= 0; --i) {
+        REQUIRE_SUCCESS(memcached_decrement_by_key(memc, S("key"), S("number"), 1, &number));
+        REQUIRE(number == static_cast<uint64_t>(i));
+      }
+    }
+    DYNAMIC_SECTION("decrement with initial (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+      uint64_t initial = GENERATE(987, 456);
+
+      if (!binary) {
+        REQUIRE_RC(MEMCACHED_INVALID_ARGUMENTS,
+            memcached_decrement_with_initial(memc, S("number"), 1, initial, 0, &number));
+      } else {
+        REQUIRE_SUCCESS(memcached_decrement_with_initial(memc, S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial);
+        REQUIRE_SUCCESS(memcached_decrement_with_initial(memc, S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial - 123);
+      }
+    }
+    DYNAMIC_SECTION("decrement with initial by_key (binary=" << binary << ", prefix=" << prefix << ")") {
+      uint64_t number;
+      uint64_t initial = GENERATE(987, 456);
+
+      if (!binary) {
+        REQUIRE_RC(MEMCACHED_INVALID_ARGUMENTS,
+            memcached_decrement_with_initial_by_key(memc, S("key"), S("number"), 1, initial, 0, &number));
+      } else {
+        REQUIRE_SUCCESS(memcached_decrement_with_initial_by_key(memc, S("key"), S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial);
+        REQUIRE_SUCCESS(memcached_decrement_with_initial_by_key(memc, S("key"), S("number"), 123, initial, 0, &number));
+        REQUIRE(number == initial - 123);
+      }
+    }
+  }
+}
diff --git a/testing/tests/memcached/ketama.cpp b/testing/tests/memcached/ketama.cpp
new file mode 100644 (file)
index 0000000..2a5a969
--- /dev/null
@@ -0,0 +1,200 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+#include "ketama_test_cases.h"
+
+#include "libmemcached/continuum.hpp"
+#include "libmemcached/instance.hpp"
+
+DECLARE_STREQUAL;
+
+TEST_CASE("memcached ketama_compat") {
+  auto test = MemcachedCluster::network();
+  auto memc = &test.memc;
+
+  REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1));
+  REQUIRE(uint64_t(1) == memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED));
+
+  SECTION("generate hash") {
+    REQUIRE_SUCCESS(memcached_behavior_set_distribution(memc, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA));
+    REQUIRE(MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA == memcached_behavior_get_distribution(memc));
+
+    memcached_server_st *server_pool = memcached_servers_parse(
+        "10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"
+    );
+    memcached_servers_reset(memc);
+    memcached_server_push(memc, server_pool);
+
+    /* verify that the server list was parsed okay. */
+    REQUIRE(8U == memcached_server_count(memc));
+    REQUIRE(strequal(server_pool[0].hostname, "10.0.1.1"));
+    REQUIRE(in_port_t(11211) == server_pool[0].port);
+    REQUIRE(600U == server_pool[0].weight);
+    REQUIRE(strequal(server_pool[2].hostname, "10.0.1.3"));
+    REQUIRE(in_port_t(11211) == server_pool[2].port);
+    REQUIRE(200U == server_pool[2].weight);
+    REQUIRE(strequal(server_pool[7].hostname, "10.0.1.8"));
+    REQUIRE(in_port_t(11211) == server_pool[7].port);
+    REQUIRE(100U == server_pool[7].weight);
+
+    /* VDEAAAAA hashes to fffcd1b5, after the last continuum point, and lets
+     * us test the boundary wraparound.
+     */
+    REQUIRE(memcached_generate_hash(memc, (char *) "VDEAAAAA", 8) == memc->ketama.continuum[0].index);
+
+    /* verify the standard ketama set. */
+    for (uint32_t x = 0; x < 99; x++) {
+      uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key));
+      const memcached_instance_st *instance =
+          memcached_server_instance_by_position(memc, server_idx);
+      const char *hostname = memcached_server_name(instance);
+
+      REQUIRE(strequal(hostname, ketama_test_cases[x].server));
+    }
+
+    memcached_server_list_free(server_pool);
+  }
+
+  SECTION("user bug 18") {
+
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH, MEMCACHED_HASH_MD5));
+    REQUIRE(MEMCACHED_HASH_MD5 == memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH));
+
+    memcached_server_st *server_pool = memcached_servers_parse(
+        "10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"
+    );
+    memcached_servers_reset(memc);
+    memcached_server_push(memc, server_pool);
+
+    /* verify that the server list was parsed okay. */
+    REQUIRE(memcached_server_count(memc) == 8);
+    REQUIRE(strequal(server_pool[0].hostname, "10.0.1.1"));
+    REQUIRE(server_pool[0].port == 11211);
+    REQUIRE(server_pool[0].weight == 600);
+    REQUIRE(strequal(server_pool[2].hostname, "10.0.1.3"));
+    REQUIRE(server_pool[2].port == 11211);
+    REQUIRE(server_pool[2].weight == 200);
+    REQUIRE(strequal(server_pool[7].hostname, "10.0.1.8"));
+    REQUIRE(server_pool[7].port == 11211);
+    REQUIRE(server_pool[7].weight == 100);
+
+    /* VDEAAAAA hashes to fffcd1b5, after the last continuum point, and lets
+     * us test the boundary wraparound.
+     */
+    REQUIRE(memcached_generate_hash(memc, (char *) "VDEAAAAA", 8) == memc->ketama.continuum[0].index);
+
+    /* verify the standard ketama set. */
+    for (auto x = 0; x < 99; x++) {
+      uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key));
+
+      const memcached_instance_st *instance =
+          memcached_server_instance_by_position(memc, server_idx);
+
+      const char *hostname = memcached_server_name(instance);
+      REQUIRE(strequal(hostname, ketama_test_cases[x].server));
+    }
+
+    memcached_server_list_free(server_pool);
+  }
+
+  SECTION("auto_eject_hosts") {
+    memcached_servers_reset(memc);
+
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH, MEMCACHED_HASH_MD5));
+    REQUIRE(MEMCACHED_HASH_MD5 == memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH));
+
+    /* server should be removed when in delay */
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS, 1));
+    REQUIRE(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS));
+
+    memcached_server_st *server_pool;
+    server_pool = memcached_servers_parse(
+        "10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"
+    );
+    memcached_servers_reset(memc);
+    memcached_server_push(memc, server_pool);
+
+    /* verify that the server list was parsed okay. */
+    REQUIRE(memcached_server_count(memc) == 8);
+    REQUIRE(strequal(server_pool[0].hostname, "10.0.1.1"));
+    REQUIRE(server_pool[0].port == 11211);
+    REQUIRE(server_pool[0].weight == 600);
+    REQUIRE(strequal(server_pool[2].hostname, "10.0.1.3"));
+    REQUIRE(server_pool[2].port == 11211);
+    REQUIRE(server_pool[2].weight == 200);
+    REQUIRE(strequal(server_pool[7].hostname, "10.0.1.8"));
+    REQUIRE(server_pool[7].port == 11211);
+    REQUIRE(server_pool[7].weight == 100);
+
+    const memcached_instance_st *instance = memcached_server_instance_by_position(memc, 2);
+    memcached_instance_next_retry(instance, time(nullptr) + 15);
+    memc->ketama.next_distribution_rebuild = time(nullptr) - 1;
+
+    /*
+      This would not work if there were only two hosts.
+    */
+    for (auto x = 0; x < 99; x++) {
+      memcached_autoeject(memc);
+      uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key));
+      REQUIRE(server_idx != 2);
+    }
+
+    /* and re-added when it's back. */
+    time_t absolute_time = time(nullptr) - 1;
+    memcached_instance_next_retry(instance, absolute_time);
+    memc->ketama.next_distribution_rebuild = absolute_time;
+    memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION, memc->distribution);
+    for (auto x = 0; x < 99; x++) {
+      uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key));
+      // We re-use instance from above.
+      instance = memcached_server_instance_by_position(memc, server_idx);
+      const char *hostname = memcached_server_name(instance);
+      REQUIRE(strequal(hostname, ketama_test_cases[x].server));
+    }
+
+    memcached_server_list_free(server_pool);
+  }
+
+  SECTION("spymemcached") {
+    memcached_servers_reset(memc);
+    REQUIRE_SUCCESS(memcached_behavior_set_distribution(memc, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY));
+    REQUIRE(MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY == memcached_behavior_get_distribution(memc));
+
+    memcached_server_st *server_pool = memcached_servers_parse(
+        "10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"
+    );
+    REQUIRE(server_pool);
+    memcached_server_push(memc, server_pool);
+
+    /* verify that the server list was parsed okay. */
+    REQUIRE(8U == memcached_server_count(memc));
+    REQUIRE(strequal(server_pool[0].hostname, "10.0.1.1"));
+    REQUIRE(in_port_t(11211) == server_pool[0].port);
+    REQUIRE(600U == server_pool[0].weight);
+    REQUIRE(strequal(server_pool[2].hostname, "10.0.1.3"));
+    REQUIRE(in_port_t(11211) == server_pool[2].port);
+    REQUIRE(200U == server_pool[2].weight);
+    REQUIRE(strequal(server_pool[7].hostname, "10.0.1.8"));
+    REQUIRE(in_port_t(11211) == server_pool[7].port);
+    REQUIRE(100U == server_pool[7].weight);
+
+    /* VDEAAAAA hashes to fffcd1b5, after the last continuum point, and lets
+     * us test the boundary wraparound.
+     */
+    REQUIRE(memcached_generate_hash(memc, (char *) "VDEAAAAA", 8) == memc->ketama.continuum[0].index);
+
+    /* verify the standard ketama set. */
+    for (uint32_t x = 0; x < 99; x++) {
+      uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases_spy[x].key,
+          strlen(ketama_test_cases_spy[x].key));
+
+      const memcached_instance_st *instance =
+          memcached_server_instance_by_position(memc, server_idx);
+
+      const char *hostname = memcached_server_name(instance);
+      REQUIRE(strequal(hostname, ketama_test_cases_spy[x].server));
+    }
+
+    memcached_server_list_free(server_pool);
+  }
+}
diff --git a/testing/tests/memcached/ketama_test_cases.h b/testing/tests/memcached/ketama_test_cases.h
new file mode 100644 (file)
index 0000000..49d1eaa
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2006-2009 Brian Aker
+ * All rights reserved.
+ *
+ * Use and distribution licensed under the BSD license.  See
+ * the COPYING file in the parent directory for full text.
+ */
+
+#pragma once
+
+static struct {
+    const char *key;
+    unsigned long hash1;
+    unsigned long hash2;
+    const char *server;
+} ketama_test_cases[99]= {
+  { "SVa_]_V41)", 443691461UL, 445379617UL, "10.0.1.7" },
+  { "*/Z;?V(.\\8", 1422915503UL, 1428303028UL, "10.0.1.1" },
+  { "30C1*Z*S/_", 1473165754UL, 1480075959UL, "10.0.1.2" },
+  { "ERR:EC58G>", 2148406511UL, 2168579133UL, "10.0.1.7" },
+  { "1I=cTMNTKF", 2882686667UL, 2885206587UL, "10.0.1.5" },
+  { "]VG<`I*Z8)", 1103544263UL, 1104827657UL, "10.0.1.5" },
+  { "UUTC`-V159", 3716288206UL, 3727224240UL, "10.0.1.5" },
+  { "@7RU6C6T+Z", 3862737685UL, 3871917949UL, "10.0.1.5" },
+  { "/XLN0@+36;", 1623269830UL, 1627683651UL, "10.0.1.1" },
+  { "4(`X;\\V.^c", 373546328UL, 383925769UL, "10.0.1.1" },
+  { "726bW=9*a4", 4213440020UL, 4213950705UL, "10.0.1.7" },
+  { "\\`)<B)UE,c", 951096736UL, 955226069UL, "10.0.1.1" },
+  { "P1[Ma3=K1/", 1989324036UL, 1994028240UL, "10.0.1.8" },
+  { "C89I.-V?cT", 1604239957UL, 1606398093UL, "10.0.1.8" },
+  { "D[HE+cFXDK", 2117036136UL, 2117124014UL, "10.0.1.3" },
+  { "P1L?NAB[)K", 2129972569UL, 2132542634UL, "10.0.1.1" },
+  { "cDT0)Z5P6,", 176485284UL, 178675413UL, "10.0.1.5" },
+  { "@JW`+[WAO8", 2720940826UL, 2743975456UL, "10.0.1.5" },
+  { "\\39DKW^)N_", 3548879868UL, 3550704865UL, "10.0.1.3" },
+  { "EM75N0+[X1", 1558531507UL, 1559308507UL, "10.0.1.4" },
+  { "`,SS]NBP,b", 1883545960UL, 1884847278UL, "10.0.1.1" },
+  { "XX1a9LT+F?", 653487707UL, 656410408UL, "10.0.1.5" },
+  { "Zc\\-,F-c6V", 1160802451UL, 1171575728UL, "10.0.1.5" },
+  { "1*RTMC7,03", 1602398012UL, 1606398093UL, "10.0.1.8" },
+  { "*Xc+V0P>32", 536016577UL, 539988520UL, "10.0.1.7" },
+  { "U))Fb-(`,.", 4128682289UL, 4136854163UL, "10.0.1.7" },
+  { "R-08RNTaRT", 3718170086UL, 3727224240UL, "10.0.1.5" },
+  { "(LHcO203I3", 1007779411UL, 1014643570UL, "10.0.1.5" },
+  { "=256P+;Qc8", 3976201210UL, 3976304873UL, "10.0.1.5" },
+  { "OI5XZ_BBT(", 2155922164UL, 2168579133UL, "10.0.1.7" },
+  { "2TLRL/UL;:", 1086800909UL, 1095659802UL, "10.0.1.7" },
+  { "WHD\\O1`ZRW", 3087923411UL, 3095471560UL, "10.0.1.5" },
+  { ".=54)_c;=T", 2497691631UL, 2502731301UL, "10.0.1.1" },
+  { ";G<W-XWZ@b", 2888169733UL, 2888728739UL, "10.0.1.5" },
+  { "(,>E`)FT\\4", 580747448UL, 581063326UL, "10.0.1.2" },
+  { "HZAU*;P*N]", 2564670474UL, 2565697267UL, "10.0.1.7" },
+  { "NZ@ZE=O84_", 533335275UL, 539988520UL, "10.0.1.7" },
+  { "6,cEI`F_P>", 3972869246UL, 3974773167UL, "10.0.1.6" },
+  { "c,5AQ/T5)6", 2835605783UL, 2847870057UL, "10.0.1.8" },
+  { ".O,>>BT)RX", 3857978174UL, 3871917949UL, "10.0.1.5" },
+  { "XY\\X::LX50", 1749241099UL, 1752196488UL, "10.0.1.6" },
+  { "+550F^/.01", 3781824099UL, 3783248219UL, "10.0.1.6" },
+  { "<.X9E2S5+9", 3232479481UL, 3234387706UL, "10.0.1.7" },
+  { "]\\.UH8_0a1", 2419699252UL, 2423002920UL, "10.0.1.4" },
+  { "8(6=(T0/Z0", 728266737UL, 729026070UL, "10.0.1.7" },
+  { "8*6a;Sc*X+", 4223431086UL, 4230156966UL, "10.0.1.2" },
+  { "<QW:;3K6;H", 2731158143UL, 2743975456UL, "10.0.1.5" },
+  { "7C@EY@-Y?_", 760770733UL, 761576669UL, "10.0.1.5" },
+  { "aPb3E1WD4K", 2500489218UL, 2502731301UL, "10.0.1.1" },
+  { "?@12R<=1BH", 1494795329UL, 1502505505UL, "10.0.1.8" },
+  { "QR(a+Q=1FU", 3238535074UL, 3238996435UL, "10.0.1.6" },
+  { "`C9^FV,960", 2628553463UL, 2628733766UL, "10.0.1.6" },
+  { "UNHVP..^8H", 977096483UL, 977319837UL, "10.0.1.4" },
+  { ":Y.2W2[(35", 2777083668UL, 2784182515UL, "10.0.1.7" },
+  { "M/HV^_HZ4O", 3623390946UL, 3624445007UL, "10.0.1.7" },
+  { "ZY16KQ<ICD", 1831153193UL, 1838563516UL, "10.0.1.4" },
+  { "bV2,`a.PY9", 1962228869UL, 1962648654UL, "10.0.1.1" },
+  { "U;9:-+5N]9", 269504649UL, 277560877UL, "10.0.1.1" },
+  { "1S/:aJ[1(;", 578069729UL, 581063326UL, "10.0.1.2" },
+  { "Nb-X^]M)I:", 330865696UL, 331009896UL, "10.0.1.6" },
+  { "2;M;ES>J5/", 2776949824UL, 2784182515UL, "10.0.1.7" },
+  { "[>RZHG97Q9", 71954686UL, 72034069UL, "10.0.1.6" },
+  { "J3/G[)9<^Z", 2799896459UL, 2805183696UL, "10.0.1.7" },
+  { "N-)88>[O`,", 50404102UL, 51792557UL, "10.0.1.5" },
+  { "NP:=FR\\OaA", 3837333776UL, 3837792034UL, "10.0.1.7" },
+  { "`@L+W;a,O[", 1512157148UL, 1522285852UL, "10.0.1.6" },
+  { "W2`P:-+1T[", 2945171975UL, 2946196424UL, "10.0.1.5" },
+  { "-6G7K^YDIN", 3168617340UL, 3170513015UL, "10.0.1.7" },
+  { "U>*>9ZI6V5", 668514946UL, 674097631UL, "10.0.1.6" },
+  { ".I?^6Ic9RK", 938419020UL, 942832691UL, "10.0.1.6" },
+  { "0OZH^9BKM[", 3682518606UL, 3686781297UL, "10.0.1.8" },
+  { "5?50UGZ:ML", 868610882UL, 869425986UL, "10.0.1.5" },
+  { "?K2NF@3=IU", 381218851UL, 383925769UL, "10.0.1.1" },
+  { "YI@G-2X?UB", 3688706179UL, 3693197681UL, "10.0.1.5" },
+  { "7cY</BSaL=", 3976870223UL, 3978903843UL, "10.0.1.6" },
+  { "A(`KF:[RH8", 3292979676UL, 3294849139UL, "10.0.1.6" },
+  { ";=ZT\\W^P+H", 1401102653UL, 1416290674UL, "10.0.1.4" },
+  { "b2?WFF56;R", 480494704UL, 486971192UL, "10.0.1.4" },
+  { "CTR74,J+N.", 137446045UL, 146633907UL, "10.0.1.8" },
+  { "<b;*R+QDST", 1304985302UL, 1308223778UL, "10.0.1.5" },
+  { "\\R^7=9UCG`", 126218373UL, 129199837UL, "10.0.1.5" },
+  { "1bQS5]WOXB", 1853470245UL, 1855329369UL, "10.0.1.4" },
+  { "M(@X^b[L:K", 3019630308UL, 3022260113UL, "10.0.1.1" },
+  { "431cBF8,YO", 1679726993UL, 1685224295UL, "10.0.1.7" },
+  { "(bEIQJ:E./", 2922607787UL, 2925521819UL, "10.0.1.6" },
+  { "WS/3H*)7F;", 419488232UL, 422140585UL, "10.0.1.5" },
+  { "ZJF[Ia6Q)+", 3960568056UL, 3962489998UL, "10.0.1.7" },
+  { "<]*QCK8U,>", 2590140172UL, 2598117636UL, "10.0.1.7" },
+  { "\\[a\\^=V_M0", 689410119UL, 698690782UL, "10.0.1.6" },
+  { "7;RM+8J9YC", 1530175299UL, 1531107082UL, "10.0.1.7" },
+  { "4*=.SPR[AV", 3928582722UL, 3928853792UL, "10.0.1.1" },
+  { "-2F+^88P4U", 3023552752UL, 3025823613UL, "10.0.1.7" },
+  { "X;-F`(N?9D", 570465234UL, 572485994UL, "10.0.1.7" },
+  { "R=F_D-K2a]", 1287750228UL, 1290935562UL, "10.0.1.7" },
+  { "X*+2aaC.EG", 3200948713UL, 3201088518UL, "10.0.1.5" },
+  { "[1ZXONX2]a", 4108881567UL, 4109865744UL, "10.0.1.4" },
+  { "FL;\\GWacaV", 458449508UL, 467374054UL, "10.0.1.4" },
+  { "\\MQ_XNT7L-", 1259349383UL, 1259509450UL, "10.0.1.7" },
+  { "VD6D0]ba_\\", 3842502950UL, 3842588691UL, "10.0.1.1" },
+};
+
+#include "ketama_test_cases_spy.h"
diff --git a/testing/tests/memcached/ketama_test_cases_spy.h b/testing/tests/memcached/ketama_test_cases_spy.h
new file mode 100644 (file)
index 0000000..b587031
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2006-2009 Brian Aker
+ * All rights reserved.
+ *
+ * Use and distribution licensed under the BSD license.  See
+ * the COPYING file in the parent directory for full text.
+ */
+
+#pragma once
+
+static struct {
+    const char *key;
+    unsigned long hash1;
+    unsigned long hash2;
+    const char *server;
+} ketama_test_cases_spy[99]= {
+  { "SVa_]_V41)", 443691461UL, 445379617UL, "10.0.1.2" },
+  { "*/Z;?V(.\\8", 1422915503UL, 1428303028UL, "10.0.1.4" },
+  { "30C1*Z*S/_", 1473165754UL, 1480075959UL, "10.0.1.2" },
+  { "ERR:EC58G>", 2148406511UL, 2168579133UL, "10.0.1.7" },
+  { "1I=cTMNTKF", 2882686667UL, 2885206587UL, "10.0.1.4" },
+  { "]VG<`I*Z8)", 1103544263UL, 1104827657UL, "10.0.1.5" },
+  { "UUTC`-V159", 3716288206UL, 3727224240UL, "10.0.1.7" },
+  { "@7RU6C6T+Z", 3862737685UL, 3871917949UL, "10.0.1.6" },
+  { "/XLN0@+36;", 1623269830UL, 1627683651UL, "10.0.1.7" },
+  { "4(`X;\\V.^c", 373546328UL, 383925769UL, "10.0.1.6" },
+  { "726bW=9*a4", 4213440020UL, 4213950705UL, "10.0.1.3" },
+  { "\\`)<B)UE,c", 951096736UL, 955226069UL, "10.0.1.8" },
+  { "P1[Ma3=K1/", 1989324036UL, 1994028240UL, "10.0.1.1" },
+  { "C89I.-V?cT", 1604239957UL, 1606398093UL, "10.0.1.5" },
+  { "D[HE+cFXDK", 2117036136UL, 2117124014UL, "10.0.1.5" },
+  { "P1L?NAB[)K", 2129972569UL, 2132542634UL, "10.0.1.7" },
+  { "cDT0)Z5P6,", 176485284UL, 178675413UL, "10.0.1.1" },
+  { "@JW`+[WAO8", 2720940826UL, 2743975456UL, "10.0.1.2" },
+  { "\\39DKW^)N_", 3548879868UL, 3550704865UL, "10.0.1.6" },
+  { "EM75N0+[X1", 1558531507UL, 1559308507UL, "10.0.1.5" },
+  { "`,SS]NBP,b", 1883545960UL, 1884847278UL, "10.0.1.1" },
+  { "XX1a9LT+F?", 653487707UL, 656410408UL, "10.0.1.6" },
+  { "Zc\\-,F-c6V", 1160802451UL, 1171575728UL, "10.0.1.6" },
+  { "1*RTMC7,03", 1602398012UL, 1606398093UL, "10.0.1.5" },
+  { "*Xc+V0P>32", 536016577UL, 539988520UL, "10.0.1.7" },
+  { "U))Fb-(`,.", 4128682289UL, 4136854163UL, "10.0.1.7" },
+  { "R-08RNTaRT", 3718170086UL, 3727224240UL, "10.0.1.5" },
+  { "(LHcO203I3", 1007779411UL, 1014643570UL, "10.0.1.1" },
+  { "=256P+;Qc8", 3976201210UL, 3976304873UL, "10.0.1.3" },
+  { "OI5XZ_BBT(", 2155922164UL, 2168579133UL, "10.0.1.5" },
+  { "2TLRL/UL;:", 1086800909UL, 1095659802UL, "10.0.1.2" },
+  { "WHD\\O1`ZRW", 3087923411UL, 3095471560UL, "10.0.1.1" },
+  { ".=54)_c;=T", 2497691631UL, 2502731301UL, "10.0.1.6" },
+  { ";G<W-XWZ@b", 2888169733UL, 2888728739UL, "10.0.1.7" },
+  { "(,>E`)FT\\4", 580747448UL, 581063326UL, "10.0.1.5" },
+  { "HZAU*;P*N]", 2564670474UL, 2565697267UL, "10.0.1.1" },
+  { "NZ@ZE=O84_", 533335275UL, 539988520UL, "10.0.1.7" },
+  { "6,cEI`F_P>", 3972869246UL, 3974773167UL, "10.0.1.3" },
+  { "c,5AQ/T5)6", 2835605783UL, 2847870057UL, "10.0.1.7" },
+  { ".O,>>BT)RX", 3857978174UL, 3871917949UL, "10.0.1.7" },
+  { "XY\\X::LX50", 1749241099UL, 1752196488UL, "10.0.1.7" },
+  { "+550F^/.01", 3781824099UL, 3783248219UL, "10.0.1.2" },
+  { "<.X9E2S5+9", 3232479481UL, 3234387706UL, "10.0.1.7" },
+  { "]\\.UH8_0a1", 2419699252UL, 2423002920UL, "10.0.1.6" },
+  { "8(6=(T0/Z0", 728266737UL, 729026070UL, "10.0.1.6" },
+  { "8*6a;Sc*X+", 4223431086UL, 4230156966UL, "10.0.1.5" },
+  { "<QW:;3K6;H", 2731158143UL, 2743975456UL, "10.0.1.7" },
+  { "7C@EY@-Y?_", 760770733UL, 761576669UL, "10.0.1.5" },
+  { "aPb3E1WD4K", 2500489218UL, 2502731301UL, "10.0.1.2" },
+  { "?@12R<=1BH", 1494795329UL, 1502505505UL, "10.0.1.1" },
+  { "QR(a+Q=1FU", 3238535074UL, 3238996435UL, "10.0.1.5" },
+  { "`C9^FV,960", 2628553463UL, 2628733766UL, "10.0.1.3" },
+  { "UNHVP..^8H", 977096483UL, 977319837UL, "10.0.1.6" },
+  { ":Y.2W2[(35", 2777083668UL, 2784182515UL, "10.0.1.6" },
+  { "M/HV^_HZ4O", 3623390946UL, 3624445007UL, "10.0.1.4" },
+  { "ZY16KQ<ICD", 1831153193UL, 1838563516UL, "10.0.1.7" },
+  { "bV2,`a.PY9", 1962228869UL, 1962648654UL, "10.0.1.7" },
+  { "U;9:-+5N]9", 269504649UL, 277560877UL, "10.0.1.2" },
+  { "1S/:aJ[1(;", 578069729UL, 581063326UL, "10.0.1.5" },
+  { "Nb-X^]M)I:", 330865696UL, 331009896UL, "10.0.1.3" },
+  { "2;M;ES>J5/", 2776949824UL, 2784182515UL, "10.0.1.6" },
+  { "[>RZHG97Q9", 71954686UL, 72034069UL, "10.0.1.4" },
+  { "J3/G[)9<^Z", 2799896459UL, 2805183696UL, "10.0.1.6" },
+  { "N-)88>[O`,", 50404102UL, 51792557UL, "10.0.1.2" },
+  { "NP:=FR\\OaA", 3837333776UL, 3837792034UL, "10.0.1.7" },
+  { "`@L+W;a,O[", 1512157148UL, 1522285852UL, "10.0.1.5" },
+  { "W2`P:-+1T[", 2945171975UL, 2946196424UL, "10.0.1.7" },
+  { "-6G7K^YDIN", 3168617340UL, 3170513015UL, "10.0.1.5" },
+  { "U>*>9ZI6V5", 668514946UL, 674097631UL, "10.0.1.5" },
+  { ".I?^6Ic9RK", 938419020UL, 942832691UL, "10.0.1.6" },
+  { "0OZH^9BKM[", 3682518606UL, 3686781297UL, "10.0.1.2" },
+  { "5?50UGZ:ML", 868610882UL, 869425986UL, "10.0.1.6" },
+  { "?K2NF@3=IU", 381218851UL, 383925769UL, "10.0.1.6" },
+  { "YI@G-2X?UB", 3688706179UL, 3693197681UL, "10.0.1.6" },
+  { "7cY</BSaL=", 3976870223UL, 3978903843UL, "10.0.1.7" },
+  { "A(`KF:[RH8", 3292979676UL, 3294849139UL, "10.0.1.6" },
+  { ";=ZT\\W^P+H", 1401102653UL, 1416290674UL, "10.0.1.6" },
+  { "b2?WFF56;R", 480494704UL, 486971192UL, "10.0.1.7" },
+  { "CTR74,J+N.", 137446045UL, 146633907UL, "10.0.1.7" },
+  { "<b;*R+QDST", 1304985302UL, 1308223778UL, "10.0.1.5" },
+  { "\\R^7=9UCG`", 126218373UL, 129199837UL, "10.0.1.6" },
+  { "1bQS5]WOXB", 1853470245UL, 1855329369UL, "10.0.1.7" },
+  { "M(@X^b[L:K", 3019630308UL, 3022260113UL, "10.0.1.4" },
+  { "431cBF8,YO", 1679726993UL, 1685224295UL, "10.0.1.1" },
+  { "(bEIQJ:E./", 2922607787UL, 2925521819UL, "10.0.1.7" },
+  { "WS/3H*)7F;", 419488232UL, 422140585UL, "10.0.1.3" },
+  { "ZJF[Ia6Q)+", 3960568056UL, 3962489998UL, "10.0.1.5" },
+  { "<]*QCK8U,>", 2590140172UL, 2598117636UL, "10.0.1.5" },
+  { "\\[a\\^=V_M0", 689410119UL, 698690782UL, "10.0.1.7" },
+  { "7;RM+8J9YC", 1530175299UL, 1531107082UL, "10.0.1.7" },
+  { "4*=.SPR[AV", 3928582722UL, 3928853792UL, "10.0.1.3" },
+  { "-2F+^88P4U", 3023552752UL, 3025823613UL, "10.0.1.7" },
+  { "X;-F`(N?9D", 570465234UL, 572485994UL, "10.0.1.5" },
+  { "R=F_D-K2a]", 1287750228UL, 1290935562UL, "10.0.1.1" },
+  { "X*+2aaC.EG", 3200948713UL, 3201088518UL, "10.0.1.3" },
+  { "[1ZXONX2]a", 4108881567UL, 4109865744UL, "10.0.1.7" },
+  { "FL;\\GWacaV", 458449508UL, 467374054UL, "10.0.1.7" },
+  { "\\MQ_XNT7L-", 1259349383UL, 1259509450UL, "10.0.1.5" },
+  { "VD6D0]ba_\\", 3842502950UL, 3842588691UL, "10.0.1.7" },
+};
diff --git a/testing/tests/memcached/prepend.cpp b/testing/tests/memcached/prepend.cpp
new file mode 100644 (file)
index 0000000..323cd75
--- /dev/null
@@ -0,0 +1,28 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached prepend") {
+  pair<string, MemcachedCluster> tests[] = {
+      {"network", MemcachedCluster::network()},
+      {"socket", MemcachedCluster::socket()}
+  };
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    test.flush();
+
+    REQUIRE_RC(MEMCACHED_NOTSTORED, memcached_prepend(memc, S(__func__), S("fail"), 0, 0));
+    REQUIRE_SUCCESS(memcached_set(memc, S(__func__), S("initial"), 0, 0));
+    REQUIRE_SUCCESS(memcached_prepend(memc, S(__func__), S("pre1"), 0, 0));
+    REQUIRE_SUCCESS(memcached_prepend(memc, S(__func__), S("pre2"), 0, 0));
+
+    memcached_return_t rc;
+    uint32_t flags;
+    size_t len;
+    char *val = memcached_get(memc, S(__func__), &len, &flags, &rc);
+    Malloced v(val);
+    REQUIRE_SUCCESS(rc);
+    REQUIRE("pre2pre1initial" == string(val, len));
+  }
+}
diff --git a/testing/tests/memcached/regression/binary_block_add.cpp b/testing/tests/memcached/regression/binary_block_add.cpp
new file mode 100644 (file)
index 0000000..6c99223
--- /dev/null
@@ -0,0 +1,21 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached regression binary_block_add") {
+  auto test = MemcachedCluster::network();
+  auto memc = &test.memc;
+  auto blob = random_ascii_string(1024);
+
+  test.enableBinaryProto();
+
+  for (auto i = 0; i < 20480; ++i) {
+    auto rkey = random_ascii_string(12);
+    memcached_return_t rc = memcached_add_by_key(memc, S("key"), rkey.c_str(), rkey.length(), blob.c_str(), blob.length(), 0, 0);
+
+    if (rc == MEMCACHED_MEMORY_ALLOCATION_FAILURE) {
+      break;
+    } else if (rc != MEMCACHED_DATA_EXISTS) {
+      REQUIRE_SUCCESS(rc);
+    }
+  }
+}
diff --git a/testing/tests/memcached/servers.cpp b/testing/tests/memcached/servers.cpp
new file mode 100644 (file)
index 0000000..1c3d699
--- /dev/null
@@ -0,0 +1,119 @@
+#include "testing/lib/common.hpp"
+
+#include "testing/lib/MemcachedCluster.hpp"
+
+static memcached_return_t server_display_function(const memcached_st *,
+    const memcached_instance_st * server,
+    void *context)
+{
+  if (context) {
+    auto bigger = reinterpret_cast<size_t *>(context);
+    REQUIRE(*bigger <= memcached_server_port(server));
+    *bigger = memcached_server_port(server);
+  }
+  return MEMCACHED_SUCCESS;
+}
+
+TEST_CASE("memcached servers") {
+  SECTION("memcached_servers_parse") {
+    SECTION("does not leak memory") {
+      memcached_server_st *s = memcached_servers_parse("1.2.3.4:1234");
+      REQUIRE(s);
+      memcached_server_free(s);
+    }
+  }
+
+  SECTION("memcached_server_list") {
+    SECTION("append with weight - all zeros") {
+      memcached_server_st *sl = memcached_server_list_append_with_weight(
+          nullptr, nullptr, 0, 0, 0
+      );
+      REQUIRE(sl);
+      memcached_server_list_free(sl);
+    }
+    SECTION("append with weight - host set only") {
+      memcached_server_st *sl = memcached_server_list_append_with_weight(
+          nullptr, "localhost", 0, 0, 0
+      );
+      REQUIRE(sl);
+      memcached_server_list_free(sl);
+    }
+    SECTION("append with weight - error set only") {
+      memcached_return_t rc;
+      memcached_server_st *sl = memcached_server_list_append_with_weight(
+          nullptr, nullptr, 0, 0, &rc
+      );
+      REQUIRE(sl);
+      REQUIRE(MEMCACHED_SUCCESS == rc);
+      memcached_server_list_free(sl);
+    }
+  }
+
+  SECTION("no configured servers") {
+    MemcachedPtr memc;
+
+    REQUIRE(MEMCACHED_NO_SERVERS == memcached_increment(*memc, S("key"), 1, nullptr));
+  }
+
+  SECTION("sort") {
+    MemcachedPtr memc;
+    auto local_memc = *memc;
+    LoneReturnMatcher test{local_memc};
+    size_t bigger = 0; /* Prime the value for the test_true in server_display_function */
+    memcached_server_fn callbacks[1]{server_display_function};
+
+    REQUIRE_SUCCESS(memcached_behavior_set(local_memc, MEMCACHED_BEHAVIOR_SORT_HOSTS, 1));
+    
+    SECTION("variation 1") {
+      for (uint32_t x = 0; x < 7; x++) {
+        REQUIRE_SUCCESS(memcached_server_add_with_weight(local_memc, "localhost", random_port(), 0));
+        REQUIRE(x + 1 == memcached_server_count(local_memc));
+      }
+      memcached_server_cursor(local_memc, callbacks, &bigger, 1);
+    }
+
+    SECTION("variation 2") {
+      const memcached_instance_st *instance;
+
+      REQUIRE_SUCCESS(memcached_server_add_with_weight(local_memc, "MEMCACHED_BEHAVIOR_SORT_HOSTS", 43043, 0));
+      instance = memcached_server_instance_by_position(local_memc, 0);
+      REQUIRE(in_port_t(43043) ==  memcached_server_port(instance));
+
+      REQUIRE_SUCCESS(memcached_server_add_with_weight(local_memc, "MEMCACHED_BEHAVIOR_SORT_HOSTS", 43042, 0));
+
+      instance = memcached_server_instance_by_position(local_memc, 0);
+      REQUIRE(in_port_t(43042) ==  memcached_server_port(instance));
+
+      instance = memcached_server_instance_by_position(local_memc, 1);
+      REQUIRE(in_port_t(43043) ==  memcached_server_port(instance));
+
+      memcached_server_cursor(local_memc, callbacks, &bigger, 1);
+    }
+  }
+
+  SECTION("regression lp:1048945") {
+    MemcachedPtr memc_ptr(memcached_create(nullptr));
+    auto memc = *memc_ptr;
+    LoneReturnMatcher test{memc};
+    memcached_return status;
+
+    auto list = memcached_server_list_append_with_weight(nullptr, "a", 11211, 0, &status);
+    REQUIRE_SUCCESS(status);
+
+    list = memcached_server_list_append_with_weight(list, "b", 11211, 0, &status);
+    REQUIRE_SUCCESS(status);
+
+    list = memcached_server_list_append_with_weight(list, "c", 11211, 0, &status);
+    REQUIRE_SUCCESS(status);
+
+    REQUIRE(3 == memcached_server_list_count(list));
+
+    REQUIRE_SUCCESS(memcached_server_push(memc, list));
+    REQUIRE_SUCCESS(memcached_server_push(memc, list));
+    memcached_server_list_free(list);
+
+    auto server = memcached_server_by_key(memc, S(__func__), &status);
+    REQUIRE(server);
+    REQUIRE_SUCCESS(status);
+  }
+}
diff --git a/testing/tests/memcached/simple.cpp b/testing/tests/memcached/simple.cpp
new file mode 100644 (file)
index 0000000..7d6f9af
--- /dev/null
@@ -0,0 +1,63 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached simple") {
+  pair<string, MemcachedCluster> tests[] = {
+      {"network", MemcachedCluster::network()},
+      {"socket", MemcachedCluster::socket()}
+  };
+
+  LOOPED_SECTION(tests) {
+    auto memc = &test.memc;
+
+    SECTION("flush") {
+      uint64_t id = memcached_query_id(memc);
+      REQUIRE_SUCCESS(memcached_flush(memc, 0));
+      REQUIRE(id + 1 == memcached_query_id(memc));
+    }
+
+    uint64_t buffered = GENERATE(0, 1);
+    uint64_t binary = GENERATE(0, 1);
+
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, buffered));
+    REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, binary));
+
+    DYNAMIC_SECTION("set (buffered=" << buffered << ",binary=" << binary << ")") {
+      for (auto i = 0; i < 10; ++i) {
+        REQUIRE_RC(buffered ? MEMCACHED_BUFFERED : MEMCACHED_SUCCESS,
+            memcached_set(memc, S(__func__), S(__func__), 0, 0));
+      }
+    }
+
+    DYNAMIC_SECTION("add (buffered=" << buffered << ",binary=" << binary << ")") {
+      memcached_return_t rc;
+
+      Malloced empty(memcached_get(memc, S(__func__), nullptr, nullptr, &rc));
+      REQUIRE_FALSE(*empty);
+      REQUIRE_RC(MEMCACHED_NOTFOUND, rc);
+
+      REQUIRE_RC(buffered ? MEMCACHED_BUFFERED : MEMCACHED_SUCCESS,
+          memcached_set(memc, S(__func__), S(__func__), 0, 0));
+
+      // flush any pending requests
+      memcached_quit(memc);
+
+      uint32_t flags;
+      size_t len;
+      Malloced val(memcached_get(memc, S(__func__), &len, &flags, &rc));
+
+      REQUIRE(*val);
+      REQUIRE(string(__func__) == string(*val, len));
+
+      REQUIRE_RC(binary ? MEMCACHED_DATA_EXISTS : MEMCACHED_NOTSTORED,
+          memcached_add(memc, S(__func__), S("update"), 0, 0));
+    }
+
+    DYNAMIC_SECTION("replace (buffered=" << buffered << ",binary=" << binary << ")") {
+      REQUIRE_RC(buffered ? MEMCACHED_BUFFERED : MEMCACHED_SUCCESS,
+          memcached_set(memc, S(__func__), S(__func__), 0, 0));
+
+      REQUIRE_SUCCESS(memcached_replace(memc, S(__func__), S("replaced"), 0, 0));
+    }
+  }
+}
diff --git a/testing/tests/noreply.cpp b/testing/tests/noreply.cpp
new file mode 100644 (file)
index 0000000..ec32458
--- /dev/null
@@ -0,0 +1,61 @@
+#include "testing/lib/common.hpp"
+#include "testing/lib/MemcachedCluster.hpp"
+
+enum action_t {ADD, REPLACE, SET, APPEND, PREPEND};
+inline action_t operator ++ (action_t &a) {
+  return a = static_cast<action_t>(underlying_type<action_t>::type(a) + 1);
+}
+
+constexpr static const int keys = 5000;
+
+TEST_CASE("memcached noreply") {
+  auto test{MemcachedCluster::mixed()};
+  auto memc = &test.memc;
+
+  REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, true));
+  REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, true));
+  REQUIRE_SUCCESS(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, true));
+
+  for (auto action = ADD; action <= PREPEND; ++action) {
+    for (auto i = 0; i < keys; ++i) {
+      auto key = to_string(i);
+      memcached_return_t rc;
+
+      switch (action) {
+      case ADD:     rc = memcached_add(memc, key.c_str(), key.length(), key.c_str(), key.length(), 0, 0); break;
+      case REPLACE: rc = memcached_replace(memc, key.c_str(), key.length(), key.c_str(), key.length(), 0, 0); break;
+      case SET:     rc = memcached_set(memc, key.c_str(), key.length(), key.c_str(), key.length(), 0, 0); break;
+      case APPEND:  rc = memcached_append(memc, key.c_str(), key.length(), key.c_str(), key.length(), 0, 0); break;
+      case PREPEND: rc = memcached_prepend(memc, key.c_str(), key.length(), key.c_str(), key.length(), 0, 0); break;
+      default:      FAIL();
+      }
+
+      if (rc != MEMCACHED_BUFFERED) {
+        REQUIRE_SUCCESS(rc);
+      }
+    }
+
+    REQUIRE_SUCCESS(memcached_flush_buffers(memc));
+
+    for (auto i = 0; i < keys; ++i) {
+      auto key = to_string(i);
+
+      size_t len;
+      memcached_return_t rc;
+      uint32_t flags;
+      Malloced val(memcached_get(memc, key.c_str(), key.length(), &len, &flags, &rc));
+
+      REQUIRE_SUCCESS(rc);
+      REQUIRE(*val);
+
+      switch (action) {
+      case ADD:     [[fallthrough]];
+      case REPLACE: [[fallthrough]];
+      case SET:     REQUIRE(key == *val);  break;
+      case APPEND:  REQUIRE(key + key == *val); break;
+      case PREPEND: REQUIRE(key + key + key == *val); break;
+      default:      FAIL();
+      }
+    }
+  }
+}
diff --git a/tests/output_plus.res b/tests/output_plus.res
deleted file mode 100644 (file)
index 39f245d..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-servers localhost:11221,
-       localhost : 11221
-
-
-retvalue 1