• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 1787

28 Oct 2023 12:35PM UTC coverage: 91.591% (+0.009%) from 91.582%
1787

push

Evergreen

web-flow
Improve configurations for sanitized builds (#6911)

* Refactor sanitizer flags for different build types:

** Enable address sanitizer for msvc
** Allow to build with sanitizer for diffent optimized build (also Debug)
** Make RelASAN, RelTSAN, RelUSAN, RelUSAN just shortcuts for half-optimized builds

* Fix usage of moved object for fuzz tester
* Check asan/tsan on macos x64/arm64
* Check asan with msvc 2019
* Remove Jenkins sanitized builders replaced by evergreen configs
* Work-around stack-use-after-scope with msvc2019 and mpark
* Fix crash on check with staled ColKeys
* fix a buffer overrun in a test
* fix a race in async_open_realm test util
* Add some logger related test fixes
* Work around catch2 limmitation with not thread safe asserts and TSAN races
* Run multiprocesses tests under sanitizers
* add assert for an error reported by undefined sanitizer
* Workaround uv scheduler main thread only constraint for callbacks called from non main thread and requesting a realm

---------

Co-authored-by: James Stone <james.stone@mongodb.com>

94356 of 173648 branches covered (0.0%)

54 of 63 new or added lines in 15 files covered. (85.71%)

2202 existing lines in 52 files now uncovered.

230692 of 251872 relevant lines covered (91.59%)

7167285.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.76
/test/util/unit_test.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <cstdlib>
20
#include <functional>
21
#include <stdexcept>
22
#include <map>
23
#include <string>
24
#include <locale>
25
#include <iostream>
26
#include <iomanip>
27
#include <fstream>
28

29
#include <realm/util/thread.hpp>
30
#include <realm/util/timestamp_logger.hpp>
31

32
#include <external/json/json.hpp>
33

34
#include "demangle.hpp"
35
#include "timer.hpp"
36
#include "random.hpp"
37
#include "wildcard.hpp"
38
#include "unit_test.hpp"
39

40
using namespace realm;
41
using namespace realm::util;
42
using namespace realm::test_util;
43
using namespace realm::test_util::unit_test;
44

45

46
// FIXME: Write quoted strings with escaped nonprintables
47

48

49
namespace {
50

51
void replace_char(std::string& str, char c, const std::string& replacement)
52
{
×
53
    for (size_t pos = str.find(c); pos != std::string::npos; pos = str.find(c, pos + 1))
×
54
        str.replace(pos, 1, replacement);
×
55
}
×
56

57

58
std::string xml_escape(const std::string& value)
59
{
×
60
    std::string value_2 = value;
×
61
    replace_char(value_2, '&', "&amp;");
×
62
    replace_char(value_2, '<', "&lt;");
×
63
    replace_char(value_2, '>', "&gt;");
×
64
    replace_char(value_2, '\'', "&apos;");
×
65
    replace_char(value_2, '\"', "&quot;");
×
66
    return value_2;
×
67
}
×
68

69
class EvergreenReporter final : public Reporter {
70
public:
71
    explicit EvergreenReporter(const std::string& output_path)
72
        : m_output(output_path)
73
    {
8✔
74
    }
8✔
75

76
    void begin(const TestContext& context) override
77
    {
7,830✔
78
        m_results.emplace(std::make_pair(context.get_test_name(), TestResult{}));
7,830✔
79
    }
7,830✔
80

81
    void fail(const TestContext& context, const char*, long, const std::string&) override
82
    {
×
83
        auto it = m_results.find(context.get_test_name());
×
84
        REALM_ASSERT(it != m_results.end());
×
85
        it->second.status = "fail";
×
86
    }
×
87

88
    void end(const TestContext& context, double) override
89
    {
7,830✔
90
        auto it = m_results.find(context.get_test_name());
7,830✔
91
        REALM_ASSERT(it != m_results.end());
7,830✔
92
        if (it->second.status == "unknown") {
7,830✔
93
            it->second.status = "pass";
7,830✔
94
        }
7,830✔
95
        it->second.end_time = std::chrono::system_clock::now();
7,830✔
96
    }
7,830✔
97

98
    void summary(const SharedContext&, const Summary&) override
99
    {
8✔
100
        auto results_arr = nlohmann::json::array();
8✔
101
        for (const auto& [test_name, cur_result] : m_results) {
7,830✔
102
            auto to_millis = [](const auto& tp) -> double {
15,660✔
103
                return static_cast<double>(
15,660✔
104
                    std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count());
15,660✔
105
            };
15,660✔
106
            double start_secs = to_millis(cur_result.start_time) / 1000;
7,830✔
107
            double end_secs = to_millis(cur_result.end_time) / 1000;
7,830✔
108
            int exit_code = 0;
7,830✔
109
            if (cur_result.status != "pass") {
7,830✔
110
                exit_code = 1;
×
111
            }
×
112

3,916✔
113
            nlohmann::json cur_result_obj = {{"test_file", test_name}, {"status", cur_result.status},
7,830✔
114
                                             {"exit_code", exit_code}, {"start", start_secs},
7,830✔
115
                                             {"end", end_secs},        {"elapsed", end_secs - start_secs}};
7,830✔
116
            results_arr.push_back(std::move(cur_result_obj));
7,830✔
117
        }
7,830✔
118
        auto result_file_obj = nlohmann::json{{"results", std::move(results_arr)}};
8✔
119
        m_output << result_file_obj << std::endl;
8✔
120
    }
8✔
121

122
private:
123
    struct TestResult {
124
        TestResult()
125
            : start_time{std::chrono::system_clock::now()}
126
            , end_time{}
127
            , status{"unknown"}
128
        {
7,830✔
129
        }
7,830✔
130
        std::chrono::time_point<std::chrono::system_clock> start_time;
131
        std::chrono::time_point<std::chrono::system_clock> end_time;
132
        std::string status;
133
    };
134
    std::map<std::string, TestResult> m_results;
135
    std::ofstream m_output;
136
};
137

138

139
class XmlReporter : public Reporter {
140
public:
141
    XmlReporter(std::ostream& out)
142
        : m_out(out)
143
    {
×
144
    }
×
145

146
    void begin(const TestContext& context) override
147
    {
×
148
        auto key = key_type(context.test_index, context.recurrence_index);
×
149
        m_tests.emplace(key, test());
×
150
    }
×
151

152
    void fail(const TestContext& context, const char* file_name, long line_number,
153
              const std::string& message) override
154
    {
×
155
        failure f;
×
156
        f.file_name = file_name;
×
157
        f.line_number = line_number;
×
158
        f.message = message;
×
159
        auto key = key_type(context.test_index, context.recurrence_index);
×
160
        auto i = m_tests.find(key);
×
161
        i->second.failures.push_back(f);
×
162
    }
×
163

164
    void end(const TestContext& context, double elapsed_seconds) override
165
    {
×
166
        auto key = key_type(context.test_index, context.recurrence_index);
×
167
        auto i = m_tests.find(key);
×
168
        i->second.elapsed_seconds = elapsed_seconds;
×
169
    }
×
170

171
    void summary(const SharedContext& context, const Summary& results_summary) override
172
    {
×
173
        m_out << "<?xml version=\"1.0\"?>\n"
×
174
                 "<unittest-results "
×
175
                 "tests=\"" << results_summary.num_executed_tests << "\" "
×
176
                 "failedtests=\"" << results_summary.num_failed_tests << "\" "
×
177
                 "checks=\"" << results_summary.num_executed_checks << "\" "
×
178
                 "failures=\"" << results_summary.num_failed_checks << "\" "
×
179
                 "time=\"" << results_summary.elapsed_seconds << "\">\n";
×
180

181
        for (const auto& p : m_tests) {
×
182
            auto key = p.first;
×
183
            const test& t = p.second;
×
184
            size_t test_index = key.first;
×
185
            int recurrence_index = key.second;
×
186
            const TestDetails details = context.test_list.get_test_details(test_index);
×
187
            std::string test_name{details.test_name};
×
188
            if (context.num_recurrences > 1) {
×
189
                test_name = test_name + "#" + util::to_string(recurrence_index + 1);
×
190
            }
×
191

192
            m_out << "  <test suite=\"" << xml_escape(details.suite_name) << "\" "
×
193
                     "name=\"" << xml_escape(test_name) << "\" "
×
194
                     "time=\"" << t.elapsed_seconds << "\"";
×
195
            if (t.failures.empty()) {
×
196
                m_out << "/>\n";
×
197
                continue;
×
198
            }
×
199
            m_out << ">\n";
×
200

201
            for (auto& i_2 : t.failures) {
×
202
                std::string msg = xml_escape(i_2.message);
×
203
                m_out << "    <failure message=\"" << i_2.file_name
×
204
                      << "(" << i_2.line_number << ") : " << msg << "\"/>\n";
×
205
            }
×
206
            m_out << "  </test>\n";
×
207
        }
×
208
        m_out << "</unittest-results>\n";
×
209
    }
×
210

211
protected:
212
    struct failure {
213
        const char* file_name;
214
        long line_number;
215
        std::string message;
216
    };
217

218
    struct test {
219
        std::vector<failure> failures;
220
        double elapsed_seconds = 0;
221
    };
222

223
    using key_type = std::pair<size_t, int>; // (test index, recurrence index)
224
    std::map<key_type, test> m_tests;
225

226
    std::ostream& m_out;
227
};
228

229

230
class JUnitReporter : public XmlReporter {
231
public:
232
    JUnitReporter(std::ostream& out, std::string_view test_suite_name)
233
        : XmlReporter(out)
234
        , m_test_suite_name(test_suite_name)
235
    {
×
236
    }
×
237

238
    void summary(const SharedContext& context, const Summary& results_summary) override
239
    {
×
240
        m_out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
×
241

242
        m_out << "<testsuites>\n"
×
243
              << "  <testsuite "
×
244
              << "name=\"" << m_test_suite_name << "\" "
×
245
              << "tests=\"" << results_summary.num_executed_tests << "\" "
×
246
              << "disabled=\"" << results_summary.num_excluded_tests << "\" "
×
247
              << "failures=\"" << results_summary.num_failed_tests << "\" "
×
248
              << "id=\"0\" "
×
249
              << "time=\"" << results_summary.elapsed_seconds << "\""
×
250
              << ">\n";
×
251

252
        for (const auto& p : m_tests) {
×
253
            auto key = p.first;
×
254
            const test& t = p.second;
×
255
            size_t test_index = key.first;
×
256
            int recurrence_index = key.second;
×
257
            const TestDetails details = context.test_list.get_test_details(test_index);
×
258
            std::string test_name{details.test_name};
×
259
            if (context.num_recurrences > 1) {
×
260
                test_name = test_name + "#" + util::to_string(recurrence_index + 1);
×
261
            }
×
262

263
            m_out << "    <testcase name=\"" << xml_escape(test_name) << "\" "
×
264
                  << "status=\"" << (t.failures.size() > 0 ? "failed" : "passed") << "\" "
×
265
                  << "classname=\"" << xml_escape(test_name) << "\" "
×
266
                  << "time=\"" << t.elapsed_seconds << "\"";
×
267

268
            if (t.failures.empty()) {
×
269
                m_out << "/>\n";
×
270
            }
×
271
            else {
×
272
                m_out << ">\n";
×
273

274
                for (auto& i_2 : t.failures) {
×
275
                    std::string msg = xml_escape(i_2.message);
×
276
                    m_out << "      <failure type=\"assertion failed\" "
×
277
                          << "message=\"" << i_2.file_name << "(" << i_2.line_number << ") : " << msg << "\"/>\n";
×
278
                }
×
279
                m_out << "    </testcase>\n";
×
280
            }
×
281
        }
×
282
        m_out << "  </testsuite>\n</testsuites>\n";
×
283
    }
×
284

285
private:
286
    std::string m_test_suite_name;
287
};
288

289

290
class ManifoldReporter : public Reporter {
291
public:
292
    ManifoldReporter(const std::vector<std::unique_ptr<Reporter>>& subreporters)
293
    {
8✔
294
        for (auto& r : subreporters)
8✔
295
            m_subreporters.push_back(r.get());
16✔
296
    }
8✔
297

298
    void begin(const TestContext& context) override
299
    {
7,830✔
300
        for (Reporter* r : m_subreporters)
7,830✔
301
            r->begin(context);
15,660✔
302
    }
7,830✔
303

304
    void fail(const TestContext& context, const char* file_name, long line_number,
305
              const std::string& message) override
306
    {
×
307
        for (Reporter* r : m_subreporters)
×
308
            r->fail(context, file_name, line_number, message);
×
309
    }
×
310

311
    void end(const TestContext& context, double elapsed_seconds) override
312
    {
7,830✔
313
        for (Reporter* r : m_subreporters)
7,830✔
314
            r->end(context, elapsed_seconds);
15,660✔
315
    }
7,830✔
316

317
    void thread_end(const ThreadContext& context) override
318
    {
96✔
319
        for (Reporter* r : m_subreporters)
96✔
320
            r->thread_end(context);
192✔
321
    }
96✔
322

323
    void summary(const SharedContext& context, const Summary& results_summary) override
324
    {
8✔
325
        for (Reporter* r : m_subreporters)
8✔
326
            r->summary(context, results_summary);
16✔
327
    }
8✔
328

329
protected:
330
    std::vector<Reporter*> m_subreporters;
331
};
332

333

334
class WildcardFilter : public Filter {
335
public:
336
    WildcardFilter(const std::string& filter)
337
    {
84✔
338
        bool exclude = false;
84✔
339
        typedef std::string::const_iterator iter;
84✔
340
        iter i = filter.begin(), end = filter.end();
84✔
341
        for (;;) {
224✔
342
            // Skip space
112✔
343
            while (i != end) {
284✔
344
                if (*i != ' ')
200✔
345
                    break;
140✔
346
                ++i;
60✔
347
            }
60✔
348

112✔
349
            // End of input?
112✔
350
            if (i == end)
224✔
351
                break;
84✔
352

70✔
353
            iter word_begin = i;
140✔
354

70✔
355
            // Find end of word
70✔
356
            while (i != end) {
872✔
357
                if (*i == ' ')
792✔
358
                    break;
60✔
359
                ++i;
732✔
360
            }
732✔
361

70✔
362
            iter word_end = i;
140✔
363
            auto word_size = word_end - word_begin;
140✔
364
            if (word_size == 1 && *word_begin == '-') {
140✔
365
                exclude = true;
32✔
366
                continue;
32✔
367
            }
32✔
368

54✔
369
            std::string word(word_begin, word_end);
108✔
370
            patterns& p = exclude ? m_exclude : m_include;
94✔
371
            p.push_back(wildcard_pattern(word));
108✔
372
        }
108✔
373

42✔
374
        // Include everything if no includes are specified.
42✔
375
        if (m_include.empty())
84✔
376
            m_include.push_back(wildcard_pattern("*"));
12✔
377
    }
84✔
378

379
    bool include(const TestDetails& details) override
380
    {
588✔
381
        const char* name_begin = details.test_name.data();
588✔
382
        const char* name_end = name_begin + details.test_name.size();
588✔
383
        typedef patterns::const_iterator iter;
588✔
384

294✔
385
        // Say "no" if it matches an exclude pattern
294✔
386
        {
588✔
387
            iter end = m_exclude.end();
588✔
388
            for (iter i = m_exclude.begin(); i != end; ++i) {
676✔
389
                if (i->match(name_begin, name_end))
184✔
390
                    return false;
96✔
391
            }
184✔
392
        }
588✔
393

294✔
394
        // Say "yes" if it matches an include pattern
294✔
395
        {
540✔
396
            iter end = m_include.end();
492✔
397
            for (iter i = m_include.begin(); i != end; ++i) {
748✔
398
                if (i->match(name_begin, name_end))
536✔
399
                    return true;
280✔
400
            }
536✔
401
        }
492✔
402

246✔
403
        // Not included
246✔
404
        return false;
352✔
405
    }
492✔
406

407
private:
408
    typedef std::vector<wildcard_pattern> patterns;
409
    patterns m_include, m_exclude;
410
};
411

412
} // anonymous namespace
413

414

415
namespace realm {
416
namespace test_util {
417
namespace unit_test {
418

419

420
class TestList::SharedContextImpl : public SharedContext {
421
public:
422
    Reporter& reporter;
423
    const bool abort_on_failure;
424
    const util::Logger::Level intra_test_log_level;
425
    Mutex mutex;
426
    struct Entry {
427
        const Test* test;
428
        size_t test_index;
429
        int recurrence_index;
430
    };
431
    std::vector<Entry> concur_tests, no_concur_tests;
432
    size_t next_concur_test = 0; // Index into `concur_tests`
433
    long num_failed_tests = 0;
434
    long long num_checks = 0;
435
    long long num_failed_checks = 0;
436
    int num_ended_threads = 0;
437
    int last_thread_to_end = -1;
438

439
    SharedContextImpl(const TestList& tests, int repetitions, int threads,
440
                      const std::shared_ptr<util::Logger>& logger_ptr, Reporter& reporter, bool aof,
441
                      util::Logger::Level itll)
442
        : SharedContext(tests, repetitions, threads, logger_ptr)
443
        , reporter(reporter)
444
        , abort_on_failure(aof)
445
        , intra_test_log_level(itll)
446
    {
180✔
447
    }
180✔
448
};
449

450

451
class TestList::ThreadContextImpl : public ThreadContext {
452
public:
453
    std::shared_ptr<Logger> intra_test_logger;
454
    SharedContextImpl& shared_context;
455

456
    // Each instance of this type is only used on one thread spawned by the
457
    // test runner, but the tests themselves may spawn additional threads that
458
    // perform checks, so it still needs to be somewhat thread-safe.
459
    Mutex mutex;
460
    std::atomic<long long> num_checks;
461
    long long num_failed_checks;
462
    long num_failed_tests;
463
    std::atomic<long> last_line_seen;
464
    bool errors_seen;
465

466
    ThreadContextImpl(SharedContextImpl& sc, int ti, const std::shared_ptr<util::Logger>& attached_logger)
467
        : ThreadContext(sc, ti, attached_logger ? attached_logger : sc.report_logger_ptr)
468
        , intra_test_logger(
469
              std::make_shared<LocalThresholdLogger>(ThreadContext::report_logger_ptr, sc.intra_test_log_level))
470
        , shared_context(sc)
471
    {
252✔
472
    }
252✔
473

474
    void run();
475
    void nonconcur_run();
476

477
    void run(SharedContextImpl::Entry, UniqueLock&);
478
    void finalize(UniqueLock&);
479

480
private:
481
    void clear_counters()
482
    {
410✔
483
        num_checks = 0;
410✔
484
        num_failed_checks = 0;
410✔
485
        num_failed_tests = 0;
410✔
486
        last_line_seen = 0;
410✔
487
    }
410✔
488
};
489

490

491
void TestList::add(RunFunc run_func, IsEnabledFunc is_enabled_func, bool allow_concur, const char* suite,
492
                   const std::string& name, const char* file, long line)
493
{
8,116✔
494
    Test test;
8,116✔
495
    test.run_func = run_func;
8,116✔
496
    test.is_enabled_func = is_enabled_func;
8,116✔
497
    test.allow_concur = allow_concur;
8,116✔
498
    test.details.suite_name = suite;
8,116✔
499
    test.details.test_name = name;
8,116✔
500
    test.details.file_name = file;
8,116✔
501
    test.details.line_number = line;
8,116✔
502
    m_tests.reserve(m_tests.size() + 1); // Throws
8,116✔
503
    m_test_storage.push_back(test);      // Throws
8,116✔
504
    m_tests.push_back(&m_test_storage.back());
8,116✔
505
}
8,116✔
506

507

508
bool TestList::run(Config config)
509
{
180✔
510
    if (config.num_repetitions < 0)
180✔
511
        throw std::runtime_error("Bad number of repetitions");
×
512
    if (config.num_threads < 1)
180✔
513
        throw std::runtime_error("Bad number of threads");
×
514

90✔
515
    std::shared_ptr<util::Logger> shared_logger;
180✔
516
    if (config.logger) {
180✔
NEW
517
        shared_logger = config.logger;
×
518
    }
×
519
    else {
180✔
520
        if (config.log_timestamps) {
180✔
521
            util::TimestampStderrLogger::Config config;
×
522
            config.precision = util::TimestampStderrLogger::Precision::milliseconds;
×
523
            config.format = "%FT%T";
×
NEW
524
            shared_logger = std::make_shared<util::TimestampStderrLogger>(std::move(config)); // Throws
×
525
        }
×
526
        else {
180✔
527
            shared_logger = util::Logger::get_default_logger(); // Throws
180✔
528
        }
180✔
529
    }
180✔
530

90✔
531
    Reporter fallback_reporter;
180✔
532
    Reporter& reporter = config.reporter ? *config.reporter : fallback_reporter;
158✔
533

90✔
534
    // Filter
90✔
535
    std::vector<std::pair<const Test*, size_t>> included_tests; // Second component is test index
180✔
536
    size_t num_enabled = 0, num_disabled = 0;
180✔
537
    for (size_t i = 0; i < m_tests.size(); ++i) {
9,004✔
538
        const Test* test = m_tests[i];
8,824✔
539
        if (!(*test->is_enabled_func)()) {
8,824✔
540
            ++num_disabled;
166✔
541
            continue;
166✔
542
        }
166✔
543
        ++num_enabled;
8,658✔
544
        if (config.filter && !config.filter->include(test->details))
8,658✔
545
            continue;
308✔
546
        included_tests.emplace_back(test, i);
8,350✔
547
    }
8,350✔
548

90✔
549
    // Repeat
90✔
550
    int num_threads = config.num_threads;
180✔
551
    std::vector<SharedContextImpl::Entry> concur_tests, no_concur_tests;
180✔
552
    size_t num_executed_tests = 0;
180✔
553
    for (int i = 0; i < config.num_repetitions; ++i) {
360✔
554
        for (auto p : included_tests) {
8,350✔
555
            SharedContextImpl::Entry entry;
8,350✔
556
            entry.test = p.first;
8,350✔
557
            entry.test_index = p.second;
8,350✔
558
            entry.recurrence_index = i;
8,350✔
559
            const Test& test = *entry.test;
8,350✔
560
            // In case only one test thread was asked for, we run all tests as
4,176✔
561
            // nonconcurrent tests to avoid reordering
4,176✔
562
            auto& tests = (test.allow_concur && num_threads > 1 ? concur_tests : no_concur_tests);
8,350✔
563
            tests.push_back(entry);
8,350✔
564
            if (num_executed_tests == std::numeric_limits<size_t>::max())
8,350✔
565
                throw std::runtime_error("Too many tests");
×
566
            ++num_executed_tests;
8,350✔
567
        }
8,350✔
568
    }
180✔
569

90✔
570
    // Don't start more threads than are needed
90✔
571
    {
180✔
572
        size_t max_threads = concur_tests.size();
180✔
573
        if (max_threads == 0 && !no_concur_tests.empty())
180✔
574
            max_threads = 1;
156✔
575
        if (max_threads < unsigned(num_threads))
180✔
576
            num_threads = int(max_threads);
16✔
577
    }
180✔
578

90✔
579
    // Shuffle
90✔
580
    if (config.shuffle) {
180✔
581
        Random random(random_int<unsigned long>()); // Seed from slow global generator
8✔
582
        random.shuffle(concur_tests.begin(), concur_tests.end());
8✔
583
        random.shuffle(no_concur_tests.begin(), no_concur_tests.end());
8✔
584
    }
8✔
585

90✔
586
    // Execute
90✔
587
    SharedContextImpl shared_context(*this, config.num_repetitions, num_threads, shared_logger, reporter,
180✔
588
                                     config.abort_on_failure, config.intra_test_log_level);
180✔
589
    shared_context.concur_tests = std::move(concur_tests);
180✔
590
    shared_context.no_concur_tests = std::move(no_concur_tests);
180✔
591
    std::vector<std::shared_ptr<util::Logger>> loggers(num_threads);
180✔
592
    if (num_threads != 1 || !config.per_thread_log_path.empty()) {
180✔
593
        std::ostringstream formatter;
24✔
594
        formatter.imbue(std::locale::classic());
24✔
595
        formatter << num_threads;
24✔
596
        int thread_digits = int(formatter.str().size());
24✔
597
        formatter.fill('0');
24✔
598
        if (config.per_thread_log_path.empty()) {
24✔
599
            for (int i = 0; i != num_threads; ++i) {
120✔
600
                formatter.str(std::string());
96✔
601
                formatter << "Thread[" << std::setw(thread_digits) << (i + 1) << "]: ";
96✔
602
                loggers[i].reset(new util::PrefixLogger(formatter.str(), shared_logger));
96✔
603
            }
96✔
604
        }
24✔
605
        else {
×
606
            const std::string& format = config.per_thread_log_path;
×
607
            auto j = format.rfind('%');
×
608
            if (j == std::string::npos)
×
609
                throw std::runtime_error("No '%' in per-thread log path");
×
610
            std::string a = format.substr(0, j), b = format.substr(j + 1);
×
611
            for (int i = 0; i != num_threads; ++i) {
×
612
                formatter.str(std::string());
×
613
                formatter << a << std::setw(thread_digits) << (i + 1) << b;
×
614
                std::string path = formatter.str();
×
615
                shared_logger->info("Logging to %1", path);
×
616
                loggers[i].reset(new util::FileLogger(path));
×
617
            }
×
618
        }
×
619
    }
24✔
620
    Timer timer;
180✔
621
    if (num_threads == 1) {
180✔
622
        ThreadContextImpl thread_context(shared_context, 0, loggers[0]);
156✔
623
        thread_context.run();
156✔
624
        thread_context.nonconcur_run();
156✔
625
    }
156✔
626
    else {
24✔
627
        std::vector<std::unique_ptr<ThreadContextImpl>> thread_contexts(num_threads);
24✔
628
        for (int i = 0; i < num_threads; ++i)
120✔
629
            thread_contexts[i].reset(new ThreadContextImpl(shared_context, i, loggers[i]));
96✔
630

12✔
631
        // First execute regular (concurrent) tests
12✔
632
        {
24✔
633
            auto thread = [&](int i) {
96✔
634
                {
96✔
635
                    std::ostringstream out;
96✔
636
                    out.imbue(std::locale::classic());
96✔
637
                    out << "test-thread-" << (i + 1);
96✔
638
                    Thread::set_name(out.str());
96✔
639
                }
96✔
640
                thread_contexts[i]->run();
96✔
641
            };
96✔
642
            std::unique_ptr<Thread[]> threads(new Thread[num_threads]);
24✔
643
            for (int i = 0; i < num_threads; ++i)
120✔
644
                threads[i].start([=] { thread(i); });
96✔
645
            for (int i = 0; i < num_threads; ++i)
120✔
646
                threads[i].join();
96✔
647
        }
24✔
648

12✔
649
        // Then execute nonconcurrent tests on main thread
12✔
650
        if (shared_context.last_thread_to_end != -1)
24✔
651
            thread_contexts[shared_context.last_thread_to_end]->nonconcur_run();
8✔
652
    }
24✔
653

90✔
654
    // Summarize
90✔
655
    Summary results_summary;
180✔
656
    results_summary.num_disabled_tests = long(num_disabled);
180✔
657
    results_summary.num_excluded_tests = long(num_enabled - included_tests.size());
180✔
658
    results_summary.num_included_tests = long(included_tests.size());
180✔
659
    results_summary.num_executed_tests = long(num_executed_tests);
180✔
660
    results_summary.num_failed_tests = shared_context.num_failed_tests;
180✔
661
    results_summary.num_executed_checks = shared_context.num_checks;
180✔
662
    results_summary.num_failed_checks = shared_context.num_failed_checks;
180✔
663
    results_summary.elapsed_seconds = timer.get_elapsed_time();
180✔
664
    reporter.summary(shared_context, results_summary);
180✔
665

90✔
666
    return shared_context.num_failed_tests == 0;
180✔
667
}
180✔
668

669

670
void TestList::ThreadContextImpl::run()
671
{
250✔
672
    clear_counters();
250✔
673

142✔
674
    UniqueLock lock(shared_context.mutex);
250✔
675
    shared_context.reporter.thread_begin(*this);
250✔
676

142✔
677
    // First run the tests that can safely run concurrently with other threads
142✔
678
    // and with itself.
142✔
679
    while (shared_context.next_concur_test < shared_context.concur_tests.size()) {
7,876✔
680
        auto entry = shared_context.concur_tests[shared_context.next_concur_test++];
7,626✔
681
        run(entry, lock);
7,626✔
682
    }
7,626✔
683

142✔
684
    // When only the last test thread is running, we can run the tests that
142✔
685
    // cannot safely run concurrently with other threads or with itself, but
142✔
686
    // this has to happen on the main thread (the one that calls
142✔
687
    // TestList::run()).
142✔
688
    if (!shared_context.no_concur_tests.empty()) {
252✔
689
        int num_remaining_threads = shared_context.num_threads - shared_context.num_ended_threads;
252✔
690
        if (num_remaining_threads == 1) {
252✔
691
            // Tell the main thread which thread context to use for executing the
82✔
692
            // nonconcurrent tests (nonconcur_run()).
82✔
693
            shared_context.last_thread_to_end = thread_index;
164✔
694
            return;
164✔
695
        }
164✔
696
    }
86✔
697

60✔
698
    ++shared_context.num_ended_threads;
86✔
699
    finalize(lock);
86✔
700
}
86✔
701

702

703
void TestList::ThreadContextImpl::nonconcur_run()
704
{
164✔
705
    clear_counters();
164✔
706

82✔
707
    UniqueLock lock(shared_context.mutex);
164✔
708

82✔
709
    for (auto entry : shared_context.no_concur_tests)
164✔
710
        run(entry, lock);
724✔
711

82✔
712
    finalize(lock);
164✔
713
}
164✔
714

715

716
void TestList::ThreadContextImpl::run(SharedContextImpl::Entry entry, UniqueLock& lock)
717
{
8,350✔
718
    const Test& test = *entry.test;
8,350✔
719
    TestContext test_context(*this, test.details, entry.test_index, entry.recurrence_index);
8,350✔
720
    shared_context.reporter.begin(test_context);
8,350✔
721
    lock.unlock();
8,350✔
722

4,176✔
723
    last_line_seen = test.details.line_number;
8,350✔
724
    errors_seen = false;
8,350✔
725
    Timer timer;
8,350✔
726
    try {
8,350✔
727
        (*test.run_func)(test_context);
8,350✔
728
    }
8,350✔
729
    catch (std::exception& ex) {
4,176✔
730
        std::string message = "Unhandled exception " + get_type_name(ex) + ": " + ex.what();
×
731
        test_context.test_failed(
×
732
            util::format("Unhandled exception after line %1 %2: %3", last_line_seen, get_type_name(ex), ex.what()));
×
733
    }
×
734
    catch (...) {
4,176✔
735
        test_context.test_failed(util::format("Unhandled exception after line %1 of unknown type", last_line_seen));
×
736
    }
×
737
    double elapsed_time = timer.get_elapsed_time();
8,350✔
738
    if (errors_seen)
8,350✔
739
        ++num_failed_tests;
216✔
740

4,176✔
741
    lock.lock();
8,350✔
742
    shared_context.reporter.end(test_context, elapsed_time);
8,350✔
743
}
8,350✔
744

745

746
void TestList::ThreadContextImpl::finalize(UniqueLock&)
747
{
252✔
748
    shared_context.num_failed_tests += num_failed_tests;
252✔
749
    shared_context.num_checks += num_checks;
252✔
750
    shared_context.num_failed_checks += num_failed_checks;
252✔
751

142✔
752
    shared_context.reporter.thread_end(*this);
252✔
753
}
252✔
754

755

756
TestList& get_default_test_list()
757
{
6,940✔
758
    static TestList list;
6,940✔
759
    return list;
6,940✔
760
}
6,940✔
761

762

763
TestContext::TestContext(TestList::ThreadContextImpl& tc, const TestDetails& td, size_t ti, int ri)
764
    : thread_context(tc)
765
    , test_details(td)
766
    , test_index(ti)
767
    , recurrence_index(ri)
768
    , logger(tc.intra_test_logger)
769
    , m_thread_context(tc)
770
{
8,350✔
771
}
8,350✔
772

773

774
void TestContext::check_succeeded(long line)
775
{
437,102,166✔
776
    ++m_thread_context.num_checks;
437,102,166✔
777
    m_thread_context.last_line_seen.store(line, std::memory_order_relaxed);
437,102,166✔
778
}
437,102,166✔
779

780

781
REALM_NORETURN void TestContext::abort()
782
{
×
783
    const SharedContext& context = thread_context.shared_context;
×
784
    const char* format =
×
785
        context.num_threads == 1 ? "Aborting due to failure" : "Aborting due to failure in test thread %1";
×
786
    context.report_logger.info(format, m_thread_context.thread_index + 1);
×
787
    ::abort();
×
788
}
×
789

790

791
void TestContext::check_failed(const char* file, long line, const std::string& message)
792
{
1,168✔
793
    m_thread_context.last_line_seen = line;
1,168✔
794
    {
1,168✔
795
        LockGuard lock(m_thread_context.mutex);
1,168✔
796
        ++m_thread_context.num_checks;
1,168✔
797
        ++m_thread_context.num_failed_checks;
1,168✔
798
        m_thread_context.errors_seen = true;
1,168✔
799
    }
1,168✔
800
    {
1,168✔
801
        TestList::SharedContextImpl& shared = m_thread_context.shared_context;
1,168✔
802
        LockGuard lock(shared.mutex);
1,168✔
803
        shared.reporter.fail(*this, file, line, message);
1,168✔
804
        if (shared.abort_on_failure)
1,168✔
805
            abort();
×
806
    }
1,168✔
807
}
1,168✔
808

809

810
void TestContext::test_failed(const std::string& message)
811
{
×
812
    {
×
813
        LockGuard lock(m_thread_context.mutex);
×
814
        m_thread_context.errors_seen = true;
×
815
    }
×
816
    {
×
817
        TestList::SharedContextImpl& shared = m_thread_context.shared_context;
×
818
        LockGuard lock(shared.mutex);
×
819
        shared.reporter.fail(*this, test_details.file_name, test_details.line_number, message);
×
820
        if (shared.abort_on_failure)
×
821
            abort();
×
822
    }
×
823
}
×
824

825

826
void TestContext::cond_failed(const char* file, long line, const char* macro_name, const char* cond_text)
827
{
32✔
828
    std::string msg = std::string(macro_name) + "(" + cond_text + ") failed";
32✔
829
    check_failed(file, line, msg);
32✔
830
}
32✔
831

832

833
void TestContext::compare_failed(const char* file, long line, const char* macro_name, const char* a_text,
834
                                 const char* b_text, const std::string& a_val, const std::string& b_val)
835
{
800✔
836
    std::string msg =
800✔
837
        std::string(macro_name) + "(" + a_text + ", " + b_text + ") failed with (" + a_val + ", " + b_val + ")";
800✔
838
    check_failed(file, line, msg);
800✔
839
}
800✔
840

841

842
void TestContext::inexact_compare_failed(const char* file, long line, const char* macro_name, const char* a_text,
843
                                         const char* b_text, const char* eps_text, long double a, long double b,
844
                                         long double eps)
845
{
320✔
846
    std::ostringstream out;
320✔
847
    out.precision(std::numeric_limits<long double>::digits10 + 1);
320✔
848
    out << macro_name << "(" << a_text << ", " << b_text << ", " << eps_text << ") "
320✔
849
                                                                                "failed with ("
320✔
850
        << a << ", " << b << ", " << eps << ")";
320✔
851
    check_failed(file, line, out.str());
320✔
852
}
320✔
853

854

855
void TestContext::throw_failed(const char* file, long line, const char* expr_text, const char* exception_name)
856
{
16✔
857
    std::ostringstream out;
16✔
858
    out << "CHECK_THROW(" << expr_text << ", " << exception_name << ") failed: Did not throw";
16✔
859
    check_failed(file, line, out.str());
16✔
860
}
16✔
861

862

863
void TestContext::throw_ex_failed(const char* file, long line, const char* expr_text, const char* exception_name,
864
                                  const char* exception_cond_text)
865
{
×
866
    std::ostringstream out;
×
867
    out << "CHECK_THROW_EX(" << expr_text << ", " << exception_name << ", " << exception_cond_text
×
868
        << ") failed: Did not throw";
×
869
    check_failed(file, line, out.str());
×
870
}
×
871

872

873
void TestContext::throw_ex_cond_failed(const char* file, long line, const char* exception_what, const char* expr_text,
874
                                       const char* exception_name, const char* exception_cond_text)
875
{
×
876
    std::ostringstream out;
×
877
    out << "CHECK_THROW_EX(" << expr_text << ", " << exception_name << ", " << exception_cond_text
×
878
        << ") failed: Did throw \"" << exception_what << "\", but condition failed";
×
879
    check_failed(file, line, out.str());
×
880
}
×
881

882

883
void TestContext::throw_any_failed(const char* file, long line, const char* expr_text)
884
{
×
885
    std::ostringstream out;
×
886
    out << "CHECK_THROW_ANY(" << expr_text << ") failed: Did not throw";
×
887
    check_failed(file, line, out.str());
×
888
}
×
889

890
bool TestContext::check_string_contains(std::string_view a, std::string_view b, const char* file, long line,
891
                                        const char* a_text, const char* b_text)
892
{
80✔
893
    bool cond = a.find(b) != a.npos;
80✔
894
    return check_compare(cond, a, b, file, line, "CHECK_STRING_CONTAINS", a_text, b_text);
80✔
895
}
80✔
896

897

898
namespace {
899
std::locale locale_classic = std::locale::classic();
900
}
901

902
std::string TestContext::get_test_name() const
903
{
21,728✔
904
    std::ostringstream out;
21,728✔
905
    out.imbue(locale_classic);
21,728✔
906
    out << test_details.test_name << '.' << (recurrence_index + 1);
21,728✔
907
    return out.str();
21,728✔
908
}
21,728✔
909

910
void TestContext::nothrow_failed(const char* file, long line, const char* expr_text, std::exception* ex)
911
{
×
912
    std::ostringstream out;
×
913
    out << "CHECK_NOTHROW(" << expr_text << ") failed: Did throw ";
×
914
    if (ex) {
×
915
        out << get_type_name(*ex) << ": " << ex->what();
×
916
    }
×
917
    else {
×
918
        out << "exception of unknown type";
×
919
    }
×
920
    check_failed(file, line, out.str());
×
921
}
×
922

923

924
void Reporter::thread_begin(const ThreadContext&)
925
{
252✔
926
}
252✔
927

928
void Reporter::begin(const TestContext&)
929
{
520✔
930
}
520✔
931

932
void Reporter::fail(const TestContext&, const char*, long, const std::string&)
933
{
1,168✔
934
}
1,168✔
935

936
void Reporter::end(const TestContext&, double)
937
{
8,350✔
938
}
8,350✔
939

940
void Reporter::thread_end(const ThreadContext&)
941
{
252✔
942
}
252✔
943

944
void Reporter::summary(const SharedContext&, const Summary&)
945
{
44✔
946
}
44✔
947

948

949
class PatternBasedFileOrder::state : public RefCountBase {
950
public:
951
    typedef std::map<const void*, int> major_map; // Key is address of TestDetails object
952
    major_map m_major_map;
953

954
    typedef std::vector<wildcard_pattern> patterns;
955
    patterns m_patterns;
956

957
    state(const char** patterns_begin, const char** patterns_end)
958
    {
8✔
959
        for (const char** i = patterns_begin; i != patterns_end; ++i)
272✔
960
            m_patterns.push_back(wildcard_pattern(*i));
264✔
961
    }
8✔
962

963
    int get_major(const TestDetails& details)
964
    {
70,440✔
965
        major_map::const_iterator i = m_major_map.find(&details);
70,440✔
966
        if (i != m_major_map.end())
70,440✔
967
            return i->second;
62,444✔
968
        patterns::const_iterator j = m_patterns.begin(), end = m_patterns.end();
7,996✔
969
        while (j != end && !j->match(details.file_name))
271,864✔
970
            ++j;
263,868✔
971
        int major = int(j - m_patterns.begin());
7,996✔
972
        m_major_map[&details] = major;
7,996✔
973
        return major;
7,996✔
974
    }
7,996✔
975
};
976

977
bool PatternBasedFileOrder::operator()(const TestDetails& a, const TestDetails& b)
978
{
35,220✔
979
    int major_a = m_wrap.m_state->get_major(a);
35,220✔
980
    int major_b = m_wrap.m_state->get_major(b);
35,220✔
981
    if (major_a < major_b)
35,220✔
982
        return true;
×
983
    if (major_a > major_b)
35,220✔
984
        return false;
×
985
    int i = strcmp(a.file_name, b.file_name);
35,220✔
986
    return i < 0;
35,220✔
987
}
35,220✔
988

989
PatternBasedFileOrder::wrap::wrap(const char** patterns_begin, const char** patterns_end)
990
    : m_state(new state(patterns_begin, patterns_end))
991
{
8✔
992
}
8✔
993

994
PatternBasedFileOrder::wrap::~wrap()
995
{
8✔
996
}
8✔
997

998
PatternBasedFileOrder::wrap::wrap(const wrap& w)
999
    : m_state(w.m_state)
1000
{
×
1001
}
×
1002

1003
PatternBasedFileOrder::wrap& PatternBasedFileOrder::wrap::operator=(const wrap& w)
1004
{
×
1005
    m_state = w.m_state;
×
1006
    return *this;
×
1007
}
×
1008

1009

1010
SimpleReporter::SimpleReporter(bool report_progress)
1011
{
8✔
1012
    m_report_progress = report_progress;
8✔
1013
}
8✔
1014

1015
void SimpleReporter::begin(const TestContext& context)
1016
{
7,830✔
1017
    if (!m_report_progress)
7,830✔
1018
        return;
×
1019

3,916✔
1020
    const TestDetails& details = context.test_details;
7,830✔
1021
    auto format =
7,830✔
1022
        context.thread_context.shared_context.num_recurrences == 1 ? "%1:%2: Begin %3" : "%1:%2: Begin %3#%4";
7,830✔
1023
    context.thread_context.report_logger.info(format, details.file_name, details.line_number, details.test_name,
7,830✔
1024
                                              context.recurrence_index + 1);
7,830✔
1025
}
7,830✔
1026

1027
void SimpleReporter::fail(const TestContext& context, const char* file, long line, const std::string& message)
1028
{
×
1029
    const TestDetails& details = context.test_details;
×
1030
    auto format = context.thread_context.shared_context.num_recurrences == 1 ? "%1:%2: ERROR in %3: %5"
×
1031
                                                                             : "%1:%2: ERROR in %3#%4: %5";
×
1032
    auto msg = util::format(format, file, line, details.test_name, context.recurrence_index + 1, message);
×
1033
    if (m_report_progress) {
×
1034
        m_error_messages.push_back(msg);
×
1035
    }
×
1036
    context.thread_context.report_logger.info("%1", msg.c_str());
×
1037
}
×
1038

1039
void SimpleReporter::thread_end(const ThreadContext& context)
1040
{
96✔
1041
    if (!m_report_progress)
96✔
1042
        return;
×
1043

64✔
1044
    if (context.shared_context.num_threads > 1) {
96✔
1045
        context.report_logger.info("End of thread");
96✔
1046
    }
96✔
1047
}
96✔
1048

1049
void SimpleReporter::summary(const SharedContext& context, const Summary& results_summary)
1050
{
8✔
1051
    util::Logger& logger = context.report_logger;
8✔
1052
    if (results_summary.num_failed_tests == 0) {
8✔
1053
        logger.info("Success: All %1 tests passed (%2 checks).", results_summary.num_executed_tests,
8✔
1054
                    results_summary.num_executed_checks);
8✔
1055
    }
8✔
1056
    else {
×
1057
        logger.info("FAILURE: %1 out of %2 tests failed (%3 out of %4 checks failed).",
×
1058
                    results_summary.num_failed_tests, results_summary.num_executed_tests,
×
1059
                    results_summary.num_failed_checks, results_summary.num_executed_checks);
×
1060
        for (auto& str : m_error_messages) {
×
1061
            logger.info(str.c_str());
×
1062
        }
×
1063
    }
×
1064
    if (auto ident = getenv("REALM_CHILD_IDENT")) {
8✔
1065
        logger.info("Spawned process with ident '%1' completed.", ident);
×
1066
    }
×
1067
    logger.info("Test time: %1", Timer::format(results_summary.elapsed_seconds));
8✔
1068
    if (results_summary.num_excluded_tests >= 1) {
8✔
1069
        auto format = results_summary.num_excluded_tests == 1 ? "Note: One test was excluded!"
×
1070
                                                              : "Note: %1 tests were excluded!";
×
1071
        logger.info(format, results_summary.num_excluded_tests);
×
1072
    }
×
1073
}
8✔
1074

1075
std::unique_ptr<Reporter> create_junit_reporter(std::ostream& out, std::string_view test_suite_name)
1076
{
×
1077
    return std::make_unique<JUnitReporter>(out, test_suite_name);
×
1078
}
×
1079

1080
std::unique_ptr<Reporter> create_xml_reporter(std::ostream& out)
1081
{
×
1082
    return std::make_unique<XmlReporter>(out);
×
1083
}
×
1084

1085
std::unique_ptr<Reporter> create_evergreen_reporter(const std::string& path)
1086
{
8✔
1087
    return std::make_unique<EvergreenReporter>(path);
8✔
1088
}
8✔
1089

1090
std::unique_ptr<Reporter> create_combined_reporter(const std::vector<std::unique_ptr<Reporter>>& subreporters)
1091
{
8✔
1092
    return std::make_unique<ManifoldReporter>(subreporters);
8✔
1093
}
8✔
1094

1095
std::unique_ptr<Filter> create_wildcard_filter(const std::string& filter)
1096
{
84✔
1097
    return std::make_unique<WildcardFilter>(filter);
84✔
1098
}
84✔
1099

1100

1101
} // namespace unit_test
1102
} // namespace test_util
1103
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc