File: TestFixture.cpp

package info (click to toggle)
ecflow 5.15.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 51,868 kB
  • sloc: cpp: 269,341; python: 22,756; sh: 3,609; perl: 770; xml: 333; f90: 204; ansic: 141; makefile: 70
file content (450 lines) | stat: -rw-r--r-- 16,658 bytes parent folder | download
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();
}