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

realm / realm-core / 1880

01 Dec 2023 01:56AM UTC coverage: 91.679% (+0.02%) from 91.661%
1880

push

Evergreen

web-flow
Js/device testing (#6964)

* add a combined test executable

* disable a recently added test on mobile

* simplify link dependencies

* add missing namespace

* fix merge

* fix object-store benchmarks on android

92350 of 169306 branches covered (0.0%)

433 of 500 new or added lines in 5 files covered. (86.6%)

35 existing lines in 13 files now uncovered.

231795 of 252834 relevant lines covered (91.68%)

6397738.9 hits per line

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

53.05
/test/util/compare_groups.cpp
1
#include "compare_groups.hpp"
2

3
#include <algorithm>
4
#include <vector>
5
#include <set>
6
#include <sstream>
7
#include <iostream>
8

9
#include <realm/table.hpp>
10
#include <realm/sync/object_id.hpp>
11
#include <realm/list.hpp>
12
#include <realm/dictionary.hpp>
13
#include <realm/set.hpp>
14

15
using namespace realm;
16

17
namespace {
18

19

20
class TableCompareLogger : public util::Logger {
21
public:
22
    TableCompareLogger(StringData table_name, util::Logger& base_logger) noexcept
23
        : util::Logger(base_logger.get_level_threshold())
24
        , m_table_name{table_name}
25
        , m_base_logger{base_logger}
26
    {
4,680✔
27
    }
4,680✔
28
    void do_log(Level level, const std::string& message) override final
29
    {
×
30
        ensure_prefix();                                          // Throws
×
31
        Logger::do_log(m_base_logger, level, m_prefix + message); // Throws
×
32
    }
×
33

34
private:
35
    const StringData m_table_name;
36
    util::Logger& m_base_logger;
37
    std::string m_prefix;
38
    void ensure_prefix()
39
    {
×
40
        if (REALM_LIKELY(!m_prefix.empty()))
×
41
            return;
×
42
        std::ostringstream out;
×
43
        out << "Table[" << m_table_name << "]: "; // Throws
×
44
        m_prefix = out.str();                     // Throws
×
45
    }
×
46
};
47

48

49
class ObjectCompareLogger : public util::Logger {
50
public:
51
    ObjectCompareLogger(Mixed pk, util::Logger& base_logger) noexcept
52
        : util::Logger(base_logger.get_level_threshold())
53
        , m_pk{pk}
54
        , m_base_logger{base_logger}
55
    {
19,178✔
56
    }
19,178✔
57
    void do_log(Level level, const std::string& message) override final
58
    {
×
59
        ensure_prefix();                                          // Throws
×
60
        Logger::do_log(m_base_logger, level, m_prefix + message); // Throws
×
61
    }
×
62

63
private:
64
    const Mixed m_pk;
65
    util::Logger& m_base_logger;
66
    std::string m_prefix;
67
    void ensure_prefix()
68
    {
×
69
        if (REALM_LIKELY(!m_prefix.empty()))
×
70
            return;
×
71
        std::ostringstream out;
×
NEW
72
        out << m_pk << ": ";                   // Throws
×
73
        m_prefix = out.str();                  // Throws
×
74
    }
×
75
};
76

77

78
template <class T, class Cmp = std::equal_to<>>
79
bool compare_arrays(T& a, T& b, Cmp equals = Cmp{})
80
{
7,872✔
81
    auto a_it = a.begin();
7,872✔
82
    auto b_it = b.begin();
7,872✔
83
    if (a.size() != b.size()) {
7,872!
84
#if REALM_DEBUG
×
85
        std::cerr << "LEFT size: " << a.size() << std::endl;
×
86
        std::cerr << "RIGHT size: " << b.size() << std::endl;
×
87
#endif // REALM_DEBUG
×
88
        return false;
×
89
    }
×
90

3,858✔
91
    // Compare entries
3,858✔
92
    for (; a_it != a.end(); ++a_it, ++b_it) {
13,738!
93
        if (!equals(*a_it, *b_it))
5,866!
94
            goto different;
×
95
    }
5,866✔
96

3,858✔
97
    return true;
7,872✔
98
different:
×
99
#if REALM_DEBUG
×
100
    std::cerr << "LEFT: " << *a_it << std::endl;
×
101
    std::cerr << "RIGHT: " << *b_it << std::endl;
×
102
#endif // REALM_DEBUG
×
103
    return false;
×
104
}
7,872✔
105

106
template <class T>
107
bool compare_set_values(const Set<T>& a, const Set<T>& b)
108
{
22✔
109
    return compare_arrays(a, b, SetElementEquals<T>{});
22✔
110
}
22✔
111

112
bool compare_dictionaries(const Dictionary& a, const Dictionary& b)
113
{
34✔
114
    auto a_it = a.begin();
34✔
115
    auto b_it = b.begin();
34✔
116
    if (a.size() != b.size()) {
34✔
117
#if REALM_DEBUG
×
118
        std::cerr << "LEFT size: " << a.size() << std::endl;
×
119
        std::cerr << "RIGHT size: " << b.size() << std::endl;
×
120
#endif
×
121
        return false;
×
122
    }
×
123

17✔
124
    // Compare entries
17✔
125
    for (; a_it != a.end(); ++a_it, ++b_it) {
186✔
126
        if (*a_it != *b_it)
152✔
127
            goto different;
×
128
    }
152✔
129

17✔
130
    return true;
34✔
131

132
different:
×
133
#if REALM_DEBUG
×
134
    std::cerr << "LEFT: " << (*a_it).first << " => " << (*a_it).second << std::endl;
×
135
    std::cerr << "RIGHT: " << (*b_it).first << " => " << (*b_it).second << std::endl;
×
136
#endif
×
137
    return false;
×
138
}
34✔
139

140
struct Column {
141
    StringData name;
142
    ColKey key_1, key_2;
143

144
    DataType get_type() const noexcept
145
    {
50,240✔
146
        return DataType(key_1.get_type());
50,240✔
147
    }
50,240✔
148

149
    bool is_list() const noexcept
150
    {
50,213✔
151
        return key_1.is_list();
50,213✔
152
    }
50,213✔
153

154
    bool is_dictionary() const noexcept
155
    {
50,273✔
156
        return key_1.is_dictionary();
50,273✔
157
    }
50,273✔
158

159
    bool is_set() const noexcept
160
    {
50,239✔
161
        return key_1.is_set();
50,239✔
162
    }
50,239✔
163

164
    bool is_nullable() const noexcept
165
    {
60,943✔
166
        return key_1.is_nullable();
60,943✔
167
    }
60,943✔
168
};
169

170
ObjKey row_for_primary_key(const Table& table, Mixed pk)
171
{
38,356✔
172
    ColKey pk_col = table.get_primary_key_column();
38,356✔
173
    if (pk_col) {
38,356✔
174
        return table.find_primary_key(pk); // will assert on type mismatch
38,356✔
175
    }
38,356✔
176

NEW
177
    if (pk.is_type(type_Link, type_TypedLink)) {
×
NEW
178
        return pk.get<ObjKey>();
×
179
    }
×
180
    else {
×
181
        REALM_TERMINATE("row_for_primary_key() with primary key, expected GlobalKey");
182
    }
×
183
    return {};
×
184
}
×
185

186
bool compare_objects(const Obj& obj_1, const Obj& obj_2, const std::vector<Column>& columns, util::Logger& logger);
187
bool compare_objects(Mixed& oid, const Table& table_1, const Table& table_2, const std::vector<Column>& columns,
188
                     util::Logger& logger);
189

190
bool compare_schemas(const Table& table_1, const Table& table_2, util::Logger& logger,
191
                     std::vector<Column>* out_columns = nullptr)
192
{
5,368✔
193
    bool equal = true;
5,368✔
194

2,700✔
195
    // Compare column names
2,700✔
196
    {
5,368✔
197
        auto col_keys = table_1.get_column_keys();
5,368✔
198
        for (auto key : col_keys) {
12,990✔
199
            StringData name = table_1.get_column_name(key);
12,990✔
200
            if (!table_2.get_column_key(name)) {
12,990✔
201
                logger.error("Column '%1' not found in right-hand side table", name);
×
202
                equal = false;
×
203
            }
×
204
        }
12,990✔
205
    }
5,368✔
206
    {
5,368✔
207
        auto col_keys = table_2.get_column_keys();
5,368✔
208
        for (auto key : col_keys) {
12,990✔
209
            StringData name = table_2.get_column_name(key);
12,990✔
210
            if (!table_1.get_column_key(name)) {
12,990✔
211
                logger.error("Column '%1' not found in left-hand side table", name);
×
212
                equal = false;
×
213
            }
×
214
        }
12,990✔
215
    }
5,368✔
216

2,700✔
217
    // Compare column signatures
2,700✔
218
    {
5,368✔
219
        auto keys_1 = table_1.get_column_keys();
5,368✔
220
        for (auto key_1 : keys_1) {
12,990✔
221
            StringData name = table_1.get_column_name(key_1);
12,990✔
222
            ColKey key_2 = table_2.get_column_key(name);
12,990✔
223
            if (!key_2)
12,990✔
224
                continue;
×
225
            DataType type_1 = table_1.get_column_type(key_1);
12,990✔
226
            DataType type_2 = table_2.get_column_type(key_2);
12,990✔
227
            if (type_1 != type_2) {
12,990✔
228
                logger.error("Type mismatch on column '%1'", name);
×
229
                equal = false;
×
230
                continue;
×
231
            }
×
232
            bool nullable_1 = table_1.is_nullable(key_1);
12,990✔
233
            bool nullable_2 = table_2.is_nullable(key_2);
12,990✔
234
            if (nullable_1 != nullable_2) {
12,990✔
235
                logger.error("Nullability mismatch on column '%1'", name);
×
236
                equal = false;
×
237
                continue;
×
238
            }
×
239
            bool is_list_1 = table_1.is_list(key_1);
12,990✔
240
            bool is_list_2 = table_2.is_list(key_2);
12,990✔
241
            if (is_list_1 != is_list_2) {
12,990✔
242
                logger.error("List type mismatch on column '%1'", name);
×
243
                equal = false;
×
244
                continue;
×
245
            }
×
246
            bool is_dictionary_1 = key_1.is_dictionary();
12,990✔
247
            bool is_dictionary_2 = key_2.is_dictionary();
12,990✔
248
            if (is_dictionary_1 != is_dictionary_2) {
12,990✔
249
                logger.error("Dictionary type mismatch on column '%1'", name);
×
250
                equal = false;
×
251
                continue;
×
252
            }
×
253
            bool is_set_1 = key_1.is_set();
12,990✔
254
            bool is_set_2 = key_2.is_set();
12,990✔
255
            if (is_set_1 != is_set_2) {
12,990✔
256
                logger.error("Set type mismatch on column '%1'", name);
×
257
                equal = false;
×
258
                continue;
×
259
            }
×
260
            if (type_1 == type_Link || type_1 == type_LinkList) {
12,990✔
261
                ConstTableRef target_1 = table_1.get_link_target(key_1);
838✔
262
                ConstTableRef target_2 = table_2.get_link_target(key_2);
838✔
263
                if (target_1->get_name() != target_2->get_name()) {
838✔
264
                    logger.error("Link target mismatch on column '%1'", name);
×
265
                    equal = false;
×
266
                    continue;
×
267
                }
×
268
            }
12,990✔
269
            if (out_columns)
12,990✔
270
                out_columns->push_back(Column{name, key_1, key_2});
12,990✔
271
        }
12,990✔
272
    }
5,368✔
273

2,700✔
274
    return equal;
5,368✔
275
}
5,368✔
276

277
bool compare_lists(const Column& col, const Obj& obj_1, const Obj& obj_2, util::Logger& logger)
278
{
8,542✔
279
    switch (col.get_type()) {
8,542✔
280
        case type_Int: {
4,128✔
281
            if (col.is_nullable()) {
4,128✔
282
                auto a = obj_1.get_list<util::Optional<int64_t>>(col.key_1);
×
283
                auto b = obj_2.get_list<util::Optional<int64_t>>(col.key_2);
×
284
                if (!compare_arrays(a, b)) {
×
285
                    logger.error("List mismatch in column '%1'", col.name);
×
286
                    return false;
×
287
                }
×
288
            }
4,128✔
289
            else {
4,128✔
290
                auto a = obj_1.get_list<int64_t>(col.key_1);
4,128✔
291
                auto b = obj_2.get_list<int64_t>(col.key_2);
4,128✔
292
                if (!compare_arrays(a, b)) {
4,128✔
293
                    logger.error("List mismatch in column '%1'", col.name);
×
294
                    return false;
×
295
                }
×
296
            }
4,128✔
297
            break;
4,128✔
298
        }
4,128✔
299
        case type_Bool: {
2,098✔
300
            auto a = obj_1.get_list<bool>(col.key_1);
×
301
            auto b = obj_2.get_list<bool>(col.key_2);
×
302
            if (!compare_arrays(a, b)) {
×
303
                logger.error("List mismatch in column '%1'", col.name);
×
304
                return false;
×
305
            }
×
306
            break;
×
307
        }
×
308
        case type_String: {
3,722✔
309
            auto a = obj_1.get_list<String>(col.key_1);
3,722✔
310
            auto b = obj_2.get_list<String>(col.key_2);
3,722✔
311
            if (!compare_arrays(a, b)) {
3,722✔
312
                logger.error("List mismatch in column '%1'", col.name);
×
313
                return false;
×
314
            }
×
315
            break;
3,722✔
316
        }
3,722✔
317
        case type_Binary: {
1,749✔
318
            auto a = obj_1.get_list<Binary>(col.key_1);
×
319
            auto b = obj_2.get_list<Binary>(col.key_2);
×
320
            if (!compare_arrays(a, b)) {
×
321
                logger.error("List mismatch in column '%1'", col.name);
×
322
                return false;
×
323
            }
×
324
            break;
×
325
        }
×
326
        case type_Float: {
✔
327
            auto a = obj_1.get_list<float>(col.key_1);
×
328
            auto b = obj_2.get_list<float>(col.key_2);
×
329
            if (!compare_arrays(a, b)) {
×
330
                logger.error("List mismatch in column '%1'", col.name);
×
331
                return false;
×
332
            }
×
333
            break;
×
334
        }
×
335
        case type_Double: {
✔
336
            auto a = obj_1.get_list<double>(col.key_1);
×
337
            auto b = obj_2.get_list<double>(col.key_2);
×
338
            if (!compare_arrays(a, b)) {
×
339
                logger.error("List mismatch in column '%1'", col.name);
×
340
                return false;
×
341
            }
×
342
            break;
×
343
        }
×
344
        case type_Timestamp: {
✔
345
            auto a = obj_1.get_list<Timestamp>(col.key_1);
×
346
            auto b = obj_2.get_list<Timestamp>(col.key_2);
×
347
            if (!compare_arrays(a, b)) {
×
348
                logger.error("List mismatch in column '%1'", col.name);
×
349
                return false;
×
350
            }
×
351
            break;
×
352
        }
×
353
        case type_ObjectId: {
✔
354
            auto a = obj_1.get_list<ObjectId>(col.key_1);
×
355
            auto b = obj_2.get_list<ObjectId>(col.key_2);
×
356
            if (!compare_arrays(a, b)) {
×
357
                logger.error("List mismatch in column '%1'", col.name);
×
358
                return false;
×
359
            }
×
360
            break;
×
361
        }
×
362
        case type_UUID: {
✔
363
            auto a = obj_1.get_list<UUID>(col.key_1);
×
364
            auto b = obj_2.get_list<UUID>(col.key_2);
×
365
            if (!compare_arrays(a, b)) {
×
366
                logger.error("List mismatch in column '%1'", col.name);
×
367
                return false;
×
368
            }
×
369
            break;
×
370
        }
×
371
        case type_Decimal: {
✔
372
            auto a = obj_1.get_list<Decimal128>(col.key_1);
×
373
            auto b = obj_2.get_list<Decimal128>(col.key_2);
×
374
            if (!compare_arrays(a, b)) {
×
375
                logger.error("List mismatch in column '%1'", col.name);
×
376
                return false;
×
377
            }
×
378
            break;
×
379
        }
×
380
        case type_Mixed: {
✔
381
            auto a = obj_1.get_list<Mixed>(col.key_1);
×
382
            auto b = obj_2.get_list<Mixed>(col.key_2);
×
383
            if (!compare_arrays(a, b)) {
×
384
                logger.error("List mismatch in column '%1'", col.name);
×
385
                return false;
×
386
            }
×
387
            break;
×
388
        }
×
389
        case type_TypedLink:
✔
390
            // FIXME: Implement
391
            break;
×
392
        case type_LinkList: {
692✔
393
            auto a = obj_1.get_list<ObjKey>(col.key_1);
692✔
394
            auto b = obj_2.get_list<ObjKey>(col.key_2);
692✔
395
            if (a.size() != b.size()) {
692✔
396
                logger.error("Link list size mismatch in column '%1'", col.name);
×
397
                return false;
×
398
                break;
×
399
            }
692✔
400
            auto table_1 = obj_1.get_table();
692✔
401
            auto table_2 = obj_2.get_table();
692✔
402
            ConstTableRef target_table_1 = table_1->get_link_target(col.key_1);
692✔
403
            ConstTableRef target_table_2 = table_2->get_link_target(col.key_2);
692✔
404

346✔
405
            bool is_embedded = target_table_1->is_embedded();
692✔
406
            std::vector<Column> embedded_columns;
692✔
407
            if (is_embedded) {
692✔
408
                // FIXME: This does the schema comparison for
320✔
409
                // embedded tables for every object with embedded
320✔
410
                // objects, just because we want to get the Column
320✔
411
                // info. Instead compare just the objects
320✔
412
                // themselves.
320✔
413
                bool schemas_equal = compare_schemas(*target_table_1, *target_table_2, logger, &embedded_columns);
640✔
414
                REALM_ASSERT(schemas_equal);
640✔
415
            }
640✔
416

346✔
417
            std::size_t n = a.size();
692✔
418
            for (std::size_t i = 0; i < n; ++i) {
1,448✔
419
                ObjKey link_1 = a.get(i);
756✔
420
                ObjKey link_2 = b.get(i);
756✔
421

378✔
422
                if (link_1.is_unresolved() || link_2.is_unresolved()) {
756✔
423
                    // if one link is unresolved, the other should also be unresolved
11✔
424
                    if (!link_1.is_unresolved() || !link_2.is_unresolved()) {
22✔
425
                        logger.error("Value mismatch in column '%1' at index %2 of the link "
×
426
                                     "list (%3 vs %4)",
×
427
                                     col.name, i, link_1, link_2);
×
428
                        return false;
×
429
                    }
×
430
                }
734✔
431
                else {
734✔
432
                    if (is_embedded) {
734✔
433
                        const Obj embedded_1 = target_table_1->get_object(link_1);
674✔
434
                        const Obj embedded_2 = target_table_2->get_object(link_2);
674✔
435
                        // Skip ID comparison for embedded objects, because
337✔
436
                        // they are only identified by their position in the
337✔
437
                        // database.
337✔
438
                        if (!compare_objects(embedded_1, embedded_2, embedded_columns, logger)) {
674✔
439
                            logger.error("Embedded object contents mismatch in column '%1'", col.name);
×
440
                            return false;
×
441
                        }
×
442
                    }
60✔
443
                    else {
60✔
444
                        Mixed target_oid_1 = target_table_1->get_primary_key(link_1);
60✔
445
                        Mixed target_oid_2 = target_table_2->get_primary_key(link_2);
60✔
446
                        if (target_oid_1 != target_oid_2) {
60✔
447
                            logger.error("Value mismatch in column '%1' at index %2 of the link "
×
448
                                         "list (%3 vs %4)",
×
449
                                         col.name, i, link_1, link_2);
×
450
                            return false;
×
451
                        }
×
452
                    }
60✔
453
                }
734✔
454
            }
756✔
455
            break;
692✔
456
        }
692✔
457
        case type_Link:
346✔
458
            REALM_TERMINATE("Unsupported column type.");
459
    }
8,542✔
460

4,193✔
461
    return true;
8,542✔
462
}
8,542✔
463

464
bool compare_sets(const Column& col, const Obj& obj_1, const Obj& obj_2, util::Logger& logger)
465
{
26✔
466
    switch (col.get_type()) {
26✔
467
        case type_Int: {
4✔
468
            if (col.is_nullable()) {
4✔
469
                auto a = obj_1.get_set<util::Optional<int64_t>>(col.key_1);
×
470
                auto b = obj_2.get_set<util::Optional<int64_t>>(col.key_2);
×
471
                if (!compare_set_values(a, b)) {
×
472
                    logger.error("Set mismatch in column '%1'", col.name);
×
473
                    return false;
×
474
                }
×
475
            }
4✔
476
            else {
4✔
477
                auto a = obj_1.get_set<int64_t>(col.key_1);
4✔
478
                auto b = obj_2.get_set<int64_t>(col.key_2);
4✔
479
                if (!compare_set_values(a, b)) {
4✔
480
                    logger.error("Set mismatch in column '%1'", col.name);
×
481
                    return false;
×
482
                }
×
483
            }
4✔
484
            break;
4✔
485
        }
4✔
486
        case type_Bool: {
2✔
487
            auto a = obj_1.get_set<bool>(col.key_1);
×
488
            auto b = obj_2.get_set<bool>(col.key_2);
×
489
            if (!compare_set_values(a, b)) {
×
490
                logger.error("Set mismatch in column '%1'", col.name);
×
491
                return false;
×
492
            }
×
493
            break;
×
494
        }
×
495
        case type_String: {
4✔
496
            auto a = obj_1.get_set<String>(col.key_1);
4✔
497
            auto b = obj_2.get_set<String>(col.key_2);
4✔
498
            if (!compare_set_values(a, b)) {
4✔
499
                logger.error("Set mismatch in column '%1'", col.name);
×
500
                return false;
×
501
            }
×
502
            break;
4✔
503
        }
4✔
504
        case type_Binary: {
2✔
505
            auto a = obj_1.get_set<Binary>(col.key_1);
×
506
            auto b = obj_2.get_set<Binary>(col.key_2);
×
507
            if (!compare_set_values(a, b)) {
×
508
                logger.error("Set mismatch in column '%1'", col.name);
×
509
                return false;
×
510
            }
×
511
            break;
×
512
        }
×
513
        case type_Float: {
✔
514
            auto a = obj_1.get_set<float>(col.key_1);
×
515
            auto b = obj_2.get_set<float>(col.key_2);
×
516
            if (!compare_set_values(a, b)) {
×
517
                logger.error("Set mismatch in column '%1'", col.name);
×
518
                return false;
×
519
            }
×
520
            break;
×
521
        }
×
522
        case type_Double: {
✔
523
            auto a = obj_1.get_set<double>(col.key_1);
×
524
            auto b = obj_2.get_set<double>(col.key_2);
×
525
            if (!compare_set_values(a, b)) {
×
526
                logger.error("Set mismatch in column '%1'", col.name);
×
527
                return false;
×
528
            }
×
529
            break;
×
530
        }
×
531
        case type_Timestamp: {
✔
532
            auto a = obj_1.get_set<Timestamp>(col.key_1);
×
533
            auto b = obj_2.get_set<Timestamp>(col.key_2);
×
534
            if (!compare_set_values(a, b)) {
×
535
                logger.error("Set mismatch in column '%1'", col.name);
×
536
                return false;
×
537
            }
×
538
            break;
×
539
        }
×
540
        case type_ObjectId: {
✔
541
            auto a = obj_1.get_set<ObjectId>(col.key_1);
×
542
            auto b = obj_2.get_set<ObjectId>(col.key_2);
×
543
            if (!compare_set_values(a, b)) {
×
544
                logger.error("Set mismatch in column '%1'", col.name);
×
545
                return false;
×
546
            }
×
547
            break;
×
548
        }
×
549
        case type_UUID: {
✔
550
            auto a = obj_1.get_set<UUID>(col.key_1);
×
551
            auto b = obj_2.get_set<UUID>(col.key_2);
×
552
            if (!compare_set_values(a, b)) {
×
553
                logger.error("Set mismatch in column '%1'", col.name);
×
554
                return false;
×
555
            }
×
556
            break;
×
557
        }
×
558
        case type_Decimal: {
✔
559
            auto a = obj_1.get_set<Decimal128>(col.key_1);
×
560
            auto b = obj_2.get_set<Decimal128>(col.key_2);
×
561
            if (!compare_set_values(a, b)) {
×
562
                logger.error("Set mismatch in column '%1'", col.name);
×
563
                return false;
×
564
            }
×
565
            break;
×
566
        }
×
567
        case type_Mixed: {
14✔
568
            auto a = obj_1.get_set<Mixed>(col.key_1);
14✔
569
            auto b = obj_2.get_set<Mixed>(col.key_2);
14✔
570
            if (!compare_set_values(a, b)) {
14✔
571
                logger.error("Set mismatch in column '%1'", col.name);
×
572
                return false;
×
573
            }
×
574
            break;
14✔
575
        }
14✔
576
        case type_Link: {
9✔
577
            auto a = obj_1.get_linkset(col.key_1);
4✔
578
            auto b = obj_2.get_linkset(col.key_2);
4✔
579
            if (a.size() != b.size()) {
4✔
580
                logger.error("Link set size mismatch in column '%1'", col.name);
×
581
                return false;
×
582
                break;
×
583
            }
4✔
584

2✔
585
            auto target_table_1 = a.get_target_table();
4✔
586
            auto target_table_2 = b.get_target_table();
4✔
587

2✔
588
            std::size_t n = a.size();
4✔
589
            for (std::size_t i = 0; i < n; ++i) {
8✔
590
                ObjKey link_1 = a.get(i);
4✔
591
                ObjKey link_2 = b.get(i);
4✔
592

2✔
593
                if (link_1.is_unresolved() || link_2.is_unresolved()) {
4✔
594
                    // if one link is unresolved, the other should also be unresolved
595
                    if (!link_1.is_unresolved() || !link_2.is_unresolved()) {
×
596
                        logger.error("Value mismatch in column '%1' at index %2 of the link "
×
597
                                     "set (%3 vs %4)",
×
598
                                     col.name, i, link_1, link_2);
×
599
                        return false;
×
600
                    }
×
601
                }
4✔
602
                else {
4✔
603
                    Mixed target_oid_1 = target_table_1->get_primary_key(link_1);
4✔
604
                    Mixed target_oid_2 = target_table_2->get_primary_key(link_2);
4✔
605
                    if (target_oid_1 != target_oid_2) {
4✔
606
                        logger.error("Value mismatch in column '%1' at index %2 of the link "
×
607
                                     "set (%3 vs %4)",
×
608
                                     col.name, i, link_1, link_2);
×
609
                        return false;
×
610
                    }
×
611
                }
4✔
612
            }
4✔
613
            break;
4✔
614
        }
4✔
615
        case type_TypedLink:
2✔
616
            // FIXME: Implement
617
            break;
×
618
        case type_LinkList:
2✔
619
            REALM_TERMINATE("Unsupported column type.");
620
    }
26✔
621

13✔
622
    return true;
26✔
623
}
26✔
624

625
bool compare_objects(const Obj& obj_1, const Obj& obj_2, const std::vector<Column>& columns, util::Logger& logger)
626
{
19,899✔
627
    bool equal = true;
19,899✔
628
    auto ptable_1 = obj_1.get_table();
19,899✔
629
    auto ptable_2 = obj_2.get_table();
19,899✔
630
    auto& table_1 = *ptable_1;
19,899✔
631
    auto& table_2 = *ptable_2;
19,899✔
632

10,214✔
633
    for (const Column& col : columns) {
56,811✔
634
        if (col.is_nullable()) {
56,811✔
635
            bool a = obj_1.is_null(col.key_1);
7,522✔
636
            bool b = obj_2.is_null(col.key_2);
7,522✔
637
            if (a && b)
7,522✔
638
                continue;
6,538✔
639
            if (a || b) {
984✔
640
                logger.error("Null/nonnull disagreement in column '%1' (%2 vs %3)", col.name, a, b);
×
641
                equal = false;
×
642
                continue;
×
643
            }
×
644
        }
50,273✔
645

25,518✔
646
        if (col.is_dictionary()) {
50,273✔
647
            auto a = obj_1.get_dictionary(col.key_1);
34✔
648
            auto b = obj_2.get_dictionary(col.key_2);
34✔
649
            if (!compare_dictionaries(a, b)) {
34✔
650
                logger.error("Dictionary mismatch in column '%1'", col.name);
×
651
                equal = false;
×
652
            }
×
653
            continue;
34✔
654
        }
34✔
655

25,501✔
656
        if (col.is_set()) {
50,239✔
657
            if (!compare_sets(col, obj_1, obj_2, logger)) {
26✔
658
                logger.error("Set mismatch in column '%1'", col.name);
×
659
                equal = false;
×
660
            }
×
661
            continue;
26✔
662
        }
26✔
663

25,488✔
664
        if (col.is_list()) {
50,213✔
665
            if (!compare_lists(col, obj_1, obj_2, logger)) {
8,542✔
666
                equal = false;
×
667
            }
×
668
            continue;
8,542✔
669
        }
8,542✔
670

21,295✔
671
        const bool nullable = table_1.is_nullable(col.key_1);
41,671✔
672
        REALM_ASSERT(table_2.is_nullable(col.key_2) == nullable);
41,671✔
673
        switch (col.get_type()) {
41,671✔
674
            case type_Int: {
33,456✔
675
                if (nullable) {
33,456✔
676
                    auto a = obj_1.get<util::Optional<int64_t>>(col.key_1);
522✔
677
                    auto b = obj_2.get<util::Optional<int64_t>>(col.key_2);
522✔
678
                    if (a != b) {
522✔
679
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
680
                        equal = false;
×
681
                    }
×
682
                }
522✔
683
                else {
32,934✔
684
                    auto a = obj_1.get<int64_t>(col.key_1);
32,934✔
685
                    auto b = obj_2.get<int64_t>(col.key_2);
32,934✔
686
                    if (a != b) {
32,934✔
687
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
688
                        equal = false;
×
689
                    }
×
690
                }
32,934✔
691
                continue;
33,456✔
692
            }
×
693
            case type_Bool: {
12✔
694
                if (nullable) {
12✔
695
                    auto a = obj_1.get<util::Optional<bool>>(col.key_1);
×
696
                    auto b = obj_2.get<util::Optional<bool>>(col.key_2);
×
697
                    if (a != b) {
×
698
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
699
                        equal = false;
×
700
                    }
×
701
                }
×
702
                else {
12✔
703
                    auto a = obj_1.get<bool>(col.key_1);
12✔
704
                    auto b = obj_2.get<bool>(col.key_2);
12✔
705
                    if (a != b) {
12✔
706
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
707
                        equal = false;
×
708
                    }
×
709
                }
12✔
710

6✔
711
                continue;
12✔
712
            }
×
713
            case type_Float: {
12✔
714
                auto a = obj_1.get<float>(col.key_1);
12✔
715
                auto b = obj_2.get<float>(col.key_2);
12✔
716
                if (a != b) {
12✔
717
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
718
                    equal = false;
×
719
                }
×
720
                continue;
12✔
721
            }
×
722
            case type_Double: {
16✔
723
                auto a = obj_1.get<double>(col.key_1);
16✔
724
                auto b = obj_2.get<double>(col.key_2);
16✔
725
                if (a != b) {
16✔
726
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
727
                    equal = false;
×
728
                }
×
729
                continue;
16✔
730
            }
×
731
            case type_String: {
7,250✔
732
                auto a = obj_1.get<StringData>(col.key_1);
7,250✔
733
                auto b = obj_2.get<StringData>(col.key_2);
7,250✔
734
                if (a != b) {
7,250✔
735
                    logger.error("Value mismatch in column '%1'", col.name);
×
736
                    equal = false;
×
737
                }
×
738
                continue;
7,250✔
739
            }
×
740
            case type_Binary: {
832✔
741
                // FIXME: This looks like an incorrect way of comparing BLOBs (Table::get_binary_iterator()).
416✔
742
                auto a = obj_1.get<BinaryData>(col.key_1);
832✔
743
                auto b = obj_2.get<BinaryData>(col.key_2);
832✔
744
                if (a != b) {
832✔
745
                    logger.error("Value mismatch in column '%1'", col.name);
×
746
                    equal = false;
×
747
                }
×
748
                continue;
832✔
749
            }
×
750
            case type_Timestamp: {
16✔
751
                auto a = obj_1.get<Timestamp>(col.key_1);
16✔
752
                auto b = obj_2.get<Timestamp>(col.key_2);
16✔
753
                if (a != b) {
16✔
754
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
755
                    equal = false;
×
756
                }
×
757
                continue;
16✔
758
            }
×
759
            case type_ObjectId: {
8✔
760
                auto a = obj_1.get<ObjectId>(col.key_1);
8✔
761
                auto b = obj_2.get<ObjectId>(col.key_2);
8✔
762
                if (a != b) {
8✔
763
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
764
                    equal = false;
×
765
                }
×
766
                continue;
8✔
767
            }
×
768
            case type_Decimal: {
8✔
769
                auto a = obj_1.get<Decimal128>(col.key_1);
8✔
770
                auto b = obj_2.get<Decimal128>(col.key_2);
8✔
771
                if (a != b) {
8✔
772
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
773
                    equal = false;
×
774
                }
×
775
                continue;
8✔
776
            }
×
777
            case type_Mixed: {
2✔
778
                auto a = obj_1.get<Mixed>(col.key_1);
2✔
779
                auto b = obj_2.get<Mixed>(col.key_2);
2✔
780
                if (a != b) {
2✔
781
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
782
                    equal = false;
×
783
                }
×
784
                continue;
2✔
785
            }
×
786
            case type_UUID: {
✔
787
                auto a = obj_1.get<UUID>(col.key_1);
×
788
                auto b = obj_2.get<UUID>(col.key_2);
×
789
                if (a != b) {
×
790
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
791
                    equal = false;
×
792
                }
×
793
                continue;
×
794
            }
×
795
            case type_TypedLink:
✔
796
                // FIXME: Implement
797
                continue;
×
798
            case type_Link: {
60✔
799
                auto link_1 = obj_1.get<ObjKey>(col.key_1);
60✔
800
                auto link_2 = obj_2.get<ObjKey>(col.key_2);
60✔
801
                ConstTableRef target_table_1 = table_1.get_link_target(col.key_1);
60✔
802
                ConstTableRef target_table_2 = table_2.get_link_target(col.key_2);
60✔
803

30✔
804
                if (!link_1 || !link_2) {
60✔
805
                    // If one link is null the other should also be null
806
                    if (link_1 != link_2) {
×
807
                        equal = false;
×
808
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, link_1, link_2);
×
809
                    }
×
810
                }
×
811
                else {
60✔
812
                    bool is_embedded = target_table_1->is_embedded();
60✔
813
                    std::vector<Column> embedded_columns;
60✔
814
                    if (is_embedded) {
60✔
815
                        // FIXME: This does the schema comparison for
24✔
816
                        // embedded tables for every object with embedded
24✔
817
                        // objects, just because we want to get the Column
24✔
818
                        // info. Instead compare just the objects
24✔
819
                        // themselves.
24✔
820
                        bool schemas_equal =
48✔
821
                            compare_schemas(*target_table_1, *target_table_2, logger, &embedded_columns);
48✔
822
                        REALM_ASSERT(schemas_equal);
48✔
823
                    }
48✔
824

30✔
825
                    if (is_embedded) {
60✔
826
                        const Obj embedded_1 = target_table_1->get_object(link_1);
48✔
827
                        const Obj embedded_2 = target_table_2->get_object(link_2);
48✔
828
                        // Skip ID comparison for embedded objects, because
24✔
829
                        // they are only identified by their position in the
24✔
830
                        // database.
24✔
831
                        if (!compare_objects(embedded_1, embedded_2, embedded_columns, logger)) {
48✔
832
                            logger.error("Embedded object contents mismatch in column '%1'", col.name);
×
833
                            equal = false;
×
834
                        }
×
835
                    }
48✔
836
                    else {
12✔
837
                        Mixed target_oid_1 = target_table_1->get_primary_key(link_1);
12✔
838
                        Mixed target_oid_2 = target_table_2->get_primary_key(link_2);
12✔
839
                        if (target_oid_1 != target_oid_2) {
12✔
NEW
840
                            logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, target_oid_1,
×
NEW
841
                                         target_oid_2);
×
842
                            equal = false;
×
843
                        }
×
844
                    }
12✔
845
                }
60✔
846

30✔
847
                continue;
60✔
848
            }
×
849
            case type_LinkList:
✔
850
                break;
×
851
        }
×
852
        REALM_TERMINATE("Unsupported column type.");
853
    }
×
854
    return equal;
19,900✔
855
}
19,899✔
856

