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

realm / realm-core / 2094

02 Mar 2024 12:43AM UTC coverage: 90.925% (+0.005%) from 90.92%
2094

push

Evergreen

web-flow
Use updated curl on evergreen windows hosts (#7409)

93946 of 173116 branches covered (54.27%)

238403 of 262196 relevant lines covered (90.93%)

5592101.41 hits per line

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

91.31
/test/test_sync.cpp
1
#include <cstddef>
2
#include <cstdint>
3
#include <cmath>
4
#include <cstdlib>
5
#include <cstring>
6
#include <memory>
7
#include <tuple>
8
#include <set>
9
#include <string>
10
#include <sstream>
11
#include <mutex>
12
#include <condition_variable>
13
#include <thread>
14
#include <chrono>
15

16
#include <realm.hpp>
17
#include <realm/chunked_binary.hpp>
18
#include <realm/data_type.hpp>
19
#include <realm/history.hpp>
20
#include <realm/impl/simulated_failure.hpp>
21
#include <realm/list.hpp>
22
#include <realm/sync/binding_callback_thread_observer.hpp>
23
#include <realm/sync/changeset.hpp>
24
#include <realm/sync/changeset_encoder.hpp>
25
#include <realm/sync/client.hpp>
26
#include <realm/sync/history.hpp>
27
#include <realm/sync/instructions.hpp>
28
#include <realm/sync/network/default_socket.hpp>
29
#include <realm/sync/network/http.hpp>
30
#include <realm/sync/network/network.hpp>
31
#include <realm/sync/network/websocket.hpp>
32
#include <realm/sync/noinst/protocol_codec.hpp>
33
#include <realm/sync/noinst/server/server.hpp>
34
#include <realm/sync/noinst/server/server_dir.hpp>
35
#include <realm/sync/noinst/server/server_history.hpp>
36
#include <realm/sync/object_id.hpp>
37
#include <realm/sync/protocol.hpp>
38
#include <realm/sync/transform.hpp>
39
#include <realm/util/buffer.hpp>
40
#include <realm/util/features.h>
41
#include <realm/util/logger.hpp>
42
#include <realm/util/random.hpp>
43
#include <realm/util/uri.hpp>
44
#include <realm/version.hpp>
45

46
#include "sync_fixtures.hpp"
47

48
#include "test.hpp"
49
#include "util/demangle.hpp"
50
#include "util/semaphore.hpp"
51
#include "util/thread_wrapper.hpp"
52
#include "util/compare_groups.hpp"
53

54
using namespace realm;
55
using namespace realm::sync;
56
using namespace realm::test_util;
57
using namespace realm::fixtures;
58

59

60
// Test independence and thread-safety
61
// -----------------------------------
62
//
63
// All tests must be thread safe and independent of each other. This
64
// is required because it allows for both shuffling of the execution
65
// order and for parallelized testing.
66
//
67
// In particular, avoid using std::rand() since it is not guaranteed
68
// to be thread safe. Instead use the API offered in
69
// `test/util/random.hpp`.
70
//
71
// All files created in tests must use the TEST_PATH macro (or one of
72
// its friends) to obtain a suitable file system path. See
73
// `test/util/test_path.hpp`.
74
//
75
//
76
// Debugging and the ONLY() macro
77
// ------------------------------
78
//
79
// A simple way of disabling all tests except one called `Foo`, is to
80
// replace TEST(Foo) with ONLY(Foo) and then recompile and rerun the
81
// test suite. Note that you can also use filtering by setting the
82
// environment variable `UNITTEST_FILTER`. See `README.md` for more on
83
// this.
84
//
85
// Another way to debug a particular test, is to copy that test into
86
// `experiments/testcase.cpp` and then run `sh build.sh
87
// check-testcase` (or one of its friends) from the command line.
88

89

90
namespace {
91

92
using ErrorInfo = SessionErrorInfo;
93

94
class TestServerHistoryContext : public _impl::ServerHistory::Context {
95
public:
96
    std::mt19937_64& server_history_get_random() noexcept override final
97
    {
6✔
98
        return m_random;
6✔
99
    }
6✔
100

101
private:
102
    std::mt19937_64 m_random;
103
};
104

105
#define TEST_CLIENT_DB(name)                                                                                         \
106
    SHARED_GROUP_TEST_PATH(name##_path);                                                                             \
316✔
107
    auto name = DB::create(make_client_replication(), name##_path);
316✔
108

109
template <typename Function>
110
void write_transaction(DBRef db, Function&& function)
111
{
8,102✔
112
    WriteTransaction wt(db);
8,102✔
113
    function(wt);
8,102✔
114
    wt.commit();
8,102✔
115
}
8,102✔
116

117
ClientReplication& get_replication(DBRef db)
118
{
10✔
119
    auto repl = dynamic_cast<ClientReplication*>(db->get_replication());
10✔
120
    REALM_ASSERT(repl);
10✔
121
    return *repl;
10✔
122
}
10✔
123

124
ClientHistory& get_history(DBRef db)
125
{
8✔
126
    return get_replication(db).get_history();
8✔
127
}
8✔
128

129
#if !REALM_MOBILE // the server is not implemented on devices
130
TEST(Sync_BadVirtualPath)
131
{
2✔
132
    // NOTE:  This test is no longer valid after migration to MongoDB Realm
1✔
133
    //  It still passes because it runs against the mock C++ server, but the
1✔
134
    //  MongoDB Realm server will behave differently
1✔
135

1✔
136
    TEST_DIR(dir);
2✔
137
    TEST_CLIENT_DB(db_1);
2✔
138
    TEST_CLIENT_DB(db_2);
2✔
139
    TEST_CLIENT_DB(db_3);
2✔
140
    ClientServerFixture fixture(dir, test_context);
2✔
141
    fixture.start();
2✔
142

1✔
143
    int nerrors = 0;
2✔
144

1✔
145
    auto listener = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
18✔
146
        if (state != ConnectionState::disconnected)
18✔
147
            return;
12✔
148
        REALM_ASSERT(error_info);
6✔
149
        CHECK_EQUAL(error_info->status, ErrorCodes::BadSyncPartitionValue);
6✔
150
        CHECK(error_info->is_fatal);
6✔
151
        ++nerrors;
6✔
152
        if (nerrors == 3)
6✔
153
            fixture.stop();
2✔
154
    };
6✔
155

1✔
156
    Session session_1 = fixture.make_session(db_1, "/test.realm");
2✔
157
    session_1.set_connection_state_change_listener(listener);
2✔
158
    session_1.bind();
2✔
159

1✔
160
    Session session_2 = fixture.make_session(db_2, "/../test");
2✔
161
    session_2.set_connection_state_change_listener(listener);
2✔
162
    session_2.bind();
2✔
163

1✔
164
    Session session_3 = fixture.make_session(db_3, "test%abc ");
2✔
165
    session_3.set_connection_state_change_listener(listener);
2✔
166
    session_3.bind();
2✔
167

1✔
168
    session_1.wait_for_download_complete_or_client_stopped();
2✔
169
    session_2.wait_for_download_complete_or_client_stopped();
2✔
170
    session_3.wait_for_download_complete_or_client_stopped();
2✔
171
    CHECK_EQUAL(nerrors, 3);
2✔
172
}
2✔
173

174

175
TEST(Sync_AsyncWaitForUploadCompletion)
176
{
2✔
177
    TEST_DIR(dir);
2✔
178
    TEST_CLIENT_DB(db);
2✔
179
    ClientServerFixture fixture(dir, test_context);
2✔
180
    fixture.start();
2✔
181

1✔
182
    Session session = fixture.make_bound_session(db, "/test");
2✔
183

1✔
184
    auto wait = [&] {
8✔
185
        BowlOfStonesSemaphore bowl;
8✔
186
        auto handler = [&](Status status) {
8✔
187
            if (CHECK(status.is_ok()))
8✔
188
                bowl.add_stone();
8✔
189
        };
8✔
190
        session.async_wait_for_upload_completion(handler);
8✔
191
        bowl.get_stone();
8✔
192
    };
8✔
193

1✔
194
    // Empty
1✔
195
    wait();
2✔
196

1✔
197
    // Nonempty
1✔
198
    write_transaction(db, [](WriteTransaction& wt) {
2✔
199
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
200
    });
2✔
201
    wait();
2✔
202

1✔
203
    // Already done
1✔
204
    wait();
2✔
205

1✔
206
    // More
1✔
207
    write_transaction(db, [](WriteTransaction& wt) {
2✔
208
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
209
    });
2✔
210
    wait();
2✔
211
}
2✔
212

213

214
TEST(Sync_AsyncWaitForUploadCompletionNoPendingLocalChanges)
215
{
2✔
216
    TEST_DIR(dir);
2✔
217
    TEST_CLIENT_DB(db);
2✔
218
    ClientServerFixture fixture(dir, test_context);
2✔
219
    fixture.start();
2✔
220

1✔
221
    Session session = fixture.make_bound_session(db, "/test");
2✔
222

1✔
223
    write_transaction(db, [](WriteTransaction& wt) {
2✔
224
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
225
    });
2✔
226

1✔
227
    auto pf = util::make_promise_future<bool>();
2✔
228
    session.async_wait_for_upload_completion(
2✔
229
        [promise = std::move(pf.promise), tr = db->start_read()](Status status) mutable {
2✔
230
            REALM_ASSERT(status.is_ok());
2✔
231
            tr->advance_read();
2✔
232
            promise.emplace_value(tr->get_history()->no_pending_local_changes(tr->get_version()));
2✔
233
        });
2✔
234
    CHECK(pf.future.get());
2✔
235
}
2✔
236

237

238
TEST(Sync_AsyncWaitForDownloadCompletion)
239
{
2✔
240
    TEST_DIR(dir);
2✔
241
    TEST_CLIENT_DB(db_1);
2✔
242
    TEST_CLIENT_DB(db_2);
2✔
243
    ClientServerFixture fixture(dir, test_context);
2✔
244
    fixture.start();
2✔
245

1✔
246
    auto wait = [&](Session& session) {
12✔
247
        BowlOfStonesSemaphore bowl;
12✔
248
        auto handler = [&](Status status) {
12✔
249
            if (CHECK(status.is_ok()))
12✔
250
                bowl.add_stone();
12✔
251
        };
12✔
252
        session.async_wait_for_download_completion(handler);
12✔
253
        bowl.get_stone();
12✔
254
    };
12✔
255

1✔
256
    // Nothing to download
1✔
257
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
258
    wait(session_1);
2✔
259

1✔
260
    // Again
1✔
261
    wait(session_1);
2✔
262

1✔
263
    // Upload something via session 2
1✔
264
    Session session_2 = fixture.make_bound_session(db_2, "/test");
2✔
265
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
266
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
267
    });
2✔
268
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
269

1✔
270
    // Wait for session 1 to download it
1✔
271
    wait(session_1);
2✔
272
    {
2✔
273
        ReadTransaction rt_1(db_1);
2✔
274
        ReadTransaction rt_2(db_2);
2✔
275
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
276
    }
2✔
277

1✔
278
    // Again
1✔
279
    wait(session_1);
2✔
280

1✔
281
    // Wait for session 2 to download nothing
1✔
282
    wait(session_2);
2✔
283

1✔
284
    // Upload something via session 1
1✔
285
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
286
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
287
    });
2✔
288
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
289

1✔
290
    // Wait for session 2 to download it
1✔
291
    wait(session_2);
2✔
292
    {
2✔
293
        ReadTransaction rt_1(db_1);
2✔
294
        ReadTransaction rt_2(db_2);
2✔
295
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
296
    }
2✔
297
}
2✔
298

299

300
TEST(Sync_AsyncWaitForSyncCompletion)
301
{
2✔
302
    TEST_DIR(dir);
2✔
303
    TEST_CLIENT_DB(db_1);
2✔
304
    TEST_CLIENT_DB(db_2);
2✔
305
    ClientServerFixture fixture(dir, test_context);
2✔
306
    fixture.start();
2✔
307

1✔
308
    auto wait = [&](Session& session) {
8✔
309
        BowlOfStonesSemaphore bowl;
8✔
310
        auto handler = [&](Status status) {
8✔
311
            if (CHECK(status.is_ok()))
8✔
312
                bowl.add_stone();
8✔
313
        };
8✔
314
        session.async_wait_for_sync_completion(handler);
8✔
315
        bowl.get_stone();
8✔
316
    };
8✔
317

1✔
318
    // Nothing to synchronize
1✔
319
    Session session_1 = fixture.make_bound_session(db_1);
2✔
320
    wait(session_1);
2✔
321

1✔
322
    // Again
1✔
323
    wait(session_1);
2✔
324

1✔
325
    // Generate changes to be downloaded (uploading via session 2)
1✔
326
    Session session_2 = fixture.make_bound_session(db_2);
2✔
327
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
328
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
329
    });
2✔
330
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
331

1✔
332
    // Generate changes to be uploaded
1✔
333
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
334
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
335
    });
2✔
336

1✔
337
    // Nontrivial synchronization (upload and download required)
1✔
338
    wait(session_1);
2✔
339
    wait(session_2);
2✔
340

1✔
341
    {
2✔
342
        ReadTransaction rt_1(db_1);
2✔
343
        ReadTransaction rt_2(db_2);
2✔
344
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
345
    }
2✔
346
}
2✔
347

348

349
TEST(Sync_AsyncWaitCancellation)
350
{
2✔
351
    TEST_DIR(dir);
2✔
352
    TEST_CLIENT_DB(db);
2✔
353
    ClientServerFixture fixture(dir, test_context);
2✔
354

1✔
355
    BowlOfStonesSemaphore bowl;
2✔
356
    auto completion_handler = [&](Status status) {
6✔
357
        CHECK_EQUAL(status, ErrorCodes::OperationAborted);
6✔
358
        bowl.add_stone();
6✔
359
    };
6✔
360
    {
2✔
361
        Session session = fixture.make_bound_session(db, "/test");
2✔
362
        session.async_wait_for_upload_completion(completion_handler);
2✔
363
        session.async_wait_for_download_completion(completion_handler);
2✔
364
        session.async_wait_for_sync_completion(completion_handler);
2✔
365
        // Destruction of session cancels wait operations
1✔
366
    }
2✔
367
    fixture.start();
2✔
368
    bowl.get_stone();
2✔
369
    bowl.get_stone();
2✔
370
    bowl.get_stone();
2✔
371
}
2✔
372

373

374
TEST(Sync_WaitForUploadCompletion)
375
{
2✔
376
    TEST_DIR(dir);
2✔
377
    TEST_CLIENT_DB(db);
2✔
378
    ClientServerFixture fixture{dir, test_context};
2✔
379
    std::string virtual_path = "/test";
2✔
380
    std::string server_path = fixture.map_virtual_to_real_path(virtual_path);
2✔
381
    fixture.start();
2✔
382

1✔
383
    // Empty
1✔
384
    Session session = fixture.make_bound_session(db);
2✔
385
    // Since the Realm is empty, the following wait operation can complete
1✔
386
    // without the client ever having been in contact with the server
1✔
387
    session.wait_for_upload_complete_or_client_stopped();
2✔
388

1✔
389
    // Nonempty
1✔
390
    write_transaction(db, [](WriteTransaction& wt) {
2✔
391
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
392
    });
2✔
393
    // Since the Realm is no longer empty, the following wait operation cannot
1✔
394
    // complete until the client has been in contact with the server, and caused
1✔
395
    // the server to create the server-side file
1✔
396
    session.wait_for_upload_complete_or_client_stopped();
2✔
397
    CHECK(util::File::exists(server_path));
2✔
398

1✔
399
    // Already done
1✔
400
    session.wait_for_upload_complete_or_client_stopped();
2✔
401

1✔
402
    // More changes
1✔
403
    write_transaction(db, [](WriteTransaction& wt) {
2✔
404
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
405
    });
2✔
406
    session.wait_for_upload_complete_or_client_stopped();
2✔
407
}
2✔
408

409

410
TEST(Sync_WaitForUploadCompletionAfterEmptyTransaction)
411
{
2✔
412
    TEST_DIR(dir);
2✔
413
    TEST_CLIENT_DB(db);
2✔
414
    ClientServerFixture fixture(dir, test_context);
2✔
415
    fixture.start();
2✔
416

1✔
417
    Session session = fixture.make_bound_session(db);
2✔
418
    for (int i = 0; i < 100; ++i) {
202✔
419
        WriteTransaction wt(db);
200✔
420
        wt.commit();
200✔
421
        session.wait_for_upload_complete_or_client_stopped();
200✔
422
    }
200✔
423
    {
2✔
424
        WriteTransaction wt(db);
2✔
425
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
426
        wt.commit();
2✔
427
        session.wait_for_upload_complete_or_client_stopped();
2✔
428
    }
2✔
429
}
2✔
430

431

432
TEST(Sync_WaitForDownloadCompletion)
433
{
2✔
434
    TEST_DIR(dir);
2✔
435
    TEST_CLIENT_DB(db_1);
2✔
436
    TEST_CLIENT_DB(db_2);
2✔
437
    ClientServerFixture fixture(dir, test_context);
2✔
438
    fixture.start();
2✔
439

1✔
440
    // Noting to download
1✔
441
    Session session_1 = fixture.make_bound_session(db_1);
2✔
442
    session_1.wait_for_download_complete_or_client_stopped();
2✔
443

1✔
444
    // Again
1✔
445
    session_1.wait_for_download_complete_or_client_stopped();
2✔
446

1✔
447
    // Upload something via session 2
1✔
448
    Session session_2 = fixture.make_bound_session(db_2);
2✔
449
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
450
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
451
    });
2✔
452
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
453

1✔
454
    // Wait for session 1 to download it
1✔
455
    session_1.wait_for_download_complete_or_client_stopped();
2✔
456
    {
2✔
457
        ReadTransaction rt_1(db_1);
2✔
458
        ReadTransaction rt_2(db_2);
2✔
459
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
460
    }
2✔
461

1✔
462
    // Again
1✔
463
    session_1.wait_for_download_complete_or_client_stopped();
2✔
464

1✔
465
    // Wait for session 2 to download nothing
1✔
466
    session_2.wait_for_download_complete_or_client_stopped();
2✔
467

1✔
468
    // Upload something via session 1
1✔
469
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
470
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
471
    });
2✔
472
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
473

1✔
474
    // Wait for session 2 to download it
1✔
475
    session_2.wait_for_download_complete_or_client_stopped();
2✔
476
    {
2✔
477
        ReadTransaction rt_1(db_1);
2✔
478
        ReadTransaction rt_2(db_2);
2✔
479
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
480
    }
2✔
481
}
2✔
482

483

484
TEST(Sync_WaitForDownloadCompletionAfterEmptyTransaction)
485
{
2✔
486
    TEST_DIR(dir);
2✔
487
    TEST_CLIENT_DB(db);
2✔
488
    ClientServerFixture fixture(dir, test_context);
2✔
489

1✔
490
    {
2✔
491
        WriteTransaction wt(db);
2✔
492
        wt.commit();
2✔
493
    }
2✔
494
    fixture.start();
2✔
495
    for (int i = 0; i < 8; ++i) {
18✔
496
        Session session = fixture.make_bound_session(db, "/test");
16✔
497
        session.wait_for_download_complete_or_client_stopped();
16✔
498
        session.wait_for_download_complete_or_client_stopped();
16✔
499
        {
16✔
500
            WriteTransaction wt(db);
16✔
501
            wt.commit();
16✔
502
        }
16✔
503
        session.wait_for_download_complete_or_client_stopped();
16✔
504
        session.wait_for_download_complete_or_client_stopped();
16✔
505
    }
16✔
506
}
2✔
507

508

509
TEST(Sync_WaitForDownloadCompletionManyConcurrent)
510
{
2✔
511
    TEST_DIR(dir);
2✔
512
    TEST_CLIENT_DB(db);
2✔
513
    ClientServerFixture fixture(dir, test_context);
2✔
514
    fixture.start();
2✔
515

1✔
516
    Session session = fixture.make_bound_session(db);
2✔
517
    constexpr int num_threads = 8;
2✔
518
    std::thread threads[num_threads];
2✔
519
    for (int i = 0; i < num_threads; ++i) {
18✔
520
        auto handler = [&] {
16✔
521
            session.wait_for_download_complete_or_client_stopped();
16✔
522
        };
16✔
523
        threads[i] = std::thread{handler};
16✔
524
    }
16✔
525
    for (int i = 0; i < num_threads; ++i)
18✔
526
        threads[i].join();
16✔
527
}
2✔
528

529

530
TEST(Sync_WaitForSessionTerminations)
531
{
2✔
532
    TEST_DIR(server_dir);
2✔
533
    TEST_CLIENT_DB(db);
2✔
534

1✔
535
    ClientServerFixture fixture(server_dir, test_context);
2✔
536
    fixture.start();
2✔
537

1✔
538
    Session session = fixture.make_bound_session(db, "/test");
2✔
539
    session.wait_for_download_complete_or_client_stopped();
2✔
540
    // Note: Atomicity would not be needed if
1✔
541
    // Session::async_wait_for_download_completion() was assumed to work.
1✔
542
    std::atomic<bool> called{false};
2✔
543
    auto handler = [&](Status) {
2✔
544
        called = true;
2✔
545
    };
2✔
546
    session.async_wait_for_download_completion(std::move(handler));
2✔
547
    session.detach();
2✔
548
    // The completion handler of an asynchronous wait operation is guaranteed
1✔
549
    // to be called, and no later than at session termination time. Also, any
1✔
550
    // callback function associated with a session on which termination has been
1✔
551
    // initiated, including the completion handler of the asynchronous wait
1✔
552
    // operation, must have finished executing when
1✔
553
    // Client::wait_for_session_terminations_or_client_stopped() returns.
1✔
554
    fixture.wait_for_session_terminations_or_client_stopped();
2✔
555
    CHECK(called);
2✔
556
}
2✔
557

558

559
TEST(Sync_TokenWithoutExpirationAllowed)
560
{
2✔
561
    bool did_fail = false;
2✔
562
    {
2✔
563
        TEST_DIR(dir);
2✔
564
        TEST_CLIENT_DB(db);
2✔
565
        ClientServerFixture fixture(dir, test_context);
2✔
566

1✔
567
        auto listener = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
4✔
568
            if (state != ConnectionState::disconnected)
4✔
569
                return;
4✔
570
            REALM_ASSERT(error_info);
×
571
            CHECK_EQUAL(error_info->status, ErrorCodes::SyncPermissionDenied);
×
572
            did_fail = true;
×
573
            fixture.stop();
×
574
        };
×
575

1✔
576
        fixture.start();
2✔
577

1✔
578
        Session::Config sess_config;
2✔
579
        sess_config.signed_user_token = g_signed_test_user_token_expiration_unspecified;
2✔
580
        Session session = fixture.make_session(db, "/test", std::move(sess_config));
2✔
581
        session.set_connection_state_change_listener(listener);
2✔
582
        session.bind();
2✔
583
        write_transaction(db, [](WriteTransaction& wt) {
2✔
584
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
585
        });
2✔
586
        session.wait_for_upload_complete_or_client_stopped();
2✔
587
        session.wait_for_download_complete_or_client_stopped();
2✔
588
    }
2✔
589
    CHECK_NOT(did_fail);
2✔
590
}
2✔
591

592

593
TEST(Sync_TokenWithNullExpirationAllowed)
594
{
2✔
595
    bool did_fail = false;
2✔
596
    {
2✔
597
        TEST_DIR(dir);
2✔
598
        TEST_CLIENT_DB(db);
2✔
599
        ClientServerFixture fixture(dir, test_context);
2✔
600
        auto error_handler = [&](Status, bool) {
1✔
601
            did_fail = true;
×
602
            fixture.stop();
×
603
        };
×
604
        fixture.set_client_side_error_handler(error_handler);
2✔
605
        fixture.start();
2✔
606

1✔
607
        Session::Config config;
2✔
608
        config.signed_user_token = g_signed_test_user_token_expiration_null;
2✔
609
        Session session = fixture.make_session(db, "/test", std::move(config));
2✔
610
        session.bind();
2✔
611
        {
2✔
612
            write_transaction(db, [](WriteTransaction& wt) {
2✔
613
                wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
614
            });
2✔
615
        }
2✔
616
        session.wait_for_upload_complete_or_client_stopped();
2✔
617
        session.wait_for_download_complete_or_client_stopped();
2✔
618
    }
2✔
619
    CHECK_NOT(did_fail);
2✔
620
}
2✔
621

622

623
TEST(Sync_Upload)
624
{
2✔
625
    TEST_DIR(dir);
2✔
626
    TEST_CLIENT_DB(db);
2✔
627
    ClientServerFixture fixture(dir, test_context);
2✔
628
    fixture.start();
2✔
629

1✔
630
    Session session = fixture.make_bound_session(db);
2✔
631

1✔
632
    {
2✔
633
        write_transaction(db, [](WriteTransaction& wt) {
2✔
634
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
635
            table->add_column(type_Int, "i");
2✔
636
        });
2✔
637
        for (int i = 0; i < 100; ++i) {
202✔
638
            WriteTransaction wt(db);
200✔
639
            TableRef table = wt.get_table("class_foo");
200✔
640
            table->create_object_with_primary_key(i);
200✔
641
            wt.commit();
200✔
642
        }
200✔
643
    }
2✔
644
    session.wait_for_upload_complete_or_client_stopped();
2✔
645
    session.wait_for_download_complete_or_client_stopped();
2✔
646
}
2✔
647

648

649
TEST(Sync_Replication)
650
{
2✔
651
    // Replicate changes in file 1 to file 2.
1✔
652

1✔
653
    TEST_CLIENT_DB(db_1);
2✔
654
    TEST_CLIENT_DB(db_2);
2✔
655

1✔
656
    {
2✔
657
        TEST_DIR(dir);
2✔
658
        ClientServerFixture fixture(dir, test_context);
2✔
659
        fixture.start();
2✔
660

1✔
661
        Session session_1 = fixture.make_bound_session(db_1);
2✔
662

1✔
663
        Session session_2 = fixture.make_session(db_2, "/test");
2✔
664
        session_2.bind();
2✔
665

1✔
666
        // Create schema
1✔
667
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
668
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
669
            table->add_column(type_Int, "i");
2✔
670
        });
2✔
671
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
672
        for (int i = 0; i < 100; ++i) {
202✔
673
            WriteTransaction wt(db_1);
200✔
674
            TableRef table = wt.get_table("class_foo");
200✔
675
            table->create_object_with_primary_key(i);
200✔
676
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
200✔
677
            obj.set<int64_t>("i", random.draw_int_max(0x7FFFFFFFFFFFFFFF));
200✔
678
            wt.commit();
200✔
679
        }
200✔
680

1✔
681
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
682
        session_2.wait_for_download_complete_or_client_stopped();
2✔
683
    }
2✔
684

1✔
685
    ReadTransaction rt_1(db_1);
2✔
686
    ReadTransaction rt_2(db_2);
2✔
687
    const Group& group_1 = rt_1;
2✔
688
    const Group& group_2 = rt_2;
2✔
689
    group_1.verify();
2✔
690
    group_2.verify();
2✔
691
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
692
    ConstTableRef table = group_1.get_table("class_foo");
2✔
693
    CHECK_EQUAL(100, table->size());
2✔
694
}
2✔
695

696

697
TEST(Sync_Merge)
698
{
2✔
699

1✔
700
    TEST_CLIENT_DB(db_1);
2✔
701
    TEST_CLIENT_DB(db_2);
2✔
702

1✔
703
    {
2✔
704
        TEST_DIR(dir);
2✔
705
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
706
        fixture.start();
2✔
707

1✔
708
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
709
        session_1.bind();
2✔
710

1✔
711
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
712
        session_2.bind();
2✔
713

1✔
714
        // Create schema on both clients.
1✔
715
        auto create_schema = [](DBRef db) {
4✔
716
            WriteTransaction wt(db);
4✔
717
            if (wt.has_table("class_foo"))
4✔
718
                return;
×
719
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
4✔
720
            table->add_column(type_Int, "i");
4✔
721
            wt.commit();
4✔
722
        };
4✔
723
        create_schema(db_1);
2✔
724
        create_schema(db_2);
2✔
725

1✔
726
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
727
            TableRef table = wt.get_table("class_foo");
2✔
728
            table->create_object_with_primary_key(1).set("i", 5);
2✔
729
            table->create_object_with_primary_key(2).set("i", 6);
2✔
730
        });
2✔
731
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
732
            TableRef table = wt.get_table("class_foo");
2✔
733
            table->create_object_with_primary_key(3).set("i", 7);
2✔
734
            table->create_object_with_primary_key(4).set("i", 8);
2✔
735
        });
2✔
736

1✔
737
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
738
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
739
        session_1.wait_for_download_complete_or_client_stopped();
2✔
740
        session_2.wait_for_download_complete_or_client_stopped();
2✔
741
    }
2✔
742

1✔
743
    ReadTransaction rt_1(db_1);
2✔
744
    ReadTransaction rt_2(db_2);
2✔
745
    const Group& group_1 = rt_1;
2✔
746
    const Group& group_2 = rt_2;
2✔
747
    group_1.verify();
2✔
748
    group_2.verify();
2✔
749
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
750
    ConstTableRef table = group_1.get_table("class_foo");
2✔
751
    CHECK_EQUAL(4, table->size());
2✔
752
}
2✔
753

754
struct ExpectChangesetError {
755
    unit_test::TestContext& test_context;
756
    MultiClientServerFixture& fixture;
757
    std::string expected_error;
758

759
    void operator()(ConnectionState state, util::Optional<ErrorInfo> error_info) const noexcept
760
    {
57✔
761
        if (state == ConnectionState::disconnected) {
57✔
762
            return;
×
763
        }
×
764
        if (!error_info)
57✔
765
            return;
45✔
766
        REALM_ASSERT(error_info);
12✔
767
        CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
12✔
768
        CHECK(!error_info->is_fatal);
12✔
769
        CHECK_EQUAL(error_info->status.reason(),
12✔
770
                    "Failed to transform received changeset: Schema mismatch: " + expected_error);
12✔
771
        fixture.stop();
12✔
772
    }
12✔
773
};
774

775
void test_schema_mismatch(unit_test::TestContext& test_context, util::FunctionRef<void(WriteTransaction&)> fn_1,
776
                          util::FunctionRef<void(WriteTransaction&)> fn_2, const char* expected_error_1,
777
                          const char* expected_error_2 = nullptr)
778
{
12✔
779
    auto perform_write_transaction = [](DBRef db, util::FunctionRef<void(WriteTransaction&)> function) {
24✔
780
        WriteTransaction wt(db);
24✔
781
        function(wt);
24✔
782
        return wt.commit();
24✔
783
    };
24✔
784

6✔
785
    TEST_DIR(dir);
12✔
786
    TEST_CLIENT_DB(db_1);
12✔
787
    TEST_CLIENT_DB(db_2);
12✔
788

6✔
789
    perform_write_transaction(db_1, fn_1);
12✔
790
    perform_write_transaction(db_2, fn_2);
12✔
791

6✔
792
    MultiClientServerFixture fixture(2, 1, dir, test_context);
12✔
793
    fixture.allow_server_errors(0, 1);
12✔
794
    fixture.start();
12✔
795

6✔
796
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
12✔
797
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
12✔
798

6✔
799
    if (!expected_error_2)
12✔
800
        expected_error_2 = expected_error_1;
4✔
801

6✔
802
    session_1.set_connection_state_change_listener(ExpectChangesetError{test_context, fixture, expected_error_1});
12✔
803
    session_2.set_connection_state_change_listener(ExpectChangesetError{test_context, fixture, expected_error_2});
12✔
804

6✔
805
    session_1.bind();
12✔
806
    session_2.bind();
12✔
807

6✔
808
    session_1.wait_for_upload_complete_or_client_stopped();
12✔
809
    session_2.wait_for_upload_complete_or_client_stopped();
12✔
810
    session_1.wait_for_download_complete_or_client_stopped();
12✔
811
    session_2.wait_for_download_complete_or_client_stopped();
12✔
812
}
12✔
813

814

815
TEST(Sync_DetectSchemaMismatch_ColumnType)
816
{
2✔
817
    test_schema_mismatch(
2✔
818
        test_context,
2✔
819
        [](WriteTransaction& wt) {
2✔
820
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
821
            ColKey col_ndx = table->add_column(type_Int, "column");
2✔
822
            table->create_object_with_primary_key(1).set<int64_t>(col_ndx, 123);
2✔
823
        },
2✔
824
        [](WriteTransaction& wt) {
2✔
825
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
826
            ColKey col_ndx = table->add_column(type_String, "column");
2✔
827
            table->create_object_with_primary_key(2).set(col_ndx, "Hello, World!");
2✔
828
        },
2✔
829
        "Property 'column' in class 'foo' is of type Int on one side and type String on the other.",
2✔
830
        "Property 'column' in class 'foo' is of type String on one side and type Int on the other.");
2✔
831
}
2✔
832

833

834
TEST(Sync_DetectSchemaMismatch_Nullability)
835
{
2✔
836
    test_schema_mismatch(
2✔
837
        test_context,
2✔
838
        [](WriteTransaction& wt) {
2✔
839
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
840
            bool nullable = false;
2✔
841
            ColKey col_ndx = table->add_column(type_Int, "column", nullable);
2✔
842
            table->create_object_with_primary_key(1).set<int64_t>(col_ndx, 123);
2✔
843
        },
2✔
844
        [](WriteTransaction& wt) {
2✔
845
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
846
            bool nullable = true;
2✔
847
            ColKey col_ndx = table->add_column(type_Int, "column", nullable);
2✔
848
            table->create_object_with_primary_key(2).set<int64_t>(col_ndx, 123);
2✔
849
        },
2✔
850
        "Property 'column' in class 'foo' is nullable on one side and not on the other.");
2✔
851
}
2✔
852

853

854
TEST(Sync_DetectSchemaMismatch_Links)
855
{
2✔
856
    test_schema_mismatch(
2✔
857
        test_context,
2✔
858
        [](WriteTransaction& wt) {
2✔
859
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
860
            TableRef target = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
861
            table->add_column(*target, "column");
2✔
862
        },
2✔
863
        [](WriteTransaction& wt) {
2✔
864
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
865
            TableRef target = wt.get_group().add_table_with_primary_key("class_baz", type_Int, "id");
2✔
866
            table->add_column(*target, "column");
2✔
867
        },
2✔
868
        "Link property 'column' in class 'foo' points to class 'bar' on one side and to 'baz' on the other.",
2✔
869
        "Link property 'column' in class 'foo' points to class 'baz' on one side and to 'bar' on the other.");
2✔
870
}
2✔
871

872

873
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Name)
874
{
2✔
875
    test_schema_mismatch(
2✔
876
        test_context,
2✔
877
        [](WriteTransaction& wt) {
2✔
878
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a");
2✔
879
        },
2✔
880
        [](WriteTransaction& wt) {
2✔
881
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "b");
2✔
882
        },
2✔
883
        "'foo' has primary key 'a' on one side, but primary key 'b' on the other.",
2✔
884
        "'foo' has primary key 'b' on one side, but primary key 'a' on the other.");
2✔
885
}
2✔
886

887

888
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Type)
889
{
2✔
890
    test_schema_mismatch(
2✔
891
        test_context,
2✔
892
        [](WriteTransaction& wt) {
2✔
893
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a");
2✔
894
        },
2✔
895
        [](WriteTransaction& wt) {
2✔
896
            wt.get_group().add_table_with_primary_key("class_foo", type_String, "a");
2✔
897
        },
2✔
898
        "'foo' has primary key 'a', which is of type Int on one side and type String on the other.",
2✔
899
        "'foo' has primary key 'a', which is of type String on one side and type Int on the other.");
2✔
900
}
2✔
901

902

903
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Nullability)
904
{
2✔
905
    test_schema_mismatch(
2✔
906
        test_context,
2✔
907
        [](WriteTransaction& wt) {
2✔
908
            bool nullable = false;
2✔
909
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a", nullable);
2✔
910
        },
2✔
911
        [](WriteTransaction& wt) {
2✔
912
            bool nullable = true;
2✔
913
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a", nullable);
2✔
914
        },
2✔
915
        "'foo' has primary key 'a', which is nullable on one side, but not the other.");
2✔
916
}
2✔
917

918

919
TEST(Sync_LateBind)
920
{
2✔
921
    // Test that a session can be initiated at a point in time where the client
1✔
922
    // already has established a connection to the server.
1✔
923

1✔
924
    TEST_CLIENT_DB(db_1);
2✔
925
    TEST_CLIENT_DB(db_2);
2✔
926

1✔
927
    {
2✔
928
        TEST_DIR(dir);
2✔
929
        ClientServerFixture fixture(dir, test_context);
2✔
930
        fixture.start();
2✔
931

1✔
932
        Session session_1 = fixture.make_bound_session(db_1);
2✔
933
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
934
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
935
        });
2✔
936
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
937

1✔
938
        Session session_2 = fixture.make_bound_session(db_2);
2✔
939
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
940
            wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
941
        });
2✔
942
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
943

1✔
944
        session_1.wait_for_download_complete_or_client_stopped();
2✔
945
        session_2.wait_for_download_complete_or_client_stopped();
2✔
946
    }
2✔
947

1✔
948
    ReadTransaction rt_1(db_1);
2✔
949
    ReadTransaction rt_2(db_2);
2✔
950
    const Group& group_1 = rt_1;
2✔
951
    const Group& group_2 = rt_2;
2✔
952
    group_1.verify();
2✔
953
    group_2.verify();
2✔
954
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
955
    CHECK_EQUAL(2, group_1.size());
2✔
956
}
2✔
957

958

959
TEST(Sync_EarlyUnbind)
960
{
2✔
961
    // Verify that it is possible to unbind one session while another session
1✔
962
    // keeps the connection to the server open.
1✔
963

1✔
964
    TEST_DIR(dir);
2✔
965
    TEST_CLIENT_DB(db_1);
2✔
966
    TEST_CLIENT_DB(db_2);
2✔
967
    TEST_CLIENT_DB(db_3);
2✔
968
    ClientServerFixture fixture(dir, test_context);
2✔
969
    fixture.start();
2✔
970

1✔
971
    // Session 1 is here only to keep the connection alive
1✔
972
    Session session_1 = fixture.make_bound_session(db_1, "/dummy");
2✔
973
    {
2✔
974
        Session session_2 = fixture.make_bound_session(db_2);
2✔
975
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
976
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
977
        });
2✔
978
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
979
        // Session 2 is now connected, but will be abandoned at end of scope
1✔
980
    }
2✔
981
    {
2✔
982
        // Starting a new session 3 forces closure of all previously abandoned
1✔
983
        // sessions, in turn forcing session 2 to be enlisted for writing its
1✔
984
        // UNBIND before session 3 is enlisted for writing BIND.
1✔
985
        Session session_3 = fixture.make_bound_session(db_3);
2✔
986
        // We now use MARK messages to wait for a complete unbind of session
1✔
987
        // 2. The client is guaranteed to receive the UNBIND response for session
1✔
988
        // 2 before it receives the MARK response for session 3.
1✔
989
        session_3.wait_for_download_complete_or_client_stopped();
2✔
990
    }
2✔
991
}
2✔
992

993

994
TEST(Sync_FastRebind)
995
{
2✔
996
    // Verify that it is possible to create multiple immediately consecutive
1✔
997
    // sessions for the same Realm file.
1✔
998

1✔
999
    TEST_DIR(dir);
2✔
1000
    TEST_CLIENT_DB(db_1);
2✔
1001
    TEST_CLIENT_DB(db_2);
2✔
1002
    ClientServerFixture fixture(dir, test_context);
2✔
1003
    fixture.start();
2✔
1004

1✔
1005
    // Session 1 is here only to keep the connection alive
1✔
1006
    Session session_1 = fixture.make_bound_session(db_1, "/dummy");
2✔
1007
    {
2✔
1008
        Session session_2 = fixture.make_bound_session(db_2, "/test");
2✔
1009
        WriteTransaction wt(db_2);
2✔
1010
        TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1011
        table->add_column(type_Int, "i");
2✔
1012
        table->create_object_with_primary_key(1);
2✔
1013
        wt.commit();
2✔
1014
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1015
    }
2✔
1016
    for (int i = 0; i < 100; ++i) {
202✔
1017
        Session session_2 = fixture.make_bound_session(db_2, "/test");
200✔
1018
        WriteTransaction wt(db_2);
200✔
1019
        TableRef table = wt.get_table("class_foo");
200✔
1020
        table->begin()->set<int64_t>("i", i);
200✔
1021
        wt.commit();
200✔
1022
        session_2.wait_for_upload_complete_or_client_stopped();
200✔
1023
    }
200✔
1024
}
2✔
1025

1026

1027
TEST(Sync_UnbindBeforeActivation)
1028
{
2✔
1029
    // This test tries to make it likely that the server receives an UNBIND
1✔
1030
    // message for a session that is still not activated, i.e., before the
1✔
1031
    // server receives the IDENT message.
1✔
1032

1✔
1033
    TEST_DIR(dir);
2✔
1034
    TEST_CLIENT_DB(db_1);
2✔
1035
    TEST_CLIENT_DB(db_2);
2✔
1036
    ClientServerFixture fixture(dir, test_context);
2✔
1037
    fixture.start();
2✔
1038

1✔
1039
    // Session 1 is here only to keep the connection alive
1✔
1040
    Session session_1 = fixture.make_bound_session(db_1);
2✔
1041
    for (int i = 0; i < 1000; ++i) {
2,002✔
1042
        Session session_2 = fixture.make_bound_session(db_2);
2,000✔
1043
        session_2.wait_for_upload_complete_or_client_stopped();
2,000✔
1044
    }
2,000✔
1045
}
2✔
1046

1047

1048
TEST(Sync_AbandonUnboundSessions)
1049
{
2✔
1050
    TEST_DIR(dir);
2✔
1051
    TEST_CLIENT_DB(db_1);
2✔
1052
    TEST_CLIENT_DB(db_2);
2✔
1053
    TEST_CLIENT_DB(db_3);
2✔
1054
    ClientServerFixture fixture(dir, test_context);
2✔
1055
    fixture.start();
2✔
1056

1✔
1057
    int n = 32;
2✔
1058
    for (int i = 0; i < n; ++i) {
66✔
1059
        fixture.make_session(db_1, "/test");
64✔
1060
        fixture.make_session(db_2, "/test");
64✔
1061
        fixture.make_session(db_3, "/test");
64✔
1062
    }
64✔
1063

1✔
1064
    for (int i = 0; i < n; ++i) {
66✔
1065
        fixture.make_session(db_1, "/test");
64✔
1066
        Session session = fixture.make_session(db_2, "/test");
64✔
1067
        fixture.make_session(db_3, "/test");
64✔
1068
        session.bind();
64✔
1069
    }
64✔
1070

1✔
1071
    for (int i = 0; i < n; ++i) {
66✔
1072
        fixture.make_session(db_1, "/test");
64✔
1073
        Session session = fixture.make_session(db_2, "/test");
64✔
1074
        fixture.make_session(db_3, "/test");
64✔
1075
        session.bind();
64✔
1076
        session.wait_for_upload_complete_or_client_stopped();
64✔
1077
    }
64✔
1078

1✔
1079
    for (int i = 0; i < n; ++i) {
66✔
1080
        fixture.make_session(db_1, "/test");
64✔
1081
        Session session = fixture.make_session(db_2, "/test");
64✔
1082
        fixture.make_session(db_3, "/test");
64✔
1083
        session.bind();
64✔
1084
        session.wait_for_download_complete_or_client_stopped();
64✔
1085
    }
64✔
1086
}
2✔
1087

1088

1089
#if 0  // FIXME: Disabled because substring operations are not yet supported in Core 6.
1090

1091
// This test illustrates that our instruction set and merge rules
1092
// do not have higher order convergence. The final merge result depends
1093
// on the order with which the changesets reach the server. This example
1094
// employs three clients operating on the same state. The state consists
1095
// of two tables, "source" and "target". "source" has a link list pointing
1096
// to target. Target contains three rows 0, 1, and 2. Source contains one
1097
// row with a link list whose value is 2.
1098
//
1099
// The three clients produce changesets with client 1 having the earliest time
1100
// stamp, client 2 the middle time stamp, and client 3 the latest time stamp.
1101
// The clients produce the following changesets.
1102
//
1103
// client 1: target.move_last_over(0)
1104
// client 2: source.link_list.set(0, 0);
1105
// client 3: source.link_list.set(0, 1);
1106
//
1107
// In part a of the test, the order of the clients reaching the server is
1108
// 1, 2, 3. The result is an empty link list since the merge of client 1 and 2
1109
// produces a nullify link list instruction.
1110
//
1111
// In part b, the order of the clients reaching the server is 3, 1, 2. The
1112
// result is a link list of size 1, since client 3 wins due to having the
1113
// latest time stamp.
1114
//
1115
// If the "natural" peer to peer system of these merge rules were built, the
1116
// transition from server a to server b involves an insert link instruction. In
1117
// other words, the diff between two servers differing in the order of one
1118
// move_last_over and two link_list_set instructions is an insert instruction.
1119
// Repeated application of the pairwise merge rules would never produce this
1120
// result.
1121
//
1122
// The test is not run in general since it just checks that we do not have
1123
// higher order convergence, and the absence of higher order convergence is not
1124
// a desired feature in itself.
1125
TEST_IF(Sync_NonDeterministicMerge, false)
1126
{
1127
    TEST_DIR(dir);
1128
    TEST_CLIENT_DB(db_a1);
1129
    TEST_CLIENT_DB(db_a2);
1130
    TEST_CLIENT_DB(db_a3);
1131
    TEST_CLIENT_DB(db_b1);
1132
    TEST_CLIENT_DB(db_b2);
1133
    TEST_CLIENT_DB(db_b3);
1134

1135
    ClientServerFixture fixture{dir, test_context};
1136
    fixture.start();
1137

1138
    // Part a of the test.
1139
    {
1140
        WriteTransaction wt{db_a1};
1141

1142
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target");
1143
        ColKey col_ndx = table_target->add_column(type_Int, "value");
1144
        CHECK_EQUAL(col_ndx, 1);
1145
        Obj row0 = table_target->create_object_with_primary_key(i);
1146
        Obj row1 = table_target->create_object_with_primary_key(i);
1147
        Obj row2 = table_target->create_object_with_primary_key(i);
1148
        row0.set(col_ndx, 123);
1149
        row1.set(col_ndx, 456);
1150
        row2.set(col_ndx, 789);
1151

1152
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source");
1153
        col_ndx = table_source->add_column_link(type_LinkList, "target_link",
1154
                                                *table_target);
1155
        CHECK_EQUAL(col_ndx, 1);
1156
        Obj obj = table_source->create_object_with_primary_key(i);
1157
        auto ll = obj.get_linklist(col_ndx);
1158
        ll.insert(0, row2.get_key());
1159
        CHECK_EQUAL(ll.size(), 1);
1160
        wt.commit();
1161
    }
1162

1163
    {
1164
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1165
        session.wait_for_upload_complete_or_client_stopped();
1166
    }
1167

1168
    {
1169
        Session session = fixture.make_bound_session(db_a2, "/server-path-a");
1170
        session.wait_for_download_complete_or_client_stopped();
1171
    }
1172

1173
    {
1174
        Session session = fixture.make_bound_session(db_a3, "/server-path-a");
1175
        session.wait_for_download_complete_or_client_stopped();
1176
    }
1177

1178
    {
1179
        WriteTransaction wt{db_a1};
1180
        TableRef table = wt.get_table("class_target");
1181
        table->remove_object(table->begin());
1182
        CHECK_EQUAL(table->size(), 2);
1183
        wt.commit();
1184
    }
1185

1186
    {
1187
        WriteTransaction wt{db_a2};
1188
        TableRef table = wt.get_table("class_source");
1189
        auto ll = table->get_linklist(1, 0);
1190
        CHECK_EQUAL(ll->size(), 1);
1191
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1192
        ll->set(0, 0);
1193
        CHECK_EQUAL(ll->get(0).get_int(1), 123);
1194
        wt.commit();
1195
    }
1196

1197
    {
1198
        WriteTransaction wt{db_a3};
1199
        TableRef table = wt.get_table("class_source");
1200
        auto ll = table->get_linklist(1, 0);
1201
        CHECK_EQUAL(ll->size(), 1);
1202
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1203
        ll->set(0, 1);
1204
        CHECK_EQUAL(ll->get(0).get_int(1), 456);
1205
        wt.commit();
1206
    }
1207

1208
    {
1209
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1210
        session.wait_for_upload_complete_or_client_stopped();
1211
    }
1212

1213
    {
1214
        Session session = fixture.make_bound_session(db_a2, "/server-path-a");
1215
        session.wait_for_upload_complete_or_client_stopped();
1216
    }
1217

1218
    {
1219
        Session session = fixture.make_bound_session(db_a3, "/server-path-a");
1220
        session.wait_for_upload_complete_or_client_stopped();
1221
    }
1222

1223
    {
1224
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1225
        session.wait_for_download_complete_or_client_stopped();
1226
    }
1227

1228
    // Part b of the test.
1229
    {
1230
        WriteTransaction wt{db_b1};
1231

1232
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target");
1233
        ColKey col_ndx = table_target->add_column(type_Int, "value");
1234
        CHECK_EQUAL(col_ndx, 1);
1235
        table_target->create_object_with_primary_key(i);
1236
        table_target->create_object_with_primary_key(i);
1237
        table_target->create_object_with_primary_key(i);
1238
        table_target->begin()->set(col_ndx, 123);
1239
        table_target->get_object(1).set(col_ndx, 456);
1240
        table_target->get_object(2).set(col_ndx, 789);
1241

1242
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source");
1243
        col_ndx = table_source->add_column_link(type_LinkList, "target_link",
1244
                                                *table_target);
1245
        CHECK_EQUAL(col_ndx, 1);
1246
        table_source->create_object_with_primary_key(i);
1247
        auto ll = table_source->get_linklist(col_ndx, 0);
1248
        ll->insert(0, 2);
1249
        CHECK_EQUAL(ll->size(), 1);
1250
        wt.commit();
1251
    }
1252

1253
    {
1254
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1255
        session.wait_for_upload_complete_or_client_stopped();
1256
    }
1257

1258
    {
1259
        Session session = fixture.make_bound_session(db_b2, "/server-path-b");
1260
        session.wait_for_download_complete_or_client_stopped();
1261
    }
1262

1263
    {
1264
        Session session = fixture.make_bound_session(db_b3, "/server-path-b");
1265
        session.wait_for_download_complete_or_client_stopped();
1266
    }
1267

1268
    {
1269
        WriteTransaction wt{db_b1};
1270
        TableRef table = wt.get_table("class_target");
1271
        table->move_last_over(0);
1272
        CHECK_EQUAL(table->size(), 2);
1273
        wt.commit();
1274
    }
1275

1276
    {
1277
        WriteTransaction wt{db_b2};
1278
        TableRef table = wt.get_table("class_source");
1279
        auto ll = table->get_linklist(1, 0);
1280
        CHECK_EQUAL(ll->size(), 1);
1281
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1282
        ll->set(0, 0);
1283
        CHECK_EQUAL(ll->get(0).get_int(1), 123);
1284
        wt.commit();
1285
    }
1286

1287
    {
1288
        WriteTransaction wt{db_b3};
1289
        TableRef table = wt.get_table("class_source");
1290
        auto ll = table->get_linklist(1, 0);
1291
        CHECK_EQUAL(ll->size(), 1);
1292
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1293
        ll->set(0, 1);
1294
        CHECK_EQUAL(ll->get(0).get_int(1), 456);
1295
        wt.commit();
1296
    }
1297

1298
    // The crucial difference between part a and b is that client 3
1299
    // uploads it changes first in part b and last in part a.
1300
    {
1301
        Session session = fixture.make_bound_session(db_b3, "/server-path-b");
1302
        session.wait_for_upload_complete_or_client_stopped();
1303
    }
1304

1305
    {
1306
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1307
        session.wait_for_upload_complete_or_client_stopped();
1308
    }
1309

1310
    {
1311
        Session session = fixture.make_bound_session(db_b2, "/server-path-b");
1312
        session.wait_for_upload_complete_or_client_stopped();
1313
    }
1314

1315
    {
1316
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1317
        session.wait_for_download_complete_or_client_stopped();
1318
    }
1319

1320

1321
    // Check the end result.
1322

1323
    size_t size_link_list_a;
1324
    size_t size_link_list_b;
1325

1326
    {
1327
        ReadTransaction wt{db_a1};
1328
        ConstTableRef table = wt.get_table("class_source");
1329
        auto ll = table->get_linklist(1, 0);
1330
        size_link_list_a = ll->size();
1331
    }
1332

1333
    {
1334
        ReadTransaction wt{db_b1};
1335
        ConstTableRef table = wt.get_table("class_source");
1336
        auto ll = table->get_linklist(1, 0);
1337
        size_link_list_b = ll->size();
1338
        CHECK_EQUAL(ll->size(), 1);
1339
    }
1340

1341
    // The final link list has size 0 in part a and size 1 in part b.
1342
    // These checks confirm that the OT system behaves as expected.
1343
    // The expected behavior is higher order divergence.
1344
    CHECK_EQUAL(size_link_list_a, 0);
1345
    CHECK_EQUAL(size_link_list_b, 1);
1346
    CHECK_NOT_EQUAL(size_link_list_a, size_link_list_b);
1347
}
1348
#endif // 0
1349

1350

1351
TEST(Sync_Randomized)
1352
{
2✔
1353
    constexpr size_t num_clients = 7;
2✔
1354

1✔
1355
    auto client_test_program = [](DBRef db) {
14✔
1356
        // Create the schema
7✔
1357
        write_transaction(db, [](WriteTransaction& wt) {
14✔
1358
            if (wt.has_table("class_foo"))
14✔
1359
                return;
×
1360
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
14✔
1361
            table->add_column(type_Int, "i");
14✔
1362
            table->create_object_with_primary_key(1);
14✔
1363
        });
14✔
1364

7✔
1365
        Random random(random_int<unsigned long>()); // Seed from slow global generator
14✔
1366
        for (int i = 0; i < 100; ++i) {
1,414✔
1367
            WriteTransaction wt(db);
1,400✔
1368
            if (random.chance(4, 5)) {
1,400✔
1369
                TableRef table = wt.get_table("class_foo");
1,138✔
1370
                if (random.chance(1, 5)) {
1,138✔
1371
                    table->create_object_with_primary_key(i);
237✔
1372
                }
237✔
1373
                int value = random.draw_int(-32767, 32767);
1,138✔
1374
                size_t row_ndx = random.draw_int_mod(table->size());
1,138✔
1375
                table->get_object(row_ndx).set("i", value);
1,138✔
1376
            }
1,138✔
1377
            wt.commit();
1,400✔
1378
        }
1,400✔
1379
    };
14✔
1380

1✔
1381
    TEST_DIR(dir);
2✔
1382
    MultiClientServerFixture fixture(num_clients, 1, dir, test_context);
2✔
1383
    fixture.start();
2✔
1384

1✔
1385
    std::unique_ptr<DBTestPathGuard> client_path_guards[num_clients];
2✔
1386
    DBRef client_shared_groups[num_clients];
2✔
1387
    for (size_t i = 0; i < num_clients; ++i) {
16✔
1388
        std::string suffix = util::format(".client_%1.realm", i);
14✔
1389
        std::string test_path = get_test_path(test_context.get_test_name(), suffix);
14✔
1390
        client_path_guards[i].reset(new DBTestPathGuard(test_path));
14✔
1391
        client_shared_groups[i] = DB::create(make_client_replication(), test_path);
14✔
1392
    }
14✔
1393

1✔
1394
    std::vector<std::unique_ptr<Session>> sessions(num_clients);
2✔
1395
    for (size_t i = 0; i < num_clients; ++i) {
16✔
1396
        auto db = client_shared_groups[i];
14✔
1397
        sessions[i] = std::make_unique<Session>(fixture.make_session(int(i), 0, db, "/test"));
14✔
1398
        sessions[i]->bind();
14✔
1399
    }
14✔
1400

1✔
1401
    auto run_client_test_program = [&](size_t i) {
14✔
1402
        try {
14✔
1403
            client_test_program(client_shared_groups[i]);
14✔
1404
        }
14✔
1405
        catch (...) {
7✔
1406
            fixture.stop();
×
1407
            throw;
×
1408
        }
×
1409
    };
14✔
1410

1✔
1411
    ThreadWrapper client_program_threads[num_clients];
2✔
1412
    for (size_t i = 0; i < num_clients; ++i)
16✔
1413
        client_program_threads[i].start([=] {
14✔
1414
            run_client_test_program(i);
14✔
1415
        });
14✔
1416

1✔
1417
    for (size_t i = 0; i < num_clients; ++i)
16✔
1418
        CHECK(!client_program_threads[i].join());
14✔
1419

1✔
1420
    log("All client programs completed");
2✔
1421

1✔
1422
    // Wait until all local changes are uploaded, and acknowledged by the
1✔
1423
    // server.
1✔
1424
    for (size_t i = 0; i < num_clients; ++i)
16✔
1425
        sessions[i]->wait_for_upload_complete_or_client_stopped();
14✔
1426

1✔
1427
    log("Everything uploaded");
2✔
1428

1✔
1429
    // Now wait for all previously uploaded changes to be downloaded by all
1✔
1430
    // others.
1✔
1431
    for (size_t i = 0; i < num_clients; ++i)
16✔
1432
        sessions[i]->wait_for_download_complete_or_client_stopped();
14✔
1433

1✔
1434
    log("Everything downloaded");
2✔
1435

1✔
1436
    REALM_ASSERT(num_clients > 0);
2✔
1437
    ReadTransaction rt_0(client_shared_groups[0]);
2✔
1438
    rt_0.get_group().verify();
2✔
1439
    for (size_t i = 1; i < num_clients; ++i) {
14✔
1440
        ReadTransaction rt(client_shared_groups[i]);
12✔
1441
        rt.get_group().verify();
12✔
1442
        // Logger is guaranteed to be defined
6✔
1443
        CHECK(compare_groups(rt_0, rt, *test_context.logger));
12✔
1444
    }
12✔
1445
}
2✔
1446

1447
#ifdef REALM_DEBUG // Failure simulation only works in debug mode
1448

1449
TEST(Sync_ReadFailureSimulation)
1450
{
2✔
1451
    TEST_DIR(server_dir);
2✔
1452
    TEST_CLIENT_DB(db);
2✔
1453

1✔
1454
    // Check that read failure simulation works on the client-side
1✔
1455
    {
2✔
1456
        bool client_side_read_did_fail = false;
2✔
1457
        {
2✔
1458
            ClientServerFixture fixture(server_dir, test_context);
2✔
1459
            fixture.set_client_side_error_rate(1, 1); // 100% chance of failure
2✔
1460
            auto error_handler = [&](Status status, bool is_fatal) {
2✔
1461
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
2✔
1462
                CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read");
2✔
1463
                CHECK_NOT(is_fatal);
2✔
1464
                client_side_read_did_fail = true;
2✔
1465
                fixture.stop();
2✔
1466
            };
2✔
1467
            fixture.set_client_side_error_handler(error_handler);
2✔
1468
            Session session = fixture.make_bound_session(db, "/test");
2✔
1469
            fixture.start();
2✔
1470
            session.wait_for_download_complete_or_client_stopped();
2✔
1471
        }
2✔
1472
        CHECK(client_side_read_did_fail);
2✔
1473
    }
2✔
1474

1✔
1475
    // FIXME: Figure out a way to check that read failure simulation works on
1✔
1476
    // the server-side
1✔
1477
}
2✔
1478

1479
#endif // REALM_DEBUG
1480
TEST(Sync_FailingReadsOnClientSide)
1481
{
2✔
1482
    TEST_CLIENT_DB(db_1);
2✔
1483
    TEST_CLIENT_DB(db_2);
2✔
1484

1✔
1485
    {
2✔
1486
        TEST_DIR(dir);
2✔
1487
        ClientServerFixture fixture{dir, test_context};
2✔
1488
        fixture.set_client_side_error_rate(5, 100); // 5% chance of failure
2✔
1489
        auto error_handler = [&](Status status, bool is_fatal) {
434✔
1490
            if (CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read")) {
434✔
1491
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
434✔
1492
                CHECK_NOT(is_fatal);
434✔
1493
                fixture.cancel_reconnect_delay();
434✔
1494
            }
434✔
1495
        };
434✔
1496
        fixture.set_client_side_error_handler(error_handler);
2✔
1497
        fixture.start();
2✔
1498

1✔
1499
        Session session_1 = fixture.make_bound_session(db_1);
2✔
1500

1✔
1501
        Session session_2 = fixture.make_bound_session(db_2);
2✔
1502

1✔
1503
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
1504
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1505
            table->add_column(type_Int, "i");
2✔
1506
            table->create_object_with_primary_key(1);
2✔
1507
        });
2✔
1508
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
1509
            TableRef table = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
1510
            table->add_column(type_Int, "i");
2✔
1511
            table->create_object_with_primary_key(2);
2✔
1512
        });
2✔
1513
        for (int i = 0; i < 100; ++i) {
202✔
1514
            session_1.wait_for_upload_complete_or_client_stopped();
200✔
1515
            session_2.wait_for_upload_complete_or_client_stopped();
200✔
1516
            for (int i = 0; i < 10; ++i) {
2,200✔
1517
                write_transaction(db_1, [=](WriteTransaction& wt) {
2,000✔
1518
                    TableRef table = wt.get_table("class_foo");
2,000✔
1519
                    table->begin()->set("i", i);
2,000✔
1520
                });
2,000✔
1521
                write_transaction(db_2, [=](WriteTransaction& wt) {
2,000✔
1522
                    TableRef table = wt.get_table("class_bar");
2,000✔
1523
                    table->begin()->set("i", i);
2,000✔
1524
                });
2,000✔
1525
            }
2,000✔
1526
        }
200✔
1527
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1528
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1529
        session_1.wait_for_download_complete_or_client_stopped();
2✔
1530
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1531
    }
2✔
1532

1✔
1533
    ReadTransaction rt_1(db_1);
2✔
1534
    ReadTransaction rt_2(db_2);
2✔
1535
    const Group& group_1 = rt_1;
2✔
1536
    const Group& group_2 = rt_2;
2✔
1537
    group_1.verify();
2✔
1538
    group_2.verify();
2✔
1539
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
1540
}
2✔
1541

1542

1543
TEST(Sync_FailingReadsOnServerSide)
1544
{
2✔
1545
    TEST_CLIENT_DB(db_1);
2✔
1546
    TEST_CLIENT_DB(db_2);
2✔
1547

1✔
1548
    {
2✔
1549
        TEST_DIR(dir);
2✔
1550
        ClientServerFixture fixture{dir, test_context};
2✔
1551
        fixture.set_server_side_error_rate(5, 100); // 5% chance of failure
2✔
1552
        auto error_handler = [&](Status, bool is_fatal) {
396✔
1553
            CHECK_NOT(is_fatal);
396✔
1554
            fixture.cancel_reconnect_delay();
396✔
1555
        };
396✔
1556
        fixture.set_client_side_error_handler(error_handler);
2✔
1557
        fixture.start();
2✔
1558

1✔
1559
        Session session_1 = fixture.make_bound_session(db_1);
2✔
1560

1✔
1561
        Session session_2 = fixture.make_bound_session(db_2);
2✔
1562

1✔
1563
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
1564
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1565
            table->add_column(type_Int, "i");
2✔
1566
            table->create_object_with_primary_key(1);
2✔
1567
        });
2✔
1568
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
1569
            TableRef table = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
1570
            table->add_column(type_Int, "i");
2✔
1571
            table->create_object_with_primary_key(2);
2✔
1572
        });
2✔
1573
        for (int i = 0; i < 100; ++i) {
202✔
1574
            session_1.wait_for_upload_complete_or_client_stopped();
200✔
1575
            session_2.wait_for_upload_complete_or_client_stopped();
200✔
1576
            for (int i = 0; i < 10; ++i) {
2,200✔
1577
                write_transaction(db_1, [=](WriteTransaction& wt) {
2,000✔
1578
                    TableRef table = wt.get_table("class_foo");
2,000✔
1579
                    table->begin()->set("i", i);
2,000✔
1580
                });
2,000✔
1581
                write_transaction(db_2, [=](WriteTransaction& wt) {
2,000✔
1582
                    TableRef table = wt.get_table("class_bar");
2,000✔
1583
                    table->begin()->set("i", i);
2,000✔
1584
                });
2,000✔
1585
            }
2,000✔
1586
        }
200✔
1587
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1588
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1589
        session_1.wait_for_download_complete_or_client_stopped();
2✔
1590
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1591
    }
2✔
1592

1✔
1593
    ReadTransaction rt_1(db_1);
2✔
1594
    ReadTransaction rt_2(db_2);
2✔
1595
    const Group& group_1 = rt_1;
2✔
1596
    const Group& group_2 = rt_2;
2✔
1597
    group_1.verify();
2✔
1598
    group_2.verify();
2✔
1599
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
1600
}
2✔
1601

1602

1603
TEST(Sync_ErrorAfterServerRestore_BadClientFileIdent)
1604
{
2✔
1605
    TEST_DIR(server_dir);
2✔
1606
    TEST_CLIENT_DB(db);
2✔
1607

1✔
1608
    std::string server_path = "/test";
2✔
1609
    std::string server_realm_path;
2✔
1610

1✔
1611
    // Make a change and synchronize with server
1✔
1612
    {
2✔
1613
        ClientServerFixture fixture(server_dir, test_context);
2✔
1614
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1615
        Session session = fixture.make_bound_session(db, server_path);
2✔
1616
        WriteTransaction wt{db};
2✔
1617
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1618
        wt.commit();
2✔
1619
        fixture.start();
2✔
1620
        session.wait_for_upload_complete_or_client_stopped();
2✔
1621
    }
2✔
1622

1✔
1623
    // Emulate a server-side restore to before the creation of the Realm
1✔
1624
    util::File::remove(server_realm_path);
2✔
1625

1✔
1626
    // Provoke error by attempting to resynchronize
1✔
1627
    bool did_fail = false;
2✔
1628
    {
2✔
1629
        ClientServerFixture fixture(server_dir, test_context);
2✔
1630
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1631
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1632
            CHECK(is_fatal);
2✔
1633
            did_fail = true;
2✔
1634
            fixture.stop();
2✔
1635
        };
2✔
1636
        fixture.set_client_side_error_handler(error_handler);
2✔
1637
        Session session = fixture.make_bound_session(db, server_path);
2✔
1638
        fixture.start();
2✔
1639
        session.wait_for_download_complete_or_client_stopped();
2✔
1640
    }
2✔
1641
    CHECK(did_fail);
2✔
1642
}
2✔
1643

1644

1645
TEST(Sync_HTTP404NotFound)
1646
{
2✔
1647
    TEST_DIR(server_dir);
2✔
1648

1✔
1649
    std::string server_address = "localhost";
2✔
1650

1✔
1651
    Server::Config server_config;
2✔
1652
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
1653
    server_config.listen_address = server_address;
2✔
1654
    server_config.listen_port = "";
2✔
1655
    server_config.tcp_no_delay = true;
2✔
1656

1✔
1657
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
1658
    Server server(server_dir, std::move(public_key), server_config);
2✔
1659
    server.start();
2✔
1660
    network::Endpoint endpoint = server.listen_endpoint();
2✔
1661

1✔
1662
    ThreadWrapper server_thread;
2✔
1663
    server_thread.start([&] {
2✔
1664
        server.run();
2✔
1665
    });
2✔
1666

1✔
1667
    HTTPRequest request;
2✔
1668
    request.path = "/not-found";
2✔
1669

1✔
1670
    HTTPRequestClient client(test_context.logger, endpoint, request);
2✔
1671
    client.fetch_response();
2✔
1672

1✔
1673
    server.stop();
2✔
1674

1✔
1675
    server_thread.join();
2✔
1676

1✔
1677
    const HTTPResponse& response = client.get_response();
2✔
1678

1✔
1679
    CHECK(response.status == HTTPStatus::NotFound);
2✔
1680
    CHECK(response.headers.find("Server")->second == "RealmSync/" REALM_VERSION_STRING);
2✔
1681
}
2✔
1682

1683

1684
namespace {
1685

1686
class RequestWithContentLength {
1687
public:
1688
    RequestWithContentLength(test_util::unit_test::TestContext& test_context, network::Service& service,
1689
                             const network::Endpoint& endpoint, const std::string& content_length,
1690
                             const std::string& expected_response_line)
1691
        : test_context{test_context}
1692
        , m_socket{service}
1693
        , m_endpoint{endpoint}
1694
        , m_content_length{content_length}
1695
        , m_expected_response_line{expected_response_line}
1696
    {
8✔
1697
        m_request = "POST /does-not-exist-1234 HTTP/1.1\r\n"
8✔
1698
                    "Content-Length: " +
8✔
1699
                    m_content_length +
8✔
1700
                    "\r\n"
8✔
1701
                    "\r\n";
8✔
1702
    }
8✔
1703

1704
    void write_completion_handler(std::error_code ec, size_t nbytes)
1705
    {
8✔
1706
        CHECK_NOT(ec);
8✔
1707
        CHECK_EQUAL(m_request.size(), nbytes);
8✔
1708
        auto handler = [&](std::error_code ec, size_t nbytes) {
8✔
1709
            this->read_completion_handler(ec, nbytes);
8✔
1710
        };
8✔
1711
        m_socket.async_read_until(m_buffer, m_buf_size, '\n', m_read_ahead_buffer, handler);
8✔
1712
    }
8✔
1713

1714
    void read_completion_handler(std::error_code ec, size_t nbytes)
1715
    {
8✔
1716
        CHECK_NOT(ec);
8✔
1717
        std::string response_line{m_buffer, nbytes};
8✔
1718
        CHECK_EQUAL(response_line, m_expected_response_line);
8✔
1719
    }
8✔
1720

1721
    void start()
1722
    {
8✔
1723
        std::error_code ec;
8✔
1724
        m_socket.connect(m_endpoint, ec);
8✔
1725
        CHECK_NOT(ec);
8✔
1726

4✔
1727
        auto handler = [&](std::error_code ec, size_t nbytes) {
8✔
1728
            this->write_completion_handler(ec, nbytes);
8✔
1729
        };
8✔
1730
        m_socket.async_write(m_request.data(), m_request.size(), handler);
8✔
1731
    }
8✔
1732

1733
private:
1734
    test_util::unit_test::TestContext& test_context;
1735
    network::Socket m_socket;
1736
    network::ReadAheadBuffer m_read_ahead_buffer;
1737
    static constexpr size_t m_buf_size = 1000;
1738
    char m_buffer[m_buf_size];
1739
    const network::Endpoint& m_endpoint;
1740
    const std::string m_content_length;
1741
    std::string m_request;
1742
    const std::string m_expected_response_line;
1743
};
1744

1745
} // namespace
1746

1747
// Test the server's HTTP response to a Content-Length header of zero, empty,
1748
// and a non-number string.
1749
TEST(Sync_HTTP_ContentLength)
1750
{
2✔
1751
    TEST_DIR(server_dir);
2✔
1752

1✔
1753
    std::string server_address = "localhost";
2✔
1754

1✔
1755
    Server::Config server_config;
2✔
1756
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
1757
    server_config.listen_address = server_address;
2✔
1758
    server_config.listen_port = "";
2✔
1759
    server_config.tcp_no_delay = true;
2✔
1760

1✔
1761
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
1762
    Server server(server_dir, std::move(public_key), server_config);
2✔
1763
    server.start();
2✔
1764
    network::Endpoint endpoint = server.listen_endpoint();
2✔
1765

1✔
1766
    ThreadWrapper server_thread;
2✔
1767
    server_thread.start([&] {
2✔
1768
        server.run();
2✔
1769
    });
2✔
1770

1✔
1771
    network::Service service;
2✔
1772

1✔
1773
    RequestWithContentLength req_0(test_context, service, endpoint, "0", "HTTP/1.1 404 Not Found\r\n");
2✔
1774

1✔
1775
    RequestWithContentLength req_1(test_context, service, endpoint, "", "HTTP/1.1 404 Not Found\r\n");
2✔
1776

1✔
1777
    RequestWithContentLength req_2(test_context, service, endpoint, "abc", "HTTP/1.1 400 Bad Request\r\n");
2✔
1778

1✔
1779
    RequestWithContentLength req_3(test_context, service, endpoint, "5abc", "HTTP/1.1 400 Bad Request\r\n");
2✔
1780

1✔
1781
    req_0.start();
2✔
1782
    req_1.start();
2✔
1783
    req_2.start();
2✔
1784
    req_3.start();
2✔
1785

1✔
1786
    service.run();
2✔
1787

1✔
1788
    server.stop();
2✔
1789
    server_thread.join();
2✔
1790
}
2✔
1791

1792

1793
TEST(Sync_ErrorAfterServerRestore_BadServerVersion)
1794
{
2✔
1795
    TEST_DIR(server_dir);
2✔
1796
    TEST_DIR(backup_dir);
2✔
1797
    TEST_CLIENT_DB(db);
2✔
1798

1✔
1799
    std::string server_path = "/test";
2✔
1800
    std::string server_realm_path;
2✔
1801
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1802

1✔
1803
    // Create schema and synchronize with server
1✔
1804
    {
2✔
1805
        ClientServerFixture fixture(server_dir, test_context);
2✔
1806
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1807
        Session session = fixture.make_bound_session(db, server_path);
2✔
1808
        WriteTransaction wt{db};
2✔
1809
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1810
        table->add_column(type_Int, "column");
2✔
1811
        wt.commit();
2✔
1812
        fixture.start();
2✔
1813
        session.wait_for_upload_complete_or_client_stopped();
2✔
1814
    }
2✔
1815

1✔
1816
    // Save a snapshot of the server-side Realm file
1✔
1817
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1818

1✔
1819
    // Make change in which will be lost when restoring snapshot
1✔
1820
    {
2✔
1821
        ClientServerFixture fixture(server_dir, test_context);
2✔
1822
        Session session = fixture.make_bound_session(db, server_path);
2✔
1823
        WriteTransaction wt{db};
2✔
1824
        TableRef table = wt.get_table("class_table");
2✔
1825
        table->create_object_with_primary_key(1);
2✔
1826
        wt.commit();
2✔
1827
        fixture.start();
2✔
1828
        session.wait_for_upload_complete_or_client_stopped();
2✔
1829
    }
2✔
1830

1✔
1831
    // Restore the snapshot
1✔
1832
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1833

1✔
1834
    // Provoke error by resynchronizing
1✔
1835
    bool did_fail = false;
2✔
1836
    {
2✔
1837
        ClientServerFixture fixture(server_dir, test_context);
2✔
1838
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1839
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1840
            CHECK(is_fatal);
2✔
1841
            did_fail = true;
2✔
1842
            fixture.stop();
2✔
1843
        };
2✔
1844
        fixture.set_client_side_error_handler(error_handler);
2✔
1845
        Session session = fixture.make_bound_session(db, server_path);
2✔
1846
        fixture.start();
2✔
1847
        session.wait_for_download_complete_or_client_stopped();
2✔
1848
    }
2✔
1849
    CHECK(did_fail);
2✔
1850
}
2✔
1851

1852

1853
TEST(Sync_ErrorAfterServerRestore_BadClientVersion)
1854
{
2✔
1855
    TEST_DIR(server_dir);
2✔
1856
    TEST_DIR(backup_dir);
2✔
1857
    TEST_CLIENT_DB(db_1);
2✔
1858
    TEST_CLIENT_DB(db_2);
2✔
1859

1✔
1860
    std::string server_path = "/test";
2✔
1861
    std::string server_realm_path;
2✔
1862
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1863

1✔
1864
    // Create schema and synchronize client files
1✔
1865
    {
2✔
1866
        ClientServerFixture fixture(server_dir, test_context);
2✔
1867
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1868
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
1869
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
1870
        WriteTransaction wt{db_1};
2✔
1871
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1872
        table->add_column(type_Int, "column");
2✔
1873
        wt.commit();
2✔
1874
        fixture.start();
2✔
1875
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1876
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1877
    }
2✔
1878

1✔
1879
    // Save a snapshot of the server-side Realm file
1✔
1880
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1881

1✔
1882
    // Make change in 1st file which will be lost when restoring snapshot
1✔
1883
    {
2✔
1884
        ClientServerFixture fixture(server_dir, test_context);
2✔
1885
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1886
        WriteTransaction wt{db_1};
2✔
1887
        TableRef table = wt.get_table("class_table");
2✔
1888
        table->create_object_with_primary_key(1);
2✔
1889
        wt.commit();
2✔
1890
        fixture.start();
2✔
1891
        session.wait_for_upload_complete_or_client_stopped();
2✔
1892
    }
2✔
1893

1✔
1894
    // Restore the snapshot
1✔
1895
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1896

1✔
1897
    // Make a conflicting change in 2nd file relative to reverted server state
1✔
1898
    {
2✔
1899
        ClientServerFixture fixture(server_dir, test_context);
2✔
1900
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1901
        WriteTransaction wt{db_2};
2✔
1902
        TableRef table = wt.get_table("class_table");
2✔
1903
        table->create_object_with_primary_key(2);
2✔
1904
        wt.commit();
2✔
1905
        fixture.start();
2✔
1906
        session.wait_for_upload_complete_or_client_stopped();
2✔
1907
    }
2✔
1908

1✔
1909
    // Provoke error by synchronizing 1st file
1✔
1910
    bool did_fail = false;
2✔
1911
    {
2✔
1912
        ClientServerFixture fixture(server_dir, test_context);
2✔
1913
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1914
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1915
            CHECK(is_fatal);
2✔
1916
            did_fail = true;
2✔
1917
            fixture.stop();
2✔
1918
        };
2✔
1919
        fixture.set_client_side_error_handler(error_handler);
2✔
1920
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1921
        fixture.start();
2✔
1922
        session.wait_for_download_complete_or_client_stopped();
2✔
1923
    }
2✔
1924
    CHECK(did_fail);
2✔
1925
}
2✔
1926

1927

1928
TEST(Sync_ErrorAfterServerRestore_BadClientFileIdentSalt)
1929
{
2✔
1930
    TEST_DIR(server_dir);
2✔
1931
    TEST_DIR(backup_dir);
2✔
1932
    TEST_CLIENT_DB(db_1);
2✔
1933
    TEST_CLIENT_DB(db_2);
2✔
1934
    TEST_CLIENT_DB(db_3);
2✔
1935

1✔
1936
    std::string server_path = "/test";
2✔
1937
    std::string server_realm_path;
2✔
1938
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1939

1✔
1940
    // Register 1st file with server
1✔
1941
    {
2✔
1942
        ClientServerFixture fixture(server_dir, test_context);
2✔
1943
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1944
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1945
        WriteTransaction wt{db_1};
2✔
1946
        TableRef table = wt.get_group().add_table_with_primary_key("class_table_1", type_Int, "id");
2✔
1947
        table->add_column(type_Int, "column");
2✔
1948
        wt.commit();
2✔
1949
        fixture.start();
2✔
1950
        session.wait_for_upload_complete_or_client_stopped();
2✔
1951
    }
2✔
1952

1✔
1953
    // Save a snapshot of the server-side Realm file
1✔
1954
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1955

1✔
1956
    // Register 2nd file with server
1✔
1957
    {
2✔
1958
        ClientServerFixture fixture(server_dir, test_context);
2✔
1959
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1960
        fixture.start();
2✔
1961
        session.wait_for_download_complete_or_client_stopped();
2✔
1962
    }
2✔
1963

1✔
1964
    // Restore the snapshot
1✔
1965
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1966

1✔
1967
    // Register 3rd conflicting file with server
1✔
1968
    {
2✔
1969
        ClientServerFixture fixture(server_dir, test_context);
2✔
1970
        Session session = fixture.make_bound_session(db_3, server_path);
2✔
1971
        fixture.start();
2✔
1972
        session.wait_for_download_complete_or_client_stopped();
2✔
1973
    }
2✔
1974

1✔
1975
    // Provoke error by resynchronizing 2nd file
1✔
1976
    bool did_fail = false;
2✔
1977
    {
2✔
1978
        ClientServerFixture fixture(server_dir, test_context);
2✔
1979
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1980
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1981
            CHECK(is_fatal);
2✔
1982
            did_fail = true;
2✔
1983
            fixture.stop();
2✔
1984
        };
2✔
1985
        fixture.set_client_side_error_handler(error_handler);
2✔
1986
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1987
        fixture.start();
2✔
1988
        session.wait_for_download_complete_or_client_stopped();
2✔
1989
    }
2✔
1990
    CHECK(did_fail);
2✔
1991
}
2✔
1992

1993

1994
TEST(Sync_ErrorAfterServerRestore_BadServerVersionSalt)
1995
{
2✔
1996
    TEST_DIR(server_dir);
2✔
1997
    TEST_DIR(backup_dir);
2✔
1998
    TEST_CLIENT_DB(db_1);
2✔
1999
    TEST_CLIENT_DB(db_2);
2✔
2000
    TEST_CLIENT_DB(db_3);
2✔
2001

1✔
2002
    std::string server_path = "/test";
2✔
2003
    std::string server_realm_path;
2✔
2004
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
2005

1✔
2006
    // Create schema and synchronize client files
1✔
2007
    {
2✔
2008
        ClientServerFixture fixture(server_dir, test_context);
2✔
2009
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
2010
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
2011
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
2012
        Session session_3 = fixture.make_bound_session(db_3, server_path);
2✔
2013
        WriteTransaction wt{db_1};
2✔
2014
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2015
        table->add_column(type_Int, "column");
2✔
2016
        wt.commit();
2✔
2017
        fixture.start();
2✔
2018
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
2019
        session_2.wait_for_download_complete_or_client_stopped();
2✔
2020
        session_3.wait_for_download_complete_or_client_stopped();
2✔
2021
    }
2✔
2022

1✔
2023
    // Save a snapshot of the server-side Realm file
1✔
2024
    util::File::copy(server_realm_path, backup_realm_path);
2✔
2025

1✔
2026
    // Make change in 1st file which will be lost when restoring snapshot, and
1✔
2027
    // make 2nd file download it.
1✔
2028
    {
2✔
2029
        ClientServerFixture fixture(server_dir, test_context);
2✔
2030
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
2031
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
2032
        WriteTransaction wt{db_1};
2✔
2033
        TableRef table = wt.get_table("class_table");
2✔
2034
        table->create_object_with_primary_key(1);
2✔
2035
        wt.commit();
2✔
2036
        fixture.start();
2✔
2037
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
2038
        session_2.wait_for_download_complete_or_client_stopped();
2✔
2039
    }
2✔
2040

1✔
2041
    // Restore the snapshot
1✔
2042
    util::File::copy(backup_realm_path, server_realm_path);
2✔
2043

1✔
2044
    // Make a conflicting change in 3rd file relative to reverted server state
1✔
2045
    {
2✔
2046
        ClientServerFixture fixture(server_dir, test_context);
2✔
2047
        Session session = fixture.make_bound_session(db_3, server_path);
2✔
2048
        WriteTransaction wt{db_3};
2✔
2049
        TableRef table = wt.get_table("class_table");
2✔
2050
        table->create_object_with_primary_key(2);
2✔
2051
        wt.commit();
2✔
2052
        fixture.start();
2✔
2053
        session.wait_for_upload_complete_or_client_stopped();
2✔
2054
    }
2✔
2055

1✔
2056
    // Provoke error by synchronizing 2nd file
1✔
2057
    bool did_fail = false;
2✔
2058
    {
2✔
2059
        ClientServerFixture fixture(server_dir, test_context);
2✔
2060
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
2061
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
2062
            CHECK(is_fatal);
2✔
2063
            did_fail = true;
2✔
2064
            fixture.stop();
2✔
2065
        };
2✔
2066
        fixture.set_client_side_error_handler(error_handler);
2✔
2067
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
2068
        fixture.start();
2✔
2069
        session.wait_for_download_complete_or_client_stopped();
2✔
2070
    }
2✔
2071
    CHECK(did_fail);
2✔
2072
}
2✔
2073

2074

2075
TEST(Sync_MultipleServers)
2076
{
2✔
2077
    // Check that a client can make lots of connection to lots of servers in a
1✔
2078
    // concurrent manner.
1✔
2079

1✔
2080
    const int num_servers = 2;
2✔
2081
    const int num_realms_per_server = 2;
2✔
2082
    const int num_files_per_realm = 4;
2✔
2083
    const int num_sessions_per_file = 8;
2✔
2084
    const int num_transacts_per_session = 2;
2✔
2085

1✔
2086
    TEST_DIR(dir);
2✔
2087
    int num_clients = 1;
2✔
2088
    MultiClientServerFixture fixture(num_clients, num_servers, dir, test_context);
2✔
2089
    fixture.start();
2✔
2090

1✔
2091
    TEST_DIR(dir_2);
2✔
2092
    auto get_file_path = [&](int server_index, int realm_index, int file_index) {
96✔
2093
        std::ostringstream out;
96✔
2094
        out << server_index << "_" << realm_index << "_" << file_index << ".realm";
96✔
2095
        return util::File::resolve(out.str(), dir_2);
96✔
2096
    };
96✔
2097
    std::atomic<int> id = 0;
2✔
2098

1✔
2099
    auto run = [&](int server_index, int realm_index, int file_index) {
32✔
2100
        try {
32✔
2101
            std::string path = get_file_path(server_index, realm_index, file_index);
32✔
2102
            DBRef db = DB::create(make_client_replication(), path);
32✔
2103
            {
32✔
2104
                WriteTransaction wt(db);
32✔
2105
                TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
32✔
2106
                table->add_column(type_Int, "server_index");
32✔
2107
                table->add_column(type_Int, "realm_index");
32✔
2108
                table->add_column(type_Int, "file_index");
32✔
2109
                table->add_column(type_Int, "session_index");
32✔
2110
                table->add_column(type_Int, "transact_index");
32✔
2111
                wt.commit();
32✔
2112
            }
32✔
2113
            std::string server_path = "/" + std::to_string(realm_index);
32✔
2114
            for (int i = 0; i < num_sessions_per_file; ++i) {
288✔
2115
                int client_index = 0;
256✔
2116
                Session session = fixture.make_session(client_index, server_index, db, server_path);
256✔
2117
                session.bind();
256✔
2118
                for (int j = 0; j < num_transacts_per_session; ++j) {
768✔
2119
                    WriteTransaction wt(db);
512✔
2120
                    TableRef table = wt.get_table("class_table");
512✔
2121
                    Obj obj = table->create_object_with_primary_key(id.fetch_add(1));
512✔
2122
                    obj.set("server_index", server_index);
512✔
2123
                    obj.set("realm_index", realm_index);
512✔
2124
                    obj.set("file_index", file_index);
512✔
2125
                    obj.set("session_index", i);
512✔
2126
                    obj.set("transact_index", j);
512✔
2127
                    wt.commit();
512✔
2128
                }
512✔
2129
                session.wait_for_upload_complete_or_client_stopped();
256✔
2130
            }
256✔
2131
        }
32✔
2132
        catch (...) {
16✔
2133
            fixture.stop();
×
2134
            throw;
×
2135
        }
×
2136
    };
32✔
2137

1✔
2138
    auto finish_download = [&](int server_index, int realm_index, int file_index) {
32✔
2139
        try {
32✔
2140
            int client_index = 0;
32✔
2141
            std::string path = get_file_path(server_index, realm_index, file_index);
32✔
2142
            DBRef db = DB::create(make_client_replication(), path);
32✔
2143
            std::string server_path = "/" + std::to_string(realm_index);
32✔
2144
            Session session = fixture.make_session(client_index, server_index, db, server_path);
32✔
2145
            session.bind();
32✔
2146
            session.wait_for_download_complete_or_client_stopped();
32✔
2147
        }
32✔
2148
        catch (...) {
16✔
2149
            fixture.stop();
×
2150
            throw;
×
2151
        }
×
2152
    };
32✔
2153

1✔
2154
    // Make and upload changes
1✔
2155
    {
2✔
2156
        ThreadWrapper threads[num_servers][num_realms_per_server][num_files_per_realm];
2✔
2157
        for (int i = 0; i < num_servers; ++i) {
6✔
2158
            for (int j = 0; j < num_realms_per_server; ++j) {
12✔
2159
                for (int k = 0; k < num_files_per_realm; ++k)
40✔
2160
                    threads[i][j][k].start([=] {
32✔
2161
                        run(i, j, k);
32✔
2162
                    });
32✔
2163
            }
8✔
2164
        }
4✔
2165
        for (size_t i = 0; i < num_servers; ++i) {
6✔
2166
            for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2167
                for (size_t k = 0; k < num_files_per_realm; ++k)
40✔
2168
                    CHECK_NOT(threads[i][j][k].join());
32✔
2169
            }
8✔
2170
        }
4✔
2171
    }
2✔
2172

1✔
2173
    // Finish downloading
1✔
2174
    {
2✔
2175
        ThreadWrapper threads[num_servers][num_realms_per_server][num_files_per_realm];
2✔
2176
        for (int i = 0; i < num_servers; ++i) {
6✔
2177
            for (int j = 0; j < num_realms_per_server; ++j) {
12✔
2178
                for (int k = 0; k < num_files_per_realm; ++k)
40✔
2179
                    threads[i][j][k].start([=] {
32✔
2180
                        finish_download(i, j, k);
32✔
2181
                    });
32✔
2182
            }
8✔
2183
        }
4✔
2184
        for (size_t i = 0; i < num_servers; ++i) {
6✔
2185
            for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2186
                for (size_t k = 0; k < num_files_per_realm; ++k)
40✔
2187
                    CHECK_NOT(threads[i][j][k].join());
32✔
2188
            }
8✔
2189
        }
4✔
2190
    }
2✔
2191

1✔
2192
    // Check that all client side Realms have been correctly synchronized
1✔
2193
    std::set<std::tuple<int, int, int>> expected_rows;
2✔
2194
    for (int i = 0; i < num_files_per_realm; ++i) {
10✔
2195
        for (int j = 0; j < num_sessions_per_file; ++j) {
72✔
2196
            for (int k = 0; k < num_transacts_per_session; ++k)
192✔
2197
                expected_rows.emplace(i, j, k);
128✔
2198
        }
64✔
2199
    }
8✔
2200
    for (size_t i = 0; i < num_servers; ++i) {
6✔
2201
        for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2202
            REALM_ASSERT(num_files_per_realm > 0);
8✔
2203
            int file_index_0 = 0;
8✔
2204
            std::string path_0 = get_file_path(int(i), int(j), file_index_0);
8✔
2205
            std::unique_ptr<Replication> history_0 = make_client_replication();
8✔
2206
            DBRef db_0 = DB::create(*history_0, path_0);
8✔
2207
            ReadTransaction rt_0(db_0);
8✔
2208
            {
8✔
2209
                ConstTableRef table = rt_0.get_table("class_table");
8✔
2210
                if (CHECK(table)) {
8✔
2211
                    std::set<std::tuple<int, int, int>> rows;
8✔
2212
                    for (const Obj& obj : *table) {
512✔
2213
                        int server_index = int(obj.get<int64_t>("server_index"));
512✔
2214
                        int realm_index = int(obj.get<int64_t>("realm_index"));
512✔
2215
                        int file_index = int(obj.get<int64_t>("file_index"));
512✔
2216
                        int session_index = int(obj.get<int64_t>("session_index"));
512✔
2217
                        int transact_index = int(obj.get<int64_t>("transact_index"));
512✔
2218
                        CHECK_EQUAL(i, server_index);
512✔
2219
                        CHECK_EQUAL(j, realm_index);
512✔
2220
                        rows.emplace(file_index, session_index, transact_index);
512✔
2221
                    }
512✔
2222
                    CHECK(rows == expected_rows);
8✔
2223
                }
8✔
2224
            }
8✔
2225
            for (int k = 1; k < num_files_per_realm; ++k) {
32✔
2226
                std::string path = get_file_path(int(i), int(j), k);
24✔
2227
                DBRef db = DB::create(make_client_replication(), path);
24✔
2228
                ReadTransaction rt(db);
24✔
2229
                CHECK(compare_groups(rt_0, rt));
24✔
2230
            }
24✔
2231
        }
8✔
2232
    }
4✔
2233
}
2✔
2234

2235

2236
TEST_IF(Sync_ReadOnlyClient, false)
2237
{
×
2238
    TEST_CLIENT_DB(db_1);
×
2239
    TEST_CLIENT_DB(db_2);
×
2240

2241
    TEST_DIR(server_dir);
×
2242
    MultiClientServerFixture fixture(2, 1, server_dir, test_context);
×
2243
    bool did_get_permission_denied = false;
×
2244
    fixture.set_client_side_error_handler(1, [&](Status status, bool) {
×
2245
        CHECK_EQUAL(status, ErrorCodes::SyncPermissionDenied);
×
2246
        did_get_permission_denied = true;
×
2247
        fixture.get_client(1).shutdown();
×
2248
    });
×
2249
    fixture.start();
×
2250

2251
    // Write some stuff from the client that can upload
2252
    {
×
2253
        Session session_1 = fixture.make_bound_session(0, db_1, 0, "/test");
×
2254
        WriteTransaction wt(db_1);
×
2255
        auto table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
×
2256
        table->add_column(type_Int, "i");
×
2257
        table->create_object_with_primary_key(1);
×
2258
        table->begin()->set("i", 123);
×
2259
        wt.commit();
×
2260
        session_1.wait_for_upload_complete_or_client_stopped();
×
2261
    }
×
2262

2263
    // Check that the stuff was received on the read-only client
2264
    {
×
2265
        Session session_2 = fixture.make_bound_session(1, db_2, 0, "/test", g_signed_test_user_token_readonly);
×
2266
        session_2.wait_for_download_complete_or_client_stopped();
×
2267
        {
×
2268
            ReadTransaction rt(db_2);
×
2269
            auto table = rt.get_table("class_foo");
×
2270
            CHECK_EQUAL(table->begin()->get<Int>("i"), 123);
×
2271
        }
×
2272
        // Try to upload something
2273
        {
×
2274
            WriteTransaction wt(db_2);
×
2275
            auto table = wt.get_table("class_foo");
×
2276
            table->begin()->set("i", 456);
×
2277
            wt.commit();
×
2278
        }
×
2279
        session_2.wait_for_upload_complete_or_client_stopped();
×
2280
        CHECK(did_get_permission_denied);
×
2281
    }
×
2282

2283
    // Check that the original client was unchanged
2284
    {
×
2285
        Session session_1 = fixture.make_bound_session(0, db_1, 0, "/test");
×
2286
        session_1.wait_for_download_complete_or_client_stopped();
×
2287
        ReadTransaction rt(db_1);
×
2288
        auto table = rt.get_table("class_foo");
×
2289
        CHECK_EQUAL(table->begin()->get<Int>("i"), 123);
×
2290
    }
×
2291
}
×
2292

2293

2294
// This test is a performance study. A single client keeps creating
2295
// transactions that creates new objects and uploads them. The time to perform
2296
// upload completion is measured and logged at info level.
2297
TEST(Sync_SingleClientUploadForever_CreateObjects)
2298
{
2✔
2299
    int_fast32_t number_of_transactions = 100; // Set to low number in ordinary testing.
2✔
2300

1✔
2301
    util::Logger& logger = *test_context.logger;
2✔
2302

1✔
2303
    logger.info("Sync_SingleClientUploadForever_CreateObjects test. Number of transactions = %1",
2✔
2304
                number_of_transactions);
2✔
2305

1✔
2306
    TEST_DIR(server_dir);
2✔
2307
    TEST_CLIENT_DB(db);
2✔
2308

1✔
2309
    ClientServerFixture fixture(server_dir, test_context);
2✔
2310
    fixture.start();
2✔
2311

1✔
2312
    ColKey col_int;
2✔
2313
    ColKey col_str;
2✔
2314
    ColKey col_dbl;
2✔
2315
    ColKey col_time;
2✔
2316

1✔
2317
    {
2✔
2318
        WriteTransaction wt{db};
2✔
2319
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2320
        col_int = tr->add_column(type_Int, "integer column");
2✔
2321
        col_str = tr->add_column(type_String, "string column");
2✔
2322
        col_dbl = tr->add_column(type_Double, "double column");
2✔
2323
        col_time = tr->add_column(type_Timestamp, "timestamp column");
2✔
2324
        wt.commit();
2✔
2325
    }
2✔
2326

1✔
2327
    Session session = fixture.make_bound_session(db);
2✔
2328
    session.wait_for_upload_complete_or_client_stopped();
2✔
2329

1✔
2330
    for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
202✔
2331
        WriteTransaction wt{db};
200✔
2332
        TableRef tr = wt.get_table("class_table");
200✔
2333
        auto obj = tr->create_object_with_primary_key(i);
200✔
2334
        int_fast32_t number = i;
200✔
2335
        obj.set<Int>(col_int, number);
200✔
2336
        std::string str = "str: " + std::to_string(number);
200✔
2337
        StringData str_data = StringData(str);
200✔
2338
        obj.set(col_str, str_data);
200✔
2339
        obj.set(col_dbl, double(number));
200✔
2340
        obj.set(col_time, Timestamp{123, 456});
200✔
2341
        wt.commit();
200✔
2342
        auto before_upload = std::chrono::steady_clock::now();
200✔
2343
        session.wait_for_upload_complete_or_client_stopped();
200✔
2344
        auto after_upload = std::chrono::steady_clock::now();
200✔
2345

100✔
2346
        // We only log the duration every 1000 transactions. The duration is for a single changeset.
100✔
2347
        if (i % 1000 == 0) {
200✔
2348
            auto duration =
2✔
2349
                std::chrono::duration_cast<std::chrono::milliseconds>(after_upload - before_upload).count();
2✔
2350
            logger.info("Duration of single changeset upload(%1) = %2 ms", i, duration);
2✔
2351
        }
2✔
2352
    }
200✔
2353
}
2✔
2354

2355

2356
// This test is a performance study. A single client keeps creating
2357
// transactions that changes the value of an existing object and uploads them.
2358
// The time to perform upload completion is measured and logged at info level.
2359
TEST(Sync_SingleClientUploadForever_MutateObject)
2360
{
2✔
2361
    int_fast32_t number_of_transactions = 100; // Set to low number in ordinary testing.
2✔
2362

1✔
2363
    util::Logger& logger = *test_context.logger;
2✔
2364

1✔
2365
    logger.info("Sync_SingleClientUploadForever_MutateObject test. Number of transactions = %1",
2✔
2366
                number_of_transactions);
2✔
2367

1✔
2368
    TEST_DIR(server_dir);
2✔
2369
    TEST_CLIENT_DB(db);
2✔
2370

1✔
2371
    ClientServerFixture fixture(server_dir, test_context);
2✔
2372
    fixture.start();
2✔
2373

1✔
2374
    ColKey col_int;
2✔
2375
    ColKey col_str;
2✔
2376
    ColKey col_dbl;
2✔
2377
    ColKey col_time;
2✔
2378
    ObjKey obj_key;
2✔
2379

1✔
2380
    {
2✔
2381
        WriteTransaction wt{db};
2✔
2382
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2383
        col_int = tr->add_column(type_Int, "integer column");
2✔
2384
        col_str = tr->add_column(type_String, "string column");
2✔
2385
        col_dbl = tr->add_column(type_Double, "double column");
2✔
2386
        col_time = tr->add_column(type_Timestamp, "timestamp column");
2✔
2387
        obj_key = tr->create_object_with_primary_key(1).get_key();
2✔
2388
        wt.commit();
2✔
2389
    }
2✔
2390

1✔
2391
    Session session = fixture.make_bound_session(db);
2✔
2392
    session.wait_for_upload_complete_or_client_stopped();
2✔
2393

1✔
2394
    for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
202✔
2395
        WriteTransaction wt{db};
200✔
2396
        TableRef tr = wt.get_table("class_table");
200✔
2397
        int_fast32_t number = i;
200✔
2398
        auto obj = tr->get_object(obj_key);
200✔
2399
        obj.set<Int>(col_int, number);
200✔
2400
        std::string str = "str: " + std::to_string(number);
200✔
2401
        StringData str_data = StringData(str);
200✔
2402
        obj.set(col_str, str_data);
200✔
2403
        obj.set(col_dbl, double(number));
200✔
2404
        obj.set(col_time, Timestamp{123, 456});
200✔
2405
        wt.commit();
200✔
2406
        auto before_upload = std::chrono::steady_clock::now();
200✔
2407
        session.wait_for_upload_complete_or_client_stopped();
200✔
2408
        auto after_upload = std::chrono::steady_clock::now();
200✔
2409

100✔
2410
        // We only log the duration every 1000 transactions. The duration is for a single changeset.
100✔
2411
        if (i % 1000 == 0) {
200✔
2412
            auto duration =
2✔
2413
                std::chrono::duration_cast<std::chrono::milliseconds>(after_upload - before_upload).count();
2✔
2414
            logger.info("Duration of single changeset upload(%1) = %2 ms", i, duration);
2✔
2415
        }
2✔
2416
    }
200✔
2417
}
2✔
2418

2419

2420
// This test is used to time upload and download.
2421
// The test might be moved to a performance test directory later.
2422
TEST(Sync_LargeUploadDownloadPerformance)
2423
{
2✔
2424
    int_fast32_t number_of_transactions = 2;         // Set to low number in ordinary testing.
2✔
2425
    int_fast32_t number_of_rows_per_transaction = 5; // Set to low number in ordinary testing.
2✔
2426
    int number_of_download_clients = 1;              // Set to low number in ordinary testing
2✔
2427
    bool print_durations = false;                    // Set to false in ordinary testing.
2✔
2428

1✔
2429
    if (print_durations) {
2✔
2430
        std::cerr << "Number of transactions = " << number_of_transactions << std::endl;
×
2431
        std::cerr << "Number of rows per transaction = " << number_of_rows_per_transaction << std::endl;
×
2432
        std::cerr << "Number of download clients = " << number_of_download_clients << std::endl;
×
2433
    }
×
2434

1✔
2435
    TEST_DIR(server_dir);
2✔
2436
    ClientServerFixture fixture(server_dir, test_context);
2✔
2437
    fixture.start();
2✔
2438

1✔
2439
    TEST_CLIENT_DB(db_upload);
2✔
2440

1✔
2441
    // Populate path_upload realm with data.
1✔
2442
    auto start_data_creation = std::chrono::steady_clock::now();
2✔
2443
    {
2✔
2444
        {
2✔
2445
            WriteTransaction wt{db_upload};
2✔
2446
            TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2447
            tr->add_column(type_Int, "integer column");
2✔
2448
            tr->add_column(type_String, "string column");
2✔
2449
            tr->add_column(type_Double, "double column");
2✔
2450
            tr->add_column(type_Timestamp, "timestamp column");
2✔
2451
            wt.commit();
2✔
2452
        }
2✔
2453

1✔
2454
        for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
6✔
2455
            WriteTransaction wt{db_upload};
4✔
2456
            TableRef tr = wt.get_table("class_table");
4✔
2457
            for (int_fast32_t j = 0; j < number_of_rows_per_transaction; ++j) {
24✔
2458
                Obj obj = tr->create_object_with_primary_key(i);
20✔
2459
                int_fast32_t number = i * number_of_rows_per_transaction + j;
20✔
2460
                obj.set("integer column", number);
20✔
2461
                std::string str = "str: " + std::to_string(number);
20✔
2462
                StringData str_data = StringData(str);
20✔
2463
                obj.set("string column", str_data);
20✔
2464
                obj.set("double column", double(number));
20✔
2465
                obj.set("timestamp column", Timestamp{123, 456});
20✔
2466
            }
20✔
2467
            wt.commit();
4✔
2468
        }
4✔
2469
    }
2✔
2470
    auto end_data_creation = std::chrono::steady_clock::now();
2✔
2471
    auto duration_data_creation =
2✔
2472
        std::chrono::duration_cast<std::chrono::milliseconds>(end_data_creation - start_data_creation).count();
2✔
2473
    if (print_durations)
2✔
2474
        std::cerr << "Duration of data creation = " << duration_data_creation << " ms" << std::endl;
×
2475

1✔
2476
    // Upload the data.
1✔
2477
    auto start_session_upload = std::chrono::steady_clock::now();
2✔
2478

1✔
2479
    Session session_upload = fixture.make_bound_session(db_upload);
2✔
2480
    session_upload.wait_for_upload_complete_or_client_stopped();
2✔
2481

1✔
2482
    auto end_session_upload = std::chrono::steady_clock::now();
2✔
2483
    auto duration_upload =
2✔
2484
        std::chrono::duration_cast<std::chrono::milliseconds>(end_session_upload - start_session_upload).count();
2✔
2485
    if (print_durations)
2✔
2486
        std::cerr << "Duration of uploading = " << duration_upload << " ms" << std::endl;
×
2487

1✔
2488

1✔
2489
    // Download the data to the download realms.
1✔
2490
    auto start_sesion_download = std::chrono::steady_clock::now();
2✔
2491

1✔
2492
    std::vector<DBTestPathGuard> shared_group_test_path_guards;
2✔
2493
    std::vector<DBRef> dbs;
2✔
2494
    std::vector<Session> sessions;
2✔
2495

1✔
2496
    for (int i = 0; i < number_of_download_clients; ++i) {
4✔
2497
        std::string path = get_test_path(test_context.get_test_name(), std::to_string(i));
2✔
2498
        shared_group_test_path_guards.emplace_back(path);
2✔
2499
        dbs.push_back(DB::create(make_client_replication(), path));
2✔
2500
        sessions.push_back(fixture.make_bound_session(dbs.back()));
2✔
2501
    }
2✔
2502

1✔
2503
    // Wait for all Realms to finish. They might finish in another order than
1✔
2504
    // started, but calling download_complete on a client after it finished only
1✔
2505
    // adds a tiny amount of extra mark messages.
1✔
2506
    for (auto& session : sessions)
2✔
2507
        session.wait_for_download_complete_or_client_stopped();
2✔
2508

1✔
2509

1✔
2510
    auto end_session_download = std::chrono::steady_clock::now();
2✔
2511
    auto duration_download =
2✔
2512
        std::chrono::duration_cast<std::chrono::milliseconds>(end_session_download - start_sesion_download).count();
2✔
2513
    if (print_durations)
2✔
2514
        std::cerr << "Duration of downloading = " << duration_download << " ms" << std::endl;
×
2515

1✔
2516

1✔
2517
    // Check convergence.
1✔
2518
    for (int i = 0; i < number_of_download_clients; ++i) {
4✔
2519
        ReadTransaction rt_1(db_upload);
2✔
2520
        ReadTransaction rt_2(dbs[i]);
2✔
2521
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
2522
    }
2✔
2523
}
2✔
2524

2525

2526
// This test creates a changeset that is larger than 4GB, uploads it and downloads it to another client.
2527
// The test checks that compression and other aspects of large changeset handling works.
2528
// The test is disabled since it requires a powerful machine to run.
2529
TEST_IF(Sync_4GB_Messages, false)
2530
{
×
2531
    // The changeset will be slightly larger.
2532
    const uint64_t approximate_changeset_size = uint64_t(1) << 32;
×
2533

2534
    TEST_DIR(dir);
×
2535
    TEST_CLIENT_DB(db_1);
×
2536
    TEST_CLIENT_DB(db_2);
×
2537
    ClientServerFixture fixture(dir, test_context);
×
2538
    fixture.start();
×
2539

2540
    Session session_1 = fixture.make_bound_session(db_1);
×
2541
    session_1.wait_for_download_complete_or_client_stopped();
×
2542

2543
    Session session_2 = fixture.make_bound_session(db_2);
×
2544
    session_2.wait_for_download_complete_or_client_stopped();
×
2545

2546
    const size_t single_object_data_size = size_t(1e7); // 10 MB which is below the 16 MB limit
×
2547
    const int num_objects = approximate_changeset_size / single_object_data_size + 1;
×
2548

2549
    const std::string str_a(single_object_data_size, 'a');
×
2550
    BinaryData bd_a(str_a.data(), single_object_data_size);
×
2551

2552
    const std::string str_b(single_object_data_size, 'b');
×
2553
    BinaryData bd_b(str_b.data(), single_object_data_size);
×
2554

2555
    const std::string str_c(single_object_data_size, 'c');
×
2556
    BinaryData bd_c(str_c.data(), single_object_data_size);
×
2557

2558
    {
×
2559
        WriteTransaction wt{db_1};
×
2560

2561
        TableRef tr = wt.get_group().add_table_with_primary_key("class_simple_data", type_Int, "id");
×
2562
        auto col_key = tr->add_column(type_Binary, "binary column");
×
2563
        for (int i = 0; i < num_objects; ++i) {
×
2564
            Obj obj = tr->create_object_with_primary_key(i);
×
2565
            switch (i % 3) {
×
2566
                case 0:
×
2567
                    obj.set(col_key, bd_a);
×
2568
                    break;
×
2569
                case 1:
×
2570
                    obj.set(col_key, bd_b);
×
2571
                    break;
×
2572
                default:
×
2573
                    obj.set(col_key, bd_c);
×
2574
            }
×
2575
        }
×
2576
        wt.commit();
×
2577
    }
×
2578
    session_1.wait_for_upload_complete_or_client_stopped();
×
2579
    session_2.wait_for_download_complete_or_client_stopped();
×
2580

2581
    // Check convergence.
2582
    {
×
2583
        ReadTransaction rt_1(db_1);
×
2584
        ReadTransaction rt_2(db_2);
×
2585
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
×
2586
    }
×
2587
}
×
2588

2589

2590
TEST(Sync_RefreshSignedUserToken)
2591
{
2✔
2592
    TEST_DIR(dir);
2✔
2593
    TEST_CLIENT_DB(db);
2✔
2594
    ClientServerFixture fixture(dir, test_context);
2✔
2595
    fixture.start();
2✔
2596

1✔
2597
    Session session = fixture.make_bound_session(db);
2✔
2598
    session.wait_for_download_complete_or_client_stopped();
2✔
2599
    session.refresh(g_signed_test_user_token);
2✔
2600
    session.wait_for_download_complete_or_client_stopped();
2✔
2601
}
2✔
2602

2603

2604
// This test refreshes the user token multiple times right after binding
2605
// the session. The test tries to achieve a situation where a session is
2606
// enlisted to send after sending BIND but before receiving ALLOC.
2607
// The token is refreshed multiple times to increase the probability that the
2608
// refresh took place after BIND. The check of the test is just the absence of
2609
// errors.
2610
TEST(Sync_RefreshRightAfterBind)
2611
{
2✔
2612
    TEST_DIR(dir);
2✔
2613
    TEST_CLIENT_DB(db);
2✔
2614
    ClientServerFixture fixture(dir, test_context);
2✔
2615
    fixture.start();
2✔
2616

1✔
2617
    Session session = fixture.make_bound_session(db);
2✔
2618
    for (int i = 0; i < 50; ++i) {
102✔
2619
        session.refresh(g_signed_test_user_token_readonly);
100✔
2620
        std::this_thread::sleep_for(std::chrono::milliseconds{1});
100✔
2621
    }
100✔
2622
    session.wait_for_download_complete_or_client_stopped();
2✔
2623
}
2✔
2624

2625

2626
TEST(Sync_Permissions)
2627
{
2✔
2628
    TEST_CLIENT_DB(db_valid);
2✔
2629

1✔
2630
    bool did_see_error_for_valid = false;
2✔
2631

1✔
2632
    TEST_DIR(server_dir);
2✔
2633

1✔
2634
    ClientServerFixture fixture{server_dir, test_context};
2✔
2635
    fixture.set_client_side_error_handler([&](Status status, bool) {
1✔
2636
        CHECK_EQUAL("", status.reason());
×
2637
        did_see_error_for_valid = true;
×
2638
    });
×
2639
    fixture.start();
2✔
2640

1✔
2641
    Session session_valid = fixture.make_bound_session(db_valid, "/valid", g_signed_test_user_token_for_path);
2✔
2642

1✔
2643
    write_transaction(db_valid, [](WriteTransaction& wt) {
2✔
2644
        wt.get_group().add_table_with_primary_key("class_a", type_Int, "id");
2✔
2645
    });
2✔
2646

1✔
2647
    auto completed = session_valid.wait_for_upload_complete_or_client_stopped();
2✔
2648
    CHECK_NOT(did_see_error_for_valid);
2✔
2649
    CHECK(completed);
2✔
2650
}
2✔
2651

2652

2653
// This test checks that a client SSL connection to localhost succeeds when the
2654
// server presents a certificate issued to localhost signed by a CA whose
2655
// certificate the client loads.
2656
TEST(Sync_SSL_Certificate_1)
2657
{
2✔
2658
    TEST_DIR(server_dir);
2✔
2659
    TEST_CLIENT_DB(db);
2✔
2660
    std::string ca_dir = get_test_resource_path();
2✔
2661

1✔
2662
    ClientServerFixture::Config config;
2✔
2663
    config.enable_server_ssl = true;
2✔
2664
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2665
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2666

1✔
2667
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
2668

1✔
2669
    Session::Config session_config;
2✔
2670
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2671
    session_config.verify_servers_ssl_certificate = true;
2✔
2672
    session_config.ssl_trust_certificate_path = ca_dir + "crt.pem";
2✔
2673
    session_config.signed_user_token = g_signed_test_user_token;
2✔
2674

1✔
2675
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
2676
    session.bind();
2✔
2677

1✔
2678
    fixture.start();
2✔
2679
    session.wait_for_download_complete_or_client_stopped();
2✔
2680
}
2✔
2681

2682

2683
// This test checks that a client SSL connection to localhost does not succeed
2684
// when the server presents a certificate issued to localhost signed by a CA whose
2685
// certificate does not match the certificate loaded by the client.
2686
TEST(Sync_SSL_Certificate_2)
2687
{
2✔
2688
    bool did_fail = false;
2✔
2689
    TEST_DIR(server_dir);
2✔
2690
    TEST_CLIENT_DB(db);
2✔
2691
    std::string ca_dir = get_test_resource_path();
2✔
2692

1✔
2693
    ClientServerFixture::Config config;
2✔
2694
    config.enable_server_ssl = true;
2✔
2695
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2696
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2697

1✔
2698
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
2699

1✔
2700
    Session::Config session_config;
2✔
2701
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2702
    session_config.verify_servers_ssl_certificate = true;
2✔
2703
    session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem";
2✔
2704

1✔
2705
    auto error_handler = [&](Status status, bool) {
2✔
2706
        CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed);
2✔
2707
        did_fail = true;
2✔
2708
        fixture.stop();
2✔
2709
    };
2✔
2710
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
2711

1✔
2712
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
2✔
2713
    fixture.start();
2✔
2714
    session.wait_for_download_complete_or_client_stopped();
2✔
2715
    CHECK(did_fail);
2✔
2716
}
2✔
2717

2718

2719
// This test checks that a client SSL connection to localhost succeeds
2720
// if verify_servers_ssl_certificate = false, even when
2721
// when the server presents a certificate issued to localhost signed by a CA whose
2722
// certificate does not match the certificate loaded by the client.
2723
// This test is identical to Sync_SSL_Certificate_2 except for
2724
// the value of verify_servers_ssl_certificate.
2725
TEST(Sync_SSL_Certificate_3)
2726
{
2✔
2727
    TEST_DIR(server_dir);
2✔
2728
    TEST_CLIENT_DB(db);
2✔
2729
    std::string ca_dir = get_test_resource_path();
2✔
2730

1✔
2731
    ClientServerFixture::Config config;
2✔
2732
    config.enable_server_ssl = true;
2✔
2733
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2734
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2735

1✔
2736
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
2737

1✔
2738
    Session::Config session_config;
2✔
2739
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2740
    session_config.verify_servers_ssl_certificate = false;
2✔
2741
    session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem";
2✔
2742

1✔
2743
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
2✔
2744
    fixture.start();
2✔
2745
    session.wait_for_download_complete_or_client_stopped();
2✔
2746
}
2✔
2747

2748

2749
#if REALM_HAVE_SECURE_TRANSPORT
2750

2751
// This test checks that the client can also use a certificate in DER format.
2752
TEST(Sync_SSL_Certificate_DER)
2753
{
1✔
2754
    TEST_DIR(server_dir);
1✔
2755
    TEST_CLIENT_DB(db);
1✔
2756
    std::string ca_dir = get_test_resource_path();
1✔
2757

2758
    ClientServerFixture::Config config;
1✔
2759
    config.enable_server_ssl = true;
1✔
2760
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2761
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2762

2763
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
1✔
2764

2765
    Session::Config session_config;
1✔
2766
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2767
    session_config.verify_servers_ssl_certificate = true;
1✔
2768
    session_config.ssl_trust_certificate_path = ca_dir + "localhost-chain.crt.cer";
1✔
2769
    session_config.signed_user_token = g_signed_test_user_token;
1✔
2770

2771
    Session session = fixture.make_session(db, "/test", std::move(session_config));
1✔
2772
    session.bind();
1✔
2773

2774
    fixture.start();
1✔
2775
    session.wait_for_download_complete_or_client_stopped();
1✔
2776
}
1✔
2777

2778
#endif // REALM_HAVE_SECURE_TRANSPORT
2779

2780

2781
#if REALM_HAVE_OPENSSL
2782

2783
// This test checks that the SSL connection is accepted if the verify callback
2784
// always returns true.
2785
TEST(Sync_SSL_Certificate_Verify_Callback_1)
2786
{
1✔
2787
    TEST_DIR(server_dir);
1✔
2788
    TEST_CLIENT_DB(db);
1✔
2789
    std::string ca_dir = get_test_resource_path();
1✔
2790

1✔
2791
    Session::port_type server_port_ssl;
1✔
2792
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port, const char*,
1✔
2793
                                   size_t, int, int) {
2✔
2794
        CHECK_EQUAL(server_address, "localhost");
2✔
2795
        server_port_ssl = server_port;
2✔
2796
        return true;
2✔
2797
    };
2✔
2798

1✔
2799
    ClientServerFixture::Config config;
1✔
2800
    config.enable_server_ssl = true;
1✔
2801
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2802
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2803

1✔
2804
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2805

1✔
2806
    Session::Config session_config;
1✔
2807
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2808
    session_config.verify_servers_ssl_certificate = true;
1✔
2809
    session_config.ssl_trust_certificate_path = util::none;
1✔
2810
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2811

1✔
2812
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2813
    fixture.start();
1✔
2814
    session.wait_for_download_complete_or_client_stopped();
1✔
2815

1✔
2816
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2817
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2818
}
1✔
2819

2820

2821
// This test checks that the SSL connection is rejected if the verify callback
2822
// always returns false. It also checks that preverify_ok and depth have
2823
// the expected values.
2824
TEST(Sync_SSL_Certificate_Verify_Callback_2)
2825
{
1✔
2826
    bool did_fail = false;
1✔
2827
    TEST_DIR(server_dir);
1✔
2828
    TEST_CLIENT_DB(db);
1✔
2829
    std::string ca_dir = get_test_resource_path();
1✔
2830

1✔
2831
    Session::port_type server_port_ssl;
1✔
2832
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
1✔
2833
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
1✔
2834
        CHECK_EQUAL(server_address, "localhost");
1✔
2835
        server_port_ssl = server_port;
1✔
2836
        CHECK_EQUAL(preverify_ok, 0);
1✔
2837
        CHECK_EQUAL(depth, 1);
1✔
2838
        CHECK_EQUAL(pem_size, 2082);
1✔
2839
        std::string pem(pem_data, pem_size);
1✔
2840

1✔
2841
        std::string expected = "-----BEGIN CERTIFICATE-----\n"
1✔
2842
                               "MIIF0zCCA7ugAwIBAgIBCDANBgkqhkiG9w0BAQsFADB1MRIwEAYKCZImiZPyLGQB\n";
1✔
2843

1✔
2844
        CHECK_EQUAL(expected, pem.substr(0, expected.size()));
1✔
2845

1✔
2846
        return false;
1✔
2847
    };
1✔
2848

1✔
2849
    ClientServerFixture::Config config;
1✔
2850
    config.enable_server_ssl = true;
1✔
2851
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2852
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2853

1✔
2854
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2855

1✔
2856
    auto error_handler = [&](Status status, bool) {
1✔
2857
        CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed);
1✔
2858
        did_fail = true;
1✔
2859
        fixture.stop();
1✔
2860
    };
1✔
2861
    fixture.set_client_side_error_handler(std::move(error_handler));
1✔
2862

1✔
2863
    Session::Config session_config;
1✔
2864
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2865
    session_config.verify_servers_ssl_certificate = true;
1✔
2866
    session_config.ssl_trust_certificate_path = util::none;
1✔
2867
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2868

1✔
2869
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2870
    fixture.start();
1✔
2871
    session.wait_for_download_complete_or_client_stopped();
1✔
2872
    CHECK(did_fail);
1✔
2873
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2874
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2875
}
1✔
2876

2877

2878
// This test checks that the verify callback function receives the expected
2879
// certificates.
2880
TEST(Sync_SSL_Certificate_Verify_Callback_3)
2881
{
1✔
2882
    TEST_DIR(server_dir);
1✔
2883
    TEST_CLIENT_DB(db);
1✔
2884
    std::string ca_dir = get_test_resource_path();
1✔
2885

1✔
2886
    Session::port_type server_port_ssl = 0;
1✔
2887
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
1✔
2888
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
2✔
2889
        CHECK_EQUAL(server_address, "localhost");
2✔
2890
        server_port_ssl = server_port;
2✔
2891

2✔
2892
        CHECK(depth == 0 || depth == 1);
2✔
2893
        if (depth == 1) {
2✔
2894
            CHECK_EQUAL(pem_size, 2082);
1✔
2895
            CHECK_EQUAL(pem_data[93], 'G');
1✔
2896
        }
1✔
2897
        else {
1✔
2898
            CHECK_EQUAL(pem_size, 1700);
1✔
2899
            CHECK_EQUAL(preverify_ok, 1);
1✔
2900
            CHECK_EQUAL(pem_data[1667], '2');
1✔
2901
            CHECK_EQUAL(pem_data[1698], '-');
1✔
2902
            CHECK_EQUAL(pem_data[1699], '\n');
1✔
2903
        }
1✔
2904

2✔
2905
        return true;
2✔
2906
    };
2✔
2907

1✔
2908
    ClientServerFixture::Config config;
1✔
2909
    config.enable_server_ssl = true;
1✔
2910
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2911
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2912

1✔
2913
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2914

1✔
2915
    Session::Config session_config;
1✔
2916
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2917
    session_config.verify_servers_ssl_certificate = true;
1✔
2918
    session_config.ssl_trust_certificate_path = util::none;
1✔
2919
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2920

1✔
2921
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2922
    fixture.start();
1✔
2923
    session.wait_for_download_complete_or_client_stopped();
1✔
2924
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2925
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2926
}
1✔
2927

2928

2929
// This test is used to verify the ssl_verify_callback function against an
2930
// external server. The tests should only be used for debugging should normally
2931
// be disabled.
2932
TEST_IF(Sync_SSL_Certificate_Verify_Callback_External, false)
2933
{
2934
    const std::string server_address = "www.writeurl.com";
2935
    Session::port_type port = 443;
2936

2937
    TEST_CLIENT_DB(db);
2938

2939
    Client::Config config;
2940
    config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2941
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(config.logger, "");
2942
    config.socket_provider = socket_provider;
2943
    config.reconnect_mode = ReconnectMode::testing;
2944
    Client client(config);
2945

2946
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
2947
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
2948
        StringData pem{pem_data, pem_size};
2949
        test_context.logger->info("server_address = %1, server_port = %2, pem =\n%3\n, "
2950
                                  " preverify_ok = %4, depth = %5",
2951
                                  server_address, server_port, pem, preverify_ok, depth);
2952
        if (depth == 0)
2953
            client.shutdown();
2954
        return true;
2955
    };
2956

2957
    Session::Config session_config;
2958
    session_config.server_address = server_address;
2959
    session_config.server_port = port;
2960
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2961
    session_config.verify_servers_ssl_certificate = true;
2962
    session_config.ssl_trust_certificate_path = util::none;
2963
    session_config.ssl_verify_callback = ssl_verify_callback;
2964

2965
    Session session(client, db, nullptr, nullptr, std::move(session_config));
2966
    session.bind();
2967
    session.wait_for_download_complete_or_client_stopped();
2968

2969
    client.shutdown_and_wait();
2970
}
2971

2972
#endif // REALM_HAVE_OPENSSL
2973

2974

2975
// This test has a single client connected to a server with
2976
// one session.
2977
// The client creates four changesets at various times and
2978
// uploads them to the server. The session has a registered
2979
// progress_handler. It is checked that downloaded_bytes,
2980
// downloadable_bytes, uploaded_bytes, and uploadable_bytes
2981
// are correct. This client does not have any downloaded_bytes
2982
// or downloadable bytes because it created all the changesets
2983
// itself.
2984
TEST(Sync_UploadDownloadProgress_1)
2985
{
2✔
2986
    TEST_DIR(server_dir);
2✔
2987
    TEST_CLIENT_DB(db);
2✔
2988

1✔
2989
    uint_fast64_t global_snapshot_version;
2✔
2990

1✔
2991
    {
2✔
2992
        int handler_entry = 0;
2✔
2993

1✔
2994
        bool cond_var_signaled = false;
2✔
2995
        std::mutex mutex;
2✔
2996
        std::condition_variable cond_var;
2✔
2997

1✔
2998
        std::atomic<uint_fast64_t> downloaded_bytes;
2✔
2999
        std::atomic<uint_fast64_t> downloadable_bytes;
2✔
3000
        std::atomic<uint_fast64_t> uploaded_bytes;
2✔
3001
        std::atomic<uint_fast64_t> uploadable_bytes;
2✔
3002
        std::atomic<uint_fast64_t> progress_version;
2✔
3003
        std::atomic<uint_fast64_t> snapshot_version;
2✔
3004

1✔
3005
        ClientServerFixture fixture(server_dir, test_context);
2✔
3006
        fixture.start();
2✔
3007

1✔
3008
        Session session = fixture.make_session(db, "/test");
2✔
3009

1✔
3010
        auto progress_handler = [&](uint_fast64_t downloaded, uint_fast64_t downloadable, uint_fast64_t uploaded,
2✔
3011
                                    uint_fast64_t uploadable, uint_fast64_t progress, uint_fast64_t snapshot) {
12✔
3012
            downloaded_bytes = downloaded;
12✔
3013
            downloadable_bytes = downloadable;
12✔
3014
            uploaded_bytes = uploaded;
12✔
3015
            uploadable_bytes = uploadable;
12✔
3016
            progress_version = progress;
12✔
3017
            snapshot_version = snapshot;
12✔
3018

6✔
3019
            if (handler_entry == 0) {
12✔
3020
                std::unique_lock<std::mutex> lock(mutex);
2✔
3021
                cond_var_signaled = true;
2✔
3022
                lock.unlock();
2✔
3023
                cond_var.notify_one();
2✔
3024
            }
2✔
3025
            ++handler_entry;
12✔
3026
        };
12✔
3027

1✔
3028
        std::unique_lock<std::mutex> lock(mutex);
2✔
3029
        session.set_progress_handler(progress_handler);
2✔
3030
        session.bind();
2✔
3031
        cond_var.wait(lock, [&] {
4✔
3032
            return cond_var_signaled;
4✔
3033
        });
4✔
3034

1✔
3035
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3036
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3037
        CHECK_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3038
        CHECK_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3039
        CHECK_GREATER_EQUAL(snapshot_version, uint_fast64_t(1));
2✔
3040

1✔
3041
        uint_fast64_t commit_version;
2✔
3042
        {
2✔
3043
            WriteTransaction wt{db};
2✔
3044
            TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3045
            tr->add_column(type_Int, "integer column");
2✔
3046
            commit_version = wt.commit();
2✔
3047
        }
2✔
3048

1✔
3049
        session.wait_for_upload_complete_or_client_stopped();
2✔
3050
        session.wait_for_download_complete_or_client_stopped();
2✔
3051

1✔
3052
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3053
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3054
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3055
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3056
        CHECK_GREATER(progress_version, uint_fast64_t(0));
2✔
3057
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
3058

1✔
3059
        {
2✔
3060
            WriteTransaction wt{db};
2✔
3061
            TableRef tr = wt.get_table("class_table");
2✔
3062
            tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3063
            commit_version = wt.commit();
2✔
3064
        }
2✔
3065

1✔
3066
        session.wait_for_upload_complete_or_client_stopped();
2✔
3067
        session.wait_for_download_complete_or_client_stopped();
2✔
3068

1✔
3069
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3070
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3071
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3072
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3073
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
3074

1✔
3075
        global_snapshot_version = snapshot_version;
2✔
3076
    }
2✔
3077

1✔
3078
    {
2✔
3079
        // Here we check that the progress handler is called
1✔
3080
        // after the session is bound, and that the values
1✔
3081
        // are the ones stored in the Realm in the previous
1✔
3082
        // session.
1✔
3083

1✔
3084
        bool cond_var_signaled = false;
2✔
3085
        std::mutex mutex;
2✔
3086
        std::condition_variable cond_var;
2✔
3087

1✔
3088
        Client::Config config;
2✔
3089
        config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3090
        auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(config.logger, "");
2✔
3091
        config.socket_provider = socket_provider;
2✔
3092
        config.reconnect_mode = ReconnectMode::testing;
2✔
3093
        Client client(config);
2✔
3094

1✔
3095
        Session::Config sess_config;
2✔
3096
        sess_config.server_address = "no server";
2✔
3097
        sess_config.server_port = 8000;
2✔
3098
        sess_config.realm_identifier = "/test";
2✔
3099
        sess_config.signed_user_token = g_signed_test_user_token;
2✔
3100

1✔
3101
        Session session(client, db, nullptr, nullptr, std::move(sess_config));
2✔
3102

1✔
3103
        int number_of_handler_calls = 0;
2✔
3104

1✔
3105
        auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3106
                                    uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3107
                                    uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
2✔
3108
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3109
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3110
            CHECK_NOT_EQUAL(uploaded_bytes, 0);
2✔
3111
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3112
            CHECK_EQUAL(progress_version, 0);
2✔
3113
            CHECK_EQUAL(snapshot_version, global_snapshot_version);
2✔
3114
            number_of_handler_calls++;
2✔
3115

1✔
3116
            std::unique_lock<std::mutex> lock(mutex);
2✔
3117
            cond_var_signaled = true;
2✔
3118
            lock.unlock();
2✔
3119
            cond_var.notify_one();
2✔
3120
        };
2✔
3121

1✔
3122
        std::unique_lock<std::mutex> lock(mutex);
2✔
3123
        session.set_progress_handler(progress_handler);
2✔
3124
        session.bind();
2✔
3125
        cond_var.wait(lock, [&] {
4✔
3126
            return cond_var_signaled;
4✔
3127
        });
4✔
3128

1✔
3129
        client.shutdown();
2✔
3130
        CHECK_EQUAL(number_of_handler_calls, 1);
2✔
3131
    }
2✔
3132
}
2✔
3133

3134

3135
// This test creates one server and a client with
3136
// two sessions that synchronizes with the same server Realm.
3137
// The clients generate changesets, uploads and downloads, and
3138
// waits for upload/download completion. Both sessions have a
3139
// progress handler registered, and it is checked that the
3140
// progress handlers report the correct values.
3141
TEST(Sync_UploadDownloadProgress_2)
3142
{
2✔
3143
    TEST_DIR(server_dir);
2✔
3144
    TEST_CLIENT_DB(db_1);
2✔
3145
    TEST_CLIENT_DB(db_2);
2✔
3146

1✔
3147
    ClientServerFixture fixture(server_dir, test_context);
2✔
3148
    fixture.start();
2✔
3149

1✔
3150
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
3151
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
3152

1✔
3153
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3154
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3155
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3156
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3157
    uint_fast64_t progress_version_1 = 123;
2✔
3158
    uint_fast64_t snapshot_version_1 = 0;
2✔
3159

1✔
3160
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3161
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3162
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
22✔
3163
        downloaded_bytes_1 = downloaded_bytes;
22✔
3164
        downloadable_bytes_1 = downloadable_bytes;
22✔
3165
        uploaded_bytes_1 = uploaded_bytes;
22✔
3166
        uploadable_bytes_1 = uploadable_bytes;
22✔
3167
        progress_version_1 = progress_version;
22✔
3168
        snapshot_version_1 = snapshot_version;
22✔
3169
    };
22✔
3170

1✔
3171
    session_1.set_progress_handler(progress_handler_1);
2✔
3172

1✔
3173
    uint_fast64_t downloaded_bytes_2 = 123;
2✔
3174
    uint_fast64_t downloadable_bytes_2 = 123;
2✔
3175
    uint_fast64_t uploaded_bytes_2 = 123;
2✔
3176
    uint_fast64_t uploadable_bytes_2 = 123;
2✔
3177
    uint_fast64_t progress_version_2 = 123;
2✔
3178
    uint_fast64_t snapshot_version_2 = 0;
2✔
3179

1✔
3180
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3181
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3182
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
19✔
3183
        downloaded_bytes_2 = downloaded_bytes;
19✔
3184
        downloadable_bytes_2 = downloadable_bytes;
19✔
3185
        uploaded_bytes_2 = uploaded_bytes;
19✔
3186
        uploadable_bytes_2 = uploadable_bytes;
19✔
3187
        progress_version_2 = progress_version;
19✔
3188
        snapshot_version_2 = snapshot_version;
19✔
3189
    };
19✔
3190

1✔
3191
    session_2.set_progress_handler(progress_handler_2);
2✔
3192

1✔
3193
    session_1.bind();
2✔
3194
    session_2.bind();
2✔
3195

1✔
3196
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3197
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3198
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3199
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3200

1✔
3201
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3202
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3203
    CHECK_EQUAL(downloaded_bytes_1, downloaded_bytes_2);
2✔
3204
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3205
    CHECK_GREATER(progress_version_1, 0);
2✔
3206
    CHECK_GREATER(snapshot_version_1, 0);
2✔
3207

1✔
3208
    CHECK_EQUAL(uploaded_bytes_1, 0);
2✔
3209
    CHECK_EQUAL(uploadable_bytes_1, 0);
2✔
3210

1✔
3211
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3212
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3213
    CHECK_GREATER(progress_version_2, 0);
2✔
3214
    CHECK_GREATER(snapshot_version_2, 0);
2✔
3215

1✔
3216
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3217
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3218
        tr->add_column(type_Int, "integer column");
2✔
3219
    });
2✔
3220

1✔
3221
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3222
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3223
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3224
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3225

1✔
3226
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3227
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3228

1✔
3229
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3230
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3231

1✔
3232
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3233
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3234

1✔
3235
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3236
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3237

1✔
3238
    CHECK_GREATER(snapshot_version_1, 1);
2✔
3239
    CHECK_GREATER(snapshot_version_2, 1);
2✔
3240

1✔
3241
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3242
        TableRef tr = wt.get_table("class_table");
2✔
3243
        tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3244
    });
2✔
3245

1✔
3246
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3247
        TableRef tr = wt.get_table("class_table");
2✔
3248
        tr->create_object_with_primary_key(2).set("integer column", 44);
2✔
3249
    });
2✔
3250

1✔
3251
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3252
        TableRef tr = wt.get_table("class_table");
2✔
3253
        tr->create_object_with_primary_key(3).set("integer column", 43);
2✔
3254
    });
2✔
3255

1✔
3256
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3257
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3258
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3259
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3260

1✔
3261
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3262
    CHECK_NOT_EQUAL(downloadable_bytes_1, 0);
2✔
3263

1✔
3264
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3265
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3266

1✔
3267
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3268
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3269

1✔
3270
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3271
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3272

1✔
3273
    CHECK_GREATER(snapshot_version_1, 4);
2✔
3274
    CHECK_GREATER(snapshot_version_2, 3);
2✔
3275

1✔
3276
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3277
        TableRef tr = wt.get_table("class_table");
2✔
3278
        tr->begin()->set("integer column", 101);
2✔
3279
    });
2✔
3280

1✔
3281
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3282
        TableRef tr = wt.get_table("class_table");
2✔
3283
        tr->begin()->set("integer column", 102);
2✔
3284
    });
2✔
3285

1✔
3286
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3287
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3288
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3289
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3290

1✔
3291
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3292

1✔
3293
    // uncertainty due to merge
1✔
3294
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3295

1✔
3296
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3297
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3298

1✔
3299
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3300
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3301

1✔
3302
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3303
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3304

1✔
3305
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3306
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3307

1✔
3308
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3309
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3310

1✔
3311
    // Check convergence.
1✔
3312
    {
2✔
3313
        ReadTransaction rt_1(db_1);
2✔
3314
        ReadTransaction rt_2(db_2);
2✔
3315
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
3316
    }
2✔
3317
}
2✔
3318

3319

3320
// This test creates a server and a client. Initially, the server is not running.
3321
// The client generates changes and binds a session. It is verified that the
3322
// progress_handler() is called and that the four arguments of progress_handler()
3323
// have the correct values. The server is started in the first call to
3324
// progress_handler() and it is checked that after upload and download completion,
3325
// the upload_progress_handler has been called again, and that the four arguments
3326
// have the correct values. After this, the server is stopped and the client produces
3327
// more changes. It is checked that the progress_handler() is called and that the
3328
// final values are correct.
3329
TEST(Sync_UploadDownloadProgress_3)
3330
{
2✔
3331
    TEST_DIR(server_dir);
2✔
3332
    TEST_CLIENT_DB(db);
2✔
3333

1✔
3334
    std::string server_address = "localhost";
2✔
3335

1✔
3336
    Server::Config server_config;
2✔
3337
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3338
    server_config.listen_address = server_address;
2✔
3339
    server_config.listen_port = "";
2✔
3340
    server_config.tcp_no_delay = true;
2✔
3341

1✔
3342
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3343
    Server server(server_dir, std::move(public_key), server_config);
2✔
3344
    server.start();
2✔
3345
    auto server_port = server.listen_endpoint().port();
2✔
3346

1✔
3347
    ThreadWrapper server_thread;
2✔
3348

1✔
3349
    // The server is not running.
1✔
3350

1✔
3351
    {
2✔
3352
        WriteTransaction wt{db};
2✔
3353
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3354
        tr->add_column(type_Int, "integer column");
2✔
3355
        wt.commit();
2✔
3356
    }
2✔
3357

1✔
3358

1✔
3359
    Client::Config client_config;
2✔
3360
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3361
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3362
    client_config.socket_provider = socket_provider;
2✔
3363
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3364
    Client client(client_config);
2✔
3365

1✔
3366
    // when connecting to the C++ server, use URL prefix:
1✔
3367
    Session::Config config;
2✔
3368
    config.service_identifier = "/realm-sync";
2✔
3369
    config.server_address = server_address;
2✔
3370
    config.signed_user_token = g_signed_test_user_token;
2✔
3371
    config.server_port = server_port;
2✔
3372
    config.realm_identifier = "/test";
2✔
3373

1✔
3374
    Session session(client, db, nullptr, nullptr, std::move(config));
2✔
3375

1✔
3376
    // entry is used to count the number of calls to
1✔
3377
    // progress_handler. At the first call, the server is
1✔
3378
    // not running, and it is started by progress_handler().
1✔
3379

1✔
3380
    bool should_signal_cond_var = false;
2✔
3381
    auto signal_pf = util::make_promise_future<void>();
2✔
3382

1✔
3383
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3384
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3385
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3386
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3387
    uint_fast64_t progress_version_1 = 123;
2✔
3388
    uint_fast64_t snapshot_version_1 = 0;
2✔
3389

1✔
3390
    auto progress_handler = [&, entry = int(0), promise = util::CopyablePromiseHolder(std::move(signal_pf.promise))](
2✔
3391
                                uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3392
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3393
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) mutable {
8✔
3394
        downloaded_bytes_1 = downloaded_bytes;
8✔
3395
        downloadable_bytes_1 = downloadable_bytes;
8✔
3396
        uploaded_bytes_1 = uploaded_bytes;
8✔
3397
        uploadable_bytes_1 = uploadable_bytes;
8✔
3398
        progress_version_1 = progress_version;
8✔
3399
        snapshot_version_1 = snapshot_version;
8✔
3400

4✔
3401
        if (entry == 0) {
8✔
3402
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3403
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3404
            CHECK_EQUAL(uploaded_bytes, 0);
2✔
3405
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3406
            CHECK_EQUAL(snapshot_version, 2);
2✔
3407
        }
2✔
3408

4✔
3409
        if (entry == 0) {
8✔
3410
            server_thread.start([&] {
2✔
3411
                server.run();
2✔
3412
            });
2✔
3413
        }
2✔
3414

4✔
3415
        if (should_signal_cond_var) {
8✔
3416
            promise.get_promise().emplace_value();
2✔
3417
        }
2✔
3418

4✔
3419
        entry++;
8✔
3420
    };
8✔
3421

1✔
3422
    session.set_progress_handler(progress_handler);
2✔
3423

1✔
3424
    session.bind();
2✔
3425

1✔
3426
    session.wait_for_upload_complete_or_client_stopped();
2✔
3427
    session.wait_for_download_complete_or_client_stopped();
2✔
3428

1✔
3429
    // Now the server is running.
1✔
3430

1✔
3431
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3432
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3433
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3434
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3435
    CHECK_GREATER(progress_version_1, 0);
2✔
3436
    CHECK_GREATER_EQUAL(snapshot_version_1, 2);
2✔
3437

1✔
3438
    server.stop();
2✔
3439

1✔
3440
    // The server is stopped
1✔
3441

1✔
3442
    should_signal_cond_var = true;
2✔
3443

1✔
3444
    uint_fast64_t commited_version;
2✔
3445
    {
2✔
3446
        WriteTransaction wt{db};
2✔
3447
        TableRef tr = wt.get_table("class_table");
2✔
3448
        tr->create_object_with_primary_key(123).set("integer column", 42);
2✔
3449
        commited_version = wt.commit();
2✔
3450
    }
2✔
3451

1✔
3452
    signal_pf.future.get();
2✔
3453

1✔
3454
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3455
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3456
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3457
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3458
    CHECK_EQUAL(snapshot_version_1, commited_version);
2✔
3459

1✔
3460
    server_thread.join();
2✔
3461
}
2✔
3462

3463

3464
// This test creates a server and two clients. The first client uploads two
3465
// large changesets. The other client downloads them. The download messages to
3466
// the second client contains one changeset because the changesets are larger
3467
// than the soft size limit for changesets in the DOWNLOAD message. This implies
3468
// that after receiving the first DOWNLOAD message, the second client will have
3469
// downloaded_bytes < downloadable_bytes.
3470
TEST(Sync_UploadDownloadProgress_4)
3471
{
2✔
3472
    TEST_DIR(server_dir);
2✔
3473
    TEST_CLIENT_DB(db_1);
2✔
3474
    TEST_CLIENT_DB(db_2);
2✔
3475

1✔
3476
    {
2✔
3477
        WriteTransaction wt{db_1};
2✔
3478
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3479
        auto col = tr->add_column(type_Binary, "binary column");
2✔
3480
        tr->create_object_with_primary_key(1);
2✔
3481
        std::string str(size_t(5e5), 'a');
2✔
3482
        BinaryData bd(str.data(), str.size());
2✔
3483
        tr->begin()->set(col, bd);
2✔
3484
        wt.commit();
2✔
3485
    }
2✔
3486

1✔
3487
    {
2✔
3488
        WriteTransaction wt{db_1};
2✔
3489
        TableRef tr = wt.get_table("class_table");
2✔
3490
        auto col = tr->get_column_key("binary column");
2✔
3491
        tr->create_object_with_primary_key(2);
2✔
3492
        std::string str(size_t(1e6), 'a');
2✔
3493
        BinaryData bd(str.data(), str.size());
2✔
3494
        tr->begin()->set(col, bd);
2✔
3495
        wt.commit();
2✔
3496
    }
2✔
3497

1✔
3498
    ClientServerFixture::Config config;
2✔
3499
    config.max_download_size = size_t(1e5);
2✔
3500
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3501
    fixture.start();
2✔
3502

1✔
3503
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
3504

1✔
3505
    int entry_1 = 0;
2✔
3506

1✔
3507
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3508
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3509
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
8✔
3510
        CHECK_EQUAL(downloaded_bytes, 0);
8✔
3511
        CHECK_EQUAL(downloadable_bytes, 0);
8✔
3512
        CHECK_NOT_EQUAL(uploadable_bytes, 0);
8✔
3513

4✔
3514
        switch (entry_1) {
8✔
3515
            case 0:
2✔
3516
                // Session is bound and initial state is reported
1✔
3517
                CHECK_EQUAL(progress_version, 0);
2✔
3518
                CHECK_EQUAL(uploaded_bytes, 0);
2✔
3519
                CHECK_EQUAL(snapshot_version, 3);
2✔
3520
                break;
2✔
3521

3522
            case 1:
2✔
3523
                // We've received the empty DOWNLOAD message and now have reliable
1✔
3524
                // download progress
1✔
3525
                CHECK_EQUAL(progress_version, 1);
2✔
3526
                CHECK_EQUAL(uploaded_bytes, 0);
2✔
3527
                CHECK_EQUAL(snapshot_version, 5);
2✔
3528
                break;
2✔
3529

3530
            case 2:
2✔
3531
                // First UPLOAD is complete, but we still have more to upload
1✔
3532
                // because the changesets are too large to batch into a single upload
1✔
3533
                CHECK_EQUAL(progress_version, 1);
2✔
3534
                CHECK_GREATER(uploaded_bytes, 0);
2✔
3535
                CHECK_LESS(uploaded_bytes, uploadable_bytes);
2✔
3536
                CHECK_EQUAL(snapshot_version, 6);
2✔
3537
                break;
2✔
3538

3539
            case 3:
2✔
3540
                // Second UPLOAD is complete and we're done uploading
1✔
3541
                CHECK_EQUAL(progress_version, 1);
2✔
3542
                CHECK_EQUAL(uploaded_bytes, uploadable_bytes);
2✔
3543
                CHECK_EQUAL(snapshot_version, 7);
2✔
3544
                break;
2✔
3545
        }
8✔
3546

4✔
3547
        ++entry_1;
8✔
3548
    };
8✔
3549

1✔
3550
    session_1.set_progress_handler(progress_handler_1);
2✔
3551

1✔
3552
    session_1.bind();
2✔
3553

1✔
3554
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3555
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3556

1✔
3557
    CHECK_EQUAL(entry_1, 4);
2✔
3558

1✔
3559
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
3560

1✔
3561
    int entry_2 = 0;
2✔
3562

1✔
3563
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3564
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3565
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
6✔
3566
        CHECK_EQUAL(uploaded_bytes, 0);
6✔
3567
        CHECK_EQUAL(uploadable_bytes, 0);
6✔
3568

3✔
3569
        switch (entry_2) {
6✔
3570
            case 0:
2✔
3571
                // Session is bound and initial state is reported
1✔
3572
                CHECK_EQUAL(progress_version, 0);
2✔
3573
                CHECK_EQUAL(downloaded_bytes, 0);
2✔
3574
                CHECK_EQUAL(downloadable_bytes, 0);
2✔
3575
                CHECK_EQUAL(snapshot_version, 1);
2✔
3576
                break;
2✔
3577

3578
            case 1:
2✔
3579
                // First DOWNLOAD message received. Some data is downloaded, but
1✔
3580
                // download isn't compelte
1✔
3581
                CHECK_EQUAL(progress_version, 1);
2✔
3582
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3583
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3584
                CHECK_LESS(downloaded_bytes, downloadable_bytes);
2✔
3585
                CHECK_EQUAL(snapshot_version, 3);
2✔
3586
                break;
2✔
3587

3588
            case 2:
2✔
3589
                // Second DOWNLOAD message received. Download is now complete.
1✔
3590
                CHECK_EQUAL(progress_version, 1);
2✔
3591
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3592
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3593
                CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
3594
                CHECK_EQUAL(snapshot_version, 4);
2✔
3595
                break;
2✔
3596
        }
6✔
3597
        ++entry_2;
6✔
3598
    };
6✔
3599

1✔
3600
    session_2.set_progress_handler(progress_handler_2);
2✔
3601

1✔
3602
    session_2.bind();
2✔
3603

1✔
3604
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3605
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3606
    CHECK_EQUAL(entry_2, 3);
2✔
3607
}
2✔
3608

3609

3610
// This test has a single client connected to a server with one session. The
3611
// client does not create any changesets. The test verifies that the client gets
3612
// a confirmation from the server of downloadable_bytes = 0.
3613
TEST(Sync_UploadDownloadProgress_5)
3614
{
2✔
3615
    TEST_DIR(server_dir);
2✔
3616
    TEST_CLIENT_DB(db);
2✔
3617

1✔
3618
    auto [progress_handled_promise, progress_handled] = util::make_promise_future<void>();
2✔
3619

1✔
3620
    ClientServerFixture fixture(server_dir, test_context);
2✔
3621
    fixture.start();
2✔
3622

1✔
3623
    Session session = fixture.make_session(db, "/test");
2✔
3624

1✔
3625
    auto progress_handler = [&, promise = util::CopyablePromiseHolder(std::move(progress_handled_promise))](
2✔
3626
                                uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3627
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3628
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) mutable {
4✔
3629
        CHECK_EQUAL(downloaded_bytes, 0);
4✔
3630
        CHECK_EQUAL(downloadable_bytes, 0);
4✔
3631
        CHECK_EQUAL(uploaded_bytes, 0);
4✔
3632
        CHECK_EQUAL(uploadable_bytes, 0);
4✔
3633

2✔
3634
        if (progress_version > 0) {
4✔
3635
            CHECK_EQUAL(snapshot_version, 3);
2✔
3636
            promise.get_promise().emplace_value();
2✔
3637
        }
2✔
3638
    };
4✔
3639

1✔
3640
    session.set_progress_handler(progress_handler);
2✔
3641

1✔
3642
    session.bind();
2✔
3643
    progress_handled.get();
2✔
3644

1✔
3645
    // The check is that we reach this point.
1✔
3646
}
2✔
3647

3648

3649
// This test has a single client connected to a server with one session.
3650
// The session has a registered progress handler.
3651
TEST(Sync_UploadDownloadProgress_6)
3652
{
2✔
3653
    TEST_DIR(server_dir);
2✔
3654
    TEST_CLIENT_DB(db);
2✔
3655

1✔
3656
    Server::Config server_config;
2✔
3657
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3658
    server_config.listen_address = "localhost";
2✔
3659
    server_config.listen_port = "";
2✔
3660
    server_config.tcp_no_delay = true;
2✔
3661

1✔
3662
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3663
    Server server(server_dir, std::move(public_key), server_config);
2✔
3664
    server.start();
2✔
3665

1✔
3666
    auto server_port = server.listen_endpoint().port();
2✔
3667

1✔
3668
    ThreadWrapper server_thread;
2✔
3669
    server_thread.start([&] {
2✔
3670
        server.run();
2✔
3671
    });
2✔
3672

1✔
3673
    Client::Config client_config;
2✔
3674
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3675
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3676
    client_config.socket_provider = socket_provider;
2✔
3677
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3678
    client_config.one_connection_per_session = false;
2✔
3679
    Client client(client_config);
2✔
3680

1✔
3681
    Session::Config session_config;
2✔
3682
    session_config.server_address = "localhost";
2✔
3683
    session_config.server_port = server_port;
2✔
3684
    session_config.realm_identifier = "/test";
2✔
3685
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3686

1✔
3687
    std::mutex mutex;
2✔
3688
    std::condition_variable session_cv;
2✔
3689
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3690

1✔
3691
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3692
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3693
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
2✔
3694
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3695
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3696
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3697
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3698
        CHECK_EQUAL(progress_version, 0);
2✔
3699
        CHECK_EQUAL(snapshot_version, 1);
2✔
3700
        std::lock_guard lock{mutex};
2✔
3701
        session.reset();
2✔
3702
        session_cv.notify_one();
2✔
3703
    };
2✔
3704

1✔
3705
    session->set_progress_handler(progress_handler);
2✔
3706

1✔
3707
    {
2✔
3708
        std::unique_lock lock{mutex};
2✔
3709
        session->bind();
2✔
3710
        // Wait until the progress handler is called on the session before tearing down the client
1✔
3711
        session_cv.wait_for(lock, std::chrono::seconds(30), [&session]() {
4✔
3712
            return !bool(session);
4✔
3713
        });
4✔
3714
    }
2✔
3715

1✔
3716
    client.shutdown_and_wait();
2✔
3717
    server.stop();
2✔
3718
    server_thread.join();
2✔
3719

1✔
3720
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
1✔
3721
    // down the active session
1✔
3722
}
2✔
3723

3724
// This test has a single client starting to connect to the server with one session.
3725
// The client is torn down immediately after bind is called on the session.
3726
// The session will still be active and has an unactualized session wrapper when the
3727
// client is torn down, which leads to both calls to finalize_before_actualization() and
3728
// and finalize().
3729
TEST(Sync_UploadDownloadProgress_7)
3730
{
2✔
3731
    TEST_DIR(server_dir);
2✔
3732
    TEST_CLIENT_DB(db);
2✔
3733

1✔
3734
    Server::Config server_config;
2✔
3735
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3736
    server_config.listen_address = "localhost";
2✔
3737
    server_config.listen_port = "";
2✔
3738
    server_config.tcp_no_delay = true;
2✔
3739

1✔
3740
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3741
    Server server(server_dir, std::move(public_key), server_config);
2✔
3742
    server.start();
2✔
3743

1✔
3744
    auto server_port = server.listen_endpoint().port();
2✔
3745

1✔
3746
    ThreadWrapper server_thread;
2✔
3747
    server_thread.start([&] {
2✔
3748
        server.run();
2✔
3749
    });
2✔
3750

1✔
3751
    Client::Config client_config;
2✔
3752
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3753
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3754
    client_config.socket_provider = socket_provider;
2✔
3755
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3756
    client_config.one_connection_per_session = false;
2✔
3757
    Client client(client_config);
2✔
3758

1✔
3759
    Session::Config session_config;
2✔
3760
    session_config.server_address = "localhost";
2✔
3761
    session_config.server_port = server_port;
2✔
3762
    session_config.realm_identifier = "/test";
2✔
3763
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3764

1✔
3765
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3766
    session->bind();
2✔
3767

1✔
3768
    client.shutdown_and_wait();
2✔
3769
    server.stop();
2✔
3770
    server_thread.join();
2✔
3771

1✔
3772
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
1✔
3773
    // down the session that is in the process of being created.
1✔
3774
}
2✔
3775

3776
TEST(Sync_UploadProgress_EmptyCommits)
3777
{
2✔
3778
    TEST_DIR(server_dir);
2✔
3779
    TEST_CLIENT_DB(db);
2✔
3780

1✔
3781
    ClientServerFixture fixture(server_dir, test_context);
2✔
3782
    fixture.start();
2✔
3783
    Session session = fixture.make_session(db, "/test");
2✔
3784

1✔
3785
    {
2✔
3786
        WriteTransaction wt{db};
2✔
3787
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "_id");
2✔
3788
        wt.commit();
2✔
3789
    }
2✔
3790

1✔
3791
    std::atomic<int> entry = 0;
2✔
3792
    session.set_progress_handler(
2✔
3793
        [&](uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t) {
10✔
3794
            ++entry;
10✔
3795
        });
10✔
3796
    session.bind();
2✔
3797

1✔
3798
    // Each step calls wait_for_upload_complete twice because upload completion
1✔
3799
    // is fired before progress handlers, so we need another hop through the
1✔
3800
    // event loop after upload completion to know that the handler has been called
1✔
3801
    session.wait_for_upload_complete_or_client_stopped();
2✔
3802
    session.wait_for_upload_complete_or_client_stopped();
2✔
3803

1✔
3804
    // Binding produces three notifications: the initial state, one after receiving
1✔
3805
    // the DOWNLOAD message, and one after uploading the schema
1✔
3806
    CHECK_EQUAL(entry, 3);
2✔
3807

1✔
3808
    // No notification sent because an empty commit doesn't change uploadable_bytes
1✔
3809
    {
2✔
3810
        WriteTransaction wt{db};
2✔
3811
        wt.commit();
2✔
3812
    }
2✔
3813
    session.wait_for_upload_complete_or_client_stopped();
2✔
3814
    session.wait_for_upload_complete_or_client_stopped();
2✔
3815
    CHECK_EQUAL(entry, 3);
2✔
3816

1✔
3817
    // Both the external and local commits are empty, so again no change in
1✔
3818
    // uploadable_bytes
1✔
3819
    {
2✔
3820
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3821
        WriteTransaction wt{db2};
2✔
3822
        wt.commit();
2✔
3823
        WriteTransaction wt2{db};
2✔
3824
        wt2.commit();
2✔
3825
    }
2✔
3826
    session.wait_for_upload_complete_or_client_stopped();
2✔
3827
    session.wait_for_upload_complete_or_client_stopped();
2✔
3828
    CHECK_EQUAL(entry, 3);
2✔
3829

1✔
3830
    // Local commit is empty, but the changeset created by the external write
1✔
3831
    // is discovered after the local write, resulting in two notifications (one
1✔
3832
    // before uploading and one after).
1✔
3833
    {
2✔
3834
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3835
        WriteTransaction wt{db2};
2✔
3836
        wt.get_table("class_table")->create_object_with_primary_key(0);
2✔
3837
        wt.commit();
2✔
3838
        WriteTransaction wt2{db};
2✔
3839
        wt2.commit();
2✔
3840
    }
2✔
3841
    session.wait_for_upload_complete_or_client_stopped();
2✔
3842
    session.wait_for_upload_complete_or_client_stopped();
2✔
3843
    CHECK_EQUAL(entry, 5);
2✔
3844
}
2✔
3845

3846
TEST(Sync_MultipleSyncAgentsNotAllowed)
3847
{
2✔
3848
    // At most one sync agent is allowed to participate in a Realm file access
1✔
3849
    // session at any particular point in time. Note that a Realm file access
1✔
3850
    // session is a group of temporally overlapping accesses to a Realm file,
1✔
3851
    // and that the group of participants is the transitive closure of a
1✔
3852
    // particular session participant over the "temporally overlapping access"
1✔
3853
    // relation.
1✔
3854

1✔
3855
    TEST_CLIENT_DB(db);
2✔
3856
    Client::Config config;
2✔
3857
    config.logger = test_context.logger;
2✔
3858
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(
2✔
3859
        config.logger, "", nullptr, websocket::DefaultSocketProvider::AutoStart{false});
2✔
3860
    config.socket_provider = socket_provider;
2✔
3861
    config.reconnect_mode = ReconnectMode::testing;
2✔
3862
    Client client{config};
2✔
3863
    {
2✔
3864
        Session::Config config_1;
2✔
3865
        config_1.realm_identifier = "blablabla";
2✔
3866
        Session::Config config_2;
2✔
3867
        config_2.realm_identifier = config_1.realm_identifier;
2✔
3868
        Session session_1{client, db, nullptr, nullptr, std::move(config_1)};
2✔
3869
        Session session_2{client, db, nullptr, nullptr, std::move(config_2)};
2✔
3870
        session_1.bind();
2✔
3871
        session_2.bind();
2✔
3872
        CHECK_THROW(
2✔
3873
            websocket::DefaultSocketProvider::OnlyForTesting::run_event_loop_on_current_thread(socket_provider.get()),
2✔
3874
            MultipleSyncAgents);
2✔
3875
        websocket::DefaultSocketProvider::OnlyForTesting::prep_event_loop_for_restart(socket_provider.get());
2✔
3876
    }
2✔
3877

1✔
3878
    socket_provider->start();
2✔
3879
}
2✔
3880

3881
TEST(Sync_CancelReconnectDelay)
3882
{
2✔
3883
    TEST_DIR(server_dir);
2✔
3884
    TEST_CLIENT_DB(db);
2✔
3885
    TEST_CLIENT_DB(db_x);
2✔
3886

1✔
3887
    ClientServerFixture::Config fixture_config;
2✔
3888
    fixture_config.one_connection_per_session = false;
2✔
3889

1✔
3890
    // After connection-level error, and at session-level.
1✔
3891
    {
2✔
3892
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3893
        fixture.start();
2✔
3894

1✔
3895
        BowlOfStonesSemaphore bowl;
2✔
3896
        auto handler = [&](const SessionErrorInfo& info) {
2✔
3897
            if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3898
                bowl.add_stone();
2✔
3899
        };
2✔
3900
        Session session = fixture.make_session(db, "/test");
2✔
3901
        session.set_error_handler(std::move(handler));
2✔
3902
        session.bind();
2✔
3903
        session.wait_for_download_complete_or_client_stopped();
2✔
3904
        fixture.close_server_side_connections();
2✔
3905
        bowl.get_stone();
2✔
3906

1✔
3907
        session.cancel_reconnect_delay();
2✔
3908
        session.wait_for_download_complete_or_client_stopped();
2✔
3909
    }
2✔
3910

1✔
3911
    // After connection-level error, and at client-level while connection
1✔
3912
    // object exists (ConnectionImpl in clinet.cpp).
1✔
3913
    {
2✔
3914
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3915
        fixture.start();
2✔
3916

1✔
3917
        BowlOfStonesSemaphore bowl;
2✔
3918
        auto handler = [&](const SessionErrorInfo& info) {
2✔
3919
            if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3920
                bowl.add_stone();
2✔
3921
        };
2✔
3922
        Session session = fixture.make_session(db, "/test");
2✔
3923
        session.set_error_handler(std::move(handler));
2✔
3924
        session.bind();
2✔
3925
        session.wait_for_download_complete_or_client_stopped();
2✔
3926
        fixture.close_server_side_connections();
2✔
3927
        bowl.get_stone();
2✔
3928

1✔
3929
        fixture.cancel_reconnect_delay();
2✔
3930
        session.wait_for_download_complete_or_client_stopped();
2✔
3931
    }
2✔
3932

1✔
3933
    // After connection-level error, and at client-level while connection object
1✔
3934
    // does not exist (ConnectionImpl in clinet.cpp).
1✔
3935
    {
2✔
3936
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3937
        fixture.start();
2✔
3938

1✔
3939
        {
2✔
3940
            BowlOfStonesSemaphore bowl;
2✔
3941
            auto handler = [&](const SessionErrorInfo& info) {
2✔
3942
                if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3943
                    bowl.add_stone();
2✔
3944
            };
2✔
3945
            Session session = fixture.make_session(db, "/test");
2✔
3946
            session.set_error_handler(std::move(handler));
2✔
3947
            session.bind();
2✔
3948
            session.wait_for_download_complete_or_client_stopped();
2✔
3949
            fixture.close_server_side_connections();
2✔
3950
            bowl.get_stone();
2✔
3951
        }
2✔
3952

1✔
3953
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3954
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3955
        // The connection object no longer exists at this time. After the first
1✔
3956
        // of the two waits above, the invocation of ConnectionImpl::on_idle()
1✔
3957
        // (in client.cpp) has been scheduled. After the second wait, it has
1✔
3958
        // been called, and that destroys the connection object.
1✔
3959

1✔
3960
        fixture.cancel_reconnect_delay();
2✔
3961
        {
2✔
3962
            Session session = fixture.make_bound_session(db, "/test");
2✔
3963
            session.wait_for_download_complete_or_client_stopped();
2✔
3964
        }
2✔
3965
    }
2✔
3966

1✔
3967
    // After session-level error, and at session-level.
1✔
3968
    {
2✔
3969
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3970
        fixture.start();
2✔
3971

1✔
3972
        // Add a session for the purpose of keeping the connection open
1✔
3973
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3974
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3975

1✔
3976
        BowlOfStonesSemaphore bowl;
2✔
3977
        auto handler = [&](const SessionErrorInfo& info) {
4✔
3978
            if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue))
4✔
3979
                bowl.add_stone();
4✔
3980
        };
4✔
3981
        Session session = fixture.make_session(db, "/..");
2✔
3982
        session.set_error_handler(std::move(handler));
2✔
3983
        session.bind();
2✔
3984
        bowl.get_stone();
2✔
3985

1✔
3986
        session.cancel_reconnect_delay();
2✔
3987
        bowl.get_stone();
2✔
3988
    }
2✔
3989

1✔
3990
    // After session-level error, and at client-level.
1✔
3991
    {
2✔
3992
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3993
        fixture.start();
2✔
3994

1✔
3995
        // Add a session for the purpose of keeping the connection open
1✔
3996
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3997
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3998

1✔
3999
        BowlOfStonesSemaphore bowl;
2✔
4000
        auto handler = [&](const SessionErrorInfo& info) {
4✔
4001
            if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue))
4✔
4002
                bowl.add_stone();
4✔
4003
        };
4✔
4004
        Session session = fixture.make_session(db, "/..");
2✔
4005
        session.set_error_handler(std::move(handler));
2✔
4006
        session.bind();
2✔
4007
        bowl.get_stone();
2✔
4008

1✔
4009
        fixture.cancel_reconnect_delay();
2✔
4010
        bowl.get_stone();
2✔
4011
    }
2✔
4012
}
2✔
4013

4014

4015
#ifndef REALM_PLATFORM_WIN32
4016

4017
// This test checks that it is possible to create, upload, download, and merge
4018
// changesets larger than 16 MB.
4019
//
4020
// Fails with 'bad alloc' around 1 GB mem usage on 32-bit Windows + 32-bit Linux
4021
TEST_IF(Sync_MergeLargeBinary, !(REALM_ARCHITECTURE_X86_32))
4022
{
2✔
4023
    // Two binaries are inserted in each transaction such that the total size
1✔
4024
    // of the changeset exceeds 16 MB. A single set_binary operation does not
1✔
4025
    // accept a binary larger than 16 MB.
1✔
4026
    size_t binary_sizes[] = {
2✔
4027
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e6), static_cast<size_t>(11e6),
2✔
4028
        static_cast<size_t>(6e6), static_cast<size_t>(12e6), static_cast<size_t>(5e6), static_cast<size_t>(13e6),
2✔
4029
    };
2✔
4030

1✔
4031
    TEST_CLIENT_DB(db_1);
2✔
4032
    TEST_CLIENT_DB(db_2);
2✔
4033

1✔
4034
    {
2✔
4035
        WriteTransaction wt(db_1);
2✔
4036
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4037
        table->add_column(type_Binary, "column name");
2✔
4038
        std::string str_1(binary_sizes[0], 'a');
2✔
4039
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4040
        std::string str_2(binary_sizes[1], 'b');
2✔
4041
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4042
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
4043
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
4044
        wt.commit();
2✔
4045
    }
2✔
4046

1✔
4047
    {
2✔
4048
        WriteTransaction wt(db_1);
2✔
4049
        TableRef table = wt.get_table("class_table name");
2✔
4050
        std::string str_1(binary_sizes[2], 'c');
2✔
4051
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4052
        std::string str_2(binary_sizes[3], 'd');
2✔
4053
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4054
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4055
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4056
        wt.commit();
2✔
4057
    }
2✔
4058

1✔
4059
    {
2✔
4060
        WriteTransaction wt(db_2);
2✔
4061
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4062
        table->add_column(type_Binary, "column name");
2✔
4063
        std::string str_1(binary_sizes[4], 'e');
2✔
4064
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4065
        std::string str_2(binary_sizes[5], 'f');
2✔
4066
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4067
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4068
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4069
        wt.commit();
2✔
4070
    }
2✔
4071

1✔
4072
    {
2✔
4073
        WriteTransaction wt(db_2);
2✔
4074
        TableRef table = wt.get_table("class_table name");
2✔
4075
        std::string str_1(binary_sizes[6], 'g');
2✔
4076
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4077
        std::string str_2(binary_sizes[7], 'h');
2✔
4078
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4079
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4080
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4081
        wt.commit();
2✔
4082
    }
2✔
4083

1✔
4084
    std::uint_fast64_t downloaded_bytes_1 = 0;
2✔
4085
    std::uint_fast64_t downloadable_bytes_1 = 0;
2✔
4086
    std::uint_fast64_t uploaded_bytes_1 = 0;
2✔
4087
    std::uint_fast64_t uploadable_bytes_1 = 0;
2✔
4088

1✔
4089
    auto progress_handler_1 = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
4090
                                  std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
4091
                                  std::uint_fast64_t, std::uint_fast64_t) {
14✔
4092
        downloaded_bytes_1 = downloaded_bytes;
14✔
4093
        downloadable_bytes_1 = downloadable_bytes;
14✔
4094
        uploaded_bytes_1 = uploaded_bytes;
14✔
4095
        uploadable_bytes_1 = uploadable_bytes;
14✔
4096
    };
14✔
4097

1✔
4098
    std::uint_fast64_t downloaded_bytes_2 = 0;
2✔
4099
    std::uint_fast64_t downloadable_bytes_2 = 0;
2✔
4100
    std::uint_fast64_t uploaded_bytes_2 = 0;
2✔
4101
    std::uint_fast64_t uploadable_bytes_2 = 0;
2✔
4102

1✔
4103
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4104
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t,
2✔
4105
                                  uint_fast64_t) {
10✔
4106
        downloaded_bytes_2 = downloaded_bytes;
10✔
4107
        downloadable_bytes_2 = downloadable_bytes;
10✔
4108
        uploaded_bytes_2 = uploaded_bytes;
10✔
4109
        uploadable_bytes_2 = uploadable_bytes;
10✔
4110
    };
10✔
4111

1✔
4112
    {
2✔
4113
        TEST_DIR(dir);
2✔
4114
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4115
        fixture.start();
2✔
4116

1✔
4117
        {
2✔
4118
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4119
            session_1.set_progress_handler(progress_handler_1);
2✔
4120
            session_1.bind();
2✔
4121
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4122
        }
2✔
4123

1✔
4124
        {
2✔
4125
            Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4126
            session_2.set_progress_handler(progress_handler_2);
2✔
4127
            session_2.bind();
2✔
4128
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4129
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4130
        }
2✔
4131

1✔
4132
        {
2✔
4133
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4134
            session_1.set_progress_handler(progress_handler_1);
2✔
4135
            session_1.bind();
2✔
4136
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4137
        }
2✔
4138
    }
2✔
4139

1✔
4140
    ReadTransaction read_1(db_1);
2✔
4141
    ReadTransaction read_2(db_2);
2✔
4142

1✔
4143
    const Group& group = read_1;
2✔
4144
    CHECK(compare_groups(read_1, read_2));
2✔
4145
    ConstTableRef table = group.get_table("class_table name");
2✔
4146
    CHECK_EQUAL(table->size(), 8);
2✔
4147
    {
2✔
4148
        const Obj obj = *table->begin();
2✔
4149
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
4150
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4151
    }
2✔
4152
    {
2✔
4153
        const Obj obj = *(table->begin() + 7);
2✔
4154
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
4155
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2!
4156
    }
2✔
4157

1✔
4158
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4159
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4160
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4161

1✔
4162
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4163
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4164
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4165

1✔
4166
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4167
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4168
}
2✔
4169

4170

4171
// This test checks that it is possible to create, upload, download, and merge
4172
// changesets larger than 16 MB. This test uses less memory than
4173
// Sync_MergeLargeBinary.
4174
TEST(Sync_MergeLargeBinaryReducedMemory)
4175
{
2✔
4176
    // Two binaries are inserted in a transaction such that the total size
1✔
4177
    // of the changeset exceeds 16MB. A single set_binary operation does not
1✔
4178
    // accept a binary larger than 16MB. Only one changeset is larger than
1✔
4179
    // 16 MB in this test.
1✔
4180
    size_t binary_sizes[] = {
2✔
4181
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e4), static_cast<size_t>(11e4),
2✔
4182
        static_cast<size_t>(6e4), static_cast<size_t>(12e4), static_cast<size_t>(5e4), static_cast<size_t>(13e4),
2✔
4183
    };
2✔
4184

1✔
4185
    TEST_CLIENT_DB(db_1);
2✔
4186
    TEST_CLIENT_DB(db_2);
2✔
4187

1✔
4188
    {
2✔
4189
        WriteTransaction wt(db_1);
2✔
4190
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4191
        table->add_column(type_Binary, "column name");
2✔
4192
        std::string str_1(binary_sizes[0], 'a');
2✔
4193
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4194
        std::string str_2(binary_sizes[1], 'b');
2✔
4195
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4196
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
4197
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
4198
        wt.commit();
2✔
4199
    }
2✔
4200

1✔
4201
    {
2✔
4202
        WriteTransaction wt(db_1);
2✔
4203
        TableRef table = wt.get_table("class_table name");
2✔
4204
        std::string str_1(binary_sizes[2], 'c');
2✔
4205
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4206
        std::string str_2(binary_sizes[3], 'd');
2✔
4207
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4208
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4209
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4210
        wt.commit();
2✔
4211
    }
2✔
4212

1✔
4213
    {
2✔
4214
        WriteTransaction wt(db_2);
2✔
4215
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4216
        table->add_column(type_Binary, "column name");
2✔
4217
        std::string str_1(binary_sizes[4], 'e');
2✔
4218
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4219
        std::string str_2(binary_sizes[5], 'f');
2✔
4220
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4221
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4222
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4223
        wt.commit();
2✔
4224
    }
2✔
4225

1✔
4226
    {
2✔
4227
        WriteTransaction wt(db_2);
2✔
4228
        TableRef table = wt.get_table("class_table name");
2✔
4229
        std::string str_1(binary_sizes[6], 'g');
2✔
4230
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4231
        std::string str_2(binary_sizes[7], 'h');
2✔
4232
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4233
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4234
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4235
        wt.commit();
2✔
4236
    }
2✔
4237

1✔
4238
    uint_fast64_t downloaded_bytes_1 = 0;
2✔
4239
    uint_fast64_t downloadable_bytes_1 = 0;
2✔
4240
    uint_fast64_t uploaded_bytes_1 = 0;
2✔
4241
    uint_fast64_t uploadable_bytes_1 = 0;
2✔
4242

1✔
4243
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4244
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4245
                                  uint_fast64_t /* progress_version */, uint_fast64_t /* snapshot_version */) {
12✔
4246
        downloaded_bytes_1 = downloaded_bytes;
12✔
4247
        downloadable_bytes_1 = downloadable_bytes;
12✔
4248
        uploaded_bytes_1 = uploaded_bytes;
12✔
4249
        uploadable_bytes_1 = uploadable_bytes;
12✔
4250
    };
12✔
4251

1✔
4252
    uint_fast64_t downloaded_bytes_2 = 0;
2✔
4253
    uint_fast64_t downloadable_bytes_2 = 0;
2✔
4254
    uint_fast64_t uploaded_bytes_2 = 0;
2✔
4255
    uint_fast64_t uploadable_bytes_2 = 0;
2✔
4256

1✔
4257
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4258
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4259
                                  uint_fast64_t /* progress_version */, uint_fast64_t /* snapshot_version */) {
10✔
4260
        downloaded_bytes_2 = downloaded_bytes;
10✔
4261
        downloadable_bytes_2 = downloadable_bytes;
10✔
4262
        uploaded_bytes_2 = uploaded_bytes;
10✔
4263
        uploadable_bytes_2 = uploadable_bytes;
10✔
4264
    };
10✔
4265

1✔
4266
    {
2✔
4267
        TEST_DIR(dir);
2✔
4268
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4269
        fixture.start();
2✔
4270

1✔
4271
        {
2✔
4272
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4273
            session_1.set_progress_handler(progress_handler_1);
2✔
4274
            session_1.bind();
2✔
4275
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4276
        }
2✔
4277

1✔
4278
        {
2✔
4279
            Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4280
            session_2.set_progress_handler(progress_handler_2);
2✔
4281
            session_2.bind();
2✔
4282
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4283
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4284
        }
2✔
4285

1✔
4286
        {
2✔
4287
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4288
            session_1.set_progress_handler(progress_handler_1);
2✔
4289
            session_1.bind();
2✔
4290
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4291
        }
2✔
4292
    }
2✔
4293

1✔
4294
    ReadTransaction read_1(db_1);
2✔
4295
    ReadTransaction read_2(db_2);
2✔
4296

1✔
4297
    const Group& group = read_1;
2✔
4298
    CHECK(compare_groups(read_1, read_2));
2✔
4299
    ConstTableRef table = group.get_table("class_table name");
2✔
4300
    CHECK_EQUAL(table->size(), 8);
2✔
4301
    {
2✔
4302
        const Obj obj = *table->begin();
2✔
4303
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4304
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4305
    }
2✔
4306
    {
2✔
4307
        const Obj obj = *(table->begin() + 7);
2✔
4308
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4309
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2!
4310
    }
2✔
4311

1✔
4312
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4313
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4314
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4315

1✔
4316
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4317
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4318
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4319

1✔
4320
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4321
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4322
}
2✔
4323

4324

4325
// This test checks that it is possible to create, upload, download, and merge
4326
// changesets larger than 16MB.
4327
TEST(Sync_MergeLargeChangesets)
4328
{
2✔
4329
    constexpr int number_of_rows = 200;
2✔
4330

1✔
4331
    TEST_CLIENT_DB(db_1);
2✔
4332
    TEST_CLIENT_DB(db_2);
2✔
4333

1✔
4334
    {
2✔
4335
        WriteTransaction wt(db_1);
2✔
4336
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4337
        table->add_column(type_Binary, "column name");
2✔
4338
        table->add_column(type_Int, "integer column");
2✔
4339
        wt.commit();
2✔
4340
    }
2✔
4341

1✔
4342
    {
2✔
4343
        WriteTransaction wt(db_2);
2✔
4344
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4345
        table->add_column(type_Binary, "column name");
2✔
4346
        table->add_column(type_Int, "integer column");
2✔
4347
        wt.commit();
2✔
4348
    }
2✔
4349

1✔
4350
    {
2✔
4351
        WriteTransaction wt(db_1);
2✔
4352
        TableRef table = wt.get_table("class_table name");
2✔
4353
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4354
            table->create_object_with_primary_key(i);
400✔
4355
        }
400✔
4356
        std::string str(100000, 'a');
2✔
4357
        BinaryData bd(str.data(), str.size());
2✔
4358
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4359
            table->get_object(size_t(row)).set("column name", bd);
400✔
4360
            table->get_object(size_t(row)).set("integer column", 2 * row);
400✔
4361
        }
400✔
4362
        wt.commit();
2✔
4363
    }
2✔
4364

1✔
4365
    {
2✔
4366
        WriteTransaction wt(db_2);
2✔
4367
        TableRef table = wt.get_table("class_table name");
2✔
4368
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4369
            table->create_object_with_primary_key(i + number_of_rows);
400✔
4370
        }
400✔
4371
        std::string str(100000, 'b');
2✔
4372
        BinaryData bd(str.data(), str.size());
2✔
4373
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4374
            table->get_object(size_t(row)).set("column name", bd);
400✔
4375
            table->get_object(size_t(row)).set("integer column", 2 * row + 1);
400✔
4376
        }
400✔
4377
        wt.commit();
2✔
4378
    }
2✔
4379

1✔
4380
    {
2✔
4381
        TEST_DIR(dir);
2✔
4382
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4383

1✔
4384
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4385
        session_1.bind();
2✔
4386
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4387
        session_2.bind();
2✔
4388

1✔
4389
        fixture.start();
2✔
4390

1✔
4391
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4392
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4393
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4394
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4395
    }
2✔
4396

1✔
4397
    ReadTransaction read_1(db_1);
2✔
4398
    ReadTransaction read_2(db_2);
2✔
4399
    const Group& group = read_1;
2✔
4400
    CHECK(compare_groups(read_1, read_2));
2✔
4401
    ConstTableRef table = group.get_table("class_table name");
2✔
4402
    CHECK_EQUAL(table->size(), 2 * number_of_rows);
2✔
4403
}
2✔
4404

4405

4406
TEST(Sync_MergeMultipleChangesets)
4407
{
2✔
4408
    constexpr int number_of_changesets = 100;
2✔
4409
    constexpr int number_of_instructions = 10;
2✔
4410

1✔
4411
    TEST_CLIENT_DB(db_1);
2✔
4412
    TEST_CLIENT_DB(db_2);
2✔
4413

1✔
4414
    std::atomic<int> id = 0;
2✔
4415

1✔
4416
    {
2✔
4417
        WriteTransaction wt(db_1);
2✔
4418
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4419
        table->add_column(type_Int, "integer column");
2✔
4420
        wt.commit();
2✔
4421
    }
2✔
4422

1✔
4423
    {
2✔
4424
        WriteTransaction wt(db_2);
2✔
4425
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4426
        table->add_column(type_Int, "integer column");
2✔
4427
        wt.commit();
2✔
4428
    }
2✔
4429

1✔
4430
    {
2✔
4431
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4432
            WriteTransaction wt(db_1);
200✔
4433
            TableRef table = wt.get_table("class_table name");
200✔
4434
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4435
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4436
                obj.set("integer column", 2 * j);
2,000✔
4437
            }
2,000✔
4438
            wt.commit();
200✔
4439
        }
200✔
4440
    }
2✔
4441

1✔
4442
    {
2✔
4443
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4444
            WriteTransaction wt(db_2);
200✔
4445
            TableRef table = wt.get_table("class_table name");
200✔
4446
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4447
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4448
                obj.set("integer column", 2 * j + 1);
2,000✔
4449
            }
2,000✔
4450
            wt.commit();
200✔
4451
        }
200✔
4452
    }
2✔
4453

1✔
4454
    {
2✔
4455
        TEST_DIR(dir);
2✔
4456
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4457

1✔
4458

1✔
4459
        // Start server and upload changes of first client.
1✔
4460
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4461
        session_1.bind();
2✔
4462
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4463
        session_2.bind();
2✔
4464

1✔
4465
        fixture.start_server(0);
2✔
4466
        fixture.start_client(0);
2✔
4467
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4468
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4469
        session_1.detach();
2✔
4470
        // Stop first client.
1✔
4471
        fixture.stop_client(0);
2✔
4472

1✔
4473
        // Start the second client and upload their changes.
1✔
4474
        // Wait to integrate changes from the first client.
1✔
4475
        fixture.start_client(1);
2✔
4476
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4477
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4478
    }
2✔
4479

1✔
4480
    ReadTransaction read_1(db_1);
2✔
4481
    ReadTransaction read_2(db_2);
2✔
4482
    const Group& group1 = read_1;
2✔
4483
    const Group& group2 = read_2;
2✔
4484
    ConstTableRef table1 = group1.get_table("class_table name");
2✔
4485
    ConstTableRef table2 = group2.get_table("class_table name");
2✔
4486
    CHECK_EQUAL(table1->size(), number_of_changesets * number_of_instructions);
2✔
4487
    CHECK_EQUAL(table2->size(), 2 * number_of_changesets * number_of_instructions);
2✔
4488
}
2✔
4489

4490

4491
#endif // REALM_PLATFORM_WIN32
4492

4493

4494
TEST(Sync_PingTimesOut)
4495
{
2✔
4496
    bool did_fail = false;
2✔
4497
    {
2✔
4498
        TEST_DIR(dir);
2✔
4499
        TEST_CLIENT_DB(db);
2✔
4500

1✔
4501
        ClientServerFixture::Config config;
2✔
4502
        config.client_ping_period = 0;  // send ping immediately
2✔
4503
        config.client_pong_timeout = 0; // time out immediately
2✔
4504
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4505

1✔
4506
        auto error_handler = [&](Status status, bool) {
2✔
4507
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4508
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4509
            did_fail = true;
2✔
4510
            fixture.stop();
2✔
4511
        };
2✔
4512
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4513

1✔
4514
        fixture.start();
2✔
4515

1✔
4516
        Session session = fixture.make_bound_session(db);
2✔
4517
        session.wait_for_download_complete_or_client_stopped();
2✔
4518
    }
2✔
4519
    CHECK(did_fail);
2✔
4520
}
2✔
4521

4522

4523
TEST(Sync_ReconnectAfterPingTimeout)
4524
{
2✔
4525
    TEST_DIR(dir);
2✔
4526
    TEST_CLIENT_DB(db);
2✔
4527

1✔
4528
    ClientServerFixture::Config config;
2✔
4529
    config.client_ping_period = 0;  // send ping immediately
2✔
4530
    config.client_pong_timeout = 0; // time out immediately
2✔
4531

1✔
4532
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4533

1✔
4534
    BowlOfStonesSemaphore bowl;
2✔
4535
    auto error_handler = [&](Status status, bool) {
2✔
4536
        if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) {
2✔
4537
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4538
            bowl.add_stone();
2✔
4539
        }
2✔
4540
    };
2✔
4541
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4542
    fixture.start();
2✔
4543

1✔
4544
    Session session = fixture.make_bound_session(db, "/test");
2✔
4545
    bowl.get_stone();
2✔
4546
}
2✔
4547

4548

4549
TEST(Sync_UrgentPingIsSent)
4550
{
2✔
4551
    bool did_fail = false;
2✔
4552
    {
2✔
4553
        TEST_DIR(dir);
2✔
4554
        TEST_CLIENT_DB(db);
2✔
4555

1✔
4556
        ClientServerFixture::Config config;
2✔
4557
        config.client_pong_timeout = 0; // urgent pings time out immediately
2✔
4558

1✔
4559
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4560

1✔
4561
        auto error_handler = [&](Status status, bool) {
2✔
4562
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4563
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4564
            did_fail = true;
2✔
4565
            fixture.stop();
2✔
4566
        };
2✔
4567
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4568

1✔
4569
        fixture.start();
2✔
4570

1✔
4571
        Session session = fixture.make_bound_session(db);
2✔
4572
        session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4573
        session.cancel_reconnect_delay();                       // send an urgent ping
2✔
4574
        session.wait_for_download_complete_or_client_stopped();
2✔
4575
    }
2✔
4576
    CHECK(did_fail);
2✔
4577
}
2✔
4578

4579

4580
TEST(Sync_ServerDiscardDeadConnections)
4581
{
2✔
4582
    TEST_DIR(dir);
2✔
4583
    TEST_CLIENT_DB(db);
2✔
4584

1✔
4585
    ClientServerFixture::Config config;
2✔
4586
    config.server_connection_reaper_interval = 1; // discard dead connections quickly, FIXME: 0 will not work here :(
2✔
4587

1✔
4588
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4589

1✔
4590
    BowlOfStonesSemaphore bowl;
2✔
4591
    auto error_handler = [&](Status status, bool) {
2✔
4592
        CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4593
        bowl.add_stone();
2✔
4594
    };
2✔
4595
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4596
    fixture.start();
2✔
4597

1✔
4598
    Session session = fixture.make_bound_session(db);
2✔
4599
    session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4600
    fixture.set_server_connection_reaper_timeout(0);        // all connections will now be considered dead
2✔
4601
    bowl.get_stone();
2✔
4602
}
2✔
4603

4604

4605
TEST(Sync_Quadratic_Merge)
4606
{
2✔
4607
    size_t num_instructions_1 = 100;
2✔
4608
    size_t num_instructions_2 = 200;
2✔
4609
    REALM_ASSERT(num_instructions_1 >= 3 && num_instructions_2 >= 3);
2✔
4610

1✔
4611
    TEST_DIR(server_dir);
2✔
4612
    TEST_CLIENT_DB(db_1);
2✔
4613
    TEST_CLIENT_DB(db_2);
2✔
4614

1✔
4615
    // The schema and data is created with
1✔
4616
    // n_operations instructions. The instructions are:
1✔
4617
    // create table
1✔
4618
    // add column
1✔
4619
    // create object
1✔
4620
    // n_operations - 3 add_int instructions.
1✔
4621
    auto create_data = [](DBRef db, size_t n_operations) {
4✔
4622
        WriteTransaction wt(db);
4✔
4623
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
4✔
4624
        table->add_column(type_Int, "i");
4✔
4625
        Obj obj = table->create_object_with_primary_key(1);
4✔
4626
        for (size_t i = 0; i < n_operations - 3; ++i)
592✔
4627
            obj.add_int("i", 1);
588✔
4628
        wt.commit();
4✔
4629
    };
4✔
4630

1✔
4631
    create_data(db_1, num_instructions_1);
2✔
4632
    create_data(db_2, num_instructions_2);
2✔
4633

1✔
4634
    int num_clients = 2;
2✔
4635
    int num_servers = 1;
2✔
4636
    MultiClientServerFixture fixture{num_clients, num_servers, server_dir, test_context};
2✔
4637
    fixture.start();
2✔
4638

1✔
4639
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4640
    session_1.bind();
2✔
4641
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4642

1✔
4643
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4644
    session_2.bind();
2✔
4645
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
4646

1✔
4647
    session_1.wait_for_download_complete_or_client_stopped();
2✔
4648
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4649
}
2✔
4650

4651

4652
TEST(Sync_BatchedUploadMessages)
4653
{
2✔
4654
    TEST_DIR(server_dir);
2✔
4655
    TEST_CLIENT_DB(db);
2✔
4656

1✔
4657
    ClientServerFixture fixture(server_dir, test_context);
2✔
4658
    fixture.start();
2✔
4659

1✔
4660
    Session session = fixture.make_session(db, "/test");
2✔
4661

1✔
4662
    {
2✔
4663
        WriteTransaction wt{db};
2✔
4664
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4665
        tr->add_column(type_Int, "integer column");
2✔
4666
        wt.commit();
2✔
4667
    }
2✔
4668

1✔
4669
    // Create a lot of changesets. We will attempt to check that
1✔
4670
    // they are uploaded in a few upload messages.
1✔
4671
    for (int i = 0; i < 400; ++i) {
802✔
4672
        WriteTransaction wt{db};
800✔
4673
        TableRef tr = wt.get_table("class_foo");
800✔
4674
        tr->create_object_with_primary_key(i).set("integer column", i);
800✔
4675
        wt.commit();
800✔
4676
    }
800✔
4677

1✔
4678
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4679
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4680
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
6✔
4681
        CHECK_GREATER(uploadable_bytes, 1000);
6✔
4682

3✔
4683
        // This is the important check. If the changesets were not batched,
3✔
4684
        // there would be callbacks with partial uploaded_bytes.
3✔
4685
        // With batching, all uploadable_bytes are uploaded in the same message.
3✔
4686
        CHECK(uploaded_bytes == 0 || uploaded_bytes == uploadable_bytes);
6✔
4687
        CHECK_EQUAL(0, downloaded_bytes);
6✔
4688
        CHECK_EQUAL(0, downloadable_bytes);
6✔
4689
        static_cast<void>(progress_version);
6✔
4690
        static_cast<void>(snapshot_version);
6✔
4691
    };
6✔
4692

1✔
4693
    session.set_progress_handler(progress_handler);
2✔
4694
    session.bind();
2✔
4695
    session.wait_for_upload_complete_or_client_stopped();
2✔
4696
}
2✔
4697

4698

4699
TEST(Sync_UploadLogCompactionEnabled)
4700
{
2✔
4701
    TEST_DIR(server_dir);
2✔
4702
    TEST_CLIENT_DB(db_1);
2✔
4703
    TEST_CLIENT_DB(db_2);
2✔
4704

1✔
4705
    ClientServerFixture::Config config;
2✔
4706
    config.disable_upload_compaction = false;
2✔
4707
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
4708
    fixture.start();
2✔
4709

1✔
4710
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
4711
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
4712

1✔
4713
    // Create a changeset with lots of overwrites of the
1✔
4714
    // same fields.
1✔
4715
    {
2✔
4716
        WriteTransaction wt{db_1};
2✔
4717
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4718
        tr->add_column(type_Int, "integer column");
2✔
4719
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4720
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4721
        for (int i = 0; i < 10000; ++i) {
20,002✔
4722
            obj0.set("integer column", i);
20,000✔
4723
            obj1.set("integer column", 2 * i);
20,000✔
4724
        }
20,000✔
4725
        wt.commit();
2✔
4726
    }
2✔
4727

1✔
4728
    session_1.bind();
2✔
4729
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4730

1✔
4731
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4732
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4733
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
4✔
4734
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
4✔
4735
        CHECK_EQUAL(0, uploaded_bytes);
4✔
4736
        CHECK_EQUAL(0, uploadable_bytes);
4✔
4737
        static_cast<void>(snapshot_version);
4✔
4738
        if (progress_version > 0)
4✔
4739
            CHECK_NOT_EQUAL(downloadable_bytes, 0);
3✔
4740
    };
4✔
4741

1✔
4742
    session_2.set_progress_handler(progress_handler);
2✔
4743

1✔
4744
    session_2.bind();
2✔
4745

1✔
4746
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4747

1✔
4748
    {
2✔
4749
        ReadTransaction rt_1(db_1);
2✔
4750
        ReadTransaction rt_2(db_2);
2✔
4751
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4752
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4753
        CHECK_EQUAL(2, table->size());
2✔
4754
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4755
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4756
    }
2✔
4757
}
2✔
4758

4759

4760
TEST(Sync_UploadLogCompactionDisabled)
4761
{
2✔
4762
    TEST_DIR(server_dir);
2✔
4763
    TEST_CLIENT_DB(db_1);
2✔
4764
    TEST_CLIENT_DB(db_2);
2✔
4765

1✔
4766
    ClientServerFixture::Config config;
2✔
4767
    config.disable_upload_compaction = true;
2✔
4768
    config.disable_history_compaction = true;
2✔
4769
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4770
    fixture.start();
2✔
4771

1✔
4772
    // Create a changeset with lots of overwrites of the
1✔
4773
    // same fields.
1✔
4774
    {
2✔
4775
        WriteTransaction wt{db_1};
2✔
4776
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4777
        auto col_int = tr->add_column(type_Int, "integer column");
2✔
4778
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4779
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4780
        for (int i = 0; i < 10000; ++i) {
20,002✔
4781
            obj0.set(col_int, i);
20,000✔
4782
            obj1.set(col_int, 2 * i);
20,000✔
4783
        }
20,000✔
4784
        wt.commit();
2✔
4785
    }
2✔
4786

1✔
4787
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
4788
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4789

1✔
4790
    auto progress_handler = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
4791
                                std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
4792
                                std::uint_fast64_t progress_version, std::uint_fast64_t snapshot_version) {
4✔
4793
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
4✔
4794
        CHECK_EQUAL(0, uploaded_bytes);
4✔
4795
        CHECK_EQUAL(0, uploadable_bytes);
4✔
4796
        static_cast<void>(snapshot_version);
4✔
4797
        if (progress_version > 0)
4✔
4798
            CHECK_NOT_EQUAL(0, downloadable_bytes);
3✔
4799
    };
4✔
4800

1✔
4801
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
4802
    session_2.set_progress_handler(progress_handler);
2✔
4803
    session_2.bind();
2✔
4804
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4805

1✔
4806
    {
2✔
4807
        ReadTransaction rt_1(db_1);
2✔
4808
        ReadTransaction rt_2(db_2);
2✔
4809
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4810
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4811
        CHECK_EQUAL(2, table->size());
2✔
4812
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4813
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4814
    }
2✔
4815
}
2✔
4816

4817

4818
TEST(Sync_ReadOnlyClientSideHistoryTrim)
4819
{
2✔
4820
    TEST_DIR(dir);
2✔
4821
    TEST_CLIENT_DB(db_1);
2✔
4822
    TEST_CLIENT_DB(db_2);
2✔
4823

1✔
4824
    ClientServerFixture fixture{dir, test_context};
2✔
4825
    fixture.start();
2✔
4826

1✔
4827
    ColKey col_ndx_blob_data;
2✔
4828
    {
2✔
4829
        WriteTransaction wt{db_1};
2✔
4830
        TableRef blobs = wt.get_group().add_table_with_primary_key("class_Blob", type_Int, "id");
2✔
4831
        col_ndx_blob_data = blobs->add_column(type_Binary, "data");
2✔
4832
        blobs->create_object_with_primary_key(1);
2✔
4833
        wt.commit();
2✔
4834
    }
2✔
4835

1✔
4836
    Session session_1 = fixture.make_bound_session(db_1, "/foo");
2✔
4837
    Session session_2 = fixture.make_bound_session(db_2, "/foo");
2✔
4838

1✔
4839
    std::string blob(0x4000, '\0');
2✔
4840
    for (long i = 0; i < 1024; ++i) {
2,050✔
4841
        {
2,048✔
4842
            WriteTransaction wt{db_1};
2,048✔
4843
            TableRef blobs = wt.get_table("class_Blob");
2,048✔
4844
            blobs->begin()->set(col_ndx_blob_data, BinaryData{blob});
2,048✔
4845
            wt.commit();
2,048✔
4846
        }
2,048✔
4847
        session_1.wait_for_upload_complete_or_client_stopped();
2,048✔
4848
        session_2.wait_for_download_complete_or_client_stopped();
2,048✔
4849
    }
2,048✔
4850

1✔
4851
    // Check that the file size is less than 4 MiB. If it is, then the history
1✔
4852
    // must have been trimmed, as the combined size of all the blobs is at least
1✔
4853
    // 16 MiB.
1✔
4854
    CHECK_LESS(util::File{db_1_path}.get_size(), 0x400000);
2✔
4855
}
2✔
4856

4857
// This test creates two objects in a target table and a link list
4858
// in a source table. The first target object is inserted in the link list,
4859
// and later the link is set to the second target object.
4860
// Both the target objects are deleted afterwards. The tests verifies that
4861
// sync works with log compaction turned on.
4862
TEST(Sync_ContainerInsertAndSetLogCompaction)
4863
{
2✔
4864
    TEST_DIR(dir);
2✔
4865
    TEST_CLIENT_DB(db_1);
2✔
4866
    TEST_CLIENT_DB(db_2);
2✔
4867
    ClientServerFixture fixture(dir, test_context);
2✔
4868
    fixture.start();
2✔
4869

1✔
4870
    {
2✔
4871
        WriteTransaction wt{db_1};
2✔
4872

1✔
4873
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
4874
        ColKey col_ndx = table_target->add_column(type_Int, "value");
2✔
4875
        auto k0 = table_target->create_object_with_primary_key(1).set(col_ndx, 123).get_key();
2✔
4876
        auto k1 = table_target->create_object_with_primary_key(2).set(col_ndx, 456).get_key();
2✔
4877

1✔
4878
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
4879
        col_ndx = table_source->add_column_list(*table_target, "target_link");
2✔
4880
        Obj obj = table_source->create_object_with_primary_key(1);
2✔
4881
        LnkLst ll = obj.get_linklist(col_ndx);
2✔
4882
        ll.insert(0, k0);
2✔
4883
        ll.set(0, k1);
2✔
4884

1✔
4885
        table_target->remove_object(k1);
2✔
4886
        table_target->remove_object(k0);
2✔
4887

1✔
4888
        wt.commit();
2✔
4889
    }
2✔
4890

1✔
4891
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4892
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4893

1✔
4894
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4895
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4896

1✔
4897
    {
2✔
4898
        ReadTransaction rt_1(db_1);
2✔
4899
        ReadTransaction rt_2(db_2);
2✔
4900
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4901
    }
2✔
4902
}
2✔
4903

4904

4905
TEST(Sync_MultipleContainerColumns)
4906
{
2✔
4907
    TEST_DIR(dir);
2✔
4908
    TEST_CLIENT_DB(db_1);
2✔
4909
    TEST_CLIENT_DB(db_2);
2✔
4910
    ClientServerFixture fixture(dir, test_context);
2✔
4911
    fixture.start();
2✔
4912

1✔
4913
    {
2✔
4914
        WriteTransaction wt{db_1};
2✔
4915

1✔
4916
        TableRef table = wt.get_group().add_table_with_primary_key("class_Table", type_Int, "id");
2✔
4917
        table->add_column_list(type_String, "array1");
2✔
4918
        table->add_column_list(type_String, "array2");
2✔
4919

1✔
4920
        Obj row = table->create_object_with_primary_key(1);
2✔
4921
        {
2✔
4922
            Lst<StringData> array1 = row.get_list<StringData>("array1");
2✔
4923
            array1.clear();
2✔
4924
            array1.add("Hello");
2✔
4925
        }
2✔
4926
        {
2✔
4927
            Lst<StringData> array2 = row.get_list<StringData>("array2");
2✔
4928
            array2.clear();
2✔
4929
            array2.add("World");
2✔
4930
        }
2✔
4931

1✔
4932
        wt.commit();
2✔
4933
    }
2✔
4934

1✔
4935
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4936
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4937

1✔
4938
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4939
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4940

1✔
4941
    {
2✔
4942
        ReadTransaction rt_1(db_1);
2✔
4943
        ReadTransaction rt_2(db_2);
2✔
4944
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4945

1✔
4946
        ConstTableRef table = rt_1.get_table("class_Table");
2✔
4947
        const Obj row = *table->begin();
2✔
4948
        auto array1 = row.get_list<StringData>("array1");
2✔
4949
        auto array2 = row.get_list<StringData>("array2");
2✔
4950
        CHECK_EQUAL(array1.size(), 1);
2✔
4951
        CHECK_EQUAL(array2.size(), 1);
2✔
4952
        CHECK_EQUAL(array1.get(0), "Hello");
2✔
4953
        CHECK_EQUAL(array2.get(0), "World");
2✔
4954
    }
2✔
4955
}
2✔
4956

4957

4958
TEST(Sync_ConnectionStateChange)
4959
{
2✔
4960
    TEST_DIR(dir);
2✔
4961
    TEST_CLIENT_DB(db_1);
2✔
4962
    TEST_CLIENT_DB(db_2);
2✔
4963

1✔
4964
    std::vector<ConnectionState> states_1, states_2;
2✔
4965
    {
2✔
4966
        ClientServerFixture fixture(dir, test_context);
2✔
4967
        fixture.start();
2✔
4968

1✔
4969
        BowlOfStonesSemaphore bowl_1, bowl_2;
2✔
4970
        auto listener_1 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4971
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4972
            states_1.push_back(state);
6✔
4973
            if (state == ConnectionState::disconnected)
6✔
4974
                bowl_1.add_stone();
2✔
4975
        };
6✔
4976
        auto listener_2 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4977
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4978
            states_2.push_back(state);
6✔
4979
            if (state == ConnectionState::disconnected)
6✔
4980
                bowl_2.add_stone();
2✔
4981
        };
6✔
4982

1✔
4983
        Session session_1 = fixture.make_session(db_1, "/test");
2✔
4984
        session_1.set_connection_state_change_listener(listener_1);
2✔
4985
        session_1.bind();
2✔
4986
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4987

1✔
4988
        Session session_2 = fixture.make_session(db_2, "/test");
2✔
4989
        session_2.set_connection_state_change_listener(listener_2);
2✔
4990
        session_2.bind();
2✔
4991
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4992

1✔
4993
        fixture.close_server_side_connections();
2✔
4994
        bowl_1.get_stone();
2✔
4995
        bowl_2.get_stone();
2✔
4996
    }
2✔
4997
    std::vector<ConnectionState> reference{ConnectionState::connecting, ConnectionState::connected,
2✔
4998
                                           ConnectionState::disconnected};
2✔
4999
    CHECK(states_1 == reference);
2✔
5000
    CHECK(states_2 == reference);
2✔
5001
}
2✔
5002

5003

5004
TEST(Sync_ClientErrorHandler)
5005
{
2✔
5006
    TEST_DIR(dir);
2✔
5007
    TEST_CLIENT_DB(db);
2✔
5008
    ClientServerFixture fixture(dir, test_context);
2✔
5009
    fixture.start();
2✔
5010

1✔
5011
    BowlOfStonesSemaphore bowl;
2✔
5012
    auto handler = [&](const SessionErrorInfo&) {
2✔
5013
        bowl.add_stone();
2✔
5014
    };
2✔
5015

1✔
5016
    Session session = fixture.make_session(db, "/test");
2✔
5017
    session.set_error_handler(std::move(handler));
2✔
5018
    session.bind();
2✔
5019
    session.wait_for_download_complete_or_client_stopped();
2✔
5020

1✔
5021
    fixture.close_server_side_connections();
2✔
5022
    bowl.get_stone();
2✔
5023
}
2✔
5024

5025

5026
TEST(Sync_VerifyServerHistoryAfterLargeUpload)
5027
{
2✔
5028
    TEST_DIR(server_dir);
2✔
5029
    TEST_CLIENT_DB(db);
2✔
5030

1✔
5031
    ClientServerFixture fixture{server_dir, test_context};
2✔
5032
    fixture.start();
2✔
5033

1✔
5034
    {
2✔
5035
        auto wt = db->start_write();
2✔
5036
        auto table = wt->add_table_with_primary_key("class_table", type_Int, "id");
2✔
5037
        ColKey col = table->add_column(type_Binary, "data");
2✔
5038

1✔
5039
        // Create enough data that our changeset cannot be stored contiguously
1✔
5040
        // by BinaryColumn (> 16MB).
1✔
5041
        std::size_t data_size = 8 * 1024 * 1024;
2✔
5042
        std::string data(data_size, '\0');
2✔
5043
        for (int i = 0; i < 8; ++i) {
18✔
5044
            table->create_object_with_primary_key(i).set(col, BinaryData{data.data(), data.size()});
16✔
5045
        }
16✔
5046

1✔
5047
        wt->commit();
2✔
5048

1✔
5049
        Session session = fixture.make_session(db, "/test");
2✔
5050
        session.bind();
2✔
5051
        session.wait_for_upload_complete_or_client_stopped();
2✔
5052
    }
2✔
5053

1✔
5054
    {
2✔
5055
        std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
5056
        TestServerHistoryContext context;
2✔
5057
        _impl::ServerHistory history{context};
2✔
5058
        DBRef db = DB::create(history, server_path);
2✔
5059
        {
2✔
5060
            ReadTransaction rt{db};
2✔
5061
            rt.get_group().verify();
2✔
5062
        }
2✔
5063
    }
2✔
5064
}
2✔
5065

5066

5067
TEST(Sync_ServerSideModify_Randomize)
5068
{
2✔
5069
    int num_server_side_transacts = 1200;
2✔
5070
    int num_client_side_transacts = 1200;
2✔
5071

1✔
5072
    TEST_DIR(server_dir);
2✔
5073
    TEST_CLIENT_DB(db_2);
2✔
5074

1✔
5075
    ClientServerFixture::Config config;
2✔
5076
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
5077
    fixture.start();
2✔
5078

1✔
5079
    Session session = fixture.make_bound_session(db_2, "/test");
2✔
5080

1✔
5081
    std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
5082
    TestServerHistoryContext context;
2✔
5083
    _impl::ServerHistory history_1{context};
2✔
5084
    DBRef db_1 = DB::create(history_1, server_path);
2✔
5085

1✔
5086
    auto server_side_program = [num_server_side_transacts, &db_1, &fixture, &session] {
2✔
5087
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
5088
        for (int i = 0; i < num_server_side_transacts; ++i) {
2,402✔
5089
            WriteTransaction wt{db_1};
2,400✔
5090
            TableRef table = wt.get_table("class_foo");
2,400✔
5091
            if (!table) {
2,400✔
5092
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
5093
                table->add_column(type_Int, "i");
2✔
5094
            }
2✔
5095
            if (i % 2 == 0)
2,400✔
5096
                table->create_object_with_primary_key(0 - i);
1,200✔
5097
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
5098
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
5099
            wt.commit();
2,400✔
5100
            fixture.inform_server_about_external_change("/test");
2,400✔
5101
            session.wait_for_download_complete_or_client_stopped();
2,400✔
5102
        }
2,400✔
5103
    };
2✔
5104

1✔
5105
    auto client_side_program = [num_client_side_transacts, &db_2, &session] {
2✔
5106
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
5107
        for (int i = 0; i < num_client_side_transacts; ++i) {
2,402✔
5108
            WriteTransaction wt{db_2};
2,400✔
5109
            TableRef table = wt.get_table("class_foo");
2,400✔
5110
            if (!table) {
2,400✔
5111
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
5112
                table->add_column(type_Int, "i");
2✔
5113
            }
2✔
5114
            if (i % 2 == 0)
2,400✔
5115
                table->create_object_with_primary_key(i);
1,200✔
5116
            ;
2,400✔
5117
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
5118
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
5119
            wt.commit();
2,400✔
5120
            if (i % 16 == 0)
2,400✔
5121
                session.wait_for_upload_complete_or_client_stopped();
150✔
5122
        }
2,400✔
5123
    };
2✔
5124

1✔
5125
    ThreadWrapper server_program_thread;
2✔
5126
    server_program_thread.start(std::move(server_side_program));
2✔
5127
    client_side_program();
2✔
5128
    CHECK(!server_program_thread.join());
2✔
5129

1✔
5130
    session.wait_for_upload_complete_or_client_stopped();
2✔
5131
    session.wait_for_download_complete_or_client_stopped();
2✔
5132

1✔
5133
    ReadTransaction rt_1{db_1};
2✔
5134
    ReadTransaction rt_2{db_2};
2✔
5135
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
5136
}
2✔
5137

5138

5139
// This test connects a sync client to the realm cloud service using a SSL
5140
// connection. The purpose of the test is to check that the server's SSL
5141
// certificate is accepted by the client.  The client will connect with an
5142
// invalid token and get an error code back.  The check is that the error is
5143
// not rejected certificate.  The test should be disabled under normal
5144
// circumstances since it requires network access and cloud availability. The
5145
// test might be enabled during testing of SSL functionality.
5146
TEST_IF(Sync_SSL_Certificates, false)
5147
{
×
5148
    TEST_CLIENT_DB(db);
×
5149

5150
    const char* server_address[] = {
×
5151
        "morten-krogh.us1.cloud.realm.io",
×
5152
        "fantastic-cotton-shoes.us1.cloud.realm.io",
×
5153
        "www.realm.io",
×
5154
        "www.yahoo.com",
×
5155
        "www.nytimes.com",
×
5156
        "www.ibm.com",
×
5157
        "www.ssllabs.com",
×
5158
    };
×
5159

5160
    size_t num_servers = sizeof(server_address) / sizeof(server_address[0]);
×
5161

5162
    auto client_logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
×
5163

5164
    for (size_t i = 0; i < num_servers; ++i) {
×
5165
        Client::Config client_config;
×
5166
        client_config.logger = client_logger;
×
5167
        client_config.reconnect_mode = ReconnectMode::testing;
×
5168
        Client client(client_config);
×
5169

5170
        Session::Config session_config;
×
5171
        session_config.server_address = server_address[i];
×
5172
        session_config.server_port = 443;
×
5173
        session_config.realm_identifier = "/anything";
×
5174
        session_config.protocol_envelope = ProtocolEnvelope::realms;
×
5175

5176
        // Invalid token for the cloud.
5177
        session_config.signed_user_token = g_signed_test_user_token;
×
5178

5179
        Session session{client, db, nullptr, nullptr, std::move(session_config)};
×
5180

5181
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>& error_info) {
×
5182
            if (state == ConnectionState::disconnected) {
×
5183
                CHECK(error_info);
×
5184
                client_logger->debug("State change: disconnected, error_code = %1, is_fatal = %2", error_info->status,
×
5185
                                     error_info->is_fatal);
×
5186
                // We expect to get through the SSL handshake but will hit an error due to the wrong token.
5187
                CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed);
×
5188
                client.shutdown();
×
5189
            }
×
5190
        };
×
5191

5192
        session.set_connection_state_change_listener(listener);
×
5193
        session.bind();
×
5194

5195
        session.wait_for_download_complete_or_client_stopped();
×
5196
    }
×
5197
}
×
5198

5199

5200
// Testing the custom authorization header name.  The sync protocol does not
5201
// currently use the HTTP Authorization header, so the test is to watch the
5202
// logs and see that the client use the right header name. Proxies and the sync
5203
// server HTTP api use the Authorization header.
5204
TEST(Sync_AuthorizationHeaderName)
5205
{
2✔
5206
    TEST_DIR(dir);
2✔
5207
    TEST_CLIENT_DB(db);
2✔
5208

1✔
5209
    const char* authorization_header_name = "X-Alternative-Name";
2✔
5210
    ClientServerFixture::Config config;
2✔
5211
    config.authorization_header_name = authorization_header_name;
2✔
5212
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5213
    fixture.start();
2✔
5214

1✔
5215
    Session::Config session_config;
2✔
5216
    session_config.authorization_header_name = authorization_header_name;
2✔
5217

1✔
5218
    std::map<std::string, std::string> custom_http_headers;
2✔
5219
    custom_http_headers["Header-Name-1"] = "Header-Value-1";
2✔
5220
    custom_http_headers["Header-Name-2"] = "Header-Value-2";
2✔
5221
    session_config.custom_http_headers = std::move(custom_http_headers);
2✔
5222
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5223
    session.bind();
2✔
5224

1✔
5225
    session.wait_for_download_complete_or_client_stopped();
2✔
5226
}
2✔
5227

5228

5229
TEST(Sync_BadChangeset)
5230
{
2✔
5231
    TEST_DIR(dir);
2✔
5232
    TEST_CLIENT_DB(db);
2✔
5233

1✔
5234
    bool did_fail = false;
2✔
5235
    {
2✔
5236
        ClientServerFixture::Config config;
2✔
5237
        config.disable_upload_compaction = true;
2✔
5238
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5239
        fixture.start();
2✔
5240

1✔
5241
        {
2✔
5242
            Session session = fixture.make_bound_session(db);
2✔
5243
            session.wait_for_download_complete_or_client_stopped();
2✔
5244
        }
2✔
5245

1✔
5246
        {
2✔
5247
            WriteTransaction wt(db);
2✔
5248
            TableRef table = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5249
            table->add_column(type_Int, "i");
2✔
5250
            table->create_object_with_primary_key(5).set_all(123);
2✔
5251
            const ChangesetEncoder::Buffer& buffer = get_replication(db).get_instruction_encoder().buffer();
2✔
5252
            char bad_instruction = 0x3e;
2✔
5253
            const_cast<ChangesetEncoder::Buffer&>(buffer).append(&bad_instruction, 1);
2✔
5254
            wt.commit();
2✔
5255
        }
2✔
5256

1✔
5257
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>& error_info) {
6✔
5258
            if (state != ConnectionState::disconnected)
6✔
5259
                return;
4✔
5260
            REALM_ASSERT(error_info);
2✔
5261
            CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
2✔
5262
            CHECK(error_info->is_fatal);
2✔
5263
            did_fail = true;
2✔
5264
            fixture.stop();
2✔
5265
        };
2✔
5266

1✔
5267
        Session session = fixture.make_session(db, "/test");
2✔
5268
        session.set_connection_state_change_listener(listener);
2✔
5269
        session.bind();
2✔
5270

1✔
5271
        session.wait_for_upload_complete_or_client_stopped();
2✔
5272
        session.wait_for_download_complete_or_client_stopped();
2✔
5273
    }
2✔
5274
    CHECK(did_fail);
2✔
5275
}
2✔
5276

5277

5278
TEST(Sync_GoodChangeset_AccentCharacterInFieldName)
5279
{
2✔
5280
    TEST_DIR(dir);
2✔
5281
    TEST_CLIENT_DB(db);
2✔
5282

1✔
5283
    bool did_fail = false;
2✔
5284
    {
2✔
5285
        ClientServerFixture::Config config;
2✔
5286
        config.disable_upload_compaction = true;
2✔
5287
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5288
        fixture.start();
2✔
5289

1✔
5290
        {
2✔
5291
            Session session = fixture.make_bound_session(db);
2✔
5292
        }
2✔
5293

1✔
5294
        {
2✔
5295
            WriteTransaction wt(db);
2✔
5296
            TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
5297
            table->add_column(type_Int, "prógram");
2✔
5298
            table->add_column(type_Int, "program");
2✔
5299
            auto obj = table->create_object_with_primary_key(1);
2✔
5300
            obj.add_int("program", 42);
2✔
5301
            wt.commit();
2✔
5302
        }
2✔
5303

1✔
5304
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>) {
4✔
5305
            if (state != ConnectionState::disconnected)
4✔
5306
                return;
4✔
5307
            did_fail = true;
×
5308
            fixture.stop();
×
5309
        };
×
5310

1✔
5311
        Session session = fixture.make_session(db, "/test");
2✔
5312
        session.set_connection_state_change_listener(listener);
2✔
5313
        session.bind();
2✔
5314

1✔
5315
        session.wait_for_upload_complete_or_client_stopped();
2✔
5316
    }
2✔
5317
    CHECK_NOT(did_fail);
2✔
5318
}
2✔
5319

5320

5321
namespace issue2104 {
5322

5323
class ServerHistoryContext : public _impl::ServerHistory::Context {
5324
public:
5325
    ServerHistoryContext() {}
×
5326

5327
    std::mt19937_64& server_history_get_random() noexcept override
5328
    {
×
5329
        return m_random;
×
5330
    }
×
5331

5332
private:
5333
    std::mt19937_64 m_random;
5334
};
5335

5336
} // namespace issue2104
5337

5338
// This test reproduces a slow merge seen in issue 2104.
5339
// The test uses a user supplied Realm and a changeset
5340
// from a client.
5341
// The test uses a user supplied Realm that is very large
5342
// and not kept in the repo. The realm has checksum 3693867489.
5343
//
5344
// This test might be modified to avoid having a large Realm
5345
// (96 MB uncompressed) in the repo.
5346
TEST_IF(Sync_Issue2104, false)
5347
{
×
5348
    TEST_DIR(dir);
×
5349

5350
    // Save a snapshot of the server Realm file.
5351
    std::string realm_path = "issue_2104_server.realm";
×
5352
    std::string realm_path_copy = util::File::resolve("issue_2104.realm", dir);
×
5353
    util::File::copy(realm_path, realm_path_copy);
×
5354

5355
    std::string changeset_hex = "3F 00 07 41 42 43 44 61 74 61 3F 01 02 69 64 3F 02 09 41 6C 69 67 6E 6D 65 6E 74 3F "
×
5356
                                "03 12 42 65 68 61 76 69 6F 72 4F 63 63 75 72 72 65 6E 63 65 3F 04 0D 42 65 68 61 76 "
×
5357
                                "69 6F 72 50 68 61 73 65 3F 05 09 43 6F 6C 6C 65 63 74 6F 72 3F 06 09 43 72 69 74 65 "
×
5358
                                "72 69 6F 6E 3F 07 07 46 65 61 74 75 72 65 3F 08 12 49 6E 73 74 72 75 63 74 69 6F 6E "
×
5359
                                "61 6C 54 72 69 61 6C 3F 09 14 4D 65 61 73 75 72 65 6D 65 6E 74 50 72 6F 63 65 64 75 "
×
5360
                                "72 65 3F 0A 07 4D 65 73 73 61 67 65 3F 0B 04 4E 6F 74 65 3F 0C 16 4F 6E 62 6F 61 72 "
×
5361
                                "64 69 6E 67 54 6F 75 72 50 72 6F 67 72 65 73 73 3F 0D 05 50 68 61 73 65 3F 0E 07 50 "
×
5362
                                "72 6F 67 72 61 6D 3F 0F 0C 50 72 6F 67 72 61 6D 47 72 6F 75 70 3F 10 0A 50 72 6F 67 "
×
5363
                                "72 61 6D 52 75 6E 3F 11 0F 50 72 6F 67 72 61 6D 54 65 6D 70 6C 61 74 65 3F 12 0B 52 "
×
5364
                                "65 61 6C 6D 53 74 72 69 6E 67 3F 13 0B 53 65 73 73 69 6F 6E 4E 6F 74 65 3F 14 07 53 "
×
5365
                                "74 75 64 65 6E 74 3F 15 06 54 61 72 67 65 74 3F 16 0E 54 61 72 67 65 74 54 65 6D 70 "
×
5366
                                "6C 61 74 65 3F 17 04 54 61 73 6B 3F 18 05 54 6F 6B 65 6E 3F 19 04 55 73 65 72 3F 1A "
×
5367
                                "07 5F 5F 43 6C 61 73 73 3F 1B 04 6E 61 6D 65 3F 1C 0C 5F 5F 50 65 72 6D 69 73 73 69 "
×
5368
                                "6F 6E 3F 1D 07 5F 5F 52 65 61 6C 6D 3F 1E 06 5F 5F 52 6F 6C 65 3F 1F 06 5F 5F 55 73 "
×
5369
                                "65 72 3F 20 09 63 72 65 61 74 65 64 41 74 3F 21 0A 6D 6F 64 69 66 69 65 64 41 74 3F "
×
5370
                                "22 09 63 72 65 61 74 65 64 42 79 3F 23 0A 6D 6F 64 69 66 69 65 64 42 79 3F 24 07 70 "
×
5371
                                "72 6F 67 72 61 6D 3F 25 04 64 61 74 65 3F 26 0A 61 6E 74 65 63 65 64 65 6E 74 3F 27 "
×
5372
                                "08 62 65 68 61 76 69 6F 72 3F 28 0B 63 6F 6E 73 65 71 75 65 6E 63 65 3F 29 07 73 65 "
×
5373
                                "74 74 69 6E 67 3F 2A 04 6E 6F 74 65 3F 2B 08 63 61 74 65 67 6F 72 79 3F 2C 05 6C 65 "
×
5374
                                "76 65 6C 3F 2D 0A 6F 63 63 75 72 72 65 64 41 74 3F 2E 05 70 68 61 73 65 3F 2F 08 64 "
×
5375
                                "75 72 61 74 69 6F 6E 3F 30 07 6D 61 72 6B 52 61 77 3F 31 09 73 68 6F 72 74 4E 61 6D "
×
5376
                                "65 3F 32 0A 64 65 66 69 6E 69 74 69 6F 6E 3F 33 06 74 61 72 67 65 74 3F 34 08 74 65 "
×
5377
                                "6D 70 6C 61 74 65 3F 35 0D 6C 61 62 65 6C 4F 76 65 72 72 69 64 65 3F 36 08 62 61 73 "
×
5378
                                "65 6C 69 6E 65 3F 37 13 63 6F 6C 6C 65 63 74 69 6F 6E 46 72 65 71 75 65 6E 63 79 3F "
×
5379
                                "38 0E 61 64 64 69 74 69 6F 6E 61 6C 49 6E 66 6F 3F 39 0D 64 61 79 73 54 6F 49 6E 63 "
×
5380
                                "6C 75 64 65 3F 3A 0D 64 61 79 73 54 6F 45 78 63 6C 75 64 65 3F 3B 07 74 79 70 65 52 "
×
5381
                                "61 77 3F 3C 09 66 72 65 71 75 65 6E 63 79 3F 3D 08 69 6E 74 65 72 76 61 6C 3F 3E 0E "
×
5382
                                "70 6F 69 6E 74 73 41 6E 61 6C 79 7A 65 64 3F 3F 0D 6D 69 6E 50 65 72 63 65 6E 74 61 "
×
5383
                                "67 65 3F C0 00 04 63 6F 64 65 3F C1 00 06 74 65 61 6D 49 64 3F C2 00 03 75 72 6C 3F "
×
5384
                                "C3 00 07 73 65 63 74 69 6F 6E 3F C4 00 11 63 72 69 74 65 72 69 6F 6E 44 65 66 61 75 "
×
5385
                                "6C 74 73 3F C5 00 04 74 61 73 6B 3F C6 00 09 72 65 73 75 6C 74 52 61 77 3F C7 00 09 "
×
5386
                                "70 72 6F 6D 70 74 52 61 77 3F C8 00 04 74 65 78 74 3F C9 00 0A 70 72 6F 67 72 61 6D "
×
5387
                                "52 75 6E 3F CA 00 09 72 65 63 69 70 69 65 6E 74 3F CB 00 04 62 6F 64 79 3F CC 00 06 "
×
5388
                                "61 63 74 69 76 65 3F CD 00 0D 62 65 68 61 76 69 6F 72 50 68 61 73 65 3F CE 00 03 64 "
×
5389
                                "61 79 3F CF 00 06 74 6F 75 72 49 64 3F D0 00 08 63 6F 6D 70 6C 65 74 65 3F D1 00 05 "
×
5390
                                "73 74 61 72 74 3F D2 00 03 65 6E 64 3F D3 00 05 74 69 74 6C 65 3F D4 00 12 70 72 6F "
×
5391
                                "67 72 61 6D 44 65 73 63 72 69 70 74 69 6F 6E 3F D5 00 09 63 72 69 74 65 72 69 6F 6E "
×
5392
                                "3F D6 00 0E 63 72 69 74 65 72 69 6F 6E 52 75 6C 65 73 3F D7 00 03 73 74 6F 3F D8 00 "
×
5393
                                "03 6C 74 6F 3F D9 00 18 72 65 69 6E 66 6F 72 63 65 6D 65 6E 74 53 63 68 65 64 75 6C "
×
5394
                                "65 52 61 77 3F DA 00 0D 72 65 69 6E 66 6F 72 63 65 6D 65 6E 74 3F DB 00 11 72 65 69 "
×
5395
                                "6E 66 6F 72 63 65 6D 65 6E 74 54 79 70 65 3F DC 00 16 64 69 73 63 72 69 6D 69 6E 61 "
×
5396
                                "74 69 76 65 53 74 69 6D 75 6C 75 73 3F DD 00 07 74 61 72 67 65 74 73 3F DE 00 05 74 "
×
5397
                                "61 73 6B 73 3F DF 00 0A 74 61 73 6B 53 74 61 74 65 73 3F E0 00 0C 74 6F 74 61 6C 49 "
×
5398
                                "54 43 6F 75 6E 74 3F E1 00 0A 73 61 6D 70 6C 65 54 69 6D 65 3F E2 00 10 64 65 66 61 "
×
5399
                                "75 6C 74 52 65 73 75 6C 74 52 61 77 3F E3 00 0F 76 61 72 69 61 62 6C 65 49 54 43 6F "
×
5400
                                "75 6E 74 3F E4 00 09 65 72 72 6F 72 6C 65 73 73 3F E5 00 0C 6D 69 6E 41 74 74 65 6D "
×
5401
                                "70 74 65 64 3F E6 00 10 64 65 66 61 75 6C 74 4D 65 74 68 6F 64 52 61 77 3F E7 00 0A "
×
5402
                                "73 65 74 74 69 6E 67 52 61 77 3F E8 00 07 73 74 75 64 65 6E 74 3F E9 00 0F 6D 61 73 "
×
5403
                                "74 65 72 65 64 54 61 72 67 65 74 73 3F EA 00 0D 66 75 74 75 72 65 54 61 72 67 65 74 "
×
5404
                                "73 3F EB 00 05 67 72 6F 75 70 3F EC 00 06 6C 6F 63 6B 65 64 3F ED 00 0E 6C 61 73 74 "
×
5405
                                "44 65 63 69 73 69 6F 6E 41 74 3F EE 00 08 61 72 63 68 69 76 65 64 3F EF 00 0E 64 61 "
×
5406
                                "74 65 73 54 6F 49 6E 63 6C 75 64 65 3F F0 00 0E 64 61 74 65 73 54 6F 45 78 63 6C 75 "
×
5407
                                "64 65 3F F1 00 09 64 72 61 77 65 72 52 61 77 3F F2 00 0B 63 6F 6D 70 6C 65 74 65 64 "
×
5408
                                "41 74 3F F3 00 03 49 54 73 3F F4 00 0C 64 69 73 70 6C 61 79 4F 72 64 65 72 3F F5 00 "
×
5409
                                "0F 63 6F 72 72 65 63 74 4F 76 65 72 72 69 64 65 3F F6 00 11 61 74 74 65 6D 70 74 65 "
×
5410
                                "64 4F 76 65 72 72 69 64 65 3F F7 00 09 6D 65 74 68 6F 64 52 61 77 3F F8 00 08 73 74 "
×
5411
                                "61 74 65 52 61 77 3F F9 00 0C 70 6F 69 6E 74 54 79 70 65 52 61 77 3F FA 00 09 61 6C "
×
5412
                                "69 67 6E 6D 65 6E 74 3F FB 00 08 65 78 61 6D 70 6C 65 73 3F FC 00 0E 67 65 6E 65 72 "
×
5413
                                "61 6C 69 7A 61 74 69 6F 6E 3F FD 00 09 6D 61 74 65 72 69 61 6C 73 3F FE 00 09 6F 62 "
×
5414
                                "6A 65 63 74 69 76 65 3F FF 00 0F 72 65 63 6F 6D 6D 65 6E 64 61 74 69 6F 6E 73 3F 80 "
×
5415
                                "01 08 73 74 69 6D 75 6C 75 73 3F 81 01 0B 74 61 72 67 65 74 4E 6F 74 65 73 3F 82 01 "
×
5416
                                "11 74 65 61 63 68 69 6E 67 50 72 6F 63 65 64 75 72 65 3F 83 01 0A 76 62 6D 61 70 70 "
×
5417
                                "54 61 67 73 3F 84 01 08 61 66 6C 73 54 61 67 73 3F 85 01 09 6E 79 73 6C 73 54 61 67 "
×
5418
                                "73 3F 86 01 06 64 6F 6D 61 69 6E 3F 87 01 04 67 6F 61 6C 3F 88 01 07 73 75 62 6A 65 "
×
5419
                                "63 74 3F 89 01 0B 6A 6F 62 43 61 74 65 67 6F 72 79 3F 8A 01 13 70 72 6F 6D 70 74 69 "
×
5420
                                "6E 67 50 72 6F 63 65 64 75 72 65 73 3F 8B 01 10 70 72 65 73 63 68 6F 6F 6C 4D 61 73 "
×
5421
                                "74 65 72 79 3F 8C 01 0C 61 62 6C 6C 73 4D 61 73 74 65 72 79 3F 8D 01 0D 64 61 74 61 "
×
5422
                                "52 65 63 6F 72 64 69 6E 67 3F 8E 01 0F 65 72 72 6F 72 43 6F 72 72 65 63 74 69 6F 6E "
×
5423
                                "3F 8F 01 0B 73 74 72 69 6E 67 56 61 6C 75 65 3F 90 01 06 63 6C 69 65 6E 74 3F 91 01 "
×
5424
                                "09 74 68 65 72 61 70 69 73 74 3F 92 01 0B 72 65 69 6E 66 6F 72 63 65 72 73 3F 93 01 "
×
5425
                                "05 6E 6F 74 65 73 3F 94 01 0F 74 61 72 67 65 74 42 65 68 61 76 69 6F 72 73 3F 95 01 "
×
5426
                                "08 67 6F 61 6C 73 4D 65 74 3F 96 01 0D 74 79 70 65 4F 66 53 65 72 76 69 63 65 3F 97 "
×
5427
                                "01 0D 70 65 6F 70 6C 65 50 72 65 73 65 6E 74 3F 98 01 08 6C 61 74 69 74 75 64 65 3F "
×
5428
                                "99 01 09 6C 6F 6E 67 69 74 75 64 65 3F 9A 01 06 61 6C 65 72 74 73 3F 9B 01 03 65 69 "
×
5429
                                "6E 3F 9C 01 03 64 6F 62 3F 9D 01 0F 70 72 69 6D 61 72 79 47 75 61 72 64 69 61 6E 3F "
×
5430
                                "9E 01 11 73 65 63 6F 6E 64 61 72 79 47 75 61 72 64 69 61 6E 3F 9F 01 08 69 6D 61 67 "
×
5431
                                "65 55 72 6C 3F A0 01 0B 64 65 61 63 74 69 76 61 74 65 64 3F A1 01 11 74 61 72 67 65 "
×
5432
                                "74 44 65 73 63 72 69 70 74 69 6F 6E 3F A2 01 08 6D 61 73 74 65 72 65 64 3F A3 01 0F "
×
5433
                                "74 61 73 6B 44 65 73 63 72 69 70 74 69 6F 6E 3F A4 01 09 65 78 70 69 72 65 73 41 74 "
×
5434
                                "3F A5 01 0C 63 6F 6C 6C 65 63 74 6F 72 49 64 73 3F A6 01 08 73 74 75 64 65 6E 74 73 "
×
5435
                                "3F A7 01 12 6F 6E 62 6F 61 72 64 69 6E 67 50 72 6F 67 72 65 73 73 3F A8 01 05 65 6D "
×
5436
                                "61 69 6C 3F A9 01 05 70 68 6F 6E 65 3F AA 01 07 72 6F 6C 65 52 61 77 3F AB 01 08 73 "
×
5437
                                "65 74 74 69 6E 67 73 3F AC 01 0B 70 65 72 6D 69 73 73 69 6F 6E 73 3F AD 01 04 72 6F "
×
5438
                                "6C 65 3F AE 01 07 63 61 6E 52 65 61 64 3F AF 01 09 63 61 6E 55 70 64 61 74 65 3F B0 "
×
5439
                                "01 09 63 61 6E 44 65 6C 65 74 65 3F B1 01 11 63 61 6E 53 65 74 50 65 72 6D 69 73 73 "
×
5440
                                "69 6F 6E 73 3F B2 01 08 63 61 6E 51 75 65 72 79 3F B3 01 09 63 61 6E 43 72 65 61 74 "
×
5441
                                "65 3F B4 01 0F 63 61 6E 4D 6F 64 69 66 79 53 63 68 65 6D 61 3F B5 01 07 6D 65 6D 62 "
×
5442
                                "65 72 73 02 00 01 01 02 00 02 02 01 01 02 00 02 03 01 01 02 00 02 04 01 01 02 00 02 "
×
5443
                                "05 01 01 02 01 02 06 01 01 02 01 02 07 01 01 02 00 02 08 01 01 02 00 02 09 01 01 02 "
×
5444
                                "00 02 0A 01 01 02 00 02 0B 01 01 02 00 02 0C 01 01 02 00 02 0D 01 01 02 00 02 0E 01 "
×
5445
                                "01 02 00 02 0F 01 01 02 00 02 10 01 01 02 00 02 11 01 01 02 00 02 12 00 02 13 01 01 "
×
5446
                                "02 00 02 14 01 01 02 00 02 15 01 01 02 00 02 16 01 01 02 00 02 17 01 01 02 00 02 18 "
×
5447
                                "01 01 02 00 02 19 01 01 02 00 02 1A 01 1B 02 00 02 1C 00 02 1D 01 01 00 00 02 1E 01 "
×
5448
                                "1B 02 00 02 1F 01 01 02 00 00 00 0B 20 08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 "
×
5449
                                "0C 00 19 0B 24 0C 00 0E 0B 25 08 00 00 0B 26 02 00 01 0B 27 02 00 01 0B 28 02 00 01 "
×
5450
                                "0B 29 02 00 01 0B 2A 02 00 01 00 02 0B 20 08 00 00 0B 21 08 00 00 0B 2B 02 00 01 0B "
×
5451
                                "2C 02 00 01 00 03 0B 20 08 00 00 0B 21 08 00 00 0B 2D 08 00 00 0B 22 0C 00 19 0B 23 "
×
5452
                                "0C 00 19 0B 2E 0C 00 04 0B 2F 0A 00 01 0B 30 02 00 00 00 04 0B 20 08 00 00 0B 21 08 "
×
5453
                                "00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B 1B 02 00 01 0B 31 02 00 01 0B 32 02 00 01 0B "
×
5454
                                "33 02 00 01 0B 24 0C 00 0E 0B 34 0C 00 11 0B 35 02 00 01 0B 36 02 00 01 0B 37 02 00 "
×
5455
                                "01 0B 38 02 00 01 0B 39 08 02 00 0B 3A 08 02 00 0B 3B 02 00 00 00 05 0B 2F 0C 00 04 "
×
5456
                                "0B 3C 0C 00 04 0B 3D 0C 00 10 00 06 0B 3E 00 00 00 0B 3F 0A 00 00 00 07 0B C0 00 02 "
×
5457
                                "00 00 0B C1 00 02 00 01 0B C2 00 02 00 01 0B C3 00 02 00 01 0B C4 00 0D 00 06 00 08 "
×
5458
                                "0B 20 08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B C5 00 0C 00 17 0B 33 "
×
5459
                                "0C 00 15 0B C6 00 02 00 00 0B C7 00 02 00 00 00 09 0B C8 00 02 00 01 00 0A 0B 20 08 "
×
5460
                                "00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B C9 00 0C 00 10 0B 24 0C 00 0E "
×
5461
                                "0B CA 00 0C 00 19 0B CB 00 02 00 00 0B CC 00 01 00 00 0B 3B 02 00 00 00 0B 0B 20 08 "
×
5462
                                "00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B CD 00 0C 00 04 0B CE 00 08 00 "
×
5463
                                "00 0B CB 00 02 00 00 0B CC 00 01 00 00 00 0C 0B CF 00 02 00 00 0B D0 00 01 00 00 00 "
×
5464
                                "0D 0B 20 08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B 24 0C 00 0E 0B D1 "
×
5465
                                "00 08 00 00 0B D2 00 08 00 01 0B D3 00 02 00 01 0B D4 00 02 00 01 0B 32 02 00 01 0B "
×
5466
                                "D5 00 02 00 01 0B D6 00 0D 00 06 0B D7 00 02 00 01 0B D8 00 02 00 01 0B 36 02 00 01 "
×
5467
                                "0B 37 02 00 01 0B 35 02 00 01 0B 38 02 00 01 0B C7 00 02 00 00 0B D9 00 02 00 00 0B "
×
5468
                                "DA 00 00 00 01 0B DB 00 02 00 01 0B DC 00 02 00 01 0B DD 00 0D 00 15 0B DE 00 0D 00 "
×
5469
                                "17 0B DF 00 0D 00 12 0B E0 00 00 00 01 0B E1 00 0A 00 01 0B E2 00 02 00 00 0B E3 00 "
×
5470
                                "01 00 00 0B E4 00 01 00 00 0B E5 00 00 00 00 0B E6 00 02 00 00 0B E7 00 02 00 00 00 "
×
5471
                                "0E 0B 20 08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B E8 00 0C 00 14 0B "
×
5472
                                "E9 00 0D 00 15 0B EA 00 0D 00 15 0B EB 00 0C 00 0F 0B EC 00 01 00 00 0B ED 00 08 00 "
×
5473
                                "01 0B EE 00 01 00 00 0B 34 0C 00 11 0B EF 00 08 02 00 0B F0 00 08 02 00 0B F1 00 02 "
×
5474
                                "00 00 00 0F 0B 20 08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 00 10 0B 20 "
×
5475
                                "08 00 00 0B 21 08 00 00 0B F2 00 08 00 01 0B 22 0C 00 19 0B 23 0C 00 19 0B F3 00 0D "
×
5476
                                "00 08 0B CC 00 01 00 00 0B F4 00 00 00 01 0B F5 00 00 00 01 0B F6 00 00 00 01 0B F7 "
×
5477
                                "00 02 00 00 0B F8 00 02 00 00 0B F9 00 02 00 00 0B 2E 0C 00 0D 0B 2A 02 00 01 0B EE "
×
5478
                                "00 01 00 00 00 11 0B 20 08 00 00 0B 21 08 00 00 0B FA 00 0C 00 02 0B 36 02 00 01 0B "
×
5479
                                "FB 00 02 00 01 0B EA 00 0D 00 16 0B FC 00 02 00 01 0B FD 00 02 00 01 0B 1B 02 00 01 "
×
5480
                                "0B FE 00 02 00 01 0B FF 00 02 00 01 0B 80 01 02 00 01 0B 81 01 02 00 01 0B 82 01 02 "
×
5481
                                "00 01 0B 32 02 00 01 0B 83 01 02 00 01 0B 84 01 02 00 01 0B 85 01 02 00 01 0B 86 01 "
×
5482
                                "02 00 01 0B 87 01 02 00 01 0B 88 01 02 00 01 0B 89 01 02 00 01 0B D8 00 02 00 01 0B "
×
5483
                                "8A 01 02 00 01 0B 8B 01 02 00 01 0B 8C 01 02 00 01 0B 8D 01 02 00 01 0B 8E 01 02 00 "
×
5484
                                "01 0B D5 00 0D 00 06 00 12 0B 8F 01 02 00 00 00 13 0B 20 08 00 00 0B 21 08 00 00 0B "
×
5485
                                "22 0C 00 19 0B 23 0C 00 19 0B 90 01 0C 00 14 0B 91 01 02 00 01 0B 92 01 02 00 01 0B "
×
5486
                                "93 01 02 00 01 0B 94 01 02 00 01 0B 95 01 02 00 01 0B 96 01 02 00 01 0B 97 01 02 00 "
×
5487
                                "01 0B D1 00 08 00 01 0B D2 00 08 00 01 0B 98 01 0A 00 01 0B 99 01 0A 00 01 00 14 0B "
×
5488
                                "20 08 00 00 0B 21 08 00 00 0B 1B 02 00 01 0B 9A 01 02 00 01 0B 9B 01 02 00 01 0B 9C "
×
5489
                                "01 08 00 01 0B 9D 01 0C 00 19 0B 9E 01 0C 00 19 0B 9F 01 02 00 01 0B A0 01 01 00 00 "
×
5490
                                "00 15 0B 20 08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B A1 01 02 00 01 "
×
5491
                                "0B A2 01 08 00 01 00 16 0B 20 08 00 00 0B 21 08 00 00 0B A1 01 02 00 01 00 17 0B 20 "
×
5492
                                "08 00 00 0B 21 08 00 00 0B 22 0C 00 19 0B 23 0C 00 19 0B A3 01 02 00 01 0B F8 00 02 "
×
5493
                                "00 00 00 18 0B A4 01 08 00 00 0B CB 00 02 00 01 00 19 0B 20 08 00 00 0B 21 08 00 00 "
×
5494
                                "0B A5 01 02 02 00 0B A6 01 0D 00 14 0B A7 01 0D 00 0C 0B 1B 02 00 01 0B A8 01 02 00 "
×
5495
                                "01 0B A9 01 02 00 01 0B 9F 01 02 00 01 0B AA 01 02 00 00 0B AB 01 02 02 00 00 1A 0B "
×
5496
                                "AC 01 0D 00 1C 00 1C 0B AD 01 0C 00 1E 0B AE 01 01 00 00 0B AF 01 01 00 00 0B B0 01 "
×
5497
                                "01 00 00 0B B1 01 01 00 00 0B B2 01 01 00 00 0B B3 01 01 00 00 0B B4 01 01 00 00 00 "
×
5498
                                "1D 0B AC 01 0D 00 1C 00 1E 0B B5 01 0D 00 1F 00 1F 0B AD 01 0C 00 1E";
×
5499

5500
    std::vector<char> changeset_vec;
×
5501
    {
×
5502
        std::istringstream in{changeset_hex};
×
5503
        int n;
×
5504
        in >> std::hex >> n;
×
5505
        while (in) {
×
5506
            REALM_ASSERT(n >= 0 && n <= 255);
×
5507
            changeset_vec.push_back(n);
×
5508
            in >> std::hex >> n;
×
5509
        }
×
5510
    }
×
5511

5512
    BinaryData changeset_bin{changeset_vec.data(), changeset_vec.size()};
×
5513

5514
    file_ident_type client_file_ident = 51;
×
5515
    timestamp_type origin_timestamp = 103573722140;
×
5516
    file_ident_type origin_file_ident = 0;
×
5517
    version_type client_version = 2;
×
5518
    version_type last_integrated_server_version = 0;
×
5519
    UploadCursor upload_cursor{client_version, last_integrated_server_version};
×
5520

5521
    _impl::ServerHistory::IntegratableChangeset integratable_changeset{
×
5522
        client_file_ident, origin_timestamp, origin_file_ident, upload_cursor, changeset_bin};
×
5523

5524
    _impl::ServerHistory::IntegratableChangesets integratable_changesets;
×
5525
    integratable_changesets[client_file_ident].changesets.push_back(integratable_changeset);
×
5526

5527
    issue2104::ServerHistoryContext history_context;
×
5528
    _impl::ServerHistory history{history_context};
×
5529
    DBRef db = DB::create(history, realm_path_copy);
×
5530

5531
    VersionInfo version_info;
×
5532
    bool backup_whole_realm;
×
5533
    _impl::ServerHistory::IntegrationResult result;
×
5534
    history.integrate_client_changesets(integratable_changesets, version_info, backup_whole_realm, result,
×
5535
                                        *test_context.logger);
×
5536
}
×
5537

5538

5539
TEST(Sync_RunServerWithoutPublicKey)
5540
{
2✔
5541
    TEST_CLIENT_DB(db);
2✔
5542
    TEST_DIR(server_dir);
2✔
5543
    ClientServerFixture::Config config;
2✔
5544
    config.server_public_key_path = {};
2✔
5545
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5546
    fixture.start();
2✔
5547

1✔
5548
    // Server must accept an unsigned token when a public key is not passed to
1✔
5549
    // it
1✔
5550
    {
2✔
5551
        Session session = fixture.make_bound_session(db, "/test", g_unsigned_test_user_token);
2✔
5552
        session.wait_for_download_complete_or_client_stopped();
2✔
5553
    }
2✔
5554

1✔
5555
    // Server must also accept a signed token when a public key is not passed to
1✔
5556
    // it
1✔
5557
    {
2✔
5558
        Session session = fixture.make_bound_session(db, "/test");
2✔
5559
        session.wait_for_download_complete_or_client_stopped();
2✔
5560
    }
2✔
5561
}
2✔
5562

5563

5564
TEST(Sync_ServerSideEncryption)
5565
{
2✔
5566
    TEST_CLIENT_DB(db);
2✔
5567
    {
2✔
5568
        WriteTransaction wt(db);
2✔
5569
        wt.get_group().add_table_with_primary_key("class_Test", type_Int, "id");
2✔
5570
        wt.commit();
2✔
5571
    }
2✔
5572

1✔
5573
    TEST_DIR(server_dir);
2✔
5574
    bool always_encrypt = true;
2✔
5575
    std::string server_path;
2✔
5576
    {
2✔
5577
        ClientServerFixture::Config config;
2✔
5578
        config.server_encryption_key = crypt_key_2(always_encrypt);
2✔
5579
        ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5580
        fixture.start();
2✔
5581

1✔
5582
        Session session = fixture.make_bound_session(db, "/test");
2✔
5583
        session.wait_for_upload_complete_or_client_stopped();
2✔
5584

1✔
5585
        server_path = fixture.map_virtual_to_real_path("/test");
2✔
5586
    }
2✔
5587

1✔
5588
    const char* encryption_key = crypt_key(always_encrypt);
2✔
5589
    Group group{server_path, encryption_key};
2✔
5590
    CHECK(group.has_table("class_Test"));
2✔
5591
}
2✔
5592

5593
TEST(Sync_LogCompaction_EraseObject_LinkList)
5594
{
2✔
5595
    TEST_DIR(dir);
2✔
5596
    TEST_CLIENT_DB(db_1);
2✔
5597
    TEST_CLIENT_DB(db_2);
2✔
5598
    ClientServerFixture::Config config;
2✔
5599

1✔
5600
    // Log comapction is true by default, but we emphasize it.
1✔
5601
    config.disable_upload_compaction = false;
2✔
5602
    config.disable_download_compaction = false;
2✔
5603

1✔
5604
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5605
    fixture.start();
2✔
5606

1✔
5607
    {
2✔
5608
        WriteTransaction wt{db_1};
2✔
5609

1✔
5610
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
5611
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
5612
        auto col_key = table_source->add_column_list(*table_target, "target_link");
2✔
5613

1✔
5614
        auto k0 = table_target->create_object_with_primary_key(1).get_key();
2✔
5615
        auto k1 = table_target->create_object_with_primary_key(2).get_key();
2✔
5616

1✔
5617
        auto ll = table_source->create_object_with_primary_key(0).get_linklist_ptr(col_key);
2✔
5618
        ll->add(k0);
2✔
5619
        ll->add(k1);
2✔
5620
        CHECK_EQUAL(ll->size(), 2);
2✔
5621
        wt.commit();
2✔
5622
    }
2✔
5623

1✔
5624
    {
2✔
5625
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5626
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5627

1✔
5628
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5629
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5630
    }
2✔
5631

1✔
5632
    {
2✔
5633
        WriteTransaction wt{db_1};
2✔
5634

1✔
5635
        TableRef table_source = wt.get_table("class_source");
2✔
5636
        TableRef table_target = wt.get_table("class_target");
2✔
5637

1✔
5638
        CHECK_EQUAL(table_source->size(), 1);
2✔
5639
        CHECK_EQUAL(table_target->size(), 2);
2✔
5640

1✔
5641
        table_target->get_object(1).remove();
2✔
5642
        table_target->get_object(0).remove();
2✔
5643

1✔
5644
        table_source->get_object(0).remove();
2✔
5645
        wt.commit();
2✔
5646
    }
2✔
5647

1✔
5648
    {
2✔
5649
        WriteTransaction wt{db_2};
2✔
5650

1✔
5651
        TableRef table_source = wt.get_table("class_source");
2✔
5652
        TableRef table_target = wt.get_table("class_target");
2✔
5653
        auto col_key = table_source->get_column_key("target_link");
2✔
5654

1✔
5655
        CHECK_EQUAL(table_source->size(), 1);
2✔
5656
        CHECK_EQUAL(table_target->size(), 2);
2✔
5657

1✔
5658
        auto k0 = table_target->begin()->get_key();
2✔
5659

1✔
5660
        auto ll = table_source->get_object(0).get_linklist_ptr(col_key);
2✔
5661
        ll->add(k0);
2✔
5662
        wt.commit();
2✔
5663
    }
2✔
5664

1✔
5665
    {
2✔
5666
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5667
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5668
    }
2✔
5669

1✔
5670
    {
2✔
5671
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5672
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
5673
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5674
    }
2✔
5675

1✔
5676
    {
2✔
5677
        ReadTransaction rt{db_2};
2✔
5678

1✔
5679
        ConstTableRef table_source = rt.get_group().get_table("class_source");
2✔
5680
        ConstTableRef table_target = rt.get_group().get_table("class_target");
2✔
5681

1✔
5682
        CHECK_EQUAL(table_source->size(), 0);
2✔
5683
        CHECK_EQUAL(table_target->size(), 0);
2✔
5684
    }
2✔
5685
}
2✔
5686

5687

5688
// This test could trigger the assertion that the row_for_object_id cache is
5689
// valid before the cache was properly invalidated in the case of a short
5690
// circuited sync replicator.
5691
TEST(Sync_CreateObjects_EraseObjects)
5692
{
2✔
5693
    TEST_DIR(dir);
2✔
5694
    TEST_CLIENT_DB(db_1);
2✔
5695
    TEST_CLIENT_DB(db_2);
2✔
5696
    ClientServerFixture fixture(dir, test_context);
2✔
5697
    fixture.start();
2✔
5698

1✔
5699
    Session session_1 = fixture.make_bound_session(db_1);
2✔
5700
    Session session_2 = fixture.make_bound_session(db_2);
2✔
5701

1✔
5702
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
5703
        TableRef table = wt.get_group().add_table_with_primary_key("class_persons", type_Int, "id");
2✔
5704
        table->create_object_with_primary_key(1);
2✔
5705
        table->create_object_with_primary_key(2);
2✔
5706
    });
2✔
5707
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5708
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5709

1✔
5710
    write_transaction(db_1, [&](WriteTransaction& wt) {
2✔
5711
        TableRef table = wt.get_table("class_persons");
2✔
5712
        CHECK_EQUAL(table->size(), 2);
2✔
5713
        table->get_object(0).remove();
2✔
5714
        table->get_object(0).remove();
2✔
5715
    });
2✔
5716
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5717
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5718
}
2✔
5719

5720

5721
TEST(Sync_CreateDeleteCreateTableWithPrimaryKey)
5722
{
2✔
5723
    TEST_DIR(dir);
2✔
5724
    TEST_CLIENT_DB(db);
2✔
5725
    ClientServerFixture fixture(dir, test_context);
2✔
5726
    fixture.start();
2✔
5727

1✔
5728
    Session session = fixture.make_bound_session(db);
2✔
5729

1✔
5730
    write_transaction(db, [](WriteTransaction& wt) {
2✔
5731
        TableRef table = wt.get_group().add_table_with_primary_key("class_t", type_Int, "pk");
2✔
5732
        wt.get_group().remove_table(table->get_key());
2✔
5733
        table = wt.get_group().add_table_with_primary_key("class_t", type_String, "pk");
2✔
5734
    });
2✔
5735
    session.wait_for_upload_complete_or_client_stopped();
2✔
5736
    session.wait_for_download_complete_or_client_stopped();
2✔
5737
}
2✔
5738

5739
template <typename T>
5740
T sequence_next()
5741
{
5742
    REALM_UNREACHABLE();
5743
}
5744

5745
template <>
5746
ObjectId sequence_next()
5747
{
8✔
5748
    return ObjectId::gen();
8✔
5749
}
8✔
5750

5751
template <>
5752
UUID sequence_next()
5753
{
8✔
5754
    union {
8✔
5755
        struct {
8✔
5756
            uint64_t upper;
8✔
5757
            uint64_t lower;
8✔
5758
        } ints;
8✔
5759
        UUID::UUIDBytes bytes;
8✔
5760
    } u;
8✔
5761
    static uint64_t counter = test_util::random_int(0, 1000);
8✔
5762
    u.ints.upper = ++counter;
8✔
5763
    u.ints.lower = ++counter;
8✔
5764
    return UUID{u.bytes};
8✔
5765
}
8✔
5766

5767
template <>
5768
Int sequence_next()
5769
{
8✔
5770
    static Int count = test_util::random_int(-1000, 1000);
8✔
5771
    return ++count;
8✔
5772
}
8✔
5773

5774
template <>
5775
String sequence_next()
5776
{
4✔
5777
    static std::string str;
4✔
5778
    static Int sequence = test_util::random_int(-1000, 1000);
4✔
5779
    str = util::format("string sequence %1", ++sequence);
4✔
5780
    return String(str);
4✔
5781
}
4✔
5782

5783
NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util::Optional<Int>,
5784
                         util::Optional<ObjectId>, util::Optional<UUID>)
5785
{
14✔
5786
    using underlying_type = typename util::RemoveOptional<TEST_TYPE>::type;
14✔
5787
    constexpr bool is_optional = !std::is_same_v<underlying_type, TEST_TYPE>;
14✔
5788
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
14✔
5789

7✔
5790
    TEST_CLIENT_DB(db_1);
14✔
5791
    TEST_CLIENT_DB(db_2);
14✔
5792

7✔
5793
    TEST_DIR(dir);
14✔
5794
    fixtures::ClientServerFixture fixture{dir, test_context};
14✔
5795
    fixture.start();
14✔
5796

7✔
5797
    Session session_1 = fixture.make_session(db_1, "/test");
14✔
5798
    Session session_2 = fixture.make_session(db_2, "/test");
14✔
5799
    session_1.bind();
14✔
5800
    session_2.bind();
14✔
5801

7✔
5802
    TEST_TYPE obj_1_id;
14✔
5803
    TEST_TYPE obj_2_id;
14✔
5804

7✔
5805
    TEST_TYPE default_or_null{};
14✔
5806
    if constexpr (std::is_same_v<TEST_TYPE, String>) {
14✔
5807
        default_or_null = "";
2✔
5808
    }
2✔
5809
    if constexpr (is_optional) {
14✔
5810
        CHECK(!default_or_null);
6✔
5811
    }
6✔
5812

7✔
5813
    {
14✔
5814
        WriteTransaction tr{db_1};
14✔
5815
        auto table_1 = tr.get_group().add_table_with_primary_key("class_Table1", type, "id", is_optional);
14✔
5816
        auto table_2 = tr.get_group().add_table_with_primary_key("class_Table2", type, "id", is_optional);
14✔
5817
        table_1->add_column_list(type, "oids", is_optional);
14✔
5818

7✔
5819
        auto obj_1 = table_1->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5820
        auto obj_2 = table_2->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5821
        if constexpr (is_optional) {
14✔
5822
            table_2->create_object_with_primary_key(default_or_null);
6✔
5823
        }
6✔
5824

7✔
5825
        auto list = obj_1.template get_list<TEST_TYPE>("oids");
14✔
5826
        obj_1_id = obj_1.template get<TEST_TYPE>("id");
14✔
5827
        obj_2_id = obj_2.template get<TEST_TYPE>("id");
14✔
5828
        list.insert(0, obj_2_id);
14✔
5829
        list.insert(1, default_or_null);
14✔
5830
        list.add(default_or_null);
14✔
5831

7✔
5832
        tr.commit();
14✔
5833
    }
14✔
5834

7✔
5835
    session_1.wait_for_upload_complete_or_client_stopped();
14✔
5836
    session_2.wait_for_download_complete_or_client_stopped();
14✔
5837

7✔
5838
    {
14✔
5839
        ReadTransaction tr{db_2};
14✔
5840
        auto table_1 = tr.get_table("class_Table1");
14✔
5841
        auto table_2 = tr.get_table("class_Table2");
14✔
5842
        auto obj_1 = *table_1->begin();
14✔
5843
        auto obj_2 = table_2->find_first(table_2->get_column_key("id"), obj_2_id);
14✔
5844
        CHECK(obj_2);
14✔
5845
        auto list = obj_1.get_list<TEST_TYPE>("oids");
14✔
5846
        CHECK_EQUAL(obj_1.template get<TEST_TYPE>("id"), obj_1_id);
14✔
5847
        CHECK_EQUAL(list.size(), 3);
14✔
5848
        CHECK_NOT(list.is_null(0));
14✔
5849
        CHECK_EQUAL(list.get(0), obj_2_id);
14✔
5850
        CHECK_EQUAL(list.get(1), default_or_null);
14✔
5851
        CHECK_EQUAL(list.get(2), default_or_null);
14✔
5852
        if constexpr (is_optional) {
14✔
5853
            auto obj_3 = table_2->find_first_null(table_2->get_column_key("id"));
6✔
5854
            CHECK(obj_3);
6✔
5855
            CHECK(list.is_null(1));
6✔
5856
            CHECK(list.is_null(2));
6✔
5857
        }
6✔
5858
    }
14✔
5859
}
14✔
5860

5861
TEST(Sync_Mixed)
5862
{
2✔
5863
    // Test replication and synchronization of Mixed values and lists.
1✔
5864
    DBOptions options;
2✔
5865
    options.logger = test_context.logger;
2✔
5866
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
5867
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
5868
    auto db_1 = DB::create(make_client_replication(), db_1_path, options);
2✔
5869
    auto db_2 = DB::create(make_client_replication(), db_2_path, options);
2✔
5870

1✔
5871
    TEST_DIR(dir);
2✔
5872
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5873
    fixture.start();
2✔
5874

1✔
5875
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5876
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5877
    session_1.bind();
2✔
5878
    session_2.bind();
2✔
5879

1✔
5880
    {
2✔
5881
        WriteTransaction tr{db_1};
2✔
5882
        auto& g = tr.get_group();
2✔
5883
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5884
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5885
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5886
        foos->add_column(type_Mixed, "value", true);
2✔
5887
        foos->add_column_list(type_Mixed, "values");
2✔
5888

1✔
5889
        auto foo = foos->create_object_with_primary_key(123);
2✔
5890
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5891
        auto fop = fops->create_object_with_primary_key(456);
2✔
5892

1✔
5893
        foo.set("value", Mixed(6.2f));
2✔
5894
        auto values = foo.get_list<Mixed>("values");
2✔
5895
        values.insert(0, StringData("A"));
2✔
5896
        values.insert(1, ObjLink{bars->get_key(), bar.get_key()});
2✔
5897
        values.insert(2, ObjLink{fops->get_key(), fop.get_key()});
2✔
5898
        values.insert(3, 123.f);
2✔
5899

1✔
5900
        tr.commit();
2✔
5901
    }
2✔
5902

1✔
5903
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5904
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5905

1✔
5906
    {
2✔
5907
        ReadTransaction tr{db_2};
2✔
5908

1✔
5909
        auto foos = tr.get_table("class_Foo");
2✔
5910
        auto bars = tr.get_table("class_Bar");
2✔
5911
        auto fops = tr.get_table("class_Fop");
2✔
5912

1✔
5913
        CHECK_EQUAL(foos->size(), 1);
2✔
5914
        CHECK_EQUAL(bars->size(), 1);
2✔
5915
        CHECK_EQUAL(fops->size(), 1);
2✔
5916

1✔
5917
        auto foo = *foos->begin();
2✔
5918
        auto value = foo.get<Mixed>("value");
2✔
5919
        CHECK_EQUAL(value, Mixed{6.2f});
2✔
5920
        auto values = foo.get_list<Mixed>("values");
2✔
5921
        CHECK_EQUAL(values.size(), 4);
2✔
5922

1✔
5923
        auto v0 = values.get(0);
2✔
5924
        auto v1 = values.get(1);
2✔
5925
        auto v2 = values.get(2);
2✔
5926
        auto v3 = values.get(3);
2✔
5927

1✔
5928
        auto l1 = v1.get_link();
2✔
5929
        auto l2 = v2.get_link();
2✔
5930

1✔
5931
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5932
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5933

1✔
5934
        CHECK_EQUAL(v0, Mixed{"A"});
2✔
5935
        CHECK_EQUAL(l1_table, bars);
2✔
5936
        CHECK_EQUAL(l2_table, fops);
2✔
5937
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5938
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5939
        CHECK_EQUAL(v3, Mixed{123.f});
2✔
5940
    }
2✔
5941
}
2✔
5942

5943
/*
5944
TEST(Sync_TypedLinks)
5945
{
5946
    // Test replication and synchronization of Mixed values and lists.
5947

5948
    TEST_CLIENT_DB(db_1);
5949
    TEST_CLIENT_DB(db_2);
5950

5951
    TEST_DIR(dir);
5952
    fixtures::ClientServerFixture fixture{dir, test_context};
5953
    fixture.start();
5954

5955
    Session session_1 = fixture.make_session(db_1, "/test");
5956
    Session session_2 = fixture.make_session(db_2, "/test");
5957
    session_1.bind();
5958
    session_2.bind();
5959

5960
    write_transaction(db_1, [](WriteTransaction& tr) {
5961
        auto& g = tr.get_group();
5962
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
5963
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
5964
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
5965
        foos->add_column(type_TypedLink, "link");
5966

5967
        auto foo1 = foos->create_object_with_primary_key(123);
5968
        auto foo2 = foos->create_object_with_primary_key(456);
5969
        auto bar = bars->create_object_with_primary_key("Hello");
5970
        auto fop = fops->create_object_with_primary_key(456);
5971

5972
        foo1.set("link", ObjLink(bars->get_key(), bar.get_key()));
5973
        foo2.set("link", ObjLink(fops->get_key(), fop.get_key()));
5974
    });
5975

5976
    session_1.wait_for_upload_complete_or_client_stopped();
5977
    session_2.wait_for_download_complete_or_client_stopped();
5978

5979
    {
5980
        ReadTransaction tr{db_2};
5981

5982
        auto foos = tr.get_table("class_Foo");
5983
        auto bars = tr.get_table("class_Bar");
5984
        auto fops = tr.get_table("class_Fop");
5985

5986
        CHECK_EQUAL(foos->size(), 2);
5987
        CHECK_EQUAL(bars->size(), 1);
5988
        CHECK_EQUAL(fops->size(), 1);
5989

5990
        auto it = foos->begin();
5991
        auto l1 = it->get<ObjLink>("link");
5992
        ++it;
5993
        auto l2 = it->get<ObjLink>("link");
5994

5995
        auto l1_table = tr.get_table(l1.get_table_key());
5996
        auto l2_table = tr.get_table(l2.get_table_key());
5997

5998
        CHECK_EQUAL(l1_table, bars);
5999
        CHECK_EQUAL(l2_table, fops);
6000
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
6001
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
6002
    }
6003
}
6004
*/
6005

6006
TEST(Sync_Dictionary)
6007
{
2✔
6008
    // Test replication and synchronization of Mixed values and lists.
1✔
6009

1✔
6010
    TEST_CLIENT_DB(db_1);
2✔
6011
    TEST_CLIENT_DB(db_2);
2✔
6012

1✔
6013
    TEST_DIR(dir);
2✔
6014
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6015
    fixture.start();
2✔
6016

1✔
6017
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6018
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6019
    session_1.bind();
2✔
6020
    session_2.bind();
2✔
6021

1✔
6022
    Timestamp now{std::chrono::system_clock::now()};
2✔
6023

1✔
6024
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6025
        auto& g = tr.get_group();
2✔
6026
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6027
        auto col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6028
        auto col_dict_str = foos->add_column_dictionary(type_String, "str_dict", true);
2✔
6029

1✔
6030
        auto foo = foos->create_object_with_primary_key(123);
2✔
6031

1✔
6032
        auto dict = foo.get_dictionary(col_dict);
2✔
6033
        dict.insert("hello", "world");
2✔
6034
        dict.insert("cnt", 7);
2✔
6035
        dict.insert("when", now);
2✔
6036

1✔
6037
        auto dict_str = foo.get_dictionary(col_dict_str);
2✔
6038
        dict_str.insert("some", "text");
2✔
6039
        dict_str.insert("nothing", null());
2✔
6040
    });
2✔
6041

1✔
6042
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6043
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6044

1✔
6045
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6046
        auto foos = tr.get_table("class_Foo");
2✔
6047
        CHECK_EQUAL(foos->size(), 1);
2✔
6048

1✔
6049
        auto it = foos->begin();
2✔
6050
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6051
        CHECK(dict.get_value_data_type() == type_Mixed);
2✔
6052
        CHECK_EQUAL(dict.size(), 3);
2✔
6053

1✔
6054
        auto col_dict_str = foos->get_column_key("str_dict");
2✔
6055
        auto dict_str = it->get_dictionary(col_dict_str);
2✔
6056
        CHECK(col_dict_str.is_nullable());
2✔
6057
        CHECK(dict_str.get_value_data_type() == type_String);
2✔
6058
        CHECK_EQUAL(dict_str.size(), 2);
2✔
6059

1✔
6060
        Mixed val = dict["hello"];
2✔
6061
        CHECK_EQUAL(val.get_string(), "world");
2✔
6062
        val = dict.get("cnt");
2✔
6063
        CHECK_EQUAL(val.get_int(), 7);
2✔
6064
        val = dict.get("when");
2✔
6065
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6066

1✔
6067
        dict.erase("cnt");
2✔
6068
        dict.insert("hello", "goodbye");
2✔
6069
    });
2✔
6070

1✔
6071
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6072
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6073

1✔
6074
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6075
        auto foos = tr.get_table("class_Foo");
2✔
6076
        CHECK_EQUAL(foos->size(), 1);
2✔
6077

1✔
6078
        auto it = foos->begin();
2✔
6079
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6080
        CHECK_EQUAL(dict.size(), 2);
2✔
6081

1✔
6082
        Mixed val = dict["hello"];
2✔
6083
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
6084
        val = dict.get("when");
2✔
6085
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6086

1✔
6087
        dict.clear();
2✔
6088
    });
2✔
6089

1✔
6090
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6091
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6092

1✔
6093
    {
2✔
6094
        ReadTransaction read_1{db_1};
2✔
6095
        ReadTransaction read_2{db_2};
2✔
6096
        // tr.get_group().to_json(std::cout);
1✔
6097

1✔
6098
        auto foos = read_2.get_table("class_Foo");
2✔
6099

1✔
6100
        CHECK_EQUAL(foos->size(), 1);
2✔
6101

1✔
6102
        auto it = foos->begin();
2✔
6103
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6104
        CHECK_EQUAL(dict.size(), 0);
2✔
6105

1✔
6106
        CHECK(compare_groups(read_1, read_2));
2✔
6107
    }
2✔
6108
}
2✔
6109

6110
TEST_IF(Sync_CollectionInMixed, sync::SYNC_SUPPORTS_NESTED_COLLECTIONS)
6111
{
×
6112
    TEST_CLIENT_DB(db_1);
×
6113
    TEST_CLIENT_DB(db_2);
×
6114

6115
    TEST_DIR(dir);
×
6116
    fixtures::ClientServerFixture fixture{dir, test_context};
×
6117
    fixture.start();
×
6118

6119
    Session session_1 = fixture.make_session(db_1, "/test");
×
6120
    Session session_2 = fixture.make_session(db_2, "/test");
×
6121
    session_1.bind();
×
6122
    session_2.bind();
×
6123

6124
    Timestamp now{std::chrono::system_clock::now()};
×
6125

6126
    write_transaction(db_1, [&](WriteTransaction& tr) {
×
6127
        auto& g = tr.get_group();
×
6128
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
×
6129
        auto col_any = table->add_column(type_Mixed, "any");
×
6130

6131
        auto foo = table->create_object_with_primary_key(123);
×
6132

6133
        // Create dictionary in Mixed property
6134
        foo.set_collection(col_any, CollectionType::Dictionary);
×
6135
        auto dict = foo.get_dictionary_ptr(col_any);
×
6136
        dict->insert("hello", "world");
×
6137
        dict->insert("cnt", 7);
×
6138
        dict->insert("when", now);
×
6139
        // Insert a List in a Dictionary
6140
        dict->insert_collection("list", CollectionType::List);
×
6141
        auto l = dict->get_list("list");
×
6142
        l->add(5);
×
6143
        l->insert_collection(1, CollectionType::List);
×
6144
        l->get_list(1)->add(7);
×
6145

6146
        auto bar = table->create_object_with_primary_key(456);
×
6147

6148
        // Create list in Mixed property
6149
        bar.set_collection(col_any, CollectionType::List);
×
6150
        auto list = bar.get_list_ptr<Mixed>(col_any);
×
6151
        list->add("John");
×
6152
        list->insert(0, 5);
×
6153
    });
×
6154

6155
    session_1.wait_for_upload_complete_or_client_stopped();
×
6156
    session_2.wait_for_download_complete_or_client_stopped();
×
6157

6158
    write_transaction(db_2, [&](WriteTransaction& tr) {
×
6159
        auto table = tr.get_table("class_Table");
×
6160
        auto col_any = table->get_column_key("any");
×
6161
        CHECK_EQUAL(table->size(), 2);
×
6162

6163
        auto obj = table->get_object_with_primary_key(123);
×
6164
        auto dict = obj.get_dictionary_ptr(col_any);
×
6165
        CHECK(dict->get_value_data_type() == type_Mixed);
×
6166
        CHECK_EQUAL(dict->size(), 4);
×
6167

6168
        // Check that values are replicated
6169
        Mixed val = dict->get("hello");
×
6170
        CHECK_EQUAL(val.get_string(), "world");
×
6171
        val = dict->get("cnt");
×
6172
        CHECK_EQUAL(val.get_int(), 7);
×
6173
        val = dict->get("when");
×
6174
        CHECK_EQUAL(val.get<Timestamp>(), now);
×
6175
        CHECK_EQUAL(dict->get_list("list")->get(0).get_int(), 5);
×
6176

6177
        // Erase dictionary element
6178
        dict->erase("cnt");
×
6179
        // Replace dictionary element
6180
        dict->insert("hello", "goodbye");
×
6181

6182
        obj = table->get_object_with_primary_key(456);
×
6183
        auto list = obj.get_list_ptr<Mixed>(col_any);
×
6184
        // Check that values are replicated
6185
        CHECK_EQUAL(list->get(0).get_int(), 5);
×
6186
        CHECK_EQUAL(list->get(1).get_string(), "John");
×
6187
        // Replace list element
6188
        list->set(1, "Paul");
×
6189
        // Erase list element
6190
        list->remove(0);
×
6191
    });
×
6192

6193
    session_2.wait_for_upload_complete_or_client_stopped();
×
6194
    session_1.wait_for_download_complete_or_client_stopped();
×
6195

6196
    write_transaction(db_1, [&](WriteTransaction& tr) {
×
6197
        auto table = tr.get_table("class_Table");
×
6198
        auto col_any = table->get_column_key("any");
×
6199
        CHECK_EQUAL(table->size(), 2);
×
6200

6201
        auto obj = table->get_object_with_primary_key(123);
×
6202
        auto dict = obj.get_dictionary(col_any);
×
6203
        CHECK_EQUAL(dict.size(), 3);
×
6204

6205
        Mixed val = dict["hello"];
×
6206
        CHECK_EQUAL(val.get_string(), "goodbye");
×
6207
        val = dict.get("when");
×
6208
        CHECK_EQUAL(val.get<Timestamp>(), now);
×
6209

6210
        // Dictionary clear
6211
        dict.clear();
×
6212

6213
        obj = table->get_object_with_primary_key(456);
×
6214
        auto list = obj.get_list_ptr<Mixed>(col_any);
×
6215
        CHECK_EQUAL(list->size(), 1);
×
6216
        CHECK_EQUAL(list->get(0).get_string(), "Paul");
×
6217
        // List clear
6218
        list->clear();
×
6219
    });
×
6220

6221
    session_1.wait_for_upload_complete_or_client_stopped();
×
6222
    session_2.wait_for_download_complete_or_client_stopped();
×
6223

6224
    write_transaction(db_2, [&](WriteTransaction& tr) {
×
6225
        auto table = tr.get_table("class_Table");
×
6226
        auto col_any = table->get_column_key("any");
×
6227

6228
        CHECK_EQUAL(table->size(), 2);
×
6229

6230
        auto obj = table->get_object_with_primary_key(123);
×
6231
        auto dict = obj.get_dictionary(col_any);
×
6232
        CHECK_EQUAL(dict.size(), 0);
×
6233

6234
        // Replace dictionary with list on property
6235
        obj.set_collection(col_any, CollectionType::List);
×
6236

6237
        obj = table->get_object_with_primary_key(456);
×
6238
        auto list = obj.get_list<Mixed>(col_any);
×
6239
        CHECK_EQUAL(list.size(), 0);
×
6240
        // Replace list with Dictionary on property
6241
        obj.set_collection(col_any, CollectionType::Dictionary);
×
6242

6243
    });
×
6244

6245
    session_2.wait_for_upload_complete_or_client_stopped();
×
6246
    session_1.wait_for_download_complete_or_client_stopped();
×
6247

6248
    {
×
6249
        ReadTransaction read_1{db_1};
×
6250
        ReadTransaction read_2{db_2};
×
6251

6252
        auto table = read_2.get_table("class_Table");
×
6253
        auto col_any = table->get_column_key("any");
×
6254

6255
        CHECK_EQUAL(table->size(), 2);
×
6256

6257
        auto obj = table->get_object_with_primary_key(123);
×
6258
        auto list = obj.get_list<Mixed>(col_any);
×
6259
        CHECK_EQUAL(list.size(), 0);
×
6260

6261
        obj = table->get_object_with_primary_key(456);
×
6262
        auto dict = obj.get_dictionary(col_any);
×
6263
        CHECK_EQUAL(dict.size(), 0);
×
6264

6265
        CHECK(compare_groups(read_1, read_2));
×
6266
    }
×
6267
}
×
6268

6269
TEST_IF(Sync_CollectionInCollection, SYNC_SUPPORTS_NESTED_COLLECTIONS)
6270
{
×
6271
    TEST_CLIENT_DB(db_1);
×
6272
    TEST_CLIENT_DB(db_2);
×
6273

6274
    TEST_DIR(dir);
×
6275
    fixtures::ClientServerFixture fixture{dir, test_context};
×
6276
    fixture.start();
×
6277

6278
    Session session_1 = fixture.make_session(db_1, "/test");
×
6279
    Session session_2 = fixture.make_session(db_2, "/test");
×
6280
    session_1.bind();
×
6281
    session_2.bind();
×
6282

6283
    Timestamp now{std::chrono::system_clock::now()};
×
6284

6285
    write_transaction(db_1, [&](WriteTransaction& tr) {
×
6286
        auto& g = tr.get_group();
×
6287
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
×
6288
        auto col_any = table->add_column(type_Mixed, "any");
×
6289

6290
        auto foo = table->create_object_with_primary_key(123);
×
6291

6292
        // Create dictionary in Mixed property
6293
        foo.set_collection(col_any, CollectionType::Dictionary);
×
6294
        auto dict = foo.get_dictionary_ptr(col_any);
×
6295
        dict->insert("hello", "world");
×
6296
        dict->insert("cnt", 7);
×
6297
        dict->insert("when", now);
×
6298
        // Insert a List in a Dictionary
6299
        dict->insert_collection("collection", CollectionType::List);
×
6300
        auto l = dict->get_list("collection");
×
6301
        l->add(5);
×
6302

6303
        auto bar = table->create_object_with_primary_key(456);
×
6304

6305
        // Create list in Mixed property
6306
        bar.set_collection(col_any, CollectionType::List);
×
6307
        auto list = bar.get_list_ptr<Mixed>(col_any);
×
6308
        list->add("John");
×
6309
        list->insert(0, 5);
×
6310
        // Insert dictionary in List
6311
        list->insert_collection(2, CollectionType::Dictionary);
×
6312
        auto d = list->get_dictionary(2);
×
6313
        d->insert("One", 1);
×
6314
        d->insert("Two", 2);
×
6315
    });
×
6316

6317
    session_1.wait_for_upload_complete_or_client_stopped();
×
6318
    session_2.wait_for_download_complete_or_client_stopped();
×
6319

6320
    write_transaction(db_2, [&](WriteTransaction& tr) {
×
6321
        auto table = tr.get_table("class_Table");
×
6322
        auto col_any = table->get_column_key("any");
×
6323
        CHECK_EQUAL(table->size(), 2);
×
6324

6325
        auto obj = table->get_object_with_primary_key(123);
×
6326
        auto dict = obj.get_dictionary_ptr(col_any);
×
6327
        CHECK(dict->get_value_data_type() == type_Mixed);
×
6328
        CHECK_EQUAL(dict->size(), 4);
×
6329

6330
        // Replace List with Dictionary
6331
        dict->insert_collection("collection", CollectionType::Dictionary);
×
6332
        auto d = dict->get_dictionary("collection");
×
6333
        d->insert("Three", 3);
×
6334
        d->insert("Four", 4);
×
6335

6336
        obj = table->get_object_with_primary_key(456);
×
6337
        auto list = obj.get_list_ptr<Mixed>(col_any);
×
6338
        // Replace Dictionary with List
6339
        list->set_collection(2, CollectionType::List);
×
6340
        auto l = list->get_list(2);
×
6341
        l->add(47);
×
6342
    });
×
6343

6344
    session_2.wait_for_upload_complete_or_client_stopped();
×
6345
    session_1.wait_for_download_complete_or_client_stopped();
×
6346

6347
    {
×
6348
        ReadTransaction read_1{db_1};
×
6349
        ReadTransaction read_2{db_2};
×
6350

6351
        auto table = read_2.get_table("class_Table");
×
6352
        auto col_any = table->get_column_key("any");
×
6353

6354
        CHECK_EQUAL(table->size(), 2);
×
6355

6356
        auto obj = table->get_object_with_primary_key(123);
×
6357
        auto dict = obj.get_dictionary_ptr(col_any);
×
6358
        auto d = dict->get_dictionary("collection");
×
6359
        CHECK_EQUAL(d->get("Four").get_int(), 4);
×
6360

6361
        obj = table->get_object_with_primary_key(456);
×
6362
        auto list = obj.get_list_ptr<Mixed>(col_any);
×
6363
        auto l = list->get_list(2);
×
6364
        CHECK_EQUAL(l->get_any(0).get_int(), 47);
×
6365
    }
×
6366
}
×
6367

6368
TEST(Sync_Dictionary_Links)
6369
{
2✔
6370
    TEST_CLIENT_DB(db_1);
2✔
6371
    TEST_CLIENT_DB(db_2);
2✔
6372

1✔
6373
    TEST_DIR(dir);
2✔
6374
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6375
    fixture.start();
2✔
6376

1✔
6377
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6378
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6379
    session_1.bind();
2✔
6380
    session_2.bind();
2✔
6381

1✔
6382
    // Test that we can transmit links.
1✔
6383

1✔
6384
    ColKey col_dict;
2✔
6385

1✔
6386
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6387
        auto& g = tr.get_group();
2✔
6388
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6389
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6390
        col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6391

1✔
6392
        auto foo = foos->create_object_with_primary_key(123);
2✔
6393
        auto a = bars->create_object_with_primary_key("a");
2✔
6394
        auto b = bars->create_object_with_primary_key("b");
2✔
6395

1✔
6396
        auto dict = foo.get_dictionary(col_dict);
2✔
6397
        dict.insert("a", a);
2✔
6398
        dict.insert("b", b);
2✔
6399
    });
2✔
6400

1✔
6401
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6402
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6403

1✔
6404
    {
2✔
6405
        ReadTransaction tr{db_2};
2✔
6406

1✔
6407
        auto foos = tr.get_table("class_Foo");
2✔
6408
        auto bars = tr.get_table("class_Bar");
2✔
6409

1✔
6410
        CHECK_EQUAL(foos->size(), 1);
2✔
6411
        CHECK_EQUAL(bars->size(), 2);
2✔
6412

1✔
6413
        auto foo = foos->get_object_with_primary_key(123);
2✔
6414
        auto a = bars->get_object_with_primary_key("a");
2✔
6415
        auto b = bars->get_object_with_primary_key("b");
2✔
6416

1✔
6417
        auto dict = foo.get_dictionary(foos->get_column_key("dict"));
2✔
6418
        CHECK_EQUAL(dict.size(), 2);
2✔
6419

1✔
6420
        auto dict_a = dict.get("a");
2✔
6421
        auto dict_b = dict.get("b");
2✔
6422
        CHECK(dict_a == Mixed{a.get_link()});
2✔
6423
        CHECK(dict_b == Mixed{b.get_link()});
2✔
6424
    }
2✔
6425

1✔
6426
    // Test that we can create tombstones for objects in dictionaries.
1✔
6427

1✔
6428
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6429
        auto& g = tr.get_group();
2✔
6430

1✔
6431
        auto bars = g.get_table("class_Bar");
2✔
6432
        auto a = bars->get_object_with_primary_key("a");
2✔
6433
        a.invalidate();
2✔
6434

1✔
6435
        auto foos = g.get_table("class_Foo");
2✔
6436
        auto foo = foos->get_object_with_primary_key(123);
2✔
6437
        auto dict = foo.get_dictionary(col_dict);
2✔
6438

1✔
6439
        CHECK_EQUAL(dict.size(), 2);
2✔
6440
        CHECK((*dict.find("a")).second.is_null());
2✔
6441

1✔
6442
        CHECK(dict.find("b") != dict.end());
2✔
6443
    });
2✔
6444

1✔
6445
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6446
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6447

1✔
6448
    {
2✔
6449
        ReadTransaction tr{db_2};
2✔
6450

1✔
6451
        auto foos = tr.get_table("class_Foo");
2✔
6452
        auto bars = tr.get_table("class_Bar");
2✔
6453

1✔
6454
        CHECK_EQUAL(foos->size(), 1);
2✔
6455
        CHECK_EQUAL(bars->size(), 1);
2✔
6456

1✔
6457
        auto b = bars->get_object_with_primary_key("b");
2✔
6458

1✔
6459
        auto foo = foos->get_object_with_primary_key(123);
2✔
6460
        auto dict = foo.get_dictionary(col_dict);
2✔
6461

1✔
6462
        CHECK_EQUAL(dict.size(), 2);
2✔
6463
        CHECK((*dict.find("a")).second.is_null());
2✔
6464

1✔
6465
        CHECK(dict.find("b") != dict.end());
2✔
6466
        CHECK((*dict.find("b")).second == Mixed{b.get_link()});
2✔
6467
    }
2✔
6468
}
2✔
6469

6470
TEST(Sync_Set)
6471
{
2✔
6472
    // Test replication and synchronization of Set values.
1✔
6473

1✔
6474
    TEST_CLIENT_DB(db_1);
2✔
6475
    TEST_CLIENT_DB(db_2);
2✔
6476

1✔
6477
    TEST_DIR(dir);
2✔
6478
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6479
    fixture.start();
2✔
6480

1✔
6481
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6482
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6483
    session_1.bind();
2✔
6484
    session_2.bind();
2✔
6485

1✔
6486
    ColKey col_ints, col_strings, col_mixeds;
2✔
6487
    {
2✔
6488
        WriteTransaction wt{db_1};
2✔
6489
        auto t = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "pk");
2✔
6490
        col_ints = t->add_column_set(type_Int, "ints");
2✔
6491
        col_strings = t->add_column_set(type_String, "strings");
2✔
6492
        col_mixeds = t->add_column_set(type_Mixed, "mixeds");
2✔
6493

1✔
6494
        auto obj = t->create_object_with_primary_key(0);
2✔
6495

1✔
6496
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6497
        auto strings = obj.get_set<StringData>(col_strings);
2✔
6498
        auto mixeds = obj.get_set<Mixed>(col_mixeds);
2✔
6499

1✔
6500
        ints.insert(123);
2✔
6501
        ints.insert(456);
2✔
6502
        ints.insert(789);
2✔
6503
        ints.insert(123);
2✔
6504
        ints.insert(456);
2✔
6505
        ints.insert(789);
2✔
6506

1✔
6507
        CHECK_EQUAL(ints.size(), 3);
2✔
6508
        CHECK_EQUAL(ints.find(123), 0);
2✔
6509
        CHECK_EQUAL(ints.find(456), 1);
2✔
6510
        CHECK_EQUAL(ints.find(789), 2);
2✔
6511

1✔
6512
        strings.insert("a");
2✔
6513
        strings.insert("b");
2✔
6514
        strings.insert("c");
2✔
6515
        strings.insert("a");
2✔
6516
        strings.insert("b");
2✔
6517
        strings.insert("c");
2✔
6518

1✔
6519
        CHECK_EQUAL(strings.size(), 3);
2✔
6520
        CHECK_EQUAL(strings.find("a"), 0);
2✔
6521
        CHECK_EQUAL(strings.find("b"), 1);
2✔
6522
        CHECK_EQUAL(strings.find("c"), 2);
2✔
6523

1✔
6524
        mixeds.insert(Mixed{123});
2✔
6525
        mixeds.insert(Mixed{"a"});
2✔
6526
        mixeds.insert(Mixed{456.0});
2✔
6527
        mixeds.insert(Mixed{123});
2✔
6528
        mixeds.insert(Mixed{"a"});
2✔
6529
        mixeds.insert(Mixed{456.0});
2✔
6530

1✔
6531
        CHECK_EQUAL(mixeds.size(), 3);
2✔
6532
        CHECK_EQUAL(mixeds.find(123), 0);
2✔
6533
        CHECK_EQUAL(mixeds.find(456.0), 1);
2✔
6534
        CHECK_EQUAL(mixeds.find("a"), 2);
2✔
6535

1✔
6536
        wt.commit();
2✔
6537
    }
2✔
6538

1✔
6539
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6540
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6541

1✔
6542
    // Create a conflict. Session 1 should lose, because it has a lower peer ID.
1✔
6543
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6544
        auto t = wt.get_table("class_Foo");
2✔
6545
        auto obj = t->get_object_with_primary_key(0);
2✔
6546

1✔
6547
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6548
        ints.insert(999);
2✔
6549
    });
2✔
6550

1✔
6551
    write_transaction(db_2, [=](WriteTransaction& wt) {
2✔
6552
        auto t = wt.get_table("class_Foo");
2✔
6553
        auto obj = t->get_object_with_primary_key(0);
2✔
6554

1✔
6555
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6556
        ints.insert(999);
2✔
6557
        ints.erase(999);
2✔
6558
    });
2✔
6559

1✔
6560
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6561
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6562
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6563
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6564

1✔
6565
    {
2✔
6566
        ReadTransaction read_1{db_1};
2✔
6567
        ReadTransaction read_2{db_2};
2✔
6568
        CHECK(compare_groups(read_1, read_2));
2✔
6569
    }
2✔
6570

1✔
6571
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6572
        auto t = wt.get_table("class_Foo");
2✔
6573
        auto obj = t->get_object_with_primary_key(0);
2✔
6574
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6575
        ints.clear();
2✔
6576
    });
2✔
6577

1✔
6578
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6579
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6580

1✔
6581
    {
2✔
6582
        ReadTransaction read_1{db_1};
2✔
6583
        ReadTransaction read_2{db_2};
2✔
6584
        CHECK(compare_groups(read_1, read_2));
2✔
6585
    }
2✔
6586
}
2✔
6587

6588
TEST(Sync_BundledRealmFile)
6589
{
2✔
6590
    TEST_CLIENT_DB(db);
2✔
6591
    SHARED_GROUP_TEST_PATH(path);
2✔
6592

1✔
6593
    TEST_DIR(dir);
2✔
6594
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6595
    fixture.start();
2✔
6596

1✔
6597
    Session session = fixture.make_bound_session(db);
2✔
6598

1✔
6599
    write_transaction(db, [](WriteTransaction& tr) {
2✔
6600
        auto foos = tr.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6601
        foos->create_object_with_primary_key(123);
2✔
6602
    });
2✔
6603

1✔
6604
    // We cannot write out file if changes are not synced to server
1✔
6605
    CHECK_THROW_ANY(db->write_copy(path.c_str(), nullptr));
2✔
6606

1✔
6607
    session.wait_for_upload_complete_or_client_stopped();
2✔
6608
    session.wait_for_download_complete_or_client_stopped();
2✔
6609

1✔
6610
    // Now we can
1✔
6611
    db->write_copy(path.c_str(), nullptr);
2✔
6612
}
2✔
6613

6614
TEST(Sync_UpgradeToClientHistory)
6615
{
2✔
6616
    DBOptions options;
2✔
6617
    options.logger = test_context.logger;
2✔
6618
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
6619
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
6620
    auto db_1 = DB::create(make_in_realm_history(), db_1_path, options);
2✔
6621
    auto db_2 = DB::create(make_in_realm_history(), db_2_path, options);
2✔
6622
    {
2✔
6623
        auto tr = db_1->start_write();
2✔
6624

1✔
6625
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
6626
        auto col_float = embedded->add_column(type_Float, "float");
2✔
6627
        auto col_additional = embedded->add_column_dictionary(*embedded, "additional");
2✔
6628

1✔
6629
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6630
        auto col_list = baas->add_column_list(type_Int, "list");
2✔
6631
        auto col_set = baas->add_column_set(type_Int, "set");
2✔
6632
        auto col_dict = baas->add_column_dictionary(type_Int, "dictionary");
2✔
6633
        auto col_child = baas->add_column(*embedded, "child");
2✔
6634

1✔
6635
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6636
        auto col_str = foos->add_column(type_String, "str");
2✔
6637
        auto col_children = foos->add_column_list(*embedded, "children");
2✔
6638

1✔
6639
        auto foobaas = tr->add_table_with_primary_key("class_FooBaa", type_ObjectId, "_id");
2✔
6640
        auto col_time = foobaas->add_column(type_Timestamp, "time");
2✔
6641

1✔
6642
        auto col_link = baas->add_column(*foos, "link");
2✔
6643

1✔
6644
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Hello");
2✔
6645
        auto children = foo.get_linklist(col_children);
2✔
6646
        children.create_and_insert_linked_object(0);
2✔
6647
        auto baa = baas->create_object_with_primary_key(999).set(col_link, foo.get_key());
2✔
6648
        auto obj = baa.create_and_set_linked_object(col_child);
2✔
6649
        obj.set(col_float, 42.f);
2✔
6650
        auto additional = obj.get_dictionary(col_additional);
2✔
6651
        additional.create_and_insert_linked_object("One").set(col_float, 1.f);
2✔
6652
        additional.create_and_insert_linked_object("Two").set(col_float, 2.f);
2✔
6653
        additional.create_and_insert_linked_object("Three").set(col_float, 3.f);
2✔
6654

1✔
6655
        auto list = baa.get_list<Int>(col_list);
2✔
6656
        list.add(1);
2✔
6657
        list.add(0);
2✔
6658
        list.add(2);
2✔
6659
        list.add(3);
2✔
6660
        list.set(1, 5);
2✔
6661
        list.remove(1);
2✔
6662
        auto set = baa.get_set<Int>(col_set);
2✔
6663
        set.insert(4);
2✔
6664
        set.insert(2);
2✔
6665
        set.insert(5);
2✔
6666
        set.insert(6);
2✔
6667
        set.erase(2);
2✔
6668
        auto dict = baa.get_dictionary(col_dict);
2✔
6669
        dict.insert("key6", 6);
2✔
6670
        dict.insert("key7", 7);
2✔
6671
        dict.insert("key8", 8);
2✔
6672
        dict.insert("key9", 9);
2✔
6673
        dict.erase("key6");
2✔
6674

1✔
6675
        for (int i = 0; i < 100; i++) {
202✔
6676
            foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i));
200✔
6677
        }
200✔
6678

1✔
6679
        tr->commit();
2✔
6680
    }
2✔
6681
    {
2✔
6682
        auto tr = db_2->start_write();
2✔
6683
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6684
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6685
        auto col_str = foos->add_column(type_String, "str");
2✔
6686
        auto col_link = baas->add_column(*foos, "link");
2✔
6687

1✔
6688
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Goodbye");
2✔
6689
        baas->create_object_with_primary_key(888).set(col_link, foo.get_key());
2✔
6690

1✔
6691
        tr->commit();
2✔
6692
    }
2✔
6693

1✔
6694
    db_1->create_new_history(make_client_replication());
2✔
6695
    db_2->create_new_history(make_client_replication());
2✔
6696

1✔
6697
    TEST_DIR(dir);
2✔
6698
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6699
    fixture.start();
2✔
6700

1✔
6701
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6702
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6703
    session_1.bind();
2✔
6704
    session_2.bind();
2✔
6705

1✔
6706
    write_transaction(db_1, [](WriteTransaction& tr) {
2✔
6707
        auto foos = tr.get_group().get_table("class_Foo");
2✔
6708
        foos->create_object_with_primary_key("456");
2✔
6709
    });
2✔
6710
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6711
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6712
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6713
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6714

1✔
6715
    // db_2->start_read()->to_json(std::cout);
1✔
6716
}
2✔
6717

6718
// This test is extracted from ClientReset_ThreeClients
6719
// because it uncovers a bug in how MSVC 2019 compiles
6720
// things in Changeset::get_key()
6721
TEST(Sync_MergeStringPrimaryKey)
6722
{
2✔
6723
    TEST_DIR(dir_1); // The server.
2✔
6724
    TEST_CLIENT_DB(db_1);
2✔
6725
    TEST_CLIENT_DB(db_2);
2✔
6726
    TEST_DIR(metadata_dir_1);
2✔
6727
    TEST_DIR(metadata_dir_2);
2✔
6728

1✔
6729
    const std::string server_path = "/data";
2✔
6730

1✔
6731
    std::string real_path_1, real_path_2;
2✔
6732

1✔
6733
    auto create_schema = [&](Transaction& group) {
4✔
6734
        TableRef table_0 = group.add_table_with_primary_key("class_table_0", type_Int, "id");
4✔
6735
        table_0->add_column(type_Int, "int");
4✔
6736
        table_0->add_column(type_Bool, "bool");
4✔
6737
        table_0->add_column(type_Float, "float");
4✔
6738
        table_0->add_column(type_Double, "double");
4✔
6739
        table_0->add_column(type_Timestamp, "timestamp");
4✔
6740

2✔
6741
        TableRef table_1 = group.add_table_with_primary_key("class_table_1", type_Int, "pk_int");
4✔
6742
        table_1->add_column(type_String, "String");
4✔
6743

2✔
6744
        TableRef table_2 = group.add_table_with_primary_key("class_table_2", type_String, "pk_string");
4✔
6745
        table_2->add_column_list(type_String, "array_string");
4✔
6746
    };
4✔
6747

1✔
6748
    // First we make changesets. Then we upload them.
1✔
6749
    {
2✔
6750
        ClientServerFixture fixture(dir_1, test_context);
2✔
6751
        fixture.start();
2✔
6752
        real_path_1 = fixture.map_virtual_to_real_path(server_path);
2✔
6753

1✔
6754
        {
2✔
6755
            WriteTransaction wt{db_1};
2✔
6756
            create_schema(wt);
2✔
6757
            wt.commit();
2✔
6758
        }
2✔
6759
        {
2✔
6760
            WriteTransaction wt{db_2};
2✔
6761
            create_schema(wt);
2✔
6762

1✔
6763
            TableRef table_2 = wt.get_table("class_table_2");
2✔
6764
            auto col = table_2->get_column_key("array_string");
2✔
6765
            auto list_string = table_2->create_object_with_primary_key("aaa").get_list<String>(col);
2✔
6766
            list_string.add("a");
2✔
6767
            list_string.add("b");
2✔
6768

1✔
6769
            wt.commit();
2✔
6770
        }
2✔
6771

1✔
6772
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
6773
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
6774

1✔
6775
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
6776
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
6777
        // Download completion is not important.
1✔
6778
    }
2✔
6779
}
2✔
6780

6781
TEST(Sync_DifferentUsersMultiplexing)
6782
{
2✔
6783
    ClientServerFixture::Config fixture_config;
2✔
6784
    fixture_config.one_connection_per_session = false;
2✔
6785

1✔
6786
    TEST_DIR(server_dir);
2✔
6787
    ClientServerFixture fixture(server_dir, test_context, std::move(fixture_config));
2✔
6788

1✔
6789
    struct SessionBundle {
2✔
6790
        test_util::DBTestPathGuard path_guard;
2✔
6791
        DBRef db;
2✔
6792
        Session sess;
2✔
6793

1✔
6794
        SessionBundle(unit_test::TestContext& ctx, ClientServerFixture& fixture, std::string name,
2✔
6795
                      std::string signed_token, std::string user_id)
2✔
6796
            : path_guard(realm::test_util::get_test_path(ctx.get_test_name(), "." + name + ".realm"))
2✔
6797
            , db(DB::create(make_client_replication(), path_guard))
2✔
6798
        {
8✔
6799
            Session::Config config;
8✔
6800
            config.signed_user_token = signed_token;
8✔
6801
            config.user_id = user_id;
8✔
6802
            sess = fixture.make_bound_session(db, "/test", std::move(config));
8✔
6803
            sess.wait_for_download_complete_or_client_stopped();
8✔
6804
        }
8✔
6805
    };
2✔
6806

1✔
6807
    fixture.start();
2✔
6808

1✔
6809
    SessionBundle user_1_sess_1(test_context, fixture, "user_1_db_1", g_user_0_token, "user_0");
2✔
6810
    SessionBundle user_2_sess_1(test_context, fixture, "user_2_db_1", g_user_1_token, "user_1");
2✔
6811
    SessionBundle user_1_sess_2(test_context, fixture, "user_1_db_2", g_user_0_token, "user_0");
2✔
6812
    SessionBundle user_2_sess_2(test_context, fixture, "user_2_db_2", g_user_1_token, "user_1");
2✔
6813

1✔
6814
    CHECK_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6815
                user_1_sess_2.sess.get_appservices_connection_id());
2✔
6816
    CHECK_EQUAL(user_2_sess_1.sess.get_appservices_connection_id(),
2✔
6817
                user_2_sess_2.sess.get_appservices_connection_id());
2✔
6818
    CHECK_NOT_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6819
                    user_2_sess_1.sess.get_appservices_connection_id());
2✔
6820
    CHECK_NOT_EQUAL(user_1_sess_2.sess.get_appservices_connection_id(),
2✔
6821
                    user_2_sess_2.sess.get_appservices_connection_id());
2✔
6822
}
2✔
6823

6824
TEST(Sync_TransformAgainstEmptyReciprocalChangeset)
6825
{
2✔
6826
    TEST_CLIENT_DB(seed_db);
2✔
6827
    TEST_CLIENT_DB(db_1);
2✔
6828
    TEST_CLIENT_DB(db_2);
2✔
6829

1✔
6830
    {
2✔
6831
        auto tr = seed_db->start_write();
2✔
6832
        // Create schema: single table with array of ints as property.
1✔
6833
        auto table = tr->add_table_with_primary_key("class_table", type_Int, "_id");
2✔
6834
        table->add_column_list(type_Int, "ints");
2✔
6835
        table->add_column(type_String, "string");
2✔
6836
        tr->commit_and_continue_writing();
2✔
6837

1✔
6838
        // Create object and initialize array.
1✔
6839
        table = tr->get_table("class_table");
2✔
6840
        auto obj = table->create_object_with_primary_key(42);
2✔
6841
        auto ints = obj.get_list<int64_t>("ints");
2✔
6842
        for (auto i = 0; i < 8; ++i) {
18✔
6843
            ints.insert(i, i);
16✔
6844
        }
16✔
6845
        tr->commit();
2✔
6846
    }
2✔
6847

1✔
6848
    {
2✔
6849
        TEST_DIR(dir);
2✔
6850
        MultiClientServerFixture fixture(3, 1, dir, test_context);
2✔
6851
        fixture.start();
2✔
6852

1✔
6853
        util::Optional<Session> seed_session = fixture.make_bound_session(0, seed_db, 0, "/test");
2✔
6854
        util::Optional<Session> db_1_session = fixture.make_bound_session(1, db_1, 0, "/test");
2✔
6855
        util::Optional<Session> db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6856

1✔
6857
        seed_session->wait_for_upload_complete_or_client_stopped();
2✔
6858
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6859
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6860
        seed_session.reset();
2✔
6861
        db_2_session.reset();
2✔
6862

1✔
6863
        auto move_element = [&](const DBRef& db, size_t from, size_t to, size_t string_size = 0) {
8✔
6864
            auto wt = db->start_write();
8✔
6865
            auto table = wt->get_table("class_table");
8✔
6866
            auto obj = table->get_object_with_primary_key(42);
8✔
6867
            auto ints = obj.get_list<int64_t>("ints");
8✔
6868
            ints.move(from, to);
8✔
6869
            obj.set("string", std::string(string_size, 'a'));
8✔
6870
            wt->commit();
8✔
6871
        };
8✔
6872

1✔
6873
        // Client 1 uploads two move instructions.
1✔
6874
        move_element(db_1, 7, 2);
2✔
6875
        move_element(db_1, 7, 6);
2✔
6876

1✔
6877
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6878

1✔
6879
        std::this_thread::sleep_for(std::chrono::milliseconds{10});
2✔
6880

1✔
6881
        // Client 2 uploads two move instructions.
1✔
6882
        // The sync client uploads at most 128 KB of data so we make the first changeset large enough so two upload
1✔
6883
        // messages are sent to the server instead of one. Each change is transformed against the changes from
1✔
6884
        // Client 1.
1✔
6885

1✔
6886
        // First change discards the first change (move(7, 2)) of Client 1.
1✔
6887
        move_element(db_2, 7, 0, 200 * 1024);
2✔
6888
        // Second change is tranformed against an empty reciprocal changeset as result of the change above.
1✔
6889
        move_element(db_2, 7, 5);
2✔
6890
        db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6891

1✔
6892
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6893
        db_2_session->wait_for_upload_complete_or_client_stopped();
2✔
6894

1✔
6895
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6896
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6897
    }
2✔
6898

1✔
6899
    ReadTransaction rt_1(db_1);
2✔
6900
    ReadTransaction rt_2(db_2);
2✔
6901
    const Group& group_1 = rt_1;
2✔
6902
    const Group& group_2 = rt_2;
2✔
6903
    group_1.verify();
2✔
6904
    group_2.verify();
2✔
6905
    CHECK(compare_groups(rt_1, rt_2));
2✔
6906
}
2✔
6907

6908
#endif // !REALM_MOBILE
6909

6910
// Tests that an empty reciprocal changesets is set and retrieved correctly.
6911
TEST(Sync_SetAndGetEmptyReciprocalChangeset)
6912
{
2✔
6913
    using namespace realm;
2✔
6914
    using namespace realm::sync::instr;
2✔
6915
    using realm::sync::Changeset;
2✔
6916

1✔
6917
    TEST_CLIENT_DB(db);
2✔
6918

1✔
6919
    auto& history = get_history(db);
2✔
6920
    history.set_client_file_ident(SaltedFileIdent{1, 0x1234567812345678}, false);
2✔
6921
    timestamp_type timestamp{1};
2✔
6922
    history.set_local_origin_timestamp_source([&] {
6✔
6923
        return ++timestamp;
6✔
6924
    });
6✔
6925

1✔
6926
    auto latest_local_version = [&] {
2✔
6927
        auto tr = db->start_write();
2✔
6928
        // Create schema: single table with array of ints as property.
1✔
6929
        tr->add_table_with_primary_key("class_table", type_Int, "_id")->add_column_list(type_Int, "ints");
2✔
6930
        tr->commit_and_continue_writing();
2✔
6931

1✔
6932
        // Create object and initialize array.
1✔
6933
        TableRef table = tr->get_table("class_table");
2✔
6934
        auto obj = table->create_object_with_primary_key(42);
2✔
6935
        auto ints = obj.get_list<int64_t>("ints");
2✔
6936
        for (auto i = 0; i < 8; ++i) {
18✔
6937
            ints.insert(i, i);
16✔
6938
        }
16✔
6939
        tr->commit_and_continue_writing();
2✔
6940

1✔
6941
        // Move element in array.
1✔
6942
        ints.move(7, 2);
2✔
6943
        return tr->commit();
2✔
6944
    }();
2✔
6945

1✔
6946
    // Create changeset which moves element from index 7 to index 0 in array.
1✔
6947
    // This changeset will discard the previous move (reciprocal changeset), leaving the local reciprocal changesets
1✔
6948
    // with no instructions (empty).
1✔
6949
    Changeset changeset;
2✔
6950
    ArrayMove instr;
2✔
6951
    instr.table = changeset.intern_string("table");
2✔
6952
    instr.object = instr::PrimaryKey{42};
2✔
6953
    instr.field = changeset.intern_string("ints");
2✔
6954
    instr.path.push_back(7);
2✔
6955
    instr.ndx_2 = 0;
2✔
6956
    instr.prior_size = 8;
2✔
6957
    changeset.push_back(instr);
2✔
6958
    changeset.version = 1;
2✔
6959
    changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6960
    changeset.origin_timestamp = timestamp;
2✔
6961
    changeset.origin_file_ident = 2;
2✔
6962

1✔
6963
    ChangesetEncoder::Buffer encoded;
2✔
6964
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6965
    encode_changeset(changeset, encoded);
2✔
6966
    server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
2✔
6967
                                           BinaryData(encoded.data(), encoded.size()), changeset.origin_timestamp,
2✔
6968
                                           changeset.origin_file_ident);
2✔
6969

1✔
6970
    SyncProgress progress = {};
2✔
6971
    progress.download.server_version = changeset.version;
2✔
6972
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6973
    progress.latest_server_version.version = changeset.version;
2✔
6974
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6975

1✔
6976
    uint_fast64_t downloadable_bytes = 0;
2✔
6977
    VersionInfo version_info;
2✔
6978
    auto transact = db->start_read();
2✔
6979
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6980
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6981

1✔
6982
    bool is_compressed = false;
2✔
6983
    auto data = history.get_reciprocal_transform(latest_local_version, is_compressed);
2✔
6984
    Changeset reciprocal_changeset;
2✔
6985
    ChunkedBinaryInputStream in{data};
2✔
6986
    if (is_compressed) {
2✔
6987
        size_t total_size;
2✔
6988
        auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
2✔
6989
        CHECK(decompressed);
2✔
6990
        sync::parse_changeset(*decompressed, reciprocal_changeset); // Throws
2✔
6991
    }
2✔
6992
    else {
×
6993
        sync::parse_changeset(in, reciprocal_changeset); // Throws
×
6994
    }
×
6995
    // The only instruction in the reciprocal changeset was discarded during OT.
1✔
6996
    CHECK(reciprocal_changeset.empty());
2✔
6997
}
2✔
6998

6999
TEST(Sync_InvalidChangesetFromServer)
7000
{
2✔
7001
    TEST_CLIENT_DB(db);
2✔
7002

1✔
7003
    auto& history = get_history(db);
2✔
7004
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
7005

1✔
7006
    instr::CreateObject bad_instr;
2✔
7007
    bad_instr.object = InternString{1};
2✔
7008
    bad_instr.table = InternString{2};
2✔
7009

1✔
7010
    Changeset changeset;
2✔
7011
    changeset.push_back(bad_instr);
2✔
7012

1✔
7013
    ChangesetEncoder::Buffer encoded;
2✔
7014
    encode_changeset(changeset, encoded);
2✔
7015
    RemoteChangeset server_changeset;
2✔
7016
    server_changeset.origin_file_ident = 1;
2✔
7017
    server_changeset.remote_version = 1;
2✔
7018
    server_changeset.data = BinaryData(encoded.data(), encoded.size());
2✔
7019

1✔
7020
    VersionInfo version_info;
2✔
7021
    auto transact = db->start_read();
2✔
7022
    CHECK_THROW_EX(history.integrate_server_changesets({}, nullptr, util::Span(&server_changeset, 1), version_info,
2✔
7023
                                                       DownloadBatchState::SteadyState, *test_context.logger,
2✔
7024
                                                       transact),
2✔
7025
                   sync::IntegrationException,
2✔
7026
                   StringData(e.what()).contains("Failed to parse received changeset: Invalid interned string"));
2✔
7027
}
2✔
7028

7029
TEST(Sync_ServerVersionsSkippedFromDownloadCursor)
7030
{
2✔
7031
    TEST_CLIENT_DB(db);
2✔
7032

1✔
7033
    auto& history = get_history(db);
2✔
7034
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
7035
    timestamp_type timestamp{1};
2✔
7036
    history.set_local_origin_timestamp_source([&] {
2✔
7037
        return ++timestamp;
2✔
7038
    });
2✔
7039

1✔
7040
    auto latest_local_version = [&] {
2✔
7041
        auto tr = db->start_write();
2✔
7042
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
7043
        return tr->commit();
2✔
7044
    }();
2✔
7045

1✔
7046
    Changeset server_changeset;
2✔
7047
    server_changeset.version = 10;
2✔
7048
    server_changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
7049
    server_changeset.origin_timestamp = ++timestamp;
2✔
7050
    server_changeset.origin_file_ident = 1;
2✔
7051

1✔
7052
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
7053
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
7054
    encoded.emplace_back();
2✔
7055
    encode_changeset(server_changeset, encoded.back());
2✔
7056
    server_changesets_encoded.emplace_back(server_changeset.version, server_changeset.last_integrated_remote_version,
2✔
7057
                                           BinaryData(encoded.back().data(), encoded.back().size()),
2✔
7058
                                           server_changeset.origin_timestamp, server_changeset.origin_file_ident);
2✔
7059

1✔
7060
    SyncProgress progress = {};
2✔
7061
    // The server skips 10 server versions.
1✔
7062
    progress.download.server_version = server_changeset.version + 10;
2✔
7063
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
7064
    progress.latest_server_version.version = server_changeset.version + 15;
2✔
7065
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
7066

1✔
7067
    uint_fast64_t downloadable_bytes = 0;
2✔
7068
    VersionInfo version_info;
2✔
7069
    auto transact = db->start_read();
2✔
7070
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
7071
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
7072

1✔
7073
    version_type current_version;
2✔
7074
    SaltedFileIdent file_ident;
2✔
7075
    SyncProgress expected_progress;
2✔
7076
    history.get_status(current_version, file_ident, expected_progress);
2✔
7077

1✔
7078
    // Check progress is reported correctly.
1✔
7079
    CHECK_EQUAL(progress.latest_server_version.salt, expected_progress.latest_server_version.salt);
2✔
7080
    CHECK_EQUAL(progress.latest_server_version.version, expected_progress.latest_server_version.version);
2✔
7081
    CHECK_EQUAL(progress.download.last_integrated_client_version,
2✔
7082
                expected_progress.download.last_integrated_client_version);
2✔
7083
    CHECK_EQUAL(progress.download.server_version, expected_progress.download.server_version);
2✔
7084
    CHECK_EQUAL(progress.upload.client_version, expected_progress.upload.client_version);
2✔
7085
    CHECK_EQUAL(progress.upload.last_integrated_server_version,
2✔
7086
                expected_progress.upload.last_integrated_server_version);
2✔
7087
}
2✔
7088

7089
TEST(Sync_NonIncreasingServerVersions)
7090
{
2✔
7091
    TEST_CLIENT_DB(db);
2✔
7092

1✔
7093
    auto& history = get_history(db);
2✔
7094
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
7095
    timestamp_type timestamp{1};
2✔
7096
    history.set_local_origin_timestamp_source([&] {
2✔
7097
        return ++timestamp;
2✔
7098
    });
2✔
7099

1✔
7100
    auto latest_local_version = [&] {
2✔
7101
        auto tr = db->start_write();
2✔
7102
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
7103
        return tr->commit();
2✔
7104
    }();
2✔
7105

1✔
7106
    std::vector<Changeset> server_changesets;
2✔
7107
    auto prep_changeset = [&](auto pk_name, auto int_col_val) {
8✔
7108
        Changeset changeset;
8✔
7109
        changeset.version = 10;
8✔
7110
        changeset.last_integrated_remote_version = latest_local_version - 1;
8✔
7111
        changeset.origin_timestamp = ++timestamp;
8✔
7112
        changeset.origin_file_ident = 1;
8✔
7113
        instr::PrimaryKey pk{changeset.intern_string(pk_name)};
8✔
7114
        auto table_name = changeset.intern_string("foo");
8✔
7115
        auto col_name = changeset.intern_string("int_col");
8✔
7116
        instr::EraseObject erase_1;
8✔
7117
        erase_1.object = pk;
8✔
7118
        erase_1.table = table_name;
8✔
7119
        changeset.push_back(erase_1);
8✔
7120
        instr::CreateObject create_1;
8✔
7121
        create_1.object = pk;
8✔
7122
        create_1.table = table_name;
8✔
7123
        changeset.push_back(create_1);
8✔
7124
        instr::Update update_1;
8✔
7125
        update_1.table = table_name;
8✔
7126
        update_1.object = pk;
8✔
7127
        update_1.field = col_name;
8✔
7128
        update_1.value = instr::Payload{int64_t(int_col_val)};
8✔
7129
        changeset.push_back(update_1);
8✔
7130
        server_changesets.push_back(std::move(changeset));
8✔
7131
    };
8✔
7132
    prep_changeset("bizz", 1);
2✔
7133
    prep_changeset("buzz", 2);
2✔
7134
    prep_changeset("baz", 3);
2✔
7135
    prep_changeset("bar", 4);
2✔
7136
    ++server_changesets.back().version;
2✔
7137

1✔
7138
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
7139
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
7140
    for (const auto& changeset : server_changesets) {
8✔
7141
        encoded.emplace_back();
8✔
7142
        encode_changeset(changeset, encoded.back());
8✔
7143
        server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
8✔
7144
                                               BinaryData(encoded.back().data(), encoded.back().size()),
8✔
7145
                                               changeset.origin_timestamp, changeset.origin_file_ident);
8✔
7146
    }
8✔
7147

1✔
7148
    SyncProgress progress = {};
2✔
7149
    progress.download.server_version = server_changesets.back().version;
2✔
7150
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
7151
    progress.latest_server_version.version = server_changesets.back().version;
2✔
7152
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
7153

1✔
7154
    uint_fast64_t downloadable_bytes = 0;
2✔
7155
    VersionInfo version_info;
2✔
7156
    auto transact = db->start_read();
2✔
7157
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
7158
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
7159
}
2✔
7160

7161
TEST(Sync_DanglingLinksCountInPriorSize)
7162
{
2✔
7163
    SHARED_GROUP_TEST_PATH(path);
2✔
7164
    ClientReplication repl;
2✔
7165
    auto local_db = realm::DB::create(repl, path);
2✔
7166
    auto& history = repl.get_history();
2✔
7167
    history.set_client_file_ident(sync::SaltedFileIdent{1, 123456}, true);
2✔
7168

1✔
7169
    version_type last_version, last_version_observed = 0;
2✔
7170
    auto dump_uploadable = [&] {
4✔
7171
        UploadCursor upload_cursor{last_version_observed, 0};
4✔
7172
        std::vector<sync::ClientHistory::UploadChangeset> changesets_to_upload;
4✔
7173
        version_type locked_server_version = 0;
4✔
7174
        history.find_uploadable_changesets(upload_cursor, last_version, changesets_to_upload, locked_server_version);
4✔
7175
        CHECK_EQUAL(changesets_to_upload.size(), static_cast<size_t>(1));
4✔
7176
        realm::sync::Changeset parsed_changeset;
4✔
7177
        auto unparsed_changeset = changesets_to_upload[0].changeset.get_first_chunk();
4✔
7178
        realm::util::SimpleInputStream changeset_stream(unparsed_changeset);
4✔
7179
        realm::sync::parse_changeset(changeset_stream, parsed_changeset);
4✔
7180
        test_context.logger->info("changeset at version %1: %2", last_version, parsed_changeset);
4✔
7181
        last_version_observed = last_version;
4✔
7182
        return parsed_changeset;
4✔
7183
    };
4✔
7184

1✔
7185
    TableKey source_table_key, target_table_key;
2✔
7186
    {
2✔
7187
        auto wt = local_db->start_write();
2✔
7188
        auto source_table = wt->add_table_with_primary_key("class_source", type_String, "_id");
2✔
7189
        auto target_table = wt->add_table_with_primary_key("class_target", type_String, "_id");
2✔
7190
        source_table->add_column_list(*target_table, "links");
2✔
7191

1✔
7192
        source_table_key = source_table->get_key();
2✔
7193
        target_table_key = target_table->get_key();
2✔
7194

1✔
7195
        auto obj_to_keep = target_table->create_object_with_primary_key(std::string{"target1"});
2✔
7196
        auto obj_to_delete = target_table->create_object_with_primary_key(std::string{"target2"});
2✔
7197
        auto source_obj = source_table->create_object_with_primary_key(std::string{"source"});
2✔
7198

1✔
7199
        auto links_list = source_obj.get_linklist("links");
2✔
7200
        links_list.add(obj_to_keep.get_key());
2✔
7201
        links_list.add(obj_to_delete.get_key());
2✔
7202
        last_version = wt->commit();
2✔
7203
    }
2✔
7204

1✔
7205
    dump_uploadable();
2✔
7206

1✔
7207
    {
2✔
7208
        // Simulate removing the object via the sync client so we get a dangling link
1✔
7209
        TempShortCircuitReplication disable_repl(repl);
2✔
7210
        auto wt = local_db->start_write();
2✔
7211
        auto target_table = wt->get_table(target_table_key);
2✔
7212
        auto obj = target_table->get_object_with_primary_key(std::string{"target2"});
2✔
7213
        obj.invalidate();
2✔
7214
        last_version = wt->commit();
2✔
7215
    }
2✔
7216

1✔
7217
    {
2✔
7218
        auto wt = local_db->start_write();
2✔
7219
        auto source_table = wt->get_table(source_table_key);
2✔
7220
        auto target_table = wt->get_table(target_table_key);
2✔
7221

1✔
7222
        auto obj_to_add = target_table->create_object_with_primary_key(std::string{"target3"});
2✔
7223

1✔
7224
        auto source_obj = source_table->get_object_with_primary_key(std::string{"source"});
2✔
7225
        auto links_list = source_obj.get_linklist("links");
2✔
7226
        links_list.add(obj_to_add.get_key());
2✔
7227
        last_version = wt->commit();
2✔
7228
    }
2✔
7229

1✔
7230
    auto changeset = dump_uploadable();
2✔
7231
    CHECK_EQUAL(changeset.size(), static_cast<size_t>(2));
2✔
7232
    auto changeset_it = changeset.end();
2✔
7233
    --changeset_it;
2✔
7234
    auto last_instr = *changeset_it;
2✔
7235
    CHECK_EQUAL(last_instr->type(), Instruction::Type::ArrayInsert);
2✔
7236
    auto arr_insert_instr = last_instr->get_as<Instruction::ArrayInsert>();
2✔
7237
    CHECK_EQUAL(changeset.get_string(arr_insert_instr.table), StringData("source"));
2✔
7238
    CHECK(arr_insert_instr.value.type == sync::instr::Payload::Type::Link);
2✔
7239
    CHECK_EQUAL(changeset.get_string(mpark::get<InternString>(arr_insert_instr.value.data.link.target)),
2✔
7240
                StringData("target3"));
2✔
7241
    CHECK_EQUAL(arr_insert_instr.prior_size, 2);
2✔
7242
}
2✔
7243

7244
// This test calls row_for_object_id() for various object ids and tests that
7245
// the right value is returned including that no assertions are hit.
7246
TEST(Sync_RowForGlobalKey)
7247
{
2✔
7248
    TEST_CLIENT_DB(db);
2✔
7249

1✔
7250
    {
2✔
7251
        WriteTransaction wt(db);
2✔
7252
        TableRef table = wt.add_table("class_foo");
2✔
7253
        table->add_column(type_Int, "i");
2✔
7254
        wt.commit();
2✔
7255
    }
2✔
7256

1✔
7257
    // Check that various object_ids are not in the table.
1✔
7258
    {
2✔
7259
        ReadTransaction rt(db);
2✔
7260
        ConstTableRef table = rt.get_table("class_foo");
2✔
7261
        CHECK(table);
2✔
7262

1✔
7263
        // Default constructed GlobalKey
1✔
7264
        {
2✔
7265
            GlobalKey object_id;
2✔
7266
            auto row_ndx = table->get_objkey(object_id);
2✔
7267
            CHECK_NOT(row_ndx);
2✔
7268
        }
2✔
7269

1✔
7270
        // GlobalKey with small lo and hi values
1✔
7271
        {
2✔
7272
            GlobalKey object_id{12, 24};
2✔
7273
            auto row_ndx = table->get_objkey(object_id);
2✔
7274
            CHECK_NOT(row_ndx);
2✔
7275
        }
2✔
7276

1✔
7277
        // GlobalKey with lo and hi values past the 32 bit limit.
1✔
7278
        {
2✔
7279
            GlobalKey object_id{uint_fast64_t(1) << 50, uint_fast64_t(1) << 52};
2✔
7280
            auto row_ndx = table->get_objkey(object_id);
2✔
7281
            CHECK_NOT(row_ndx);
2✔
7282
        }
2✔
7283
    }
2✔
7284
}
2✔
7285

7286

7287
} // 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