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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

63.8
/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
#include <thread>
29

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

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

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

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

46

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

49

50
namespace {
51

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

58

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

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

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

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

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

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

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

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

139

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

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

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

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

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

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

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

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

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

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

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

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

230

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

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

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

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

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

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

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

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

290

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

299
    void begin(const TestContext& context) override
300
    {
4,256✔
301
        for (Reporter* r : m_subreporters)
4,256✔
302
            r->begin(context);
8,512✔
303
    }
4,256✔
304

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

312
    void end(const TestContext& context, double elapsed_seconds) override
313
    {
4,256✔
314
        for (Reporter* r : m_subreporters)
4,256✔
315
            r->end(context, elapsed_seconds);
8,512✔
316
    }
4,256✔
317

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

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

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

334

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

350
            // End of input?
351
            if (i == end)
112✔
352
                break;
42✔
353

354
            iter word_begin = i;
70✔
355

356
            // Find end of word
357
            while (i != end) {
436✔
358
                if (*i == ' ')
396✔
359
                    break;
30✔
360
                ++i;
366✔
361
            }
366✔
362

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

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

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

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

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

395
        // Say "yes" if it matches an include pattern
396
        {
246✔
397
            iter end = m_include.end();
246✔
398
            for (iter i = m_include.begin(); i != end; ++i) {
374✔
399
                if (i->match(name_begin, name_end))
268✔
400
                    return true;
140✔
401
            }
268✔
402
        }
246✔
403

404
        // Not included
405
        return false;
106✔
406
    }
246✔
407

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

413
} // anonymous namespace
414

415

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

420

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

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

451

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

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

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

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

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

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

491

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

508

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

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

532
    Reporter fallback_reporter;
90✔
533
    Reporter& reporter = config.reporter ? *config.reporter : fallback_reporter;
90✔
534

535
    // Filter
536
    std::vector<std::pair<const Test*, size_t>> included_tests; // Second component is test index
90✔
537
    size_t num_enabled = 0, num_disabled = 0;
90✔
538
    for (size_t i = 0; i < m_tests.size(); ++i) {
4,832✔
539
        const Test* test = m_tests[i];
4,742✔
540
        if (!(*test->is_enabled_func)()) {
4,742✔
541
            ++num_disabled;
72✔
542
            continue;
72✔
543
        }
72✔
544
        ++num_enabled;
4,670✔
545
        if (config.filter && !config.filter->include(test->details))
4,670✔
546
            continue;
154✔
547
        included_tests.emplace_back(test, i);
4,516✔
548
    }
4,516✔
549

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

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

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

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

632
        // First execute regular (concurrent) tests
633
        {
12✔
634
            auto thread = [&](int i) {
64✔
635
                Thread::set_name(util::format("test-thread-%1", i + 1));
64✔
636
                thread_contexts[i]->run();
64✔
637
            };
64✔
638
            auto threads = std::make_unique<std::thread[]>(num_threads);
12✔
639
            for (int i = 0; i < num_threads; ++i)
76✔
640
                threads[i] = std::thread(thread, i);
64✔
641
            for (int i = 0; i < num_threads; ++i)
76✔
642
                threads[i].join();
64✔
643
        }
12✔
644

645
        // Then execute nonconcurrent tests on main thread
646
        if (shared_context.last_thread_to_end != -1)
12✔
647
            thread_contexts[shared_context.last_thread_to_end]->nonconcur_run();
4✔
648
    }
12✔
649

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

662
    return shared_context.num_failed_tests == 0;
90✔
663
}
90✔
664

665

666
void TestList::ThreadContextImpl::run()
667
{
142✔
668
    clear_counters();
142✔
669

670
    UniqueLock lock(shared_context.mutex);
142✔
671
    shared_context.reporter.thread_begin(*this);
142✔
672

673
    // First run the tests that can safely run concurrently with other threads
674
    // and with itself.
675
    while (shared_context.next_concur_test < shared_context.concur_tests.size()) {
4,310✔
676
        auto entry = shared_context.concur_tests[shared_context.next_concur_test++];
4,168✔
677
        run(entry, lock);
4,168✔
678
    }
4,168✔
679

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

694
    ++shared_context.num_ended_threads;
60✔
695
    finalize(lock);
60✔
696
}
60✔
697

698

699
void TestList::ThreadContextImpl::nonconcur_run()
700
{
82✔
701
    clear_counters();
82✔
702

703
    UniqueLock lock(shared_context.mutex);
82✔
704

705
    for (auto entry : shared_context.no_concur_tests)
82✔
706
        run(entry, lock);
348✔
707

708
    finalize(lock);
82✔
709
}
82✔
710

711

712
void TestList::ThreadContextImpl::run(SharedContextImpl::Entry entry, UniqueLock& lock)
713
{
4,516✔
714
    const Test& test = *entry.test;
4,516✔
715
    TestContext test_context(*this, test.details, entry.test_index, entry.recurrence_index);
4,516✔
716
    shared_context.reporter.begin(test_context);
4,516✔
717
    last_line_seen = test.details.line_number;
4,516✔
718
    errors_seen = false;
4,516✔
719
    lock.unlock();
4,516✔
720

721
    Timer timer;
4,516✔
722
    try {
4,516✔
723
        (*test.run_func)(test_context);
4,516✔
724
    }
4,516✔
725
    catch (std::exception& ex) {
4,516✔
726
        std::string message = "Unhandled exception " + get_type_name(ex) + ": " + ex.what();
×
727
        test_context.test_failed(
×
728
            util::format("Unhandled exception after line %1 %2: %3", last_line_seen, get_type_name(ex), ex.what()));
×
729
    }
×
730
    catch (...) {
4,516✔
731
        test_context.test_failed(util::format("Unhandled exception after line %1 of unknown type", last_line_seen));
×
732
    }
×
733
    double elapsed_time = timer.get_elapsed_time();
4,516✔
734

735
    lock.lock();
4,516✔
736
    if (errors_seen)
4,516✔
737
        ++num_failed_tests;
108✔
738

739
    shared_context.reporter.end(test_context, elapsed_time);
4,516✔
740
}
4,516✔
741

742

743
void TestList::ThreadContextImpl::finalize(UniqueLock&)
744
{
142✔
745
    shared_context.num_failed_tests += num_failed_tests;
142✔
746
    shared_context.num_checks += num_checks;
142✔
747
    shared_context.num_failed_checks += num_failed_checks;
142✔
748

749
    shared_context.reporter.thread_end(*this);
142✔
750
}
142✔
751

752

753
TestList& get_default_test_list()
754
{
3,696✔
755
    static TestList list;
3,696✔
756
    return list;
3,696✔
757
}
3,696✔
758

759

760
TestContext::TestContext(TestList::ThreadContextImpl& tc, const TestDetails& td, size_t ti, int ri)
761
    : thread_context(tc)
4,516✔
762
    , test_details(td)
4,516✔
763
    , test_index(ti)
4,516✔
764
    , recurrence_index(ri)
4,516✔
765
    , logger(tc.intra_test_logger)
4,516✔
766
    , m_thread_context(tc)
4,516✔
767
{
4,516✔
768
}
4,516✔
769

770

771
void TestContext::check_succeeded(long line)
772
{
224,631,806✔
773
    ++m_thread_context.num_checks;
224,631,806✔
774
    m_thread_context.last_line_seen.store(line, std::memory_order_relaxed);
224,631,806✔
775
}
224,631,806✔
776

777

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

787

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

806

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

822

823
void TestContext::cond_failed(const char* file, long line, const char* macro_name, const char* cond_text)
824
{
16✔
825
    std::string msg = std::string(macro_name) + "(" + cond_text + ") failed";
16✔
826
    check_failed(file, line, msg);
16✔
827
}
16✔
828

829

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

838

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

850

851
void TestContext::throw_failed(const char* file, long line, const char* expr_text, const char* exception_name)
852
{
8✔
853
    std::ostringstream out;
8✔
854
    out << "CHECK_THROW(" << expr_text << ", " << exception_name << ") failed: Did not throw";
8✔
855
    check_failed(file, line, out.str());
8✔
856
}
8✔
857

858

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

868

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

878

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

886
bool TestContext::check_string_contains(std::string_view a, std::string_view b, const char* file, long line,
887
                                        const char* a_text, const char* b_text)
888
{
50✔
889
    bool cond = a.find(b) != a.npos;
50✔
890
    return check_compare(cond, a, b, file, line, "CHECK_STRING_CONTAINS", a_text, b_text);
50✔
891
}
50✔
892

893

894
namespace {
895
std::locale locale_classic = std::locale::classic();
896
}
897

898
std::string TestContext::get_test_name() const
899
{
11,190✔
900
    std::ostringstream out;
11,190✔
901
    out.imbue(locale_classic);
11,190✔
902
    out << test_details.test_name << '.' << (recurrence_index + 1);
11,190✔
903
    return out.str();
11,190✔
904
}
11,190✔
905

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

919

920
void Reporter::thread_begin(const ThreadContext&) {}
142✔
921

922
void Reporter::begin(const TestContext&) {}
260✔
923

924
void Reporter::fail(const TestContext&, const char*, long, const std::string&) {}
584✔
925

926
void Reporter::end(const TestContext&, double) {}
4,516✔
927

928
void Reporter::thread_end(const ThreadContext&) {}
142✔
929

930
void Reporter::summary(const SharedContext&, const Summary&) {}
22✔
931

932

933
class PatternBasedFileOrder::state : public RefCountBase {
934
public:
935
    typedef std::map<const void*, int> major_map; // Key is address of TestDetails object
936
    major_map m_major_map;
937

938
    typedef std::vector<wildcard_pattern> patterns;
939
    patterns m_patterns;
940

941
    state(const char** patterns_begin, const char** patterns_end)
942
    {
4✔
943
        for (const char** i = patterns_begin; i != patterns_end; ++i)
140✔
944
            m_patterns.push_back(wildcard_pattern(*i));
136✔
945
    }
4✔
946

947
    int get_major(const TestDetails& details)
948
    {
55,272✔
949
        major_map::const_iterator i = m_major_map.find(&details);
55,272✔
950
        if (i != m_major_map.end())
55,272✔
951
            return i->second;
50,944✔
952
        patterns::const_iterator j = m_patterns.begin(), end = m_patterns.end();
4,328✔
953
        while (j != end && !j->match(details.file_name))
151,480✔
954
            ++j;
147,152✔
955
        int major = int(j - m_patterns.begin());
4,328✔
956
        m_major_map[&details] = major;
4,328✔
957
        return major;
4,328✔
958
    }
55,272✔
959
};
960

961
bool PatternBasedFileOrder::operator()(const TestDetails& a, const TestDetails& b)
962
{
27,636✔
963
    int major_a = m_wrap.m_state->get_major(a);
27,636✔
964
    int major_b = m_wrap.m_state->get_major(b);
27,636✔
965
    if (major_a < major_b)
27,636✔
966
        return true;
×
967
    if (major_a > major_b)
27,636✔
968
        return false;
×
969
    int i = strcmp(a.file_name, b.file_name);
27,636✔
970
    return i < 0;
27,636✔
971
}
27,636✔
972

973
PatternBasedFileOrder::wrap::wrap(const char** patterns_begin, const char** patterns_end)
974
    : m_state(new state(patterns_begin, patterns_end))
4✔
975
{
4✔
976
}
4✔
977

978
PatternBasedFileOrder::wrap::~wrap() {}
4✔
979

980
PatternBasedFileOrder::wrap::wrap(const wrap& w)
981
    : m_state(w.m_state)
×
982
{
×
983
}
×
984

985
PatternBasedFileOrder::wrap& PatternBasedFileOrder::wrap::operator=(const wrap& w)
986
{
×
987
    m_state = w.m_state;
×
988
    return *this;
×
989
}
×
990

991

992
SimpleReporter::SimpleReporter(bool report_progress)
993
{
4✔
994
    m_report_progress = report_progress;
4✔
995
}
4✔
996

