flush
authorMichael Wallner <mike@php.net>
Fri, 11 Sep 2020 11:30:32 +0000 (13:30 +0200)
committerMichael Wallner <mike@php.net>
Fri, 11 Sep 2020 11:30:32 +0000 (13:30 +0200)
17 files changed:
src/bin/memdump.cc
testing/CMakeLists.txt
testing/conf.h.in [new file with mode: 0644]
testing/lib/MemcachedCluster.cpp
testing/lib/MemcachedCluster.hpp
testing/lib/ReturnMatcher.cpp [new file with mode: 0644]
testing/lib/ReturnMatcher.hpp [new file with mode: 0644]
testing/lib/Shell.cpp [new file with mode: 0644]
testing/lib/Shell.hpp [new file with mode: 0644]
testing/lib/common.hpp
testing/lib/random.cpp
testing/lib/random.hpp
testing/tests/bin/memcat.cpp [new file with mode: 0644]
testing/tests/bin/memcp.cpp [new file with mode: 0644]
testing/tests/bin/memdump.cpp [new file with mode: 0644]
testing/tests/bin/memerror.cpp [new file with mode: 0644]
testing/tests/bin/memexist.cpp [new file with mode: 0644]

index 8e12066ba20e6103147d9732133bdd96d707dac5..cf99027b8f504bc2c9b6de657854c6f0fb316be5 100644 (file)
@@ -70,7 +70,7 @@ int main(int argc, char *argv[])
     {
       opt_servers= strdup(temp);
     }
-    else if (argc >= 1 and argv[--argc])
+    else if (argc > 1 and argv[--argc])
     {
       opt_servers= strdup(argv[argc]);
     }
index 145e6ada2e6b89f867c062c0da3a2800f4f03cf3..9f2239a7be255e40b9e441e62649f3fda528b226 100644 (file)
@@ -1,6 +1,8 @@
 
 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})
diff --git a/testing/conf.h.in b/testing/conf.h.in
new file mode 100644 (file)
index 0000000..e06af91
--- /dev/null
@@ -0,0 +1,3 @@
+#pragma once
+
+#cmakedefine TESTING_ROOT "@TESTING_ROOT@"
index 6b9af3e39e4672909f7152a87a159fc8a1c79ec6..6473614908a66a2c15a259ca1630f05cc3408897 100644 (file)
@@ -82,30 +82,3 @@ MemcachedCluster &MemcachedCluster::operator=(MemcachedCluster &&mc) {
   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 7286e69f9424c0141e549dcdac815347e5e45c09..ea6aa3467f650af197a6085fa949765856426dbe 100644 (file)
@@ -2,38 +2,8 @@
 
 #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:
diff --git a/testing/lib/ReturnMatcher.cpp b/testing/lib/ReturnMatcher.cpp
new file mode 100644 (file)
index 0000000..863945e
--- /dev/null
@@ -0,0 +1,28 @@
+#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);
+}
diff --git a/testing/lib/ReturnMatcher.hpp b/testing/lib/ReturnMatcher.hpp
new file mode 100644 (file)
index 0000000..286bc67
--- /dev/null
@@ -0,0 +1,35 @@
+#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}
+  {}
+};
diff --git a/testing/lib/Shell.cpp b/testing/lib/Shell.cpp
new file mode 100644 (file)
index 0000000..cd3017a
--- /dev/null
@@ -0,0 +1,71 @@
+#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;
+}
diff --git a/testing/lib/Shell.hpp b/testing/lib/Shell.hpp
new file mode 100644 (file)
index 0000000..03b60d7
--- /dev/null
@@ -0,0 +1,16 @@
+#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_);
+};
index 9f9578224d48581a798a6b717c9d600b35ea4641..17199839495be35da7fecec89893e65718387215 100644 (file)
@@ -11,6 +11,7 @@
 #include <variant>
 #include <vector>
 
+#include "testing/conf.h"
 #include "testing/lib/catch.hpp"
 #include "testing/lib/random.hpp"
 
@@ -55,6 +56,33 @@ inline memcached_return_t fetch_all_results(memcached_st *memc, unsigned int &ke
   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;
index a238c743dd3c71bc3f823159db0ce6f220dc75d1..f312d8bdb9211eb4f9f99e0f1fb5bf88832009cd 100644 (file)
@@ -33,6 +33,10 @@ unsigned random_port() {
   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";
 }
@@ -74,4 +78,3 @@ pair<string, string> random_ascii_pair(size_t minlen, size_t maxlen) {
       random_ascii_string(random_num(minlen, maxlen))
   };
 }
-
index 208b09f43daac11ff5965a24e3471bff939424d5..06fc5ef69064fbc456407c0ae69de5b8b8f19416 100644 (file)
@@ -13,6 +13,7 @@ template<typename T>
 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 = '~');
diff --git a/testing/tests/bin/memcat.cpp b/testing/tests/bin/memcat.cpp
new file mode 100644 (file)
index 0000000..e09bf28
--- /dev/null
@@ -0,0 +1,59 @@
+#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"));
+    }
+  }
+}
diff --git a/testing/tests/bin/memcp.cpp b/testing/tests/bin/memcp.cpp
new file mode 100644 (file)
index 0000000..6c69a50
--- /dev/null
@@ -0,0 +1,75 @@
+#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"));
+    }
+  }
+}
diff --git a/testing/tests/bin/memdump.cpp b/testing/tests/bin/memdump.cpp
new file mode 100644 (file)
index 0000000..fe30233
--- /dev/null
@@ -0,0 +1,66 @@
+#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 == "");
+    }
+  }
+}
diff --git a/testing/tests/bin/memerror.cpp b/testing/tests/bin/memerror.cpp
new file mode 100644 (file)
index 0000000..26bb6cf
--- /dev/null
@@ -0,0 +1,37 @@
+#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"));
+  }
+}
diff --git a/testing/tests/bin/memexist.cpp b/testing/tests/bin/memexist.cpp
new file mode 100644 (file)
index 0000000..d887790
--- /dev/null
@@ -0,0 +1,58 @@
+#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());
+    }
+  }
+}