File: TestLog.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 (385 lines) | stat: -rw-r--r-- 14,214 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
/*
 * 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 <stdexcept>
#include <string>

#include <boost/test/unit_test.hpp>

#include "ecflow/core/File.hpp"
#include "ecflow/core/Log.hpp"
#include "ecflow/core/Pid.hpp"
#include "ecflow/core/Timer.hpp"
#include "ecflow/test/scaffold/Naming.hpp"

using namespace ecf;

void dump_path(const fs::path& path) {
    ECF_TEST_DBG(<< "path = " << path);
    ECF_TEST_DBG(<< "path.root_path(): " << path.root_path());
    ECF_TEST_DBG(<< "path.root_name() : " << path.root_name());
    ECF_TEST_DBG(<< "path.root_directory()  : " << path.root_directory());
    ECF_TEST_DBG(<< "path.relative_path()  : " << path.relative_path());
    ECF_TEST_DBG(<< "path.parent_path()  : " << path.parent_path());
    ECF_TEST_DBG(<< "path.filename()  : " << path.filename());
    ECF_TEST_DBG(<< "path.stem()  : " << path.stem());
    ECF_TEST_DBG(<< "path.extension()  : " << path.extension());
}

BOOST_AUTO_TEST_SUITE(U_Core)

BOOST_AUTO_TEST_SUITE(T_Log)

static std::string getLogPath() {

    // ECFLOW-712, generate unique name for log file, To allow parallel test
    std::string log_file = "libs/core/test/logfile";
    log_file += Pid::getpid(); // can throw
    log_file += ".txt";
    return File::test_data(log_file, "libs/core");
}

BOOST_AUTO_TEST_CASE(test_log) {
    std::string path = getLogPath();
    ECF_NAME_THIS_TEST(<< ", using log file:" << path);

    // delete the log file if it exists.
    fs::remove(path);
    BOOST_REQUIRE_MESSAGE(!fs::exists(path), "log file not deleted " << path);

    LogFlusher logFlusher;
    Log::create(path);
    LOG(Log::MSG, "First Message");
    LOG(Log::LOG, "LOG");
    LOG(Log::ERR, "ERROR");
    LOG(Log::WAR, "WARNING");
    LOG(Log::DBG, "DEBUG");
    LOG(Log::OTH, "OTHER");
    log(Log::OTH, "test: void log(Log::LogType,const std::string& message)");

    LOG(Log::OTH, "test: LOG(level,path << path) " << path << " " << path);

    Log::instance()->log(Log::OTH, "OTHER2");

    BOOST_CHECK_MESSAGE(fs::exists(path), "log file " << path << " not created \n");
}

BOOST_AUTO_TEST_CASE(test_log_append) {
    ECF_NAME_THIS_TEST();

    std::string path = getLogPath();

    BOOST_REQUIRE_MESSAGE(fs::exists(path), "log file " << path << " not created by previous test\n");

    {
        LogFlusher logFlusher;
        LOG(Log::MSG, "Last Message");
    }

    // Load the log file into a vector, of strings, and test content
    std::vector<std::string> lines;
    BOOST_REQUIRE_MESSAGE(File::splitFileIntoLines(path, lines, true /*IGNORE EMPTY LINE AT THE END*/),
                          "Failed to open log file" << " (" << strerror(errno) << ")");
    BOOST_REQUIRE(lines.size() != 0);
    BOOST_CHECK_MESSAGE(lines.size() == 10, " Expected 10 lines in log, but found " << lines.size() << "\n");
    BOOST_CHECK_MESSAGE(lines[0].find("First Message") != std::string::npos,
                        "Expected first line to contain 'First Message' but found " << lines[0] << "\n");
    BOOST_CHECK_MESSAGE(lines.back().find("Last Message") != std::string::npos,
                        "Expected last line to contain 'Last Message' but found " << lines.back() << "\n");

    // Clear the log file. Comment out for debugging
    Log::instance()->clear();
    BOOST_CHECK_MESSAGE(fs::file_size(path) == 0, "Clear of log file failed\n");

    // Remove the log file. Comment out for debugging
    fs::remove(path);

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();
}

BOOST_AUTO_TEST_CASE(test_log_path) {
    ECF_NAME_THIS_TEST();

    Log::create("test_log_path.log");

    // make sure path returned is absolute
    std::string path = Log::instance()->path();
    BOOST_REQUIRE_MESSAGE(path[0] == '/', "Expected absolute paths for log file but found " << path);

    // Remove the log file. Comment out for debugging
    fs::remove(path);

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();
}

