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

realm / realm-core / 1751

11 Oct 2023 08:13PM UTC coverage: 91.585% (+0.02%) from 91.563%
1751

push

Evergreen

web-flow
Merge pull request #7031 from realm/tg/commit-notify

Simplify internal commit notification

94262 of 173480 branches covered (0.0%)

425 of 432 new or added lines in 20 files covered. (98.38%)

37 existing lines in 12 files now uncovered.

230487 of 251665 relevant lines covered (91.58%)

6531054.97 hits per line

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

86.4
/test/test_transform.cpp
1
#include <stddef.h>
2
#include <stdint.h>
3
#include <algorithm>
4
#include <utility>
5
#include <memory>
6
#include <initializer_list>
7
#include <string>
8
#include <vector>
9
#include <map>
10
#include <sstream>
11
#include <iostream>
12
#include <fstream>
13

14
#include <realm/util/features.h>
15
#include <realm/binary_data.hpp>
16
#include <realm/db.hpp>
17
#include <realm/replication.hpp>
18
#include <realm/list.hpp>
19
#include <realm/set.hpp>
20
#include <realm/sync/transform.hpp>
21

22
#include "test.hpp"
23
#include "util/quote.hpp"
24

25
#include "peer.hpp"
26
#include "fuzz_tester.hpp" // Transform_Randomized
27
#include "util/compare_groups.hpp"
28
#include "util/dump_changesets.hpp"
29

30
extern unsigned int unit_test_random_seed;
31

32
namespace {
33

34
using namespace realm;
35
using namespace realm::sync;
36
using namespace realm::test_util;
37
using unit_test::TestContext;
38

39
// Test independence and thread-safety
40
// -----------------------------------
41
//
42
// All tests must be thread safe and independent of each other. This
43
// is required because it allows for both shuffling of the execution
44
// order and for parallelized testing.
45
//
46
// In particular, avoid using std::rand() since it is not guaranteed
47
// to be thread safe. Instead use the API offered in
48
// `test/util/random.hpp`.
49
//
50
// All files created in tests must use the TEST_PATH macro (or one of
51
// its friends) to obtain a suitable file system path. See
52
// `test/util/test_path.hpp`.
53
//
54
//
55
// Debugging and the ONLY() macro
56
// ------------------------------
57
//
58
// A simple way of disabling all tests except one called `Foo`, is to
59
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
60
// test suite. Note that you can also use filtering by setting the
61
// environment varible `UNITTEST_FILTER`. See `README.md` for more on
62
// this.
63
//
64
// Another way to debug a particular test, is to copy that test into
65
// `experiments/testcase.cpp` and then run `sh build.sh
66
// check-testcase` (or one of its friends) from the command line.
67

68

69
TEST(Transform_OneClient)
70
{
2✔
71
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
72
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
73
    auto client = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
74

1✔
75
    client->create_schema([](WriteTransaction& tr) {
2✔
76
        TableRef t = tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
77
        t->add_column(type_Int, "i");
2✔
78
    });
2✔
79
    synchronize(server.get(), {client.get()});
2✔
80

1✔
81
    ReadTransaction read_server(server->shared_group);
2✔
82
    ReadTransaction read_client(client->shared_group);
2✔
83
    CHECK(compare_groups(read_server, read_client));
2✔
84
}
2✔
85

86

87
TEST(Transform_TwoClients)
88
{
2✔
89
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
90
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
91
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
92
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
93

1✔
94
    auto create_schema = [](WriteTransaction& tr) {
4✔
95
        TableRef foo = tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
4✔
96
        foo->add_column(type_Int, "i");
4✔
97
    };
4✔
98

1✔
99
    client_1->create_schema(create_schema);
2✔
100
    client_2->create_schema(create_schema);
2✔
101

1✔
102
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
103

1✔
104
    ReadTransaction read_server(server->shared_group);
2✔
105
    {
2✔
106
        ReadTransaction read_client_1(client_1->shared_group);
2✔
107
        CHECK(compare_groups(read_server, read_client_1));
2✔
108
    }
2✔
109
    {
2✔
110
        ReadTransaction read_client_2(client_2->shared_group);
2✔
111
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
112
    }
2✔
113
}
2✔
114

115
TEST(Transform_AddTableInOrder)
116
{
2✔
117
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
118
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
119
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
120
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
121

1✔
122
    client_1->create_schema([](WriteTransaction& tr) {
2✔
123
        tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
124
        tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
125
    });
2✔
126

1✔
127
    client_2->create_schema([](WriteTransaction& tr) {
2✔
128
        tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
129
        tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
130
    });
2✔
131

1✔
132
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
133

1✔
134
    ReadTransaction read_server(server->shared_group);
2✔
135
    {
2✔
136
        ReadTransaction read_client_1(client_1->shared_group);
2✔
137
        CHECK(compare_groups(read_server, read_client_1));
2✔
138
    }
2✔
139
    {
2✔
140
        ReadTransaction read_client_2(client_2->shared_group);
2✔
141
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
142
    }
2✔
143
}
2✔
144

145
TEST(Transform_AddTableOutOfOrder)
146
{
2✔
147
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
148
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
149
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
150
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
151

1✔
152
    client_1->create_schema([](WriteTransaction& tr) {
2✔
153
        tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
154
        tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
155
    });
2✔
156

1✔
157
    client_2->create_schema([](WriteTransaction& tr) {
2✔
158
        tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
159
        tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
160
    });
2✔
161

1✔
162
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
163

1✔
164
    ReadTransaction read_server(server->shared_group);
2✔
165
    {
2✔
166
        ReadTransaction read_client_1(client_1->shared_group);
2✔
167
        CHECK(compare_groups(read_server, read_client_1));
2✔
168
    }
2✔
169
    {
2✔
170
        ReadTransaction read_client_2(client_2->shared_group);
2✔
171
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
172
    }
2✔
173
}
2✔
174

175
TEST(Transform_AddColumnsInOrder)
176
{
2✔
177
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
178
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
179
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
180
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
181

1✔
182
    client_1->create_schema([](WriteTransaction& tr) {
2✔
183
        TableRef foo = tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
184
        foo->add_column(type_Int, "foo_col");
2✔
185
        foo->add_column(type_String, "foo_col2");
2✔
186
        TableRef bar = tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
187
        bar->add_column(type_String, "bar_col");
2✔
188
    });
2✔
189

1✔
190
    client_2->create_schema([](WriteTransaction& tr) {
2✔
191
        TableRef foo = tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
192
        foo->add_column(type_Int, "foo_col");
2✔
193
        foo->add_column(type_String, "foo_col2");
2✔
194
        TableRef bar = tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
195
        bar->add_column(type_String, "bar_col");
2✔
196
    });
2✔
197

1✔
198
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
199

1✔
200
    ReadTransaction read_server(server->shared_group);
2✔
201
    {
2✔
202
        ReadTransaction read_client_1(client_1->shared_group);
2✔
203
        CHECK(compare_groups(read_server, read_client_1));
2✔
204
    }
2✔
205
    {
2✔
206
        ReadTransaction read_client_2(client_2->shared_group);
2✔
207
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
208
    }
2✔
209
}
2✔
210

211
TEST(Transform_AddColumnsOutOfOrder)
212
{
2✔
213
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
214
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
215
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
216
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
217

1✔
218
    client_1->create_schema([](WriteTransaction& tr) {
2✔
219
        TableRef bar = tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
220
        bar->add_column(type_String, "bar_col");
2✔
221
        TableRef foo = tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
222
        foo->add_column(type_Int, "foo_int");
2✔
223
        foo->add_column(type_String, "foo_string");
2✔
224
    });
2✔
225

1✔
226
    client_2->create_schema([](WriteTransaction& tr) {
2✔
227
        TableRef foo = tr.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
228
        foo->add_column(type_String, "foo_string");
2✔
229
        foo->add_column(type_Int, "foo_int");
2✔
230
        TableRef bar = tr.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
231
        bar->add_column(type_String, "bar_col");
2✔
232
    });
2✔
233

1✔
234
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
235

1✔
236
    ReadTransaction read_server(server->shared_group);
2✔
237
    ReadTransaction read_client_1(client_1->shared_group);
2✔
238
    ReadTransaction read_client_2(client_2->shared_group);
2✔
239
    CHECK(compare_groups(read_server, read_client_1));
2✔
240
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
241
}
2✔
242

243
TEST(Transform_LinkListSet_vs_MoveLastOver)
244
{
2✔
245
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
246
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
247
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
248
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
249
    auto create_schema = [](WriteTransaction& transaction) {
4✔
250
        TableRef foo = transaction.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
4✔
251
        foo->add_column(type_Int, "i");
4✔
252
        TableRef bar = transaction.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
4✔
253
        bar->add_column_list(*foo, "ll");
4✔
254
    };
4✔
255
    client_1->create_schema(create_schema);
2✔
256
    client_2->create_schema(create_schema);
2✔
257

1✔
258
    client_1->transaction([](Peer& p) {
2✔
259
        p.table("class_foo")->create_object_with_primary_key(1);
2✔
260
        ObjKey foo1 = p.table("class_foo")->create_object_with_primary_key(2).get_key();
2✔
261
        auto ll = p.table("class_bar")->create_object_with_primary_key(1).get_linklist("ll");
2✔
262
        ll.insert(0, foo1);
2✔
263
    });
2✔
264

1✔
265
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
266

1✔
267
    client_2->transaction([](Peer& p) {
2✔
268
        ObjKey foo0 = p.table("class_foo")->begin()->get_key();
2✔
269
        auto ll = p.table("class_bar")->begin()->get_linklist("ll");
2✔
270
        ll.set(0, foo0);
2✔
271
    });
2✔
272

1✔
273
    client_1->transaction([](Peer& p) {
2✔
274
        p.table("class_foo")->remove_object(p.table("class_foo")->begin() + 0);
2✔
275
    });
2✔
276

1✔
277
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
278

1✔
279
    ReadTransaction read_server(server->shared_group);
2✔
280
    ReadTransaction read_client_1(client_1->shared_group);
2✔
281
    ReadTransaction read_client_2(client_2->shared_group);
2✔
282
    CHECK(compare_groups(read_server, read_client_1));
2✔
283
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
284
}
2✔
285

286
TEST(Transform_LinkListInsert_vs_MoveLastOver)
287
{
2✔
288
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
289
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
290
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
291
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
292
    auto create_schema = [](WriteTransaction& transaction) {
4✔
293
        TableRef foo = transaction.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
4✔
294
        foo->add_column(type_Int, "i");
4✔
295
        TableRef bar = transaction.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
4✔
296
        bar->add_column_list(*foo, "ll");
4✔
297
    };
4✔
298
    client_1->create_schema(create_schema);
2✔
299
    client_2->create_schema(create_schema);
2✔
300

1✔
301
    client_1->transaction([](Peer& p) {
2✔
302
        p.table("class_foo")->create_object_with_primary_key(1);
2✔
303
        p.table("class_foo")->create_object_with_primary_key(2);
2✔
304
        p.table("class_bar")->create_object_with_primary_key(1);
2✔
305
        auto ll = p.table("class_bar")->begin()->get_linklist("ll");
2✔
306
    });
2✔
307

1✔
308
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
309

1✔
310
    client_2->transaction([](Peer& p) {
2✔
311
        auto ll = p.table("class_bar")->begin()->get_linklist("ll");
2✔
312
        TableRef target = p.table("class_foo");
2✔
313
        ll.insert(0, target->begin()->get_key());
2✔
314
    });
2✔
315

1✔
316
    client_1->transaction([](Peer& p) {
2✔
317
        p.table("class_foo")->remove_object(p.table("class_foo")->begin() + 0);
2✔
318
    });
2✔
319

1✔
320
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
321

1✔
322
    ReadTransaction read_client_1(client_1->shared_group);
2✔
323
    ReadTransaction read_client_2(client_2->shared_group);
2✔
324
    CHECK(compare_groups(read_client_1, read_client_2));
2✔
325
}
2✔
326

327

328
TEST(Transform_Experiment)
329
{
2✔
330
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
331
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
332
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
333
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
334

1✔
335
    auto schema = [](WriteTransaction& tr) {
4✔
336
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
337
        TableRef t2 = tr.get_group().add_table_with_primary_key("class_t2", type_Int, "id");
4✔
338
        t2->add_column(type_Int, "i");
4✔
339
        t->add_column_list(*t2, "ll");
4✔
340
    };
4✔
341

1✔
342
    client_1->create_schema(schema);
2✔
343
    client_2->create_schema(schema);
2✔
344

1✔
345
    client_1->transaction([&](Peer& c1) {
2✔
346
        TableRef t = c1.table("class_t");
2✔
347
        TableRef t2 = c1.table("class_t2");
2✔
348
        t->create_object_with_primary_key(1);
2✔
349
        t2->create_object_with_primary_key(1);
2✔
350
        t2->create_object_with_primary_key(2);
2✔
351
        (t->begin() + 0)->get_linklist("ll").add(t2->begin()->get_key());
2✔
352
        (t->begin() + 0)->get_linklist("ll").add(t2->begin()->get_key());
2✔
353
    });
2✔
354

1✔
355
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
356

1✔
357
    client_1->transaction([&](Peer& c1) {
2✔
358
        TableRef t = c1.table("class_t");
2✔
359
        TableRef t2 = c1.table("class_t2");
2✔
360
        t2->remove_object(t2->begin() + 1);
2✔
361
        (t->begin() + 0)->get_linklist("ll").add(t2->begin()->get_key());
2✔
362
    });
2✔
363

1✔
364
    client_2->transaction([&](Peer& c2) {
2✔
365
        TableRef t = c2.table("class_t");
2✔
366
        TableRef t2 = c2.table("class_t2");
2✔
367
        (t->begin() + 0)->get_linklist("ll").set(1, (t2->begin() + 1)->get_key());
2✔
368
    });
2✔
369

1✔
370

1✔
371
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
372
    ReadTransaction read_server(server->shared_group);
2✔
373
    ReadTransaction read_client_1(client_1->shared_group);
2✔
374
    ReadTransaction read_client_2(client_2->shared_group);
2✔
375
    CHECK(compare_groups(read_server, read_client_1));
2✔
376
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
377

1✔
378
    CHECK_EQUAL(read_server.get_table("class_t")->size(), 1);
2✔
379
    CHECK_EQUAL(read_server.get_table("class_t")->begin()->get_linklist("ll").size(), 2);
2✔
380
}
2✔
381

382
TEST(Transform_SelectLinkList)
383
{
2✔
384
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
385
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
386
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
387
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
388

1✔
389
    auto schema = [](WriteTransaction& tr) {
4✔
390
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
391
        TableRef t2 = tr.get_group().add_table_with_primary_key("class_t2", type_Int, "id");
4✔
392
        t2->add_column(type_Int, "i");
4✔
393
        t->add_column_list(*t2, "ll");
4✔
394
    };
4✔
395

1✔
396
    client_1->create_schema(schema);
2✔
397
    client_2->create_schema(schema);
2✔
398

1✔
399
    client_1->transaction([&](Peer& c1) {
2✔
400
        c1.table("class_t2")->create_object_with_primary_key(1);
2✔
401
        c1.table("class_t")->create_object_with_primary_key(1);
2✔
402
        c1.table("class_t")->create_object_with_primary_key(2);
2✔
403
    });
2✔
404

1✔
405
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
406

1✔
407
    client_1->transaction([&](Peer& c1) {
2✔
408
        TableRef t = c1.table("class_t");
2✔
409
        TableRef t2 = c1.table("class_t2");
2✔
410
        (t->begin() + 1)->get_linklist("ll").add(t2->begin()->get_key());
2✔
411
        t->remove_object(t->begin() + 0);
2✔
412
    });
2✔
413

1✔
414
    client_2->transaction([&](Peer& c2) {
2✔
415
        TableRef t = c2.table("class_t");
2✔
416
        TableRef t2 = c2.table("class_t2");
2✔
417
        (t->begin() + 1)->get_linklist("ll").add(t2->begin()->get_key());
2✔
418
    });
2✔
419

1✔
420

1✔
421
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
422
    ReadTransaction read_server(server->shared_group);
2✔
423
    ReadTransaction read_client_1(client_1->shared_group);
2✔
424
    ReadTransaction read_client_2(client_2->shared_group);
2✔
425
    CHECK(compare_groups(read_server, read_client_1));
2✔
426
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
427

1✔
428
    CHECK_EQUAL(read_server.get_table("class_t")->size(), 1);
2✔
429
    CHECK_EQUAL(read_server.get_table("class_t")->begin()->get_linklist("ll").size(), 2);
2✔
430
}
2✔
431

432

433
TEST(Transform_InsertRows)
434
{
2✔
435
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
436
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
437
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
438
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
439

1✔
440
    auto schema = [](WriteTransaction& tr) {
4✔
441
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
442
        t->add_column(type_Int, "i");
4✔
443
    };
4✔
444

1✔
445
    client_1->create_schema(schema);
2✔
446
    client_2->create_schema(schema);
2✔
447
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
448

1✔
449
    client_1->start_transaction();
2✔
450
    client_1->table("class_t")->create_object_with_primary_key(1);
2✔
451
    client_1->table("class_t")->begin()->set("i", 123);
2✔
452
    client_1->commit();
2✔
453

1✔
454
    client_2->start_transaction();
2✔
455
    client_2->table("class_t")->create_object_with_primary_key(2);
2✔
456
    client_2->table("class_t")->begin()->set("i", 456);
2✔
457
    client_2->commit();
2✔
458

1✔
459
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
460

1✔
461
    ReadTransaction read_server(server->shared_group);
2✔
462
    ReadTransaction read_client_1(client_1->shared_group);
2✔
463
    ReadTransaction read_client_2(client_2->shared_group);
2✔
464
    CHECK(compare_groups(read_server, read_client_1));
2✔
465
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
466
}
2✔
467

468
TEST(Transform_AdjustSetLinkPayload)
469
{
2✔
470
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
471
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
472
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
473
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
474

1✔
475
    auto schema = [](WriteTransaction& tr) {
4✔
476
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
477
        t->add_column(type_Int, "i");
4✔
478
        TableRef l = tr.get_group().add_table_with_primary_key("class_l", type_Int, "id");
4✔
479
        l->add_column(*t, "l");
4✔
480
    };
4✔
481

1✔
482
    client_1->create_schema(schema);
2✔
483
    client_2->create_schema(schema);
2✔
484
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
485

1✔
486
    client_1->transaction([](Peer& client_1) {
2✔
487
        TableRef t = client_1.table("class_t");
2✔
488
        TableRef l = client_1.table("class_l");
2✔
489
        t->create_object_with_primary_key(1);
2✔
490
        t->begin()->set("i", 123);
2✔
491
        l->create_object_with_primary_key(1);
2✔
492
        l->begin()->set("l", t->begin()->get_key());
2✔
493
    });
2✔
494

1✔
495
    client_2->transaction([](Peer& client_2) {
2✔
496
        TableRef t = client_2.table("class_t");
2✔
497
        TableRef l = client_2.table("class_l");
2✔
498
        t->create_object_with_primary_key(2);
2✔
499
        t->begin()->set("i", 456);
2✔
500
        client_2.table("class_l")->create_object_with_primary_key(2);
2✔
501
        l->begin()->set("l", t->begin()->get_key());
2✔
502
    });
2✔
503

1✔
504
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
505

1✔
506
    ReadTransaction read_server(server->shared_group);
2✔
507
    ReadTransaction read_client_1(client_1->shared_group);
2✔
508
    ReadTransaction read_client_2(client_2->shared_group);
2✔
509
    CHECK(compare_groups(read_server, read_client_1));
2✔
510
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
511

1✔
512
    {
2✔
513
        ConstTableRef t = read_client_1.get_table("class_t");
2✔
514
        ConstTableRef l = read_client_1.get_table("class_l");
2✔
515
        ObjKey link0 = l->begin()->get<ObjKey>("l");
2✔
516
        ObjKey link1 = (l->begin() + 1)->get<ObjKey>("l");
2✔
517
        CHECK_EQUAL(123, t->get_object(link0).get<int64_t>("i"));
2✔
518
        CHECK_EQUAL(456, t->get_object(link1).get<int64_t>("i"));
2✔
519
    }
2✔
520
}
2✔
521

522
TEST(Transform_AdjustLinkListSetPayload)
523
{
2✔
524
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
525
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
526
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
527
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
528

1✔
529
    auto schema = [](WriteTransaction& tr) {
4✔
530
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
531
        t->add_column(type_Int, "i");
4✔
532
        TableRef l = tr.get_group().add_table_with_primary_key("class_ll", type_Int, "id");
4✔
533
        l->add_column_list(*t, "ll");
4✔
534
    };
4✔
535

1✔
536
    client_1->create_schema(schema);
2✔
537
    client_2->create_schema(schema);
2✔
538
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
539

1✔
540
    client_1->transaction([](Peer& client_1) {
2✔
541
        client_1.table("class_t")->create_object_with_primary_key(1).set("i", 123);
2✔
542
        LnkLst ll = client_1.table("class_ll")->create_object_with_primary_key(1).get_linklist("ll");
2✔
543
        ll.add((ll.get_target_table()->begin() + 0)->get_key());
2✔
544
    });
2✔
545

1✔
546
    client_2->transaction([](Peer& client_2) {
2✔
547
        client_2.table("class_t")->create_object_with_primary_key(2).set("i", 456);
2✔
548
        LnkLst ll = client_2.table("class_ll")->create_object_with_primary_key(2).get_linklist("ll");
2✔
549
        ll.add((ll.get_target_table()->begin() + 0)->get_key());
2✔
550
    });
2✔
551

1✔
552
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
553

1✔
554
    ReadTransaction read_server(server->shared_group);
2✔
555
    ReadTransaction read_client_1(client_1->shared_group);
2✔
556
    ReadTransaction read_client_2(client_2->shared_group);
2✔
557
    CHECK(compare_groups(read_server, read_client_1));
2✔
558
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
559

1✔
560
    ConstTableRef client_1_table_link = read_client_1.get_table("class_ll");
2✔
561
    LnkLst ll = client_1_table_link->get_object_with_primary_key(1).get_linklist("ll");
2✔
562
    CHECK_EQUAL(123, ll.get_object(0).get<int64_t>("i"));
2✔
563
    ll = client_1_table_link->get_object_with_primary_key(2).get_linklist("ll");
2✔
564
    CHECK_EQUAL(456, ll.get_object(0).get<int64_t>("i"));
2✔
565
}
2✔
566

567
TEST(Transform_MergeInsertSetAndErase)
568
{
2✔
569
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
570
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
571
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
572
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
573

1✔
574
    auto schema = [](WriteTransaction& tr) {
4✔
575
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
576
        t->add_column(type_Int, "i");
4✔
577
    };
4✔
578

1✔
579
    client_1->create_schema(schema);
2✔
580
    client_2->create_schema(schema);
2✔
581

1✔
582
    client_1->transaction([](Peer& client_1) {
2✔
583
        client_1.table("class_t")->create_object_with_primary_key(1).set("i", 123);
2✔
584
        client_1.table("class_t")->create_object_with_primary_key(2).set("i", 456);
2✔
585
    });
2✔
586
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
587

1✔
588
    client_1->transaction([](Peer& client_1) {
2✔
589
        client_1.table("class_t")->create_object_with_primary_key(3).set("i", 789);
2✔
590
    });
2✔
591

1✔
592
    client_2->transaction([](Peer& client_2) {
2✔
593
        client_2.table("class_t")->remove_object(client_2.table("class_t")->begin());
2✔
594
    });
2✔
595
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
596

1✔
597
    ReadTransaction read_server(server->shared_group);
2✔
598
    ReadTransaction read_client_1(client_1->shared_group);
2✔
599
    ReadTransaction read_client_2(client_2->shared_group);
2✔
600
    CHECK(compare_groups(read_server, read_client_1));
2✔
601
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
602

1✔
603
    {
2✔
604
        ConstTableRef t = read_client_1.get_table("class_t");
2✔
605
        CHECK_EQUAL(2, t->size());
2✔
606
    }
2✔
607
}
2✔
608

609

610
TEST(Transform_MergeSetLinkAndMoveLastOver)
611
{
2✔
612
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
613
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
614
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
615
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
616

1✔
617
    auto schema = [](WriteTransaction& tr) {
4✔
618
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
619
        t->add_column(type_Int, "i");
4✔
620
        TableRef l = tr.get_group().add_table_with_primary_key("class_l", type_Int, "id");
4✔
621
        l->add_column(*t, "l");
4✔
622
    };
4✔
623

1✔
624
    client_1->create_schema(schema);
2✔
625
    client_2->create_schema(schema);
2✔
626
    client_1->transaction([](Peer& client_1) {
2✔
627
        client_1.table("class_t")->create_object_with_primary_key(1).set("i", 123);
2✔
628
    });
2✔
629
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
630

1✔
631
    client_1->transaction([](Peer& client_1) {
2✔
632
        auto k = client_1.table("class_t")->begin()->get_key();
2✔
633
        client_1.table("class_l")->create_object_with_primary_key(1).set("l", k);
2✔
634
    });
2✔
635

1✔
636
    client_2->transaction([](Peer& client_2) {
2✔
637
        client_2.table("class_t")->begin()->remove();
2✔
638
    });
2✔
639
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
640

1✔
641
    ReadTransaction read_server(server->shared_group);
2✔
642
    ReadTransaction read_client_1(client_1->shared_group);
2✔
643
    ReadTransaction read_client_2(client_2->shared_group);
2✔
644
    CHECK(compare_groups(read_server, read_client_1));
2✔
645
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
646

1✔
647
    {
2✔
648
        ConstTableRef t = read_client_1.get_table("class_t");
2✔
649
        CHECK_EQUAL(0, t->size());
2✔
650
        ConstTableRef l = read_client_1.get_table("class_l");
2✔
651
        CHECK_EQUAL(1, l->size());
2✔
652
        ObjKey target_row = l->begin()->get<ObjKey>("l");
2✔
653
        CHECK_NOT(target_row);
2✔
654
    }
2✔
655
}
2✔
656

657

658
TEST(Transform_MergeSetDefault)
659
{
2✔
660
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
661
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
662
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
663
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
664

1✔
665
    auto schema = [](WriteTransaction& tr) {
4✔
666
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "i");
4✔
667
        t->add_column(type_Int, "j");
4✔
668
    };
4✔
669

1✔
670
    client_1->create_schema(schema);
2✔
671
    client_2->create_schema(schema);
2✔
672
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
673

1✔
674
    client_1->transaction([&](Peer& client_1) {
2✔
675
        TableRef t = client_1.table("class_t");
2✔
676
        t->create_object_with_primary_key(123);
2✔
677
        bool is_default = false;
2✔
678
        t->begin()->set("j", 456, is_default);
2✔
679
    });
2✔
680

1✔
681
    // SetDefault at later timestamp.
1✔
682
    client_2->history.advance_time(100);
2✔
683

1✔
684
    client_2->transaction([&](Peer& client_2) {
2✔
685
        TableRef t = client_2.table("class_t");
2✔
686
        t->create_object_with_primary_key(123);
2✔
687
        bool is_default = true;
2✔
688
        t->begin()->set("j", 789, is_default);
2✔
689
    });
2✔
690

1✔
691
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
692

1✔
693

1✔
694
    ReadTransaction read_server(server->shared_group);
2✔
695
    ReadTransaction read_client_1(client_1->shared_group);
2✔
696
    ReadTransaction read_client_2(client_2->shared_group);
2✔
697
    CHECK(compare_groups(read_server, read_client_1));
2✔
698
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
699

1✔
700
    ConstTableRef t = read_client_1.get_table("class_t");
2✔
701
    CHECK_EQUAL(t->size(), 1);
2✔
702
    // Check that the later SetDefault did not overwrite the Set instruction.
1✔
703
    CHECK_EQUAL(t->begin()->get<Int>("j"), 456);
2✔
704
}
2✔
705

