From 3eb760f3f379e174ab271668ba7d5069854344c1 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Mon, 21 Oct 2024 10:58:42 +0100 Subject: [PATCH] Move test methods in ClientInvoker to SCPort - create `harness` libraries to support the tests Re ECFLOW-1957 --- libs/attribute/CMakeLists.txt | 4 +- libs/base/CMakeLists.txt | 7 +- libs/base/test/harness/CMakeLists.txt | 29 +++ libs/base/test/harness/MockServer.cpp | 17 ++ libs/base/test/{ => harness}/MockServer.hpp | 6 +- libs/base/test/{ => harness}/TestHelper.hpp | 0 libs/client/CMakeLists.txt | 22 +- .../src/ecflow/client/ClientInvoker.cpp | 106 -------- .../src/ecflow/client/ClientInvoker.hpp | 14 - libs/client/test/SCPort.cpp | 102 -------- libs/client/test/harness/CMakeLists.txt | 30 +++ .../test/{ => harness}/InvokeServer.hpp | 0 libs/client/test/harness/SCPort.cpp | 240 ++++++++++++++++++ libs/client/test/{ => harness}/SCPort.hpp | 17 ++ libs/core/CMakeLists.txt | 19 +- libs/core/test/harness/CMakeLists.txt | 27 ++ libs/core/test/{ => harness}/TestNaming.hpp | 6 +- .../test/{ => harness}/TestSerialisation.hpp | 6 +- libs/node/CMakeLists.txt | 8 +- libs/rest/CMakeLists.txt | 8 +- libs/server/CMakeLists.txt | 4 +- libs/test/CMakeLists.txt | 13 +- libs/test/{src => harness}/CMakeLists.txt | 9 +- .../{src => harness}/LocalServerLauncher.cpp | 0 .../{src => harness}/LocalServerLauncher.hpp | 0 libs/test/{src => harness}/ScratchDir.hpp | 0 .../{src => harness}/ServerTestHarness.cpp | 0 .../{src => harness}/ServerTestHarness.hpp | 0 libs/test/{src => harness}/TestFixture.cpp | 28 +- libs/test/{src => harness}/TestFixture.hpp | 0 libs/test/{src => harness}/ZombieUtil.hpp | 0 libs/test/{src => harness}/ZombieUtill.cpp | 0 libs/udp/CMakeLists.txt | 2 +- 33 files changed, 400 insertions(+), 324 deletions(-) create mode 100644 libs/base/test/harness/CMakeLists.txt create mode 100644 libs/base/test/harness/MockServer.cpp rename libs/base/test/{ => harness}/MockServer.hpp (97%) rename libs/base/test/{ => harness}/TestHelper.hpp (100%) delete mode 100644 libs/client/test/SCPort.cpp create mode 100644 libs/client/test/harness/CMakeLists.txt rename libs/client/test/{ => harness}/InvokeServer.hpp (100%) create mode 100644 libs/client/test/harness/SCPort.cpp rename libs/client/test/{ => harness}/SCPort.hpp (61%) create mode 100644 libs/core/test/harness/CMakeLists.txt rename libs/core/test/{ => harness}/TestNaming.hpp (92%) rename libs/core/test/{ => harness}/TestSerialisation.hpp (91%) rename libs/test/{src => harness}/CMakeLists.txt (86%) rename libs/test/{src => harness}/LocalServerLauncher.cpp (100%) rename libs/test/{src => harness}/LocalServerLauncher.hpp (100%) rename libs/test/{src => harness}/ScratchDir.hpp (100%) rename libs/test/{src => harness}/ServerTestHarness.cpp (100%) rename libs/test/{src => harness}/ServerTestHarness.hpp (100%) rename libs/test/{src => harness}/TestFixture.cpp (92%) rename libs/test/{src => harness}/TestFixture.hpp (100%) rename libs/test/{src => harness}/ZombieUtil.hpp (100%) rename libs/test/{src => harness}/ZombieUtill.cpp (100%) diff --git a/libs/attribute/CMakeLists.txt b/libs/attribute/CMakeLists.txt index 240881fc4..33722ec54 100644 --- a/libs/attribute/CMakeLists.txt +++ b/libs/attribute/CMakeLists.txt @@ -35,13 +35,11 @@ ecbuild_add_test( nightly SOURCES ${test_srcs} - INCLUDES - ../core/test LIBS ecflow_all + test_harness.core TEST_DEPENDS u_core - Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) ) target_clangformat(u_attributes CONDITION ENABLE_TESTS diff --git a/libs/base/CMakeLists.txt b/libs/base/CMakeLists.txt index 7974e44aa..092a42650 100644 --- a/libs/base/CMakeLists.txt +++ b/libs/base/CMakeLists.txt @@ -8,10 +8,9 @@ # nor does it submit to any jurisdiction. # +add_subdirectory(test/harness) + set(test_srcs - # Headers - test/MockServer.hpp - test/TestHelper.hpp # Sources test/TestAlterCmd.cpp test/TestArchiveAndRestoreCmd.cpp @@ -50,7 +49,7 @@ ecbuild_add_test( ecflow_all Threads::Threads $<$:OpenSSL::SSL> - test_support + test_harness.base TEST_DEPENDS u_parser ) diff --git a/libs/base/test/harness/CMakeLists.txt b/libs/base/test/harness/CMakeLists.txt new file mode 100644 index 000000000..7c6275b3e --- /dev/null +++ b/libs/base/test/harness/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# Copyright 2009- ECMWF. +# +# This software is licensed under the terms of the Apache Licence version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +ecbuild_add_library( + TARGET + test_harness.base + NOINSTALL + TYPE STATIC + SOURCES + # Headers + MockServer.hpp + TestHelper.hpp + # Sources + MockServer.cpp + PUBLIC_INCLUDES + ./ + PUBLIC_LIBS + test_harness.core +) +target_clangformat(test_harness.base + CONDITION ENABLE_TESTS +) diff --git a/libs/base/test/harness/MockServer.cpp b/libs/base/test/harness/MockServer.cpp new file mode 100644 index 000000000..e925eda88 --- /dev/null +++ b/libs/base/test/harness/MockServer.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "MockServer.hpp" + +void MockServer::set_server_state(SState::State ss) { + serverState_ = ss; + stats().status_ = static_cast(serverState_); + defs_->set_server().set_state(serverState_); +} diff --git a/libs/base/test/MockServer.hpp b/libs/base/test/harness/MockServer.hpp similarity index 97% rename from libs/base/test/MockServer.hpp rename to libs/base/test/harness/MockServer.hpp index 0b1225b2d..2d27f4e1b 100644 --- a/libs/base/test/MockServer.hpp +++ b/libs/base/test/harness/MockServer.hpp @@ -37,11 +37,7 @@ class MockServer : public AbstractServer { explicit MockServer(defs_ptr defs) : defs_(defs) { Ecf::set_server(true); } ~MockServer() override { Ecf::set_server(false); } - void set_server_state(SState::State ss) { - serverState_ = ss; - stats().status_ = static_cast(serverState_); - defs_->set_server().set_state(serverState_); - } + void set_server_state(SState::State ss); // AbstractServer functions SState::State state() const override { return serverState_; } diff --git a/libs/base/test/TestHelper.hpp b/libs/base/test/harness/TestHelper.hpp similarity index 100% rename from libs/base/test/TestHelper.hpp rename to libs/base/test/harness/TestHelper.hpp diff --git a/libs/client/CMakeLists.txt b/libs/client/CMakeLists.txt index 4b2316577..f8ebb6d6f 100644 --- a/libs/client/CMakeLists.txt +++ b/libs/client/CMakeLists.txt @@ -32,8 +32,6 @@ SET_TARGET_PROPERTIES(ecflow_client INSTALL_RPATH "" ) - - # use, i.e. don't skip the full RPATH for the build tree #SET(CMAKE_SKIP_BUILD_RPATH FALSE) @@ -48,12 +46,11 @@ SET_TARGET_PROPERTIES(ecflow_client # which point to directories outside the build tree to the install RPATH #SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) +add_subdirectory(test/harness) # ================================================================================ # TEST set(test_srcs - # Headers - test/InvokeServer.hpp # Sources test/TestClient_main.cpp # test entry point test/TestClientEnvironment.cpp @@ -68,10 +65,7 @@ set(test_srcs if (ENABLE_SERVER) list(APPEND test_srcs - # Headers - test/SCPort.hpp # Sources - test/SCPort.cpp test/TestClientTimeout.cpp test/TestClientHandleCmd.cpp test/TestCheckPtDefsCmd.cpp @@ -101,10 +95,9 @@ ecbuild_add_test( ../node/test LIBS ecflow_all - Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) Boost::timer $<$:OpenSSL::SSL> - test_support + test_harness.client DEPENDS ecflow_server # the server is launched to support tests TEST_DEPENDS @@ -128,18 +121,15 @@ if (ENABLE_ALL_TESTS AND ENABLE_SERVER) manual # requires manual definition of env var ECF_TEST_DEFS_DIR SOURCES # Headers - test/SCPort.hpp # Sources - test/SCPort.cpp test/TestSinglePerf.cpp test/TestSinglePerf_main.cpp # test entry point INCLUDES ../base/test LIBS ecflow_all - Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> - test_support + test_harness.client DEPENDS ecflow_server # the server is launched to support tests TEST_DEPENDS @@ -159,19 +149,15 @@ if (ENABLE_ALL_TESTS AND ENABLE_SERVER) migration manual # requires command line argument: m_test_migration <> SOURCES - # Headers - test/SCPort.hpp # Sources - test/SCPort.cpp test/TestMigration.cpp test/TestMigration_main.cpp # test entry point INCLUDES ../base/test LIBS ecflow_all - Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> - test_support + test_harness.client DEPENDS ecflow_server # the server is launched to support tests TEST_DEPENDS diff --git a/libs/client/src/ecflow/client/ClientInvoker.cpp b/libs/client/src/ecflow/client/ClientInvoker.cpp index aeab174fd..0970811b9 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.cpp +++ b/libs/client/src/ecflow/client/ClientInvoker.cpp @@ -1524,112 +1524,6 @@ std::string ClientInvoker::client_env_host_port() const { return host_port; } -bool ClientInvoker::is_free_port(int port, bool debug) { - // Ping failed, We need to distinguish between: - // a/ Server does not exist : port - // b/ Address in use : port on existing server - // Using server_version() but then get error messages - // ******** Until this is done we can't implement port hopping ********** - - if (debug) { - cout << " ClientInvoker::is_free_port: checking port " << port << "\n"; - } - - ClientInvoker client; - client.set_retry_connection_period(1); // avoid long wait - client.set_connection_attempts(1); // avoid long wait - - const auto the_port = ecf::convert_to(port); - try { - if (debug) { - cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << the_port << "'\n"; - } - client.set_host_port(Str::LOCALHOST(), the_port); - client.pingServer(); - if (debug) { - cout << " Connected to server on port " << the_port << ". Returning FALSE\n"; - } - return false; - } - catch (std::runtime_error& e) { - std::string msg = e.what(); - if (debug) { - cout << " " << msg; - } - if (msg.find("authentication failed") != std::string::npos) { - if (debug) { - cout << " Could not connect, due to authentication failure, hence port " << the_port - << " is used. Returning FALSE\n"; - } - return false; - } - if (msg.find("invalid_argument") != std::string::npos) { - if (debug) { - cout << " Mixing 4 and 5 series ?, hence port " << the_port << " is used. Returning FALSE\n"; - } - return false; - } - else { - if (debug) { - cout << " Found free port " << the_port << "\n"; - } - } - } - return true; -} - -std::string ClientInvoker::find_free_port(int seed_port_number, bool debug) { - // Ping failed, We need to distinguish between: - // a/ Server does not exist : port - // b/ Address in use : port on existing server - // Using server_version() but then get error messages - // ******** Until this is done we can't implement port hopping ********** - - if (debug) - cout << " ClientInvoker::find_free_port: starting with port " << seed_port_number << "\n"; - int the_port = seed_port_number; - std::string free_port; - ClientInvoker client; - client.set_retry_connection_period(1); // avoid long wait - client.set_connection_attempts(1); // avoid long wait - while (true) { - free_port = ecf::convert_to(the_port); - try { - if (debug) - cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << free_port << "'\n"; - client.set_host_port(Str::LOCALHOST(), free_port); - client.pingServer(); - if (debug) - cout << " Connected to server on port " << free_port << " trying next port\n"; - the_port++; - } - catch (std::runtime_error& e) { - std::string error_msg = e.what(); - if (debug) - cout << " " << error_msg; - if (error_msg.find("authentication failed") != std::string::npos) { - if (debug) - cout << " Could not connect, due to authentication failure, hence port " << the_port - << " is used, trying next port\n"; - the_port++; - continue; - } - if (error_msg.find("invalid_argument") != std::string::npos) { - if (debug) - cout << " Mixing 4 and 5 series ?, hence port " << the_port << " is used, trying next port\n"; - the_port++; - continue; - } - else { - if (debug) - cout << " Found free port " << free_port << "\n"; - break; - } - } - } - return free_port; -} - bool ClientInvoker::wait_for_server_reply(int time_out) const { DurationTimer timer; while (true) { diff --git a/libs/client/src/ecflow/client/ClientInvoker.hpp b/libs/client/src/ecflow/client/ClientInvoker.hpp index f476d964e..2b16d9d34 100644 --- a/libs/client/src/ecflow/client/ClientInvoker.hpp +++ b/libs/client/src/ecflow/client/ClientInvoker.hpp @@ -222,20 +222,6 @@ class ClientInvoker { int news(defs_ptr& client_defs) const; int news_local() const; - /** - * Check if there is *no *ecFlow server running on the given port. - * - * The check is performed by connecting to the port (on localhost) and issuing a ping request. - * - * \returns false, if: (1) the server answers with a ping response; - * (2) the request is accepted, but fails due to failed authentication; - * (3) there is a mismatch communication protocol (4.x vs 5.x) - * true, otherwise - */ - static bool is_free_port(int port, bool debug = false); - // find free port on local host. Not 100% accurate, use in test - static std::string find_free_port(int seed_port_number, bool debug = false); - bool wait_for_server_reply(int time_out = 60) const; // wait for server reply, returning false means timed out. bool wait_for_server_death( int time_out = 60) const; // wait for server reply, returning true means server died,false means timed out. diff --git a/libs/client/test/SCPort.cpp b/libs/client/test/SCPort.cpp deleted file mode 100644 index 796d25c60..000000000 --- a/libs/client/test/SCPort.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2009- ECMWF. - * - * This software is licensed under the terms of the Apache Licence version 2.0 - * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - * In applying this licence, ECMWF does not waive the privileges and immunities - * granted to it by virtue of its status as an intergovernmental organisation - * nor does it submit to any jurisdiction. - */ - -#include "SCPort.hpp" - -#include - -#include "ecflow/client/ClientEnvironment.hpp" -#include "ecflow/client/ClientInvoker.hpp" -#include "ecflow/core/EcfPortLock.hpp" -#include "ecflow/core/Environment.hpp" -#include "ecflow/core/Str.hpp" - -namespace ecf { - -// init the globals. Note we dont use 3141, so that in the case where we already -// have a remote/local server started external to the test, it does not clash -// Also debug and release version use different port numbers to avoid clashes, if both tests run at same time - -#ifdef DEBUG -int SCPort::thePort_ = 3161; -#else -int SCPort::thePort_ = 3144; -#endif - -std::string SCPort::next() { - bool debug = false; - ecf::environment::get("ECF_DEBUG_TEST", debug); - - if (debug) { - std::cout << "\nSCPort::next() : "; - } - - // Allow parallel tests - - if (auto port = ecf::environment::fetch("ECF_FREE_PORT"); port) { - if (debug) { - std::cout << " seed_port=ECF_FREE_PORT=(" << port.value() << ")"; - } - try { - thePort_ = ecf::convert_to(port.value()); - } - catch (...) { - std::cout << "SCPort::next() ECF_FREE_PORT(" << port.value() << ") not convertible to an integer\n"; - } - } - - // This is used to test remote servers(or legacy server with new client). Here ECF_HOST=localhost in the test - // scripts - std::string host = ClientEnvironment::hostSpecified(); - if (debug) { - std::cout << " ECF_HOST('" << host << "')"; - } - - if (host == Str::LOCALHOST()) { - if (auto port = ecf::environment::fetch(ecf::environment::ECF_PORT); port) { - if (!port.value().empty()) { - if (debug) { - std::cout << " ECF_PORT('" << port.value() << "')\n"; - } - return port.value(); - } - } - if (debug) { - std::cout << " !!!!!! ERROR when ECF_HOST=localhost EXPECTED ECF_PORT to be set !!!!!! "; - } - } - - if (debug) { - std::cout << "\n"; - } - std::string the_port = next_only(debug); - if (debug) { - std::cout << " SCPort::next() returning free port=" << the_port << "\n"; - } - return the_port; -} - -std::string SCPort::next_only(bool debug) { - if (debug) { - std::cout << " SCPort::next_only : starting seed_port(" << thePort_ << ")\n"; - } - - // Use a combination of local lock file, and pinging the server - while (!EcfPortLock::is_free(thePort_, debug)) { - thePort_++; - } - - if (debug) { - std::cout << " SCPort::next_only() seed_port(" << thePort_ << ")\n"; - } - return ClientInvoker::find_free_port(thePort_, debug); -} - -} // namespace ecf diff --git a/libs/client/test/harness/CMakeLists.txt b/libs/client/test/harness/CMakeLists.txt new file mode 100644 index 000000000..04320b1b2 --- /dev/null +++ b/libs/client/test/harness/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Copyright 2009- ECMWF. +# +# This software is licensed under the terms of the Apache Licence version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +ecbuild_add_library( + TARGET + test_harness.client + NOINSTALL + TYPE + STATIC + SOURCES + # Headers + InvokeServer.hpp + SCPort.hpp + # Sources + SCPort.cpp + PUBLIC_INCLUDES + . + PUBLIC_LIBS + test_harness.base +) +target_clangformat(test_harness.client + CONDITION ENABLE_TESTS +) diff --git a/libs/client/test/InvokeServer.hpp b/libs/client/test/harness/InvokeServer.hpp similarity index 100% rename from libs/client/test/InvokeServer.hpp rename to libs/client/test/harness/InvokeServer.hpp diff --git a/libs/client/test/harness/SCPort.cpp b/libs/client/test/harness/SCPort.cpp new file mode 100644 index 000000000..56a967216 --- /dev/null +++ b/libs/client/test/harness/SCPort.cpp @@ -0,0 +1,240 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "SCPort.hpp" + +#include + +#include "ecflow/client/ClientEnvironment.hpp" +#include "ecflow/client/ClientInvoker.hpp" +#include "ecflow/core/EcfPortLock.hpp" +#include "ecflow/core/Environment.hpp" +#include "ecflow/core/Str.hpp" + +namespace ecf { + +// init the globals. Note we dont use 3141, so that in the case where we already +// have a remote/local server started external to the test, it does not clash +// Also debug and release version use different port numbers to avoid clashes, if both tests run at same time + +#ifdef DEBUG +int SCPort::thePort_ = 3161; +#else +int SCPort::thePort_ = 3144; +#endif + +std::string SCPort::next() { + bool debug = false; + ecf::environment::get("ECF_DEBUG_TEST", debug); + + if (debug) { + std::cout << "\nSCPort::next() : "; + } + + // Allow parallel tests + + if (auto port = ecf::environment::fetch("ECF_FREE_PORT"); port) { + if (debug) { + std::cout << " seed_port=ECF_FREE_PORT=(" << port.value() << ")"; + } + try { + thePort_ = ecf::convert_to(port.value()); + } + catch (...) { + std::cout << "SCPort::next() ECF_FREE_PORT(" << port.value() << ") not convertible to an integer\n"; + } + } + + // This is used to test remote servers(or legacy server with new client). Here ECF_HOST=localhost in the test + // scripts + std::string host = ClientEnvironment::hostSpecified(); + if (debug) { + std::cout << " ECF_HOST('" << host << "')"; + } + + if (host == Str::LOCALHOST()) { + if (auto port = ecf::environment::fetch(ecf::environment::ECF_PORT); port) { + if (!port.value().empty()) { + if (debug) { + std::cout << " ECF_PORT('" << port.value() << "')\n"; + } + return port.value(); + } + } + if (debug) { + std::cout << " !!!!!! ERROR when ECF_HOST=localhost EXPECTED ECF_PORT to be set !!!!!! "; + } + } + + if (debug) { + std::cout << "\n"; + } + std::string the_port = next_only(debug); + if (debug) { + std::cout << " SCPort::next() returning free port=" << the_port << "\n"; + } + return the_port; +} + +std::string SCPort::next_only(bool debug) { + if (debug) { + std::cout << " SCPort::next_only : starting seed_port(" << thePort_ << ")\n"; + } + + // Use a combination of local lock file, and pinging the server + while (!EcfPortLock::is_free(thePort_, debug)) { + thePort_++; + } + + if (debug) { + std::cout << " SCPort::next_only() seed_port(" << thePort_ << ")\n"; + } + return SCPort::find_free_port(thePort_, debug); +} + +bool SCPort::is_free_port(int port, bool debug) { + // Ping failed, We need to distinguish between: + // a/ Server does not exist : port + // b/ Address in use : port on existing server + // Using server_version() but then get error messages + // ******** Until this is done we can't implement port hopping ********** + + if (debug) { + std::cout << " ClientInvoker::is_free_port: checking port " << port << "\n"; + } + + ClientInvoker client; + client.set_retry_connection_period(1); // avoid long wait + client.set_connection_attempts(1); // avoid long wait + + const auto the_port = ecf::convert_to(port); + try { + if (debug) { + std::cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << the_port << "'\n"; + } + client.set_host_port(Str::LOCALHOST(), the_port); + client.pingServer(); + if (debug) { + std::cout << " Connected to server on port " << the_port << ". Returning FALSE\n"; + } + return false; + } + catch (std::runtime_error& e) { + std::string msg = e.what(); + if (debug) { + std::cout << " " << msg; + } + if (msg.find("authentication failed") != std::string::npos) { + if (debug) { + std::cout << " Could not connect, due to authentication failure, hence port " << the_port + << " is used. Returning FALSE\n"; + } + return false; + } + if (msg.find("invalid_argument") != std::string::npos) { + if (debug) { + std::cout << " Mixing 4 and 5 series ?, hence port " << the_port << " is used. Returning FALSE\n"; + } + return false; + } + else { + if (debug) { + std::cout << " Found free port " << the_port << "\n"; + } + } + } + return true; +} + +std::string SCPort::find_free_port(int seed_port_number, bool debug) { + // Ping failed, We need to distinguish between: + // a/ Server does not exist : port + // b/ Address in use : port on existing server + // Using server_version() but then get error messages + // ******** Until this is done we can't implement port hopping ********** + + if (debug) { + std::cout << " ClientInvoker::find_free_port: starting with port " << seed_port_number << "\n"; + } + int the_port = seed_port_number; + std::string free_port; + ClientInvoker client; + client.set_retry_connection_period(1); // avoid long wait + client.set_connection_attempts(1); // avoid long wait + while (true) { + free_port = ecf::convert_to(the_port); + try { + if (debug) { + std::cout << " Trying to connect to server on '" << Str::LOCALHOST() << ":" << free_port << "'\n"; + } + client.set_host_port(Str::LOCALHOST(), free_port); + client.pingServer(); + if (debug) { + std::cout << " Connected to server on port " << free_port << " trying next port\n"; + } + the_port++; + } + catch (std::runtime_error& e) { + std::string error_msg = e.what(); + if (debug) { + std::cout << " " << error_msg; + } + if (error_msg.find("authentication failed") != std::string::npos) { + if (debug) { + std::cout << " Could not connect, due to authentication failure, hence port " << the_port + << " is used, trying next port\n"; + } + the_port++; + continue; + } + if (error_msg.find("invalid_argument") != std::string::npos) { + if (debug) { + std::cout << " Mixing 4 and 5 series ?, hence port " << the_port + << " is used, trying next port\n"; + } + the_port++; + continue; + } + else { + if (debug) { + std::cout << " Found free port " << free_port << "\n"; + } + break; + } + } + } + return free_port; +} + +std::string SCPort::find_available_port(const std::string& port) { + auto current_port = ecf::convert_to(port); + + // The goal is to create a unique port number, allowing debug and release tests to run at the same time + // Since several serves might run on same machine, on different workspaces, checking lock file is not sufficient. + // We try to ensure the port is available by attempting to contact the server to confirm that the lock has been + // performed. + + // (1) Search for port that isn't locked (i.e. for which there is no `.lock` file) + for (int selected_port = EcfPortLock::try_next_port_lock(current_port, true); /* always advance */; + selected_port = EcfPortLock::try_next_port_lock(selected_port, true)) { + if (SCPort::is_free_port(selected_port, true)) { + // We found the free port that we wanted! + return ecf::convert_to(selected_port); + } + else { + // This port is in use (maybe by a server running on another workspace) + EcfPortLock::try_port_lock(selected_port); + } + } + + throw std::runtime_error("Unable to find an available port"); +} + +} // namespace ecf diff --git a/libs/client/test/SCPort.hpp b/libs/client/test/harness/SCPort.hpp similarity index 61% rename from libs/client/test/SCPort.hpp rename to libs/client/test/harness/SCPort.hpp index a94e65907..f6ca409a0 100644 --- a/libs/client/test/SCPort.hpp +++ b/libs/client/test/harness/SCPort.hpp @@ -29,6 +29,23 @@ class SCPort { /// make sure we have a unique port, each time next_only() is called; static std::string next_only(bool debug = false); + /** + * Check if there is *no* ecFlow server running on the given port. + * + * The check is performed by connecting to the port (on localhost) and issuing a ping request. + * + * \returns false, if: (1) the server answers with a ping response; + * (2) the request is accepted, but fails due to failed authentication; + * (3) there is a mismatch communication protocol (4.x vs 5.x) + * true, otherwise + */ + static bool is_free_port(int port, bool debug = false); + + static std::string find_available_port(const std::string& port); + + // find free port on local host. Not 100% accurate, use in test + [[deprecated]] static std::string find_free_port(int seed_port_number, bool debug = false); + private: SCPort() = delete; ~SCPort() = delete; diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index d6c85f0c9..61ba9093b 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -8,22 +8,7 @@ # nor does it submit to any jurisdiction. # -## -## Notice: test_support is an INTERFACE-only test utility library. -## - -ecbuild_add_library( - TARGET - test_support - NOINSTALL - TYPE INTERFACE - SOURCES - test/TestSerialisation.hpp - test/TestNaming.hpp - PUBLIC_INCLUDES - test -) -target_clangformat(test_support) +add_subdirectory(test/harness) set(test_srcs # Headers @@ -71,7 +56,7 @@ ecbuild_add_test( SOURCES ${test_srcs} LIBS - test_support + test_harness.core ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) Boost::timer diff --git a/libs/core/test/harness/CMakeLists.txt b/libs/core/test/harness/CMakeLists.txt new file mode 100644 index 000000000..55b216339 --- /dev/null +++ b/libs/core/test/harness/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# Copyright 2009- ECMWF. +# +# This software is licensed under the terms of the Apache Licence version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +ecbuild_add_library( + TARGET + test_harness.core + NOINSTALL + TYPE INTERFACE + SOURCES + TestSerialisation.hpp + TestNaming.hpp + PUBLIC_INCLUDES + ./ + PUBLIC_LIBS + ecflow_all + Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) +) +target_clangformat(test_harness.core + CONDITION ENABLE_TESTS +) diff --git a/libs/core/test/TestNaming.hpp b/libs/core/test/harness/TestNaming.hpp similarity index 92% rename from libs/core/test/TestNaming.hpp rename to libs/core/test/harness/TestNaming.hpp index 4b64a3af9..9fedfda28 100644 --- a/libs/core/test/TestNaming.hpp +++ b/libs/core/test/harness/TestNaming.hpp @@ -8,8 +8,8 @@ * nor does it submit to any jurisdiction. */ -#ifndef ecflow_core_TestNaming_HPP -#define ecflow_core_TestNaming_HPP +#ifndef ecflow_core_test_harness_TestNaming_HPP +#define ecflow_core_test_harness_TestNaming_HPP #include #include @@ -50,4 +50,4 @@ inline std::string name_this_test() { std::cerr << " +++ " ARGS << std::endl; \ } while (0) -#endif /* ecflow_core_TestNaming_HPP */ +#endif /* ecflow_core_test_harness_TestNaming_HPP */ diff --git a/libs/core/test/TestSerialisation.hpp b/libs/core/test/harness/TestSerialisation.hpp similarity index 91% rename from libs/core/test/TestSerialisation.hpp rename to libs/core/test/harness/TestSerialisation.hpp index 8b7970877..fb48b3612 100644 --- a/libs/core/test/TestSerialisation.hpp +++ b/libs/core/test/harness/TestSerialisation.hpp @@ -8,8 +8,8 @@ * nor does it submit to any jurisdiction. */ -#ifndef ecflow_core_TestSerialisation_HPP -#define ecflow_core_TestSerialisation_HPP +#ifndef ecflow_core_test_harness_TestSerialisation_HPP +#define ecflow_core_test_harness_TestSerialisation_HPP #include #include @@ -68,4 +68,4 @@ void doSaveAndRestore(const std::string& fileName) { } // namespace ecf -#endif /* ecflow_core_TestSerialisation_HPP */ +#endif /* ecflow_core_test_harness_TestSerialisation_HPP */ diff --git a/libs/node/CMakeLists.txt b/libs/node/CMakeLists.txt index 7e655acd1..de9962ef3 100644 --- a/libs/node/CMakeLists.txt +++ b/libs/node/CMakeLists.txt @@ -65,7 +65,7 @@ ecbuild_add_test( SOURCES ${test_srcs} LIBS - test_support + test_harness.core ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) TEST_DEPENDS @@ -106,7 +106,7 @@ ecbuild_add_test( LIBS ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) - test_support + test_harness.core TEST_DEPENDS u_node ) @@ -131,7 +131,7 @@ if (ENABLE_ALL_TESTS) LIBS ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) - test_support + test_harness.core TEST_DEPENDS u_anattr ) @@ -163,7 +163,7 @@ if (ENABLE_ALL_TESTS) ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) Boost::timer - test_support + test_harness.core ) target_clangformat(p_parser CONDITION ENABLE_TESTS diff --git a/libs/rest/CMakeLists.txt b/libs/rest/CMakeLists.txt index e629d84c9..e0b121cf9 100644 --- a/libs/rest/CMakeLists.txt +++ b/libs/rest/CMakeLists.txt @@ -127,14 +127,12 @@ if (ENABLE_HTTP AND ENABLE_SERVER) integration nightly SOURCES ${test_srcs} - INCLUDES - ../base/test LIBS libhttp ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> - test_support + test_harness.base DEPENDS ecflow_server # the server is launched to support tests ecflow_http @@ -153,8 +151,6 @@ if (ENABLE_HTTP AND ENABLE_SERVER) integration nightly SOURCES ${test_srcs} - INCLUDES - ../base/test DEFINITIONS ECF_TEST_HTTP_BACKEND LIBS @@ -162,7 +158,7 @@ if (ENABLE_HTTP AND ENABLE_SERVER) ecflow_all Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> - test_support + test_harness.base DEPENDS ecflow_server # the server is launched to support tests ecflow_http diff --git a/libs/server/CMakeLists.txt b/libs/server/CMakeLists.txt index 629e3b20e..4423a0431 100644 --- a/libs/server/CMakeLists.txt +++ b/libs/server/CMakeLists.txt @@ -70,7 +70,7 @@ ecbuild_add_test( Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> Threads::Threads - test_support + test_harness.core TEST_DEPENDS u_base ) @@ -94,7 +94,7 @@ ecbuild_add_test( Boost::boost # Boost header-only libraries must be available (namely unit_test_framework) $<$:OpenSSL::SSL> Threads::Threads - test_support + test_harness.core TEST_DEPENDS u_base ) diff --git a/libs/test/CMakeLists.txt b/libs/test/CMakeLists.txt index 7af77cc8d..dd6461bea 100644 --- a/libs/test/CMakeLists.txt +++ b/libs/test/CMakeLists.txt @@ -8,7 +8,7 @@ # nor does it submit to any jurisdiction. # -add_subdirectory(src) +add_subdirectory(harness) # ================================================================= # test @@ -55,9 +55,8 @@ if (ENABLE_ALL_TESTS) SOURCES ${test_srcs} LIBS - libharness + test_harness.test $<$:OpenSSL::SSL> - test_support TEST_DEPENDS s_client ) @@ -74,9 +73,8 @@ if (ENABLE_ALL_TESTS) DEFINITIONS ECFLOW_TEST_SERVER_HTTP LIBS - libharness + test_harness.test $<$:OpenSSL::SSL> - test_support ENVIRONMENT ECF_TEST_USING_HTTP=1 TEST_DEPENDS @@ -94,9 +92,8 @@ if (ENABLE_ALL_TESTS) TestZombies.cpp TestZombies_main.cpp # test entry point LIBS - libharness + test_harness.test $<$:OpenSSL::SSL> - test_support TEST_DEPENDS s_test ) @@ -113,7 +110,7 @@ if (ENABLE_ALL_TESTS) TestSingle.cpp TestSingle_main.cpp # test entry point LIBS - libharness + test_harness.test $<$:OpenSSL::SSL> TEST_DEPENDS s_client diff --git a/libs/test/src/CMakeLists.txt b/libs/test/harness/CMakeLists.txt similarity index 86% rename from libs/test/src/CMakeLists.txt rename to libs/test/harness/CMakeLists.txt index b31382149..2b8698808 100644 --- a/libs/test/src/CMakeLists.txt +++ b/libs/test/harness/CMakeLists.txt @@ -24,15 +24,16 @@ set(srcs ecbuild_add_library( TARGET - libharness + test_harness.test NOINSTALL TYPE STATIC SOURCES ${srcs} PUBLIC_INCLUDES ./ - ../../base/test PUBLIC_LIBS - ecflow_all + test_harness.client +) +target_clangformat(test_harness.test + CONDITION ENABLE_TESTS ) -target_clangformat(libharness) diff --git a/libs/test/src/LocalServerLauncher.cpp b/libs/test/harness/LocalServerLauncher.cpp similarity index 100% rename from libs/test/src/LocalServerLauncher.cpp rename to libs/test/harness/LocalServerLauncher.cpp diff --git a/libs/test/src/LocalServerLauncher.hpp b/libs/test/harness/LocalServerLauncher.hpp similarity index 100% rename from libs/test/src/LocalServerLauncher.hpp rename to libs/test/harness/LocalServerLauncher.hpp diff --git a/libs/test/src/ScratchDir.hpp b/libs/test/harness/ScratchDir.hpp similarity index 100% rename from libs/test/src/ScratchDir.hpp rename to libs/test/harness/ScratchDir.hpp diff --git a/libs/test/src/ServerTestHarness.cpp b/libs/test/harness/ServerTestHarness.cpp similarity index 100% rename from libs/test/src/ServerTestHarness.cpp rename to libs/test/harness/ServerTestHarness.cpp diff --git a/libs/test/src/ServerTestHarness.hpp b/libs/test/harness/ServerTestHarness.hpp similarity index 100% rename from libs/test/src/ServerTestHarness.hpp rename to libs/test/harness/ServerTestHarness.hpp diff --git a/libs/test/src/TestFixture.cpp b/libs/test/harness/TestFixture.cpp similarity index 92% rename from libs/test/src/TestFixture.cpp rename to libs/test/harness/TestFixture.cpp index 679f24d09..033560d1c 100644 --- a/libs/test/src/TestFixture.cpp +++ b/libs/test/harness/TestFixture.cpp @@ -14,6 +14,7 @@ #include #include "LocalServerLauncher.hpp" +#include "SCPort.hpp" #include "TestHelper.hpp" #include "ecflow/base/cts/user/CtsApi.hpp" #include "ecflow/client/ClientEnvironment.hpp" // needed for static ClientEnvironment::hostSpecified(); ONLY @@ -55,27 +56,6 @@ bool is_local_server(std::string_view host) { return host.empty() || host == Str::LOCALHOST(); } -std::string find_available_port(std::string port) { - auto current_port = ecf::convert_to(port); - - // The goal is to create a unique port number, allowing debug and release tests to run at the same time - // Since several serves might run on same machine, on different workspaces, checking lock file is not sufficient. - // We try to ensure the port is available by attempting to contact the server to confirm that the lock has been performed. - - // (1) Search for port that isn't locked (i.e. for which there is no `.lock` file) - for(int selected_port = EcfPortLock::try_next_port_lock(current_port, true); /* always advance */; selected_port = EcfPortLock::try_next_port_lock(selected_port, true)) { - if (ClientInvoker::is_free_port(selected_port, true)) { - // We found the free port that we wanted! - return ecf::convert_to(selected_port); - } else { - // This port is in use (maybe by a server running on another workspace) - EcfPortLock::try_port_lock(selected_port); - } - } - - throw std::runtime_error("Unable to find an available port"); -} - } // namespace // ************************************************************************************************ @@ -228,7 +208,7 @@ void TestFixture::init(const std::string& project_test_dir) { // Update ClientInvoker with local host and port host_ = Str::LOCALHOST(); - port_ = find_available_port(port_); + port_ = ecf::SCPort::find_available_port(port_); client().set_host_port(host_, port_); std::cout << " _LOCAL_ SERVER running on the _LOCAL_ platform"; @@ -425,9 +405,9 @@ std::string TestFixture::local_ecf_home() { build_type = "release"; #endif - auto pid = getpid(); - std::string rel_path = "data/ECF_HOME__" + build_type + "__" + compiler + "__pid" + ecf::convert_to(pid) + "__"; + std::string rel_path = + "data/ECF_HOME__" + build_type + "__" + compiler + "__pid" + ecf::convert_to(pid) + "__"; // Allow post-fix to be added, to allow test to run in parallel if (auto custom_postfix = ecf::environment::fetch("TEST_ECF_HOME_POSTFIX"); custom_postfix) { diff --git a/libs/test/src/TestFixture.hpp b/libs/test/harness/TestFixture.hpp similarity index 100% rename from libs/test/src/TestFixture.hpp rename to libs/test/harness/TestFixture.hpp diff --git a/libs/test/src/ZombieUtil.hpp b/libs/test/harness/ZombieUtil.hpp similarity index 100% rename from libs/test/src/ZombieUtil.hpp rename to libs/test/harness/ZombieUtil.hpp diff --git a/libs/test/src/ZombieUtill.cpp b/libs/test/harness/ZombieUtill.cpp similarity index 100% rename from libs/test/src/ZombieUtill.cpp rename to libs/test/harness/ZombieUtill.cpp diff --git a/libs/udp/CMakeLists.txt b/libs/udp/CMakeLists.txt index 37e378c34..c1f2c04a2 100644 --- a/libs/udp/CMakeLists.txt +++ b/libs/udp/CMakeLists.txt @@ -89,7 +89,7 @@ ecbuild_add_test( Boost::boost Boost::unit_test_framework $<$:OpenSSL::SSL> - test_support + test_harness.core DEPENDS ecflow_server # the server is launched to support tests ${SERVER_TARGET}