BOOST_AUTO_TEST_CASE(test_log_new_path_errors) {
    ECF_NAME_THIS_TEST();

    // delete the log file if it exists.
    std::string path = getLogPath();
    fs::remove(path);

    // create a now log file.
    Log::create(path);
    LOG(Log::MSG, "First Message");
    LOG(Log::LOG, "LOG");

    // Specify bad paths for new log file
    // First test empty path throws
    BOOST_REQUIRE_THROW(Log::instance()->new_path(""), std::runtime_error);

    // If a path is specified make sure parent directory exists
    fs::path current_path = fs::current_path();
    std::string path2     = current_path.string();
    path2 += "/a/made/up/path/fred.log";
    BOOST_REQUIRE_THROW(Log::instance()->new_path(path2), std::runtime_error);

    // Make sure path does not correspond to a directory
    BOOST_REQUIRE_THROW(Log::instance()->new_path(current_path.parent_path().string()), std::runtime_error);

    // fs::path valid_path = getLogPath();
    // dump_path(valid_path);

    // Remove the log file. Comment out for debugging
    fs::remove(Log::instance()->path());

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();
}

BOOST_AUTO_TEST_CASE(test_log_new_path) {
    ECF_NAME_THIS_TEST();

    // delete the log file if it exists.
    std::string path = getLogPath();
    fs::remove(path);

    // create a new log file.
    Log::create(path);
    BOOST_CHECK_MESSAGE(fs::exists(Log::instance()->path()),
                        "Log file should be created after explicit call to Log::create()\n");
    LOG(Log::LOG, "LOG");
    fs::remove(Log::instance()->path());

    // Specify a new log path. Path could be a relative path like "test/logfile.log"
    std::string relative_path = File::test_data("libs/core/test/logfile.log", "libs/core");

    BOOST_REQUIRE_NO_THROW(Log::instance()->new_path(relative_path));
    BOOST_CHECK_MESSAGE(!fs::exists(Log::instance()->path()),
                        "Log file should *NOT* be created until first message is logged\n");
    LOG(Log::LOG, "LOG");
    BOOST_CHECK_MESSAGE(fs::exists(Log::instance()->path()),
                        "Log file should be created after first message is logged\n");
    fs::remove(Log::instance()->path());

    // Specify a new log path. This time we just specify a file name, without a path.
    BOOST_REQUIRE_NO_THROW(Log::instance()->new_path("testlog.log"));
    BOOST_CHECK_MESSAGE(!fs::exists(Log::instance()->path()),
                        "Log file should not be created until first message is logged\n");
    // File not created until a message is logged
    LOG(Log::LOG, "LOG");
    BOOST_CHECK_MESSAGE(fs::exists(Log::instance()->path()),
                        "Log file should be created after first message is logged\n");

    fs::remove(Log::instance()->path());

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();
}

BOOST_AUTO_TEST_CASE(test_get_last_n_lines_from_log) {
    ECF_NAME_THIS_TEST();

    // delete the log file if it exists.
    std::string path = getLogPath();
    fs::remove(path);
    BOOST_REQUIRE_MESSAGE(!fs::exists(path), "log file not deleted " << path);

    // Create the log file;
    Log::create(path);
    BOOST_CHECK_MESSAGE(fs::exists(path), "log file " << path << " not created \n");

    // Log file should be empty
    const int NO_OF_LINES_IN_LOG_FILE = 200;
    {
        for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; i++) {
            std::string line = Log::instance()->contents(i);
            BOOST_CHECK_MESSAGE(line.empty(), "Expected empty string but found\n" << line);
        }
    }

    // Populate the log file
    std::string msg = "This is message ";
    for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; ++i) {
        LOG(Log::MSG, msg << i);
    }

    // Now check, getting the lines
    {
        std::string line = Log::instance()->contents(0);
        BOOST_CHECK_MESSAGE(line.empty(), "Expected empty string but found\n" << line);
    }
    {
        // Check we get back the number of line requested
        for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; i++) {
            std::string lines = Log::instance()->contents(i);
            int newlineCount  = std::count(lines.begin(), lines.end(), '\n');
            BOOST_CHECK_MESSAGE(i == newlineCount, "expected to  " << i << " newlines but found " << newlineCount);
        }
    }
    {
        // Check we get back *ALL* lines requested
        std::string lines = Log::instance()->contents(NO_OF_LINES_IN_LOG_FILE);
        for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; i++) {
            std::stringstream ss;
            ss << msg << i;
            std::string str_to_find = ss.str();
            BOOST_CHECK_MESSAGE(lines.find(str_to_find) != std::string::npos,
                                "expected to find " << str_to_find << " in the log file");
        }
    }

    {
        // Request more than is available, should only get back whats there
        std::string lines = Log::instance()->contents(NO_OF_LINES_IN_LOG_FILE * 2);
        int newlineCount  = std::count(lines.begin(), lines.end(), '\n');
        BOOST_CHECK_MESSAGE(NO_OF_LINES_IN_LOG_FILE == newlineCount,
                            "expected " << NO_OF_LINES_IN_LOG_FILE << " newlines but found " << newlineCount);
    }

    fs::remove(Log::instance()->path());

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();
}