706
TEST(Transform_MergeLinkListsWithPrimaryKeys)
707
{
2✔
708
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
709
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
710
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
711
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
712

1✔
713
    auto schema = [](WriteTransaction& tr) {
4✔
714
        TableRef t = tr.get_group().add_table_with_primary_key("class_t", type_Int, "i");
4✔
715
        TableRef t2 = tr.get_group().add_table_with_primary_key("class_t2", type_Int, "id");
4✔
716
        t->add_column(type_String, "s");
4✔
717
        t->add_column_list(*t2, "ll");
4✔
718
        t2->add_column(type_Int, "i2");
4✔
719
    };
4✔
720

1✔
721
    client_1->create_schema(schema);
2✔
722
    client_2->create_schema(schema);
2✔
723
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
724

1✔
725
    client_1->transaction([](Peer& client_1) {
2✔
726
        TableRef t = client_1.table("class_t");
2✔
727
        TableRef t2 = client_1.table("class_t2");
2✔
728
        t->create_object_with_primary_key(123);
2✔
729
        t->begin()->set("s", "a");
2✔
730
        t2->create_object_with_primary_key(1).set("i2", 1);
2✔
731
        (t->begin() + 0)->get_linklist("ll").add(t2->begin()->get_key());
2✔
732
    });
2✔
733

1✔
734
    client_2->history.advance_time(10);
2✔
735

1✔
736
    client_2->transaction([](Peer& client_2) {
2✔
737
        TableRef t = client_2.table("class_t");
2✔
738
        TableRef t2 = client_2.table("class_t2");
2✔
739
        t->create_object_with_primary_key(123);
2✔
740
        t->begin()->set("s", "bb");
2✔
741
        t2->create_object_with_primary_key(2);
2✔
742
        t2->begin()->set("i2", 2);
2✔
743
        auto ll = (t->begin() + 0)->get_linklist("ll");
2✔
744
        ll.add(ll.get_target_table()->begin()->get_key());
2✔
745
        ll.add(ll.get_target_table()->begin()->get_key());
2✔
746
    });
2✔
747

1✔
748
    client_1->history.advance_time(20);
2✔
749

1✔
750
    client_1->transaction([](Peer& client_1) {
2✔
751
        TableRef t = client_1.table("class_t");
2✔
752
        TableRef t2 = client_1.table("class_t2");
2✔
753
        auto k = t2->create_object_with_primary_key(3).set("i2", 3).get_key();
2✔
754
        (t->begin() + 0)->get_linklist("ll").add(k);
2✔
755
    });
2✔
756

1✔
757
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
758

1✔
759
    ReadTransaction read_server(server->shared_group);
2✔
760
    ReadTransaction read_client_1(client_1->shared_group);
2✔
761
    ReadTransaction read_client_2(client_2->shared_group);
2✔
762
    CHECK(compare_groups(read_server, read_client_1));
2✔
763
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
764

1✔
765
    ConstTableRef t = read_client_1.get_table("class_t");
2✔
766
    CHECK_EQUAL(t->size(), 1);
2✔
767
    CHECK_EQUAL(t->begin()->get<StringData>("s"), "bb");
2✔
768
    LnkLst lv = (t->begin() + 0)->get_linklist("ll");
2✔
769
    CHECK_EQUAL(lv.size(), 4);
2✔
770
    CHECK_EQUAL(lv.get_object(0).get<Int>("i2"), 1);
2✔
771
    CHECK_EQUAL(lv.get_object(1).get<Int>("i2"), 2);
2✔
772
    CHECK_EQUAL(lv.get_object(3).get<Int>("i2"), 3);
2✔
773
}
2✔
774