997
void SimpleReporter::begin(const TestContext& context)
998
{
4,256✔
999
    if (!m_report_progress)
4,256✔
1000
        return;
×
1001

1002
    const TestDetails& details = context.test_details;
4,256✔
1003
    auto format =
4,256✔
1004
        context.thread_context.shared_context.num_recurrences == 1 ? "%1:%2: Begin %3" : "%1:%2: Begin %3#%4";
4,256✔
1005
    context.thread_context.report_logger.info(format, details.file_name, details.line_number, details.test_name,
4,256✔
1006
                                              context.recurrence_index + 1);
4,256✔
1007
}
4,256✔
1008

1009
void SimpleReporter::fail(const TestContext& context, const char* file, long line, const std::string& message)
1010
{
×
1011
    const TestDetails& details = context.test_details;
×
1012
    auto format = context.thread_context.shared_context.num_recurrences == 1 ? "%1:%2: ERROR in %3: %5"
×
1013
                                                                             : "%1:%2: ERROR in %3#%4: %5";
×
1014
    auto msg = util::format(format, file, line, details.test_name, context.recurrence_index + 1, message);
×
1015
    if (m_report_progress) {
×
1016
        m_error_messages.push_back(msg);
×
1017
    }
×
1018
    context.thread_context.report_logger.info("%1", msg.c_str());
×
1019
}
×
1020

1021
void SimpleReporter::thread_end(const ThreadContext& context)
1022
{
64✔
1023
    if (!m_report_progress)
64✔
1024
        return;
×
1025

1026
    if (context.shared_context.num_threads > 1) {
64✔
1027
        context.report_logger.info("End of thread");
64✔
1028
    }
64✔
1029
}
64✔
1030

1031
void SimpleReporter::summary(const SharedContext& context, const Summary& results_summary)
1032
{
4✔
1033
    util::Logger& logger = context.report_logger;
4✔
1034
    if (results_summary.num_failed_tests == 0) {
4✔
1035
        logger.info("Success: All %1 tests passed (%2 checks).", results_summary.num_executed_tests,
4✔
1036
                    results_summary.num_executed_checks);
4✔
1037
    }
4✔
1038
    else {
×
1039
        logger.info("FAILURE: %1 out of %2 tests failed (%3 out of %4 checks failed).",
×
1040
                    results_summary.num_failed_tests, results_summary.num_executed_tests,
×
1041
                    results_summary.num_failed_checks, results_summary.num_executed_checks);
×
1042
        for (auto& str : m_error_messages) {
×
1043
            logger.info(str.c_str());
×
1044
        }
×
1045
    }
×
1046
    if (auto ident = getenv("REALM_CHILD_IDENT")) {
4✔
1047
        logger.info("Spawned process with ident '%1' completed.", ident);
×
1048
    }
×
1049
    logger.info("Test time: %1", Timer::format(results_summary.elapsed_seconds));
4✔
1050
    if (results_summary.num_excluded_tests >= 1) {
4✔
1051
        auto format = results_summary.num_excluded_tests == 1 ? "Note: One test was excluded!"
×
1052
                                                              : "Note: %1 tests were excluded!";
×
1053
        logger.info(format, results_summary.num_excluded_tests);
×
1054
    }
×
1055
}
4✔
1056

1057
std::unique_ptr<Reporter> create_junit_reporter(std::ostream& out, std::string_view test_suite_name)
1058
{
×
1059
    return std::make_unique<JUnitReporter>(out, test_suite_name);
×
1060
}
×
1061

1062
std::unique_ptr<Reporter> create_xml_reporter(std::ostream& out)
1063
{
×
1064
    return std::make_unique<XmlReporter>(out);
×
1065
}
×
1066

1067
std::unique_ptr<Reporter> create_evergreen_reporter(const std::string& path)
1068
{
4✔
1069
    return std::make_unique<EvergreenReporter>(path);
4✔
1070
}
4✔
1071

1072
std::unique_ptr<Reporter> create_combined_reporter(const std::vector<std::unique_ptr<Reporter>>& subreporters)
1073
{
4✔
1074
    return std::make_unique<ManifoldReporter>(subreporters);
4✔
1075
}
4✔
1076

1077
std::unique_ptr<Filter> create_wildcard_filter(const std::string& filter)
1078
{
42✔
1079
    return std::make_unique<WildcardFilter>(filter);
42✔
1080
}
42✔
1081

1082

1083
} // namespace unit_test
1084
} // namespace test_util
1085
} // 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