BOOST_AUTO_TEST_CASE(test_get_first_n_lines_from_log) {
    ECF_NAME_THIS_TEST();

    // delete the log file if it exists.
    std::string path = getLogPath();
    fs::remove(path);
    BOOST_REQUIRE_MESSAGE(!fs::exists(path), "log file not deleted " << path << " not created \n");

    // Create the log file;
    Log::create(path);
    BOOST_CHECK_MESSAGE(fs::exists(path), "log file " << path << " not created \n");

    // Populate the log file
    const int NO_OF_LINES_IN_LOG_FILE = 200;
    std::string msg                   = "This is message ";
    for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; ++i) {
        LOG(Log::MSG, msg << i);
    }

    // Now check, getting the lines
    {
        // Get the first line
        std::string line     = Log::instance()->contents(-1);
        std::string expected = msg + "0";
        BOOST_CHECK_MESSAGE(line.find(expected) != std::string::npos,
                            "Expected '" << expected << "' but found\n"
                                         << line);
    }
    {
        // Get the first & second line
        std::string line      = Log::instance()->contents(-2);
        std::string expected0 = msg + "0";
        std::string expected1 = msg + "1";
        BOOST_CHECK_MESSAGE(line.find(expected0) != std::string::npos,
                            "Expected '" << expected0 << "' but found\n"
                                         << line);
        BOOST_CHECK_MESSAGE(line.find(expected1) != std::string::npos,
                            "Expected '" << expected1 << "' but found\n"
                                         << line);
    }
    {
        // Check we get back the number of line requested
        for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; i++) {
            std::string lines = Log::instance()->contents(-i);
            int newlineCount  = std::count(lines.begin(), lines.end(), '\n');
            BOOST_CHECK_MESSAGE(i == newlineCount, "expected to  " << i << " newlines but found " << newlineCount);
        }
    }
    {
        std::string lines = Log::instance()->contents(-NO_OF_LINES_IN_LOG_FILE);
        for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; i++) {
            std::stringstream ss;
            ss << msg << i;
            std::string expected = ss.str();
            BOOST_CHECK_MESSAGE(lines.find(expected) != std::string::npos,
                                "Expected '" << expected << "' but found for i " << i);
        }
    }

    {
        // Request more than is available, should only get back whats there
        std::string lines = Log::instance()->contents(-NO_OF_LINES_IN_LOG_FILE * 2);
        int newlineCount  = std::count(lines.begin(), lines.end(), '\n');
        BOOST_CHECK_MESSAGE(NO_OF_LINES_IN_LOG_FILE == newlineCount,
                            "expected " << NO_OF_LINES_IN_LOG_FILE << " newlines but found " << newlineCount);
    }

    fs::remove(Log::instance()->path());

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();
}

BOOST_AUTO_TEST_CASE(test_get_log_timing) {
    ECF_NAME_THIS_TEST();

    // *************************************************************************************
    // This test was used with *DIFFERENT* implementations for Log::instance()->contents(1)
    // What is shows, is that for optimal performance we should *NOT* load the entire log file
    // This can be several giga bytes.
    // **************************************************************************************

    // delete the log file if it exists.
    std::string path = getLogPath();
    fs::remove(getLogPath());
    BOOST_REQUIRE_MESSAGE(!fs::exists(path), "log file not deleted " << path << " not created \n");

    // Create the log file;
    Log::create(path);
    BOOST_CHECK_MESSAGE(fs::exists(path), "log file " << path << " not created \n");

    // Populate the log file
    const int NO_OF_LINES_IN_LOG_FILE = 20000;
    std::string msg                   = "This is message ";
    for (int i = 0; i < NO_OF_LINES_IN_LOG_FILE; ++i) {
        LOG(Log::MSG, msg << i);
    }

    DurationTimer timer;

    {
        const int LOOP = 100;
        for (int i = 0; i < LOOP; i++) {
            std::string lines = Log::instance()->contents(1);
            BOOST_CHECK_MESSAGE(!lines.empty(), "expected entry");
        }
    }

    fs::remove(Log::instance()->path());

    // Explicitly destroy log. To keep valgrind happy
    Log::destroy();

#if PRINT_TIMING_RESULTS
    ECF_TEST_DBG(timer.duration() << "s\n");
#endif
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()