775
TEST(Transform_AddInteger)
776
{
2✔
777

1✔
778
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
779
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
780
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
781
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
782

1✔
783
    auto schema = [](WriteTransaction& tr) {
4✔
784
        TableRef t1 = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
785
        t1->add_column(type_Int, "i");
4✔
786
    };
4✔
787

1✔
788
    client_1->create_schema(schema);
2✔
789
    client_2->create_schema(schema);
2✔
790

1✔
791
    client_1->transaction([](Peer& client_1) {
2✔
792
        client_1.table("class_t")->create_object_with_primary_key(1);
2✔
793
    });
2✔
794
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
795

1✔
796
    client_1->transaction([](Peer& client_1) {
2✔
797
        client_1.table("class_t")->begin()->add_int("i", 5);
2✔
798
    });
2✔
799
    client_2->transaction([](Peer& client_2) {
2✔
800
        client_2.table("class_t")->begin()->add_int("i", 4);
2✔
801
    });
2✔
802

1✔
803
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
804

1✔
805
    {
2✔
806
        ReadTransaction read_server(server->shared_group);
2✔
807
        ReadTransaction read_client_1(client_1->shared_group);
2✔
808
        ReadTransaction read_client_2(client_2->shared_group);
2✔
809
        CHECK_EQUAL(read_server.get_table("class_t")->begin()->get<Int>("i"), 9);
2✔
810
        CHECK(compare_groups(read_server, read_client_1));
2✔
811
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
812
    }
2✔
813

1✔
814
    client_2->history.advance_time(0);
2✔
815
    client_2->transaction([](Peer& client_2) {
2✔
816
        client_2.table("class_t")->begin()->add_int("i", 2); // This ends up being discarded
2✔
817
    });
2✔
818

1✔
819
    client_1->history.advance_time(10);
2✔
820
    client_1->transaction([](Peer& client_1) {
2✔
821
        client_1.table("class_t")->begin()->set("i", 100);
2✔
822
    });
2✔
823

1✔
824
    client_2->history.advance_time(20);
2✔
825
    client_2->transaction([](Peer& client_2) {
2✔
826
        client_2.table("class_t")->begin()->add_int("i", 3); // This comes after the set on client_1, so comes in
2✔
827
    });
2✔
828

1✔
829
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
830
    {
2✔
831
        ReadTransaction read_server(server->shared_group);
2✔
832
        ReadTransaction read_client_1(client_1->shared_group);
2✔
833
        ReadTransaction read_client_2(client_2->shared_group);
2✔
834
        CHECK_EQUAL(read_server.get_table("class_t")->begin()->get<Int>("i"), 103);
2✔
835
        CHECK(compare_groups(read_server, read_client_1));
2✔
836
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
837
    }
2✔
838
}
2✔
839

840
TEST(Transform_AddIntegerSetNull)
841
{
2✔
842

1✔
843
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
844
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
845
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
846
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
847

1✔
848
    auto schema = [](WriteTransaction& tr) {
4✔
849
        TableRef t1 = tr.get_group().add_table_with_primary_key("class_t", type_Int, "id");
4✔
850
        bool nullable = true;
4✔
851
        t1->add_column(type_Int, "i", nullable);
4✔
852
    };
4✔
853

1✔
854
    client_1->create_schema(schema);
2✔
855
    client_2->create_schema(schema);
2✔
856

1✔
857
    client_1->transaction([](Peer& client_1) {
2✔
858
        client_1.table("class_t")->create_object_with_primary_key(1);
2✔
859
    });
2✔
860
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
861

1✔
862
    client_1->transaction([](Peer& client_1) {
2✔
863
        client_1.table("class_t")->begin()->set("i", 0);
2✔
864
    });
2✔
865

1✔
866
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
867

1✔
868
    client_2->history.advance_time(0);
2✔
869
    client_2->transaction([](Peer& client_2) {
2✔
870
        client_2.table("class_t")->begin()->add_int("i", 2); // This ends up being discarded
2✔
871
    });
2✔
872

1✔
873
    client_1->history.advance_time(10);
2✔
874
    client_1->transaction([](Peer& client_1) {
2✔
875
        client_1.table("class_t")->begin()->set_null("i");
2✔
876
    });
2✔
877

1✔
878
    client_2->history.advance_time(20);
2✔
879
    client_2->transaction([](Peer& client_2) {
2✔
880
        client_2.table("class_t")->begin()->add_int("i", 3); // This ends up being discarded
2✔
881
    });
2✔
882

1✔
883
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
884
    {
2✔
885
        ReadTransaction read_server(server->shared_group);
2✔
886
        ReadTransaction read_client_1(client_1->shared_group);
2✔
887
        ReadTransaction read_client_2(client_2->shared_group);
2✔
888
        CHECK(read_server.get_table("class_t")->begin()->is_null("i"));
2✔
889
        CHECK(compare_groups(read_server, read_client_1));
2✔
890
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
891
    }
2✔
892
}
2✔
893

894

895
TEST(Transform_EraseSelectedLinkView)
896
{
2✔
897
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
898
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
899
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
900
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
901

1✔
902
    auto init = [](WriteTransaction& tr) {
2✔
903
        TableRef origin = tr.get_group().add_table_with_primary_key("class_origin", type_Int, "id");
2✔
904
        TableRef target = tr.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
905
        origin->add_column_list(*target, "ll");
2✔
906
        target->add_column(type_Int, "");
2✔
907
        origin->create_object_with_primary_key(1);
2✔
908
        origin->create_object_with_primary_key(2);
2✔
909
        target->create_object_with_primary_key(3);
2✔
910
        target->create_object_with_primary_key(4);
2✔
911
        target->create_object_with_primary_key(5);
2✔
912
        target->create_object_with_primary_key(6);
2✔
913
        target->create_object_with_primary_key(7);
2✔
914
        target->create_object_with_primary_key(8);
2✔
915
        LnkLst link_list = (origin->begin() + 1)->get_linklist("ll");
2✔
916
        link_list.add(target->begin()->get_key());
2✔
917
        link_list.add((target->begin() + 1)->get_key());
2✔
918
    };
2✔
919

1✔
920
    client_1->create_schema(init);
2✔
921
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
922

1✔
923
    client_1->history.set_time(1);
2✔
924
    client_2->history.set_time(2);
2✔
925

1✔
926
    auto transact_1 = [](WriteTransaction& tr) {
2✔
927
        TableRef origin = tr.get_table("class_origin");
2✔
928
        LnkLst link_list = (origin->begin() + 1)->get_linklist("ll");
2✔
929
        auto target_table = link_list.get_target_table();
2✔
930
        link_list.set(0, target_table->get_object(2).get_key()); // Select the link list of the 2nd row
2✔
931
        origin->remove_object(origin->begin() + 0);     // Move that link list
2✔
932
        if (link_list.size() > 1) {
2✔
933
            link_list.set(1, target_table->get_object(3).get_key()); // Now modify it again
2✔
934
        }
2✔
935
    };
2✔
936
    auto transact_2 = [](WriteTransaction& tr) {
2✔
937
        TableRef origin = tr.get_table("class_origin");
2✔
938
        LnkLst link_list = (origin->begin() + 1)->get_linklist("ll");
2✔
939
        auto target_table = link_list.get_target_table();
2✔
940
        if (link_list.size() > 1) {
2✔
941
            link_list.set(0, target_table->get_object(4).get_key()); // Select the link list of the 2nd row
2✔
942
            link_list.set(1, target_table->get_object(5).get_key()); // Now modify it again
2✔
943
        }
2✔
944
    };
2✔
945

1✔
946
    client_1->create_schema(transact_1);
2✔
947
    client_2->create_schema(transact_2);
2✔
948
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
949

1✔
950
    {
2✔
951
        ReadTransaction read_server(server->shared_group);
2✔
952
        ReadTransaction read_client_1(client_1->shared_group);
2✔
953
        ReadTransaction read_client_2(client_2->shared_group);
2✔
954
        CHECK(compare_groups(read_server, read_client_1));
2✔
955
        CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
2✔
956

1✔
957
        ConstTableRef origin = read_server.get_table("class_origin");
2✔
958
        ConstTableRef target = read_server.get_table("class_target");
2✔
959
        CHECK_EQUAL(1, origin->size());
2✔
960
        LnkLst link_list = (origin->begin() + 0)->get_linklist("ll");
2✔
961
        CHECK_EQUAL(2, link_list.size());
2✔
962
        CHECK_EQUAL((target->begin() + 4)->get_key(), link_list.get(0));
2✔
963
        CHECK_EQUAL((target->begin() + 5)->get_key(), link_list.get(1));
2✔
964
    }
2✔
965
}
2✔
966

967
// this test can take upwards of an hour if sync to disk is enabled
968
TEST_IF(Transform_Randomized, get_disable_sync_to_disk())
969
{
2✔
970
    const char* trace_p = ::getenv("UNITTEST_RANDOMIZED_TRACE");
2✔
971
    bool trace = trace_p && (StringData{trace_p} != "no");
2!
972

1✔
973
    // FIXME: Unfortunately these rounds are terribly slow, presumable due to
1✔
974
    // sync-to-disk. Can we use "in memory" mode too boost them?
1✔
975
    int num_major_rounds = 100;
2✔
976
    int num_minor_rounds = 1;
2✔
977

1✔
978
    Random random(unit_test_random_seed); // Seed from slow global generator
2✔
979
    FuzzTester<Random> randomized(random, trace);
2✔
980

1✔
981
    for (int major_round = 0; major_round < num_major_rounds; ++major_round) {
202✔
982
        for (int minor_round = 0; minor_round < num_minor_rounds; ++minor_round) {
400✔
983
            if (trace)
200✔
984
                std::cerr << "---------------\n";
×
985
            randomized.round(test_context);
200✔
986
        }
200✔
987
        if (trace)
200✔
988
            std::cerr << "Round " << (major_round + 1) << "\n";
×
989
    }
200✔
990
}
2✔
991