857
bool compare_objects(Mixed& pk, const Table& table_1, const Table& table_2, const std::vector<Column>& columns,
858
                     util::Logger& logger)
859
{
19,178✔
860
    ObjKey oid_1 = row_for_primary_key(table_1, pk);
19,178✔
861
    ObjKey oid_2 = row_for_primary_key(table_2, pk);
19,178✔
862

9,854✔
863
    // Note: This is ensured by the inventory handling in compare_tables().
9,854✔
864
    REALM_ASSERT(oid_1);
19,178✔
865
    REALM_ASSERT(oid_2);
19,178✔
866
    const Obj obj_1 = table_1.get_object(oid_1);
19,178✔
867
    const Obj obj_2 = table_2.get_object(oid_2);
19,178✔
868
    return compare_objects(obj_1, obj_2, columns, logger);
19,178✔
869
}
19,178✔
870

871
} // anonymous namespace
872

873
namespace realm::test_util {
874

875
bool compare_tables(const Table& table_1, const Table& table_2)
876
{
×
877
    util::NullLogger logger;
×
878
    return compare_tables(table_1, table_2, logger);
×
879
}
×
880

881
bool compare_tables(const Table& table_1, const Table& table_2, util::Logger& logger)
882
{
4,680✔
883
    bool equal = true;
4,680✔
884

2,356✔
885
    std::vector<Column> columns;
4,680✔
886
    equal = compare_schemas(table_1, table_2, logger, &columns);
4,680✔
887

2,356✔
888
    if (table_1.is_embedded() != table_2.is_embedded()) {
4,680✔
889
        logger.error("Table embeddedness mismatch");
×
890
        equal = false;
×
891
    }
×
892

2,356✔
893
    if (table_1.is_embedded() || table_2.is_embedded()) {
4,680✔
894
        if (table_1.size() != table_2.size()) {
90✔
895
            logger.error("Embedded table size mismatch (%1 vs %2): %3", table_1.size(), table_2.size(),
×
896
                         table_1.get_name());
×
897
            equal = false;
×
898
        }
×
899
        // Do not attempt to compare by row on embedded tables.
45✔
900
        return equal;
90✔
901
    }
90✔
902

2,311✔
903
    // Compare row sets
2,311✔
904
    using Objects = std::set<Mixed>;
4,590✔
905
    auto make_inventory = [](const Table& table, Objects& objects) {
9,180✔
906
        for (const Obj& obj : table) {
38,356✔
907
            objects.insert(obj.get_primary_key());
38,356✔
908
        }
38,356✔
909
    };
9,180✔
910
    Objects objects_1, objects_2;
4,590✔
911
    make_inventory(table_1, objects_1);
4,590✔
912
    make_inventory(table_2, objects_2);
4,590✔
913
    auto report_missing = [&](const char* hand_2, Objects& objects_1, Objects& objects_2) {
9,180✔
914
        std::vector<Mixed> missing;
9,180✔
915
        for (auto oid : objects_1) {
38,356✔
916
            if (objects_2.find(oid) == objects_2.end())
38,356✔
917
                missing.push_back(oid);
×
918
        }
38,356✔
919
        if (missing.empty())
9,180✔
920
            return;
9,180✔
921
        std::size_t n = missing.size();
×
922
        if (n == 1) {
×
NEW
923
            logger.error("One object missing in %1 side table: %2", hand_2, missing[0]);
×
924
            equal = false;
×
925
            return;
×
926
        }
×
927
        std::ostringstream out;
×
NEW
928
        out << missing[0];
×
929
        std::size_t m = std::min<std::size_t>(4, n);
×
930
        for (std::size_t i = 1; i < m; ++i)
×
NEW
931
            out << ", " << missing[i];
×
932
        if (m < n)
×
933
            out << ", ...";
×
934
        logger.error("%1 objects missing in %2 side table: %3", n, hand_2, out.str());
×
935
        equal = false;
×
936
    };
×
937
    report_missing("right-hand", objects_1, objects_2);
4,590✔
938
    report_missing("left-hand", objects_2, objects_1);
4,590✔
939

2,311✔
940
    // Compare individual rows
2,311✔
941
    for (auto pk : objects_1) {
19,178✔
942
        if (objects_2.find(pk) != objects_2.end()) {
19,178✔
943
            ObjectCompareLogger sublogger{pk, logger};
19,178✔
944
            if (!compare_objects(pk, table_1, table_2, columns, sublogger)) {
19,178✔
945
                equal = false;
×
946
            }
×
947
        }
19,178✔
948
    }
19,178✔
949

2,311✔
950
    return equal;
4,590✔
951
}
4,590✔
952

953

954
bool compare_groups(const Transaction& group_1, const Transaction& group_2)
955
{
936✔
956
    util::NullLogger logger;
936✔
957
    return compare_groups(group_1, group_2, logger);
936✔
958
}
936✔
959

960

961
bool compare_groups(const Transaction& group_1, const Transaction& group_2,
962
                    util::FunctionRef<bool(StringData)> filter_func, util::Logger& logger)
963
{
1,342✔
964
    auto filter = [&](const Group& group, std::vector<StringData>& tables) {
2,684✔
965
        auto table_keys = group.get_table_keys();
2,684✔
966
        for (auto i : table_keys) {
9,384✔
967
            ConstTableRef table = group.get_table(i);
9,384✔
968
            StringData name = table->get_name();
9,384✔
969
            if (name != "pk" && name != "metadata" && name != "client_reset_metadata" && filter_func(name))
9,384✔
970
                tables.push_back(name);
9,364✔
971
        }
9,384✔
972
    };
2,684✔
973

671✔
974
    std::vector<StringData> tables_1, tables_2;
1,342✔
975
    filter(group_1, tables_1);
1,342✔
976
    filter(group_2, tables_2);
1,342✔
977

671✔
978
    bool equal = true;
1,342✔
979
    for (StringData table_name : tables_1) {
4,682✔
980
        if (!group_2.has_table(table_name)) {
4,682✔
981
            logger.error("Table '%1' not found in right-hand side group", table_name);
2✔
982
            equal = false;
2✔
983
        }
2✔
984
    }
4,682✔
985
    for (StringData table_name : tables_2) {
4,682✔
986
        if (!group_1.has_table(table_name)) {
4,682✔
987
            logger.error("Table '%1' not found in left-hand side group", table_name);
2✔
988
            equal = false;
2✔
989
        }
2✔
990
    }
4,682✔
991

671✔
992
    for (StringData table_name : tables_1) {
4,682✔
993
        ConstTableRef table_1 = group_1.get_table(table_name);
4,682✔
994
        ConstTableRef table_2 = group_2.get_table(table_name);
4,682✔
995
        if (table_2) {
4,682✔
996
            TableCompareLogger sublogger{table_name, logger};
4,680✔
997
            if (!compare_tables(*table_1, *table_2, sublogger))
4,680✔
998
                equal = false;
×
999
        }
4,680✔
1000
    }
4,682✔
1001

671✔
1002
    return equal;
1,342✔
1003
}
1,342✔
1004

1005
} // namespace realm::test_util
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

© 2026 Coveralls, Inc