From: Michael Wallner Date: Wed, 2 Sep 2020 16:38:28 +0000 (+0200) Subject: flush [ci skip] X-Git-Tag: 1.1.0-beta1~236^2~83 X-Git-Url: https://git.m6w6.name/?a=commitdiff_plain;h=f33fd6e7d1df8e5878ce5c5605f64bab7b02ceb6;p=m6w6%2Flibmemcached flush [ci skip] --- diff --git a/include/libhashkit-1.0/hashkit.hpp b/include/libhashkit-1.0/hashkit.hpp index a953d72d..11a250fa 100644 --- a/include/libhashkit-1.0/hashkit.hpp +++ b/include/libhashkit-1.0/hashkit.hpp @@ -85,7 +85,7 @@ public: hashkit_return_t set_distribution_function(hashkit_hash_algorithm_t hash_algorithm) { - return hashkit_set_function(&self, hash_algorithm); + return hashkit_set_distribution_function(&self, hash_algorithm); } ~Hashkit() diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 4bfcff81..ab2f30e8 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -2,16 +2,26 @@ add_executable(catch_main main.cpp - lib/random_.cpp + lib/common.cpp lib/Cluster.cpp - lib/Poll.cpp + lib/Connection.cpp + lib/ForkAndExec.cpp + lib/MemcachedCluster.cpp lib/Server.cpp - lib/WaitForConn.cpp - lib/WaitForExec.cpp + lib/Retry.cpp - server.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 ) 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 new file mode 100644 index 00000000..b162b9c9 --- /dev/null +++ b/testing/hashkit/basic.cpp @@ -0,0 +1,217 @@ +#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(LITERAL("apple"))); + REQUIRE(2297466611U == hashkit_digest(&st, LITERAL("apple"))); + } + + SECTION("can set hash function") { + for (int f = HASHKIT_HASH_DEFAULT; f < HASHKIT_HASH_MAX; ++f) { + auto h = static_cast(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(LITERAL(i))); + CHECK(output[f][n] == hashkit_digest(&st, LITERAL(i))); + CHECK(output[f][n] == libhashkit_digest(LITERAL(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 new file mode 100644 index 00000000..571b56bf --- /dev/null +++ b/testing/lib.cpp @@ -0,0 +1,60 @@ +#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()); + } + } + } +} diff --git a/testing/lib/Cluster.cpp b/testing/lib/Cluster.cpp index 8a601495..5d2ab42a 100644 --- a/testing/lib/Cluster.cpp +++ b/testing/lib/Cluster.cpp @@ -2,6 +2,25 @@ #include +Cluster::Cluster(Server &&serv, uint16_t cnt) +: count{cnt} +, proto{forward(serv)} +{ + if (!cnt) { + count = thread::hardware_concurrency()/2 ?: 4; + } + reset(); +} + +Cluster::~Cluster() { + stop(); + wait(); +} + +const vector &Cluster::getServers() const { + return cluster; +} + void Cluster::reset() { pids.clear(); cluster.clear(); @@ -15,8 +34,10 @@ bool Cluster::start() { for (auto &server : cluster) { auto pid = server.start(); - if (started &= pid.has_value()) { + if (pid.has_value()) { pids[*pid] = &server; + } else { + started = false; } } @@ -25,7 +46,8 @@ bool Cluster::start() { void Cluster::stop() { for (auto &server : cluster) { - server.stop(); + // no cookies for memcached; TERM is just too slow + server.signal(SIGKILL); } } @@ -38,22 +60,23 @@ bool Cluster::isStopped() { return true; } -bool Cluster::isListening(int max_timeout) { - vector conns; - +bool Cluster::isListening() { for (auto &server : cluster) { - auto conn = server.createSocket(); - if (!conn) { - return false; + 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; + } + } + return server.isListening(); } - conns.emplace_back(conn.value()); } - WaitForConn wait_for_conn{ - std::move(conns), - Poll{POLLOUT, 2, max_timeout} - }; - return wait_for_conn(); + return true; } void Cluster::wait() { @@ -65,8 +88,10 @@ void Cluster::wait() { return; } - if (pids[inf.si_pid]) { - pids[inf.si_pid]->wait(); + auto server = pids.find(inf.si_pid); + if (server != pids.end()) { + server->second->wait(); } } } + diff --git a/testing/lib/Cluster.hpp b/testing/lib/Cluster.hpp index d6a11a7e..416da138 100644 --- a/testing/lib/Cluster.hpp +++ b/testing/lib/Cluster.hpp @@ -1,34 +1,33 @@ #pragma once +#include "common.hpp" #include "Server.hpp" -#include -#include - class Cluster { -private: - uint16_t count; - Server proto; - vector cluster; - - map pids; - public: explicit - Cluster(Server &&serv, uint16_t cnt = 0) - : count{cnt} - , proto{serv} - { - if (!cnt) { - count = thread::hardware_concurrency() ?: 4; - } - reset(); - } + Cluster(Server &&serv, uint16_t cnt = 0); + + ~Cluster(); + + Cluster(const Cluster &c) = delete; + Cluster &operator = (const Cluster &c) = delete; + + Cluster(Cluster &&c) = default; + Cluster &operator = (Cluster &&c) = default; + + const vector &getServers() const; bool start(); void stop(); void reset(); bool isStopped(); - bool isListening(int max_timeout = 1000); + bool isListening(); void wait(); + +private: + uint16_t count; + Server proto; + vector cluster; + map pids; }; diff --git a/testing/lib/Connection.cpp b/testing/lib/Connection.cpp new file mode 100644 index 00000000..c4e01d97 --- /dev/null +++ b/testing/lib/Connection.cpp @@ -0,0 +1,177 @@ +#include "Connection.hpp" + +#include +#include +#include + +Connection::Connection(socket_or_port_t socket_or_port) { + if (holds_alternative(socket_or_port)) { + const auto path = get(socket_or_port); + const auto safe = path.c_str(); + const auto zlen = path.length() + 1; + const auto ulen = sizeof(sockaddr_un) - sizeof(sa_family_t); + + if (zlen >= ulen) { + throw invalid_argument(error({"socket(): path too long '", path, "'"})); + } + + if (0 > (sock = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) { + throw runtime_error(error({"socket(): ", strerror(errno)})); + } + + auto sa = reinterpret_cast(&addr); + sa->sun_family = AF_UNIX; + copy(safe, safe + zlen, sa->sun_path); + + size = UNIX; + + } else { + if (0 > (sock = socket(AF_INET6, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) { + throw runtime_error(error({"socket(): ", strerror(errno)})); + } + + const auto port = get(socket_or_port); + auto sa = reinterpret_cast(&addr); + sa->sin6_family = AF_INET6; + sa->sin6_port = htons(static_cast(port)); + sa->sin6_addr = IN6ADDR_LOOPBACK_INIT; + + size = INET6; + } +} + +Connection::~Connection() { + close(); +} + +void swap(Connection &a, Connection &b) { + a.swap(b); +} + +void Connection::swap(Connection &conn) { + Connection copy(conn); + conn.sock = sock; + conn.addr = addr; + conn.size = size; + conn.last_err = last_err; + sock = exchange(copy.sock, -1); + addr = copy.addr; + size = copy.size; + last_err = copy.last_err; +} + +Connection::Connection(const Connection &conn) { + if (conn.sock > -1) { + sock = dup(conn.sock); + } + addr = conn.addr; + size = conn.size; + last_err = conn.last_err; +} + +Connection &Connection::operator=(const Connection &conn) { + Connection copy(conn); + copy.swap(*this); + return *this; +} + +Connection::Connection(Connection &&conn) noexcept { + close(); + swap(conn); +} + +Connection &Connection::operator=(Connection &&conn) noexcept { + Connection copy(forward(conn)); + copy.swap(*this); + return *this; +} + +void Connection::close() { + if (sock > -1) { + ::close(sock); + sock = -1; + last_err = -1; + } +} + +int Connection::getError() { + int err = -1; + socklen_t len = sizeof(int); + if (sock > -1) { + errno = 0; + if (0 > getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len)) { + err = errno; + } + } + last_err = err; + return err; +} + +int Connection::getLastError() { + if (last_err == -1) { + return getError(); + } + return last_err; +} + +bool Connection::isWritable() { + pollfd fd{sock, POLLOUT, 0}; + if (1 > poll(&fd, 1, 0)) { + return false; + } + if (fd.revents & (POLLNVAL|POLLERR|POLLHUP)) { + return false; + } + return fd.revents & POLLOUT; +} + +bool Connection::isOpen() { + if (sock > -1){ + if (isWritable()) { + return getError() == 0; + } else if (open()) { + if (isWritable()) { + return getError() == 0; + } + } + } + return false; +} + +bool Connection::open() { + if (connected) { + return true; + } + connect_again: + errno = 0; + if (0 == ::connect(sock, reinterpret_cast(&addr), size)) { + connected = true; + return true; + } + + switch (errno) { + case EINTR: + goto connect_again; + case EISCONN: + connected = true; + [[fallthrough]]; + case EAGAIN: + case EALREADY: + case EINPROGRESS: + return true; + + default: + return false; + } +} + +string Connection::error(const initializer_list &args) { + stringstream ss; + + for (auto &arg : args) { + ss << arg; + } + + cerr << ss.str() << endl; + return ss.str(); +} diff --git a/testing/lib/Connection.hpp b/testing/lib/Connection.hpp new file mode 100644 index 00000000..6a50a4f1 --- /dev/null +++ b/testing/lib/Connection.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "common.hpp" + +#include +#include + +class Connection { +public: + explicit Connection(socket_or_port_t socket_or_port); + ~Connection(); + + friend void swap(Connection &a, Connection &b); + void swap(Connection &conn); + + Connection(const Connection &conn); + Connection &operator = (const Connection &conn); + + Connection(Connection &&conn) noexcept ; + Connection &operator = (Connection &&conn) noexcept ; + + int getError(); + int getLastError(); + + bool isWritable(); + bool isOpen(); + + bool open(); + void close(); + +private: + int sock{-1}, last_err{-1}; + sockaddr_storage addr{0, {0}, 0}; + enum sockaddr_size { + NONE = 0, + UNIX = sizeof(sockaddr_un), + INET = sizeof(sockaddr_in), + INET6 = sizeof(sockaddr_in6) + } size; + bool connected{false}; + + static string error(const initializer_list &args); +}; diff --git a/testing/lib/ForkAndExec.cpp b/testing/lib/ForkAndExec.cpp new file mode 100644 index 00000000..b882eb43 --- /dev/null +++ b/testing/lib/ForkAndExec.cpp @@ -0,0 +1,54 @@ +#include "ForkAndExec.hpp" + +#include +#include + +#include +#include +#include + +ForkAndExec::ForkAndExec(const char *binary_, char **argv_) +: binary{binary_} +, argv{argv_} +{ + if (pipe2(pipes, O_CLOEXEC|O_NONBLOCK)) { + int error = errno; + perror("Server::start pipe2()"); + throw system_error(error, system_category()); + } +} + +ForkAndExec::~ForkAndExec() { + if (pipes[0] != -1) { + close(pipes[0]); + } + if (pipes[1] != -1) { + close(pipes[1]); + } +} + +optional ForkAndExec::operator()() { + if (pipes[0] == -1) { + return {}; + } + if (pipes[1] != -1) { + close(pipes[1]); + pipes[1] = -1; + } + + switch (pid_t pid = fork()) { + case 0: + execvp(binary, argv); + [[fallthrough]]; + case -1: + perror("fork() && exec()"); + return {}; + + default: + pollfd fd{pipes[0], 0, 0}; + if (1 > poll(&fd, 1, 5000)) { + cerr << "exec() timed out" << endl; + } + return pid; + } +} diff --git a/testing/lib/ForkAndExec.hpp b/testing/lib/ForkAndExec.hpp new file mode 100644 index 00000000..b88c56a7 --- /dev/null +++ b/testing/lib/ForkAndExec.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "common.hpp" + +class ForkAndExec { +public: + ForkAndExec(const char *binary, char **argv); + ~ForkAndExec(); + + ForkAndExec(const ForkAndExec &) = delete; + ForkAndExec &operator = (const ForkAndExec &) = delete; + ForkAndExec(ForkAndExec &&) = default; + ForkAndExec &operator = (ForkAndExec &&) = default; + + optional operator () (); + +private: + int pipes[2]; + const char *binary; + char **argv; +}; diff --git a/testing/lib/MemcachedCluster.cpp b/testing/lib/MemcachedCluster.cpp new file mode 100644 index 00000000..4a330247 --- /dev/null +++ b/testing/lib/MemcachedCluster.cpp @@ -0,0 +1,57 @@ +#include "MemcachedCluster.hpp" +#include "Retry.hpp" + +void MemcachedCluster::init() { + REQUIRE(cluster.start()); + + REQUIRE(memcached_create(&memc)); + for (const auto &server : cluster.getServers()) { + auto target = server.getSocketOrPort(); + if (holds_alternative(target)) { + REQUIRE(MEMCACHED_SUCCESS == memcached_server_add_unix_socket(&memc, get(target).c_str())); + } else { + REQUIRE(MEMCACHED_SUCCESS == memcached_server_add(&memc, "localhost", get(target))); + } + } + + Retry cluster_is_listening([this]() { + return cluster.isListening(); + }); + REQUIRE(cluster_is_listening()); +} + +MemcachedCluster::~MemcachedCluster() { + memcached_free(&memc); +} + +void MemcachedCluster::flush() { + REQUIRE(MEMCACHED_SUCCESS == memcached_flush(&memc, 0)); +} + +MemcachedCluster::MemcachedCluster() +: cluster{Server{getenv_else("MEMCACHED_BINARY", "memcached"), {random_socket_or_port_arg()}}} +{ + init(); +} + +MemcachedCluster::MemcachedCluster(Cluster &&cluster_) +: cluster{forward(cluster_)} +{ + init(); +} + +MemcachedCluster MemcachedCluster::mixed() { + return MemcachedCluster{}; +} + +MemcachedCluster MemcachedCluster::net() { + return MemcachedCluster{Cluster{Server{getenv_else("MEMCACHED_BINARY", "memcached"), {"-p", random_socket_or_port_string}}}}; +} + +MemcachedCluster MemcachedCluster::socket() { + return MemcachedCluster{Cluster{Server{getenv_else("MEMCACHED_BINARY", "memcached"), {"-s", random_socket_or_port_string}}}}; +} + +void MemcachedCluster::enableBinary(bool enable) { + REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(&memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, enable)); +} diff --git a/testing/lib/MemcachedCluster.hpp b/testing/lib/MemcachedCluster.hpp new file mode 100644 index 00000000..45fce24c --- /dev/null +++ b/testing/lib/MemcachedCluster.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "common.hpp" +#include "Cluster.hpp" + +class MemcachedCluster { +public: + Cluster cluster; + memcached_st memc; + + MemcachedCluster(); + explicit + MemcachedCluster(Cluster &&cluster); + ~MemcachedCluster(); + + void enableBinary(bool enable = true); + void flush(); + + static MemcachedCluster mixed(); + static MemcachedCluster net(); + static MemcachedCluster socket(); + +private: + void init(); +}; diff --git a/testing/lib/Poll.cpp b/testing/lib/Poll.cpp deleted file mode 100644 index ac17e726..00000000 --- a/testing/lib/Poll.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "Poll.hpp" - -#include -#include -#include -#include - -#include - -using namespace std; - -bool Poll::operator() (const vector &fds) { - vector pfds; - - pfds.reserve(fds.size()); - for (auto fd : fds) { - pfds.emplace_back(pollfd{fd, events, 0}); - } - - while (!pfds.empty() && timeout <= max) { - auto nfds = poll(pfds.data(), pfds.size(), timeout); - - if (nfds == -1) { - perror("Poll::() poll()"); - return false; - } - - /* timeout */ - if (!nfds) { - timeout = ceil(static_cast(timeout) * growth); - continue; - } - - auto pred = [](const pollfd &pfd){ - return pfd.revents & POLLHUP - || pfd.revents & POLLERR - || pfd.revents & pfd.events; - }; - auto iter = remove_if(pfds.begin(), pfds.end(), pred); - pfds.erase(iter, pfds.end()); - } - - return pfds.empty(); -} diff --git a/testing/lib/Poll.hpp b/testing/lib/Poll.hpp deleted file mode 100644 index 0b00e237..00000000 --- a/testing/lib/Poll.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -using namespace std; - -class Poll { -private: - short events; - int timeout, max; - float growth; - -public: - explicit Poll(short events_ = 0, int timeout_ = 20, int max_ = 1000, float growth_ = 1.1) - : events{events_} - , timeout{timeout_} - , max{max_} - , growth{growth_} - {} - - bool operator () (const vector &fds); -}; diff --git a/testing/lib/Retry.cpp b/testing/lib/Retry.cpp new file mode 100644 index 00000000..dd6b83c1 --- /dev/null +++ b/testing/lib/Retry.cpp @@ -0,0 +1,22 @@ +#include "Retry.hpp" + +Retry::Retry(predicate &&pred_, unsigned int max_, chrono::milliseconds sleep_for_) +: max{max_} +, sleep_for{sleep_for_} +, pred{forward(pred_)} +{} + +bool Retry::operator()() { + auto cnt = max; + auto dur = sleep_for; + + while (cnt--) { + if (pred()) { + return true; + } + this_thread::sleep_for(dur); + dur *= 2; + } + + return false; +} diff --git a/testing/lib/Retry.hpp b/testing/lib/Retry.hpp new file mode 100644 index 00000000..26924b3a --- /dev/null +++ b/testing/lib/Retry.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "common.hpp" + +class Retry { +public: + + using predicate = function; + + explicit Retry(predicate &&pred_, unsigned max_ = 10, chrono::milliseconds sleep_for_ = 20ms); + + bool operator () (); + +private: + unsigned max; + chrono::milliseconds sleep_for; + predicate pred; +}; + diff --git a/testing/lib/Server.cpp b/testing/lib/Server.cpp index bc5de99f..b3d0118b 100644 --- a/testing/lib/Server.cpp +++ b/testing/lib/Server.cpp @@ -1,100 +1,90 @@ #include "Server.hpp" -#include "WaitForExec.hpp" -#include "WaitForConn.hpp" +#include "Connection.hpp" +#include "ForkAndExec.hpp" -#include -#include -#include -#include #include #include -#include -#include - -using namespace std; - -[[nodiscard]] -auto Server::createArgv() { - auto i = 0, port = -1, socket = -1; - auto arr = new char *[str_args.size() + dyn_args.size()*2 + 2] { - strdup(binary.c_str()) - }; - - for (auto &arg : str_args) { - arr[++i] = strdup(arg.c_str()); - if (arg == "-p") { - port = i + 1; - } else if (arg == "-s") { - socket = i + 1; - } - } - for (auto &arg : dyn_args) { - arr[++i] = strdup(arg.first.c_str()); - arr[++i] = strdup(arg.second(arg.first).c_str()); - if (arg.first == "-p") { - port = i; - } else if (arg.first == "-s") { - socket = i; - } +Server::Server(string &&binary_, Server::argv_t &&args_) + : binary{forward(binary_)} + , args{forward(args_)} +{} +Server::~Server() { + stop(); + wait(); + if (holds_alternative(socket_or_port)) { + unlink(get(socket_or_port).c_str()); } - arr[i+1] = nullptr; +} - if (socket > -1) { - socket_or_port = arr[socket]; - } else if (port > -1) { - socket_or_port = stoi(arr[port]); +static inline string extractArg(const Server::arg_t &arg_cont, const string &func_arg) { + if (holds_alternative(arg_cont)) { + return get(arg_cont); } else { - socket_or_port = 11211; + return get(arg_cont)(func_arg); } - - return arr; } -[[nodiscard]] -optional Server::createSocket() { - sockaddr_storage addr; - unsigned size = 0; - int sock; - - if (holds_alternative(socket_or_port)) { - const auto path = get(socket_or_port); - const auto safe = path.c_str(); - const auto zlen = path.length() + 1; - const auto ulen = sizeof(sockaddr_un) - sizeof(sa_family_t); - - if (zlen >= ulen) { - cerr << "Server::isListening socket(): path too long '" << path << "'\n"; - return {}; - } - - if (0 > (sock = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) { - perror("Server::isListening socket()"); - return {}; - } +static inline void pushArg(vector &arr, const string &arg) { + auto len = arg.size(); + auto str = arg.data(), end = str + len + 1; + auto ptr = new char[len + 1]; + copy(str, end, ptr); + arr.push_back(ptr); +} - auto sa = reinterpret_cast(&addr); - sa->sun_family = AF_UNIX; - strncpy(sa->sun_path, safe, zlen); +optional Server::handleArg(vector &arr, const string &arg, const arg_func_t &next_arg) { + pushArg(arr, arg); + if (arg == "-p" || arg == "--port") { + auto port = next_arg(arg); + pushArg(arr, port); + pushArg(arr, "-U"); + pushArg(arr, port); + socket_or_port = stoi(port); + return port; + } else if (arg == "-s" || arg == "--unix-socket") { + auto sock = next_arg(arg); + pushArg(arr, sock); + socket_or_port = sock; + return sock; + } + return {}; +} - size = sizeof(*sa); - } else { - if (0 > (sock = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) { - perror("Server::isListening socket()"); - return {}; +[[nodiscard]] +vector Server::createArgv() { + vector arr; + + pushArg(arr, binary); + pushArg(arr, "-v"); + + for (auto it = args.cbegin(); it != args.cend(); ++it) { + if (holds_alternative(*it)) { + // a single argument + auto arg = extractArg(get(*it), binary); + handleArg(arr, arg, [&it](const string &arg_) { + return extractArg(get(*++it), arg_); + }); + } else { + // an argument pair + auto &[one, two] = get(*it); + auto arg_one = extractArg(one, binary); + auto arg_two = extractArg(two, arg_one); + + auto next = handleArg(arr, arg_one, [&arg_two](const string &) { + return arg_two; + }); + + if (!next.has_value()) { + pushArg(arr, arg_two); + } } - - const auto port = get(socket_or_port); - auto sa = reinterpret_cast(&addr); - sa->sin_family = AF_INET; - sa->sin_port = htons(static_cast(port)), - sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - size = sizeof(*sa); } - return optional{make_tuple(sock, addr, size)}; + arr.push_back(nullptr); + + return arr; } optional Server::start() { @@ -102,36 +92,27 @@ optional Server::start() { return pid; } - WaitForExec wait_for_exec; + auto argv = createArgv(); + auto child = ForkAndExec{binary.c_str(), argv.data()}(); - switch (pid = fork()) { - case 0: - execvp(binary.c_str(), createArgv()); - [[fallthrough]]; - case -1: - perror("Server::start fork() & exec()"); - return {}; + for (auto argp : argv) { + delete [] argp; + } - default: - if (!wait_for_exec()) { - cerr << "Server::start exec(): incomplete\n"; - } - return pid; + if (child.has_value()) { + pid = child.value(); } + + return child; } -bool Server::isListening(int max_timeout) { - auto conn = createSocket(); +bool Server::isListening() { + Connection conn(socket_or_port); - if (!conn) { + if (!conn.open()) { return false; } - - WaitForConn wait_for_conn{ - {conn.value()}, - Poll{POLLOUT, 2, max_timeout} - }; - return wait_for_conn(); + return conn.isOpen(); } bool Server::stop() { @@ -168,3 +149,33 @@ bool Server::wait(int flags) { bool Server::tryWait() { return wait(WNOHANG); } + +Server::Server(const Server &s) { + binary = s.binary; + args = s.args; + socket_or_port = s.socket_or_port; +} + +Server &Server::operator=(const Server &s) { + binary = s.binary; + args = s.args; + socket_or_port = s.socket_or_port; + return *this; +} + +pid_t Server::getPid() const { + return pid; +} + +const string &Server::getBinary() const { + return binary; +} + +const Server::argv_t &Server::getArgs() const { + return args; +} + +const socket_or_port_t &Server::getSocketOrPort() const { + return socket_or_port; +} + diff --git a/testing/lib/Server.hpp b/testing/lib/Server.hpp index b411f4e3..08ab5773 100644 --- a/testing/lib/Server.hpp +++ b/testing/lib/Server.hpp @@ -1,86 +1,57 @@ #pragma once -#include "WaitForConn.hpp" +#include "common.hpp" #include -#include -#include -#include -#include -#include - -using namespace std; - class Server { - friend class Cluster; - public: - using str_args_t = vector; - using dyn_args_t = unordered_map>; - using socket_or_port_t = variant; -private: - string binary; - str_args_t str_args; - dyn_args_t dyn_args; - pid_t pid = 0; + friend class Cluster; - int status = 0; - unordered_map signalled; - socket_or_port_t socket_or_port{11211}; + using arg_func_t = function; + using arg_t = variant; + using arg_pair_t = pair; + using argv_t = vector>; -public: explicit - Server(string &&binary_, str_args_t &&str_args_ = {}, dyn_args_t &&dyn_args_ = {}) - : binary{binary_} - , str_args{str_args_} - , dyn_args{dyn_args_} - {} - - Server(string &&binary_, dyn_args_t &&dyn_args_) - : binary{binary_} - , str_args{} - , dyn_args{dyn_args_} - {} - - ~Server() { - stop(); - wait(); - } - - Server &operator = (const Server &s) = default; - Server(const Server &s) = default; + Server(string &&binary_, argv_t && args_ = {}); + + ~Server(); + + Server(const Server &s); + Server &operator = (const Server &s); Server &operator = (Server &&s) = default; Server(Server &&s) = default; - pid_t getPid() const { - return pid; - } + pid_t getPid() const; - const string &getBinary() const { - return binary; - } + const string &getBinary() const; - const socket_or_port_t &getSocketOrPort() const { - return socket_or_port; - } + const argv_t &getArgs() const; + + const socket_or_port_t &getSocketOrPort() const; optional start(); bool stop(); bool signal(int signo = SIGTERM); bool check(); - bool isListening(int max_timeout = 1000); + bool isListening(); bool wait(int flags = 0); bool tryWait(); private: - [[nodiscard]] - auto createArgv(); + string binary; + argv_t args; + pid_t pid = 0; + int status = 0; + unordered_map signalled; + socket_or_port_t socket_or_port = 11211; [[nodiscard]] - optional createSocket(); + vector createArgv(); + optional handleArg(vector &arr, const string &arg, const arg_func_t &next_arg); }; diff --git a/testing/lib/WaitForConn.cpp b/testing/lib/WaitForConn.cpp deleted file mode 100644 index 9d819d6a..00000000 --- a/testing/lib/WaitForConn.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "WaitForConn.hpp" - -#include -#include -#include -#include - -bool WaitForConn::connect(const conn_t &conn) { - int sock; - unsigned size; - sockaddr_storage addr; - - tie(sock, addr, size) = conn; - - connect_again: - if (0 == ::connect(sock, reinterpret_cast(&addr), size)) { - close(sock); - return true; - } - - switch (errno) { - case EINTR: - goto connect_again; - case EAGAIN: - case EALREADY: - case EINPROGRESS: - case EISCONN: - case ETIMEDOUT: - break; - default: - perror("WaitForConn::connect connect()"); - close(sock); - return false; - } - - return true; -} - -bool WaitForConn::operator () () { - vector fds; - - fds.reserve(conns.size()); - for (const auto &conn : conns) { - if (!connect(conn)) { - return false; - } - fds.push_back(get<0>(conn)); - } - - return poll(fds); -} diff --git a/testing/lib/WaitForConn.hpp b/testing/lib/WaitForConn.hpp deleted file mode 100644 index 758c856a..00000000 --- a/testing/lib/WaitForConn.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "Poll.hpp" - -#include -#include - -#include -#include - -using namespace std; - -class WaitForConn { -public: - using conn_t = tuple; - -private: - vector conns; - Poll poll; - -public: - explicit - WaitForConn(vector &&conns_, Poll &&poll_ = Poll{POLLOUT}) - : conns{conns_} - , poll{poll_} - { } - - static bool connect(const conn_t &conn); - - bool operator () (); -}; diff --git a/testing/lib/WaitForExec.cpp b/testing/lib/WaitForExec.cpp deleted file mode 100644 index fd25f0fe..00000000 --- a/testing/lib/WaitForExec.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "WaitForExec.hpp" - -#include -#include - -#include -#include - -#include - -using namespace std; - -WaitForExec::WaitForExec(Poll &&poll_) -: poll{poll_} -{ - if (pipe2(pipes, O_CLOEXEC|O_NONBLOCK)) { - int error = errno; - perror("Server::start pipe2()"); - throw system_error(error, system_category()); - } -} - -WaitForExec::~WaitForExec() { - if (pipes[0] != -1) { - close(pipes[0]); - } - if (pipes[1] != -1) { - close(pipes[1]); - } -} - -bool WaitForExec::operator()() { - if (pipes[0] == -1) { - return false; - } - if (pipes[1] != -1) { - close(pipes[1]); - pipes[1] = -1; - } - - return poll(initializer_list{pipes[0]}); -} diff --git a/testing/lib/WaitForExec.hpp b/testing/lib/WaitForExec.hpp deleted file mode 100644 index d067cbb5..00000000 --- a/testing/lib/WaitForExec.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "Poll.hpp" - -class WaitForExec { -private: - Poll poll; - int pipes[2]; - -public: - explicit WaitForExec(Poll &&poll = Poll{0}); - ~WaitForExec(); - - WaitForExec(const WaitForExec &) = delete; - WaitForExec(WaitForExec &&) = default; - - bool operator () (); -}; diff --git a/testing/lib/common.cpp b/testing/lib/common.cpp new file mode 100644 index 00000000..16f5e186 --- /dev/null +++ b/testing/lib/common.cpp @@ -0,0 +1,52 @@ +#include "common.hpp" +#include "Connection.hpp" + +#include +#include +#include + +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); + if (val && *val) { + return val; + } + return defval; +} + diff --git a/testing/lib/common.hpp b/testing/lib/common.hpp new file mode 100644 index 00000000..b516d669 --- /dev/null +++ b/testing/lib/common.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib/catch.hpp" + +#include "libmemcached/memcached.h" + +#define LITERAL(s) (s),strlen(s) +#define LOOPED_SECTION(tests) \ + auto i_=0; \ + for (auto &&test : tests) DYNAMIC_SECTION("test" << i_++) + +using namespace std; + +using socket_or_port_t = variant; + +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); +} + +class MemcachedPtr { +public: + memcached_st memc; + + explicit + MemcachedPtr(memcached_st *memc_) { + memset(&memc, 0, sizeof(memc)); + REQUIRE(memcached_clone(&memc, memc_)); + } + MemcachedPtr() + : MemcachedPtr(nullptr) + {} + ~MemcachedPtr() { + memcached_free(&memc); + } + memcached_st *operator * () { + return &memc; + } +}; + +class Malloced { + void *ptr; +public: + Malloced(void *ptr_) + : ptr{ptr_} + {} + ~Malloced() { + free(ptr); + } + void *operator *() { + return ptr; + } +}; diff --git a/testing/lib/random_.cpp b/testing/lib/random_.cpp deleted file mode 100644 index 3865fb1f..00000000 --- a/testing/lib/random_.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "random_.hpp" - -#include -#include -#include - -unsigned random_num(unsigned min, unsigned max) { - unsigned p; - getrandom(&p, sizeof(p), 0); - return (p % (max - min + 1)) + min; -} - -unsigned random_port() { - return random_num(34567, 65000); -} - -string random_socket() { - auto sock = tempnam(nullptr, "libmc"); - - if (!sock) { - perror("random_socket tempnam()"); - return {}; - } - - return 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() { - return random_num(0, 1) ? "-p" : "-s"; -} diff --git a/testing/lib/random_.hpp b/testing/lib/random_.hpp deleted file mode 100644 index f3f28b5b..00000000 --- a/testing/lib/random_.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include "Server.hpp" - -using namespace std; - -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(); - -inline Server::dyn_args_t::value_type random_socket_or_port_arg() { - return {random_socket_or_port_flag(), &random_socket_or_port_string}; -} diff --git a/testing/memcached/basic.cpp b/testing/memcached/basic.cpp new file mode 100644 index 00000000..8e6ccede --- /dev/null +++ b/testing/memcached/basic.cpp @@ -0,0 +1,28 @@ +#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 new file mode 100644 index 00000000..63cc0eda --- /dev/null +++ b/testing/memcached/callbacks.cpp @@ -0,0 +1,19 @@ +#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(reinterpret_cast(&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 new file mode 100644 index 00000000..016dabaa --- /dev/null +++ b/testing/memcached/dump.cpp @@ -0,0 +1,47 @@ +#include "../lib/common.hpp" +#include "../lib/MemcachedCluster.hpp" + +memcached_return_t dump_cb(const memcached_st *, const char *, size_t, void *ctx) { + size_t *c = reinterpret_cast(ctx); + ++(*c); + return MEMCACHED_SUCCESS; +} + +TEST_CASE("memcached dump") { + MemcachedCluster tests[]{ + MemcachedCluster::mixed(), + MemcachedCluster::net(), + 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) { + auto rc = memcached_set(memc, key, len, key, len, 0, 0); + INFO("last error: " << memcached_last_error(memc)); + REQUIRE(MEMCACHED_SUCCESS == rc); + } + } + + 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(MEMCACHED_SUCCESS == memcached_dump(memc, fn, &counter, 1)); + REQUIRE(counter == 64); + } + } + } +} diff --git a/testing/memcached/encoding_key.cpp b/testing/memcached/encoding_key.cpp new file mode 100644 index 00000000..cb7620ae --- /dev/null +++ b/testing/memcached/encoding_key.cpp @@ -0,0 +1,104 @@ +#include "../lib/common.hpp" +#include "../lib/MemcachedCluster.hpp" + +#define TEST_KEY LITERAL("test") +#define INITIAL_VAL LITERAL("initial") +#define REPLACED_VAL LITERAL("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") { + MemcachedCluster tests[]{ + MemcachedCluster::mixed(), + MemcachedCluster::net(), + MemcachedCluster::socket() + }; + + LOOPED_SECTION(tests) { + auto memc = &test.memc; + + SECTION("accepts encoding key") { + MemcachedPtr copy(memc); + + REQUIRE(MEMCACHED_SUCCESS == + memcached_set_encoding_key(memc, LITERAL(__func__))); + + SECTION("sets encoded value") { + REQUIRE(MEMCACHED_SUCCESS == + memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0)); + + SECTION("gets encoded value") { + check(memc, ©.memc, INITIAL_VAL); + } + + SECTION("cloned gets encoded value") { + MemcachedPtr dupe(memc); + + check(&dupe.memc, ©.memc, INITIAL_VAL); + } + } + + SECTION("adds encoded value") { + + REQUIRE(MEMCACHED_SUCCESS == + memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0)); + REQUIRE(MEMCACHED_NOTSTORED == + memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0)); + + check(memc, ©.memc, INITIAL_VAL); + + test.flush(); + + REQUIRE(MEMCACHED_SUCCESS == + memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0)); + + SECTION("gets encoded value") { + check(memc, ©.memc, REPLACED_VAL); + } + } + + SECTION("replaces encoded value") { + REQUIRE(MEMCACHED_SUCCESS == + memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0)); + + check(memc, ©.memc, INITIAL_VAL); + + REQUIRE(MEMCACHED_SUCCESS == + memcached_replace(memc, TEST_KEY, REPLACED_VAL, 0, 0)); + + SECTION("gets encoded value") { + check(memc, ©.memc, REPLACED_VAL); + } + } + + SECTION("unsupported") { + REQUIRE(MEMCACHED_NOT_SUPPORTED == + memcached_increment(memc, TEST_KEY, 0, nullptr)); + REQUIRE(MEMCACHED_NOT_SUPPORTED == + memcached_decrement(memc, TEST_KEY, 0, nullptr)); + REQUIRE(MEMCACHED_NOT_SUPPORTED == + memcached_increment_with_initial(memc, TEST_KEY, 0, 0, + 0, nullptr)); + REQUIRE(MEMCACHED_NOT_SUPPORTED == + memcached_decrement_with_initial(memc, TEST_KEY, 0, 0, + 0, nullptr)); + REQUIRE(MEMCACHED_NOT_SUPPORTED == + memcached_append(memc, TEST_KEY, REPLACED_VAL, 0, 0)); + REQUIRE(MEMCACHED_NOT_SUPPORTED == + memcached_prepend(memc, TEST_KEY, REPLACED_VAL, 0, 0)); + } + } + } +} diff --git a/testing/memcached/exist.cpp b/testing/memcached/exist.cpp new file mode 100644 index 00000000..663deecb --- /dev/null +++ b/testing/memcached/exist.cpp @@ -0,0 +1,62 @@ +#include "../lib/common.hpp" +#include "../lib/MemcachedCluster.hpp" + +TEST_CASE("memcached exist") { + MemcachedCluster tests[]{ + MemcachedCluster::mixed(), + MemcachedCluster::net(), + MemcachedCluster::socket() + }; + + tests[0].enableBinary(); + + LOOPED_SECTION(tests) { + auto memc = &test.memc; + + SECTION("initial not found") { + REQUIRE( + MEMCACHED_NOTFOUND == memcached_exist(memc, LITERAL("frog"))); + } + + SECTION("set found") { + REQUIRE(MEMCACHED_SUCCESS == + memcached_set(memc, LITERAL("frog"), LITERAL("frog"), 0, + 0)); + REQUIRE( + MEMCACHED_SUCCESS == memcached_exist(memc, LITERAL("frog"))); + + SECTION("deleted not found") { + REQUIRE(MEMCACHED_SUCCESS == + memcached_delete(memc, LITERAL("frog"), 0)); + REQUIRE(MEMCACHED_NOTFOUND == + memcached_exist(memc, LITERAL("frog"))); + } + } + + SECTION("by key") { + SECTION("initial not found") { + REQUIRE(MEMCACHED_NOTFOUND == + memcached_exist_by_key(memc, LITERAL("master"), + LITERAL("frog"))); + } + + SECTION("set found") { + REQUIRE(MEMCACHED_SUCCESS == + memcached_set_by_key(memc, LITERAL("master"), + LITERAL("frog"), LITERAL("frog"), 0, 0)); + REQUIRE(MEMCACHED_SUCCESS == + memcached_exist_by_key(memc, LITERAL("master"), + LITERAL("frog"))); + + SECTION("deleted not found") { + REQUIRE(MEMCACHED_SUCCESS == + memcached_delete_by_key(memc, LITERAL("master"), + LITERAL("frog"), 0)); + REQUIRE(MEMCACHED_NOTFOUND == + memcached_exist_by_key(memc, LITERAL("master"), + LITERAL("frog"))); + } + } + } + } +} diff --git a/testing/memcached/servers.cpp b/testing/memcached/servers.cpp new file mode 100644 index 00000000..69f76bd3 --- /dev/null +++ b/testing/memcached/servers.cpp @@ -0,0 +1,40 @@ +#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, LITERAL("key"), 1, nullptr)); + } +} diff --git a/testing/server.cpp b/testing/server.cpp deleted file mode 100644 index 0d2469db..00000000 --- a/testing/server.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "lib/catch.hpp" -#include "lib/Cluster.hpp" - -#include "lib/random_.hpp" - -TEST_CASE("Server") { - Server server{"memcached"}; - - SECTION("starts and listens") { - - REQUIRE(server.start().has_value()); - REQUIRE(server.isListening()); - - SECTION("stops") { - - REQUIRE(server.stop()); - - SECTION("is waitable") { - - REQUIRE(server.wait()); - - SECTION("stopped") { - REQUIRE(server.is) - } - } - } - } -} - -TEST_CASE("Cluster") { - Cluster cluster{Server{"memcached", { - random_socket_or_port_arg(), - }}}; - - SECTION("starts and listens") { - - REQUIRE(cluster.start()); - REQUIRE(cluster.isListening()); - - SECTION("stops") { - - cluster.stop(); - cluster.wait(); - - SECTION("stopped") { - - REQUIRE(cluster.isStopped()); - } - } - } -}