1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
|
/*
* 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 "TestFixture.hpp"
#include <fstream> // for ofstream
#include <iostream>
#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
#include "ecflow/client/Rtt.hpp"
#include "ecflow/core/EcfPortLock.hpp"
#include "ecflow/core/File.hpp"
#include "ecflow/core/Filesystem.hpp"
#include "ecflow/core/Host.hpp"
#include "ecflow/core/PrintStyle.hpp"
#include "ecflow/core/Str.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Task.hpp"
#ifdef DEBUG
std::string rtt_filename = "rtt.dat";
#else
std::string rtt_filename = "rtt_d.dat";
#endif
std::unique_ptr<ScratchDir> TestFixture::scratch_dir_;
std::string TestFixture::host_;
std::string TestFixture::port_;
std::string TestFixture::test_dir_;
std::string TestFixture::project_test_dir_ = "libs/test";
using namespace std;
using namespace ecf;
namespace /* anonymous */ {
bool is_external_server_running_remotelly(std::string_view host) {
return !host.empty() && host != Str::LOCALHOST();
}
bool is_external_server_running_locally(std::string_view host) {
return !host.empty() && host == Str::LOCALHOST();
}
bool is_local_server(std::string_view host) {
return host.empty() || host == Str::LOCALHOST();
}
} // namespace
// ************************************************************************************************
// For test purpose the server can be started:
//
// (1) Externally but on a different platform. by defining env variable: export ECF_HOST=itanium
// In this case we NEED to copy the test data, so that it is
// accessible by the client AND server
// (2) Externally but on the same machine. by defining env variable: export ECF_HOST=localhost
// WHY? To test for memory leak. (i.e. with valgrind)
// (3) By this test fixture
//
// When invoking the server Externally _MUST_ use .
// ./Server/bin/gcc.<version>/debug/server --ecfinterval=2
// The --ecfinterval=2 is _important_ or test will take a long time
// ************************************************************************************************
// Uncomment to preserve the files for test
// #define DEBUG_HOST_SERVER 1
#define DEBUG_LOCAL_SERVER 1
TestFixture::TestFixture(const std::string& project_test_dir) {
init(project_test_dir);
}
TestFixture::TestFixture() {
init("libs/test");
}
ClientInvoker& TestFixture::client() {
static ClientInvoker theClient_;
return theClient_;
}
void TestFixture::init(const std::string& project_test_dir) {
TestFixture::project_test_dir_ = project_test_dir;
auto test_dir = local_ecf_home();
std::cout << "TestFixture::TestFixture() project_test_dir :" << project_test_dir << "\n";
std::cout << "TestFixture::TestFixture() local_ecf_home :" << test_dir << "\n";
std::cout << "TestFixture::TestFixture() cwd :" << fs::current_path() << "\n";
if (!fs::exists(test_dir)) {
fs::create_directories(test_dir);
}
// client side file for recording all ClientInvoker round trip times
fs::remove(rtt_filename);
Rtt::create(rtt_filename);
// ********************************************************
// Note: Global fixture Constructor cannot use BOOST macro
// ********************************************************
// Let first see if we need do anything. If ECF_HOST is specified (ie the name
// of the machine, which has the ecflow server), only then do we need to do anything.
// The server must have access to the file system specified by ECF_HOME.
// This becomes an issue when the server is on a different machine
host_ = ClientEnvironment::hostSpecified();
port_ = ClientEnvironment::portSpecified(); // returns ECF_PORT, otherwise Str::DEFAULT_PORT_NUMBER
#ifdef ECF_OPENSSL
if (ecf::environment::has("ECF_SSL")) {
std::cout << " Openssl enabled\n";
}
#endif
if (is_external_server_running_remotelly(host_)) {
// Option (1)
//
// Going to perform the tests using an external server, running on a remote machine.
// We require the external server file system to be mounted locally,
// and use the $SCRATCH environment variable to locate the mount point of the server data.
// This allows to deploy the necessary test data, reset ECF_HOME, and configure the server.
client().set_host_port(host_, port_);
std::cout << " _EXTERNAL_ SERVER running on a _REMOTE_ platform";
std::cout << " (" << host_ << ":" << port_ << ").\n";
scratch_dir_ = std::make_unique<ScratchDir>();
if (const auto& d = scratch_dir_->test_dir(); fs::exists(d)) {
fs::remove_all(d);
}
{
auto success = File::createDirectories(scratch_dir_->home_dir());
BOOST_REQUIRE_MESSAGE(success, "Create the home directory inside the $SCRATCH");
}
{
auto success = fs::exists(scratch_dir_->home_dir());
BOOST_REQUIRE_MESSAGE(success, "The home directory inside the $SCRATCH exists");
}
{ // Ensure that local includes data exists, before attempting to copy it into $SCRATCH
auto success = fs::exists(includes());
BOOST_REQUIRE_MESSAGE(success, "The includes directory exists");
}
std::cout << " Copying test data ...\n";
// Copy over the includes directory to the SCRATCH area.
std::string scratchIncludes = scratch_dir_->test_dir() + "/";
std::string do_copy = "cp -r " + includes() + " " + scratchIncludes;
if (system(do_copy.c_str()) != 0) {
assert(false);
}
// clear log file
clearLog();
}
else if (is_external_server_running_locally(host_)) {
// Option (2)
//
// Going to perform the tests using an external server, running on the local machine.
client().set_host_port(host_, port_);
std::cout << " _EXTERNAL_ SERVER running on the _LOCAL_ platform";
std::cout << " (" << client().host() << ":" << client().port() << ").\n";
// Print the server stats before we start + checks if it is up and running::
client().set_cli(true); // so server stats are written to standard out
if (client().stats() != 0) {
std::cout << " ClientInvoker " << CtsApi::stats() << " failed: " << client().errorMsg()
<< ". Is the server running?\n";
assert(false);
}
client().set_cli(false);
// log file may have been deleted, by previous tests. Create a new log file
std::string the_log_file = TestFixture::pathToLogFile();
if (!fs::exists(the_log_file)) {
std::cout << " Log file " << the_log_file << " does NOT exist, attempting to recreate\n";
std::cout << " Creating new log(via remote server) file " << the_log_file << "\n";
if (0 == client().new_log(the_log_file)) {
client().logMsg("Created new log file. msg sent to force new log file to be written to disk");
}
else {
cout << " Log file " << TestFixture::pathToLogFile() << " creation failed " << client().errorMsg()
<< "\n";
}
}
else {
cout << " Log file " << the_log_file << " already exists\n";
}
}
else {
// Option (3)
//
// Going to perform the tests using a server launched as part of the test setup.
// Update ClientInvoker with local host and port
host_ = Str::LOCALHOST();
port_ = ecf::SCPort::find_available_port(port_);
client().set_host_port(host_, port_);
std::cout << " _LOCAL_ SERVER running on the _LOCAL_ platform";
std::cout << " (" << host_ << ":" << port_ << ").\n";
bool use_http = false;
if (auto found = ecf::environment::fetch("ECF_TEST_USING_HTTP"); found) {
use_http = found.value() == "1";
client().enable_http();
client().debug(true);
}
// clang-format off
LocalServerLauncher{}
.with_host(host_)
.with_port(port_)
.using_http(use_http)
.launch();
// clang-format on
}
/// Ping the server to see if its running
/// Assume remote/local server started on the default port
/// Either way, we wait for 60 seconds for server, for it to respond to pings
/// This is important when server is started locally. We must wait for it to come alive.
if (!client().wait_for_server_reply()) {
cout << " Ping server on " << client().host() << Str::COLON() << client().port()
<< " failed. Is the server running ? " << client().errorMsg() << "\n";
assert(false);
}
cout << " Ping OK: server running on: " << client().host() << Str::COLON() << client().port() << "\n";
// Log file must exist, otherwise test will not work. Log file required for comparison
if (!fs::exists(TestFixture::pathToLogFile())) {
cout << " Log file " << TestFixture::pathToLogFile() << " does not exist *************************** \n";
assert(false);
}
// Check host and port match
assert(host_ == client().host());
assert(port_ == client().port());
}
TestFixture::~TestFixture() {
// Note: Global fixture Destructor cannot use BOOST macro
std::cout << "TestFixture::~TestFixture() " << client().host() << ":" << client().port() << "\n";
// destructors should not allow exception propagation
try {
#ifndef DEBUG_HOST_SERVER
if (!host_.empty() && fs::exists(test_dir_)) {
fs::remove_all(test_dir_);
}
#endif
#ifndef DEBUG_LOCAL_SERVER
if (fs::exists(local_ecf_home())) {
fs::remove_all(local_ecf_home());
}
#endif
std::cout << " Print the server suites\n";
client().set_cli(true); // so server stats are written to standard out
client().set_throw_on_error(false); // destructors should not allow exception propagation
if (client().suites() != 0) {
std::cout << "TestFixture::~TestFixture(): ClientInvoker " << CtsApi::suites()
<< " failed: " << client().errorMsg() << "\n";
}
std::cout << " Print the server stats\n";
if (client().stats() != 0) {
std::cout << "TestFixture::~TestFixture(): ClientInvoker " << CtsApi::stats()
<< " failed: " << client().errorMsg() << "\n";
}
std::cout << " Kill the server, as all suites are complete. will work for local or external\n";
if (client().terminateServer() != 0) {
std::cout << "TestFixture::~TestFixture(): ClientInvoker " << CtsApi::terminateServer()
<< " failed: " << client().errorMsg() << "\n";
EcfPortLock::remove(port_);
assert(false);
}
sleep(1); // allow time to update log file
std::cout << " Remove the generated check point files, at end of test\n";
Host host;
fs::remove(host.ecf_log_file(port_));
fs::remove(host.ecf_checkpt_file(port_));
fs::remove(host.ecf_backup_checkpt_file(port_));
std::cout << " remove the lock file\n";
EcfPortLock::remove(port_);
std::cout << " Rtt::destroy(), so that we flush the rtt_filename\n";
Rtt::destroy();
cout << "\nTiming: *NOTE*: The child commands *NOT* recorded. Since its a separate exe(ecflow_client), called "
"via .ecf script\n";
cout << Rtt::analysis(rtt_filename); // report round trip times
fs::remove(rtt_filename);
}
catch (std::exception& ex) {
std::cout << "TestFixture::~TestFixture() caught exception " << ex.what() << "\n";
}
catch (...) {
std::cout << "TestFixture::~TestFixture() caught unknown exception\n";
}
std::cout << "TestFixture::~TestFixture() END\n";
}
int TestFixture::job_submission_interval() {
return LocalServerLauncher::job_submission_interval();
}
std::string TestFixture::smshome() {
if (is_local_server(TestFixture::host_)) {
return local_ecf_home();
}
return scratch_dir_->home_dir();
}
std::string TestFixture::theClientExePath() {
std::string extra_options = "";
if (auto var = ecf::environment::fetch("ECF_TEST_USING_HTTP"); var) {
extra_options = " --http";
}
if (is_local_server(TestFixture::host_)) {
return File::find_ecf_client_path() + extra_options;
}
else if (auto client_path_p = ecf::environment::fetch("ECF_CLIENT_EXE_PATH"); client_path_p) {
return client_path_p.value() + extra_options;
}
else {
// Try this before complaining
std::string path = "/usr/local/apps/ecflow/current/bin/ecflow_client";
if (fs::exists(path)) {
return path + extra_options;
}
cout << "Please set ECF_CLIENT_EXE_PATH. This needs to be set to path to the client executable\n";
cout << "The client must be the one that was built on the same platform as the server\n";
assert(false);
return string(); // This is needed to silence compiler warnings about no return
}
}
void TestFixture::clearLog() {
// Can't remove log on remote server, just clear the log file
client().clearLog();
}
std::string TestFixture::pathToLogFile() {
if (is_local_server(TestFixture::host_)) {
Host host;
return host.ecf_log_file(port_);
}
if (auto var = ecf::environment::fetch("ECF_LOG"); var) {
return var.value();
}
else {
cout << "TestFixture::pathToLogFile(): assert failed\n";
cout << "Please set ECF_LOG. This needs to be set to path to the log file\n";
cout << "that can be seen by the client and server\n";
assert(false);
return string(); // This is needed to silence compiler warnings about no return
}
}
std::string TestFixture::local_ecf_home() {
std::string compiler = "unknown";
#if defined(_AIX)
compiler = "aix";
#elif defined(HPUX)
compiler = "hpux";
#else
#if defined(__clang__)
compiler = "clang";
#elif defined(__INTEL_COMPILER)
compiler = "intel";
#elif defined(_CRAYC)
compiler = "cray";
#else
compiler = "gnu";
#endif
#endif
std::string build_type = "unknown";
#ifdef DEBUG
build_type = "debug";
#else
build_type = "release";
#endif
auto pid = getpid();
std::string rel_path =
"data/ECF_HOME__" + build_type + "__" + compiler + "__pid" + ecf::convert_to<std::string>(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) {
rel_path += custom_postfix.value();
}
std::string absolute_path = File::test_data_in_current_dir(rel_path);
return absolute_path;
}
std::string TestFixture::includes() {
// Get to the root source directory
std::string includes_path = File::root_source_dir();
includes_path += "/";
includes_path += project_test_dir_;
includes_path += "/data/includes";
// std::cout << "includes_path = " << includes_path << " ==============================================\n";
return includes_path;
}
/// Given a task name like "a" find the first task matching that name and return the absolute node path
std::string TestFixture::taskAbsNodePath(const Defs& theDefs, const std::string& taskName) {
std::vector<Task*> vec;
theDefs.getAllTasks(vec);
for (Task* t : vec) {
if (t->name() == taskName) {
return t->absNodePath();
}
}
cout << "TestFixture::taskAbsNodePath: assert failed: Could not find task " << taskName << "\n";
assert(false); // could not find the task ??
return string();
}
const std::string& TestFixture::server_version() {
client().server_version();
return TestFixture::client().get_string();
}
|