992
namespace {
993

994
void integrate_changesets(Peer* peer_to, Peer* peer_from)
995
{
×
996
    size_t n = peer_to->count_outstanding_changesets_from(*peer_from);
×
997
    for (size_t i = 0; i < n; ++i)
×
998
        peer_to->integrate_next_changeset_from(*peer_from);
×
999
}
×
1000

1001

1002
/// time_two_clients times the integration of change sets between two clients and a server.
1003
/// The two clients create the same schema indepedently at start up and sync with the server.
1004
/// The schema contains one table if same_table is true, and two tables if same_table is false.
1005
/// The tables are given one integer column each. The clients insert nrows_1 and nrows_2 empty rows respectively in
1006
/// their table. If fill_rows is true, the clients insert a value in each row.
1007
/// If \a one_change_set is true, the clients insert all rows within one transaction. Otherwise
1008
/// each row in inserted in its own transaction which will lead to one change set for each instruction.
1009
/// The synchronization between the clients and the server progresses in steps:
1010
/// The server integrates the change sets from client_1.
1011
/// The server integrates the change sets from client_2. This is one of the two slow processes.
1012
/// Client_1 integrates the new change sets from the server.
1013
/// Client_2 integates the change sets from the server. This is the second slow process.
1014
/// The function returns the tuple of durations (duration_server, duration_client_1, duration_client_2).
1015
/// Durations are measured in milliseconds.
1016
std::tuple<double, double, double> timer_two_clients(TestContext& test_context, const std::string path_add_on,
1017
                                                     int nrows_1, int nrows_2, bool same_table, bool fill_rows,
1018
                                                     bool one_change_set)
1019
{
×
1020
    std::string table_name_1 = "class_table_name_1";
×
1021
    std::string table_name_2 = same_table ? table_name_1 : "class_table_name_2";
×
1022

×
1023
    // We don't bother dumping the changesets generated by the performance tests because they aren't
×
1024
    // exercising any complex behavior of the merge rules.
×
1025
    auto server = Peer::create_server(test_context, nullptr, path_add_on);
×
1026
    auto client_1 = Peer::create_client(test_context, 2, nullptr, path_add_on);
×
1027
    auto client_2 = Peer::create_client(test_context, 3, nullptr, path_add_on);
×
1028

×
1029
    client_1->create_schema([&](WriteTransaction& tr) {
×
1030
        TableRef table = tr.get_or_add_table(table_name_1);
×
1031
        table->add_column(type_Int, "int column");
×
1032
    });
×
1033

×
1034
    client_2->create_schema([&](WriteTransaction& tr) {
×
1035
        TableRef table = tr.get_or_add_table(table_name_2);
×
1036
        table->add_column(type_Int, "int column");
×
1037
    });
×
1038

×
1039
    synchronize(server.get(), {client_1.get(), client_2.get()});
×
1040

×
1041
    int id = 0;
×
1042

×
1043
    if (one_change_set)
×
1044
        client_1->start_transaction();
×
1045
    for (int i = 0; i < nrows_1; ++i) {
×
1046
        if (!one_change_set)
×
1047
            client_1->start_transaction();
×
1048
        Obj obj = client_1->table(table_name_1)->create_object_with_primary_key(id++);
×
1049
        if (fill_rows)
×
1050
            obj.set("int column", 10 * i + 1);
×
1051
        if (!one_change_set)
×
1052
            client_1->commit();
×
1053
    }
×
1054
    if (one_change_set)
×
1055
        client_1->commit();
×
1056

×
1057
    integrate_changesets(server.get(), client_1.get());
×
1058

×
1059
    if (one_change_set)
×
1060
        client_2->start_transaction();
×
1061
    for (int i = 0; i < nrows_2; ++i) {
×
1062
        if (!one_change_set)
×
1063
            client_2->start_transaction();
×
1064
        Obj obj = client_2->table(table_name_2)->create_object_with_primary_key(id++);
×
1065
        if (fill_rows)
×
1066
            obj.set("int column", 10 * i + 2);
×
1067
        if (!one_change_set)
×
1068
            client_2->commit();
×
1069
    }
×
1070
    if (one_change_set)
×
1071
        client_2->commit();
×
1072

×
1073
    // Timing the server integrating instructions from client_2.
×
1074
    // This integration can suffer from the quadratic problem.
×
1075
    auto time_start_server = std::chrono::high_resolution_clock::now();
×
1076
    integrate_changesets(server.get(), client_2.get());
×
1077
    auto time_end_server = std::chrono::high_resolution_clock::now();
×
1078
    auto duration_server =
×
1079
        std::chrono::duration_cast<std::chrono::milliseconds>(time_end_server - time_start_server).count();
×
1080

×
1081
    // Timing client_1 integrating change sets from the server.
×
1082
    // This integration never suffers from the quadratic problem.
×
1083
    auto time_start_client_1 = std::chrono::high_resolution_clock::now();
×
1084
    integrate_changesets(client_1.get(), server.get());
×
1085
    auto time_end_client_1 = std::chrono::high_resolution_clock::now();
×
1086
    auto duration_client_1 =
×
1087
        std::chrono::duration_cast<std::chrono::milliseconds>(time_end_client_1 - time_start_client_1).count();
×
1088

×
1089
    // Timing client_1 integrating instructions from the server.
×
1090
    // This integration can suffer from the quadratic problem.
×
1091
    // In cases where the quadratic factor dominates the timing, this duration
×
1092
    // is expected to be similar to the duration od the server above.
×
1093
    auto time_start_client_2 = std::chrono::high_resolution_clock::now();
×
1094
    integrate_changesets(client_2.get(), server.get());
×
1095
    auto time_end_client_2 = std::chrono::high_resolution_clock::now();
×
1096
    auto duration_client_2 =
×
1097
        std::chrono::duration_cast<std::chrono::milliseconds>(time_end_client_2 - time_start_client_2).count();
×
1098

×
1099

×
1100
    // Check that the server and clients are synchronized
×
1101
    ReadTransaction read_server(server->shared_group);
×
1102
    ReadTransaction read_client_1(client_1->shared_group);
×
1103
    ReadTransaction read_client_2(client_2->shared_group);
×
1104
    CHECK(compare_groups(read_server, read_client_1));
×
NEW
1105
    CHECK(compare_groups(read_server, read_client_2, *test_context.logger));
×
1106

×
1107
    return std::make_tuple(double(duration_server), double(duration_client_1), double(duration_client_2));
×
1108
}
×
1109

1110

1111
/// timer_multi_clients handles \a nclients clients and one server.
1112
/// First, the clients create the same schema and synchronizes it with the server.
1113
/// Each of the clients insert \a nrows empty rows in their Realm.
1114
/// Next everything is synced across all peers.
1115
/// The return value is the duration of the server computation in milliseconds.
1116
double timer_multi_clients(TestContext& test_context, const std::string path_add_on, int nclients, int nrows)
1117
{
×
1118
    std::string table_name = "class_table_name";
×
1119

×
1120
    // We don't bother dumping the changesets generated by the performance tests because they aren't
×
1121
    // exercising any complex behavior of the merge rules.
×
1122
    auto server = Peer::create_server(test_context, nullptr, path_add_on);
×
1123
    std::vector<std::unique_ptr<Peer>> clients;
×
1124
    for (int i = 0; i < nclients; ++i)
×
1125
        clients.push_back(Peer::create_client(test_context, i + 2, nullptr, path_add_on));
×
1126

×
1127
    for (int i = 0; i < nclients; ++i) {
×
1128
        clients[i]->create_schema([&](WriteTransaction& tr) {
×
1129
            TableRef table = tr.get_or_add_table(table_name);
×
1130
            table->add_column(type_Int, "int column");
×
1131
        });
×
1132
        integrate_changesets(server.get(), clients[i].get());
×
1133
    }
×
1134

×
1135
    for (int i = 0; i < nclients; ++i) {
×
1136
        integrate_changesets(clients[i].get(), server.get());
×
1137
    }
×
1138

×
1139
    int id = 0;
×
1140
    // Fill the clients with nrows empty rows
×
1141
    for (int i = 0; i < nclients; ++i) {
×
1142
        std::unique_ptr<Peer>& client = clients[i];
×
1143
        client->start_transaction();
×
1144
        for (int i = 0; i < nrows; ++i)
×
1145
            client->table(table_name)->create_object_with_primary_key(id++);
×
1146
        client->commit();
×
1147
    }
×
1148

×
1149
    auto time_start = std::chrono::high_resolution_clock::now();
×
1150

×
1151
    // The server integrates all change sets from the clients.
×
1152
    for (int i = 0; i < nclients; ++i)
×
1153
        integrate_changesets(server.get(), clients[i].get());
×
1154

×
1155
    auto time_end = std::chrono::high_resolution_clock::now();
×
1156
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(time_end - time_start).count();
×
1157

×
1158
    // The clients integrate the change sets from the server.
×
1159
    // Each client obtains the change sets from all other clients
×
1160
    for (int i = 0; i < nclients; ++i)
×
1161
        integrate_changesets(clients[i].get(), server.get());
×
1162

×
1163
    // Check that the server and clients are synchronized
×
1164
    ReadTransaction read_server(server->shared_group);
×
1165
    for (int i = 0; i < nclients; ++i) {
×
1166
        ReadTransaction read_client(clients[i]->shared_group);
×
1167
        CHECK(compare_groups(read_server, read_client));
×
1168
    }
×
1169

×
1170
    return double(duration);
×
1171
}
×
1172

1173

1174
/// Timing of the server integrating change sets.
1175
/// The server is first populated with \a n_change_sets_server change sets
1176
/// where each change set consists of \a n_instr_server instructions.
1177
/// A client generates \a n_change_sets_client change sets each containing
1178
/// \a n_instr_client instructions.
1179
/// All instructions are insert_empty_row in the same table.
1180
/// The funtions returns the time it take the server to integrate the incoming
1181
/// \a n_change_sets_client change sets.
1182
/// The incoming change sets are causally independent of the ones residing on the server.
1183
double timer_integrate_change_sets(TestContext& test_context, const std::string path_add_on,
1184
                                   uint_fast64_t n_change_sets_server, uint_fast64_t n_instr_server,
1185
                                   uint_fast64_t n_change_sets_client, uint_fast64_t n_instr_client)
1186
{
×
1187
    std::string table_name = "class_table_name";
×
1188

×
1189
    // We don't bother dumping the changesets generated by the performance tests because they aren't
×
1190
    // exercising any complex behavior of the merge rules.
×
1191
    auto server = Peer::create_server(test_context, nullptr, path_add_on);
×
1192
    auto client_1 = Peer::create_client(test_context, 2, nullptr, path_add_on);
×
1193
    auto client_2 = Peer::create_client(test_context, 3, nullptr, path_add_on);
×
1194

×
1195
    client_1->create_schema([&](WriteTransaction& tr) {
×
1196
        TableRef table = tr.get_or_add_table(table_name);
×
1197
        table->add_column(type_Int, "int column");
×
1198
    });
×
1199

×
1200
    synchronize(server.get(), {client_1.get(), client_2.get()});
×
1201

×
1202
    int id = 0;
×
1203
    for (uint_fast64_t i = 0; i < n_change_sets_server; ++i) {
×
1204
        client_1->start_transaction();
×
1205
        for (uint_fast64_t j = 0; j < n_instr_server; ++j) {
×
1206
            client_1->table(table_name)->create_object_with_primary_key(id++);
×
1207
        }
×
1208
        client_1->commit();
×
1209
    }
×
1210

×
1211
    integrate_changesets(server.get(), client_1.get());
×
1212

×
1213
    for (uint_fast64_t i = 0; i < n_change_sets_client; ++i) {
×
1214
        client_2->start_transaction();
×
1215
        for (uint_fast64_t j = 0; j < n_instr_client; ++j) {
×
1216
            client_2->table(table_name)->create_object_with_primary_key(id++);
×
1217
        }
×
1218
        client_2->commit();
×
1219
    }
×
1220

×
1221
    auto time_start_server = std::chrono::high_resolution_clock::now();
×
1222
    integrate_changesets(server.get(), client_2.get());
×
1223
    auto time_end_server = std::chrono::high_resolution_clock::now();
×
1224
    auto duration_server =
×
1225
        std::chrono::duration_cast<std::chrono::milliseconds>(time_end_server - time_start_server).count();
×
1226

×
1227
    return double(duration_server);
×
1228
}
×
1229

1230

1231
void run_timer_two_clients(TestContext& test_context, std::string title, bool same_table, bool fill_rows,
1232
                           bool one_change_set,
1233
                           int max_single,  // The maximum number of rows
1234
                           int max_product, // The maximum of a product of rows
1235
                           std::ostream& out)
1236
{
×
1237
    out << std::endl << title << std::endl;
×
1238
    out << "nrows_1\tnrows_2\tduration server\tduration client 1\tduration client 2" << std::endl;
×
1239

×
1240
    for (int nrows_1 = 1; nrows_1 <= max_single; nrows_1 *= 10) {
×
1241
        for (int nrows_2 = 1; nrows_2 <= max_single && nrows_1 * nrows_2 <= max_product; nrows_2 *= 10) {
×
1242
            std::cout << nrows_1 << ", " << nrows_2 << std::endl;
×
1243
            std::string path_add_on = title + "_" + std::to_string(nrows_1) + "_" + std::to_string(nrows_2);
×
1244
            double duration_server, duration_client_1, duration_client_2;
×
1245
            std::tie(duration_server, duration_client_1, duration_client_2) =
×
1246
                timer_two_clients(test_context, path_add_on, nrows_1, nrows_2, same_table, fill_rows, one_change_set);
×
1247
            out << nrows_1 << "\t" << nrows_2 << "\t" << duration_server << "\t" << duration_client_1 << "\t"
×
1248
                << duration_client_2 << std::endl;
×
1249
        }
×
1250
    }
×
1251

×
1252
    out << std::endl << std::endl;
×
1253
}
×
1254

1255

1256
void run_timer_two_clients_different_tables_empty_rows_one_change_set(TestContext& test_context, std::ostream& out)
1257
{
×
1258
    std::string title = "Two clients, different tables, empty rows, one change set";
×
1259
    int max_single = int(1e6);
×
1260
    int max_product = int(1e9);
×
1261
    bool same_table = false;
×
1262
    bool fill_rows = false;
×
1263
    bool one_change_set = true;
×
1264
    run_timer_two_clients(test_context, title, same_table, fill_rows, one_change_set, max_single, max_product, out);
×
1265
}
×
1266

1267

1268
void run_timer_two_clients_different_tables_empty_rows_many_change_sets(TestContext& test_context, std::ostream& out)
1269
{
×
1270
    std::string title = "Two clients, different tables, empty rows, many change sets";
×
1271
    int max_single = int(1e5);
×
1272
    int max_product = int(1e8);
×
1273
    bool same_table = false;
×
1274
    bool fill_rows = false;
×
1275
    bool one_change_set = false;
×
1276
    run_timer_two_clients(test_context, title, same_table, fill_rows, one_change_set, max_single, max_product, out);
×
1277
}
×
1278

1279

1280
void run_timer_two_clients_same_table_filled_rows_one_change_set(TestContext& test_context, std::ostream& out)
1281
{
×
1282
    std::string title = "Two clients, same table, filled rows, one change set";
×
1283
    int max_single = int(1e6);
×
1284
    int max_product = int(1e8);
×
1285
    bool same_table = true;
×
1286
    bool fill_rows = true;
×
1287
    bool one_change_set = true;
×
1288
    run_timer_two_clients(test_context, title, same_table, fill_rows, one_change_set, max_single, max_product, out);
×
1289
}
×
1290

1291

1292
void run_timer_two_clients_same_table_filled_rows_many_change_sets(TestContext& test_context, std::ostream& out)
1293
{
×
1294
    std::string title = "Two clients, same table, filled rows, many change sets";
×
1295
    int max_single = int(1e5);
×
1296
    int max_product = int(1e8);
×
1297
    bool same_table = true;
×
1298
    bool fill_rows = true;
×
1299
    bool one_change_set = false;
×
1300
    run_timer_two_clients(test_context, title, same_table, fill_rows, one_change_set, max_single, max_product, out);
×
1301
}
×
1302

1303

1304
void run_timer_many_clients_same_table_empty_rows(TestContext& test_context, std::ostream& out)
1305
{
×
1306
    out << "Many clients, same table, empty rows" << std::endl;
×
1307
    out << "nclients\tnrows\tduration" << std::endl;
×
1308

×
1309
    int max_clients = 16;
×
1310
    uint_fast64_t max_product = uint_fast64_t(1e11);
×
1311
    for (int nclients = 1; nclients <= max_clients; nclients *= 2) {
×
1312
        for (uint_fast64_t nrows = 1; nclients * nclients * nrows * nrows <= max_product; nrows *= 10) {
×
1313
            std::string path_add_on = "many_clients_" + std::to_string(nclients) + "_" + std::to_string(nrows);
×
1314
            double duration = timer_multi_clients(test_context, path_add_on, nclients, int(nrows));
×
1315
            out << nclients << "\t" << nrows << "\t" << duration << std::endl;
×
1316
        }
×
1317
    }
×
1318

×
1319
    out << std::endl << std::endl;
×
1320
}
×
1321

1322

1323
void report_integrate_change_sets(TestContext& test_context, uint_fast64_t n_change_sets_server,
1324
                                  uint_fast64_t n_instr_server, uint_fast64_t n_change_sets_client,
1325
                                  uint_fast64_t n_instr_client, std::ostream& out)
1326
{
×
1327
    std::string path_add_on = "integrate_change_sets_" + std::to_string(n_change_sets_server) + "_" +
×
1328
                              std::to_string(n_instr_server) + "_" + std::to_string(n_change_sets_client) + "_" +
×
1329
                              std::to_string(n_instr_client);
×
1330

×
1331
    double duration = timer_integrate_change_sets(test_context, path_add_on, n_change_sets_server, n_instr_server,
×
1332
                                                  n_change_sets_client, n_instr_client);
×
1333

×
1334
    uint_fast64_t n_merges = n_change_sets_server * n_instr_server * n_change_sets_client * n_instr_client;
×
1335

×
1336
    out << n_change_sets_server << "\t" << n_instr_server << "\t";
×
1337
    out << n_change_sets_client << "\t" << n_instr_client << "\t";
×
1338
    out << duration << "\t" << (n_merges / duration) << "\t" << (n_change_sets_client / duration) << std::endl;
×
1339
}
×
1340

1341
/// This function can be used interactively to generate output for various combinations of parameters to
1342
/// the report_integrate_change_sets function.
1343
void run_timer_integrate_change_sets(TestContext& test_context, std::ostream& out)
1344
{
×
1345
    out << "integrate change sets of variable number of instructions" << std::endl;
×
1346
    out << "n_change_sets_server\tn_instr_server\tn_change_sets_client\tn_instr_client\tduration in ms\t";
×
1347
    out << "number of merges per ms\tnumber of integrated change sets per ms" << std::endl;
×
1348

×
1349
    uint_fast64_t n_change_sets_server = 1;
×
1350
    uint_fast64_t n_instr_server = 1;
×
1351
    uint_fast64_t n_change_sets_client = 1;
×
1352
    uint_fast64_t n_instr_client = 1;
×
1353

×
1354
    //    for (n_change_sets_client = 1; n_change_sets_client < 1e6; n_change_sets_client *= 10)
×
1355
    //        report_integrate_change_sets(test_context, n_change_sets_server, n_instr_server, n_change_sets_client,
×
1356
    //        n_instr_client, out);
×
1357

×
1358
    //    for (n_instr_client = 1; n_change_sets_client * n_instr_client <= 1e7; n_instr_client *= 10)
×
1359
    //        report_integrate_change_sets(test_context, n_change_sets_server, n_instr_server, n_change_sets_client,
×
1360
    //        n_instr_client, out);
×
1361

×
1362
    //    for (n_change_sets_server = 1; n_change_sets_server < 1e5; n_change_sets_server *= 2)
×
1363
    //        report_integrate_change_sets(test_context, n_change_sets_server, n_instr_server, n_change_sets_client,
×
1364
    //        n_instr_client, out);
×
1365

×
1366
    for (n_instr_server = 100; n_instr_server <= uint_fast64_t(1e8); n_instr_server *= 10) {
×
1367
        n_instr_client = uint_fast64_t(1e8) / n_instr_server;
×
1368
        report_integrate_change_sets(test_context, n_change_sets_server, n_instr_server, n_change_sets_client,
×
1369
                                     n_instr_client, out);
×
1370
    }
×
1371

×
1372
    out << std::endl << std::endl;
×
1373
}
×
1374

1375

1376
void run_all_timers(TestContext& test_context, const std::string path)
1377
{
×
1378
    std::ofstream out{path, std::ios_base::app};
×
1379

×
1380
    run_timer_two_clients_different_tables_empty_rows_one_change_set(test_context, out);
×
1381
    run_timer_two_clients_different_tables_empty_rows_many_change_sets(test_context, out);
×
1382
    run_timer_two_clients_same_table_filled_rows_one_change_set(test_context, out);
×
1383
    run_timer_two_clients_same_table_filled_rows_many_change_sets(test_context, out);
×
1384
    run_timer_many_clients_same_table_empty_rows(test_context, out);
×
1385
    run_timer_integrate_change_sets(test_context, out);
×
1386

×
1387
    out << std::endl;
×
1388
}
×
1389

1390

1391
} // End anonymous namespace
1392

