From: Michael Wallner Date: Mon, 28 Sep 2020 15:26:50 +0000 (+0200) Subject: testing: fix osx X-Git-Tag: 1.1.0-beta1~236^2~51 X-Git-Url: https://git.m6w6.name/?a=commitdiff_plain;h=9f262c9ea92d4869715ca6f534c80075a8310ac1;p=m6w6%2Flibmemcached testing: fix osx * move from testing/ to test/; CTest generates a Testing/ directory which creates ambiguities on case insensitive file systems * check for pipe2(), SOCK_CLOEXEC and SOCK_NONBLOCK * test for the O_CLOEXEC pipe already being closed by traing to read() it (see pipe(2)) --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b76f688..83bdaa01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ if(NOT BUILD_DOCSONLY) add_subdirectory(include) add_subdirectory(src) add_subdirectory(support) - add_subdirectory(testing) + add_subdirectory(test) # keep last configure_file(src/mem_config.h.in ${AUTOHEADER_FILE} @ONLY) diff --git a/test/CMake/Catch.cmake b/test/CMake/Catch.cmake new file mode 100644 index 00000000..486e3233 --- /dev/null +++ b/test/CMake/Catch.cmake @@ -0,0 +1,175 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +Catch +----- + +This module defines a function to help use the Catch test framework. + +The :command:`catch_discover_tests` discovers tests by asking the compiled test +executable to enumerate its tests. This does not require CMake to be re-run +when tests change. However, it may not work in a cross-compiling environment, +and setting test properties is less convenient. + +This command is intended to replace use of :command:`add_test` to register +tests, and will create a separate CTest test for each Catch test case. Note +that this is in some cases less efficient, as common set-up and tear-down logic +cannot be shared by multiple test cases executing in the same instance. +However, it provides more fine-grained pass/fail information to CTest, which is +usually considered as more beneficial. By default, the CTest test name is the +same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: catch_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + catch_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [TEST_LIST var] + ) + + ``catch_discover_tests`` sets up a post-build command on the test executable + that generates the list of tests by parsing the output from running the test + with the ``--list-test-names-only`` argument. This ensures that the full + list of tests is obtained. Since test discovery occurs at build time, it is + not necessary to re-run CMake when the list of tests changes. + However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set + in order to function in a cross-compiling environment. + + Additionally, setting properties on tests is somewhat less convenient, since + the tests are not available at CMake time. Additional test properties may be + assigned to the set of tests as a whole using the ``PROPERTIES`` option. If + more fine-grained test control is needed, custom content may be provided + through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` + directory property. The set of discovered tests is made accessible to such a + script via the ``_TESTS`` variable. + + The options are: + + ``target`` + Specifies the Catch executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the Catch executable with the ``--list-test-names-only`` argument. + + ``EXTRA_ARGS arg1...`` + Any extra arguments to pass on the command line to each test case. + + ``WORKING_DIRECTORY dir`` + Specifies the directory in which to run the discovered test cases. If this + option is not provided, the current binary directory is used. + + ``TEST_PREFIX prefix`` + Specifies a ``prefix`` to be prepended to the name of each discovered test + case. This can be useful when the same test executable is being used in + multiple calls to ``catch_discover_tests()`` but with different + ``TEST_SPEC`` or ``EXTRA_ARGS``. + + ``TEST_SUFFIX suffix`` + Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of + every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may + be specified. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``catch_discover_tests``. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``_TESTS``. This can be useful when the same test + executable is being used in multiple calls to ``catch_discover_tests()``. + Note that this variable is only available in CTest. + +#]=======================================================================] + +#------------------------------------------------------------------------------ +function(catch_discover_tests TARGET) + cmake_parse_arguments( + "" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" + ${ARGN} + ) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + ## Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + get_property(crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR + ) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=${TARGET}" + -D "TEST_EXECUTABLE=$" + -D "TEST_EXECUTOR=${crosscompiling_emulator}" + -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" + -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" + -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_PREFIX=${_TEST_PREFIX}" + -D "TEST_SUFFIX=${_TEST_SUFFIX}" + -D "TEST_LIST=${_TEST_LIST}" + -D "CTEST_FILE=${ctest_tests_file}" + -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM + ) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + if (NOT ${test_include_file_set}) + set_property(DIRECTORY + PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" + ) + else() + message(FATAL_ERROR + "Cannot set more than one TEST_INCLUDE_FILE" + ) + endif() + endif() + +endfunction() + +############################################################################### + +set(_CATCH_DISCOVER_TESTS_SCRIPT + ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake +) diff --git a/test/CMake/CatchAddTests.cmake b/test/CMake/CatchAddTests.cmake new file mode 100644 index 00000000..156d55ff --- /dev/null +++ b/test/CMake/CatchAddTests.cmake @@ -0,0 +1,81 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +set(prefix "${TEST_PREFIX}") +set(suffix "${TEST_SUFFIX}") +set(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(script) +set(suite) +set(tests) + +function(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument + else() + set(_args "${_args} ${_arg}") + endif() + endforeach() + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) +endfunction() + +# Run test executable to get list of available tests +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR + "Specified test executable '${TEST_EXECUTABLE}' does not exist" + ) +endif() +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only + OUTPUT_VARIABLE output + RESULT_VARIABLE result +) +# Catch --list-test-names-only reports the number of tests, so 0 is... surprising +if(${result} EQUAL 0) + message(WARNING + "Test executable '${TEST_EXECUTABLE}' contains no tests!\n" + ) +elseif(${result} LESS 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" + " Output: ${output}\n" + ) +endif() + +string(REPLACE "\n" ";" output "${output}") + +# Parse output +foreach(line ${output}) + set(test ${line}) + # Escape characters in test case names that would be parsed by Catch2 + set(test_name ${test}) + foreach(char , [ ]) + string(REPLACE ${char} "\\${char}" test_name ${test_name}) + endforeach(char) + # ...and add to script + add_command(add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "${test_name}" + ${extra_args} + ) + add_command(set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ${properties} + ) + list(APPEND tests "${prefix}${test}${suffix}") +endforeach() + +# Create a list of all discovered tests, which users may use to e.g. set +# properties on the tests +add_command(set ${TEST_LIST} ${tests}) + +# Write CTest script +file(WRITE "${CTEST_FILE}" "${script}") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..dbc7aecb --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,32 @@ +if(NOT BUILD_TESTING OR NOT CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + return() +endif() + +include(CMake/Catch.cmake) + +check_decl(pipe2 unistd.h) +check_decl(SOCK_NONBLOCK sys/socket.h) +check_decl(SOCK_CLOEXEC sys/socket.h) + +file(GLOB_RECURSE TESTING_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) +set(TESTING_ROOT ${CMAKE_CURRENT_BINARY_DIR}) +set_source_files_properties(main.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) +configure_file(conf.h.in conf.h @ONLY) +add_executable(runtests ${TESTING_SRC}) +set_target_properties(runtests PROPERTIES CXX_STANDARD 17) +target_include_directories(runtests PRIVATE + ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src) +target_link_libraries(runtests PRIVATE libhashkit libmemcached libmemcachedutil) +add_dependencies(runtests ${CLIENTS}) + +catch_discover_tests(runtests + TEST_SPEC "lib*") +catch_discover_tests(runtests + TEST_SPEC "hashkit*") +catch_discover_tests(runtests + TEST_SPEC "memcached*") +catch_discover_tests(runtests + TEST_SPEC "bin/*") diff --git a/test/conf.h.in b/test/conf.h.in new file mode 100644 index 00000000..91843964 --- /dev/null +++ b/test/conf.h.in @@ -0,0 +1,8 @@ +#pragma once + +#cmakedefine HAVE_PIPE2 1 +#cmakedefine HAVE_SOCK_NONBLOCK 1 +#cmakedefine HAVE_SOCK_CLOEXEC 1 + +#cmakedefine TESTING_ROOT "@TESTING_ROOT@" +#cmakedefine MEMCACHED_BINARY getenv_else("MEMCACHED_BINARY", "@MEMCACHED_BINARY@") diff --git a/test/fixtures/hashes.hpp b/test/fixtures/hashes.hpp new file mode 100644 index 00000000..c3e344f2 --- /dev/null +++ b/test/fixtures/hashes.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include "mem_config.h" + +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 + { 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 }, +#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 }, + + // murmur3 +#ifdef HAVE_MURMUR_HASH + { 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 +}; + diff --git a/test/lib/Cluster.cpp b/test/lib/Cluster.cpp new file mode 100644 index 00000000..c3c625cf --- /dev/null +++ b/test/lib/Cluster.cpp @@ -0,0 +1,110 @@ +#include "Cluster.hpp" +#include "Retry.hpp" + +#include + +Cluster::Cluster(Server serv, uint16_t cnt) +: count{cnt} +, proto{move(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(); + for (int i = 0; i < count; ++i) { + cluster.push_back(proto); + } +} + +bool Cluster::start() { + bool started = true; + + for (auto &server : cluster) { + if (!startServer(server)) { + started = false; + } + } + + return started; +} + +void Cluster::stop() { + for (auto &server : cluster) { + server.drain(); + // no cookies for memcached; TERM is just too slow + server.signal(SIGKILL); + } +} + +bool Cluster::isStopped() { + for (auto &server : cluster) { + if (server.getPid() && !server.tryWait()) { + return false; + } + } + return true; +} + +bool Cluster::isListening() { + for (auto &server : cluster) { + Retry server_is_listening{[&] { + if (!server.isListening()) { + // zombie? + auto old_pid = server.getPid(); + if (server.tryWait()) { + cerr << "Collected zombie " << server << "(old pid=" << old_pid << ")\n"; + pids.erase(old_pid); + // restart + startServer(server); + } + if (!server.isListening()) { + return false; + } + } + return true; + }}; + if (!server_is_listening()) { + return false; + } + } + + return true; +} + +bool Cluster::startServer(Server &server) { + if (server.start().has_value()) { + pids[server.getPid()] = &server; + return true; + } + return false; +} + +void Cluster::wait() { + siginfo_t inf; + + while (!isStopped()) { + if (waitid(P_ALL, 0, &inf, WEXITED | WNOWAIT)) { + perror("Cluster::wait waitid()"); + return; + } + + auto server = pids.find(inf.si_pid); + if (server != pids.end()) { + server->second->wait(); + } + } +} diff --git a/test/lib/Cluster.hpp b/test/lib/Cluster.hpp new file mode 100644 index 00000000..1bbc9d22 --- /dev/null +++ b/test/lib/Cluster.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "common.hpp" +#include "Server.hpp" + +class Cluster { +public: + explicit + Cluster(Server serv, uint16_t cnt = 0); + + ~Cluster(); + + Cluster(const Cluster &c) = delete; + Cluster &operator = (const Cluster &c) = delete; + + Cluster(Cluster &&c) + : proto{} + { + *this = move(c); + }; + Cluster &operator = (Cluster &&c) { + count = exchange(c.count, 0); + proto = exchange(c.proto, Server{}); + cluster = exchange(c.cluster, {}); + pids = exchange(c.pids, {}); + return *this; + } + + const vector &getServers() const; + + bool start(); + void stop(); + void reset(); + bool isStopped(); + bool isListening(); + void wait(); + +private: + uint16_t count; + Server proto; + vector cluster; + map pids; + + bool startServer(Server &server); +}; diff --git a/test/lib/Connection.cpp b/test/lib/Connection.cpp new file mode 100644 index 00000000..05440723 --- /dev/null +++ b/test/lib/Connection.cpp @@ -0,0 +1,195 @@ +#include "Connection.hpp" + +#include +#include +#include +#include + +static inline int socket_ex(int af, int so, int pf, int fl) { +#if HAVE_SOCK_NONBLOCK && HAVE_SOCK_CLOEXEC + return socket(af, so | fl, pf); +#else +# define SOCK_NONBLOCK O_NONBLOCK +# define SOCK_CLOEXEC O_CLOEXEC + auto sock = socket(af, so, pf); + if (0 <= sock) { + if (0 > fcntl(sock, F_SETFL, fl | fcntl(sock, F_GETFL))) { + close(sock); + sock = -1; + } + } + return sock; +#endif +} + +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_ex(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK|SOCK_CLOEXEC))) { + 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_ex(AF_INET6, SOCK_STREAM, 0, SOCK_NONBLOCK|SOCK_CLOEXEC))) { + 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(move(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/test/lib/Connection.hpp b/test/lib/Connection.hpp new file mode 100644 index 00000000..8571759e --- /dev/null +++ b/test/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{}; + 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/test/lib/ForkAndExec.cpp b/test/lib/ForkAndExec.cpp new file mode 100644 index 00000000..e182c7c4 --- /dev/null +++ b/test/lib/ForkAndExec.cpp @@ -0,0 +1,125 @@ +#include "ForkAndExec.hpp" + +#include +#include + +#include +#include +#include + +#if !HAVE_PIPE2 +static inline int setfl(int fd, int newflags) { + auto oldflags = fcntl(fd, F_GETFL); + return fcntl(fd, F_SETFL, oldflags | newflags); +} +static inline int pipe2(int pipefd[2], int flags) { + int rc; + + rc = pipe(pipefd); + if (0 > rc) { + return rc; + } + rc = setfl(pipefd[0], flags); + if (0 > rc) { + return rc; + } + return setfl(pipefd[1], flags); +} +#endif + +ForkAndExec::ForkAndExec(const char *binary_, char **argv_) +: ready{-1, -1} +, pipes{-1, -1} +, binary{binary_} +, argv{argv_} +{ +} + +ForkAndExec::~ForkAndExec() { + for (auto &pipe : {ready, pipes}) { + for (auto m : {READ, WRITE}) { + closePipe(pipe[m]); + } + } +} + +int ForkAndExec::createPipe() { + if (pipes[mode::READ] == -1) { + if(pipe2(pipes, O_NONBLOCK)) { + perror("ForkAndExec pipe()"); + return -1; + } + } + + return pipes[mode::READ]; +} + +pid_t ForkAndExec::operator()() { + if (!prepareExecReadyPipe()) { + return 0; + } + + switch (pid_t pid = fork()) { + case 0: + closePipe(pipes[mode::READ]); + prepareOutputPipe(); + execvp(binary, argv); + perror("ForkAndExec exec()"); + _exit(EXIT_FAILURE); + + case -1: + perror("ForkAndExec fork()"); + return 0; + + default: + pipes[mode::READ] = -1; + closePipe(pipes[mode::WRITE]); + pollExecReadyPipe(); + return pid; + } +} + +bool ForkAndExec::prepareExecReadyPipe() { + if (ready[mode::READ] == -1) { + if (pipe2(ready, O_CLOEXEC | O_NONBLOCK)) { + perror("ForkAndExec pipe()"); + return false; + } + closePipe(ready[mode::WRITE]); + } + + return true; +} + +void ForkAndExec::prepareOutputPipe() { + if (pipes[mode::WRITE] != -1) { + if (0 > dup2(pipes[mode::WRITE], STDERR_FILENO) || + 0 > dup2(pipes[mode::WRITE], STDOUT_FILENO)) { + perror("ForkAndExec dup()"); + } + } +} + +void ForkAndExec::closePipe(int &fd) { + if (fd != -1) { + close(fd); + fd = -1; + } +} + +void ForkAndExec::pollExecReadyPipe() { +#if __APPLE__ + char c, n = 50; + do { + if (0 == read(ready[mode::READ], &c, 1)) { + return; + } + this_thread::sleep_for(100ms); + } while (errno == EAGAIN && n--); +#else + pollfd fd{ready[mode::READ], 0, 0}; + if (1 > poll(&fd, 1, 5000)) { + cerr << "exec() timed out" << endl; + } +#endif +} diff --git a/test/lib/ForkAndExec.hpp b/test/lib/ForkAndExec.hpp new file mode 100644 index 00000000..ad7b4151 --- /dev/null +++ b/test/lib/ForkAndExec.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "common.hpp" + +class ForkAndExec { +public: + enum mode { READ, WRITE }; + + ForkAndExec(const char *binary, char **argv); + ~ForkAndExec(); + + ForkAndExec(const ForkAndExec &) = delete; + ForkAndExec &operator = (const ForkAndExec &) = delete; + ForkAndExec(ForkAndExec &&) = default; + ForkAndExec &operator = (ForkAndExec &&) = default; + + [[nodiscard]] + int createPipe(); + pid_t operator () (); + +private: + int ready[2], pipes[2]; + const char *binary; + char **argv; + + bool prepareExecReadyPipe(); + void prepareOutputPipe(); + void closePipe(int &fd); + void pollExecReadyPipe(); +}; diff --git a/test/lib/MemcachedCluster.cpp b/test/lib/MemcachedCluster.cpp new file mode 100644 index 00000000..c03bacce --- /dev/null +++ b/test/lib/MemcachedCluster.cpp @@ -0,0 +1,112 @@ +#include "MemcachedCluster.hpp" +#include "Retry.hpp" + +const memcached_st MemcachedCluster::empty_memc{}; + +void MemcachedCluster::init() { + REQUIRE(cluster.start()); + + Retry cluster_is_listening([this]() { + return cluster.isListening(); + }); + REQUIRE(cluster_is_listening()); + + 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))); + } + } + +} + +MemcachedCluster::~MemcachedCluster() { + if (memcmp(&memc, &empty_memc, sizeof(memc))) { + memcached_free(&memc); + } +} + +void MemcachedCluster::flush() { + REQUIRE(MEMCACHED_SUCCESS == memcached_flush(&memc, 0)); +} + +MemcachedCluster::MemcachedCluster() +: cluster{Server{ + MEMCACHED_BINARY, + {random_socket_or_port_arg()} +}} +{ + init(); +} + +MemcachedCluster::MemcachedCluster(Cluster &&cluster_) +: cluster{move(cluster_)} +{ + init(); +} + +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; +} + +MemcachedCluster MemcachedCluster::mixed() { + return MemcachedCluster{}; +} + +MemcachedCluster MemcachedCluster::network() { + return MemcachedCluster{Cluster{Server{ + MEMCACHED_BINARY, + {"-p", random_socket_or_port_string} + }}}; +} + +MemcachedCluster MemcachedCluster::socket() { + return MemcachedCluster{Cluster{Server{ + MEMCACHED_BINARY, + {"-s", random_socket_or_port_string} + }}}; +} + +#if LIBMEMCACHED_WITH_SASL_SUPPORT +MemcachedCluster MemcachedCluster::sasl() { + auto mc = MemcachedCluster{Cluster{Server{ + MEMCACHED_BINARY, + { + Server::arg_pair_t{"-p", random_socket_or_port_string}, + Server::arg_t{"-S"} + } + }}}; + mc.enableBinaryProto(); + REQUIRE(MEMCACHED_SUCCESS == memcached_set_sasl_auth_data(&mc.memc, + "memcached", "memcached")); + return mc; +} +#endif + +void MemcachedCluster::enableBinaryProto(bool enable) { + REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(&memc, + MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, enable)); +} + +void MemcachedCluster::enableBuffering(bool enable) { + REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(&memc, + MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, enable)); +} + +void MemcachedCluster::enableReplication() { + REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(&memc, + MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, memcached_server_count(&memc))); +} + diff --git a/test/lib/MemcachedCluster.hpp b/test/lib/MemcachedCluster.hpp new file mode 100644 index 00000000..39859b7d --- /dev/null +++ b/test/lib/MemcachedCluster.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "common.hpp" +#include "Cluster.hpp" +#include "ReturnMatcher.hpp" + + +class MemcachedCluster { +public: + Cluster cluster; + memcached_st memc{empty_memc}; + ReturnMatcher returns{&memc}; + + MemcachedCluster(); + explicit + MemcachedCluster(Cluster &&cluster); + ~MemcachedCluster(); + + MemcachedCluster(const MemcachedCluster &) = delete; + MemcachedCluster &operator=(const MemcachedCluster &) = delete; + + MemcachedCluster(MemcachedCluster &&mc);; + MemcachedCluster &operator=(MemcachedCluster &&mc); + + void enableBinaryProto(bool enable = true); + void enableBuffering(bool enable = true); + void enableReplication(); + void flush(); + + static MemcachedCluster mixed(); + static MemcachedCluster network(); + static MemcachedCluster socket(); + +#if LIBMEMCACHED_WITH_SASL_SUPPORT + static MemcachedCluster sasl(); +#endif + +private: + static const memcached_st empty_memc; + + void init(); +}; diff --git a/test/lib/Retry.cpp b/test/lib/Retry.cpp new file mode 100644 index 00000000..71fde0e0 --- /dev/null +++ b/test/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{move(pred_)} +{} + +bool Retry::operator()() { + auto cnt = max; + auto dur = sleep_for; + + while (cnt--) { + if (pred()) { + return true; + } + this_thread::sleep_for(dur); + dur *= 1.2; + } + + return false; +} diff --git a/test/lib/Retry.hpp b/test/lib/Retry.hpp new file mode 100644 index 00000000..04c0466d --- /dev/null +++ b/test/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_ = 100ms); + + bool operator () (); + +private: + unsigned max; + chrono::milliseconds sleep_for; + predicate pred; +}; + diff --git a/test/lib/ReturnMatcher.cpp b/test/lib/ReturnMatcher.cpp new file mode 100644 index 00000000..863945e8 --- /dev/null +++ b/test/lib/ReturnMatcher.cpp @@ -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/test/lib/ReturnMatcher.hpp b/test/lib/ReturnMatcher.hpp new file mode 100644 index 00000000..81c2d481 --- /dev/null +++ b/test/lib/ReturnMatcher.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "test/lib/common.hpp" + +class ReturnMatcher : public Catch::MatcherBase { +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/test/lib/Server.cpp b/test/lib/Server.cpp new file mode 100644 index 00000000..17bafe74 --- /dev/null +++ b/test/lib/Server.cpp @@ -0,0 +1,242 @@ +#include "Server.hpp" +#include "Connection.hpp" +#include "ForkAndExec.hpp" + +#include +#include + +Server::Server(string binary_, Server::argv_t args_) + : binary{move(binary_)} + , args{move(args_)} +{} + +Server::~Server() { + stop(); + wait(); + if (pipe != -1) { + close(pipe); + } + if (holds_alternative(socket_or_port)) { + unlink(get(socket_or_port).c_str()); + } +} + +static inline string extractArg(const Server::arg_t &arg_cont, const string &func_arg) { + if (holds_alternative(arg_cont)) { + return get(arg_cont); + } else { + return get(arg_cont)(func_arg); + } +} + +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); +} + +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; + } else if (arg == "-S" || arg == "--enable-sasl") { + sasl = true; + } + 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); + } + } + } + + arr.push_back(nullptr); + + return arr; +} + +optional Server::start() { + if (!pid) { + auto argv = createArgv(); + ForkAndExec fork_and_exec{binary.c_str(), argv.data()}; + + pipe = fork_and_exec.createPipe(); + pid = fork_and_exec(); + + for (auto argp : argv) { + delete [] argp; + } + + if (!pid) { + return {}; + } + } + return ChildProc{pid, pipe}; +} + +bool Server::isListening() { + MemcachedPtr memc; + + if (holds_alternative(socket_or_port)) { + if (memcached_server_add_unix_socket(*memc, get(socket_or_port).c_str())) { + return false; + } + } else { + if (memcached_server_add(*memc, "localhost", get(socket_or_port))) { + return false; + } + } + + if (sasl) { + memcached_behavior_set(*memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + memcached_set_sasl_auth_data(*memc, "memcached", "memcached"); + } + + Malloced stat(memcached_stat(*memc, nullptr, nullptr)); + if (!*stat || !stat->pid || stat->pid != pid) { + return false; + } + + return true; +} + +bool Server::stop() { + if (!pid) { + return true; + } + if (signalled[SIGTERM]) { + return signal(SIGKILL); + } else { + return signal(SIGTERM); + } +} + +bool Server::signal(int signo) { + if (!pid) { + return false; + } + signalled[signo] += 1; + return 0 <= kill(pid, signo); +} + +bool Server::check() { + return signal(0); +} + +bool Server::wait(int flags) { + if (pid && pid == waitpid(pid, &status, flags)) { + if (drain().length()) { + cerr << "Ouput of " << *this << ":\n" << output << endl; + output.clear(); + } + pid = 0; + if (pipe != -1) { + close(pipe); + pipe = -1; + } + return true; + } + return false; +} + +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; +} + +int Server::getPipe() const { + return pipe; +} + +string &Server::drain() { + if (pipe != -1) { + again: + char read_buf[1<<12]; + auto read_len = read(pipe, read_buf, sizeof(read_buf)); + + if (read_len > 0) { + output.append(read_buf, read_len); + goto again; + } + if (read_len == -1) { + switch (errno) { + case EINTR: + goto again; + default: + perror("Server::drain read()"); + [[fallthrough]]; + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + break; + } + } + } + return output; +} diff --git a/test/lib/Server.hpp b/test/lib/Server.hpp new file mode 100644 index 00000000..44fd5e99 --- /dev/null +++ b/test/lib/Server.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "common.hpp" + +#include +#include + +class Server { +public: + + friend class Cluster; + + using arg_func_t = function; + using arg_t = variant; + using arg_pair_t = pair; + using argv_t = vector>; + + explicit + Server(string binary_ = "false", argv_t args_ = {}); + + ~Server(); + + Server(const Server &s); + Server &operator = (const Server &s); + + Server(Server &&s) { + *this = move(s); + }; + Server &operator = (Server &&s) { + binary = exchange(s.binary, "false"); + args = exchange(s.args, {}); + pid = exchange(s.pid, 0); + pipe = exchange(s.pipe, -1); + status = exchange(s.status, 0); + signalled = exchange(s.signalled, {}); + socket_or_port = exchange(s.socket_or_port, {}); + output = exchange(s.output, {}); + return *this; + }; + + pid_t getPid() const; + int getPipe() const; + const string &getBinary() const; + const argv_t &getArgs() const; + const socket_or_port_t &getSocketOrPort() const; + + struct ChildProc { + pid_t pid; + int pipe; + ChildProc(pid_t pid_, int pipe_) + : pid{pid_} + , pipe{pipe_} + { + } + }; + optional start(); + bool stop(); + + bool signal(int signo = SIGTERM); + bool check(); + bool isListening(); + + bool wait(int flags = 0); + bool tryWait(); + string &drain(); + + +private: + string binary; + argv_t args; + bool sasl = false; + pid_t pid = 0; + int pipe = -1; + int status = 0; + unordered_map signalled; + socket_or_port_t socket_or_port = 11211; + string output; + + [[nodiscard]] + vector createArgv(); + optional handleArg(vector &arr, const string &arg, const arg_func_t &next_arg); +}; + +inline ostream &operator << (ostream &out, const socket_or_port_t sop) { + if (holds_alternative(sop)) { + out << get(sop); + } else { + out << ":" << get(sop); + } + return out; +} + +inline ostream &operator << (ostream &out, const Server &server) { + out << "Server{binary=" << server.getBinary() << ",pid=" << server.getPid() << ",conn=" << server.getSocketOrPort() << "}"; + return out; +} diff --git a/test/lib/Shell.cpp b/test/lib/Shell.cpp new file mode 100644 index 00000000..cd3017ac --- /dev/null +++ b/test/lib/Shell.cpp @@ -0,0 +1,71 @@ +#include "Shell.hpp" + +#include +#include + +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/test/lib/Shell.hpp b/test/lib/Shell.hpp new file mode 100644 index 00000000..d61f8ceb --- /dev/null +++ b/test/lib/Shell.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "test/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_); +}; diff --git a/test/lib/catch.hpp b/test/lib/catch.hpp new file mode 100644 index 00000000..cf1fae15 --- /dev/null +++ b/test/lib/catch.hpp @@ -0,0 +1,17799 @@ +/* + * Catch v2.13.0 + * Generated: 2020-07-12 20:07:49.015950 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 0 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(__cpp_lib_uncaught_exceptions) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +// We have to avoid both ICC and Clang, because they try to mask themselves +// as gcc, and we want only GCC in this block +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template