{
opt_servers= strdup(temp);
}
- else if (argc >= 1 and argv[--argc])
+ else if (argc > 1 and argv[--argc])
{
opt_servers= strdup(argv[argc]);
}
file(GLOB_RECURSE TESTING_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
-
+set(TESTING_ROOT ${CMAKE_CURRENT_BINARY_DIR})
+configure_file(conf.h.in conf.h @ONLY)
add_executable(catch_main ${TESTING_SRC})
set_target_properties(catch_main PROPERTIES CXX_STANDARD 17)
target_link_libraries(catch_main libhashkit libmemcached libmemcachedutil)
+add_dependencies(catch_main ${CLIENTS})
--- /dev/null
+#pragma once
+
+#cmakedefine TESTING_ROOT "@TESTING_ROOT@"
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);
-}
#include "common.hpp"
#include "Cluster.hpp"
+#include "ReturnMatcher.hpp"
-class ReturnMatcher : public Catch::MatcherBase<memcached_return_t> {
-public:
- explicit ReturnMatcher(const memcached_st *memc_, memcached_return_t expected_ = MEMCACHED_SUCCESS)
- : memc{memc_}
- , expected{expected_}
- {}
-
- ReturnMatcher(const ReturnMatcher &) = default;
- ReturnMatcher &operator = (const ReturnMatcher &) = default;
-
- 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;
-
-private:
- const memcached_st *memc;
- memcached_return_t expected{MEMCACHED_SUCCESS};
-};
-
-class LoneReturnMatcher {
-public:
- ReturnMatcher returns;
- explicit LoneReturnMatcher(const memcached_st *memc) : returns{memc}
- {}
-};
class MemcachedCluster {
public:
--- /dev/null
+#include "ReturnMatcher.hpp"
+
+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);
+}
--- /dev/null
+#pragma once
+
+#include "testing/lib/common.hpp"
+
+class ReturnMatcher : public Catch::MatcherBase<memcached_return_t> {
+public:
+ explicit ReturnMatcher(const memcached_st *memc_, memcached_return_t expected_ = MEMCACHED_SUCCESS)
+ : memc{memc_}
+ , expected{expected_}
+ {}
+
+ ReturnMatcher(const ReturnMatcher &) = default;
+ ReturnMatcher &operator = (const ReturnMatcher &) = default;
+
+ 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;
+
+private:
+ const memcached_st *memc;
+ memcached_return_t expected{MEMCACHED_SUCCESS};
+};
+
+class LoneReturnMatcher {
+public:
+ ReturnMatcher returns;
+ explicit LoneReturnMatcher(const memcached_st *memc) : returns{memc}
+ {}
+};
--- /dev/null
+#include "Shell.hpp"
+
+#include <cstdlib>
+#include <unistd.h>
+
+bool Shell::run(const string &command_, string &output) {
+ auto command = prepareCommand(command_);
+ auto *file = popen(command.c_str(), "r");
+
+ if (!file) {
+ perror("Shell::run popen()");
+ return false;
+ }
+
+ do {
+ char data[1U<<12U];
+ auto read = fread(data, 1, sizeof(data), file);
+
+ if (read) {
+ output.append(data, read);
+ }
+ if (ferror(file)) {
+ cerr << "Shell::run read(): " << strerror(ferror(file));
+ break;
+ }
+ } while (!feof(file));
+
+ auto error = ferror(file);
+ auto status = pclose(file);
+ return !error && !status;
+}
+
+bool Shell::run(const string &command) {
+ auto error = system(prepareCommand(command).c_str());
+ if (error == -1) {
+ perror("Shell::run system()");
+ return false;
+ }
+ return !error;
+}
+
+Shell::Shell(bool redirect_stderr)
+: redirect{redirect_stderr}
+{
+ if (!system(nullptr)) {
+ throw runtime_error("no shell available");
+ }
+}
+
+Shell::Shell(const string &prefix_, bool redirect_stderr)
+: prefix{prefix_}
+, redirect{redirect_stderr}
+{
+ if (!system(nullptr)) {
+ throw runtime_error("no shell available");
+ }
+}
+
+string Shell::prepareCommand(const string &command_) {
+ string command;
+ if (prefix.length()) {
+ command.append(prefix);
+ command.append("/");
+ }
+ command.append(command_);
+ if (redirect) {
+ command.append(" 2>&1");
+ }
+ INFO("Prepared command: " << command);
+ return command;
+}
--- /dev/null
+#pragma once
+
+#include "testing/lib/common.hpp"
+
+class Shell {
+public:
+ explicit Shell(bool redirect_stderr = true);
+ explicit Shell(const string &prefix, bool redirect_stderr = true);
+ bool run(const string &command, string &output);
+ bool run(const string &command);
+private:
+ string prefix;
+ bool redirect;
+
+ string prepareCommand(const string &command_);
+};
#include <variant>
#include <vector>
+#include "testing/conf.h"
#include "testing/lib/catch.hpp"
#include "testing/lib/random.hpp"
return rc;
}
+#include <cstdlib>
+#include <unistd.h>
+
+class Tempfile {
+public:
+ explicit Tempfile(const char templ_[] = "memc.test.XXXXXX") {
+ *copy(S(templ_)+templ_, fn) = '\0';
+ fd = mkstemp(fn);
+ }
+ ~Tempfile() {
+ close(fd);
+ unlink(fn);
+ }
+ int getFd() const {
+ return fd;
+ }
+ const char *getFn() const {
+ return fn;
+ }
+ bool put(const char *buf, size_t len) const {
+ return static_cast<ssize_t >(len) == write(fd, buf, len);
+ }
+private:
+ char fn[80];
+ int fd;
+};
+
class MemcachedPtr {
public:
memcached_st *memc;
goto retry;
}
+string random_port_string(const string &) {
+ return to_string(random_port());
+}
+
string random_socket(const string &prefix) {
return prefix + to_string(random_num(1U, UINT32_MAX)) + "@" + to_string(getpid()) + ".sock";
}
random_ascii_string(random_num(minlen, maxlen))
};
}
-
enable_if_t<is_integral_v<T>, T> random_num(T min, T max);
unsigned random_port();
+string random_port_string(const string &);
char random_ascii(char min = '!', char max = '~');
string random_ascii_string(size_t len, char min = '!', char max = '~');
--- /dev/null
+#include "testing/lib/common.hpp"
+#include "testing/lib/Shell.hpp"
+#include "testing/lib/Server.hpp"
+#include "testing/lib/Retry.hpp"
+#include "testing/lib/ReturnMatcher.hpp"
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("memcat") {
+ Shell sh{string{TESTING_ROOT "/../src/bin"}};
+
+ SECTION("no servers provided") {
+ string output;
+ REQUIRE_FALSE(sh.run("memcat", output));
+ REQUIRE(output == "No servers provided\n");
+ }
+
+ SECTION("--help") {
+ string output;
+ REQUIRE(sh.run("memcat --help", output));
+ REQUIRE_THAT(output, Contains("memcat"));
+ REQUIRE_THAT(output, Contains("v1"));
+ REQUIRE_THAT(output, Contains("help"));
+ REQUIRE_THAT(output, Contains("version"));
+ REQUIRE_THAT(output, Contains("option"));
+ REQUIRE_THAT(output, Contains("--"));
+ REQUIRE_THAT(output, Contains("="));
+ }
+
+ SECTION("with server") {
+ Server server{"memcached"};
+ MemcachedPtr memc;
+ LoneReturnMatcher test{*memc};
+
+ server.start();
+ Retry{[&server] { return server.isListening(); }}();
+ auto port = get<int>(server.getSocketOrPort());
+ auto comm = "memcat --servers=localhost:" + to_string(port) + " ";
+
+ REQUIRE_SUCCESS(memcached_server_add(*memc, "localhost", port));
+
+ SECTION("found") {
+ REQUIRE_SUCCESS(memcached_set(*memc, S("memcat"), S("MEMCAT-SET"), 0, 0));
+
+ string output;
+ REQUIRE(sh.run(comm + "memcat", output));
+ REQUIRE(output == "MEMCAT-SET\n");
+ }
+
+ SECTION("not found") {
+ memcached_delete(*memc, S("memcat"), 0);
+
+ string output;
+ REQUIRE_FALSE(sh.run(comm + "memcat", output));
+ REQUIRE_THAT(output, !Contains("MEMCAT-SET"));
+ REQUIRE_THAT(output, Contains("NOT FOUND"));
+ }
+ }
+}
--- /dev/null
+#include "testing/lib/common.hpp"
+#include "testing/lib/Shell.hpp"
+#include "testing/lib/Server.hpp"
+#include "testing/lib/Retry.hpp"
+#include "testing/lib/ReturnMatcher.hpp"
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("memcp") {
+ Shell sh{string{TESTING_ROOT "/../src/bin"}};
+
+ SECTION("no servers provided") {
+ string output;
+ REQUIRE_FALSE(sh.run("memcp nonexistent", output));
+ REQUIRE(output == "No Servers provided\n");
+ }
+
+ SECTION("--help") {
+ string output;
+ REQUIRE(sh.run("memcp --help", output));
+ REQUIRE_THAT(output, Contains("memcp"));
+ REQUIRE_THAT(output, Contains("v1"));
+ REQUIRE_THAT(output, Contains("help"));
+ REQUIRE_THAT(output, Contains("version"));
+ REQUIRE_THAT(output, Contains("option"));
+ REQUIRE_THAT(output, Contains("--"));
+ REQUIRE_THAT(output, Contains("="));
+ }
+
+ SECTION("with server") {
+ Server server{"memcached"};
+ MemcachedPtr memc;
+ LoneReturnMatcher test{*memc};
+
+ server.start();
+ Retry{[&server] { return server.isListening(); }}();
+ auto port = get<int>(server.getSocketOrPort());
+ auto comm = "memcp --servers=localhost:" + to_string(port) + " ";
+
+ REQUIRE_SUCCESS(memcached_server_add(*memc, "localhost", port));
+
+ SECTION("okay") {
+ Tempfile temp;
+ temp.put(S("123"));
+
+ string output;
+ REQUIRE(sh.run(comm + temp.getFn(), output));
+ REQUIRE(output == "");
+
+ size_t len;
+ memcached_return_t rc;
+ Malloced val(memcached_get(*memc, S(temp.getFn()), &len, nullptr, &rc));
+
+ REQUIRE(*val);
+ REQUIRE_SUCCESS(rc);
+ REQUIRE(string(*val, len) == "123");
+ }
+
+ SECTION("connection failure") {
+ server.signal(SIGKILL);
+
+ Tempfile temp;
+
+ string output;
+ REQUIRE_FALSE(sh.run(comm + temp.getFn(), output));
+ REQUIRE_THAT(output, Contains("CONNECTION FAILURE"));
+ }
+
+ SECTION("file not found") {
+ string output;
+ REQUIRE_FALSE(sh.run(comm + "nonexistent", output));
+ REQUIRE_THAT(output, Contains("No such file or directory"));
+ }
+ }
+}
--- /dev/null
+#include "testing/lib/common.hpp"
+#include "testing/lib/Shell.hpp"
+#include "testing/lib/Server.hpp"
+#include "testing/lib/Retry.hpp"
+#include "testing/lib/ReturnMatcher.hpp"
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("memdump") {
+ Shell sh{string{TESTING_ROOT "/../src/bin"}};
+
+ SECTION("no servers provided") {
+ string output;
+ REQUIRE_FALSE(sh.run("memdump", output));
+ REQUIRE(output == "No Servers provided\n");
+ }
+
+ SECTION("--help") {
+ string output;
+ REQUIRE(sh.run("memdump --help", output));
+ REQUIRE_THAT(output, Contains("memdump"));
+ REQUIRE_THAT(output, Contains("v1"));
+ REQUIRE_THAT(output, Contains("help"));
+ REQUIRE_THAT(output, Contains("version"));
+ REQUIRE_THAT(output, Contains("option"));
+ REQUIRE_THAT(output, Contains("--"));
+ REQUIRE_THAT(output, Contains("="));
+ }
+
+ SECTION("with server") {
+ Server server{"memcached"};
+ MemcachedPtr memc;
+ LoneReturnMatcher test{*memc};
+
+ server.start();
+ Retry{[&server] { return server.isListening(); }}();
+ auto port = get<int>(server.getSocketOrPort());
+ auto comm = "memdump --servers=localhost:" + to_string(port) + " ";
+
+ REQUIRE_SUCCESS(memcached_server_add(*memc, "localhost", port));
+
+ SECTION("okay") {
+
+ REQUIRE_SUCCESS(memcached_set(*memc, S("key1"), S("val1"), 0, 0));
+ REQUIRE_SUCCESS(memcached_set(*memc, S("key2"), S("val2"), 0, 0));
+
+ string output;
+ REQUIRE(sh.run(comm, output));
+ REQUIRE_THAT(output, Contains("key1") && Contains("key2"));
+ }
+
+ SECTION("connection failure") {
+ server.signal(SIGKILL);
+
+ string output;
+ REQUIRE_FALSE(sh.run(comm + "-v", output));
+ REQUIRE_THAT(output, Contains("CONNECTION FAILURE") || Contains("SERVER HAS FAILED"));
+ }
+
+ SECTION("empty") {
+ string output;
+ REQUIRE(sh.run(comm, output));
+ REQUIRE(output == "");
+ }
+ }
+}
--- /dev/null
+#include "testing/lib/common.hpp"
+#include "testing/lib/Shell.hpp"
+#include "testing/lib/Server.hpp"
+#include "testing/lib/Retry.hpp"
+#include "testing/lib/ReturnMatcher.hpp"
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("memerror") {
+ Shell sh{string{TESTING_ROOT "/../src/bin"}};
+
+ SECTION("--help") {
+ string output;
+ REQUIRE(sh.run("memerror --help", output));
+ REQUIRE_THAT(output, Contains("memerror"));
+ REQUIRE_THAT(output, Contains("v1"));
+ REQUIRE_THAT(output, Contains("help"));
+ REQUIRE_THAT(output, Contains("version"));
+ REQUIRE_THAT(output, Contains("option"));
+ REQUIRE_THAT(output, Contains("--"));
+ REQUIRE_THAT(output, Contains("="));
+ }
+
+ SECTION("valid error codes") {
+ for (underlying_type_t<memcached_return_t> rc = MEMCACHED_SUCCESS; rc < MEMCACHED_MAXIMUM_RETURN; ++rc) {
+ string output;
+ REQUIRE(sh.run("memerror " + to_string(rc), output));
+ CHECK(output == memcached_strerror(nullptr, static_cast<memcached_return_t>(rc)) + string{"\n"});
+ }
+ }
+
+ SECTION("unknown error code") {
+ string output;
+ REQUIRE_FALSE(sh.run("memerror " + to_string(MEMCACHED_MAXIMUM_RETURN), output));
+ REQUIRE_THAT(output, Contains("INVALID memcached_return_t"));
+ }
+}
--- /dev/null
+#include "testing/lib/common.hpp"
+#include "testing/lib/Shell.hpp"
+#include "testing/lib/Server.hpp"
+#include "testing/lib/Retry.hpp"
+#include "testing/lib/ReturnMatcher.hpp"
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("memexist") {
+ Shell sh{string{TESTING_ROOT "/../src/bin"}};
+
+ SECTION("no servers provided") {
+ string output;
+ REQUIRE_FALSE(sh.run("memexist", output));
+ REQUIRE(output == "No Servers provided\n");
+ }
+
+ SECTION("--help") {
+ string output;
+ REQUIRE(sh.run("memexist --help", output));
+ REQUIRE_THAT(output, Contains("memexist"));
+ REQUIRE_THAT(output, Contains("v1"));
+ REQUIRE_THAT(output, Contains("help"));
+ REQUIRE_THAT(output, Contains("version"));
+ REQUIRE_THAT(output, Contains("option"));
+ REQUIRE_THAT(output, Contains("--"));
+ REQUIRE_THAT(output, Contains("="));
+ }
+
+ SECTION("with server") {
+ Server server{"memcached", {"-p", random_port_string}};
+ MemcachedPtr memc;
+ LoneReturnMatcher test{*memc};
+
+ server.start();
+ Retry{[&server] { return server.isListening(); }}();
+ auto port = get<int>(server.getSocketOrPort());
+ auto comm = "memexist --servers=localhost:" + to_string(port) + " ";
+
+ REQUIRE_SUCCESS(memcached_server_add(*memc, "localhost", port));
+
+ SECTION("found") {
+ REQUIRE_SUCCESS(memcached_set(*memc, S("memexist"), S("MEMEXIST-SET"), 0, 0));
+
+ string output;
+ REQUIRE(sh.run(comm + "memexist", output));
+ REQUIRE(output.empty());
+ }
+
+ SECTION("not found") {
+ memcached_delete(*memc, S("memexist"), 0);
+
+ string output;
+ REQUIRE_FALSE(sh.run(comm + "memexist", output));
+ REQUIRE(output.empty());
+ }
+ }
+}