1393

1394
// This TEST is a benchmark that is placed here because it needs the machinery from this file.
1395
// This benchmark should be moved when and if Sync gets a formal benchmarking system.
1396
// This TEST should be disabled in normal unit testing.
1397
// FIXME: Move this benchmark to a benchmark suite.
1398
TEST(TransformTimer)
1399
{
2✔
1400
    const std::string path_of_performance_csv_file = "../../sync_performance_numbers.csv";
2✔
1401

1✔
1402
    // This should normally be false to avoid running the performance benchmark at every run of unit tests.
1✔
1403
    const bool should_performance_test_be_run = false;
2✔
1404

1✔
1405
    if (should_performance_test_be_run)
2✔
1406
        run_all_timers(test_context, path_of_performance_csv_file);
×
1407
}
2✔
1408

1409

1410
TEST(Transform_ErrorCase_LinkListDoubleMerge)
1411
{
2✔
1412
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1413
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
1414
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
1415
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
1416

1✔
1417
    client_1->transaction([](Peer& c) {
2✔
1418
        TableRef a = c.group->add_table_with_primary_key("class_a", type_Int, "pk");
2✔
1419
        TableRef b = c.group->add_table_with_primary_key("class_b", type_Int, "pk");
2✔
1420
        a->add_column_list(*b, "ll");
2✔
1421
        Obj a_obj = a->create_object_with_primary_key(123);
2✔
1422
        Obj b_obj = b->create_object_with_primary_key(456);
2✔
1423
        a_obj.get_linklist("ll").add(b_obj.get_key());
2✔
1424
    });
2✔
1425

1✔
1426
    client_2->transaction([](Peer& c) {
2✔
1427
        TableRef a = c.group->add_table_with_primary_key("class_a", type_Int, "pk");
2✔
1428
        TableRef b = c.group->add_table_with_primary_key("class_b", type_Int, "pk");
2✔
1429
        a->add_column_list(*b, "ll");
2✔
1430
        Obj a_obj = a->create_object_with_primary_key(123);
2✔
1431
        Obj b_obj = b->create_object_with_primary_key(456);
2✔
1432
        a_obj.get_linklist("ll").add(b_obj.get_key());
2✔
1433
    });
2✔
1434

1✔
1435
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
1436
    ReadTransaction rt_0(server->shared_group);
2✔
1437
    ReadTransaction rt_1(client_1->shared_group);
2✔
1438
    ReadTransaction rt_2(client_2->shared_group);
2✔
1439
    CHECK(compare_groups(rt_0, rt_1));
2✔
1440
    CHECK(compare_groups(rt_0, rt_2));
2✔
1441
    CHECK_EQUAL(rt_1.get_table("class_a")->begin()->get_linklist("ll").size(), 2);
2✔
1442
}
2✔
1443

1444
TEST(Transform_ArrayInsert_EraseObject)
1445
{
2✔
1446
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1447
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
1448
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
1449
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
1450
    ObjKey k0, k1;
2✔
1451

1✔
1452
    client_1->transaction([&k0, &k1](Peer& c) {
2✔
1453
        TableRef source = c.group->add_table_with_primary_key("class_source", type_Int, "id");
2✔
1454
        TableRef target = c.group->add_table_with_primary_key("class_target", type_Int, "id");
2✔
1455
        source->add_column_list(*target, "ll");
2✔
1456
        source->create_object_with_primary_key(1);
2✔
1457
        k0 = target->create_object_with_primary_key(0).get_key();
2✔
1458
        k1 = target->create_object_with_primary_key(1).get_key();
2✔
1459
    });
2✔
1460

1✔
1461
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
1462

1✔
1463
    client_1->transaction([k0, k1](Peer& c) {
2✔
1464
        TableRef source = c.table("class_source");
2✔
1465
        REALM_ASSERT(source);
2✔
1466
        TableRef target = c.table("class_target");
2✔
1467
        REALM_ASSERT(target);
2✔
1468
        auto ll = source->begin()->get_linklist(source->get_column_key("ll"));
2✔
1469
        ll.add(k0);
2✔
1470
        ll.add(k1);
2✔
1471
    });
2✔
1472

1✔
1473
    synchronize(server.get(), {client_1.get()});
2✔
1474

1✔
1475
    client_2->transaction([](Peer& c) {
2✔
1476
        TableRef target = c.table("class_target");
2✔
1477
        REALM_ASSERT(target);
2✔
1478
        target->begin()->remove();
2✔
1479
    });
2✔
1480

1✔
1481
    client_2.get()->integrate_next_changeset_from(*server);
2✔
1482
}
2✔
1483

1484

1485
TEST(Transform_ArrayClearVsArrayClear_TimestampBased)
1486
{
2✔
1487
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1488
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
1489
    auto client_1 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
1490
    auto client_2 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
1491
    ColKey col_ints;
2✔
1492

1✔
1493
    // Create baseline
1✔
1494
    client_1->transaction([&](Peer& c) {
2✔
1495
        TableRef table = c.group->add_table_with_primary_key("class_table", type_Int, "id");
2✔
1496
        col_ints = table->add_column_list(type_Int, "ints");
2✔
1497
        auto obj = table->create_object_with_primary_key(1);
2✔
1498
        auto ints = obj.get_list<int64_t>("ints");
2✔
1499
        ints.insert(0, 1);
2✔
1500
        ints.insert(1, 2);
2✔
1501
    });
2✔
1502

1✔
1503
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
1504

1✔
1505
    // Clear the list and insert new values on two clients. The client with the
1✔
1506
    // higher timestamp should win, and its elements should survive.
1✔
1507

1✔
1508
    client_1->transaction([&](Peer& c) {
2✔
1509
        TableRef table = c.group->get_table("class_table");
2✔
1510
        auto obj = *table->begin();
2✔
1511
        auto ints = obj.get_list<int64_t>("ints");
2✔
1512
        ints.clear();
2✔
1513
        ints.insert(0, 3);
2✔
1514
        ints.insert(1, 4);
2✔
1515
    });
2✔
1516

1✔
1517
    client_2->history.advance_time(1);
2✔
1518

1✔
1519
    client_2->transaction([&](Peer& c) {
2✔
1520
        TableRef table = c.group->get_table("class_table");
2✔
1521
        auto obj = *table->begin();
2✔
1522
        auto ints = obj.get_list<int64_t>("ints");
2✔
1523
        ints.clear();
2✔
1524
        ints.insert(0, 5);
2✔
1525
        ints.insert(1, 6);
2✔
1526
    });
2✔
1527

1✔
1528
    synchronize(server.get(), {client_1.get(), client_2.get()});
2✔
1529

1✔
1530
    ReadTransaction rt_0(server->shared_group);
2✔
1531
    ReadTransaction rt_1(client_1->shared_group);
2✔
1532
    ReadTransaction rt_2(client_2->shared_group);
2✔
1533
    CHECK(compare_groups(rt_0, rt_1));
2✔
1534
    CHECK(compare_groups(rt_0, rt_2));
2✔
1535
    auto table = rt_0.get_table("class_table");
2✔
1536
    auto obj = *table->begin();
2✔
1537
    auto ints = obj.get_list<int64_t>(col_ints);
2✔
1538
    CHECK_EQUAL(ints.size(), 2);
2✔
1539
    CHECK_EQUAL(ints[0], 5);
2✔
1540
    CHECK_EQUAL(ints[1], 6);
2✔
1541
}
2✔
1542

1543
TEST(Transform_CreateEraseCreateSequencePreservesObject)
1544
{
2✔
1545
    // If two clients independently create an object, then erase the object, and
1✔
1546
    // then recreate it, we want to preserve the object creation with the higher
1✔
1547
    // timestamp.
1✔
1548
    //
1✔
1549
    // The previous behavior was that whoever had the most EraseObject
1✔
1550
    // instructions "won".
1✔
1551

1✔
1552
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1553
    Associativity assoc{test_context, 2, changeset_dump_dir_gen.get()};
2✔
1554
    assoc.for_each_permutation([&](auto& it) {
4✔
1555
        auto server = &*it.server;
4✔
1556
        auto client_1 = &*it.clients[0];
4✔
1557
        auto client_2 = &*it.clients[1];
4✔
1558

2✔
1559
        // Disable history compaction to be certain that create-erase-create
2✔
1560
        // cycles are not eliminated.
2✔
1561
        server->history.set_disable_compaction(true);
4✔
1562
        client_1->history.set_disable_compaction(true);
4✔
1563
        client_2->history.set_disable_compaction(true);
4✔
1564

2✔
1565
        // Create baseline
2✔
1566
        client_1->transaction([&](Peer& c) {
4✔
1567
            auto table = c.group->add_table_with_primary_key("class_table", type_Int, "pk");
4✔
1568
            table->add_column(type_Int, "int");
4✔
1569
            auto obj = table->create_object_with_primary_key(123);
4✔
1570
            obj.set<int64_t>("int", 0);
4✔
1571
        });
4✔
1572

2✔
1573
        it.sync_all();
4✔
1574

2✔
1575
        // Create a Create-Erase-Create cycle.
2✔
1576
        client_1->transaction([&](Peer& c) {
4✔
1577
            TableRef table = c.group->get_table("class_table");
4✔
1578
            auto obj = *table->begin();
4✔
1579
            obj.remove();
4✔
1580
            obj = table->create_object_with_primary_key(123);
4✔
1581
            obj.set<int64_t>("int", 1);
4✔
1582
            obj.remove();
4✔
1583
            obj = table->create_object_with_primary_key(123);
4✔
1584
            obj.set<int64_t>("int", 11);
4✔
1585
        });
4✔
1586

2✔
1587
        client_2->history.advance_time(1);
4✔
1588
        client_2->transaction([&](Peer& c) {
4✔
1589
            TableRef table = c.group->get_table("class_table");
4✔
1590
            auto obj = *table->begin();
4✔
1591
            obj.remove();
4✔
1592
            obj = table->create_object_with_primary_key(123);
4✔
1593
            obj.set<int64_t>("int", 2);
4✔
1594
        });
4✔
1595

2✔
1596
        it.sync_all();
4✔
1597

2✔
1598
        ReadTransaction rt_0(server->shared_group);
4✔
1599
        auto table = rt_0.get_table("class_table");
4✔
1600
        CHECK_EQUAL(table->size(), 1);
4✔
1601
        auto obj = *table->begin();
4✔
1602
        CHECK_EQUAL(obj.get<int64_t>("pk"), 123);
4✔
1603
        CHECK_EQUAL(obj.get<int64_t>("int"), 2);
4✔
1604
    });
4✔
1605
}
2✔
1606

1607
TEST(Transform_AddIntegerSurvivesSetNull)
1608
{
2✔
1609
    // An AddInteger instruction merged with a Set(null) instruction with a
1✔
1610
    // lower timestamp should not discard the AddInteger instruction. The
1✔
1611
    // implication is that if a new Set(non-null) occurs "in between" the
1✔
1612
    // Set(null) and the AddInteger instruction, ordered by timestamp, the
1✔
1613
    // addition survives.
1✔
1614

1✔
1615
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1616
    Associativity assoc{test_context, 3, changeset_dump_dir_gen.get()};
2✔
1617
    assoc.for_each_permutation([&](auto& it) {
12✔
1618
        auto server = &*it.server;
12✔
1619
        auto client_1 = &*it.clients[0];
12✔
1620
        auto client_2 = &*it.clients[1];
12✔
1621
        auto client_3 = &*it.clients[2];
12✔
1622

6✔
1623
        // Create baseline
6✔
1624
        client_1->transaction([&](Peer& c) {
12✔
1625
            auto table = c.group->add_table_with_primary_key("class_table", type_Int, "pk");
12✔
1626
            const bool nullable = true;
12✔
1627
            table->add_column(type_Int, "int", nullable);
12✔
1628
            auto obj = table->create_object_with_primary_key(0);
12✔
1629
            obj.set<int64_t>("int", 0);
12✔
1630
        });
12✔
1631

6✔
1632
        it.sync_all();
12✔
1633

6✔
1634
        // At t0, set the field to NULL.
6✔
1635
        client_1->transaction([&](Peer& c) {
12✔
1636
            auto table = c.group->get_table("class_table");
12✔
1637
            auto obj = *table->begin();
12✔
1638
            CHECK(!obj.is_null("int"));
12✔
1639
            CHECK_EQUAL(obj.get<util::Optional<int64_t>>("int"), 0);
12✔
1640
            obj.set_null("int");
12✔
1641
        });
12✔
1642

6✔
1643
        // At t2, increment the integer.
6✔
1644
        client_2->history.advance_time(2);
12✔
1645
        client_2->transaction([&](Peer& c) {
12✔
1646
            auto table = c.group->get_table("class_table");
12✔
1647
            auto obj = *table->begin();
12✔
1648
            CHECK(!obj.is_null("int"));
12✔
1649
            CHECK_EQUAL(obj.get<util::Optional<int64_t>>("int"), 0);
12✔
1650
            obj.add_int("int", 1);
12✔
1651
        });
12✔
1652

6✔
1653
        // Synchronize client_1 and client_2. The value should be NULL
6✔
1654
        // afterwards. Note: Not using sync_all(), because we want the change
6✔
1655
        // from client_3 to not be causally dependent on the state at this
6✔
1656
        // point.
6✔
1657
        synchronize(server, {client_1, client_2});
12✔
1658

6✔
1659
        {
12✔
1660
            ReadTransaction rt{client_2->shared_group};
12✔
1661
            auto table = rt.get_table("class_table");
12✔
1662
            auto obj = *table->begin();
12✔
1663
            CHECK(obj.is_null("int"));
12✔
1664
        }
12✔
1665

6✔
1666
        // At t1, set the field to 10. Since the timeline is interleaved, the
6✔
1667
        // final value should be 11, because the AddInteger from above should be
6✔
1668
        // forward-ported on top of this value.
6✔
1669
        client_3->history.advance_time(1);
12✔
1670
        client_3->transaction([&](Peer& c) {
12✔
1671
            auto table = c.group->get_table("class_table");
12✔
1672
            auto obj = *table->begin();
12✔
1673
            CHECK_EQUAL(obj.get<util::Optional<int64_t>>("int"), 0);
12✔
1674
            obj.set<int64_t>("int", 10);
12✔
1675
        });
12✔
1676

6✔
1677
        it.sync_all();
12✔
1678

6✔
1679
        {
12✔
1680
            ReadTransaction rt_0{server->shared_group};
12✔
1681
            auto table = rt_0.get_table("class_table");
12✔
1682
            auto obj = *table->begin();
12✔
1683
            CHECK_EQUAL(obj.get<util::Optional<int64_t>>("int"), 11);
12✔
1684
        }
12✔
1685
    });
12✔
1686
}
2✔
1687

1688
TEST(Transform_AddIntegerSurvivesSetDefault)
1689
{
2✔
1690
    // Set(default) should behave as-if it occurred at the beginning of time.
1✔
1691

1✔
1692
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1693
    Associativity assoc{test_context, 3, changeset_dump_dir_gen.get()};
2✔
1694
    assoc.for_each_permutation([&](auto& it) {
12✔
1695
        auto server = &*it.server;
12✔
1696
        auto client_1 = &*it.clients[0];
12✔
1697
        auto client_2 = &*it.clients[1];
12✔
1698
        auto client_3 = &*it.clients[2];
12✔
1699

6✔
1700
        // Create baseline
6✔
1701
        client_1->transaction([&](Peer& c) {
12✔
1702
            auto table = c.group->add_table_with_primary_key("class_table", type_Int, "pk");
12✔
1703
            table->add_column(type_Int, "int");
12✔
1704
            table->create_object_with_primary_key(0);
12✔
1705
        });
12✔
1706

6✔
1707
        it.sync_all();
12✔
1708

6✔
1709
        // At t1, set value explicitly.
6✔
1710
        client_1->history.advance_time(1);
12✔
1711
        client_1->transaction([&](Peer& c) {
12✔
1712
            auto table = c.group->get_table("class_table");
12✔
1713
            auto obj = *table->begin();
12✔
1714
            obj.set("int", 1);
12✔
1715
        });
12✔
1716

6✔
1717
        // At t2, increment value.
6✔
1718
        client_2->history.advance_time(2);
12✔
1719
        client_2->transaction([&](Peer& c) {
12✔
1720
            auto table = c.group->get_table("class_table");
12✔
1721
            auto obj = *table->begin();
12✔
1722
            obj.add_int("int", 1);
12✔
1723
        });
12✔
1724

6✔
1725
        // At t3, set default value.
6✔
1726
        client_3->history.advance_time(3);
12✔
1727
        client_3->transaction([&](Peer& c) {
12✔
1728
            auto table = c.group->get_table("class_table");
12✔
1729
            auto obj = *table->begin();
12✔
1730
            const bool is_default = true;
12✔
1731
            obj.set("int", 10, is_default);
12✔
1732
        });
12✔
1733

6✔
1734
        it.sync_all();
12✔
1735

6✔
1736
        ReadTransaction rt_0(server->shared_group);
12✔
1737
        auto table = rt_0.get_table("class_table");
12✔
1738
        auto obj = *table->begin();
12✔
1739
        // Expected outcome: The SetDefault instruction has no effect, so the result should be 2.
6✔
1740
        CHECK_EQUAL(obj.get<int64_t>("int"), 2);
12✔
1741
    });
12✔
1742
}
2✔
1743

1744
TEST(Transform_AddIntegerSurvivesSetDefault_NoRegularSets)
1745
{
2✔
1746
    // Set(default) should behave as-if it occurred at the beginning of time.
1✔
1747

1✔
1748
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1749
    Associativity assoc{test_context, 3, changeset_dump_dir_gen.get()};
2✔
1750
    assoc.for_each_permutation([&](auto& it) {
12✔
1751
        auto server = &*it.server;
12✔
1752
        auto client_1 = &*it.clients[0];
12✔
1753
        auto client_2 = &*it.clients[1];
12✔
1754
        auto client_3 = &*it.clients[2];
12✔
1755

6✔
1756
        // Create baseline
6✔
1757
        client_1->transaction([&](Peer& c) {
12✔
1758
            auto table = c.group->add_table_with_primary_key("class_table", type_Int, "pk");
12✔
1759
            table->add_column(type_Int, "int");
12✔
1760
            table->create_object_with_primary_key(0);
12✔
1761
        });
12✔
1762

6✔
1763
        it.sync_all();
12✔
1764

6✔
1765
        // At t1, set value explicitly.
6✔
1766
        client_1->history.advance_time(1);
12✔
1767
        client_1->transaction([&](Peer& c) {
12✔
1768
            auto table = c.group->get_table("class_table");
12✔
1769
            auto obj = *table->begin();
12✔
1770
            const bool is_default = true;
12✔
1771
            obj.set("int", 1, is_default);
12✔
1772
        });
12✔
1773

6✔
1774
        // At t2, set a new default value.
6✔
1775
        client_2->history.advance_time(2);
12✔
1776
        client_2->transaction([&](Peer& c) {
12✔
1777
            auto table = c.group->get_table("class_table");
12✔
1778
            auto obj = *table->begin();
12✔
1779
            const bool is_default = true;
12✔
1780
            obj.set("int", 10, is_default);
12✔
1781
        });
12✔
1782

6✔
1783
        // At t3, add something based on the default value.
6✔
1784
        client_3->history.advance_time(3);
12✔
1785
        client_3->transaction([&](Peer& c) {
12✔
1786
            auto table = c.group->get_table("class_table");
12✔
1787
            auto obj = *table->begin();
12✔
1788
            obj.add_int("int", 1);
12✔
1789
        });
12✔
1790

6✔
1791
        it.sync_all();
12✔
1792

6✔
1793
        ReadTransaction rt_0(server->shared_group);
12✔
1794

6✔
1795
        auto table = rt_0.get_table("class_table");
12✔
1796
        auto obj = *table->begin();
12✔
1797
        // Expected outcome: The AddInteger instruction should be rebased on top of the latest SetDefault instruction.
6✔
1798
        CHECK_EQUAL(obj.get<int64_t>("int"), 11);
12✔
1799
    });
12✔
1800
}
2✔
1801

1802
TEST(Transform_DanglingLinks)
1803
{
2✔
1804
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1805
    Associativity assoc{test_context, 2, changeset_dump_dir_gen.get()};
2✔
1806
    assoc.for_each_permutation([&](auto& it) {
4✔
1807
        auto server = &*it.server;
4✔
1808
        auto client_1 = &*it.clients[0];
4✔
1809
        auto client_2 = &*it.clients[1];
4✔
1810

2✔
1811
        // Create baseline
2✔
1812
        client_1->transaction([&](Peer& c) {
4✔
1813
            auto& tr = *c.group;
4✔
1814
            auto table = tr.add_table_with_primary_key("class_table", type_Int, "pk");
4✔
1815
            auto table2 = tr.add_table_with_primary_key("class_table2", type_Int, "pk");
4✔
1816
            table->add_column_list(*table2, "links");
4✔
1817
            auto obj = table->create_object_with_primary_key(0);
4✔
1818
            auto obj2 = table2->create_object_with_primary_key(0);
4✔
1819
            obj.get_linklist("links").insert(0, obj2.get_key());
4✔
1820
        });
4✔
1821

2✔
1822
        it.sync_all();
4✔
1823

2✔
1824
        // Client 1 removes the target object.
2✔
1825
        client_1->transaction([&](Peer& c) {
4✔
1826
            auto& tr = *c.group;
4✔
1827
            auto table = tr.get_table("class_table");
4✔
1828
            auto table2 = tr.get_table("class_table2");
4✔
1829
            auto obj2 = table2->get_object_with_primary_key(0);
4✔
1830
            obj2.remove();
4✔
1831

2✔
1832
            auto obj = table->get_object_with_primary_key(0);
4✔
1833
            auto links = obj.get_linklist("links");
4✔
1834
            CHECK_EQUAL(links.size(), 0);
4✔
1835

2✔
1836
            // Check that backlinks were eagerly removed
2✔
1837
            auto keys = obj.get_list<ObjKey>("links");
4✔
1838
            CHECK_EQUAL(keys.size(), 0);
4✔
1839
        });
4✔
1840

2✔
1841
        // Client 2 adds a new link to the object.
2✔
1842
        client_2->transaction([&](Peer& c) {
4✔
1843
            auto& tr = *c.group;
4✔
1844
            auto table = tr.get_table("class_table");
4✔
1845
            auto table2 = tr.get_table("class_table2");
4✔
1846
            auto obj = table->get_object_with_primary_key(0);
4✔
1847
            auto obj2 = table2->get_object_with_primary_key(0);
4✔
1848
            auto links = obj.get_linklist("links");
4✔
1849
            links.insert(1, obj2.get_key());
4✔
1850
            CHECK_EQUAL(links.size(), 2);
4✔
1851

2✔
1852
            auto keys = obj.get_list<ObjKey>("links");
4✔
1853
            CHECK_EQUAL(keys.size(), 2);
4✔
1854
        });
4✔
1855

2✔
1856
        it.sync_all();
4✔
1857

2✔
1858
        ReadTransaction rt_0{server->shared_group};
4✔
1859
        auto table = rt_0.get_table("class_table");
4✔
1860
        auto table2 = rt_0.get_table("class_table2");
4✔
1861
        CHECK_EQUAL(table2->size(), 0); // The object ended up being deleted
4✔
1862

2✔
1863
        auto objkey = table->find_primary_key(0);
4✔
1864
        auto obj = table->get_object(objkey);
4✔
1865

2✔
1866
        // The "virtual" list should seem empty.
2✔
1867
        auto links = obj.get_linklist("links");
4✔
1868
        CHECK_EQUAL(links.size(), 0);
4✔
1869

2✔
1870
        // ... But the real list should contain 1 tombstone.
2✔
1871
        auto keys = obj.get_list<ObjKey>(table->get_column_key("links"));
4✔
1872
        CHECK_EQUAL(keys.size(), 1);
4✔
1873
    });
4✔
1874
}
2✔
1875

1876
TEST(Transform_Dictionary)
1877
{
2✔
1878
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1879
    Associativity assoc{test_context, 2, changeset_dump_dir_gen.get()};
2✔
1880
    assoc.for_each_permutation([&](auto& it) {
4✔
1881
        auto server = &*it.server;
4✔
1882
        auto client_1 = &*it.clients[0];
4✔
1883
        auto client_2 = &*it.clients[1];
4✔
1884

2✔
1885
        // Create baseline
2✔
1886
        client_1->transaction([&](Peer& c) {
4✔
1887
            auto& tr = *c.group;
4✔
1888
            auto table = tr.add_table_with_primary_key("class_Table", type_Int, "id");
4✔
1889
            table->add_column_dictionary(type_Mixed, "dict");
4✔
1890
            table->create_object_with_primary_key(0);
4✔
1891
            table->create_object_with_primary_key(1);
4✔
1892
        });
4✔
1893

2✔
1894
        it.sync_all();
4✔
1895

2✔
1896
        // Populate dictionary on both sides.
2✔
1897
        client_1->transaction([&](Peer& c) {
4✔
1898
            auto& tr = *c.group;
4✔
1899
            auto table = tr.get_table("class_Table");
4✔
1900
            auto obj0 = table->get_object_with_primary_key(0);
4✔
1901
            auto obj1 = table->get_object_with_primary_key(1);
4✔
1902
            auto dict0 = obj0.get_dictionary("dict");
4✔
1903
            auto dict1 = obj1.get_dictionary("dict");
4✔
1904

2✔
1905
            dict0.insert("a", 123);
4✔
1906
            dict0.insert("b", "Hello");
4✔
1907
            dict0.insert("c", 45.0);
4✔
1908

2✔
1909
            dict1.insert("a", 456);
4✔
1910
        });
4✔
1911

2✔
1912
        // Since client_2 has a higher peer ID, it should win this conflict.
2✔
1913
        client_2->transaction([&](Peer& c) {
4✔
1914
            auto& tr = *c.group;
4✔
1915
            auto table = tr.get_table("class_Table");
4✔
1916
            auto obj0 = table->get_object_with_primary_key(0);
4✔
1917
            auto obj1 = table->get_object_with_primary_key(1);
4✔
1918
            auto dict0 = obj0.get_dictionary("dict");
4✔
1919
            auto dict1 = obj1.get_dictionary("dict");
4✔
1920

2✔
1921
            dict0.insert("b", "Hello, World!");
4✔
1922
            dict0.insert("d", true);
4✔
1923

2✔
1924
            dict1.insert("b", 789.f);
4✔
1925
        });
4✔
1926

2✔
1927
        it.sync_all();
4✔
1928

2✔
1929
        ReadTransaction rt{server->shared_group};
4✔
1930
        auto table = rt.get_table("class_Table");
4✔
1931
        CHECK(table);
4✔
1932
        auto obj0 = table->get_object_with_primary_key(0);
4✔
1933
        auto obj1 = table->get_object_with_primary_key(1);
4✔
1934
        auto dict0 = obj0.get_dictionary("dict");
4✔
1935
        auto dict1 = obj1.get_dictionary("dict");
4✔
1936

2✔
1937
        CHECK_EQUAL(dict0.size(), 4);
4✔
1938
        CHECK_EQUAL(dict0.get("a"), Mixed{123});
4✔
1939
        CHECK_EQUAL(dict0.get("b"), Mixed{"Hello, World!"});
4✔
1940
        CHECK_EQUAL(dict0.get("c"), Mixed{45.0});
4✔
1941
        CHECK_EQUAL(dict0.get("d"), Mixed{true});
4✔
1942

2✔
1943
        CHECK_EQUAL(dict1.size(), 2);
4✔
1944
        CHECK_EQUAL(dict1.get("a"), 456);
4✔
1945
        CHECK_EQUAL(dict1.get("b"), 789.f);
4✔
1946
    });
4✔
1947
}
2✔
1948

1949
TEST(Transform_Set)
1950
{
2✔
1951
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
1952
    Associativity assoc{test_context, 2, changeset_dump_dir_gen.get()};
2✔
1953
    assoc.for_each_permutation([&](auto& it) {
4✔
1954
        auto server = &*it.server;
4✔
1955
        auto client_1 = &*it.clients[0];
4✔
1956
        auto client_2 = &*it.clients[1];
4✔
1957

2✔
1958
        // Create baseline
2✔
1959
        client_1->transaction([&](Peer& c) {
4✔
1960
            auto& tr = *c.group;
4✔
1961
            auto table = tr.add_table_with_primary_key("class_Table", type_Int, "id");
4✔
1962
            table->add_column_set(type_Mixed, "set");
4✔
1963
            table->create_object_with_primary_key(0);
4✔
1964
        });
4✔
1965

2✔
1966
        it.sync_all();
4✔
1967

2✔
1968
        // Populate set on both sides.
2✔
1969
        client_1->transaction([&](Peer& c) {
4✔
1970
            auto& tr = *c.group;
4✔
1971
            auto table = tr.get_table("class_Table");
4✔
1972
            auto obj = table->get_object_with_primary_key(0);
4✔
1973
            auto set = obj.get_set<Mixed>("set");
4✔
1974
            set.insert(999);
4✔
1975
            set.insert("Hello");
4✔
1976
            set.insert(123.f);
4✔
1977
        });
4✔
1978
        client_2->transaction([&](Peer& c) {
4✔
1979
            auto& tr = *c.group;
4✔
1980
            auto table = tr.get_table("class_Table");
4✔
1981
            auto obj = table->get_object_with_primary_key(0);
4✔
1982
            auto set = obj.get_set<Mixed>("set");
4✔
1983
            set.insert(999);
4✔
1984
            set.insert("World");
4✔
1985
            set.insert(456.f);
4✔
1986

2✔
1987
            // Erase an element from the set. Since client_2 has higher peer ID,
2✔
1988
            // it should win the conflict.
2✔
1989
            set.erase(999);
4✔
1990
            set.insert(999);
4✔
1991
            set.erase(999);
4✔
1992
        });
4✔
1993

2✔
1994
        it.sync_all();
4✔
1995

2✔
1996
        ReadTransaction rt{server->shared_group};
4✔
1997
        auto table = rt.get_table("class_Table");
4✔
1998
        auto obj = table->get_object_with_primary_key(0);
4✔
1999
        auto set = obj.get_set<Mixed>("set");
4✔
2000
        CHECK_EQUAL(set.size(), 4);
4✔
2001
        CHECK_NOT_EQUAL(set.find("Hello"), realm::npos);
4✔
2002
        CHECK_NOT_EQUAL(set.find(123.f), realm::npos);
4✔
2003
        CHECK_NOT_EQUAL(set.find("World"), realm::npos);
4✔
2004
        CHECK_NOT_EQUAL(set.find(456.f), realm::npos);
4✔
2005
        CHECK_EQUAL(set.find(999), realm::npos);
4✔
2006
    });
4✔
2007
}
2✔
2008

2009
TEST(Transform_ArrayEraseVsArrayErase)
2010
{
2✔
2011
    // This test case recreates the problem that the above test exposes
1✔
2012
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2013
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
2014
    auto client_3 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
2015
    auto client_4 = Peer::create_client(test_context, 4, changeset_dump_dir_gen.get());
2✔
2016
    auto client_5 = Peer::create_client(test_context, 5, changeset_dump_dir_gen.get());
2✔
2017

1✔
2018
    client_3->create_schema([](WriteTransaction& tr) {
2✔
2019
        auto t = tr.get_group().add_table_with_primary_key("class_A", type_Int, "pk");
2✔
2020
        t->add_column_list(type_String, "h");
2✔
2021
        t->create_object_with_primary_key(5);
2✔
2022
    });
2✔
2023

1✔
2024
    synchronize(server.get(), {client_3.get(), client_4.get(), client_5.get()});
2✔
2025

1✔
2026
    client_5->transaction([](Peer& p) {
2✔
2027
        auto ll = p.table("class_A")->begin()->get_list<String>("h");
2✔
2028
        ll.insert(0, "5abc");
2✔
2029
    });
2✔
2030

1✔
2031
    client_4->transaction([](Peer& p) {
2✔
2032
        auto ll = p.table("class_A")->begin()->get_list<String>("h");
2✔
2033
        ll.insert(0, "4abc");
2✔
2034
    });
2✔
2035

1✔
2036
    server->integrate_next_changeset_from(*client_5);
2✔
2037
    server->integrate_next_changeset_from(*client_4);
2✔
2038

1✔
2039
    client_3->transaction([](Peer& p) {
2✔
2040
        auto ll = p.table("class_A")->begin()->get_list<String>("h");
2✔
2041
        ll.insert(0, "3abc");
2✔
2042
    });
2✔
2043

1✔
2044
    client_5->transaction([](Peer& p) {
2✔
2045
        auto ll = p.table("class_A")->begin()->get_list<String>("h");
2✔
2046
        ll.insert(0, "5def");
2✔
2047
    });
2✔
2048

1✔
2049
    server->integrate_next_changeset_from(*client_3);
2✔
2050
    server->integrate_next_changeset_from(*client_5);
2✔
2051

1✔
2052
    client_4->transaction([](Peer& p) {
2✔
2053
        auto ll = p.table("class_A")->begin()->get_list<String>("h");
2✔
2054
        ll.remove(0);
2✔
2055
    });
2✔
2056

1✔
2057
    client_5->transaction([](Peer& p) {
2✔
2058
        auto ll = p.table("class_A")->begin()->get_list<String>("h");
2✔
2059
        ll.remove(0);
2✔
2060
    });
2✔
2061

1✔
2062
    server->integrate_next_changeset_from(*client_4);
2✔
2063
    server->integrate_next_changeset_from(*client_5);
2✔
2064
}
2✔
2065

2066
TEST(Transform_RSYNC_143)
2067
{
2✔
2068
    // Divergence between Create-Set-Erase and Create.
1✔
2069

1✔
2070
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2071
    Associativity assoc{test_context, 2, changeset_dump_dir_gen.get()};
2✔
2072
    assoc.for_each_permutation([&](auto& it) {
4✔
2073
        auto server = &*it.server;
4✔
2074
        auto client_1 = &*it.clients[0];
4✔
2075
        auto client_2 = &*it.clients[1];
4✔
2076

2✔
2077
        // Create baseline
2✔
2078
        client_1->transaction([&](Peer& c) {
4✔
2079
            auto& tr = *c.group;
4✔
2080
            auto table = tr.add_table_with_primary_key("class_Table", type_Int, "id");
4✔
2081
            table->add_column(type_Int, "int");
4✔
2082
        });
4✔
2083

2✔
2084
        it.sync_all();
4✔
2085

2✔
2086
        client_1->transaction([&](Peer& c) {
4✔
2087
            auto& tr = *c.group;
4✔
2088
            auto table = tr.get_table("class_Table");
4✔
2089
            auto obj = table->create_object_with_primary_key(123);
4✔
2090
            obj.set("int", 500);
4✔
2091
            obj.remove();
4✔
2092
        });
4✔
2093

2✔
2094
        client_2->history.advance_time(1);
4✔
2095
        client_2->transaction([&](Peer& c) {
4✔
2096
            auto& tr = *c.group;
4✔
2097
            auto table = tr.get_table("class_Table");
4✔
2098
            table->create_object_with_primary_key(123);
4✔
2099
        });
4✔
2100

2✔
2101
        it.sync_all();
4✔
2102

2✔
2103
        ReadTransaction rt{server->shared_group};
4✔
2104
        auto table = rt.get_table("class_Table");
4✔
2105
        CHECK_EQUAL(table->size(), 0);
4✔
2106
    });
4✔
2107
}
2✔
2108

2109
TEST(Transform_RSYNC_143_Fallout)
2110
{
2✔
2111
    // Divergence between Create-Set-Erase and Create.
1✔
2112

1✔
2113
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2114
    Associativity assoc{test_context, 2, changeset_dump_dir_gen.get()};
2✔
2115
    assoc.for_each_permutation([&](auto& it) {
4✔
2116
        auto server = &*it.server;
4✔
2117
        auto client_1 = &*it.clients[0];
4✔
2118
        auto client_2 = &*it.clients[1];
4✔
2119

2✔
2120
        // Create baseline
2✔
2121
        client_1->transaction([&](Peer& c) {
4✔
2122
            auto& tr = *c.group;
4✔
2123
            auto table = tr.add_table_with_primary_key("class_Table", type_Int, "id");
4✔
2124
            table->add_column(type_Int, "int");
4✔
2125
        });
4✔
2126

2✔
2127
        it.sync_all();
4✔
2128

2✔
2129
        client_1->transaction([&](Peer& c) {
4✔
2130
            auto& tr = *c.group;
4✔
2131
            auto table = tr.get_table("class_Table");
4✔
2132
            auto obj = table->create_object_with_primary_key(123);
4✔
2133
            obj.set("int", 500);
4✔
2134
        });
4✔
2135

2✔
2136
        it.sync_all();
4✔
2137

2✔
2138
        client_1->history.advance_time(1);
4✔
2139
        client_1->transaction([&](Peer& c) {
4✔
2140
            auto& tr = *c.group;
4✔
2141
            auto table = tr.get_table("class_Table");
4✔
2142
            auto obj = table->get_object_with_primary_key(123);
4✔
2143
            obj.remove();
4✔
2144
        });
4✔
2145

2✔
2146
        client_2->history.advance_time(1);
4✔
2147
        client_2->transaction([&](Peer& c) {
4✔
2148
            auto& tr = *c.group;
4✔
2149
            auto table = tr.get_table("class_Table");
4✔
2150
            auto obj = table->create_object_with_primary_key(123);
4✔
2151
            obj.set("int", 900);
4✔
2152
            obj.remove();
4✔
2153
        });
4✔
2154

2✔
2155
        it.sync_all();
4✔
2156

2✔
2157
        static_cast<void>(server);
4✔
2158
    });
4✔
2159
}
2✔
2160

2161
TEST(Transform_SetInsert_Clear_same_path)
2162
{
2✔
2163
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2164
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
2165
    auto client_2 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
2166
    auto client_3 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
2167

1✔
2168
    // Create baseline
1✔
2169
    Mixed pk(1);
2✔
2170
    client_2->transaction([&](Peer& c) {
2✔
2171
        auto& tr = *c.group;
2✔
2172
        auto table = tr.add_table_with_primary_key("class_Table", type_Int, "_id");
2✔
2173
        auto embedded_table = tr.add_table("class_Embedded", Table::Type::Embedded);
2✔
2174
        auto link_col_key = table->add_column_list(*embedded_table, "embedded");
2✔
2175
        auto set_col_key = embedded_table->add_column_set(type_Int, "set");
2✔
2176
        auto obj = table->create_object_with_primary_key(pk);
2✔
2177
        auto embedded_obj = obj.get_linklist(link_col_key).create_and_insert_linked_object(0);
2✔
2178
        auto set = embedded_obj.get_set<Int>(set_col_key);
2✔
2179
        set.insert(1);
2✔
2180
    });
2✔
2181

1✔
2182
    synchronize(server.get(), {client_2.get(), client_3.get()});
2✔
2183

1✔
2184
    client_2->transaction([&](Peer& c) {
2✔
2185
        auto& tr = *c.group;
2✔
2186
        auto table = tr.get_table("class_Table");
2✔
2187
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2188
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2189
                       .get_set<Int>("set");
2✔
2190
        set.clear();
2✔
2191
        set.insert(1);
2✔
2192
    });
2✔
2193

1✔
2194
    client_3->transaction([&](Peer& c) {
2✔
2195
        auto& tr = *c.group;
2✔
2196
        auto table = tr.get_table("class_Table");
2✔
2197
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2198
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2199
                       .get_set<Int>("set");
2✔
2200
        set.insert(2);
2✔
2201
    });
2✔
2202

1✔
2203
    server->integrate_next_changeset_from(*client_2);
2✔
2204
    server->integrate_next_changeset_from(*client_3);
2✔
2205

1✔
2206
    {
2✔
2207
        ReadTransaction check_tr(server->shared_group);
2✔
2208
        auto table = check_tr.get_table("class_Table");
2✔
2209
        auto embedded_table = check_tr.get_table("class_Embedded");
2✔
2210
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2211
                       .get_set<Int>("set");
2✔
2212
        CHECK_EQUAL(set.size(), size_t(1));
2✔
2213
        CHECK_NOT_EQUAL(set.find(1), size_t(-1));
2✔
2214
        CHECK_EQUAL(set.find(2), size_t(-1));
2✔
2215
    }
2✔
2216
}
2✔
2217

2218
TEST(Transform_SetInsert_Clear_different_paths)
2219
{
2✔
2220
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2221
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
2222
    auto client_2 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
2223
    auto client_3 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
2224

1✔
2225
    Mixed pk(1);
2✔
2226
    // Create baseline
1✔
2227
    client_2->transaction([&](Peer& c) {
2✔
2228
        auto& tr = *c.group;
2✔
2229
        auto table = tr.add_table_with_primary_key("class_Table", type_Int, "_id");
2✔
2230
        auto embedded_table = tr.add_table("class_Embedded", Table::Type::Embedded);
2✔
2231
        auto link_col_key = table->add_column_list(*embedded_table, "embedded");
2✔
2232
        auto set_col_key = embedded_table->add_column_set(type_Int, "set");
2✔
2233
        auto obj = table->create_object_with_primary_key(pk);
2✔
2234
        for (size_t i = 0; i < 2; ++i) {
6✔
2235
            auto embedded_obj = obj.get_linklist(link_col_key).create_and_insert_linked_object(i);
4✔
2236
            auto set = embedded_obj.get_set<Int>(set_col_key);
4✔
2237
            set.insert(1);
4✔
2238
            set.insert(2);
4✔
2239
        }
4✔
2240
    });
2✔
2241

1✔
2242
    synchronize(server.get(), {client_2.get(), client_3.get()});
2✔
2243

1✔
2244
    client_2->transaction([&](Peer& c) {
2✔
2245
        auto& tr = *c.group;
2✔
2246
        auto table = tr.get_table("class_Table");
2✔
2247
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2248
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2249
                       .get_set<Int>("set");
2✔
2250
        set.clear();
2✔
2251
        set.insert(1);
2✔
2252
    });
2✔
2253

1✔
2254
    client_3->transaction([&](Peer& c) {
2✔
2255
        auto& tr = *c.group;
2✔
2256
        auto table = tr.get_table("class_Table");
2✔
2257
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2258
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(1))
2✔
2259
                       .get_set<Int>("set");
2✔
2260
        set.insert(3);
2✔
2261
    });
2✔
2262

1✔
2263
    server->integrate_next_changeset_from(*client_2);
2✔
2264
    server->integrate_next_changeset_from(*client_3);
2✔
2265

1✔
2266
    {
2✔
2267
        ReadTransaction check_tr(server->shared_group);
2✔
2268
        auto table = check_tr.get_table("class_Table");
2✔
2269
        auto embedded_table = check_tr.get_table("class_Embedded");
2✔
2270
        auto set_1 =
2✔
2271
            embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2272
                .get_set<Int>("set");
2✔
2273
        auto set_2 =
2✔
2274
            embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(1))
2✔
2275
                .get_set<Int>("set");
2✔
2276
        CHECK_NOT_EQUAL(set_1.find(1), size_t(-1));
2✔
2277
        CHECK_EQUAL(set_1.find(2), size_t(-1));
2✔
2278
        CHECK_EQUAL(set_2.size(), size_t(3));
2✔
2279
    }
2✔
2280
}
2✔
2281

2282
TEST(Transform_SetErase_Clear_same_path)
2283
{
2✔
2284
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2285
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
2286
    auto client_2 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
2287
    auto client_3 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
2288

1✔
2289
    // Create baseline
1✔
2290
    Mixed pk(1);
2✔
2291
    client_2->transaction([&](Peer& c) {
2✔
2292
        auto& tr = *c.group;
2✔
2293
        auto table = tr.add_table_with_primary_key("class_Table", type_Int, "_id");
2✔
2294
        auto embedded_table = tr.add_table("class_Embedded", Table::Type::Embedded);
2✔
2295
        auto link_col_key = table->add_column_list(*embedded_table, "embedded");
2✔
2296
        auto set_col_key = embedded_table->add_column_set(type_Int, "set");
2✔
2297
        auto obj = table->create_object_with_primary_key(pk);
2✔
2298
        auto embedded_obj = obj.get_linklist(link_col_key).create_and_insert_linked_object(0);
2✔
2299
        auto set = embedded_obj.get_set<Int>(set_col_key);
2✔
2300
        set.insert(1);
2✔
2301
        set.insert(2);
2✔
2302
    });
2✔
2303

1✔
2304
    synchronize(server.get(), {client_2.get(), client_3.get()});
2✔
2305

1✔
2306
    client_2->transaction([&](Peer& c) {
2✔
2307
        auto& tr = *c.group;
2✔
2308
        auto table = tr.get_table("class_Table");
2✔
2309
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2310
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2311
                       .get_set<Int>("set");
2✔
2312
        CHECK_EQUAL(set.size(), size_t(2));
2✔
2313
        set.clear();
2✔
2314
        set.insert(2);
2✔
2315
    });
2✔
2316

1✔
2317
    client_3->transaction([&](Peer& c) {
2✔
2318
        auto& tr = *c.group;
2✔
2319
        auto table = tr.get_table("class_Table");
2✔
2320
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2321
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2322
                       .get_set<Int>("set");
2✔
2323
        auto [size, erased] = set.erase(2);
2✔
2324
        CHECK_EQUAL(size, 1);
2✔
2325
        CHECK(erased);
2✔
2326
    });
2✔
2327

1✔
2328
    server->integrate_next_changeset_from(*client_2);
2✔
2329
    server->integrate_next_changeset_from(*client_3);
2✔
2330

1✔
2331
    {
2✔
2332
        ReadTransaction check_tr(server->shared_group);
2✔
2333
        auto table = check_tr.get_table("class_Table");
2✔
2334
        auto embedded_table = check_tr.get_table("class_Embedded");
2✔
2335
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2336
                       .get_set<Int>("set");
2✔
2337
        CHECK_EQUAL(set.size(), size_t(1));
2✔
2338
        CHECK_NOT_EQUAL(set.find(2), size_t(-1));
2✔
2339
        CHECK_EQUAL(set.find(1), size_t(-1));
2✔
2340
    }
2✔
2341
}
2✔
2342

2343
TEST(Transform_SetErase_Clear_different_paths)
2344
{
2✔
2345
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2346
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
2347
    auto client_2 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
2348
    auto client_3 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
2349

1✔
2350
    // Create baseline
1✔
2351
    Mixed pk(1);
2✔
2352
    client_2->transaction([&](Peer& c) {
2✔
2353
        auto& tr = *c.group;
2✔
2354
        auto table = tr.add_table_with_primary_key("class_Table", type_Int, "_id");
2✔
2355
        auto embedded_table = tr.add_table("class_Embedded", Table::Type::Embedded);
2✔
2356
        auto link_col_key = table->add_column_list(*embedded_table, "embedded");
2✔
2357
        auto set_col_key = embedded_table->add_column_set(type_Int, "set");
2✔
2358
        auto obj = table->create_object_with_primary_key(pk);
2✔
2359
        for (size_t i = 0; i < 2; ++i) {
6✔
2360
            auto embedded_obj = obj.get_linklist(link_col_key).create_and_insert_linked_object(i);
4✔
2361
            auto set = embedded_obj.get_set<Int>(set_col_key);
4✔
2362
            set.insert(1);
4✔
2363
            set.insert(2);
4✔
2364
        }
4✔
2365
    });
2✔
2366

1✔
2367
    synchronize(server.get(), {client_2.get(), client_3.get()});
2✔
2368

1✔
2369
    client_2->transaction([&](Peer& c) {
2✔
2370
        auto& tr = *c.group;
2✔
2371
        auto table = tr.get_table("class_Table");
2✔
2372
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2373
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2374
                       .get_set<Int>("set");
2✔
2375
        CHECK_EQUAL(set.size(), size_t(2));
2✔
2376
        set.clear();
2✔
2377
    });
2✔
2378

1✔
2379
    client_3->transaction([&](Peer& c) {
2✔
2380
        auto& tr = *c.group;
2✔
2381
        auto table = tr.get_table("class_Table");
2✔
2382
        auto embedded_table = tr.get_table("class_Embedded");
2✔
2383
        auto set = embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(1))
2✔
2384
                       .get_set<Int>("set");
2✔
2385
        auto erased = set.erase(1).second;
2✔
2386
        CHECK(erased);
2✔
2387
    });
2✔
2388

1✔
2389
    server->integrate_next_changeset_from(*client_2);
2✔
2390
    server->integrate_next_changeset_from(*client_3);
2✔
2391

1✔
2392
    {
2✔
2393
        ReadTransaction check_tr(server->shared_group);
2✔
2394
        auto table = check_tr.get_table("class_Table");
2✔
2395
        auto embedded_table = check_tr.get_table("class_Embedded");
2✔
2396
        auto set_1 =
2✔
2397
            embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(0))
2✔
2398
                .get_set<Int>("set");
2✔
2399
        auto set_2 =
2✔
2400
            embedded_table->get_object(table->get_object_with_primary_key(pk).get_linklist("embedded").get(1))
2✔
2401
                .get_set<Int>("set");
2✔
2402
        CHECK_EQUAL(set_1.size(), size_t(0));
2✔
2403
        CHECK_EQUAL(set_2.size(), size_t(1));
2✔
2404
        CHECK_EQUAL(set_2.find(1), size_t(-1));
2✔
2405
        CHECK_NOT_EQUAL(set_2.find(2), size_t(-1));
2✔
2406
    }
2✔
2407
}
2✔
2408

2409
TEST(Transform_ArrayClearVersusClearRegression)
2410
{
2✔
2411
    // This test is automatically generated by fuzz testing, and would produce a
1✔
2412
    // crash due to a failure to maintain the `prior_size` field of ArrayClear.
1✔
2413

1✔
2414
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
2✔
2415
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());
2✔
2416
    std::unique_ptr<Peer> client_2 = Peer::create_client(test_context, 2, changeset_dump_dir_gen.get());
2✔
2417
    std::unique_ptr<Peer> client_3 = Peer::create_client(test_context, 3, changeset_dump_dir_gen.get());
2✔
2418
    client_2->start_transaction();
2✔
2419
    client_2->group->add_table_with_primary_key("class_F", type_Int, "id");
2✔
2420
    client_2->commit(); // changeset 2
2✔
2421
    client_2->history.advance_time(5);
2✔
2422
    client_2->start_transaction();
2✔
2423
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2424
    auto col_key1 = client_2->selected_table->add_column_list(type_Int, "g", 0);
2✔
2425
    client_2->commit(); // changeset 3
2✔
2426
    client_3->start_transaction();
2✔
2427
    client_3->group->add_table_with_primary_key("class_F", type_Int, "id");
2✔
2428
    client_3->commit(); // changeset 2
2✔
2429
    client_2->history.advance_time(2);
2✔
2430
    server->integrate_next_changeset_from(*client_2);
2✔
2431
    client_2->history.advance_time(3);
2✔
2432
    client_2->start_transaction();
2✔
2433
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2434
    ObjKey k = client_2->selected_table->create_object_with_primary_key(1).get_key();
2✔
2435
    client_2->commit(); // changeset 4
2✔
2436
    client_3->integrate_next_changeset_from(*server);
2✔
2437
    client_2->start_transaction();
2✔
2438
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2439
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2440
    client_2->selected_array->clear();
2✔
2441
    client_2->commit(); // changeset 5
2✔
2442
    client_3->start_transaction();
2✔
2443
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2444
    auto col_str = client_3->selected_table->add_column(type_String, "c", 0);
2✔
2445
    client_3->commit(); // changeset 4
2✔
2446
    server->integrate_next_changeset_from(*client_2);
2✔
2447
    server->integrate_next_changeset_from(*client_2);
2✔
2448
    server->integrate_next_changeset_from(*client_3);
2✔
2449
    client_3->history.advance_time(-5);
2✔
2450
    server->integrate_next_changeset_from(*client_3);
2✔
2451
    client_3->history.advance_time(4);
2✔
2452
    client_3->integrate_next_changeset_from(*server);
2✔
2453
    client_3->integrate_next_changeset_from(*server);
2✔
2454
    client_3->start_transaction();
2✔
2455
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2456
    auto col_key2 = client_3->selected_table->get_column_key("g");
2✔
2457
    auto k2 = client_3->selected_table->get_object_with_primary_key(1).get_key();
2✔
2458
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2459
    client_3->selected_array->clear();
2✔
2460
    client_3->commit(); // changeset 7
2✔
2461
    server->integrate_next_changeset_from(*client_2);
2✔
2462
    client_2->history.advance_time(2);
2✔
2463
    client_2->start_transaction();
2✔
2464
    client_2->group->add_table_with_primary_key("class_C", type_Int, "pk");
2✔
2465
    client_2->commit(); // changeset 6
2✔
2466
    client_3->history.advance_time(1);
2✔
2467
    server->integrate_next_changeset_from(*client_3);
2✔
2468
    client_2->history.advance_time(1);
2✔
2469
    client_2->integrate_next_changeset_from(*server);
2✔
2470
    client_2->history.advance_time(3);
2✔
2471
    client_2->start_transaction();
2✔
2472
    client_2->selected_table = client_2->group->get_table("class_C");
2✔
2473
    client_2->selected_table->create_object_with_primary_key(3);
2✔
2474
    client_2->commit(); // changeset 8
2✔
2475
    client_2->start_transaction();
2✔
2476
    client_2->selected_table = client_2->group->get_table("class_C");
2✔
2477
    client_2->selected_table->create_object_with_primary_key(6);
2✔
2478
    client_2->commit(); // changeset 9
2✔
2479
    server->integrate_next_changeset_from(*client_2);
2✔
2480
    client_2->integrate_next_changeset_from(*server);
2✔
2481
    client_2->history.advance_time(-14);
2✔
2482
    server->integrate_next_changeset_from(*client_2);
2✔
2483
    client_3->start_transaction();
2✔
2484
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2485
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2486
    static_cast<Lst<int64_t>*>(client_3->selected_array.get())->insert(0, 0);
2✔
2487
    client_3->commit(); // changeset 8
2✔
2488
    client_2->history.advance_time(1);
2✔
2489
    client_2->start_transaction();
2✔
2490
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2491
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2492
    static_cast<Lst<int64_t>*>(client_2->selected_array.get())->insert(0, 0);
2✔
2493
    client_2->commit(); // changeset 11
2✔
2494
    client_3->start_transaction();
2✔
2495
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2496
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2497
    static_cast<Lst<int64_t>*>(client_3->selected_array.get())->set(0, 430);
2✔
2498
    client_3->commit(); // changeset 9
2✔
2499
    client_3->start_transaction();
2✔
2500
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2501
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2502
    static_cast<Lst<int64_t>*>(client_3->selected_array.get())->insert(1, 0);
2✔
2503
    client_3->commit(); // changeset 10
2✔
2504
    client_2->history.advance_time(1);
2✔
2505
    client_2->start_transaction();
2✔
2506
    client_2->selected_table = client_2->group->get_table("class_C");
2✔
2507
    client_2->selected_table->add_column(type_Int, "b", 1);
2✔
2508
    client_2->commit(); // changeset 12
2✔
2509
    client_3->history.advance_time(2);
2✔
2510
    client_3->integrate_next_changeset_from(*server);
2✔
2511
    client_3->history.advance_time(2);
2✔
2512
    server->integrate_next_changeset_from(*client_3);
2✔
2513
    client_2->integrate_next_changeset_from(*server);
2✔
2514
    client_2->history.advance_time(1);
2✔
2515
    client_2->integrate_next_changeset_from(*server);
2✔
2516
    client_2->history.advance_time(2);
2✔
2517
    server->integrate_next_changeset_from(*client_2);
2✔
2518
    client_2->start_transaction();
2✔
2519
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2520
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2521
    client_2->selected_array->clear();
2✔
2522
    client_2->commit(); // changeset 15
2✔
2523
    client_3->history.advance_time(4);
2✔
2524
    client_3->start_transaction();
2✔
2525
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2526
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2527
    client_3->selected_array->clear();
2✔
2528
    client_3->commit(); // changeset 12
2✔
2529
    client_3->start_transaction();
2✔
2530
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2531
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2532
    client_3->selected_array->clear();
2✔
2533
    client_3->commit(); // changeset 13
2✔
2534
    client_3->history.advance_time(5);
2✔
2535
    client_3->integrate_next_changeset_from(*server);
2✔
2536
    client_3->history.advance_time(4);
2✔
2537
    server->integrate_next_changeset_from(*client_3);
2✔
2538
    client_3->history.advance_time(1);
2✔
2539
    client_3->integrate_next_changeset_from(*server);
2✔
2540
    client_3->history.advance_time(4);
2✔
2541
    server->integrate_next_changeset_from(*client_3);
2✔
2542
    client_2->history.advance_time(4);
2✔
2543
    server->integrate_next_changeset_from(*client_2);
2✔
2544
    client_3->history.advance_time(4);
2✔
2545
    client_3->start_transaction();
2✔
2546
    client_3->selected_table = client_3->group->get_table("class_C");
2✔
2547
    client_3->selected_table->create_object_with_primary_key(4);
2✔
2548
    client_3->commit(); // changeset 16
2✔
2549
    client_2->history.advance_time(5);
2✔
2550
    client_2->integrate_next_changeset_from(*server);
2✔
2551
    client_3->history.advance_time(1);
2✔
2552
    client_3->start_transaction();
2✔
2553
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2554
    client_3->selected_table->get_object(k2).set(col_str, "1", 0);
2✔
2555
    client_3->commit(); // changeset 17
2✔
2556
    client_2->integrate_next_changeset_from(*server);
2✔
2557
    client_3->start_transaction();
2✔
2558
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2559
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2560
    client_3->selected_array->clear();
2✔
2561
    client_3->commit(); // changeset 18
2✔
2562
    client_2->history.advance_time(4);
2✔
2563
    client_2->start_transaction();
2✔
2564
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2565
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2566
    static_cast<Lst<int64_t>*>(client_2->selected_array.get())->insert(0, 0);
2✔
2567
    client_2->commit(); // changeset 18
2✔
2568
    server->integrate_next_changeset_from(*client_2);
2✔
2569
    client_2->start_transaction();
2✔
2570
    client_2->group->add_table_with_primary_key("class_E", type_Int, "pk");
2✔
2571
    client_2->commit(); // changeset 19
2✔
2572
    client_3->start_transaction();
2✔
2573
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2574
    client_3->selected_array = client_3->selected_table->get_object(k2).get_list_ptr<int64_t>(col_key2);
2✔
2575
    client_3->selected_array->clear();
2✔
2576
    client_3->commit(); // changeset 19
2✔
2577
    client_3->history.advance_time(1);
2✔
2578
    client_3->start_transaction();
2✔
2579
    client_3->selected_table = client_3->group->get_table("class_F");
2✔
2580
    client_3->selected_table->get_object(k2).set(col_str, "2", 0);
2✔
2581
    client_3->commit(); // changeset 20
2✔
2582
    client_2->history.advance_time(5);
2✔
2583
    client_2->start_transaction();
2✔
2584
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2585
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2586
    client_2->selected_array->clear();
2✔
2587
    client_2->commit(); // changeset 20
2✔
2588
    server->integrate_next_changeset_from(*client_2);
2✔
2589
    server->integrate_next_changeset_from(*client_2);
2✔
2590
    client_3->start_transaction();
2✔
2591
    client_3->selected_table = client_3->group->get_table("class_C");
2✔
2592
    client_3->selected_table->create_object_with_primary_key(9);
2✔
2593
    client_3->commit(); // changeset 21
2✔
2594
    server->integrate_next_changeset_from(*client_2);
2✔
2595
    client_2->start_transaction();
2✔
2596
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2597
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2598
    static_cast<Lst<int64_t>*>(client_2->selected_array.get())->insert(0, 0);
2✔
2599
    client_2->commit(); // changeset 21
2✔
2600
    client_3->history.advance_time(5);
2✔
2601
    client_3->integrate_next_changeset_from(*server);
2✔
2602
    client_2->start_transaction();
2✔
2603
    client_2->selected_table = client_2->group->get_table("class_F");
2✔
2604
    client_2->selected_array = client_2->selected_table->get_object(k).get_list_ptr<int64_t>(col_key1);
2✔
2605
    static_cast<Lst<int64_t>*>(client_2->selected_array.get())->insert(0, 0);
2✔
2606
    client_2->commit(); // changeset 22
2✔
2607
    client_3->history.advance_time(2);
2✔
2608
    server->integrate_next_changeset_from(*client_3);
2✔
2609
    client_2->history.advance_time(2);
2✔
2610
    server->integrate_next_changeset_from(*client_2);
2✔
2611
}
2✔
2612

2613
} // unnamed namespace
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