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

realm / realm-core / 2356

29 May 2024 11:05PM UTC coverage: 90.809% (+0.008%) from 90.801%
2356

push

Evergreen

web-flow
Merge pull request #7609 from realm/tg/session-lifecycle

RCORE-2092 Simplify the SessionWrapper lifecycle a bit

101528 of 179868 branches covered (56.45%)

506 of 544 new or added lines in 14 files covered. (93.01%)

62 existing lines in 17 files now uncovered.

214378 of 236077 relevant lines covered (90.81%)

6035598.49 hits per line

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

92.89
/test/test_sync.cpp
1
#include <chrono>
2
#include <cmath>
3
#include <condition_variable>
4
#include <cstddef>
5
#include <cstdint>
6
#include <cstdlib>
7
#include <cstring>
8
#include <memory>
9
#include <mutex>
10
#include <set>
11
#include <sstream>
12
#include <string>
13
#include <thread>
14
#include <tuple>
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);                                                                             \
322✔
107
    auto name = DB::create(make_client_replication(), name##_path);
322✔
108

109
template <typename Function>
110
DB::version_type write_transaction(DBRef db, Function&& function)
111
{
8,124✔
112
    WriteTransaction wt(db);
8,124✔
113
    function(wt);
8,124✔
114
    return wt.commit();
8,124✔
115
}
8,124✔
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
133
    //  It still passes because it runs against the mock C++ server, but the
134
    //  MongoDB Realm server will behave differently
135

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

143
    int nerrors = 0;
2✔
144

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

160
    Session session_1 = fixture.make_session(db_1, "/test.realm", config());
2✔
161
    Session session_2 = fixture.make_session(db_2, "/../test", config());
2✔
162
    Session session_3 = fixture.make_session(db_3, "test%abc ", config());
2✔
163

164
    session_1.wait_for_download_complete_or_client_stopped();
2✔
165
    session_2.wait_for_download_complete_or_client_stopped();
2✔
166
    session_3.wait_for_download_complete_or_client_stopped();
2✔
167
    CHECK_EQUAL(nerrors, 3);
2✔
168
}
2✔
169

170

171
TEST(Sync_AsyncWaitForUploadCompletion)
172
{
2✔
173
    TEST_DIR(dir);
2✔
174
    TEST_CLIENT_DB(db);
2✔
175
    ClientServerFixture fixture(dir, test_context);
2✔
176
    fixture.start();
2✔
177

178
    Session session = fixture.make_bound_session(db, "/test");
2✔
179

180
    auto wait = [&] {
8✔
181
        BowlOfStonesSemaphore bowl;
8✔
182
        auto handler = [&](Status status) {
8✔
183
            if (CHECK(status.is_ok()))
8✔
184
                bowl.add_stone();
8✔
185
        };
8✔
186
        session.async_wait_for_upload_completion(handler);
8✔
187
        bowl.get_stone();
8✔
188
    };
8✔
189

190
    // Empty
191
    wait();
2✔
192

193
    // Nonempty
194
    write_transaction(db, [](WriteTransaction& wt) {
2✔
195
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
196
    });
2✔
197
    wait();
2✔
198

199
    // Already done
200
    wait();
2✔
201

202
    // More
203
    write_transaction(db, [](WriteTransaction& wt) {
2✔
204
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
205
    });
2✔
206
    wait();
2✔
207
}
2✔
208

209

210
TEST(Sync_AsyncWaitForUploadCompletionNoPendingLocalChanges)
211
{
2✔
212
    TEST_DIR(dir);
2✔
213
    TEST_CLIENT_DB(db);
2✔
214
    ClientServerFixture fixture(dir, test_context);
2✔
215
    fixture.start();
2✔
216

217
    Session session = fixture.make_bound_session(db, "/test");
2✔
218

219
    write_transaction(db, [](WriteTransaction& wt) {
2✔
220
        wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
221
    });
2✔
222

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

233

234
TEST(Sync_AsyncWaitForDownloadCompletion)
235
{
2✔
236
    TEST_DIR(dir);
2✔
237
    TEST_CLIENT_DB(db_1);
2✔
238
    TEST_CLIENT_DB(db_2);
2✔
239
    ClientServerFixture fixture(dir, test_context);
2✔
240
    fixture.start();
2✔
241

242
    auto wait = [&](Session& session) {
12✔
243
        BowlOfStonesSemaphore bowl;
12✔
244
        auto handler = [&](Status status) {
12✔
245
            if (CHECK(status.is_ok()))
12✔
246
                bowl.add_stone();
12✔
247
        };
12✔
248
        session.async_wait_for_download_completion(handler);
12✔
249
        bowl.get_stone();
12✔
250
    };
12✔
251

252
    // Nothing to download
253
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
254
    wait(session_1);
2✔
255

256
    // Again
257
    wait(session_1);
2✔
258

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

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

274
    // Again
275
    wait(session_1);
2✔
276

277
    // Wait for session 2 to download nothing
278
    wait(session_2);
2✔
279

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

286
    // Wait for session 2 to download it
287
    wait(session_2);
2✔
288
    {
2✔
289
        ReadTransaction rt_1(db_1);
2✔
290
        ReadTransaction rt_2(db_2);
2✔
291
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
292
    }
2✔
293
}
2✔
294

295

296
TEST(Sync_AsyncWaitForSyncCompletion)
297
{
2✔
298
    TEST_DIR(dir);
2✔
299
    TEST_CLIENT_DB(db_1);
2✔
300
    TEST_CLIENT_DB(db_2);
2✔
301
    ClientServerFixture fixture(dir, test_context);
2✔
302
    fixture.start();
2✔
303

304
    auto wait = [&](Session& session) {
8✔
305
        BowlOfStonesSemaphore bowl;
8✔
306
        auto handler = [&](Status status) {
8✔
307
            if (CHECK(status.is_ok()))
8✔
308
                bowl.add_stone();
8✔
309
        };
8✔
310
        session.async_wait_for_sync_completion(handler);
8✔
311
        bowl.get_stone();
8✔
312
    };
8✔
313

314
    // Nothing to synchronize
315
    Session session_1 = fixture.make_bound_session(db_1);
2✔
316
    wait(session_1);
2✔
317

318
    // Again
319
    wait(session_1);
2✔
320

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

328
    // Generate changes to be uploaded
329
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
330
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
331
    });
2✔
332

333
    // Nontrivial synchronization (upload and download required)
334
    wait(session_1);
2✔
335
    wait(session_2);
2✔
336

337
    {
2✔
338
        ReadTransaction rt_1(db_1);
2✔
339
        ReadTransaction rt_2(db_2);
2✔
340
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
341
    }
2✔
342
}
2✔
343

344

345
TEST(Sync_AsyncWaitCancellation)
346
{
2✔
347
    TEST_DIR(dir);
2✔
348
    TEST_CLIENT_DB(db);
2✔
349
    ClientServerFixture fixture(dir, test_context);
2✔
350

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

369

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

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

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

395
    // Already done
396
    session.wait_for_upload_complete_or_client_stopped();
2✔
397

398
    // More changes
399
    write_transaction(db, [](WriteTransaction& wt) {
2✔
400
        wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
401
    });
2✔
402
    session.wait_for_upload_complete_or_client_stopped();
2✔
403
}
2✔
404

405

406
TEST(Sync_WaitForUploadCompletionAfterEmptyTransaction)
407
{
2✔
408
    TEST_DIR(dir);
2✔
409
    TEST_CLIENT_DB(db);
2✔
410
    ClientServerFixture fixture(dir, test_context);
2✔
411
    fixture.start();
2✔
412

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

427

428
TEST(Sync_WaitForDownloadCompletion)
429
{
2✔
430
    TEST_DIR(dir);
2✔
431
    TEST_CLIENT_DB(db_1);
2✔
432
    TEST_CLIENT_DB(db_2);
2✔
433
    ClientServerFixture fixture(dir, test_context);
2✔
434
    fixture.start();
2✔
435

436
    // Noting to download
437
    Session session_1 = fixture.make_bound_session(db_1);
2✔
438
    session_1.wait_for_download_complete_or_client_stopped();
2✔
439

440
    // Again
441
    session_1.wait_for_download_complete_or_client_stopped();
2✔
442

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

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

458
    // Again
459
    session_1.wait_for_download_complete_or_client_stopped();
2✔
460

461
    // Wait for session 2 to download nothing
462
    session_2.wait_for_download_complete_or_client_stopped();
2✔
463

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

470
    // Wait for session 2 to download it
471
    session_2.wait_for_download_complete_or_client_stopped();
2✔
472
    {
2✔
473
        ReadTransaction rt_1(db_1);
2✔
474
        ReadTransaction rt_2(db_2);
2✔
475
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
476
    }
2✔
477
}
2✔
478

479

480
TEST(Sync_WaitForDownloadCompletionAfterEmptyTransaction)
481
{
2✔
482
    TEST_DIR(dir);
2✔
483
    TEST_CLIENT_DB(db);
2✔
484
    ClientServerFixture fixture(dir, test_context);
2✔
485

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

504

505
TEST(Sync_WaitForDownloadCompletionManyConcurrent)
506
{
2✔
507
    TEST_DIR(dir);
2✔
508
    TEST_CLIENT_DB(db);
2✔
509
    ClientServerFixture fixture(dir, test_context);
2✔
510
    fixture.start();
2✔
511

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

525

526
TEST(Sync_WaitForSessionTerminations)
527
{
2✔
528
    TEST_DIR(server_dir);
2✔
529
    TEST_CLIENT_DB(db);
2✔
530

531
    ClientServerFixture fixture(server_dir, test_context);
2✔
532
    fixture.start();
2✔
533

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

554

555
TEST(Sync_TokenWithoutExpirationAllowed)
556
{
2✔
557
    bool did_fail = false;
2✔
558
    {
2✔
559
        TEST_DIR(dir);
2✔
560
        TEST_CLIENT_DB(db);
2✔
561
        ClientServerFixture fixture(dir, test_context);
2✔
562

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

572
        fixture.start();
2✔
573

574
        Session::Config sess_config;
2✔
575
        sess_config.signed_user_token = g_signed_test_user_token_expiration_unspecified;
2✔
576
        sess_config.connection_state_change_listener = listener;
2✔
577
        Session session = fixture.make_session(db, "/test", std::move(sess_config));
2✔
578
        write_transaction(db, [](WriteTransaction& wt) {
2✔
579
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
580
        });
2✔
581
        session.wait_for_upload_complete_or_client_stopped();
2✔
582
        session.wait_for_download_complete_or_client_stopped();
2✔
583
    }
2✔
584
    CHECK_NOT(did_fail);
2✔
585
}
2✔
586

587

588
TEST(Sync_TokenWithNullExpirationAllowed)
589
{
2✔
590
    bool did_fail = false;
2✔
591
    {
2✔
592
        TEST_DIR(dir);
2✔
593
        TEST_CLIENT_DB(db);
2✔
594
        ClientServerFixture fixture(dir, test_context);
2✔
595
        auto error_handler = [&](Status, bool) {
2✔
596
            did_fail = true;
×
597
            fixture.stop();
×
598
        };
×
599
        fixture.set_client_side_error_handler(error_handler);
2✔
600
        fixture.start();
2✔
601

602
        Session::Config config;
2✔
603
        config.signed_user_token = g_signed_test_user_token_expiration_null;
2✔
604
        Session session = fixture.make_session(db, "/test", std::move(config));
2✔
605
        {
2✔
606
            write_transaction(db, [](WriteTransaction& wt) {
2✔
607
                wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
608
            });
2✔
609
        }
2✔
610
        session.wait_for_upload_complete_or_client_stopped();
2✔
611
        session.wait_for_download_complete_or_client_stopped();
2✔
612
    }
2✔
613
    CHECK_NOT(did_fail);
2✔
614
}
2✔
615

616

617
TEST(Sync_Upload)
618
{
2✔
619
    TEST_DIR(dir);
2✔
620
    TEST_CLIENT_DB(db);
2✔
621
    ClientServerFixture fixture(dir, test_context);
2✔
622
    fixture.start();
2✔
623

624
    Session session = fixture.make_bound_session(db);
2✔
625

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

642

643
TEST(Sync_Replication)
644
{
2✔
645
    // Replicate changes in file 1 to file 2.
646

647
    TEST_CLIENT_DB(db_1);
2✔
648
    TEST_CLIENT_DB(db_2);
2✔
649

650
    {
2✔
651
        TEST_DIR(dir);
2✔
652
        ClientServerFixture fixture(dir, test_context);
2✔
653
        fixture.start();
2✔
654

655
        Session session_1 = fixture.make_bound_session(db_1);
2✔
656
        Session session_2 = fixture.make_session(db_2, "/test");
2✔
657

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

673
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
674
        session_2.wait_for_download_complete_or_client_stopped();
2✔
675
    }
2✔
676

677
    ReadTransaction rt_1(db_1);
2✔
678
    ReadTransaction rt_2(db_2);
2✔
679
    const Group& group_1 = rt_1;
2✔
680
    const Group& group_2 = rt_2;
2✔
681
    group_1.verify();
2✔
682
    group_2.verify();
2✔
683
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
684
    ConstTableRef table = group_1.get_table("class_foo");
2✔
685
    CHECK_EQUAL(100, table->size());
2✔
686
}
2✔
687

688

689
TEST(Sync_Merge)
690
{
2✔
691

692
    TEST_CLIENT_DB(db_1);
2✔
693
    TEST_CLIENT_DB(db_2);
2✔
694

695
    {
2✔
696
        TEST_DIR(dir);
2✔
697
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
698
        fixture.start();
2✔
699

700
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
701
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
702

703
        // Create schema on both clients.
704
        auto create_schema = [](DBRef db) {
4✔
705
            WriteTransaction wt(db);
4✔
706
            if (wt.has_table("class_foo"))
4✔
707
                return;
×
708
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
4✔
709
            table->add_column(type_Int, "i");
4✔
710
            wt.commit();
4✔
711
        };
4✔
712
        create_schema(db_1);
2✔
713
        create_schema(db_2);
2✔
714

715
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
716
            TableRef table = wt.get_table("class_foo");
2✔
717
            table->create_object_with_primary_key(1).set("i", 5);
2✔
718
            table->create_object_with_primary_key(2).set("i", 6);
2✔
719
        });
2✔
720
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
721
            TableRef table = wt.get_table("class_foo");
2✔
722
            table->create_object_with_primary_key(3).set("i", 7);
2✔
723
            table->create_object_with_primary_key(4).set("i", 8);
2✔
724
        });
2✔
725

726
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
727
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
728
        session_1.wait_for_download_complete_or_client_stopped();
2✔
729
        session_2.wait_for_download_complete_or_client_stopped();
2✔
730
    }
2✔
731

732
    ReadTransaction rt_1(db_1);
2✔
733
    ReadTransaction rt_2(db_2);
2✔
734
    const Group& group_1 = rt_1;
2✔
735
    const Group& group_2 = rt_2;
2✔
736
    group_1.verify();
2✔
737
    group_2.verify();
2✔
738
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
739
    ConstTableRef table = group_1.get_table("class_foo");
2✔
740
    CHECK_EQUAL(4, table->size());
2✔
741
}
2✔
742

743
struct ExpectChangesetError {
744
    unit_test::TestContext& test_context;
745
    MultiClientServerFixture& fixture;
746
    std::string expected_error;
747

748
    void operator()(ConnectionState state, util::Optional<ErrorInfo> error_info) const noexcept
749
    {
59✔
750
        if (state == ConnectionState::disconnected) {
59✔
751
            return;
×
752
        }
×
753
        if (!error_info)
59✔
754
            return;
47✔
755
        REALM_ASSERT(error_info);
12✔
756
        CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
12✔
757
        CHECK(!error_info->is_fatal);
12✔
758
        CHECK_EQUAL(error_info->status.reason(),
12✔
759
                    "Failed to transform received changeset: Schema mismatch: " + expected_error);
12✔
760
        fixture.stop();
12✔
761
    }
12✔
762
};
763

764
void test_schema_mismatch(unit_test::TestContext& test_context, util::FunctionRef<void(WriteTransaction&)> fn_1,
765
                          util::FunctionRef<void(WriteTransaction&)> fn_2, const char* expected_error_1,
766
                          const char* expected_error_2 = nullptr)
767
{
12✔
768
    auto perform_write_transaction = [](DBRef db, util::FunctionRef<void(WriteTransaction&)> function) {
24✔
769
        WriteTransaction wt(db);
24✔
770
        function(wt);
24✔
771
        return wt.commit();
24✔
772
    };
24✔
773

774
    TEST_DIR(dir);
12✔
775
    TEST_CLIENT_DB(db_1);
12✔
776
    TEST_CLIENT_DB(db_2);
12✔
777

778
    perform_write_transaction(db_1, fn_1);
12✔
779
    perform_write_transaction(db_2, fn_2);
12✔
780

781
    MultiClientServerFixture fixture(2, 1, dir, test_context);
12✔
782
    fixture.allow_server_errors(0, 1);
12✔
783
    fixture.start();
12✔
784

785
    if (!expected_error_2)
12✔
786
        expected_error_2 = expected_error_1;
4✔
787

788
    Session::Config config_1;
12✔
789
    config_1.connection_state_change_listener = ExpectChangesetError{test_context, fixture, expected_error_1};
12✔
790
    Session::Config config_2;
12✔
791
    config_2.connection_state_change_listener = ExpectChangesetError{test_context, fixture, expected_error_2};
12✔
792

793
    Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config_1));
12✔
794
    Session session_2 = fixture.make_session(1, 0, db_2, "/test", std::move(config_2));
12✔
795

796
    session_1.wait_for_upload_complete_or_client_stopped();
12✔
797
    session_2.wait_for_upload_complete_or_client_stopped();
12✔
798
    session_1.wait_for_download_complete_or_client_stopped();
12✔
799
    session_2.wait_for_download_complete_or_client_stopped();
12✔
800
}
12✔
801

802

803
TEST(Sync_DetectSchemaMismatch_ColumnType)
804
{
2✔
805
    test_schema_mismatch(
2✔
806
        test_context,
2✔
807
        [](WriteTransaction& wt) {
2✔
808
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
809
            ColKey col_ndx = table->add_column(type_Int, "column");
2✔
810
            table->create_object_with_primary_key(1).set<int64_t>(col_ndx, 123);
2✔
811
        },
2✔
812
        [](WriteTransaction& wt) {
2✔
813
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
814
            ColKey col_ndx = table->add_column(type_String, "column");
2✔
815
            table->create_object_with_primary_key(2).set(col_ndx, "Hello, World!");
2✔
816
        },
2✔
817
        "Property 'column' in class 'foo' is of type Int on one side and type String on the other.",
2✔
818
        "Property 'column' in class 'foo' is of type String on one side and type Int on the other.");
2✔
819
}
2✔
820

821

822
TEST(Sync_DetectSchemaMismatch_Nullability)
823
{
2✔
824
    test_schema_mismatch(
2✔
825
        test_context,
2✔
826
        [](WriteTransaction& wt) {
2✔
827
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
828
            bool nullable = false;
2✔
829
            ColKey col_ndx = table->add_column(type_Int, "column", nullable);
2✔
830
            table->create_object_with_primary_key(1).set<int64_t>(col_ndx, 123);
2✔
831
        },
2✔
832
        [](WriteTransaction& wt) {
2✔
833
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
834
            bool nullable = true;
2✔
835
            ColKey col_ndx = table->add_column(type_Int, "column", nullable);
2✔
836
            table->create_object_with_primary_key(2).set<int64_t>(col_ndx, 123);
2✔
837
        },
2✔
838
        "Property 'column' in class 'foo' is nullable on one side and not on the other.");
2✔
839
}
2✔
840

841

842
TEST(Sync_DetectSchemaMismatch_Links)
843
{
2✔
844
    test_schema_mismatch(
2✔
845
        test_context,
2✔
846
        [](WriteTransaction& wt) {
2✔
847
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
848
            TableRef target = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
849
            table->add_column(*target, "column");
2✔
850
        },
2✔
851
        [](WriteTransaction& wt) {
2✔
852
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
853
            TableRef target = wt.get_group().add_table_with_primary_key("class_baz", type_Int, "id");
2✔
854
            table->add_column(*target, "column");
2✔
855
        },
2✔
856
        "Link property 'column' in class 'foo' points to class 'bar' on one side and to 'baz' on the other.",
2✔
857
        "Link property 'column' in class 'foo' points to class 'baz' on one side and to 'bar' on the other.");
2✔
858
}
2✔
859

860

861
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Name)
862
{
2✔
863
    test_schema_mismatch(
2✔
864
        test_context,
2✔
865
        [](WriteTransaction& wt) {
2✔
866
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a");
2✔
867
        },
2✔
868
        [](WriteTransaction& wt) {
2✔
869
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "b");
2✔
870
        },
2✔
871
        "'foo' has primary key 'a' on one side, but primary key 'b' on the other.",
2✔
872
        "'foo' has primary key 'b' on one side, but primary key 'a' on the other.");
2✔
873
}
2✔
874

875

876
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Type)
877
{
2✔
878
    test_schema_mismatch(
2✔
879
        test_context,
2✔
880
        [](WriteTransaction& wt) {
2✔
881
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a");
2✔
882
        },
2✔
883
        [](WriteTransaction& wt) {
2✔
884
            wt.get_group().add_table_with_primary_key("class_foo", type_String, "a");
2✔
885
        },
2✔
886
        "'foo' has primary key 'a', which is of type Int on one side and type String on the other.",
2✔
887
        "'foo' has primary key 'a', which is of type String on one side and type Int on the other.");
2✔
888
}
2✔
889

890

891
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Nullability)
892
{
2✔
893
    test_schema_mismatch(
2✔
894
        test_context,
2✔
895
        [](WriteTransaction& wt) {
2✔
896
            bool nullable = false;
2✔
897
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a", nullable);
2✔
898
        },
2✔
899
        [](WriteTransaction& wt) {
2✔
900
            bool nullable = true;
2✔
901
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a", nullable);
2✔
902
        },
2✔
903
        "'foo' has primary key 'a', which is nullable on one side, but not the other.");
2✔
904
}
2✔
905

906

907
TEST(Sync_LateBind)
908
{
2✔
909
    // Test that a session can be initiated at a point in time where the client
910
    // already has established a connection to the server.
911

912
    TEST_CLIENT_DB(db_1);
2✔
913
    TEST_CLIENT_DB(db_2);
2✔
914

915
    {
2✔
916
        TEST_DIR(dir);
2✔
917
        ClientServerFixture fixture(dir, test_context);
2✔
918
        fixture.start();
2✔
919

920
        Session session_1 = fixture.make_bound_session(db_1);
2✔
921
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
922
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
923
        });
2✔
924
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
925

926
        Session session_2 = fixture.make_bound_session(db_2);
2✔
927
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
928
            wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
929
        });
2✔
930
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
931

932
        session_1.wait_for_download_complete_or_client_stopped();
2✔
933
        session_2.wait_for_download_complete_or_client_stopped();
2✔
934
    }
2✔
935

936
    ReadTransaction rt_1(db_1);
2✔
937
    ReadTransaction rt_2(db_2);
2✔
938
    const Group& group_1 = rt_1;
2✔
939
    const Group& group_2 = rt_2;
2✔
940
    group_1.verify();
2✔
941
    group_2.verify();
2✔
942
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
943
    CHECK_EQUAL(2, group_1.size());
2✔
944
}
2✔
945

946

947
TEST(Sync_EarlyUnbind)
948
{
2✔
949
    // Verify that it is possible to unbind one session while another session
950
    // keeps the connection to the server open.
951

952
    TEST_DIR(dir);
2✔
953
    TEST_CLIENT_DB(db_1);
2✔
954
    TEST_CLIENT_DB(db_2);
2✔
955
    TEST_CLIENT_DB(db_3);
2✔
956
    ClientServerFixture fixture(dir, test_context);
2✔
957
    fixture.start();
2✔
958

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

981

982
TEST(Sync_FastRebind)
983
{
2✔
984
    // Verify that it is possible to create multiple immediately consecutive
985
    // sessions for the same Realm file.
986

987
    TEST_DIR(dir);
2✔
988
    TEST_CLIENT_DB(db_1);
2✔
989
    TEST_CLIENT_DB(db_2);
2✔
990
    ClientServerFixture fixture(dir, test_context);
2✔
991
    fixture.start();
2✔
992

993
    // Session 1 is here only to keep the connection alive
994
    Session session_1 = fixture.make_bound_session(db_1, "/dummy");
2✔
995
    {
2✔
996
        Session session_2 = fixture.make_bound_session(db_2, "/test");
2✔
997
        WriteTransaction wt(db_2);
2✔
998
        TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
999
        table->add_column(type_Int, "i");
2✔
1000
        table->create_object_with_primary_key(1);
2✔
1001
        wt.commit();
2✔
1002
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1003
    }
2✔
1004
    for (int i = 0; i < 100; ++i) {
202✔
1005
        Session session_2 = fixture.make_bound_session(db_2, "/test");
200✔
1006
        WriteTransaction wt(db_2);
200✔
1007
        TableRef table = wt.get_table("class_foo");
200✔
1008
        table->begin()->set<int64_t>("i", i);
200✔
1009
        wt.commit();
200✔
1010
        session_2.wait_for_upload_complete_or_client_stopped();
200✔
1011
    }
200✔
1012
}
2✔
1013

1014

1015
TEST(Sync_UnbindBeforeActivation)
1016
{
2✔
1017
    // This test tries to make it likely that the server receives an UNBIND
1018
    // message for a session that is still not activated, i.e., before the
1019
    // server receives the IDENT message.
1020

1021
    TEST_DIR(dir);
2✔
1022
    TEST_CLIENT_DB(db_1);
2✔
1023
    TEST_CLIENT_DB(db_2);
2✔
1024
    ClientServerFixture fixture(dir, test_context);
2✔
1025
    fixture.start();
2✔
1026

1027
    // Session 1 is here only to keep the connection alive
1028
    Session session_1 = fixture.make_bound_session(db_1);
2✔
1029
    for (int i = 0; i < 1000; ++i) {
2,002✔
1030
        Session session_2 = fixture.make_bound_session(db_2);
2,000✔
1031
        session_2.wait_for_upload_complete_or_client_stopped();
2,000✔
1032
    }
2,000✔
1033
}
2✔
1034

1035

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

1038
// This test illustrates that our instruction set and merge rules
1039
// do not have higher order convergence. The final merge result depends
1040
// on the order with which the changesets reach the server. This example
1041
// employs three clients operating on the same state. The state consists
1042
// of two tables, "source" and "target". "source" has a link list pointing
1043
// to target. Target contains three rows 0, 1, and 2. Source contains one
1044
// row with a link list whose value is 2.
1045
//
1046
// The three clients produce changesets with client 1 having the earliest time
1047
// stamp, client 2 the middle time stamp, and client 3 the latest time stamp.
1048
// The clients produce the following changesets.
1049
//
1050
// client 1: target.move_last_over(0)
1051
// client 2: source.link_list.set(0, 0);
1052
// client 3: source.link_list.set(0, 1);
1053
//
1054
// In part a of the test, the order of the clients reaching the server is
1055
// 1, 2, 3. The result is an empty link list since the merge of client 1 and 2
1056
// produces a nullify link list instruction.
1057
//
1058
// In part b, the order of the clients reaching the server is 3, 1, 2. The
1059
// result is a link list of size 1, since client 3 wins due to having the
1060
// latest time stamp.
1061
//
1062
// If the "natural" peer to peer system of these merge rules were built, the
1063
// transition from server a to server b involves an insert link instruction. In
1064
// other words, the diff between two servers differing in the order of one
1065
// move_last_over and two link_list_set instructions is an insert instruction.
1066
// Repeated application of the pairwise merge rules would never produce this
1067
// result.
1068
//
1069
// The test is not run in general since it just checks that we do not have
1070
// higher order convergence, and the absence of higher order convergence is not
1071
// a desired feature in itself.
1072
TEST_IF(Sync_NonDeterministicMerge, false)
1073
{
1074
    TEST_DIR(dir);
1075
    TEST_CLIENT_DB(db_a1);
1076
    TEST_CLIENT_DB(db_a2);
1077
    TEST_CLIENT_DB(db_a3);
1078
    TEST_CLIENT_DB(db_b1);
1079
    TEST_CLIENT_DB(db_b2);
1080
    TEST_CLIENT_DB(db_b3);
1081

1082
    ClientServerFixture fixture{dir, test_context};
1083
    fixture.start();
1084

1085
    // Part a of the test.
1086
    {
1087
        WriteTransaction wt{db_a1};
1088

1089
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target");
1090
        ColKey col_ndx = table_target->add_column(type_Int, "value");
1091
        CHECK_EQUAL(col_ndx, 1);
1092
        Obj row0 = table_target->create_object_with_primary_key(i);
1093
        Obj row1 = table_target->create_object_with_primary_key(i);
1094
        Obj row2 = table_target->create_object_with_primary_key(i);
1095
        row0.set(col_ndx, 123);
1096
        row1.set(col_ndx, 456);
1097
        row2.set(col_ndx, 789);
1098

1099
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source");
1100
        col_ndx = table_source->add_column_link(type_LinkList, "target_link",
1101
                                                *table_target);
1102
        CHECK_EQUAL(col_ndx, 1);
1103
        Obj obj = table_source->create_object_with_primary_key(i);
1104
        auto ll = obj.get_linklist(col_ndx);
1105
        ll.insert(0, row2.get_key());
1106
        CHECK_EQUAL(ll.size(), 1);
1107
        wt.commit();
1108
    }
1109

1110
    {
1111
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1112
        session.wait_for_upload_complete_or_client_stopped();
1113
    }
1114

1115
    {
1116
        Session session = fixture.make_bound_session(db_a2, "/server-path-a");
1117
        session.wait_for_download_complete_or_client_stopped();
1118
    }
1119

1120
    {
1121
        Session session = fixture.make_bound_session(db_a3, "/server-path-a");
1122
        session.wait_for_download_complete_or_client_stopped();
1123
    }
1124

1125
    {
1126
        WriteTransaction wt{db_a1};
1127
        TableRef table = wt.get_table("class_target");
1128
        table->remove_object(table->begin());
1129
        CHECK_EQUAL(table->size(), 2);
1130
        wt.commit();
1131
    }
1132

1133
    {
1134
        WriteTransaction wt{db_a2};
1135
        TableRef table = wt.get_table("class_source");
1136
        auto ll = table->get_linklist(1, 0);
1137
        CHECK_EQUAL(ll->size(), 1);
1138
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1139
        ll->set(0, 0);
1140
        CHECK_EQUAL(ll->get(0).get_int(1), 123);
1141
        wt.commit();
1142
    }
1143

1144
    {
1145
        WriteTransaction wt{db_a3};
1146
        TableRef table = wt.get_table("class_source");
1147
        auto ll = table->get_linklist(1, 0);
1148
        CHECK_EQUAL(ll->size(), 1);
1149
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1150
        ll->set(0, 1);
1151
        CHECK_EQUAL(ll->get(0).get_int(1), 456);
1152
        wt.commit();
1153
    }
1154

1155
    {
1156
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1157
        session.wait_for_upload_complete_or_client_stopped();
1158
    }
1159

1160
    {
1161
        Session session = fixture.make_bound_session(db_a2, "/server-path-a");
1162
        session.wait_for_upload_complete_or_client_stopped();
1163
    }
1164

1165
    {
1166
        Session session = fixture.make_bound_session(db_a3, "/server-path-a");
1167
        session.wait_for_upload_complete_or_client_stopped();
1168
    }
1169

1170
    {
1171
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1172
        session.wait_for_download_complete_or_client_stopped();
1173
    }
1174

1175
    // Part b of the test.
1176
    {
1177
        WriteTransaction wt{db_b1};
1178

1179
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target");
1180
        ColKey col_ndx = table_target->add_column(type_Int, "value");
1181
        CHECK_EQUAL(col_ndx, 1);
1182
        table_target->create_object_with_primary_key(i);
1183
        table_target->create_object_with_primary_key(i);
1184
        table_target->create_object_with_primary_key(i);
1185
        table_target->begin()->set(col_ndx, 123);
1186
        table_target->get_object(1).set(col_ndx, 456);
1187
        table_target->get_object(2).set(col_ndx, 789);
1188

1189
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source");
1190
        col_ndx = table_source->add_column_link(type_LinkList, "target_link",
1191
                                                *table_target);
1192
        CHECK_EQUAL(col_ndx, 1);
1193
        table_source->create_object_with_primary_key(i);
1194
        auto ll = table_source->get_linklist(col_ndx, 0);
1195
        ll->insert(0, 2);
1196
        CHECK_EQUAL(ll->size(), 1);
1197
        wt.commit();
1198
    }
1199

1200
    {
1201
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1202
        session.wait_for_upload_complete_or_client_stopped();
1203
    }
1204

1205
    {
1206
        Session session = fixture.make_bound_session(db_b2, "/server-path-b");
1207
        session.wait_for_download_complete_or_client_stopped();
1208
    }
1209

1210
    {
1211
        Session session = fixture.make_bound_session(db_b3, "/server-path-b");
1212
        session.wait_for_download_complete_or_client_stopped();
1213
    }
1214

1215
    {
1216
        WriteTransaction wt{db_b1};
1217
        TableRef table = wt.get_table("class_target");
1218
        table->move_last_over(0);
1219
        CHECK_EQUAL(table->size(), 2);
1220
        wt.commit();
1221
    }
1222

1223
    {
1224
        WriteTransaction wt{db_b2};
1225
        TableRef table = wt.get_table("class_source");
1226
        auto ll = table->get_linklist(1, 0);
1227
        CHECK_EQUAL(ll->size(), 1);
1228
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1229
        ll->set(0, 0);
1230
        CHECK_EQUAL(ll->get(0).get_int(1), 123);
1231
        wt.commit();
1232
    }
1233

1234
    {
1235
        WriteTransaction wt{db_b3};
1236
        TableRef table = wt.get_table("class_source");
1237
        auto ll = table->get_linklist(1, 0);
1238
        CHECK_EQUAL(ll->size(), 1);
1239
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1240
        ll->set(0, 1);
1241
        CHECK_EQUAL(ll->get(0).get_int(1), 456);
1242
        wt.commit();
1243
    }
1244

1245
    // The crucial difference between part a and b is that client 3
1246
    // uploads it changes first in part b and last in part a.
1247
    {
1248
        Session session = fixture.make_bound_session(db_b3, "/server-path-b");
1249
        session.wait_for_upload_complete_or_client_stopped();
1250
    }
1251

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

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

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

1267

1268
    // Check the end result.
1269

1270
    size_t size_link_list_a;
1271
    size_t size_link_list_b;
1272

1273
    {
1274
        ReadTransaction wt{db_a1};
1275
        ConstTableRef table = wt.get_table("class_source");
1276
        auto ll = table->get_linklist(1, 0);
1277
        size_link_list_a = ll->size();
1278
    }
1279

1280
    {
1281
        ReadTransaction wt{db_b1};
1282
        ConstTableRef table = wt.get_table("class_source");
1283
        auto ll = table->get_linklist(1, 0);
1284
        size_link_list_b = ll->size();
1285
        CHECK_EQUAL(ll->size(), 1);
1286
    }
1287

1288
    // The final link list has size 0 in part a and size 1 in part b.
1289
    // These checks confirm that the OT system behaves as expected.
1290
    // The expected behavior is higher order divergence.
1291
    CHECK_EQUAL(size_link_list_a, 0);
1292
    CHECK_EQUAL(size_link_list_b, 1);
1293
    CHECK_NOT_EQUAL(size_link_list_a, size_link_list_b);
1294
}
1295
#endif // 0
1296

1297

1298
TEST(Sync_Randomized)
1299
{
2✔
1300
    constexpr size_t num_clients = 7;
2✔
1301

1302
    auto client_test_program = [](DBRef db) {
14✔
1303
        // Create the schema
1304
        write_transaction(db, [](WriteTransaction& wt) {
14✔
1305
            if (wt.has_table("class_foo"))
14✔
1306
                return;
×
1307
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
14✔
1308
            table->add_column(type_Int, "i");
14✔
1309
            table->create_object_with_primary_key(1);
14✔
1310
        });
14✔
1311

1312
        Random random(random_int<unsigned long>()); // Seed from slow global generator
14✔
1313
        for (int i = 0; i < 100; ++i) {
1,414✔
1314
            WriteTransaction wt(db);
1,400✔
1315
            if (random.chance(4, 5)) {
1,400✔
1316
                TableRef table = wt.get_table("class_foo");
1,110✔
1317
                if (random.chance(1, 5)) {
1,110✔
1318
                    table->create_object_with_primary_key(i);
206✔
1319
                }
206✔
1320
                int value = random.draw_int(-32767, 32767);
1,110✔
1321
                size_t row_ndx = random.draw_int_mod(table->size());
1,110✔
1322
                table->get_object(row_ndx).set("i", value);
1,110✔
1323
            }
1,110✔
1324
            wt.commit();
1,400✔
1325
        }
1,400✔
1326
    };
14✔
1327

1328
    TEST_DIR(dir);
2✔
1329
    MultiClientServerFixture fixture(num_clients, 1, dir, test_context);
2✔
1330
    fixture.start();
2✔
1331

1332
    std::unique_ptr<DBTestPathGuard> client_path_guards[num_clients];
2✔
1333
    DBRef client_shared_groups[num_clients];
2✔
1334
    for (size_t i = 0; i < num_clients; ++i) {
16✔
1335
        std::string suffix = util::format(".client_%1.realm", i);
14✔
1336
        std::string test_path = get_test_path(test_context.get_test_name(), suffix);
14✔
1337
        client_path_guards[i].reset(new DBTestPathGuard(test_path));
14✔
1338
        client_shared_groups[i] = DB::create(make_client_replication(), test_path);
14✔
1339
    }
14✔
1340

1341
    std::vector<Session> sessions(num_clients);
2✔
1342
    for (size_t i = 0; i < num_clients; ++i) {
16✔
1343
        auto db = client_shared_groups[i];
14✔
1344
        sessions[i] = fixture.make_session(int(i), 0, db, "/test");
14✔
1345
    }
14✔
1346

1347
    auto run_client_test_program = [&](size_t i) {
14✔
1348
        try {
14✔
1349
            client_test_program(client_shared_groups[i]);
14✔
1350
        }
14✔
1351
        catch (...) {
14✔
1352
            fixture.stop();
×
1353
            throw;
×
1354
        }
×
1355
    };
14✔
1356

1357
    ThreadWrapper client_program_threads[num_clients];
2✔
1358
    for (size_t i = 0; i < num_clients; ++i)
16✔
1359
        client_program_threads[i].start([=] {
14✔
1360
            run_client_test_program(i);
14✔
1361
        });
14✔
1362

1363
    for (size_t i = 0; i < num_clients; ++i)
16✔
1364
        CHECK(!client_program_threads[i].join());
14✔
1365

1366
    log("All client programs completed");
2✔
1367

1368
    // Wait until all local changes are uploaded, and acknowledged by the
1369
    // server.
1370
    for (size_t i = 0; i < num_clients; ++i)
16✔
1371
        sessions[i].wait_for_upload_complete_or_client_stopped();
14✔
1372

1373
    log("Everything uploaded");
2✔
1374

1375
    // Now wait for all previously uploaded changes to be downloaded by all
1376
    // others.
1377
    for (size_t i = 0; i < num_clients; ++i)
16✔
1378
        sessions[i].wait_for_download_complete_or_client_stopped();
14✔
1379

1380
    log("Everything downloaded");
2✔
1381

1382
    REALM_ASSERT(num_clients > 0);
2✔
1383
    ReadTransaction rt_0(client_shared_groups[0]);
2✔
1384
    rt_0.get_group().verify();
2✔
1385
    for (size_t i = 1; i < num_clients; ++i) {
14✔
1386
        ReadTransaction rt(client_shared_groups[i]);
12✔
1387
        rt.get_group().verify();
12✔
1388
        // Logger is guaranteed to be defined
1389
        CHECK(compare_groups(rt_0, rt, *test_context.logger));
12✔
1390
    }
12✔
1391
}
2✔
1392

1393
#ifdef REALM_DEBUG // Failure simulation only works in debug mode
1394

1395
TEST(Sync_ReadFailureSimulation)
1396
{
2✔
1397
    TEST_DIR(server_dir);
2✔
1398
    TEST_CLIENT_DB(db);
2✔
1399

1400
    // Check that read failure simulation works on the client-side
1401
    {
2✔
1402
        bool client_side_read_did_fail = false;
2✔
1403
        {
2✔
1404
            ClientServerFixture fixture(server_dir, test_context);
2✔
1405
            fixture.set_client_side_error_rate(1, 1); // 100% chance of failure
2✔
1406
            auto error_handler = [&](Status status, bool is_fatal) {
2✔
1407
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
2✔
1408
                CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read");
2✔
1409
                CHECK_NOT(is_fatal);
2✔
1410
                client_side_read_did_fail = true;
2✔
1411
                fixture.stop();
2✔
1412
            };
2✔
1413
            fixture.set_client_side_error_handler(error_handler);
2✔
1414
            Session session = fixture.make_bound_session(db, "/test");
2✔
1415
            fixture.start();
2✔
1416
            session.wait_for_download_complete_or_client_stopped();
2✔
1417
        }
2✔
1418
        CHECK(client_side_read_did_fail);
2✔
1419
    }
2✔
1420

1421
    // FIXME: Figure out a way to check that read failure simulation works on
1422
    // the server-side
1423
}
2✔
1424

1425
#endif // REALM_DEBUG
1426
TEST(Sync_FailingReadsOnClientSide)
1427
{
2✔
1428
    TEST_CLIENT_DB(db_1);
2✔
1429
    TEST_CLIENT_DB(db_2);
2✔
1430

1431
    {
2✔
1432
        TEST_DIR(dir);
2✔
1433
        ClientServerFixture fixture{dir, test_context};
2✔
1434
        fixture.set_client_side_error_rate(5, 100); // 5% chance of failure
2✔
1435
        auto error_handler = [&](Status status, bool is_fatal) {
458✔
1436
            if (CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read")) {
458✔
1437
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
458✔
1438
                CHECK_NOT(is_fatal);
458✔
1439
                fixture.cancel_reconnect_delay();
458✔
1440
            }
458✔
1441
        };
458✔
1442
        fixture.set_client_side_error_handler(error_handler);
2✔
1443
        fixture.start();
2✔
1444

1445
        Session session_1 = fixture.make_bound_session(db_1);
2✔
1446

1447
        Session session_2 = fixture.make_bound_session(db_2);
2✔
1448

1449
        write_transaction(db_1, [](WriteTransaction& wt) {
2✔
1450
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1451
            table->add_column(type_Int, "i");
2✔
1452
            table->create_object_with_primary_key(1);
2✔
1453
        });
2✔
1454
        write_transaction(db_2, [](WriteTransaction& wt) {
2✔
1455
            TableRef table = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
1456
            table->add_column(type_Int, "i");
2✔
1457
            table->create_object_with_primary_key(2);
2✔
1458
        });
2✔
1459
        for (int i = 0; i < 100; ++i) {
202✔
1460
            session_1.wait_for_upload_complete_or_client_stopped();
200✔
1461
            session_2.wait_for_upload_complete_or_client_stopped();
200✔
1462
            for (int i = 0; i < 10; ++i) {
2,200✔
1463
                write_transaction(db_1, [=](WriteTransaction& wt) {
2,000✔
1464
                    TableRef table = wt.get_table("class_foo");
2,000✔
1465
                    table->begin()->set("i", i);
2,000✔
1466
                });
2,000✔
1467
                write_transaction(db_2, [=](WriteTransaction& wt) {
2,000✔
1468
                    TableRef table = wt.get_table("class_bar");
2,000✔
1469
                    table->begin()->set("i", i);
2,000✔
1470
                });
2,000✔
1471
            }
2,000✔
1472
        }
200✔
1473
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1474
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1475
        session_1.wait_for_download_complete_or_client_stopped();
2✔
1476
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1477
    }
2✔
1478

1479
    ReadTransaction rt_1(db_1);
2✔
1480
    ReadTransaction rt_2(db_2);
2✔
1481
    const Group& group_1 = rt_1;
2✔
1482
    const Group& group_2 = rt_2;
2✔
1483
    group_1.verify();
2✔
1484
    group_2.verify();
2✔
1485
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
1486
}
2✔
1487

1488

1489
TEST(Sync_FailingReadsOnServerSide)
1490
{
2✔
1491
    TEST_CLIENT_DB(db_1);
2✔
1492
    TEST_CLIENT_DB(db_2);
2✔
1493

1494
    {
2✔
1495
        TEST_DIR(dir);
2✔
1496
        ClientServerFixture fixture{dir, test_context};
2✔
1497
        fixture.set_server_side_error_rate(5, 100); // 5% chance of failure
2✔
1498
        auto error_handler = [&](Status, bool is_fatal) {
578✔
1499
            CHECK_NOT(is_fatal);
578✔
1500
            fixture.cancel_reconnect_delay();
578✔
1501
        };
578✔
1502
        fixture.set_client_side_error_handler(error_handler);
2✔
1503
        fixture.start();
2✔
1504

1505
        Session session_1 = fixture.make_bound_session(db_1);
2✔
1506

1507
        Session session_2 = fixture.make_bound_session(db_2);
2✔
1508

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

1539
    ReadTransaction rt_1(db_1);
2✔
1540
    ReadTransaction rt_2(db_2);
2✔
1541
    const Group& group_1 = rt_1;
2✔
1542
    const Group& group_2 = rt_2;
2✔
1543
    group_1.verify();
2✔
1544
    group_2.verify();
2✔
1545
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
1546
}
2✔
1547

1548

1549
TEST(Sync_ErrorAfterServerRestore_BadClientFileIdent)
1550
{
2✔
1551
    TEST_DIR(server_dir);
2✔
1552
    TEST_CLIENT_DB(db);
2✔
1553

1554
    std::string server_path = "/test";
2✔
1555
    std::string server_realm_path;
2✔
1556

1557
    // Make a change and synchronize with server
1558
    {
2✔
1559
        ClientServerFixture fixture(server_dir, test_context);
2✔
1560
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1561
        Session session = fixture.make_bound_session(db, server_path);
2✔
1562
        WriteTransaction wt{db};
2✔
1563
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1564
        wt.commit();
2✔
1565
        fixture.start();
2✔
1566
        session.wait_for_upload_complete_or_client_stopped();
2✔
1567
    }
2✔
1568

1569
    // Emulate a server-side restore to before the creation of the Realm
1570
    util::File::remove(server_realm_path);
2✔
1571

1572
    // Provoke error by attempting to resynchronize
1573
    bool did_fail = false;
2✔
1574
    {
2✔
1575
        ClientServerFixture fixture(server_dir, test_context);
2✔
1576
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1577
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1578
            CHECK(is_fatal);
2✔
1579
            did_fail = true;
2✔
1580
            fixture.stop();
2✔
1581
        };
2✔
1582
        fixture.set_client_side_error_handler(error_handler);
2✔
1583
        Session session = fixture.make_bound_session(db, server_path);
2✔
1584
        fixture.start();
2✔
1585
        session.wait_for_download_complete_or_client_stopped();
2✔
1586
    }
2✔
1587
    CHECK(did_fail);
2✔
1588
}
2✔
1589

1590

1591
TEST(Sync_HTTP404NotFound)
1592
{
2✔
1593
    TEST_DIR(server_dir);
2✔
1594

1595
    std::string server_address = "localhost";
2✔
1596

1597
    Server::Config server_config;
2✔
1598
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
1599
    server_config.listen_address = server_address;
2✔
1600
    server_config.listen_port = "";
2✔
1601
    server_config.tcp_no_delay = true;
2✔
1602

1603
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
1604
    Server server(server_dir, std::move(public_key), server_config);
2✔
1605
    server.start();
2✔
1606
    network::Endpoint endpoint = server.listen_endpoint();
2✔
1607

1608
    ThreadWrapper server_thread;
2✔
1609
    server_thread.start([&] {
2✔
1610
        server.run();
2✔
1611
    });
2✔
1612

1613
    HTTPRequest request;
2✔
1614
    request.path = "/not-found";
2✔
1615

1616
    HTTPRequestClient client(test_context.logger, endpoint, request);
2✔
1617
    client.fetch_response();
2✔
1618

1619
    server.stop();
2✔
1620

1621
    server_thread.join();
2✔
1622

1623
    const HTTPResponse& response = client.get_response();
2✔
1624

1625
    CHECK(response.status == HTTPStatus::NotFound);
2✔
1626
    CHECK(response.headers.find("Server")->second == "RealmSync/" REALM_VERSION_STRING);
2✔
1627
}
2✔
1628

1629

1630
namespace {
1631

1632
class RequestWithContentLength {
1633
public:
1634
    RequestWithContentLength(test_util::unit_test::TestContext& test_context, network::Service& service,
1635
                             const network::Endpoint& endpoint, const std::string& content_length,
1636
                             const std::string& expected_response_line)
1637
        : test_context{test_context}
4✔
1638
        , m_socket{service}
4✔
1639
        , m_endpoint{endpoint}
4✔
1640
        , m_content_length{content_length}
4✔
1641
        , m_expected_response_line{expected_response_line}
4✔
1642
    {
8✔
1643
        m_request = "POST /does-not-exist-1234 HTTP/1.1\r\n"
8✔
1644
                    "Content-Length: " +
8✔
1645
                    m_content_length +
8✔
1646
                    "\r\n"
8✔
1647
                    "\r\n";
8✔
1648
    }
8✔
1649

1650
    void write_completion_handler(std::error_code ec, size_t nbytes)
1651
    {
8✔
1652
        CHECK_NOT(ec);
8✔
1653
        CHECK_EQUAL(m_request.size(), nbytes);
8✔
1654
        auto handler = [&](std::error_code ec, size_t nbytes) {
8✔
1655
            this->read_completion_handler(ec, nbytes);
8✔
1656
        };
8✔
1657
        m_socket.async_read_until(m_buffer, m_buf_size, '\n', m_read_ahead_buffer, handler);
8✔
1658
    }
8✔
1659

1660
    void read_completion_handler(std::error_code ec, size_t nbytes)
1661
    {
8✔
1662
        CHECK_NOT(ec);
8✔
1663
        std::string response_line{m_buffer, nbytes};
8✔
1664
        CHECK_EQUAL(response_line, m_expected_response_line);
8✔
1665
    }
8✔
1666

1667
    void start()
1668
    {
8✔
1669
        std::error_code ec;
8✔
1670
        m_socket.connect(m_endpoint, ec);
8✔
1671
        CHECK_NOT(ec);
8✔
1672

1673
        auto handler = [&](std::error_code ec, size_t nbytes) {
8✔
1674
            this->write_completion_handler(ec, nbytes);
8✔
1675
        };
8✔
1676
        m_socket.async_write(m_request.data(), m_request.size(), handler);
8✔
1677
    }
8✔
1678

1679
private:
1680
    test_util::unit_test::TestContext& test_context;
1681
    network::Socket m_socket;
1682
    network::ReadAheadBuffer m_read_ahead_buffer;
1683
    static constexpr size_t m_buf_size = 1000;
1684
    char m_buffer[m_buf_size];
1685
    const network::Endpoint& m_endpoint;
1686
    const std::string m_content_length;
1687
    std::string m_request;
1688
    const std::string m_expected_response_line;
1689
};
1690

1691
} // namespace
1692

1693
// Test the server's HTTP response to a Content-Length header of zero, empty,
1694
// and a non-number string.
1695
TEST(Sync_HTTP_ContentLength)
1696
{
2✔
1697
    TEST_DIR(server_dir);
2✔
1698

1699
    std::string server_address = "localhost";
2✔
1700

1701
    Server::Config server_config;
2✔
1702
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
1703
    server_config.listen_address = server_address;
2✔
1704
    server_config.listen_port = "";
2✔
1705
    server_config.tcp_no_delay = true;
2✔
1706

1707
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
1708
    Server server(server_dir, std::move(public_key), server_config);
2✔
1709
    server.start();
2✔
1710
    network::Endpoint endpoint = server.listen_endpoint();
2✔
1711

1712
    ThreadWrapper server_thread;
2✔
1713
    server_thread.start([&] {
2✔
1714
        server.run();
2✔
1715
    });
2✔
1716

1717
    network::Service service;
2✔
1718

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

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

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

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

1727
    req_0.start();
2✔
1728
    req_1.start();
2✔
1729
    req_2.start();
2✔
1730
    req_3.start();
2✔
1731

1732
    service.run();
2✔
1733

1734
    server.stop();
2✔
1735
    server_thread.join();
2✔
1736
}
2✔
1737

1738

1739
TEST(Sync_ErrorAfterServerRestore_BadServerVersion)
1740
{
2✔
1741
    TEST_DIR(server_dir);
2✔
1742
    TEST_DIR(backup_dir);
2✔
1743
    TEST_CLIENT_DB(db);
2✔
1744

1745
    std::string server_path = "/test";
2✔
1746
    std::string server_realm_path;
2✔
1747
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1748

1749
    // Create schema and synchronize with server
1750
    {
2✔
1751
        ClientServerFixture fixture(server_dir, test_context);
2✔
1752
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1753
        Session session = fixture.make_bound_session(db, server_path);
2✔
1754
        WriteTransaction wt{db};
2✔
1755
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1756
        table->add_column(type_Int, "column");
2✔
1757
        wt.commit();
2✔
1758
        fixture.start();
2✔
1759
        session.wait_for_upload_complete_or_client_stopped();
2✔
1760
    }
2✔
1761

1762
    // Save a snapshot of the server-side Realm file
1763
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1764

1765
    // Make change in which will be lost when restoring snapshot
1766
    {
2✔
1767
        ClientServerFixture fixture(server_dir, test_context);
2✔
1768
        Session session = fixture.make_bound_session(db, server_path);
2✔
1769
        WriteTransaction wt{db};
2✔
1770
        TableRef table = wt.get_table("class_table");
2✔
1771
        table->create_object_with_primary_key(1);
2✔
1772
        wt.commit();
2✔
1773
        fixture.start();
2✔
1774
        session.wait_for_upload_complete_or_client_stopped();
2✔
1775
    }
2✔
1776

1777
    // Restore the snapshot
1778
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1779

1780
    // Provoke error by resynchronizing
1781
    bool did_fail = false;
2✔
1782
    {
2✔
1783
        ClientServerFixture fixture(server_dir, test_context);
2✔
1784
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1785
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1786
            CHECK(is_fatal);
2✔
1787
            did_fail = true;
2✔
1788
            fixture.stop();
2✔
1789
        };
2✔
1790
        fixture.set_client_side_error_handler(error_handler);
2✔
1791
        Session session = fixture.make_bound_session(db, server_path);
2✔
1792
        fixture.start();
2✔
1793
        session.wait_for_download_complete_or_client_stopped();
2✔
1794
    }
2✔
1795
    CHECK(did_fail);
2✔
1796
}
2✔
1797

1798

1799
TEST(Sync_ErrorAfterServerRestore_BadClientVersion)
1800
{
2✔
1801
    TEST_DIR(server_dir);
2✔
1802
    TEST_DIR(backup_dir);
2✔
1803
    TEST_CLIENT_DB(db_1);
2✔
1804
    TEST_CLIENT_DB(db_2);
2✔
1805

1806
    std::string server_path = "/test";
2✔
1807
    std::string server_realm_path;
2✔
1808
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1809

1810
    // Create schema and synchronize client files
1811
    {
2✔
1812
        ClientServerFixture fixture(server_dir, test_context);
2✔
1813
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1814
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
1815
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
1816
        WriteTransaction wt{db_1};
2✔
1817
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1818
        table->add_column(type_Int, "column");
2✔
1819
        wt.commit();
2✔
1820
        fixture.start();
2✔
1821
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1822
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1823
    }
2✔
1824

1825
    // Save a snapshot of the server-side Realm file
1826
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1827

1828
    // Make change in 1st file which will be lost when restoring snapshot
1829
    {
2✔
1830
        ClientServerFixture fixture(server_dir, test_context);
2✔
1831
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1832
        WriteTransaction wt{db_1};
2✔
1833
        TableRef table = wt.get_table("class_table");
2✔
1834
        table->create_object_with_primary_key(1);
2✔
1835
        wt.commit();
2✔
1836
        fixture.start();
2✔
1837
        session.wait_for_upload_complete_or_client_stopped();
2✔
1838
    }
2✔
1839

1840
    // Restore the snapshot
1841
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1842

1843
    // Make a conflicting change in 2nd file relative to reverted server state
1844
    {
2✔
1845
        ClientServerFixture fixture(server_dir, test_context);
2✔
1846
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1847
        WriteTransaction wt{db_2};
2✔
1848
        TableRef table = wt.get_table("class_table");
2✔
1849
        table->create_object_with_primary_key(2);
2✔
1850
        wt.commit();
2✔
1851
        fixture.start();
2✔
1852
        session.wait_for_upload_complete_or_client_stopped();
2✔
1853
    }
2✔
1854

1855
    // Provoke error by synchronizing 1st file
1856
    bool did_fail = false;
2✔
1857
    {
2✔
1858
        ClientServerFixture fixture(server_dir, test_context);
2✔
1859
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1860
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1861
            CHECK(is_fatal);
2✔
1862
            did_fail = true;
2✔
1863
            fixture.stop();
2✔
1864
        };
2✔
1865
        fixture.set_client_side_error_handler(error_handler);
2✔
1866
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1867
        fixture.start();
2✔
1868
        session.wait_for_download_complete_or_client_stopped();
2✔
1869
    }
2✔
1870
    CHECK(did_fail);
2✔
1871
}
2✔
1872

1873

1874
TEST(Sync_ErrorAfterServerRestore_BadClientFileIdentSalt)
1875
{
2✔
1876
    TEST_DIR(server_dir);
2✔
1877
    TEST_DIR(backup_dir);
2✔
1878
    TEST_CLIENT_DB(db_1);
2✔
1879
    TEST_CLIENT_DB(db_2);
2✔
1880
    TEST_CLIENT_DB(db_3);
2✔
1881

1882
    std::string server_path = "/test";
2✔
1883
    std::string server_realm_path;
2✔
1884
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1885

1886
    // Register 1st file with server
1887
    {
2✔
1888
        ClientServerFixture fixture(server_dir, test_context);
2✔
1889
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1890
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1891
        WriteTransaction wt{db_1};
2✔
1892
        TableRef table = wt.get_group().add_table_with_primary_key("class_table_1", type_Int, "id");
2✔
1893
        table->add_column(type_Int, "column");
2✔
1894
        wt.commit();
2✔
1895
        fixture.start();
2✔
1896
        session.wait_for_upload_complete_or_client_stopped();
2✔
1897
    }
2✔
1898

1899
    // Save a snapshot of the server-side Realm file
1900
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1901

1902
    // Register 2nd file with server
1903
    {
2✔
1904
        ClientServerFixture fixture(server_dir, test_context);
2✔
1905
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1906
        fixture.start();
2✔
1907
        session.wait_for_download_complete_or_client_stopped();
2✔
1908
    }
2✔
1909

1910
    // Restore the snapshot
1911
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1912

1913
    // Register 3rd conflicting file with server
1914
    {
2✔
1915
        ClientServerFixture fixture(server_dir, test_context);
2✔
1916
        Session session = fixture.make_bound_session(db_3, server_path);
2✔
1917
        fixture.start();
2✔
1918
        session.wait_for_download_complete_or_client_stopped();
2✔
1919
    }
2✔
1920

1921
    // Provoke error by resynchronizing 2nd file
1922
    bool did_fail = false;
2✔
1923
    {
2✔
1924
        ClientServerFixture fixture(server_dir, test_context);
2✔
1925
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1926
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1927
            CHECK(is_fatal);
2✔
1928
            did_fail = true;
2✔
1929
            fixture.stop();
2✔
1930
        };
2✔
1931
        fixture.set_client_side_error_handler(error_handler);
2✔
1932
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1933
        fixture.start();
2✔
1934
        session.wait_for_download_complete_or_client_stopped();
2✔
1935
    }
2✔
1936
    CHECK(did_fail);
2✔
1937
}
2✔
1938

1939

1940
TEST(Sync_ErrorAfterServerRestore_BadServerVersionSalt)
1941
{
2✔
1942
    TEST_DIR(server_dir);
2✔
1943
    TEST_DIR(backup_dir);
2✔
1944
    TEST_CLIENT_DB(db_1);
2✔
1945
    TEST_CLIENT_DB(db_2);
2✔
1946
    TEST_CLIENT_DB(db_3);
2✔
1947

1948
    std::string server_path = "/test";
2✔
1949
    std::string server_realm_path;
2✔
1950
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1951

1952
    // Create schema and synchronize client files
1953
    {
2✔
1954
        ClientServerFixture fixture(server_dir, test_context);
2✔
1955
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1956
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
1957
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
1958
        Session session_3 = fixture.make_bound_session(db_3, server_path);
2✔
1959
        WriteTransaction wt{db_1};
2✔
1960
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1961
        table->add_column(type_Int, "column");
2✔
1962
        wt.commit();
2✔
1963
        fixture.start();
2✔
1964
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1965
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1966
        session_3.wait_for_download_complete_or_client_stopped();
2✔
1967
    }
2✔
1968

1969
    // Save a snapshot of the server-side Realm file
1970
    util::File::copy(server_realm_path, backup_realm_path);
2✔
1971

1972
    // Make change in 1st file which will be lost when restoring snapshot, and
1973
    // make 2nd file download it.
1974
    {
2✔
1975
        ClientServerFixture fixture(server_dir, test_context);
2✔
1976
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
1977
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
1978
        WriteTransaction wt{db_1};
2✔
1979
        TableRef table = wt.get_table("class_table");
2✔
1980
        table->create_object_with_primary_key(1);
2✔
1981
        wt.commit();
2✔
1982
        fixture.start();
2✔
1983
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1984
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1985
    }
2✔
1986

1987
    // Restore the snapshot
1988
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1989

1990
    // Make a conflicting change in 3rd file relative to reverted server state
1991
    {
2✔
1992
        ClientServerFixture fixture(server_dir, test_context);
2✔
1993
        Session session = fixture.make_bound_session(db_3, server_path);
2✔
1994
        WriteTransaction wt{db_3};
2✔
1995
        TableRef table = wt.get_table("class_table");
2✔
1996
        table->create_object_with_primary_key(2);
2✔
1997
        wt.commit();
2✔
1998
        fixture.start();
2✔
1999
        session.wait_for_upload_complete_or_client_stopped();
2✔
2000
    }
2✔
2001

2002
    // Provoke error by synchronizing 2nd file
2003
    bool did_fail = false;
2✔
2004
    {
2✔
2005
        ClientServerFixture fixture(server_dir, test_context);
2✔
2006
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
2007
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
2008
            CHECK(is_fatal);
2✔
2009
            did_fail = true;
2✔
2010
            fixture.stop();
2✔
2011
        };
2✔
2012
        fixture.set_client_side_error_handler(error_handler);
2✔
2013
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
2014
        fixture.start();
2✔
2015
        session.wait_for_download_complete_or_client_stopped();
2✔
2016
    }
2✔
2017
    CHECK(did_fail);
2✔
2018
}
2✔
2019

2020

2021
TEST(Sync_MultipleServers)
2022
{
2✔
2023
    // Check that a client can make lots of connection to lots of servers in a
2024
    // concurrent manner.
2025

2026
    const int num_servers = 2;
2✔
2027
    const int num_realms_per_server = 2;
2✔
2028
    const int num_files_per_realm = 4;
2✔
2029
    const int num_sessions_per_file = 8;
2✔
2030
    const int num_transacts_per_session = 2;
2✔
2031

2032
    TEST_DIR(dir);
2✔
2033
    int num_clients = 1;
2✔
2034
    MultiClientServerFixture fixture(num_clients, num_servers, dir, test_context);
2✔
2035
    fixture.start();
2✔
2036

2037
    TEST_DIR(dir_2);
2✔
2038
    auto get_file_path = [&](int server_index, int realm_index, int file_index) {
94✔
2039
        std::ostringstream out;
94✔
2040
        out << server_index << "_" << realm_index << "_" << file_index << ".realm";
94✔
2041
        return util::File::resolve(out.str(), dir_2);
94✔
2042
    };
94✔
2043
    std::atomic<int> id = 0;
2✔
2044

2045
    auto run = [&](int server_index, int realm_index, int file_index) {
31✔
2046
        try {
31✔
2047
            std::string path = get_file_path(server_index, realm_index, file_index);
31✔
2048
            DBRef db = DB::create(make_client_replication(), path);
31✔
2049
            {
31✔
2050
                WriteTransaction wt(db);
31✔
2051
                TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
31✔
2052
                table->add_column(type_Int, "server_index");
31✔
2053
                table->add_column(type_Int, "realm_index");
31✔
2054
                table->add_column(type_Int, "file_index");
31✔
2055
                table->add_column(type_Int, "session_index");
31✔
2056
                table->add_column(type_Int, "transact_index");
31✔
2057
                wt.commit();
31✔
2058
            }
31✔
2059
            std::string server_path = "/" + std::to_string(realm_index);
31✔
2060
            for (int i = 0; i < num_sessions_per_file; ++i) {
287✔
2061
                int client_index = 0;
256✔
2062
                Session session = fixture.make_session(client_index, server_index, db, server_path);
256✔
2063
                for (int j = 0; j < num_transacts_per_session; ++j) {
768✔
2064
                    WriteTransaction wt(db);
512✔
2065
                    TableRef table = wt.get_table("class_table");
512✔
2066
                    Obj obj = table->create_object_with_primary_key(id.fetch_add(1));
512✔
2067
                    obj.set("server_index", server_index);
512✔
2068
                    obj.set("realm_index", realm_index);
512✔
2069
                    obj.set("file_index", file_index);
512✔
2070
                    obj.set("session_index", i);
512✔
2071
                    obj.set("transact_index", j);
512✔
2072
                    wt.commit();
512✔
2073
                }
512✔
2074
                session.wait_for_upload_complete_or_client_stopped();
256✔
2075
            }
256✔
2076
        }
31✔
2077
        catch (...) {
31✔
2078
            fixture.stop();
×
2079
            throw;
×
2080
        }
×
2081
    };
31✔
2082

2083
    auto finish_download = [&](int server_index, int realm_index, int file_index) {
32✔
2084
        try {
32✔
2085
            int client_index = 0;
32✔
2086
            std::string path = get_file_path(server_index, realm_index, file_index);
32✔
2087
            DBRef db = DB::create(make_client_replication(), path);
32✔
2088
            std::string server_path = "/" + std::to_string(realm_index);
32✔
2089
            Session session = fixture.make_session(client_index, server_index, db, server_path);
32✔
2090
            session.wait_for_download_complete_or_client_stopped();
32✔
2091
        }
32✔
2092
        catch (...) {
32✔
2093
            fixture.stop();
×
2094
            throw;
×
2095
        }
×
2096
    };
32✔
2097

2098
    // Make and upload changes
2099
    {
2✔
2100
        ThreadWrapper threads[num_servers][num_realms_per_server][num_files_per_realm];
2✔
2101
        for (int i = 0; i < num_servers; ++i) {
6✔
2102
            for (int j = 0; j < num_realms_per_server; ++j) {
12✔
2103
                for (int k = 0; k < num_files_per_realm; ++k)
40✔
2104
                    threads[i][j][k].start([=] {
32✔
2105
                        run(i, j, k);
31✔
2106
                    });
31✔
2107
            }
8✔
2108
        }
4✔
2109
        for (size_t i = 0; i < num_servers; ++i) {
6✔
2110
            for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2111
                for (size_t k = 0; k < num_files_per_realm; ++k)
40✔
2112
                    CHECK_NOT(threads[i][j][k].join());
32✔
2113
            }
8✔
2114
        }
4✔
2115
    }
2✔
2116

2117
    // Finish downloading
2118
    {
2✔
2119
        ThreadWrapper threads[num_servers][num_realms_per_server][num_files_per_realm];
2✔
2120
        for (int i = 0; i < num_servers; ++i) {
6✔
2121
            for (int j = 0; j < num_realms_per_server; ++j) {
12✔
2122
                for (int k = 0; k < num_files_per_realm; ++k)
40✔
2123
                    threads[i][j][k].start([=] {
32✔
2124
                        finish_download(i, j, k);
32✔
2125
                    });
32✔
2126
            }
8✔
2127
        }
4✔
2128
        for (size_t i = 0; i < num_servers; ++i) {
6✔
2129
            for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2130
                for (size_t k = 0; k < num_files_per_realm; ++k)
40✔
2131
                    CHECK_NOT(threads[i][j][k].join());
32✔
2132
            }
8✔
2133
        }
4✔
2134
    }
2✔
2135

2136
    // Check that all client side Realms have been correctly synchronized
2137
    std::set<std::tuple<int, int, int>> expected_rows;
2✔
2138
    for (int i = 0; i < num_files_per_realm; ++i) {
10✔
2139
        for (int j = 0; j < num_sessions_per_file; ++j) {
72✔
2140
            for (int k = 0; k < num_transacts_per_session; ++k)
192✔
2141
                expected_rows.emplace(i, j, k);
128✔
2142
        }
64✔
2143
    }
8✔
2144
    for (size_t i = 0; i < num_servers; ++i) {
6✔
2145
        for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2146
            REALM_ASSERT(num_files_per_realm > 0);
8✔
2147
            int file_index_0 = 0;
8✔
2148
            std::string path_0 = get_file_path(int(i), int(j), file_index_0);
8✔
2149
            std::unique_ptr<Replication> history_0 = make_client_replication();
8✔
2150
            DBRef db_0 = DB::create(*history_0, path_0);
8✔
2151
            ReadTransaction rt_0(db_0);
8✔
2152
            {
8✔
2153
                ConstTableRef table = rt_0.get_table("class_table");
8✔
2154
                if (CHECK(table)) {
8✔
2155
                    std::set<std::tuple<int, int, int>> rows;
8✔
2156
                    for (const Obj& obj : *table) {
512✔
2157
                        int server_index = int(obj.get<int64_t>("server_index"));
512✔
2158
                        int realm_index = int(obj.get<int64_t>("realm_index"));
512✔
2159
                        int file_index = int(obj.get<int64_t>("file_index"));
512✔
2160
                        int session_index = int(obj.get<int64_t>("session_index"));
512✔
2161
                        int transact_index = int(obj.get<int64_t>("transact_index"));
512✔
2162
                        CHECK_EQUAL(i, server_index);
512✔
2163
                        CHECK_EQUAL(j, realm_index);
512✔
2164
                        rows.emplace(file_index, session_index, transact_index);
512✔
2165
                    }
512✔
2166
                    CHECK(rows == expected_rows);
8✔
2167
                }
8✔
2168
            }
8✔
2169
            for (int k = 1; k < num_files_per_realm; ++k) {
32✔
2170
                std::string path = get_file_path(int(i), int(j), k);
24✔
2171
                DBRef db = DB::create(make_client_replication(), path);
24✔
2172
                ReadTransaction rt(db);
24✔
2173
                CHECK(compare_groups(rt_0, rt));
24✔
2174
            }
24✔
2175
        }
8✔
2176
    }
4✔
2177
}
2✔
2178

2179

2180
TEST_IF(Sync_ReadOnlyClient, false)
2181
{
×
2182
    TEST_CLIENT_DB(db_1);
×
2183
    TEST_CLIENT_DB(db_2);
×
2184

2185
    TEST_DIR(server_dir);
×
2186
    MultiClientServerFixture fixture(2, 1, server_dir, test_context);
×
2187
    bool did_get_permission_denied = false;
×
2188
    fixture.set_client_side_error_handler(1, [&](Status status, bool) {
×
2189
        CHECK_EQUAL(status, ErrorCodes::SyncPermissionDenied);
×
2190
        did_get_permission_denied = true;
×
2191
        fixture.get_client(1).shutdown();
×
2192
    });
×
2193
    fixture.start();
×
2194

2195
    // Write some stuff from the client that can upload
2196
    {
×
2197
        Session session_1 = fixture.make_bound_session(0, db_1, 0, "/test");
×
2198
        WriteTransaction wt(db_1);
×
2199
        auto table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
×
2200
        table->add_column(type_Int, "i");
×
2201
        table->create_object_with_primary_key(1);
×
2202
        table->begin()->set("i", 123);
×
2203
        wt.commit();
×
2204
        session_1.wait_for_upload_complete_or_client_stopped();
×
2205
    }
×
2206

2207
    // Check that the stuff was received on the read-only client
2208
    {
×
2209
        Session session_2 = fixture.make_bound_session(1, db_2, 0, "/test", g_signed_test_user_token_readonly);
×
2210
        session_2.wait_for_download_complete_or_client_stopped();
×
2211
        {
×
2212
            ReadTransaction rt(db_2);
×
2213
            auto table = rt.get_table("class_foo");
×
2214
            CHECK_EQUAL(table->begin()->get<Int>("i"), 123);
×
2215
        }
×
2216
        // Try to upload something
2217
        {
×
2218
            WriteTransaction wt(db_2);
×
2219
            auto table = wt.get_table("class_foo");
×
2220
            table->begin()->set("i", 456);
×
2221
            wt.commit();
×
2222
        }
×
2223
        session_2.wait_for_upload_complete_or_client_stopped();
×
2224
        CHECK(did_get_permission_denied);
×
2225
    }
×
2226

2227
    // Check that the original client was unchanged
2228
    {
×
2229
        Session session_1 = fixture.make_bound_session(0, db_1, 0, "/test");
×
2230
        session_1.wait_for_download_complete_or_client_stopped();
×
2231
        ReadTransaction rt(db_1);
×
2232
        auto table = rt.get_table("class_foo");
×
2233
        CHECK_EQUAL(table->begin()->get<Int>("i"), 123);
×
2234
    }
×
2235
}
×
2236

2237

2238
// This test is a performance study. A single client keeps creating
2239
// transactions that creates new objects and uploads them. The time to perform
2240
// upload completion is measured and logged at info level.
2241
TEST(Sync_SingleClientUploadForever_CreateObjects)
2242
{
2✔
2243
    int_fast32_t number_of_transactions = 100; // Set to low number in ordinary testing.
2✔
2244

2245
    util::Logger& logger = *test_context.logger;
2✔
2246

2247
    logger.info("Sync_SingleClientUploadForever_CreateObjects test. Number of transactions = %1",
2✔
2248
                number_of_transactions);
2✔
2249

2250
    TEST_DIR(server_dir);
2✔
2251
    TEST_CLIENT_DB(db);
2✔
2252

2253
    ClientServerFixture fixture(server_dir, test_context);
2✔
2254
    fixture.start();
2✔
2255

2256
    ColKey col_int;
2✔
2257
    ColKey col_str;
2✔
2258
    ColKey col_dbl;
2✔
2259
    ColKey col_time;
2✔
2260

2261
    {
2✔
2262
        WriteTransaction wt{db};
2✔
2263
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2264
        col_int = tr->add_column(type_Int, "integer column");
2✔
2265
        col_str = tr->add_column(type_String, "string column");
2✔
2266
        col_dbl = tr->add_column(type_Double, "double column");
2✔
2267
        col_time = tr->add_column(type_Timestamp, "timestamp column");
2✔
2268
        wt.commit();
2✔
2269
    }
2✔
2270

2271
    Session session = fixture.make_bound_session(db);
2✔
2272
    session.wait_for_upload_complete_or_client_stopped();
2✔
2273

2274
    for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
202✔
2275
        WriteTransaction wt{db};
200✔
2276
        TableRef tr = wt.get_table("class_table");
200✔
2277
        auto obj = tr->create_object_with_primary_key(i);
200✔
2278
        int_fast32_t number = i;
200✔
2279
        obj.set<Int>(col_int, number);
200✔
2280
        std::string str = "str: " + std::to_string(number);
200✔
2281
        StringData str_data = StringData(str);
200✔
2282
        obj.set(col_str, str_data);
200✔
2283
        obj.set(col_dbl, double(number));
200✔
2284
        obj.set(col_time, Timestamp{123, 456});
200✔
2285
        wt.commit();
200✔
2286
        auto before_upload = std::chrono::steady_clock::now();
200✔
2287
        session.wait_for_upload_complete_or_client_stopped();
200✔
2288
        auto after_upload = std::chrono::steady_clock::now();
200✔
2289

2290
        // We only log the duration every 1000 transactions. The duration is for a single changeset.
2291
        if (i % 1000 == 0) {
200✔
2292
            auto duration =
2✔
2293
                std::chrono::duration_cast<std::chrono::milliseconds>(after_upload - before_upload).count();
2✔
2294
            logger.info("Duration of single changeset upload(%1) = %2 ms", i, duration);
2✔
2295
        }
2✔
2296
    }
200✔
2297
}
2✔
2298

2299

2300
// This test is a performance study. A single client keeps creating
2301
// transactions that changes the value of an existing object and uploads them.
2302
// The time to perform upload completion is measured and logged at info level.
2303
TEST(Sync_SingleClientUploadForever_MutateObject)
2304
{
2✔
2305
    int_fast32_t number_of_transactions = 100; // Set to low number in ordinary testing.
2✔
2306

2307
    util::Logger& logger = *test_context.logger;
2✔
2308

2309
    logger.info("Sync_SingleClientUploadForever_MutateObject test. Number of transactions = %1",
2✔
2310
                number_of_transactions);
2✔
2311

2312
    TEST_DIR(server_dir);
2✔
2313
    TEST_CLIENT_DB(db);
2✔
2314

2315
    ClientServerFixture fixture(server_dir, test_context);
2✔
2316
    fixture.start();
2✔
2317

2318
    ColKey col_int;
2✔
2319
    ColKey col_str;
2✔
2320
    ColKey col_dbl;
2✔
2321
    ColKey col_time;
2✔
2322
    ObjKey obj_key;
2✔
2323

2324
    {
2✔
2325
        WriteTransaction wt{db};
2✔
2326
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2327
        col_int = tr->add_column(type_Int, "integer column");
2✔
2328
        col_str = tr->add_column(type_String, "string column");
2✔
2329
        col_dbl = tr->add_column(type_Double, "double column");
2✔
2330
        col_time = tr->add_column(type_Timestamp, "timestamp column");
2✔
2331
        obj_key = tr->create_object_with_primary_key(1).get_key();
2✔
2332
        wt.commit();
2✔
2333
    }
2✔
2334

2335
    Session session = fixture.make_bound_session(db);
2✔
2336
    session.wait_for_upload_complete_or_client_stopped();
2✔
2337

2338
    for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
202✔
2339
        WriteTransaction wt{db};
200✔
2340
        TableRef tr = wt.get_table("class_table");
200✔
2341
        int_fast32_t number = i;
200✔
2342
        auto obj = tr->get_object(obj_key);
200✔
2343
        obj.set<Int>(col_int, number);
200✔
2344
        std::string str = "str: " + std::to_string(number);
200✔
2345
        StringData str_data = StringData(str);
200✔
2346
        obj.set(col_str, str_data);
200✔
2347
        obj.set(col_dbl, double(number));
200✔
2348
        obj.set(col_time, Timestamp{123, 456});
200✔
2349
        wt.commit();
200✔
2350
        auto before_upload = std::chrono::steady_clock::now();
200✔
2351
        session.wait_for_upload_complete_or_client_stopped();
200✔
2352
        auto after_upload = std::chrono::steady_clock::now();
200✔
2353

2354
        // We only log the duration every 1000 transactions. The duration is for a single changeset.
2355
        if (i % 1000 == 0) {
200✔
2356
            auto duration =
2✔
2357
                std::chrono::duration_cast<std::chrono::milliseconds>(after_upload - before_upload).count();
2✔
2358
            logger.info("Duration of single changeset upload(%1) = %2 ms", i, duration);
2✔
2359
        }
2✔
2360
    }
200✔
2361
}
2✔
2362

2363

2364
// This test is used to time upload and download.
2365
// The test might be moved to a performance test directory later.
2366
TEST(Sync_LargeUploadDownloadPerformance)
2367
{
2✔
2368
    int_fast32_t number_of_transactions = 2;         // Set to low number in ordinary testing.
2✔
2369
    int_fast32_t number_of_rows_per_transaction = 5; // Set to low number in ordinary testing.
2✔
2370
    int number_of_download_clients = 1;              // Set to low number in ordinary testing
2✔
2371
    bool print_durations = false;                    // Set to false in ordinary testing.
2✔
2372

2373
    if (print_durations) {
2✔
2374
        std::cerr << "Number of transactions = " << number_of_transactions << std::endl;
×
2375
        std::cerr << "Number of rows per transaction = " << number_of_rows_per_transaction << std::endl;
×
2376
        std::cerr << "Number of download clients = " << number_of_download_clients << std::endl;
×
2377
    }
×
2378

2379
    TEST_DIR(server_dir);
2✔
2380
    ClientServerFixture fixture(server_dir, test_context);
2✔
2381
    fixture.start();
2✔
2382

2383
    TEST_CLIENT_DB(db_upload);
2✔
2384

2385
    // Populate path_upload realm with data.
2386
    auto start_data_creation = std::chrono::steady_clock::now();
2✔
2387
    {
2✔
2388
        {
2✔
2389
            WriteTransaction wt{db_upload};
2✔
2390
            TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2391
            tr->add_column(type_Int, "integer column");
2✔
2392
            tr->add_column(type_String, "string column");
2✔
2393
            tr->add_column(type_Double, "double column");
2✔
2394
            tr->add_column(type_Timestamp, "timestamp column");
2✔
2395
            wt.commit();
2✔
2396
        }
2✔
2397

2398
        for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
6✔
2399
            WriteTransaction wt{db_upload};
4✔
2400
            TableRef tr = wt.get_table("class_table");
4✔
2401
            for (int_fast32_t j = 0; j < number_of_rows_per_transaction; ++j) {
24✔
2402
                Obj obj = tr->create_object_with_primary_key(i);
20✔
2403
                int_fast32_t number = i * number_of_rows_per_transaction + j;
20✔
2404
                obj.set("integer column", number);
20✔
2405
                std::string str = "str: " + std::to_string(number);
20✔
2406
                StringData str_data = StringData(str);
20✔
2407
                obj.set("string column", str_data);
20✔
2408
                obj.set("double column", double(number));
20✔
2409
                obj.set("timestamp column", Timestamp{123, 456});
20✔
2410
            }
20✔
2411
            wt.commit();
4✔
2412
        }
4✔
2413
    }
2✔
2414
    auto end_data_creation = std::chrono::steady_clock::now();
2✔
2415
    auto duration_data_creation =
2✔
2416
        std::chrono::duration_cast<std::chrono::milliseconds>(end_data_creation - start_data_creation).count();
2✔
2417
    if (print_durations)
2✔
2418
        std::cerr << "Duration of data creation = " << duration_data_creation << " ms" << std::endl;
×
2419

2420
    // Upload the data.
2421
    auto start_session_upload = std::chrono::steady_clock::now();
2✔
2422

2423
    Session session_upload = fixture.make_bound_session(db_upload);
2✔
2424
    session_upload.wait_for_upload_complete_or_client_stopped();
2✔
2425

2426
    auto end_session_upload = std::chrono::steady_clock::now();
2✔
2427
    auto duration_upload =
2✔
2428
        std::chrono::duration_cast<std::chrono::milliseconds>(end_session_upload - start_session_upload).count();
2✔
2429
    if (print_durations)
2✔
2430
        std::cerr << "Duration of uploading = " << duration_upload << " ms" << std::endl;
×
2431

2432

2433
    // Download the data to the download realms.
2434
    auto start_sesion_download = std::chrono::steady_clock::now();
2✔
2435

2436
    std::vector<DBTestPathGuard> shared_group_test_path_guards;
2✔
2437
    std::vector<DBRef> dbs;
2✔
2438
    std::vector<Session> sessions;
2✔
2439

2440
    for (int i = 0; i < number_of_download_clients; ++i) {
4✔
2441
        std::string path = get_test_path(test_context.get_test_name(), std::to_string(i));
2✔
2442
        shared_group_test_path_guards.emplace_back(path);
2✔
2443
        dbs.push_back(DB::create(make_client_replication(), path));
2✔
2444
        sessions.push_back(fixture.make_bound_session(dbs.back()));
2✔
2445
    }
2✔
2446

2447
    // Wait for all Realms to finish. They might finish in another order than
2448
    // started, but calling download_complete on a client after it finished only
2449
    // adds a tiny amount of extra mark messages.
2450
    for (auto& session : sessions)
2✔
2451
        session.wait_for_download_complete_or_client_stopped();
2✔
2452

2453

2454
    auto end_session_download = std::chrono::steady_clock::now();
2✔
2455
    auto duration_download =
2✔
2456
        std::chrono::duration_cast<std::chrono::milliseconds>(end_session_download - start_sesion_download).count();
2✔
2457
    if (print_durations)
2✔
2458
        std::cerr << "Duration of downloading = " << duration_download << " ms" << std::endl;
×
2459

2460

2461
    // Check convergence.
2462
    for (int i = 0; i < number_of_download_clients; ++i) {
4✔
2463
        ReadTransaction rt_1(db_upload);
2✔
2464
        ReadTransaction rt_2(dbs[i]);
2✔
2465
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
2466
    }
2✔
2467
}
2✔
2468

2469

2470
// This test creates a changeset that is larger than 4GB, uploads it and downloads it to another client.
2471
// The test checks that compression and other aspects of large changeset handling works.
2472
// The test is disabled since it requires a powerful machine to run.
2473
TEST_IF(Sync_4GB_Messages, false)
2474
{
×
2475
    // The changeset will be slightly larger.
2476
    const uint64_t approximate_changeset_size = uint64_t(1) << 32;
×
2477

2478
    TEST_DIR(dir);
×
2479
    TEST_CLIENT_DB(db_1);
×
2480
    TEST_CLIENT_DB(db_2);
×
2481
    ClientServerFixture fixture(dir, test_context);
×
2482
    fixture.start();
×
2483

2484
    Session session_1 = fixture.make_bound_session(db_1);
×
2485
    session_1.wait_for_download_complete_or_client_stopped();
×
2486

2487
    Session session_2 = fixture.make_bound_session(db_2);
×
2488
    session_2.wait_for_download_complete_or_client_stopped();
×
2489

2490
    const size_t single_object_data_size = size_t(1e7); // 10 MB which is below the 16 MB limit
×
2491
    const int num_objects = approximate_changeset_size / single_object_data_size + 1;
×
2492

2493
    const std::string str_a(single_object_data_size, 'a');
×
2494
    BinaryData bd_a(str_a.data(), single_object_data_size);
×
2495

2496
    const std::string str_b(single_object_data_size, 'b');
×
2497
    BinaryData bd_b(str_b.data(), single_object_data_size);
×
2498

2499
    const std::string str_c(single_object_data_size, 'c');
×
2500
    BinaryData bd_c(str_c.data(), single_object_data_size);
×
2501

2502
    {
×
2503
        WriteTransaction wt{db_1};
×
2504

2505
        TableRef tr = wt.get_group().add_table_with_primary_key("class_simple_data", type_Int, "id");
×
2506
        auto col_key = tr->add_column(type_Binary, "binary column");
×
2507
        for (int i = 0; i < num_objects; ++i) {
×
2508
            Obj obj = tr->create_object_with_primary_key(i);
×
2509
            switch (i % 3) {
×
2510
                case 0:
×
2511
                    obj.set(col_key, bd_a);
×
2512
                    break;
×
2513
                case 1:
×
2514
                    obj.set(col_key, bd_b);
×
2515
                    break;
×
2516
                default:
×
2517
                    obj.set(col_key, bd_c);
×
2518
            }
×
2519
        }
×
2520
        wt.commit();
×
2521
    }
×
2522
    session_1.wait_for_upload_complete_or_client_stopped();
×
2523
    session_2.wait_for_download_complete_or_client_stopped();
×
2524

2525
    // Check convergence.
2526
    {
×
2527
        ReadTransaction rt_1(db_1);
×
2528
        ReadTransaction rt_2(db_2);
×
2529
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
×
2530
    }
×
2531
}
×
2532

2533

2534
TEST(Sync_RefreshSignedUserToken)
2535
{
2✔
2536
    TEST_DIR(dir);
2✔
2537
    TEST_CLIENT_DB(db);
2✔
2538
    ClientServerFixture fixture(dir, test_context);
2✔
2539
    fixture.start();
2✔
2540

2541
    Session session = fixture.make_bound_session(db);
2✔
2542
    session.wait_for_download_complete_or_client_stopped();
2✔
2543
    session.refresh(g_signed_test_user_token);
2✔
2544
    session.wait_for_download_complete_or_client_stopped();
2✔
2545
}
2✔
2546

2547

2548
// This test refreshes the user token multiple times right after binding
2549
// the session. The test tries to achieve a situation where a session is
2550
// enlisted to send after sending BIND but before receiving ALLOC.
2551
// The token is refreshed multiple times to increase the probability that the
2552
// refresh took place after BIND. The check of the test is just the absence of
2553
// errors.
2554
TEST(Sync_RefreshRightAfterBind)
2555
{
2✔
2556
    TEST_DIR(dir);
2✔
2557
    TEST_CLIENT_DB(db);
2✔
2558
    ClientServerFixture fixture(dir, test_context);
2✔
2559
    fixture.start();
2✔
2560

2561
    Session session = fixture.make_bound_session(db);
2✔
2562
    for (int i = 0; i < 50; ++i) {
102✔
2563
        session.refresh(g_signed_test_user_token_readonly);
100✔
2564
        std::this_thread::sleep_for(std::chrono::milliseconds{1});
100✔
2565
    }
100✔
2566
    session.wait_for_download_complete_or_client_stopped();
2✔
2567
}
2✔
2568

2569

2570
TEST(Sync_Permissions)
2571
{
2✔
2572
    TEST_CLIENT_DB(db_valid);
2✔
2573

2574
    bool did_see_error_for_valid = false;
2✔
2575

2576
    TEST_DIR(server_dir);
2✔
2577

2578
    ClientServerFixture fixture{server_dir, test_context};
2✔
2579
    fixture.set_client_side_error_handler([&](Status status, bool) {
2✔
2580
        CHECK_EQUAL("", status.reason());
×
2581
        did_see_error_for_valid = true;
×
2582
    });
×
2583
    fixture.start();
2✔
2584

2585
    Session session_valid = fixture.make_bound_session(db_valid, "/valid", g_signed_test_user_token_for_path);
2✔
2586

2587
    write_transaction(db_valid, [](WriteTransaction& wt) {
2✔
2588
        wt.get_group().add_table_with_primary_key("class_a", type_Int, "id");
2✔
2589
    });
2✔
2590

2591
    auto completed = session_valid.wait_for_upload_complete_or_client_stopped();
2✔
2592
    CHECK_NOT(did_see_error_for_valid);
2✔
2593
    CHECK(completed);
2✔
2594
}
2✔
2595

2596

2597
// This test checks that a client SSL connection to localhost succeeds when the
2598
// server presents a certificate issued to localhost signed by a CA whose
2599
// certificate the client loads.
2600
TEST(Sync_SSL_Certificate_1)
2601
{
2✔
2602
    TEST_DIR(server_dir);
2✔
2603
    TEST_CLIENT_DB(db);
2✔
2604
    std::string ca_dir = get_test_resource_path();
2✔
2605

2606
    ClientServerFixture::Config config;
2✔
2607
    config.enable_server_ssl = true;
2✔
2608
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2609
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2610

2611
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
2612

2613
    Session::Config session_config;
2✔
2614
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2615
    session_config.verify_servers_ssl_certificate = true;
2✔
2616
    session_config.ssl_trust_certificate_path = ca_dir + "crt.pem";
2✔
2617
    session_config.signed_user_token = g_signed_test_user_token;
2✔
2618

2619
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
2620

2621
    fixture.start();
2✔
2622
    session.wait_for_download_complete_or_client_stopped();
2✔
2623
}
2✔
2624

2625

2626
// This test checks that a client SSL connection to localhost does not succeed
2627
// when the server presents a certificate issued to localhost signed by a CA whose
2628
// certificate does not match the certificate loaded by the client.
2629
TEST(Sync_SSL_Certificate_2)
2630
{
2✔
2631
    bool did_fail = false;
2✔
2632
    TEST_DIR(server_dir);
2✔
2633
    TEST_CLIENT_DB(db);
2✔
2634
    std::string ca_dir = get_test_resource_path();
2✔
2635

2636
    ClientServerFixture::Config config;
2✔
2637
    config.enable_server_ssl = true;
2✔
2638
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2639
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2640

2641
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
2642

2643
    Session::Config session_config;
2✔
2644
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2645
    session_config.verify_servers_ssl_certificate = true;
2✔
2646
    session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem";
2✔
2647

2648
    auto error_handler = [&](Status status, bool) {
2✔
2649
        CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed);
2✔
2650
        did_fail = true;
2✔
2651
        fixture.stop();
2✔
2652
    };
2✔
2653
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
2654

2655
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
2✔
2656
    fixture.start();
2✔
2657
    session.wait_for_download_complete_or_client_stopped();
2✔
2658
    CHECK(did_fail);
2✔
2659
}
2✔
2660

2661

2662
// This test checks that a client SSL connection to localhost succeeds
2663
// if verify_servers_ssl_certificate = false, even when
2664
// when the server presents a certificate issued to localhost signed by a CA whose
2665
// certificate does not match the certificate loaded by the client.
2666
// This test is identical to Sync_SSL_Certificate_2 except for
2667
// the value of verify_servers_ssl_certificate.
2668
TEST(Sync_SSL_Certificate_3)
2669
{
2✔
2670
    TEST_DIR(server_dir);
2✔
2671
    TEST_CLIENT_DB(db);
2✔
2672
    std::string ca_dir = get_test_resource_path();
2✔
2673

2674
    ClientServerFixture::Config config;
2✔
2675
    config.enable_server_ssl = true;
2✔
2676
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2677
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2678

2679
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
2680

2681
    Session::Config session_config;
2✔
2682
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2683
    session_config.verify_servers_ssl_certificate = false;
2✔
2684
    session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem";
2✔
2685

2686
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
2✔
2687
    fixture.start();
2✔
2688
    session.wait_for_download_complete_or_client_stopped();
2✔
2689
}
2✔
2690

2691

2692
#if REALM_HAVE_SECURE_TRANSPORT
2693

2694
// This test checks that the client can also use a certificate in DER format.
2695
TEST(Sync_SSL_Certificate_DER)
2696
{
1✔
2697
    TEST_DIR(server_dir);
1✔
2698
    TEST_CLIENT_DB(db);
1✔
2699
    std::string ca_dir = get_test_resource_path();
1✔
2700

2701
    ClientServerFixture::Config config;
1✔
2702
    config.enable_server_ssl = true;
1✔
2703
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2704
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2705

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

2708
    Session::Config session_config;
1✔
2709
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2710
    session_config.verify_servers_ssl_certificate = true;
1✔
2711
    session_config.ssl_trust_certificate_path = ca_dir + "localhost-chain.crt.cer";
1✔
2712
    session_config.signed_user_token = g_signed_test_user_token;
1✔
2713

2714
    Session session = fixture.make_session(db, "/test", std::move(session_config));
1✔
2715

2716
    fixture.start();
1✔
2717
    session.wait_for_download_complete_or_client_stopped();
1✔
2718
}
1✔
2719

2720
#endif // REALM_HAVE_SECURE_TRANSPORT
2721

2722

2723
#if REALM_HAVE_OPENSSL
2724

2725
// This test checks that the SSL connection is accepted if the verify callback
2726
// always returns true.
2727
TEST(Sync_SSL_Certificate_Verify_Callback_1)
2728
{
1✔
2729
    TEST_DIR(server_dir);
1✔
2730
    TEST_CLIENT_DB(db);
1✔
2731
    std::string ca_dir = get_test_resource_path();
1✔
2732

2733
    Session::port_type server_port_ssl;
1✔
2734
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port, const char*,
1✔
2735
                                   size_t, int, int) {
2✔
2736
        CHECK_EQUAL(server_address, "localhost");
2✔
2737
        server_port_ssl = server_port;
2✔
2738
        return true;
2✔
2739
    };
2✔
2740

2741
    ClientServerFixture::Config config;
1✔
2742
    config.enable_server_ssl = true;
1✔
2743
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2744
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2745

2746
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2747

2748
    Session::Config session_config;
1✔
2749
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2750
    session_config.verify_servers_ssl_certificate = true;
1✔
2751
    session_config.ssl_trust_certificate_path = util::none;
1✔
2752
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2753

2754
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2755
    fixture.start();
1✔
2756
    session.wait_for_download_complete_or_client_stopped();
1✔
2757

2758
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2759
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2760
}
1✔
2761

2762

2763
// This test checks that the SSL connection is rejected if the verify callback
2764
// always returns false. It also checks that preverify_ok and depth have
2765
// the expected values.
2766
TEST(Sync_SSL_Certificate_Verify_Callback_2)
2767
{
1✔
2768
    bool did_fail = false;
1✔
2769
    TEST_DIR(server_dir);
1✔
2770
    TEST_CLIENT_DB(db);
1✔
2771
    std::string ca_dir = get_test_resource_path();
1✔
2772

2773
    Session::port_type server_port_ssl;
1✔
2774
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
1✔
2775
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
1✔
2776
        CHECK_EQUAL(server_address, "localhost");
1✔
2777
        server_port_ssl = server_port;
1✔
2778
        CHECK_EQUAL(preverify_ok, 0);
1✔
2779
        CHECK_EQUAL(depth, 1);
1✔
2780
        CHECK_EQUAL(pem_size, 2082);
1✔
2781
        std::string pem(pem_data, pem_size);
1✔
2782

2783
        std::string expected = "-----BEGIN CERTIFICATE-----\n"
1✔
2784
                               "MIIF0zCCA7ugAwIBAgIBCDANBgkqhkiG9w0BAQsFADB1MRIwEAYKCZImiZPyLGQB\n";
1✔
2785

2786
        CHECK_EQUAL(expected, pem.substr(0, expected.size()));
1✔
2787

2788
        return false;
1✔
2789
    };
1✔
2790

2791
    ClientServerFixture::Config config;
1✔
2792
    config.enable_server_ssl = true;
1✔
2793
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2794
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2795

2796
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2797

2798
    auto error_handler = [&](Status status, bool) {
1✔
2799
        CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed);
1✔
2800
        did_fail = true;
1✔
2801
        fixture.stop();
1✔
2802
    };
1✔
2803
    fixture.set_client_side_error_handler(std::move(error_handler));
1✔
2804

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

2811
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2812
    fixture.start();
1✔
2813
    session.wait_for_download_complete_or_client_stopped();
1✔
2814
    CHECK(did_fail);
1✔
2815
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2816
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2817
}
1✔
2818

2819

2820
// This test checks that the verify callback function receives the expected
2821
// certificates.
2822
TEST(Sync_SSL_Certificate_Verify_Callback_3)
2823
{
1✔
2824
    TEST_DIR(server_dir);
1✔
2825
    TEST_CLIENT_DB(db);
1✔
2826
    std::string ca_dir = get_test_resource_path();
1✔
2827

2828
    Session::port_type server_port_ssl = 0;
1✔
2829
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
1✔
2830
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
2✔
2831
        CHECK_EQUAL(server_address, "localhost");
2✔
2832
        server_port_ssl = server_port;
2✔
2833

2834
        CHECK(depth == 0 || depth == 1);
2✔
2835
        if (depth == 1) {
2✔
2836
            CHECK_EQUAL(pem_size, 2082);
1✔
2837
            CHECK_EQUAL(pem_data[93], 'G');
1✔
2838
        }
1✔
2839
        else {
1✔
2840
            CHECK_EQUAL(pem_size, 1700);
1✔
2841
            CHECK_EQUAL(preverify_ok, 1);
1✔
2842
            CHECK_EQUAL(pem_data[1667], '2');
1✔
2843
            CHECK_EQUAL(pem_data[1698], '-');
1✔
2844
            CHECK_EQUAL(pem_data[1699], '\n');
1✔
2845
        }
1✔
2846

2847
        return true;
2✔
2848
    };
2✔
2849

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

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

2857
    Session::Config session_config;
1✔
2858
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2859
    session_config.verify_servers_ssl_certificate = true;
1✔
2860
    session_config.ssl_trust_certificate_path = util::none;
1✔
2861
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2862

2863
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2864
    fixture.start();
1✔
2865
    session.wait_for_download_complete_or_client_stopped();
1✔
2866
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2867
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2868
}
1✔
2869

2870

2871
// This test is used to verify the ssl_verify_callback function against an
2872
// external server. The tests should only be used for debugging should normally
2873
// be disabled.
2874
TEST_IF(Sync_SSL_Certificate_Verify_Callback_External, false)
2875
{
2876
    const std::string server_address = "www.writeurl.com";
2877
    Session::port_type port = 443;
2878

2879
    TEST_CLIENT_DB(db);
2880

2881
    Client::Config config;
2882
    config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2883
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(config.logger, "");
2884
    config.socket_provider = socket_provider;
2885
    config.reconnect_mode = ReconnectMode::testing;
2886
    Client client(config);
2887

2888
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
2889
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
2890
        StringData pem{pem_data, pem_size};
2891
        test_context.logger->info("server_address = %1, server_port = %2, pem =\n%3\n, "
2892
                                  " preverify_ok = %4, depth = %5",
2893
                                  server_address, server_port, pem, preverify_ok, depth);
2894
        if (depth == 0)
×
2895
            client.shutdown();
2896
        return true;
2897
    };
2898

2899
    Session::Config session_config;
2900
    session_config.server_address = server_address;
2901
    session_config.server_port = port;
2902
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2903
    session_config.verify_servers_ssl_certificate = true;
2904
    session_config.ssl_trust_certificate_path = util::none;
2905
    session_config.ssl_verify_callback = ssl_verify_callback;
2906

2907
    Session session(client, db, nullptr, nullptr, std::move(session_config));
2908
    session.wait_for_download_complete_or_client_stopped();
2909

2910
    client.shutdown_and_wait();
2911
}
2912

2913
#endif // REALM_HAVE_OPENSSL
2914

2915

2916
// This test has a single client connected to a server with
2917
// one session.
2918
// The client creates four changesets at various times and
2919
// uploads them to the server. The session has a registered
2920
// progress_handler. It is checked that downloaded_bytes,
2921
// downloadable_bytes, uploaded_bytes, and uploadable_bytes
2922
// are correct. This client does not have any downloaded_bytes
2923
// or downloadable bytes because it created all the changesets
2924
// itself.
2925
TEST(Sync_UploadDownloadProgress_1)
2926
{
2✔
2927
    TEST_DIR(server_dir);
2✔
2928
    TEST_CLIENT_DB(db);
2✔
2929

2930
    std::atomic<uint_fast64_t> downloaded_bytes;
2✔
2931
    std::atomic<uint_fast64_t> downloadable_bytes;
2✔
2932
    std::atomic<uint_fast64_t> uploaded_bytes;
2✔
2933
    std::atomic<uint_fast64_t> uploadable_bytes;
2✔
2934
    std::atomic<uint_fast64_t> snapshot_version;
2✔
2935
    {
2✔
2936
        int handler_entry = 0;
2✔
2937

2938
        ClientServerFixture fixture(server_dir, test_context);
2✔
2939
        fixture.start();
2✔
2940

2941
        Session::Config config;
2✔
2942
        config.progress_handler = [&](uint64_t downloaded, uint64_t downloadable, uint64_t uploaded,
2✔
2943
                                      uint64_t uploadable, uint64_t snapshot, double, double, int64_t) {
8✔
2944
            downloaded_bytes = downloaded;
8✔
2945
            downloadable_bytes = downloadable;
8✔
2946
            uploaded_bytes = uploaded;
8✔
2947
            uploadable_bytes = uploadable;
8✔
2948
            snapshot_version = snapshot;
8✔
2949
            ++handler_entry;
8✔
2950
        };
8✔
2951

2952
        auto pf = util::make_promise_future();
2✔
2953
        config.connection_state_change_listener = [&](ConnectionState state, util::Optional<ErrorInfo>) {
4✔
2954
            if (state == ConnectionState::connected) {
4✔
2955
                pf.promise.emplace_value();
2✔
2956
            }
2✔
2957
        };
4✔
2958

2959
        Session session = fixture.make_session(db, "/test", std::move(config));
2✔
2960
        pf.future.get();
2✔
2961
        CHECK_EQUAL(handler_entry, 0);
2✔
2962

2963
        auto commit_version = write_transaction(db, [](WriteTransaction& wt) {
2✔
2964
            wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2965
        });
2✔
2966

2967
        session.wait_for_upload_complete_or_client_stopped();
2✔
2968
        session.wait_for_download_complete_or_client_stopped();
2✔
2969

2970
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
2971
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
2972
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
2973
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
2974
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
2975

2976

2977
        commit_version = write_transaction(db, [](WriteTransaction& wt) {
2✔
2978
            wt.get_table("class_table")->create_object_with_primary_key(1);
2✔
2979
        });
2✔
2980

2981
        session.wait_for_upload_complete_or_client_stopped();
2✔
2982
        session.wait_for_download_complete_or_client_stopped();
2✔
2983

2984
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
2985
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
2986
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
2987
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
2988
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
2989
    }
2✔
2990

2991
    {
2✔
2992
        // Here we check that the progress handler is called
2993
        // after the session is bound, and that the values
2994
        // are the ones stored in the Realm in the previous
2995
        // session.
2996

2997
        ClientServerFixture fixture(server_dir, test_context);
2✔
2998
        fixture.start();
2✔
2999

3000
        int number_of_handler_calls = 0;
2✔
3001

3002
        auto pf = util::make_promise_future<int>();
2✔
3003
        Session::Config config;
2✔
3004
        config.progress_handler = [&](uint64_t downloaded, uint64_t downloadable, uint64_t uploaded,
2✔
3005
                                      uint64_t uploadable, uint64_t snapshot, double, double, int64_t) {
2✔
3006
            CHECK_EQUAL(downloaded, downloaded_bytes);
2✔
3007
            CHECK_EQUAL(downloadable, downloaded_bytes);
2✔
3008
            CHECK_EQUAL(uploaded, uploaded_bytes);
2✔
3009
            CHECK_GREATER(uploadable, uploaded_bytes);
2✔
3010
            CHECK_GREATER(snapshot, snapshot_version);
2✔
3011
            number_of_handler_calls++;
2✔
3012
            pf.promise.emplace_value(number_of_handler_calls);
2✔
3013
        };
2✔
3014

3015
        Session session = fixture.make_session(db, "/test", std::move(config));
2✔
3016
        write_transaction(db, [](WriteTransaction& wt) {
2✔
3017
            wt.get_table("class_table")->create_object_with_primary_key(2);
2✔
3018
        });
2✔
3019
        CHECK_EQUAL(pf.future.get(), 1);
2✔
3020
    }
2✔
3021
}
2✔
3022

3023

3024
// This test creates one server and a client with
3025
// two sessions that synchronizes with the same server Realm.
3026
// The clients generate changesets, uploads and downloads, and
3027
// waits for upload/download completion. Both sessions have a
3028
// progress handler registered, and it is checked that the
3029
// progress handlers report the correct values.
3030
TEST(Sync_UploadDownloadProgress_2)
3031
{
2✔
3032
    TEST_DIR(server_dir);
2✔
3033
    TEST_CLIENT_DB(db_1);
2✔
3034
    TEST_CLIENT_DB(db_2);
2✔
3035

3036
    ClientServerFixture fixture(server_dir, test_context);
2✔
3037
    fixture.start();
2✔
3038

3039
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3040
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3041
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3042
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3043
    uint_fast64_t snapshot_version_1 = 0;
2✔
3044

3045
    Session::Config config_1;
2✔
3046
    config_1.progress_handler = [&](uint64_t downloaded_bytes, uint64_t downloadable_bytes, uint64_t uploaded_bytes,
2✔
3047
                                    uint64_t uploadable_bytes, uint64_t snapshot_version, double, double, int64_t) {
23✔
3048
        downloaded_bytes_1 = downloaded_bytes;
23✔
3049
        downloadable_bytes_1 = downloadable_bytes;
23✔
3050
        uploaded_bytes_1 = uploaded_bytes;
23✔
3051
        uploadable_bytes_1 = uploadable_bytes;
23✔
3052
        snapshot_version_1 = snapshot_version;
23✔
3053
    };
23✔
3054

3055
    uint_fast64_t downloaded_bytes_2 = 123;
2✔
3056
    uint_fast64_t downloadable_bytes_2 = 123;
2✔
3057
    uint_fast64_t uploaded_bytes_2 = 123;
2✔
3058
    uint_fast64_t uploadable_bytes_2 = 123;
2✔
3059
    uint_fast64_t snapshot_version_2 = 0;
2✔
3060

3061
    Session::Config config_2;
2✔
3062
    config_2.progress_handler = [&](uint64_t downloaded_bytes, uint64_t downloadable_bytes, uint64_t uploaded_bytes,
2✔
3063
                                    uint64_t uploadable_bytes, uint64_t snapshot_version, double, double, int64_t) {
24✔
3064
        downloaded_bytes_2 = downloaded_bytes;
24✔
3065
        downloadable_bytes_2 = downloadable_bytes;
24✔
3066
        uploaded_bytes_2 = uploaded_bytes;
24✔
3067
        uploadable_bytes_2 = uploadable_bytes;
24✔
3068
        snapshot_version_2 = snapshot_version;
24✔
3069
    };
24✔
3070

3071
    Session session_1 = fixture.make_session(db_1, "/test", std::move(config_1));
2✔
3072
    Session session_2 = fixture.make_session(db_2, "/test", std::move(config_2));
2✔
3073

3074
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3075
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3076
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3077
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3078

3079
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3080
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3081
    CHECK_EQUAL(downloaded_bytes_1, downloaded_bytes_2);
2✔
3082
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3083
    CHECK_GREATER(snapshot_version_1, 0);
2✔
3084

3085
    CHECK_EQUAL(uploaded_bytes_1, 0);
2✔
3086
    CHECK_EQUAL(uploadable_bytes_1, 0);
2✔
3087

3088
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3089
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3090
    CHECK_GREATER(snapshot_version_2, 0);
2✔
3091

3092
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3093
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3094
        tr->add_column(type_Int, "integer column");
2✔
3095
    });
2✔
3096

3097
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3098
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3099
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3100
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3101

3102
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3103
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3104

3105
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3106
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3107

3108
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3109
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3110

3111
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3112
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3113

3114
    CHECK_GREATER(snapshot_version_1, 1);
2✔
3115
    CHECK_GREATER(snapshot_version_2, 1);
2✔
3116

3117
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3118
        TableRef tr = wt.get_table("class_table");
2✔
3119
        tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3120
    });
2✔
3121

3122
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3123
        TableRef tr = wt.get_table("class_table");
2✔
3124
        tr->create_object_with_primary_key(2).set("integer column", 44);
2✔
3125
    });
2✔
3126

3127
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3128
        TableRef tr = wt.get_table("class_table");
2✔
3129
        tr->create_object_with_primary_key(3).set("integer column", 43);
2✔
3130
    });
2✔
3131

3132
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3133
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3134
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3135
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3136

3137
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3138
    CHECK_NOT_EQUAL(downloadable_bytes_1, 0);
2✔
3139

3140
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3141
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3142

3143
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3144
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3145

3146
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3147
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3148

3149
    CHECK_GREATER(snapshot_version_1, 4);
2✔
3150
    CHECK_GREATER(snapshot_version_2, 3);
2✔
3151

3152
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3153
        TableRef tr = wt.get_table("class_table");
2✔
3154
        tr->begin()->set("integer column", 101);
2✔
3155
    });
2✔
3156

3157
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3158
        TableRef tr = wt.get_table("class_table");
2✔
3159
        tr->begin()->set("integer column", 102);
2✔
3160
    });
2✔
3161

3162
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3163
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3164
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3165
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3166

3167
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3168

3169
    // uncertainty due to merge
3170
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3171

3172
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3173
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3174

3175
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3176
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3177

3178
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3179
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3180

3181
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3182
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3183

3184
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3185
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3186

3187
    // Check convergence.
3188
    {
2✔
3189
        ReadTransaction rt_1(db_1);
2✔
3190
        ReadTransaction rt_2(db_2);
2✔
3191
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
3192
    }
2✔
3193
}
2✔
3194

3195

3196
// This test creates a server and a client. Initially, the server is not running.
3197
// The client generates changes and binds a session. It is verified that the
3198
// progress_handler() is called and that the four arguments of progress_handler()
3199
// have the correct values. The server is started in the first call to
3200
// progress_handler() and it is checked that after upload and download completion,
3201
// the upload_progress_handler has been called again, and that the four arguments
3202
// have the correct values. After this, the server is stopped and the client produces
3203
// more changes. It is checked that the progress_handler() is called and that the
3204
// final values are correct.
3205
TEST(Sync_UploadDownloadProgress_3)
3206
{
2✔
3207
    TEST_DIR(server_dir);
2✔
3208
    TEST_CLIENT_DB(db);
2✔
3209

3210
    std::string server_address = "localhost";
2✔
3211

3212
    Server::Config server_config;
2✔
3213
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3214
    server_config.listen_address = server_address;
2✔
3215
    server_config.listen_port = "";
2✔
3216
    server_config.tcp_no_delay = true;
2✔
3217

3218
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3219
    Server server(server_dir, std::move(public_key), server_config);
2✔
3220
    server.start();
2✔
3221
    auto server_port = server.listen_endpoint().port();
2✔
3222

3223
    ThreadWrapper server_thread;
2✔
3224

3225
    // The server is not running.
3226

3227
    {
2✔
3228
        WriteTransaction wt{db};
2✔
3229
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3230
        tr->add_column(type_Int, "integer column");
2✔
3231
        wt.commit();
2✔
3232
    }
2✔
3233

3234
    Client::Config client_config;
2✔
3235
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3236
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3237
    client_config.socket_provider = socket_provider;
2✔
3238
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3239
    Client client(client_config);
2✔
3240

3241
    // entry is used to count the number of calls to
3242
    // progress_handler. At the first call, the server is
3243
    // not running, and it is started by progress_handler().
3244

3245
    bool should_signal_cond_var = false;
2✔
3246
    auto signal_pf = util::make_promise_future<void>();
2✔
3247

3248
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3249
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3250
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3251
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3252
    uint_fast64_t snapshot_version_1 = 0;
2✔
3253

3254
    Session::Config config;
2✔
3255
    config.service_identifier = "/realm-sync";
2✔
3256
    config.server_address = server_address;
2✔
3257
    config.signed_user_token = g_signed_test_user_token;
2✔
3258
    config.server_port = server_port;
2✔
3259
    config.realm_identifier = "/test";
2✔
3260
    config.progress_handler = [&, entry = 0](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3261
                                             uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3262
                                             uint_fast64_t snapshot_version, double, double, int64_t) mutable {
6✔
3263
        downloaded_bytes_1 = downloaded_bytes;
6✔
3264
        downloadable_bytes_1 = downloadable_bytes;
6✔
3265
        uploaded_bytes_1 = uploaded_bytes;
6✔
3266
        uploadable_bytes_1 = uploadable_bytes;
6✔
3267
        snapshot_version_1 = snapshot_version;
6✔
3268

3269
        if (entry == 0) {
6✔
3270
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3271
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3272
            CHECK_EQUAL(uploaded_bytes, 0);
2✔
3273
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3274
            CHECK_EQUAL(snapshot_version, 4);
2✔
3275
        }
2✔
3276

3277
        if (should_signal_cond_var) {
6✔
3278
            signal_pf.promise.emplace_value();
2✔
3279
        }
2✔
3280

3281
        entry++;
6✔
3282
    };
6✔
3283

3284
    server_thread.start([&] {
2✔
3285
        server.run();
2✔
3286
    });
2✔
3287

3288
    Session session(client, db, nullptr, nullptr, std::move(config));
2✔
3289

3290
    session.wait_for_upload_complete_or_client_stopped();
2✔
3291
    session.wait_for_download_complete_or_client_stopped();
2✔
3292

3293
    // Now the server is running.
3294

3295
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3296
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3297
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3298
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3299
    CHECK_GREATER_EQUAL(snapshot_version_1, 2);
2✔
3300

3301
    server.stop();
2✔
3302

3303
    // The server is stopped
3304

3305
    should_signal_cond_var = true;
2✔
3306

3307
    uint_fast64_t commited_version;
2✔
3308
    {
2✔
3309
        WriteTransaction wt{db};
2✔
3310
        TableRef tr = wt.get_table("class_table");
2✔
3311
        tr->create_object_with_primary_key(123).set("integer column", 42);
2✔
3312
        commited_version = wt.commit();
2✔
3313
    }
2✔
3314

3315
    signal_pf.future.get();
2✔
3316

3317
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3318
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3319
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3320
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3321
    CHECK_EQUAL(snapshot_version_1, commited_version);
2✔
3322

3323
    server_thread.join();
2✔
3324
}
2✔
3325

3326

3327
// This test creates a server and two clients. The first client uploads two
3328
// large changesets. The other client downloads them. The download messages to
3329
// the second client contains one changeset because the changesets are larger
3330
// than the soft size limit for changesets in the DOWNLOAD message. This implies
3331
// that after receiving the first DOWNLOAD message, the second client will have
3332
// downloaded_bytes < downloadable_bytes.
3333
TEST(Sync_UploadDownloadProgress_4)
3334
{
2✔
3335
    TEST_DIR(server_dir);
2✔
3336
    TEST_CLIENT_DB(db_1);
2✔
3337
    TEST_CLIENT_DB(db_2);
2✔
3338

3339
    {
2✔
3340
        WriteTransaction wt{db_1};
2✔
3341
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3342
        auto col = tr->add_column(type_Binary, "binary column");
2✔
3343
        tr->create_object_with_primary_key(1);
2✔
3344
        std::string str(size_t(5e5), 'a');
2✔
3345
        BinaryData bd(str.data(), str.size());
2✔
3346
        tr->begin()->set(col, bd);
2✔
3347
        wt.commit();
2✔
3348
    }
2✔
3349

3350
    {
2✔
3351
        WriteTransaction wt{db_1};
2✔
3352
        TableRef tr = wt.get_table("class_table");
2✔
3353
        auto col = tr->get_column_key("binary column");
2✔
3354
        tr->create_object_with_primary_key(2);
2✔
3355
        std::string str(size_t(1e6), 'a');
2✔
3356
        BinaryData bd(str.data(), str.size());
2✔
3357
        tr->begin()->set(col, bd);
2✔
3358
        wt.commit();
2✔
3359
    }
2✔
3360

3361
    ClientServerFixture::Config config;
2✔
3362
    config.max_download_size = size_t(1e5);
2✔
3363
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3364
    fixture.start();
2✔
3365

3366
    int entry_1 = 0;
2✔
3367
    Session::Config config_1;
2✔
3368
    config_1.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3369
                                    uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3370
                                    uint_fast64_t snapshot_version, double, double, int64_t) {
6✔
3371
        CHECK_EQUAL(downloaded_bytes, 0);
6✔
3372
        CHECK_EQUAL(downloadable_bytes, 0);
6✔
3373
        CHECK_NOT_EQUAL(uploadable_bytes, 0);
6✔
3374

3375
        switch (entry_1) {
6✔
3376
            case 0:
2✔
3377
                // We've received the empty DOWNLOAD message and now have reliable
3378
                // download progress
3379
                CHECK_EQUAL(uploaded_bytes, 0);
2✔
3380
                CHECK_EQUAL(snapshot_version, 5);
2✔
3381
                break;
2✔
3382

3383
            case 1:
2✔
3384
                // First UPLOAD is complete, but we still have more to upload
3385
                // because the changesets are too large to batch into a single upload
3386
                CHECK_GREATER(uploaded_bytes, 0);
2✔
3387
                CHECK_LESS(uploaded_bytes, uploadable_bytes);
2✔
3388
                CHECK_EQUAL(snapshot_version, 6);
2✔
3389
                break;
2✔
3390

3391
            case 2:
2✔
3392
                // Second UPLOAD is complete and we're done uploading
3393
                CHECK_EQUAL(uploaded_bytes, uploadable_bytes);
2✔
3394
                CHECK_EQUAL(snapshot_version, 7);
2✔
3395
                break;
2✔
3396
        }
6✔
3397

3398
        ++entry_1;
6✔
3399
    };
6✔
3400

3401
    Session session_1 = fixture.make_session(db_1, "/test", std::move(config_1));
2✔
3402
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3403
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3404

3405
    CHECK_EQUAL(entry_1, 3);
2✔
3406

3407
    int entry_2 = 0;
2✔
3408

3409
    Session::Config config_2;
2✔
3410
    config_2.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3411
                                    uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3412
                                    uint_fast64_t snapshot_version, double, double, int64_t) {
4✔
3413
        CHECK_EQUAL(uploaded_bytes, 0);
4✔
3414
        CHECK_EQUAL(uploadable_bytes, 0);
4✔
3415

3416
        switch (entry_2) {
4✔
3417
            case 0:
2✔
3418
                // First DOWNLOAD message received. Some data is downloaded, but
3419
                // download isn't compelte
3420
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3421
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3422
                CHECK_LESS(downloaded_bytes, downloadable_bytes);
2✔
3423
                CHECK_EQUAL(snapshot_version, 3);
2✔
3424
                break;
2✔
3425

3426
            case 1:
2✔
3427
                // Second DOWNLOAD message received. Download is now complete.
3428
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3429
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3430
                CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
3431
                CHECK_EQUAL(snapshot_version, 4);
2✔
3432
                break;
2✔
3433
        }
4✔
3434
        ++entry_2;
4✔
3435
    };
4✔
3436

3437
    Session session_2 = fixture.make_session(db_2, "/test", std::move(config_2));
2✔
3438

3439
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3440
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3441
    CHECK_EQUAL(entry_2, 2);
2✔
3442
}
2✔
3443

3444

3445
// This test has a single client connected to a server with one session. The
3446
// client does not create any changesets. The test verifies that the client gets
3447
// a confirmation from the server of downloadable_bytes = 0.
3448
TEST(Sync_UploadDownloadProgress_5)
3449
{
2✔
3450
    TEST_DIR(server_dir);
2✔
3451
    TEST_CLIENT_DB(db);
2✔
3452

3453
    ClientServerFixture fixture(server_dir, test_context);
2✔
3454
    fixture.start();
2✔
3455

3456
    auto pf = util::make_promise_future();
2✔
3457
    Session::Config config;
2✔
3458
    config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3459
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3460
                                  uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
3461
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3462
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3463
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3464
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3465
        CHECK_EQUAL(snapshot_version, 3);
2✔
3466
        pf.promise.emplace_value();
2✔
3467
    };
2✔
3468

3469
    Session session = fixture.make_session(db, "/test", std::move(config));
2✔
3470
    pf.future.get();
2✔
3471

3472
    // The check is that we reach this point.
3473
}
2✔
3474

3475

3476
// This test has a single client connected to a server with one session.
3477
// The session has a registered progress handler.
3478
TEST(Sync_UploadDownloadProgress_6)
3479
{
2✔
3480
    TEST_DIR(server_dir);
2✔
3481
    TEST_CLIENT_DB(db);
2✔
3482

3483
    Server::Config server_config;
2✔
3484
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3485
    server_config.listen_address = "localhost";
2✔
3486
    server_config.listen_port = "";
2✔
3487
    server_config.tcp_no_delay = true;
2✔
3488

3489
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3490
    Server server(server_dir, std::move(public_key), server_config);
2✔
3491
    server.start();
2✔
3492

3493
    auto server_port = server.listen_endpoint().port();
2✔
3494

3495
    ThreadWrapper server_thread;
2✔
3496
    server_thread.start([&] {
2✔
3497
        server.run();
2✔
3498
    });
2✔
3499

3500
    Client::Config client_config;
2✔
3501
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3502
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3503
    client_config.socket_provider = socket_provider;
2✔
3504
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3505
    client_config.one_connection_per_session = false;
2✔
3506
    Client client(client_config);
2✔
3507

3508
    util::ScopeExit cleanup([&]() noexcept {
2✔
3509
        client.shutdown_and_wait();
2✔
3510
        server.stop();
2✔
3511
        server_thread.join();
2✔
3512
    });
2✔
3513

3514
    auto session_pf = util::make_promise_future<std::unique_ptr<Session>*>();
2✔
3515
    auto complete_pf = util::make_promise_future();
2✔
3516
    Session::Config session_config;
2✔
3517
    session_config.server_address = "localhost";
2✔
3518
    session_config.server_port = server_port;
2✔
3519
    session_config.realm_identifier = "/test";
2✔
3520
    session_config.service_identifier = "/realm-sync";
2✔
3521
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3522
    session_config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3523
                                          uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3524
                                          uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
3525
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3526
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3527
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3528
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3529
        CHECK_EQUAL(snapshot_version, 3);
2✔
3530
        session_pf.future.get()->reset();
2✔
3531
        complete_pf.promise.emplace_value();
2✔
3532
    };
2✔
3533
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3534
    session_pf.promise.emplace_value(&session);
2✔
3535
    complete_pf.future.get();
2✔
3536
    CHECK(!session);
2✔
3537

3538
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
3539
    // down the active session
3540
}
2✔
3541

3542
// This test has a single client starting to connect to the server with one session.
3543
// The client is torn down immediately after bind is called on the session.
3544
// The session will still be active and has an unactualized session wrapper when the
3545
// client is torn down, which leads to both calls to finalize_before_actualization() and
3546
// and finalize().
3547
TEST(Sync_UploadDownloadProgress_7)
3548
{
2✔
3549
    TEST_DIR(server_dir);
2✔
3550
    TEST_CLIENT_DB(db);
2✔
3551

3552
    Server::Config server_config;
2✔
3553
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3554
    server_config.listen_address = "localhost";
2✔
3555
    server_config.listen_port = "";
2✔
3556
    server_config.tcp_no_delay = true;
2✔
3557

3558
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3559
    Server server(server_dir, std::move(public_key), server_config);
2✔
3560
    server.start();
2✔
3561

3562
    auto server_port = server.listen_endpoint().port();
2✔
3563

3564
    ThreadWrapper server_thread;
2✔
3565
    server_thread.start([&] {
2✔
3566
        server.run();
2✔
3567
    });
2✔
3568

3569
    Client::Config client_config;
2✔
3570
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3571
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3572
    client_config.socket_provider = socket_provider;
2✔
3573
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3574
    client_config.one_connection_per_session = false;
2✔
3575
    Client client(client_config);
2✔
3576

3577
    Session::Config session_config;
2✔
3578
    session_config.server_address = "localhost";
2✔
3579
    session_config.server_port = server_port;
2✔
3580
    session_config.realm_identifier = "/test";
2✔
3581
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3582

3583
    Session session(client, db, nullptr, nullptr, std::move(session_config));
2✔
3584

3585
    client.shutdown_and_wait();
2✔
3586
    server.stop();
2✔
3587
    server_thread.join();
2✔
3588

3589
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
3590
    // down the session that is in the process of being created.
3591
}
2✔
3592

3593
TEST(Sync_UploadProgress_EmptyCommits)
3594
{
2✔
3595
    TEST_DIR(server_dir);
2✔
3596
    TEST_CLIENT_DB(db);
2✔
3597

3598
    ClientServerFixture fixture(server_dir, test_context);
2✔
3599
    fixture.start();
2✔
3600

3601
    {
2✔
3602
        WriteTransaction wt{db};
2✔
3603
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "_id");
2✔
3604
        wt.commit();
2✔
3605
    }
2✔
3606

3607
    std::atomic<int> entry = 0;
2✔
3608
    Session::Config config;
2✔
3609
    config.progress_handler = [&](uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, double,
2✔
3610
                                  double, int64_t) {
8✔
3611
        ++entry;
8✔
3612
    };
8✔
3613

3614
    Session session = fixture.make_session(db, "/test", std::move(config));
2✔
3615

3616
    // Each step calls wait_for_upload_complete twice because upload completion
3617
    // is fired before progress handlers, so we need another hop through the
3618
    // event loop after upload completion to know that the handler has been called
3619
    session.wait_for_upload_complete_or_client_stopped();
2✔
3620
    session.wait_for_upload_complete_or_client_stopped();
2✔
3621

3622
    // Binding produces two notifications: one after receiving
3623
    // the DOWNLOAD message, and one after uploading the schema
3624
    CHECK_EQUAL(entry, 2);
2✔
3625

3626
    // No notification sent because an empty commit doesn't change uploadable_bytes
3627
    {
2✔
3628
        WriteTransaction wt{db};
2✔
3629
        wt.commit();
2✔
3630
    }
2✔
3631
    session.wait_for_upload_complete_or_client_stopped();
2✔
3632
    session.wait_for_upload_complete_or_client_stopped();
2✔
3633
    CHECK_EQUAL(entry, 2);
2✔
3634

3635
    // Both the external and local commits are empty, so again no change in
3636
    // uploadable_bytes
3637
    {
2✔
3638
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3639
        WriteTransaction wt{db2};
2✔
3640
        wt.commit();
2✔
3641
        WriteTransaction wt2{db};
2✔
3642
        wt2.commit();
2✔
3643
    }
2✔
3644
    session.wait_for_upload_complete_or_client_stopped();
2✔
3645
    session.wait_for_upload_complete_or_client_stopped();
2✔
3646
    CHECK_EQUAL(entry, 2);
2✔
3647

3648
    // Local commit is empty, but the changeset created by the external write
3649
    // is discovered after the local write, resulting in two notifications (one
3650
    // before uploading and one after).
3651
    {
2✔
3652
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3653
        WriteTransaction wt{db2};
2✔
3654
        wt.get_table("class_table")->create_object_with_primary_key(0);
2✔
3655
        wt.commit();
2✔
3656
        WriteTransaction wt2{db};
2✔
3657
        wt2.commit();
2✔
3658
    }
2✔
3659
    session.wait_for_upload_complete_or_client_stopped();
2✔
3660
    session.wait_for_upload_complete_or_client_stopped();
2✔
3661
    CHECK_EQUAL(entry, 4);
2✔
3662
}
2✔
3663

3664
TEST(Sync_MultipleSyncAgentsNotAllowed)
3665
{
2✔
3666
    // At most one sync agent is allowed to participate in a Realm file access
3667
    // session at any particular point in time. Note that a Realm file access
3668
    // session is a group of temporally overlapping accesses to a Realm file,
3669
    // and that the group of participants is the transitive closure of a
3670
    // particular session participant over the "temporally overlapping access"
3671
    // relation.
3672

3673
    TEST_DIR(server_dir);
2✔
3674
    TEST_CLIENT_DB(db);
2✔
3675

3676
    auto pf = util::make_promise_future();
2✔
3677
    struct Observer : BindingCallbackThreadObserver {
2✔
3678
        unit_test::TestContext& test_context;
2✔
3679
        util::Promise<void>& got_error;
2✔
3680
        Observer(unit_test::TestContext& test_context, util::Promise<void>& got_error)
2✔
3681
            : test_context(test_context)
2✔
3682
            , got_error(got_error)
2✔
3683
        {
2✔
3684
        }
2✔
3685

3686
        bool has_handle_error() override
2✔
3687
        {
4✔
3688
            return true;
4✔
3689
        }
4✔
3690
        bool handle_error(const std::exception& e) override
2✔
3691
        {
2✔
3692
            CHECK(dynamic_cast<const MultipleSyncAgents*>(&e));
2✔
3693
            got_error.emplace_value();
2✔
3694
            return true;
2✔
3695
        }
2✔
3696
    };
2✔
3697

3698
    auto observer = std::make_shared<Observer>(test_context, pf.promise);
2✔
3699
    ClientServerFixture::Config config;
2✔
3700
    config.socket_provider_observer = observer;
2✔
3701
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3702
    fixture.start();
2✔
3703

3704
    {
2✔
3705
        Session session = fixture.make_session(db, "/test");
2✔
3706
        Session session2 = fixture.make_session(db, "/test");
2✔
3707
        pf.future.get();
2✔
3708

3709
        // The exception caused the event loop to stop so we need to restart it
3710
        fixture.start_client(0);
2✔
3711
    }
2✔
3712

3713
    // Verify that after the error occurs (and is ignored) things are still
3714
    // in a functional state
3715
    Session session = fixture.make_session(db, "/test");
2✔
3716
    session.wait_for_upload_complete_or_client_stopped();
2✔
3717
}
2✔
3718

3719
TEST(Sync_CancelReconnectDelay)
3720
{
2✔
3721
    TEST_DIR(server_dir);
2✔
3722
    TEST_CLIENT_DB(db);
2✔
3723
    TEST_CLIENT_DB(db_x);
2✔
3724

3725
    ClientServerFixture::Config fixture_config;
2✔
3726
    fixture_config.one_connection_per_session = false;
2✔
3727

3728
    auto expect_status = [&](BowlOfStonesSemaphore& bowl, ErrorCodes::Error code) {
10✔
3729
        Session::Config config;
10✔
3730
        config.connection_state_change_listener = [&, code](ConnectionState state,
10✔
3731
                                                            std::optional<SessionErrorInfo> error) {
50✔
3732
            if (state != ConnectionState::disconnected)
50✔
3733
                return;
36✔
3734
            CHECK(error);
14✔
3735
            if (CHECK_EQUAL(error->status, code))
14✔
3736
                bowl.add_stone();
14✔
3737
        };
14✔
3738
        return config;
10✔
3739
    };
10✔
3740

3741
    // After connection-level error, and at session-level.
3742
    {
2✔
3743
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3744
        fixture.start();
2✔
3745

3746
        BowlOfStonesSemaphore bowl;
2✔
3747
        Session session = fixture.make_session(db, "/test", expect_status(bowl, ErrorCodes::ConnectionClosed));
2✔
3748
        session.wait_for_download_complete_or_client_stopped();
2✔
3749
        fixture.close_server_side_connections();
2✔
3750
        bowl.get_stone();
2✔
3751

3752
        session.cancel_reconnect_delay();
2✔
3753
        session.wait_for_download_complete_or_client_stopped();
2✔
3754
    }
2✔
3755

3756
    // After connection-level error, and at client-level while connection
3757
    // object exists (ConnectionImpl in clinet.cpp).
3758
    {
2✔
3759
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3760
        fixture.start();
2✔
3761

3762
        BowlOfStonesSemaphore bowl;
2✔
3763
        Session session = fixture.make_session(db, "/test", expect_status(bowl, ErrorCodes::ConnectionClosed));
2✔
3764
        session.wait_for_download_complete_or_client_stopped();
2✔
3765
        fixture.close_server_side_connections();
2✔
3766
        bowl.get_stone();
2✔
3767

3768
        fixture.cancel_reconnect_delay();
2✔
3769
        session.wait_for_download_complete_or_client_stopped();
2✔
3770
    }
2✔
3771

3772
    // After connection-level error, and at client-level while connection object
3773
    // does not exist (ConnectionImpl in clinet.cpp).
3774
    {
2✔
3775
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3776
        fixture.start();
2✔
3777

3778
        {
2✔
3779
            BowlOfStonesSemaphore bowl;
2✔
3780
            Session session = fixture.make_session(db, "/test", expect_status(bowl, ErrorCodes::ConnectionClosed));
2✔
3781
            session.wait_for_download_complete_or_client_stopped();
2✔
3782
            fixture.close_server_side_connections();
2✔
3783
            bowl.get_stone();
2✔
3784
        }
2✔
3785

3786
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3787
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3788
        // The connection object no longer exists at this time. After the first
3789
        // of the two waits above, the invocation of ConnectionImpl::on_idle()
3790
        // (in client.cpp) has been scheduled. After the second wait, it has
3791
        // been called, and that destroys the connection object.
3792

3793
        fixture.cancel_reconnect_delay();
2✔
3794
        {
2✔
3795
            Session session = fixture.make_bound_session(db, "/test");
2✔
3796
            session.wait_for_download_complete_or_client_stopped();
2✔
3797
        }
2✔
3798
    }
2✔
3799

3800
    // After session-level error, and at session-level.
3801
    {
2✔
3802
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3803
        fixture.start();
2✔
3804

3805
        // Add a session for the purpose of keeping the connection open
3806
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3807
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3808

3809
        BowlOfStonesSemaphore bowl;
2✔
3810
        Session session = fixture.make_session(db, "/..", expect_status(bowl, ErrorCodes::BadSyncPartitionValue));
2✔
3811
        bowl.get_stone();
2✔
3812

3813
        session.cancel_reconnect_delay();
2✔
3814
        bowl.get_stone();
2✔
3815
    }
2✔
3816

3817
    // After session-level error, and at client-level.
3818
    {
2✔
3819
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3820
        fixture.start();
2✔
3821

3822
        // Add a session for the purpose of keeping the connection open
3823
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3824
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3825

3826
        BowlOfStonesSemaphore bowl;
2✔
3827
        Session session = fixture.make_session(db, "/..", expect_status(bowl, ErrorCodes::BadSyncPartitionValue));
2✔
3828
        bowl.get_stone();
2✔
3829

3830
        fixture.cancel_reconnect_delay();
2✔
3831
        bowl.get_stone();
2✔
3832
    }
2✔
3833
}
2✔
3834

3835

3836
#ifndef REALM_PLATFORM_WIN32
3837

3838
// This test checks that it is possible to create, upload, download, and merge
3839
// changesets larger than 16 MB.
3840
//
3841
// Fails with 'bad alloc' around 1 GB mem usage on 32-bit Windows + 32-bit Linux
3842
TEST_IF(Sync_MergeLargeBinary, !(REALM_ARCHITECTURE_X86_32))
3843
{
2✔
3844
    // Two binaries are inserted in each transaction such that the total size
3845
    // of the changeset exceeds 16 MB. A single set_binary operation does not
3846
    // accept a binary larger than 16 MB.
3847
    size_t binary_sizes[] = {
2✔
3848
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e6), static_cast<size_t>(11e6),
2✔
3849
        static_cast<size_t>(6e6), static_cast<size_t>(12e6), static_cast<size_t>(5e6), static_cast<size_t>(13e6),
2✔
3850
    };
2✔
3851

3852
    TEST_CLIENT_DB(db_1);
2✔
3853
    TEST_CLIENT_DB(db_2);
2✔
3854

3855
    {
2✔
3856
        WriteTransaction wt(db_1);
2✔
3857
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
3858
        table->add_column(type_Binary, "column name");
2✔
3859
        std::string str_1(binary_sizes[0], 'a');
2✔
3860
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3861
        std::string str_2(binary_sizes[1], 'b');
2✔
3862
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3863
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
3864
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
3865
        wt.commit();
2✔
3866
    }
2✔
3867

3868
    {
2✔
3869
        WriteTransaction wt(db_1);
2✔
3870
        TableRef table = wt.get_table("class_table name");
2✔
3871
        std::string str_1(binary_sizes[2], 'c');
2✔
3872
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3873
        std::string str_2(binary_sizes[3], 'd');
2✔
3874
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3875
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
3876
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
3877
        wt.commit();
2✔
3878
    }
2✔
3879

3880
    {
2✔
3881
        WriteTransaction wt(db_2);
2✔
3882
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
3883
        table->add_column(type_Binary, "column name");
2✔
3884
        std::string str_1(binary_sizes[4], 'e');
2✔
3885
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3886
        std::string str_2(binary_sizes[5], 'f');
2✔
3887
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3888
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
3889
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
3890
        wt.commit();
2✔
3891
    }
2✔
3892

3893
    {
2✔
3894
        WriteTransaction wt(db_2);
2✔
3895
        TableRef table = wt.get_table("class_table name");
2✔
3896
        std::string str_1(binary_sizes[6], 'g');
2✔
3897
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3898
        std::string str_2(binary_sizes[7], 'h');
2✔
3899
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3900
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
3901
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
3902
        wt.commit();
2✔
3903
    }
2✔
3904

3905
    std::uint_fast64_t downloaded_bytes_1 = 0;
2✔
3906
    std::uint_fast64_t downloadable_bytes_1 = 0;
2✔
3907
    std::uint_fast64_t uploaded_bytes_1 = 0;
2✔
3908
    std::uint_fast64_t uploadable_bytes_1 = 0;
2✔
3909

3910
    auto progress_handler_1 = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
3911
                                  std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
3912
                                  std::uint_fast64_t, double, double, int64_t) {
12✔
3913
        downloaded_bytes_1 = downloaded_bytes;
12✔
3914
        downloadable_bytes_1 = downloadable_bytes;
12✔
3915
        uploaded_bytes_1 = uploaded_bytes;
12✔
3916
        uploadable_bytes_1 = uploadable_bytes;
12✔
3917
    };
12✔
3918

3919
    std::uint_fast64_t downloaded_bytes_2 = 0;
2✔
3920
    std::uint_fast64_t downloadable_bytes_2 = 0;
2✔
3921
    std::uint_fast64_t uploaded_bytes_2 = 0;
2✔
3922
    std::uint_fast64_t uploadable_bytes_2 = 0;
2✔
3923

3924
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3925
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t, double,
2✔
3926
                                  double, int64_t) {
8✔
3927
        downloaded_bytes_2 = downloaded_bytes;
8✔
3928
        downloadable_bytes_2 = downloadable_bytes;
8✔
3929
        uploaded_bytes_2 = uploaded_bytes;
8✔
3930
        uploadable_bytes_2 = uploadable_bytes;
8✔
3931
    };
8✔
3932

3933
    {
2✔
3934
        TEST_DIR(dir);
2✔
3935
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
3936
        fixture.start();
2✔
3937

3938
        {
2✔
3939
            Session::Config config;
2✔
3940
            config.progress_handler = progress_handler_1;
2✔
3941
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
3942
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
3943
        }
2✔
3944

3945
        {
2✔
3946
            Session::Config config;
2✔
3947
            config.progress_handler = progress_handler_2;
2✔
3948
            Session session_2 = fixture.make_session(1, 0, db_2, "/test", std::move(config));
2✔
3949
            session_2.wait_for_download_complete_or_client_stopped();
2✔
3950
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
3951
        }
2✔
3952

3953
        {
2✔
3954
            Session::Config config;
2✔
3955
            config.progress_handler = progress_handler_1;
2✔
3956
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
3957
            session_1.wait_for_download_complete_or_client_stopped();
2✔
3958
        }
2✔
3959
    }
2✔
3960

3961
    ReadTransaction read_1(db_1);
2✔
3962
    ReadTransaction read_2(db_2);
2✔
3963

3964
    const Group& group = read_1;
2✔
3965
    CHECK(compare_groups(read_1, read_2));
2✔
3966
    ConstTableRef table = group.get_table("class_table name");
2✔
3967
    CHECK_EQUAL(table->size(), 8);
2✔
3968
    {
2✔
3969
        const Obj obj = *table->begin();
2✔
3970
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
3971
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
3972
    }
2✔
3973
    {
2✔
3974
        const Obj obj = *(table->begin() + 7);
2✔
3975
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
3976
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2✔
3977
    }
2✔
3978

3979
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
3980
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
3981
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3982

3983
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
3984
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
3985
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3986

3987
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
3988
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3989
}
2✔
3990

3991

3992
// This test checks that it is possible to create, upload, download, and merge
3993
// changesets larger than 16 MB. This test uses less memory than
3994
// Sync_MergeLargeBinary.
3995
TEST(Sync_MergeLargeBinaryReducedMemory)
3996
{
2✔
3997
    // Two binaries are inserted in a transaction such that the total size
3998
    // of the changeset exceeds 16MB. A single set_binary operation does not
3999
    // accept a binary larger than 16MB. Only one changeset is larger than
4000
    // 16 MB in this test.
4001
    size_t binary_sizes[] = {
2✔
4002
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e4), static_cast<size_t>(11e4),
2✔
4003
        static_cast<size_t>(6e4), static_cast<size_t>(12e4), static_cast<size_t>(5e4), static_cast<size_t>(13e4),
2✔
4004
    };
2✔
4005

4006
    TEST_CLIENT_DB(db_1);
2✔
4007
    TEST_CLIENT_DB(db_2);
2✔
4008

4009
    {
2✔
4010
        WriteTransaction wt(db_1);
2✔
4011
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4012
        table->add_column(type_Binary, "column name");
2✔
4013
        std::string str_1(binary_sizes[0], 'a');
2✔
4014
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4015
        std::string str_2(binary_sizes[1], 'b');
2✔
4016
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4017
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
4018
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
4019
        wt.commit();
2✔
4020
    }
2✔
4021

4022
    {
2✔
4023
        WriteTransaction wt(db_1);
2✔
4024
        TableRef table = wt.get_table("class_table name");
2✔
4025
        std::string str_1(binary_sizes[2], 'c');
2✔
4026
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4027
        std::string str_2(binary_sizes[3], 'd');
2✔
4028
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4029
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4030
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4031
        wt.commit();
2✔
4032
    }
2✔
4033

4034
    {
2✔
4035
        WriteTransaction wt(db_2);
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[4], 'e');
2✔
4039
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4040
        std::string str_2(binary_sizes[5], 'f');
2✔
4041
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4042
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4043
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4044
        wt.commit();
2✔
4045
    }
2✔
4046

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

4059
    uint_fast64_t downloaded_bytes_1 = 0;
2✔
4060
    uint_fast64_t downloadable_bytes_1 = 0;
2✔
4061
    uint_fast64_t uploaded_bytes_1 = 0;
2✔
4062
    uint_fast64_t uploadable_bytes_1 = 0;
2✔
4063

4064
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4065
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4066
                                  uint_fast64_t /* snapshot_version */, double, double, int64_t) {
10✔
4067
        downloaded_bytes_1 = downloaded_bytes;
10✔
4068
        downloadable_bytes_1 = downloadable_bytes;
10✔
4069
        uploaded_bytes_1 = uploaded_bytes;
10✔
4070
        uploadable_bytes_1 = uploadable_bytes;
10✔
4071
    };
10✔
4072

4073
    uint_fast64_t downloaded_bytes_2 = 0;
2✔
4074
    uint_fast64_t downloadable_bytes_2 = 0;
2✔
4075
    uint_fast64_t uploaded_bytes_2 = 0;
2✔
4076
    uint_fast64_t uploadable_bytes_2 = 0;
2✔
4077

4078
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4079
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4080
                                  uint_fast64_t /* snapshot_version */, double, double, int64_t) {
8✔
4081
        downloaded_bytes_2 = downloaded_bytes;
8✔
4082
        downloadable_bytes_2 = downloadable_bytes;
8✔
4083
        uploaded_bytes_2 = uploaded_bytes;
8✔
4084
        uploadable_bytes_2 = uploadable_bytes;
8✔
4085
    };
8✔
4086

4087
    {
2✔
4088
        TEST_DIR(dir);
2✔
4089
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4090
        fixture.start();
2✔
4091

4092
        {
2✔
4093
            Session::Config config;
2✔
4094
            config.progress_handler = progress_handler_1;
2✔
4095
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
4096
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4097
        }
2✔
4098

4099
        {
2✔
4100
            Session::Config config;
2✔
4101
            config.progress_handler = progress_handler_2;
2✔
4102
            Session session_2 = fixture.make_session(1, 0, db_2, "/test", std::move(config));
2✔
4103
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4104
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4105
        }
2✔
4106

4107
        {
2✔
4108
            Session::Config config;
2✔
4109
            config.progress_handler = progress_handler_1;
2✔
4110
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
4111
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4112
        }
2✔
4113
    }
2✔
4114

4115
    ReadTransaction read_1(db_1);
2✔
4116
    ReadTransaction read_2(db_2);
2✔
4117

4118
    const Group& group = read_1;
2✔
4119
    CHECK(compare_groups(read_1, read_2));
2✔
4120
    ConstTableRef table = group.get_table("class_table name");
2✔
4121
    CHECK_EQUAL(table->size(), 8);
2✔
4122
    {
2✔
4123
        const Obj obj = *table->begin();
2✔
4124
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4125
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4126
    }
2✔
4127
    {
2✔
4128
        const Obj obj = *(table->begin() + 7);
2✔
4129
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4130
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2✔
4131
    }
2✔
4132

4133
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4134
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4135
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4136

4137
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4138
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4139
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4140

4141
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4142
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4143
}
2✔
4144

4145

4146
// This test checks that it is possible to create, upload, download, and merge
4147
// changesets larger than 16MB.
4148
TEST(Sync_MergeLargeChangesets)
4149
{
2✔
4150
    constexpr int number_of_rows = 200;
2✔
4151

4152
    TEST_CLIENT_DB(db_1);
2✔
4153
    TEST_CLIENT_DB(db_2);
2✔
4154

4155
    {
2✔
4156
        WriteTransaction wt(db_1);
2✔
4157
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4158
        table->add_column(type_Binary, "column name");
2✔
4159
        table->add_column(type_Int, "integer column");
2✔
4160
        wt.commit();
2✔
4161
    }
2✔
4162

4163
    {
2✔
4164
        WriteTransaction wt(db_2);
2✔
4165
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4166
        table->add_column(type_Binary, "column name");
2✔
4167
        table->add_column(type_Int, "integer column");
2✔
4168
        wt.commit();
2✔
4169
    }
2✔
4170

4171
    {
2✔
4172
        WriteTransaction wt(db_1);
2✔
4173
        TableRef table = wt.get_table("class_table name");
2✔
4174
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4175
            table->create_object_with_primary_key(i);
400✔
4176
        }
400✔
4177
        std::string str(100000, 'a');
2✔
4178
        BinaryData bd(str.data(), str.size());
2✔
4179
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4180
            table->get_object(size_t(row)).set("column name", bd);
400✔
4181
            table->get_object(size_t(row)).set("integer column", 2 * row);
400✔
4182
        }
400✔
4183
        wt.commit();
2✔
4184
    }
2✔
4185

4186
    {
2✔
4187
        WriteTransaction wt(db_2);
2✔
4188
        TableRef table = wt.get_table("class_table name");
2✔
4189
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4190
            table->create_object_with_primary_key(i + number_of_rows);
400✔
4191
        }
400✔
4192
        std::string str(100000, 'b');
2✔
4193
        BinaryData bd(str.data(), str.size());
2✔
4194
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4195
            table->get_object(size_t(row)).set("column name", bd);
400✔
4196
            table->get_object(size_t(row)).set("integer column", 2 * row + 1);
400✔
4197
        }
400✔
4198
        wt.commit();
2✔
4199
    }
2✔
4200

4201
    {
2✔
4202
        TEST_DIR(dir);
2✔
4203
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4204

4205
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4206
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4207

4208
        fixture.start();
2✔
4209

4210
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4211
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4212
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4213
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4214
    }
2✔
4215

4216
    ReadTransaction read_1(db_1);
2✔
4217
    ReadTransaction read_2(db_2);
2✔
4218
    const Group& group = read_1;
2✔
4219
    CHECK(compare_groups(read_1, read_2));
2✔
4220
    ConstTableRef table = group.get_table("class_table name");
2✔
4221
    CHECK_EQUAL(table->size(), 2 * number_of_rows);
2✔
4222
}
2✔
4223

4224

4225
TEST(Sync_MergeMultipleChangesets)
4226
{
2✔
4227
    constexpr int number_of_changesets = 100;
2✔
4228
    constexpr int number_of_instructions = 10;
2✔
4229

4230
    TEST_CLIENT_DB(db_1);
2✔
4231
    TEST_CLIENT_DB(db_2);
2✔
4232

4233
    std::atomic<int> id = 0;
2✔
4234

4235
    {
2✔
4236
        WriteTransaction wt(db_1);
2✔
4237
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4238
        table->add_column(type_Int, "integer column");
2✔
4239
        wt.commit();
2✔
4240
    }
2✔
4241

4242
    {
2✔
4243
        WriteTransaction wt(db_2);
2✔
4244
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4245
        table->add_column(type_Int, "integer column");
2✔
4246
        wt.commit();
2✔
4247
    }
2✔
4248

4249
    {
2✔
4250
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4251
            WriteTransaction wt(db_1);
200✔
4252
            TableRef table = wt.get_table("class_table name");
200✔
4253
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4254
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4255
                obj.set("integer column", 2 * j);
2,000✔
4256
            }
2,000✔
4257
            wt.commit();
200✔
4258
        }
200✔
4259
    }
2✔
4260

4261
    {
2✔
4262
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4263
            WriteTransaction wt(db_2);
200✔
4264
            TableRef table = wt.get_table("class_table name");
200✔
4265
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4266
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4267
                obj.set("integer column", 2 * j + 1);
2,000✔
4268
            }
2,000✔
4269
            wt.commit();
200✔
4270
        }
200✔
4271
    }
2✔
4272

4273
    {
2✔
4274
        TEST_DIR(dir);
2✔
4275
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4276

4277

4278
        // Start server and upload changes of first client.
4279
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4280
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4281

4282
        fixture.start_server(0);
2✔
4283
        fixture.start_client(0);
2✔
4284
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4285
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4286
        session_1.detach();
2✔
4287
        // Stop first client.
4288
        fixture.stop_client(0);
2✔
4289

4290
        // Start the second client and upload their changes.
4291
        // Wait to integrate changes from the first client.
4292
        fixture.start_client(1);
2✔
4293
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4294
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4295
    }
2✔
4296

4297
    ReadTransaction read_1(db_1);
2✔
4298
    ReadTransaction read_2(db_2);
2✔
4299
    const Group& group1 = read_1;
2✔
4300
    const Group& group2 = read_2;
2✔
4301
    ConstTableRef table1 = group1.get_table("class_table name");
2✔
4302
    ConstTableRef table2 = group2.get_table("class_table name");
2✔
4303
    CHECK_EQUAL(table1->size(), number_of_changesets * number_of_instructions);
2✔
4304
    CHECK_EQUAL(table2->size(), 2 * number_of_changesets * number_of_instructions);
2✔
4305
}
2✔
4306

4307

4308
#endif // REALM_PLATFORM_WIN32
4309

4310

4311
TEST(Sync_PingTimesOut)
4312
{
2✔
4313
    bool did_fail = false;
2✔
4314
    {
2✔
4315
        TEST_DIR(dir);
2✔
4316
        TEST_CLIENT_DB(db);
2✔
4317

4318
        ClientServerFixture::Config config;
2✔
4319
        config.client_ping_period = 0;  // send ping immediately
2✔
4320
        config.client_pong_timeout = 0; // time out immediately
2✔
4321
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4322

4323
        auto error_handler = [&](Status status, bool) {
2✔
4324
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4325
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4326
            did_fail = true;
2✔
4327
            fixture.stop();
2✔
4328
        };
2✔
4329
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4330

4331
        fixture.start();
2✔
4332

4333
        Session session = fixture.make_bound_session(db);
2✔
4334
        session.wait_for_download_complete_or_client_stopped();
2✔
4335
    }
2✔
4336
    CHECK(did_fail);
2✔
4337
}
2✔
4338

4339

4340
TEST(Sync_ReconnectAfterPingTimeout)
4341
{
2✔
4342
    TEST_DIR(dir);
2✔
4343
    TEST_CLIENT_DB(db);
2✔
4344

4345
    ClientServerFixture::Config config;
2✔
4346
    config.client_ping_period = 0;  // send ping immediately
2✔
4347
    config.client_pong_timeout = 0; // time out immediately
2✔
4348

4349
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4350

4351
    BowlOfStonesSemaphore bowl;
2✔
4352
    auto error_handler = [&](Status status, bool) {
2✔
4353
        if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) {
2✔
4354
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4355
            bowl.add_stone();
2✔
4356
        }
2✔
4357
    };
2✔
4358
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4359
    fixture.start();
2✔
4360

4361
    Session session = fixture.make_bound_session(db, "/test");
2✔
4362
    bowl.get_stone();
2✔
4363
}
2✔
4364

4365

4366
TEST(Sync_UrgentPingIsSent)
4367
{
2✔
4368
    bool did_fail = false;
2✔
4369
    {
2✔
4370
        TEST_DIR(dir);
2✔
4371
        TEST_CLIENT_DB(db);
2✔
4372

4373
        ClientServerFixture::Config config;
2✔
4374
        config.client_pong_timeout = 0; // urgent pings time out immediately
2✔
4375

4376
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4377

4378
        auto error_handler = [&](Status status, bool) {
2✔
4379
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4380
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4381
            did_fail = true;
2✔
4382
            fixture.stop();
2✔
4383
        };
2✔
4384
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4385

4386
        fixture.start();
2✔
4387

4388
        Session session = fixture.make_bound_session(db);
2✔
4389
        session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4390
        session.cancel_reconnect_delay();                       // send an urgent ping
2✔
4391
        session.wait_for_download_complete_or_client_stopped();
2✔
4392
    }
2✔
4393
    CHECK(did_fail);
2✔
4394
}
2✔
4395

4396

4397
TEST(Sync_ServerDiscardDeadConnections)
4398
{
2✔
4399
    TEST_DIR(dir);
2✔
4400
    TEST_CLIENT_DB(db);
2✔
4401

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

4405
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4406

4407
    BowlOfStonesSemaphore bowl;
2✔
4408
    auto error_handler = [&](Status status, bool) {
2✔
4409
        CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4410
        bowl.add_stone();
2✔
4411
    };
2✔
4412
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4413
    fixture.start();
2✔
4414

4415
    Session session = fixture.make_bound_session(db);
2✔
4416
    session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4417
    fixture.set_server_connection_reaper_timeout(0);        // all connections will now be considered dead
2✔
4418
    bowl.get_stone();
2✔
4419
}
2✔
4420

4421

4422
TEST(Sync_Quadratic_Merge)
4423
{
2✔
4424
    size_t num_instructions_1 = 100;
2✔
4425
    size_t num_instructions_2 = 200;
2✔
4426
    REALM_ASSERT(num_instructions_1 >= 3 && num_instructions_2 >= 3);
2✔
4427

4428
    TEST_DIR(server_dir);
2✔
4429
    TEST_CLIENT_DB(db_1);
2✔
4430
    TEST_CLIENT_DB(db_2);
2✔
4431

4432
    // The schema and data is created with
4433
    // n_operations instructions. The instructions are:
4434
    // create table
4435
    // add column
4436
    // create object
4437
    // n_operations - 3 add_int instructions.
4438
    auto create_data = [](DBRef db, size_t n_operations) {
4✔
4439
        WriteTransaction wt(db);
4✔
4440
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
4✔
4441
        table->add_column(type_Int, "i");
4✔
4442
        Obj obj = table->create_object_with_primary_key(1);
4✔
4443
        for (size_t i = 0; i < n_operations - 3; ++i)
592✔
4444
            obj.add_int("i", 1);
588✔
4445
        wt.commit();
4✔
4446
    };
4✔
4447

4448
    create_data(db_1, num_instructions_1);
2✔
4449
    create_data(db_2, num_instructions_2);
2✔
4450

4451
    int num_clients = 2;
2✔
4452
    int num_servers = 1;
2✔
4453
    MultiClientServerFixture fixture{num_clients, num_servers, server_dir, test_context};
2✔
4454
    fixture.start();
2✔
4455

4456
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4457
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4458

4459
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4460
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
4461

4462
    session_1.wait_for_download_complete_or_client_stopped();
2✔
4463
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4464
}
2✔
4465

4466

4467
TEST(Sync_BatchedUploadMessages)
4468
{
2✔
4469
    TEST_DIR(server_dir);
2✔
4470
    TEST_CLIENT_DB(db);
2✔
4471

4472
    ClientServerFixture fixture(server_dir, test_context);
2✔
4473
    fixture.start();
2✔
4474

4475
    {
2✔
4476
        WriteTransaction wt{db};
2✔
4477
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4478
        tr->add_column(type_Int, "integer column");
2✔
4479
        wt.commit();
2✔
4480
    }
2✔
4481

4482
    // Create a lot of changesets. We will attempt to check that
4483
    // they are uploaded in a few upload messages.
4484
    for (int i = 0; i < 400; ++i) {
802✔
4485
        WriteTransaction wt{db};
800✔
4486
        TableRef tr = wt.get_table("class_foo");
800✔
4487
        tr->create_object_with_primary_key(i).set("integer column", i);
800✔
4488
        wt.commit();
800✔
4489
    }
800✔
4490

4491
    Session::Config config;
2✔
4492
    auto pf = util::make_promise_future();
2✔
4493
    config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4494
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t, double,
2✔
4495
                                  double, int64_t) {
4✔
4496
        CHECK_GREATER(uploadable_bytes, 1000);
4✔
4497

4498
        // This is the important check. If the changesets were not batched,
4499
        // there would be callbacks with partial uploaded_bytes.
4500
        // With batching, all uploadable_bytes are uploaded in the same message.
4501
        CHECK(uploaded_bytes == 0 || uploaded_bytes == uploadable_bytes);
4✔
4502
        CHECK_EQUAL(0, downloaded_bytes);
4✔
4503
        CHECK_EQUAL(0, downloadable_bytes);
4✔
4504
        if (uploaded_bytes == uploadable_bytes) {
4✔
4505
            pf.promise.emplace_value();
2✔
4506
        }
2✔
4507
    };
4✔
4508

4509
    Session session = fixture.make_session(db, "/test", std::move(config));
2✔
4510
    session.wait_for_upload_complete_or_client_stopped();
2✔
4511
    pf.future.get();
2✔
4512
}
2✔
4513

4514

4515
TEST(Sync_UploadLogCompactionEnabled)
4516
{
2✔
4517
    TEST_DIR(server_dir);
2✔
4518
    TEST_CLIENT_DB(db_1);
2✔
4519
    TEST_CLIENT_DB(db_2);
2✔
4520

4521
    ClientServerFixture::Config config;
2✔
4522
    config.disable_upload_compaction = false;
2✔
4523
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
4524
    fixture.start();
2✔
4525

4526
    // Create a changeset with lots of overwrites of the
4527
    // same fields.
4528
    {
2✔
4529
        WriteTransaction wt{db_1};
2✔
4530
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4531
        tr->add_column(type_Int, "integer column");
2✔
4532
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4533
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4534
        for (int i = 0; i < 10000; ++i) {
20,002✔
4535
            obj0.set("integer column", i);
20,000✔
4536
            obj1.set("integer column", 2 * i);
20,000✔
4537
        }
20,000✔
4538
        wt.commit();
2✔
4539
    }
2✔
4540

4541
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
4542
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4543

4544
    Session::Config session_config;
2✔
4545
    session_config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4546
                                          uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4547
                                          uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
4548
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
4549
        CHECK_EQUAL(0, uploaded_bytes);
2✔
4550
        CHECK_EQUAL(0, uploadable_bytes);
2✔
4551
        static_cast<void>(snapshot_version);
2✔
4552
        CHECK_NOT_EQUAL(downloadable_bytes, 0);
2✔
4553
    };
2✔
4554

4555
    Session session_2 = fixture.make_session(db_2, "/test", std::move(session_config));
2✔
4556
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4557

4558
    {
2✔
4559
        ReadTransaction rt_1(db_1);
2✔
4560
        ReadTransaction rt_2(db_2);
2✔
4561
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4562
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4563
        CHECK_EQUAL(2, table->size());
2✔
4564
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4565
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4566
    }
2✔
4567
}
2✔
4568

4569

4570
TEST(Sync_UploadLogCompactionDisabled)
4571
{
2✔
4572
    TEST_DIR(server_dir);
2✔
4573
    TEST_CLIENT_DB(db_1);
2✔
4574
    TEST_CLIENT_DB(db_2);
2✔
4575

4576
    ClientServerFixture::Config config;
2✔
4577
    config.disable_upload_compaction = true;
2✔
4578
    config.disable_history_compaction = true;
2✔
4579
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4580
    fixture.start();
2✔
4581

4582
    // Create a changeset with lots of overwrites of the
4583
    // same fields.
4584
    {
2✔
4585
        WriteTransaction wt{db_1};
2✔
4586
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4587
        auto col_int = tr->add_column(type_Int, "integer column");
2✔
4588
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4589
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4590
        for (int i = 0; i < 10000; ++i) {
20,002✔
4591
            obj0.set(col_int, i);
20,000✔
4592
            obj1.set(col_int, 2 * i);
20,000✔
4593
        }
20,000✔
4594
        wt.commit();
2✔
4595
    }
2✔
4596

4597
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
4598
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4599

4600
    Session::Config session_config;
2✔
4601
    session_config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4602
                                          uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4603
                                          uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
4604
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
4605
        CHECK_EQUAL(0, uploaded_bytes);
2✔
4606
        CHECK_EQUAL(0, uploadable_bytes);
2✔
4607
        static_cast<void>(snapshot_version);
2✔
4608
        CHECK_NOT_EQUAL(0, downloadable_bytes);
2✔
4609
    };
2✔
4610

4611
    Session session_2 = fixture.make_session(db_2, "/test", std::move(session_config));
2✔
4612
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4613

4614
    {
2✔
4615
        ReadTransaction rt_1(db_1);
2✔
4616
        ReadTransaction rt_2(db_2);
2✔
4617
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4618
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4619
        CHECK_EQUAL(2, table->size());
2✔
4620
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4621
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4622
    }
2✔
4623
}
2✔
4624

4625

4626
TEST(Sync_ReadOnlyClientSideHistoryTrim)
4627
{
2✔
4628
    TEST_DIR(dir);
2✔
4629
    TEST_CLIENT_DB(db_1);
2✔
4630
    TEST_CLIENT_DB(db_2);
2✔
4631

4632
    ClientServerFixture fixture{dir, test_context};
2✔
4633
    fixture.start();
2✔
4634

4635
    ColKey col_ndx_blob_data;
2✔
4636
    {
2✔
4637
        WriteTransaction wt{db_1};
2✔
4638
        TableRef blobs = wt.get_group().add_table_with_primary_key("class_Blob", type_Int, "id");
2✔
4639
        col_ndx_blob_data = blobs->add_column(type_Binary, "data");
2✔
4640
        blobs->create_object_with_primary_key(1);
2✔
4641
        wt.commit();
2✔
4642
    }
2✔
4643

4644
    Session session_1 = fixture.make_bound_session(db_1, "/foo");
2✔
4645
    Session session_2 = fixture.make_bound_session(db_2, "/foo");
2✔
4646

4647
    std::string blob(0x4000, '\0');
2✔
4648
    for (long i = 0; i < 1024; ++i) {
2,050✔
4649
        {
2,048✔
4650
            WriteTransaction wt{db_1};
2,048✔
4651
            TableRef blobs = wt.get_table("class_Blob");
2,048✔
4652
            blobs->begin()->set(col_ndx_blob_data, BinaryData{blob});
2,048✔
4653
            wt.commit();
2,048✔
4654
        }
2,048✔
4655
        session_1.wait_for_upload_complete_or_client_stopped();
2,048✔
4656
        session_2.wait_for_download_complete_or_client_stopped();
2,048✔
4657
    }
2,048✔
4658

4659
    // Check that the file size is less than 4 MiB. If it is, then the history
4660
    // must have been trimmed, as the combined size of all the blobs is at least
4661
    // 16 MiB.
4662
    CHECK_LESS(util::File{db_1_path}.get_size(), 0x400000);
2✔
4663
}
2✔
4664

4665
// This test creates two objects in a target table and a link list
4666
// in a source table. The first target object is inserted in the link list,
4667
// and later the link is set to the second target object.
4668
// Both the target objects are deleted afterwards. The tests verifies that
4669
// sync works with log compaction turned on.
4670
TEST(Sync_ContainerInsertAndSetLogCompaction)
4671
{
2✔
4672
    TEST_DIR(dir);
2✔
4673
    TEST_CLIENT_DB(db_1);
2✔
4674
    TEST_CLIENT_DB(db_2);
2✔
4675
    ClientServerFixture fixture(dir, test_context);
2✔
4676
    fixture.start();
2✔
4677

4678
    {
2✔
4679
        WriteTransaction wt{db_1};
2✔
4680

4681
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
4682
        ColKey col_ndx = table_target->add_column(type_Int, "value");
2✔
4683
        auto k0 = table_target->create_object_with_primary_key(1).set(col_ndx, 123).get_key();
2✔
4684
        auto k1 = table_target->create_object_with_primary_key(2).set(col_ndx, 456).get_key();
2✔
4685

4686
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
4687
        col_ndx = table_source->add_column_list(*table_target, "target_link");
2✔
4688
        Obj obj = table_source->create_object_with_primary_key(1);
2✔
4689
        LnkLst ll = obj.get_linklist(col_ndx);
2✔
4690
        ll.insert(0, k0);
2✔
4691
        ll.set(0, k1);
2✔
4692

4693
        table_target->remove_object(k1);
2✔
4694
        table_target->remove_object(k0);
2✔
4695

4696
        wt.commit();
2✔
4697
    }
2✔
4698

4699
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4700
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4701

4702
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4703
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4704

4705
    {
2✔
4706
        ReadTransaction rt_1(db_1);
2✔
4707
        ReadTransaction rt_2(db_2);
2✔
4708
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4709
    }
2✔
4710
}
2✔
4711

4712

4713
TEST(Sync_MultipleContainerColumns)
4714
{
2✔
4715
    TEST_DIR(dir);
2✔
4716
    TEST_CLIENT_DB(db_1);
2✔
4717
    TEST_CLIENT_DB(db_2);
2✔
4718
    ClientServerFixture fixture(dir, test_context);
2✔
4719
    fixture.start();
2✔
4720

4721
    {
2✔
4722
        WriteTransaction wt{db_1};
2✔
4723

4724
        TableRef table = wt.get_group().add_table_with_primary_key("class_Table", type_Int, "id");
2✔
4725
        table->add_column_list(type_String, "array1");
2✔
4726
        table->add_column_list(type_String, "array2");
2✔
4727

4728
        Obj row = table->create_object_with_primary_key(1);
2✔
4729
        {
2✔
4730
            Lst<StringData> array1 = row.get_list<StringData>("array1");
2✔
4731
            array1.clear();
2✔
4732
            array1.add("Hello");
2✔
4733
        }
2✔
4734
        {
2✔
4735
            Lst<StringData> array2 = row.get_list<StringData>("array2");
2✔
4736
            array2.clear();
2✔
4737
            array2.add("World");
2✔
4738
        }
2✔
4739

4740
        wt.commit();
2✔
4741
    }
2✔
4742

4743
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4744
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4745

4746
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4747
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4748

4749
    {
2✔
4750
        ReadTransaction rt_1(db_1);
2✔
4751
        ReadTransaction rt_2(db_2);
2✔
4752
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4753

4754
        ConstTableRef table = rt_1.get_table("class_Table");
2✔
4755
        const Obj row = *table->begin();
2✔
4756
        auto array1 = row.get_list<StringData>("array1");
2✔
4757
        auto array2 = row.get_list<StringData>("array2");
2✔
4758
        CHECK_EQUAL(array1.size(), 1);
2✔
4759
        CHECK_EQUAL(array2.size(), 1);
2✔
4760
        CHECK_EQUAL(array1.get(0), "Hello");
2✔
4761
        CHECK_EQUAL(array2.get(0), "World");
2✔
4762
    }
2✔
4763
}
2✔
4764

4765

4766
TEST(Sync_ConnectionStateChange)
4767
{
2✔
4768
    TEST_DIR(dir);
2✔
4769
    TEST_CLIENT_DB(db_1);
2✔
4770
    TEST_CLIENT_DB(db_2);
2✔
4771

4772
    std::vector<ConnectionState> states_1, states_2;
2✔
4773
    {
2✔
4774
        ClientServerFixture fixture(dir, test_context);
2✔
4775
        fixture.start();
2✔
4776

4777
        BowlOfStonesSemaphore bowl_1, bowl_2;
2✔
4778
        auto listener_1 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4779
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4780
            states_1.push_back(state);
6✔
4781
            if (state == ConnectionState::disconnected)
6✔
4782
                bowl_1.add_stone();
2✔
4783
        };
6✔
4784
        auto listener_2 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4785
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4786
            states_2.push_back(state);
6✔
4787
            if (state == ConnectionState::disconnected)
6✔
4788
                bowl_2.add_stone();
2✔
4789
        };
6✔
4790

4791
        Session::Config config_1;
2✔
4792
        config_1.connection_state_change_listener = listener_1;
2✔
4793
        Session session_1 = fixture.make_session(db_1, "/test", std::move(config_1));
2✔
4794
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4795

4796
        Session::Config config_2;
2✔
4797
        config_2.connection_state_change_listener = listener_2;
2✔
4798
        Session session_2 = fixture.make_session(db_2, "/test", std::move(config_2));
2✔
4799
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4800

4801
        fixture.close_server_side_connections();
2✔
4802
        bowl_1.get_stone();
2✔
4803
        bowl_2.get_stone();
2✔
4804
    }
2✔
4805
    std::vector<ConnectionState> reference{ConnectionState::connecting, ConnectionState::connected,
2✔
4806
                                           ConnectionState::disconnected};
2✔
4807
    CHECK(states_1 == reference);
2✔
4808
    CHECK(states_2 == reference);
2✔
4809
}
2✔
4810

4811

4812
TEST(Sync_VerifyServerHistoryAfterLargeUpload)
4813
{
2✔
4814
    TEST_DIR(server_dir);
2✔
4815
    TEST_CLIENT_DB(db);
2✔
4816

4817
    ClientServerFixture fixture{server_dir, test_context};
2✔
4818
    fixture.start();
2✔
4819

4820
    {
2✔
4821
        auto wt = db->start_write();
2✔
4822
        auto table = wt->add_table_with_primary_key("class_table", type_Int, "id");
2✔
4823
        ColKey col = table->add_column(type_Binary, "data");
2✔
4824

4825
        // Create enough data that our changeset cannot be stored contiguously
4826
        // by BinaryColumn (> 16MB).
4827
        std::size_t data_size = 8 * 1024 * 1024;
2✔
4828
        std::string data(data_size, '\0');
2✔
4829
        for (int i = 0; i < 8; ++i) {
18✔
4830
            table->create_object_with_primary_key(i).set(col, BinaryData{data.data(), data.size()});
16✔
4831
        }
16✔
4832

4833
        wt->commit();
2✔
4834

4835
        Session session = fixture.make_session(db, "/test");
2✔
4836
        session.wait_for_upload_complete_or_client_stopped();
2✔
4837
    }
2✔
4838

4839
    {
2✔
4840
        std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
4841
        TestServerHistoryContext context;
2✔
4842
        _impl::ServerHistory history{context};
2✔
4843
        DBRef db = DB::create(history, server_path);
2✔
4844
        {
2✔
4845
            ReadTransaction rt{db};
2✔
4846
            rt.get_group().verify();
2✔
4847
        }
2✔
4848
    }
2✔
4849
}
2✔
4850

4851

4852
TEST(Sync_ServerSideModify_Randomize)
4853
{
2✔
4854
    int num_server_side_transacts = 1200;
2✔
4855
    int num_client_side_transacts = 1200;
2✔
4856

4857
    TEST_DIR(server_dir);
2✔
4858
    TEST_CLIENT_DB(db_2);
2✔
4859

4860
    ClientServerFixture::Config config;
2✔
4861
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4862
    fixture.start();
2✔
4863

4864
    Session session = fixture.make_bound_session(db_2, "/test");
2✔
4865

4866
    std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
4867
    TestServerHistoryContext context;
2✔
4868
    _impl::ServerHistory history_1{context};
2✔
4869
    DBRef db_1 = DB::create(history_1, server_path);
2✔
4870

4871
    auto server_side_program = [num_server_side_transacts, &db_1, &fixture, &session] {
2✔
4872
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
4873
        for (int i = 0; i < num_server_side_transacts; ++i) {
2,402✔
4874
            WriteTransaction wt{db_1};
2,400✔
4875
            TableRef table = wt.get_table("class_foo");
2,400✔
4876
            if (!table) {
2,400✔
4877
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4878
                table->add_column(type_Int, "i");
2✔
4879
            }
2✔
4880
            if (i % 2 == 0)
2,400✔
4881
                table->create_object_with_primary_key(0 - i);
1,200✔
4882
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
4883
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
4884
            wt.commit();
2,400✔
4885
            fixture.inform_server_about_external_change("/test");
2,400✔
4886
            session.wait_for_download_complete_or_client_stopped();
2,400✔
4887
        }
2,400✔
4888
    };
2✔
4889

4890
    auto client_side_program = [num_client_side_transacts, &db_2, &session] {
2✔
4891
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
4892
        for (int i = 0; i < num_client_side_transacts; ++i) {
2,402✔
4893
            WriteTransaction wt{db_2};
2,400✔
4894
            TableRef table = wt.get_table("class_foo");
2,400✔
4895
            if (!table) {
2,400✔
4896
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4897
                table->add_column(type_Int, "i");
2✔
4898
            }
2✔
4899
            if (i % 2 == 0)
2,400✔
4900
                table->create_object_with_primary_key(i);
1,200✔
4901
            ;
2,400✔
4902
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
4903
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
4904
            wt.commit();
2,400✔
4905
            if (i % 16 == 0)
2,400✔
4906
                session.wait_for_upload_complete_or_client_stopped();
150✔
4907
        }
2,400✔
4908
    };
2✔
4909

4910
    ThreadWrapper server_program_thread;
2✔
4911
    server_program_thread.start(std::move(server_side_program));
2✔
4912
    client_side_program();
2✔
4913
    CHECK(!server_program_thread.join());
2✔
4914

4915
    session.wait_for_upload_complete_or_client_stopped();
2✔
4916
    session.wait_for_download_complete_or_client_stopped();
2✔
4917

4918
    ReadTransaction rt_1{db_1};
2✔
4919
    ReadTransaction rt_2{db_2};
2✔
4920
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4921
}
2✔
4922

4923

4924
// This test connects a sync client to the realm cloud service using a SSL
4925
// connection. The purpose of the test is to check that the server's SSL
4926
// certificate is accepted by the client.  The client will connect with an
4927
// invalid token and get an error code back.  The check is that the error is
4928
// not rejected certificate.  The test should be disabled under normal
4929
// circumstances since it requires network access and cloud availability. The
4930
// test might be enabled during testing of SSL functionality.
4931
TEST_IF(Sync_SSL_Certificates, false)
4932
{
×
4933
    TEST_CLIENT_DB(db);
×
4934

4935
    const char* server_address[] = {
×
4936
        "morten-krogh.us1.cloud.realm.io",
×
4937
        "fantastic-cotton-shoes.us1.cloud.realm.io",
×
4938
        "www.realm.io",
×
4939
        "www.yahoo.com",
×
4940
        "www.nytimes.com",
×
4941
        "www.ibm.com",
×
4942
        "www.ssllabs.com",
×
4943
    };
×
4944

4945
    size_t num_servers = sizeof(server_address) / sizeof(server_address[0]);
×
4946

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

4949
    for (size_t i = 0; i < num_servers; ++i) {
×
4950
        Client::Config client_config;
×
4951
        client_config.logger = client_logger;
×
4952
        client_config.reconnect_mode = ReconnectMode::testing;
×
4953
        Client client(client_config);
×
4954

4955
        Session::Config session_config;
×
4956
        session_config.server_address = server_address[i];
×
4957
        session_config.server_port = 443;
×
4958
        session_config.realm_identifier = "/anything";
×
4959
        session_config.protocol_envelope = ProtocolEnvelope::realms;
×
4960

4961
        // Invalid token for the cloud.
4962
        session_config.signed_user_token = g_signed_test_user_token;
×
4963

NEW
4964
        session_config.connection_state_change_listener = [&](ConnectionState state,
×
NEW
4965
                                                              const util::Optional<ErrorInfo>& error_info) {
×
4966
            if (state == ConnectionState::disconnected) {
×
4967
                CHECK(error_info);
×
4968
                client_logger->debug("State change: disconnected, error_code = %1, is_fatal = %2", error_info->status,
×
4969
                                     error_info->is_fatal);
×
4970
                // We expect to get through the SSL handshake but will hit an error due to the wrong token.
4971
                CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed);
×
4972
                client.shutdown();
×
4973
            }
×
4974
        };
×
4975

NEW
4976
        Session session{client, db, nullptr, nullptr, std::move(session_config)};
×
4977
        session.wait_for_download_complete_or_client_stopped();
×
4978
    }
×
4979
}
×
4980

4981

4982
// Testing the custom authorization header name.  The sync protocol does not
4983
// currently use the HTTP Authorization header, so the test is to watch the
4984
// logs and see that the client use the right header name. Proxies and the sync
4985
// server HTTP api use the Authorization header.
4986
TEST(Sync_AuthorizationHeaderName)
4987
{
2✔
4988
    TEST_DIR(dir);
2✔
4989
    TEST_CLIENT_DB(db);
2✔
4990

4991
    const char* authorization_header_name = "X-Alternative-Name";
2✔
4992
    ClientServerFixture::Config config;
2✔
4993
    config.authorization_header_name = authorization_header_name;
2✔
4994
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4995
    fixture.start();
2✔
4996

4997
    Session::Config session_config;
2✔
4998
    session_config.authorization_header_name = authorization_header_name;
2✔
4999

5000
    std::map<std::string, std::string> custom_http_headers;
2✔
5001
    custom_http_headers["Header-Name-1"] = "Header-Value-1";
2✔
5002
    custom_http_headers["Header-Name-2"] = "Header-Value-2";
2✔
5003
    session_config.custom_http_headers = std::move(custom_http_headers);
2✔
5004
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5005

5006
    session.wait_for_download_complete_or_client_stopped();
2✔
5007
}
2✔
5008

5009

5010
TEST(Sync_BadChangeset)
5011
{
2✔
5012
    TEST_DIR(dir);
2✔
5013
    TEST_CLIENT_DB(db);
2✔
5014

5015
    bool did_fail = false;
2✔
5016
    {
2✔
5017
        ClientServerFixture::Config config;
2✔
5018
        config.disable_upload_compaction = true;
2✔
5019
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5020
        fixture.start();
2✔
5021

5022
        {
2✔
5023
            Session session = fixture.make_bound_session(db);
2✔
5024
            session.wait_for_download_complete_or_client_stopped();
2✔
5025
        }
2✔
5026

5027
        {
2✔
5028
            WriteTransaction wt(db);
2✔
5029
            TableRef table = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5030
            table->add_column(type_Int, "i");
2✔
5031
            table->create_object_with_primary_key(5).set_all(123);
2✔
5032
            const ChangesetEncoder::Buffer& buffer = get_replication(db).get_instruction_encoder().buffer();
2✔
5033
            char bad_instruction = 0x3e;
2✔
5034
            const_cast<ChangesetEncoder::Buffer&>(buffer).append(&bad_instruction, 1);
2✔
5035
            wt.commit();
2✔
5036
        }
2✔
5037

5038
        Session::Config session_config;
2✔
5039
        session_config.connection_state_change_listener = [&](ConnectionState state,
2✔
5040
                                                              const util::Optional<ErrorInfo>& error_info) {
6✔
5041
            if (state != ConnectionState::disconnected)
6✔
5042
                return;
4✔
5043
            REALM_ASSERT(error_info);
2✔
5044
            CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
2✔
5045
            CHECK(error_info->is_fatal);
2✔
5046
            did_fail = true;
2✔
5047
            fixture.stop();
2✔
5048
        };
2✔
5049
        Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5050
        session.wait_for_upload_complete_or_client_stopped();
2✔
5051
        session.wait_for_download_complete_or_client_stopped();
2✔
5052
    }
2✔
5053
    CHECK(did_fail);
2✔
5054
}
2✔
5055

5056

5057
TEST(Sync_GoodChangeset_AccentCharacterInFieldName)
5058
{
2✔
5059
    TEST_DIR(dir);
2✔
5060
    TEST_CLIENT_DB(db);
2✔
5061

5062
    bool did_fail = false;
2✔
5063
    {
2✔
5064
        ClientServerFixture::Config config;
2✔
5065
        config.disable_upload_compaction = true;
2✔
5066
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5067
        fixture.start();
2✔
5068

5069
        {
2✔
5070
            Session session = fixture.make_bound_session(db);
2✔
5071
        }
2✔
5072

5073
        {
2✔
5074
            WriteTransaction wt(db);
2✔
5075
            TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
5076
            table->add_column(type_Int, "prógram");
2✔
5077
            table->add_column(type_Int, "program");
2✔
5078
            auto obj = table->create_object_with_primary_key(1);
2✔
5079
            obj.add_int("program", 42);
2✔
5080
            wt.commit();
2✔
5081
        }
2✔
5082

5083
        Session::Config session_config;
2✔
5084
        session_config.connection_state_change_listener = [&](ConnectionState state,
2✔
5085
                                                              const util::Optional<ErrorInfo>) {
4✔
5086
            if (state != ConnectionState::disconnected)
4✔
5087
                return;
4✔
5088
            did_fail = true;
×
5089
            fixture.stop();
×
5090
        };
×
5091
        Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5092
        session.wait_for_upload_complete_or_client_stopped();
2✔
5093
    }
2✔
5094
    CHECK_NOT(did_fail);
2✔
5095
}
2✔
5096

5097

5098
namespace issue2104 {
5099

5100
class ServerHistoryContext : public _impl::ServerHistory::Context {
5101
public:
5102
    ServerHistoryContext() {}
×
5103

5104
    std::mt19937_64& server_history_get_random() noexcept override
5105
    {
×
5106
        return m_random;
×
5107
    }
×
5108

5109
private:
5110
    std::mt19937_64 m_random;
5111
};
5112

5113
} // namespace issue2104
5114

5115
// This test reproduces a slow merge seen in issue 2104.
5116
// The test uses a user supplied Realm and a changeset
5117
// from a client.
5118
// The test uses a user supplied Realm that is very large
5119
// and not kept in the repo. The realm has checksum 3693867489.
5120
//
5121
// This test might be modified to avoid having a large Realm
5122
// (96 MB uncompressed) in the repo.
5123
TEST_IF(Sync_Issue2104, false)
5124
{
×
5125
    TEST_DIR(dir);
×
5126

5127
    // Save a snapshot of the server Realm file.
5128
    std::string realm_path = "issue_2104_server.realm";
×
5129
    std::string realm_path_copy = util::File::resolve("issue_2104.realm", dir);
×
5130
    util::File::copy(realm_path, realm_path_copy);
×
5131

5132
    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 "
×
5133
                                "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 "
×
5134
                                "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 "
×
5135
                                "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 "
×
5136
                                "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 "
×
5137
                                "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 "
×
5138
                                "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 "
×
5139
                                "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 "
×
5140
                                "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 "
×
5141
                                "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 "
×
5142
                                "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 "
×
5143
                                "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 "
×
5144
                                "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 "
×
5145
                                "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 "
×
5146
                                "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 "
×
5147
                                "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 "
×
5148
                                "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 "
×
5149
                                "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 "
×
5150
                                "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 "
×
5151
                                "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 "
×
5152
                                "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 "
×
5153
                                "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 "
×
5154
                                "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 "
×
5155
                                "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 "
×
5156
                                "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 "
×
5157
                                "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 "
×
5158
                                "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 "
×
5159
                                "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 "
×
5160
                                "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 "
×
5161
                                "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 "
×
5162
                                "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 "
×
5163
                                "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 "
×
5164
                                "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 "
×
5165
                                "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 "
×
5166
                                "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 "
×
5167
                                "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 "
×
5168
                                "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 "
×
5169
                                "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 "
×
5170
                                "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 "
×
5171
                                "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 "
×
5172
                                "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 "
×
5173
                                "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 "
×
5174
                                "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 "
×
5175
                                "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 "
×
5176
                                "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 "
×
5177
                                "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 "
×
5178
                                "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 "
×
5179
                                "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 "
×
5180
                                "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 "
×
5181
                                "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 "
×
5182
                                "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 "
×
5183
                                "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 "
×
5184
                                "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 "
×
5185
                                "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 "
×
5186
                                "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 "
×
5187
                                "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 "
×
5188
                                "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 "
×
5189
                                "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 "
×
5190
                                "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 "
×
5191
                                "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 "
×
5192
                                "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 "
×
5193
                                "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 "
×
5194
                                "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 "
×
5195
                                "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 "
×
5196
                                "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 "
×
5197
                                "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 "
×
5198
                                "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 "
×
5199
                                "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 "
×
5200
                                "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 "
×
5201
                                "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 "
×
5202
                                "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 "
×
5203
                                "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 "
×
5204
                                "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 "
×
5205
                                "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 "
×
5206
                                "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 "
×
5207
                                "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 "
×
5208
                                "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 "
×
5209
                                "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 "
×
5210
                                "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 "
×
5211
                                "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 "
×
5212
                                "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 "
×
5213
                                "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 "
×
5214
                                "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 "
×
5215
                                "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 "
×
5216
                                "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 "
×
5217
                                "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 "
×
5218
                                "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 "
×
5219
                                "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 "
×
5220
                                "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 "
×
5221
                                "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 "
×
5222
                                "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 "
×
5223
                                "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 "
×
5224
                                "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 "
×
5225
                                "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 "
×
5226
                                "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 "
×
5227
                                "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 "
×
5228
                                "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 "
×
5229
                                "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 "
×
5230
                                "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 "
×
5231
                                "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 "
×
5232
                                "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 "
×
5233
                                "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 "
×
5234
                                "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 "
×
5235
                                "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 "
×
5236
                                "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 "
×
5237
                                "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 "
×
5238
                                "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 "
×
5239
                                "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 "
×
5240
                                "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 "
×
5241
                                "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 "
×
5242
                                "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 "
×
5243
                                "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 "
×
5244
                                "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 "
×
5245
                                "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 "
×
5246
                                "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 "
×
5247
                                "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 "
×
5248
                                "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 "
×
5249
                                "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 "
×
5250
                                "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 "
×
5251
                                "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 "
×
5252
                                "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 "
×
5253
                                "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 "
×
5254
                                "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 "
×
5255
                                "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 "
×
5256
                                "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 "
×
5257
                                "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 "
×
5258
                                "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 "
×
5259
                                "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 "
×
5260
                                "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 "
×
5261
                                "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 "
×
5262
                                "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 "
×
5263
                                "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 "
×
5264
                                "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 "
×
5265
                                "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 "
×
5266
                                "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 "
×
5267
                                "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 "
×
5268
                                "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 "
×
5269
                                "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 "
×
5270
                                "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 "
×
5271
                                "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 "
×
5272
                                "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 "
×
5273
                                "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 "
×
5274
                                "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 "
×
5275
                                "1D 0B AC 01 0D 00 1C 00 1E 0B B5 01 0D 00 1F 00 1F 0B AD 01 0C 00 1E";
×
5276

5277
    std::vector<char> changeset_vec;
×
5278
    {
×
5279
        std::istringstream in{changeset_hex};
×
5280
        int n;
×
5281
        in >> std::hex >> n;
×
5282
        while (in) {
×
5283
            REALM_ASSERT(n >= 0 && n <= 255);
×
5284
            changeset_vec.push_back(n);
×
5285
            in >> std::hex >> n;
×
5286
        }
×
5287
    }
×
5288

5289
    BinaryData changeset_bin{changeset_vec.data(), changeset_vec.size()};
×
5290

5291
    file_ident_type client_file_ident = 51;
×
5292
    timestamp_type origin_timestamp = 103573722140;
×
5293
    file_ident_type origin_file_ident = 0;
×
5294
    version_type client_version = 2;
×
5295
    version_type last_integrated_server_version = 0;
×
5296
    UploadCursor upload_cursor{client_version, last_integrated_server_version};
×
5297

5298
    _impl::ServerHistory::IntegratableChangeset integratable_changeset{
×
5299
        client_file_ident, origin_timestamp, origin_file_ident, upload_cursor, changeset_bin};
×
5300

5301
    _impl::ServerHistory::IntegratableChangesets integratable_changesets;
×
5302
    integratable_changesets[client_file_ident].changesets.push_back(integratable_changeset);
×
5303

5304
    issue2104::ServerHistoryContext history_context;
×
5305
    _impl::ServerHistory history{history_context};
×
5306
    DBRef db = DB::create(history, realm_path_copy);
×
5307

5308
    VersionInfo version_info;
×
5309
    bool backup_whole_realm;
×
5310
    _impl::ServerHistory::IntegrationResult result;
×
5311
    history.integrate_client_changesets(integratable_changesets, version_info, backup_whole_realm, result,
×
5312
                                        *test_context.logger);
×
5313
}
×
5314

5315

5316
TEST(Sync_RunServerWithoutPublicKey)
5317
{
2✔
5318
    TEST_CLIENT_DB(db);
2✔
5319
    TEST_DIR(server_dir);
2✔
5320
    ClientServerFixture::Config config;
2✔
5321
    config.server_public_key_path = {};
2✔
5322
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5323
    fixture.start();
2✔
5324

5325
    // Server must accept an unsigned token when a public key is not passed to
5326
    // it
5327
    {
2✔
5328
        Session session = fixture.make_bound_session(db, "/test", g_unsigned_test_user_token);
2✔
5329
        session.wait_for_download_complete_or_client_stopped();
2✔
5330
    }
2✔
5331

5332
    // Server must also accept a signed token when a public key is not passed to
5333
    // it
5334
    {
2✔
5335
        Session session = fixture.make_bound_session(db, "/test");
2✔
5336
        session.wait_for_download_complete_or_client_stopped();
2✔
5337
    }
2✔
5338
}
2✔
5339

5340

5341
TEST(Sync_ServerSideEncryption)
5342
{
2✔
5343
    TEST_CLIENT_DB(db);
2✔
5344
    {
2✔
5345
        WriteTransaction wt(db);
2✔
5346
        wt.get_group().add_table_with_primary_key("class_Test", type_Int, "id");
2✔
5347
        wt.commit();
2✔
5348
    }
2✔
5349

5350
    TEST_DIR(server_dir);
2✔
5351
    bool always_encrypt = true;
2✔
5352
    std::string server_path;
2✔
5353
    {
2✔
5354
        ClientServerFixture::Config config;
2✔
5355
        config.server_encryption_key = crypt_key_2(always_encrypt);
2✔
5356
        ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5357
        fixture.start();
2✔
5358

5359
        Session session = fixture.make_bound_session(db, "/test");
2✔
5360
        session.wait_for_upload_complete_or_client_stopped();
2✔
5361

5362
        server_path = fixture.map_virtual_to_real_path("/test");
2✔
5363
    }
2✔
5364

5365
    const char* encryption_key = crypt_key(always_encrypt);
2✔
5366
    Group group{server_path, encryption_key};
2✔
5367
    CHECK(group.has_table("class_Test"));
2✔
5368
}
2✔
5369

5370
TEST(Sync_LogCompaction_EraseObject_LinkList)
5371
{
2✔
5372
    TEST_DIR(dir);
2✔
5373
    TEST_CLIENT_DB(db_1);
2✔
5374
    TEST_CLIENT_DB(db_2);
2✔
5375
    ClientServerFixture::Config config;
2✔
5376

5377
    // Log comapction is true by default, but we emphasize it.
5378
    config.disable_upload_compaction = false;
2✔
5379
    config.disable_download_compaction = false;
2✔
5380

5381
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5382
    fixture.start();
2✔
5383

5384
    {
2✔
5385
        WriteTransaction wt{db_1};
2✔
5386

5387
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
5388
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
5389
        auto col_key = table_source->add_column_list(*table_target, "target_link");
2✔
5390

5391
        auto k0 = table_target->create_object_with_primary_key(1).get_key();
2✔
5392
        auto k1 = table_target->create_object_with_primary_key(2).get_key();
2✔
5393

5394
        auto ll = table_source->create_object_with_primary_key(0).get_linklist_ptr(col_key);
2✔
5395
        ll->add(k0);
2✔
5396
        ll->add(k1);
2✔
5397
        CHECK_EQUAL(ll->size(), 2);
2✔
5398
        wt.commit();
2✔
5399
    }
2✔
5400

5401
    {
2✔
5402
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5403
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5404

5405
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5406
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5407
    }
2✔
5408

5409
    {
2✔
5410
        WriteTransaction wt{db_1};
2✔
5411

5412
        TableRef table_source = wt.get_table("class_source");
2✔
5413
        TableRef table_target = wt.get_table("class_target");
2✔
5414

5415
        CHECK_EQUAL(table_source->size(), 1);
2✔
5416
        CHECK_EQUAL(table_target->size(), 2);
2✔
5417

5418
        table_target->get_object(1).remove();
2✔
5419
        table_target->get_object(0).remove();
2✔
5420

5421
        table_source->get_object(0).remove();
2✔
5422
        wt.commit();
2✔
5423
    }
2✔
5424

5425
    {
2✔
5426
        WriteTransaction wt{db_2};
2✔
5427

5428
        TableRef table_source = wt.get_table("class_source");
2✔
5429
        TableRef table_target = wt.get_table("class_target");
2✔
5430
        auto col_key = table_source->get_column_key("target_link");
2✔
5431

5432
        CHECK_EQUAL(table_source->size(), 1);
2✔
5433
        CHECK_EQUAL(table_target->size(), 2);
2✔
5434

5435
        auto k0 = table_target->begin()->get_key();
2✔
5436

5437
        auto ll = table_source->get_object(0).get_linklist_ptr(col_key);
2✔
5438
        ll->add(k0);
2✔
5439
        wt.commit();
2✔
5440
    }
2✔
5441

5442
    {
2✔
5443
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5444
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5445
    }
2✔
5446

5447
    {
2✔
5448
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5449
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
5450
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5451
    }
2✔
5452

5453
    {
2✔
5454
        ReadTransaction rt{db_2};
2✔
5455

5456
        ConstTableRef table_source = rt.get_group().get_table("class_source");
2✔
5457
        ConstTableRef table_target = rt.get_group().get_table("class_target");
2✔
5458

5459
        CHECK_EQUAL(table_source->size(), 0);
2✔
5460
        CHECK_EQUAL(table_target->size(), 0);
2✔
5461
    }
2✔
5462
}
2✔
5463

5464

5465
// This test could trigger the assertion that the row_for_object_id cache is
5466
// valid before the cache was properly invalidated in the case of a short
5467
// circuited sync replicator.
5468
TEST(Sync_CreateObjects_EraseObjects)
5469
{
2✔
5470
    TEST_DIR(dir);
2✔
5471
    TEST_CLIENT_DB(db_1);
2✔
5472
    TEST_CLIENT_DB(db_2);
2✔
5473
    ClientServerFixture fixture(dir, test_context);
2✔
5474
    fixture.start();
2✔
5475

5476
    Session session_1 = fixture.make_bound_session(db_1);
2✔
5477
    Session session_2 = fixture.make_bound_session(db_2);
2✔
5478

5479
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
5480
        TableRef table = wt.get_group().add_table_with_primary_key("class_persons", type_Int, "id");
2✔
5481
        table->create_object_with_primary_key(1);
2✔
5482
        table->create_object_with_primary_key(2);
2✔
5483
    });
2✔
5484
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5485
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5486

5487
    write_transaction(db_1, [&](WriteTransaction& wt) {
2✔
5488
        TableRef table = wt.get_table("class_persons");
2✔
5489
        CHECK_EQUAL(table->size(), 2);
2✔
5490
        table->get_object(0).remove();
2✔
5491
        table->get_object(0).remove();
2✔
5492
    });
2✔
5493
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5494
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5495
}
2✔
5496

5497

5498
TEST(Sync_CreateDeleteCreateTableWithPrimaryKey)
5499
{
2✔
5500
    TEST_DIR(dir);
2✔
5501
    TEST_CLIENT_DB(db);
2✔
5502
    ClientServerFixture fixture(dir, test_context);
2✔
5503
    fixture.start();
2✔
5504

5505
    Session session = fixture.make_bound_session(db);
2✔
5506

5507
    write_transaction(db, [](WriteTransaction& wt) {
2✔
5508
        TableRef table = wt.get_group().add_table_with_primary_key("class_t", type_Int, "pk");
2✔
5509
        wt.get_group().remove_table(table->get_key());
2✔
5510
        table = wt.get_group().add_table_with_primary_key("class_t", type_String, "pk");
2✔
5511
    });
2✔
5512
    session.wait_for_upload_complete_or_client_stopped();
2✔
5513
    session.wait_for_download_complete_or_client_stopped();
2✔
5514
}
2✔
5515

5516
template <typename T>
5517
T sequence_next()
5518
{
5519
    REALM_UNREACHABLE();
5520
}
5521

5522
template <>
5523
ObjectId sequence_next()
5524
{
8✔
5525
    return ObjectId::gen();
8✔
5526
}
8✔
5527

5528
template <>
5529
UUID sequence_next()
5530
{
8✔
5531
    union {
8✔
5532
        struct {
8✔
5533
            uint64_t upper;
8✔
5534
            uint64_t lower;
8✔
5535
        } ints;
8✔
5536
        UUID::UUIDBytes bytes;
8✔
5537
    } u;
8✔
5538
    static uint64_t counter = test_util::random_int(0, 1000);
8✔
5539
    u.ints.upper = ++counter;
8✔
5540
    u.ints.lower = ++counter;
8✔
5541
    return UUID{u.bytes};
8✔
5542
}
8✔
5543

5544
template <>
5545
Int sequence_next()
5546
{
8✔
5547
    static Int count = test_util::random_int(-1000, 1000);
8✔
5548
    return ++count;
8✔
5549
}
8✔
5550

5551
template <>
5552
String sequence_next()
5553
{
4✔
5554
    static std::string str;
4✔
5555
    static Int sequence = test_util::random_int(-1000, 1000);
4✔
5556
    str = util::format("string sequence %1", ++sequence);
4✔
5557
    return String(str);
4✔
5558
}
4✔
5559

5560
NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util::Optional<Int>,
5561
                         util::Optional<ObjectId>, util::Optional<UUID>)
5562
{
14✔
5563
    using underlying_type = typename util::RemoveOptional<TEST_TYPE>::type;
14✔
5564
    constexpr bool is_optional = !std::is_same_v<underlying_type, TEST_TYPE>;
14✔
5565
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
14✔
5566

5567
    TEST_CLIENT_DB(db_1);
14✔
5568
    TEST_CLIENT_DB(db_2);
14✔
5569

5570
    TEST_DIR(dir);
14✔
5571
    fixtures::ClientServerFixture fixture{dir, test_context};
14✔
5572
    fixture.start();
14✔
5573

5574
    Session session_1 = fixture.make_session(db_1, "/test");
14✔
5575
    Session session_2 = fixture.make_session(db_2, "/test");
14✔
5576

5577
    TEST_TYPE obj_1_id;
14✔
5578
    TEST_TYPE obj_2_id;
14✔
5579

5580
    TEST_TYPE default_or_null{};
14✔
5581
    if constexpr (std::is_same_v<TEST_TYPE, String>) {
14✔
5582
        default_or_null = "";
2✔
5583
    }
2✔
5584
    if constexpr (is_optional) {
14✔
5585
        CHECK(!default_or_null);
6✔
5586
    }
6✔
5587

5588
    {
14✔
5589
        WriteTransaction tr{db_1};
14✔
5590
        auto table_1 = tr.get_group().add_table_with_primary_key("class_Table1", type, "id", is_optional);
14✔
5591
        auto table_2 = tr.get_group().add_table_with_primary_key("class_Table2", type, "id", is_optional);
14✔
5592
        table_1->add_column_list(type, "oids", is_optional);
14✔
5593

5594
        auto obj_1 = table_1->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5595
        auto obj_2 = table_2->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5596
        if constexpr (is_optional) {
14✔
5597
            table_2->create_object_with_primary_key(default_or_null);
6✔
5598
        }
6✔
5599

5600
        auto list = obj_1.template get_list<TEST_TYPE>("oids");
14✔
5601
        obj_1_id = obj_1.template get<TEST_TYPE>("id");
14✔
5602
        obj_2_id = obj_2.template get<TEST_TYPE>("id");
14✔
5603
        list.insert(0, obj_2_id);
14✔
5604
        list.insert(1, default_or_null);
14✔
5605
        list.add(default_or_null);
14✔
5606

5607
        tr.commit();
14✔
5608
    }
14✔
5609

5610
    session_1.wait_for_upload_complete_or_client_stopped();
14✔
5611
    session_2.wait_for_download_complete_or_client_stopped();
14✔
5612

5613
    {
14✔
5614
        ReadTransaction tr{db_2};
14✔
5615
        auto table_1 = tr.get_table("class_Table1");
14✔
5616
        auto table_2 = tr.get_table("class_Table2");
14✔
5617
        auto obj_1 = *table_1->begin();
14✔
5618
        auto obj_2 = table_2->find_first(table_2->get_column_key("id"), obj_2_id);
14✔
5619
        CHECK(obj_2);
14✔
5620
        auto list = obj_1.get_list<TEST_TYPE>("oids");
14✔
5621
        CHECK_EQUAL(obj_1.template get<TEST_TYPE>("id"), obj_1_id);
14✔
5622
        CHECK_EQUAL(list.size(), 3);
14✔
5623
        CHECK_NOT(list.is_null(0));
14✔
5624
        CHECK_EQUAL(list.get(0), obj_2_id);
14✔
5625
        CHECK_EQUAL(list.get(1), default_or_null);
14✔
5626
        CHECK_EQUAL(list.get(2), default_or_null);
14✔
5627
        if constexpr (is_optional) {
14✔
5628
            auto obj_3 = table_2->find_first_null(table_2->get_column_key("id"));
6✔
5629
            CHECK(obj_3);
6✔
5630
            CHECK(list.is_null(1));
6✔
5631
            CHECK(list.is_null(2));
6✔
5632
        }
6✔
5633
    }
14✔
5634
}
14✔
5635

5636
TEST(Sync_Mixed)
5637
{
2✔
5638
    // Test replication and synchronization of Mixed values and lists.
5639
    DBOptions options;
2✔
5640
    options.logger = test_context.logger;
2✔
5641
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
5642
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
5643
    auto db_1 = DB::create(make_client_replication(), db_1_path, options);
2✔
5644
    auto db_2 = DB::create(make_client_replication(), db_2_path, options);
2✔
5645

5646
    TEST_DIR(dir);
2✔
5647
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5648
    fixture.start();
2✔
5649

5650
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5651
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5652

5653
    {
2✔
5654
        WriteTransaction tr{db_1};
2✔
5655
        auto& g = tr.get_group();
2✔
5656
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5657
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5658
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5659
        foos->add_column(type_Mixed, "value", true);
2✔
5660
        foos->add_column_list(type_Mixed, "values");
2✔
5661

5662
        auto foo = foos->create_object_with_primary_key(123);
2✔
5663
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5664
        auto fop = fops->create_object_with_primary_key(456);
2✔
5665

5666
        foo.set("value", Mixed(6.2f));
2✔
5667
        auto values = foo.get_list<Mixed>("values");
2✔
5668
        values.insert(0, StringData("A"));
2✔
5669
        values.insert(1, ObjLink{bars->get_key(), bar.get_key()});
2✔
5670
        values.insert(2, ObjLink{fops->get_key(), fop.get_key()});
2✔
5671
        values.insert(3, 123.f);
2✔
5672

5673
        tr.commit();
2✔
5674
    }
2✔
5675

5676
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5677
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5678

5679
    {
2✔
5680
        ReadTransaction tr{db_2};
2✔
5681

5682
        auto foos = tr.get_table("class_Foo");
2✔
5683
        auto bars = tr.get_table("class_Bar");
2✔
5684
        auto fops = tr.get_table("class_Fop");
2✔
5685

5686
        CHECK_EQUAL(foos->size(), 1);
2✔
5687
        CHECK_EQUAL(bars->size(), 1);
2✔
5688
        CHECK_EQUAL(fops->size(), 1);
2✔
5689

5690
        auto foo = *foos->begin();
2✔
5691
        auto value = foo.get<Mixed>("value");
2✔
5692
        CHECK_EQUAL(value, Mixed{6.2f});
2✔
5693
        auto values = foo.get_list<Mixed>("values");
2✔
5694
        CHECK_EQUAL(values.size(), 4);
2✔
5695

5696
        auto v0 = values.get(0);
2✔
5697
        auto v1 = values.get(1);
2✔
5698
        auto v2 = values.get(2);
2✔
5699
        auto v3 = values.get(3);
2✔
5700

5701
        auto l1 = v1.get_link();
2✔
5702
        auto l2 = v2.get_link();
2✔
5703

5704
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5705
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5706

5707
        CHECK_EQUAL(v0, Mixed{"A"});
2✔
5708
        CHECK_EQUAL(l1_table, bars);
2✔
5709
        CHECK_EQUAL(l2_table, fops);
2✔
5710
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5711
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5712
        CHECK_EQUAL(v3, Mixed{123.f});
2✔
5713
    }
2✔
5714
}
2✔
5715

5716
/*
5717
TEST(Sync_TypedLinks)
5718
{
5719
    // Test replication and synchronization of Mixed values and lists.
5720

5721
    TEST_CLIENT_DB(db_1);
5722
    TEST_CLIENT_DB(db_2);
5723

5724
    TEST_DIR(dir);
5725
    fixtures::ClientServerFixture fixture{dir, test_context};
5726
    fixture.start();
5727

5728
    Session session_1 = fixture.make_session(db_1, "/test");
5729
    Session session_2 = fixture.make_session(db_2, "/test");
5730

5731
    write_transaction(db_1, [](WriteTransaction& tr) {
5732
        auto& g = tr.get_group();
5733
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
5734
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
5735
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
5736
        foos->add_column(type_TypedLink, "link");
5737

5738
        auto foo1 = foos->create_object_with_primary_key(123);
5739
        auto foo2 = foos->create_object_with_primary_key(456);
5740
        auto bar = bars->create_object_with_primary_key("Hello");
5741
        auto fop = fops->create_object_with_primary_key(456);
5742

5743
        foo1.set("link", ObjLink(bars->get_key(), bar.get_key()));
5744
        foo2.set("link", ObjLink(fops->get_key(), fop.get_key()));
5745
    });
5746

5747
    session_1.wait_for_upload_complete_or_client_stopped();
5748
    session_2.wait_for_download_complete_or_client_stopped();
5749

5750
    {
5751
        ReadTransaction tr{db_2};
5752

5753
        auto foos = tr.get_table("class_Foo");
5754
        auto bars = tr.get_table("class_Bar");
5755
        auto fops = tr.get_table("class_Fop");
5756

5757
        CHECK_EQUAL(foos->size(), 2);
5758
        CHECK_EQUAL(bars->size(), 1);
5759
        CHECK_EQUAL(fops->size(), 1);
5760

5761
        auto it = foos->begin();
5762
        auto l1 = it->get<ObjLink>("link");
5763
        ++it;
5764
        auto l2 = it->get<ObjLink>("link");
5765

5766
        auto l1_table = tr.get_table(l1.get_table_key());
5767
        auto l2_table = tr.get_table(l2.get_table_key());
5768

5769
        CHECK_EQUAL(l1_table, bars);
5770
        CHECK_EQUAL(l2_table, fops);
5771
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
5772
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
5773
    }
5774
}
5775
*/
5776

5777
TEST(Sync_Dictionary)
5778
{
2✔
5779
    // Test replication and synchronization of Mixed values and lists.
5780

5781
    TEST_CLIENT_DB(db_1);
2✔
5782
    TEST_CLIENT_DB(db_2);
2✔
5783

5784
    TEST_DIR(dir);
2✔
5785
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5786
    fixture.start();
2✔
5787

5788
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5789
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5790

5791
    Timestamp now{std::chrono::system_clock::now()};
2✔
5792

5793
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5794
        auto& g = tr.get_group();
2✔
5795
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5796
        auto col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
5797
        auto col_dict_str = foos->add_column_dictionary(type_String, "str_dict", true);
2✔
5798

5799
        auto foo = foos->create_object_with_primary_key(123);
2✔
5800

5801
        auto dict = foo.get_dictionary(col_dict);
2✔
5802
        dict.insert("hello", "world");
2✔
5803
        dict.insert("cnt", 7);
2✔
5804
        dict.insert("when", now);
2✔
5805

5806
        auto dict_str = foo.get_dictionary(col_dict_str);
2✔
5807
        dict_str.insert("some", "text");
2✔
5808
        dict_str.insert("nothing", null());
2✔
5809
    });
2✔
5810

5811
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5812
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5813

5814
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
5815
        auto foos = tr.get_table("class_Foo");
2✔
5816
        CHECK_EQUAL(foos->size(), 1);
2✔
5817

5818
        auto it = foos->begin();
2✔
5819
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
5820
        CHECK(dict.get_value_data_type() == type_Mixed);
2✔
5821
        CHECK_EQUAL(dict.size(), 3);
2✔
5822

5823
        auto col_dict_str = foos->get_column_key("str_dict");
2✔
5824
        auto dict_str = it->get_dictionary(col_dict_str);
2✔
5825
        CHECK(col_dict_str.is_nullable());
2✔
5826
        CHECK(dict_str.get_value_data_type() == type_String);
2✔
5827
        CHECK_EQUAL(dict_str.size(), 2);
2✔
5828

5829
        Mixed val = dict["hello"];
2✔
5830
        CHECK_EQUAL(val.get_string(), "world");
2✔
5831
        val = dict.get("cnt");
2✔
5832
        CHECK_EQUAL(val.get_int(), 7);
2✔
5833
        val = dict.get("when");
2✔
5834
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5835

5836
        dict.erase("cnt");
2✔
5837
        dict.insert("hello", "goodbye");
2✔
5838
    });
2✔
5839

5840
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
5841
    session_1.wait_for_download_complete_or_client_stopped();
2✔
5842

5843
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5844
        auto foos = tr.get_table("class_Foo");
2✔
5845
        CHECK_EQUAL(foos->size(), 1);
2✔
5846

5847
        auto it = foos->begin();
2✔
5848
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
5849
        CHECK_EQUAL(dict.size(), 2);
2✔
5850

5851
        Mixed val = dict["hello"];
2✔
5852
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
5853
        val = dict.get("when");
2✔
5854
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5855

5856
        dict.clear();
2✔
5857
    });
2✔
5858

5859
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5860
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5861

5862
    {
2✔
5863
        ReadTransaction read_1{db_1};
2✔
5864
        ReadTransaction read_2{db_2};
2✔
5865
        // tr.get_group().to_json(std::cout);
5866

5867
        auto foos = read_2.get_table("class_Foo");
2✔
5868

5869
        CHECK_EQUAL(foos->size(), 1);
2✔
5870

5871
        auto it = foos->begin();
2✔
5872
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
5873
        CHECK_EQUAL(dict.size(), 0);
2✔
5874

5875
        CHECK(compare_groups(read_1, read_2));
2✔
5876
    }
2✔
5877
}
2✔
5878

5879
TEST(Sync_CollectionInMixed)
5880
{
2✔
5881
    TEST_CLIENT_DB(db_1);
2✔
5882
    TEST_CLIENT_DB(db_2);
2✔
5883

5884
    TEST_DIR(dir);
2✔
5885
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5886
    fixture.start();
2✔
5887

5888
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5889
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5890

5891
    Timestamp now{std::chrono::system_clock::now()};
2✔
5892

5893
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5894
        auto& g = tr.get_group();
2✔
5895
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
5896
        auto col_any = table->add_column(type_Mixed, "any");
2✔
5897

5898
        auto foo = table->create_object_with_primary_key(123);
2✔
5899

5900
        // Create dictionary in Mixed property
5901
        foo.set_collection(col_any, CollectionType::Dictionary);
2✔
5902
        auto dict = foo.get_dictionary_ptr(col_any);
2✔
5903
        dict->insert("hello", "world");
2✔
5904
        dict->insert("cnt", 7);
2✔
5905
        dict->insert("when", now);
2✔
5906
        // Insert a List in a Dictionary
5907
        dict->insert_collection("list", CollectionType::List);
2✔
5908
        auto l = dict->get_list("list");
2✔
5909
        l->add(5);
2✔
5910
        l->insert_collection(1, CollectionType::List);
2✔
5911
        l->get_list(1)->add(7);
2✔
5912

5913
        auto bar = table->create_object_with_primary_key(456);
2✔
5914

5915
        // Create list in Mixed property
5916
        bar.set_collection(col_any, CollectionType::List);
2✔
5917
        auto list = bar.get_list_ptr<Mixed>(col_any);
2✔
5918
        list->add("John");
2✔
5919
        list->insert(0, 5);
2✔
5920
    });
2✔
5921

5922
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5923
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5924

5925
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
5926
        auto table = tr.get_table("class_Table");
2✔
5927
        auto col_any = table->get_column_key("any");
2✔
5928
        CHECK_EQUAL(table->size(), 2);
2✔
5929

5930
        auto obj = table->get_object_with_primary_key(123);
2✔
5931
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
5932
        CHECK(dict->get_value_data_type() == type_Mixed);
2✔
5933
        CHECK_EQUAL(dict->size(), 4);
2✔
5934

5935
        // Check that values are replicated
5936
        Mixed val = dict->get("hello");
2✔
5937
        CHECK_EQUAL(val.get_string(), "world");
2✔
5938
        val = dict->get("cnt");
2✔
5939
        CHECK_EQUAL(val.get_int(), 7);
2✔
5940
        val = dict->get("when");
2✔
5941
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5942
        CHECK_EQUAL(dict->get_list("list")->get(0).get_int(), 5);
2✔
5943

5944
        // Erase dictionary element
5945
        dict->erase("cnt");
2✔
5946
        // Replace dictionary element
5947
        dict->insert("hello", "goodbye");
2✔
5948

5949
        obj = table->get_object_with_primary_key(456);
2✔
5950
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
5951
        // Check that values are replicated
5952
        CHECK_EQUAL(list->get(0).get_int(), 5);
2✔
5953
        CHECK_EQUAL(list->get(1).get_string(), "John");
2✔
5954
        // Replace list element
5955
        list->set(1, "Paul");
2✔
5956
        // Erase list element
5957
        list->remove(0);
2✔
5958
    });
2✔
5959

5960
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
5961
    session_1.wait_for_download_complete_or_client_stopped();
2✔
5962

5963
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5964
        auto table = tr.get_table("class_Table");
2✔
5965
        auto col_any = table->get_column_key("any");
2✔
5966
        CHECK_EQUAL(table->size(), 2);
2✔
5967

5968
        auto obj = table->get_object_with_primary_key(123);
2✔
5969
        auto dict = obj.get_dictionary(col_any);
2✔
5970
        CHECK_EQUAL(dict.size(), 3);
2✔
5971

5972
        Mixed val = dict["hello"];
2✔
5973
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
5974
        val = dict.get("when");
2✔
5975
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5976

5977
        // Dictionary clear
5978
        dict.clear();
2✔
5979

5980
        obj = table->get_object_with_primary_key(456);
2✔
5981
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
5982
        CHECK_EQUAL(list->size(), 1);
2✔
5983
        CHECK_EQUAL(list->get(0).get_string(), "Paul");
2✔
5984
        // List clear
5985
        list->clear();
2✔
5986
    });
2✔
5987

5988
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5989
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5990

5991
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
5992
        auto table = tr.get_table("class_Table");
2✔
5993
        auto col_any = table->get_column_key("any");
2✔
5994

5995
        CHECK_EQUAL(table->size(), 2);
2✔
5996

5997
        auto obj = table->get_object_with_primary_key(123);
2✔
5998
        auto dict = obj.get_dictionary(col_any);
2✔
5999
        CHECK_EQUAL(dict.size(), 0);
2✔
6000

6001
        // Replace dictionary with list on property
6002
        obj.set_collection(col_any, CollectionType::List);
2✔
6003

6004
        obj = table->get_object_with_primary_key(456);
2✔
6005
        auto list = obj.get_list<Mixed>(col_any);
2✔
6006
        CHECK_EQUAL(list.size(), 0);
2✔
6007
        // Replace list with Dictionary on property
6008
        obj.set_collection(col_any, CollectionType::Dictionary);
2✔
6009
    });
2✔
6010

6011
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6012
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6013

6014
    {
2✔
6015
        ReadTransaction read_1{db_1};
2✔
6016
        ReadTransaction read_2{db_2};
2✔
6017

6018
        auto table = read_2.get_table("class_Table");
2✔
6019
        auto col_any = table->get_column_key("any");
2✔
6020

6021
        CHECK_EQUAL(table->size(), 2);
2✔
6022

6023
        auto obj = table->get_object_with_primary_key(123);
2✔
6024
        auto list = obj.get_list<Mixed>(col_any);
2✔
6025
        CHECK_EQUAL(list.size(), 0);
2✔
6026

6027
        obj = table->get_object_with_primary_key(456);
2✔
6028
        auto dict = obj.get_dictionary(col_any);
2✔
6029
        CHECK_EQUAL(dict.size(), 0);
2✔
6030

6031
        CHECK(compare_groups(read_1, read_2));
2✔
6032
    }
2✔
6033
}
2✔
6034

6035
TEST(Sync_CollectionInCollection)
6036
{
2✔
6037
    TEST_CLIENT_DB(db_1);
2✔
6038
    TEST_CLIENT_DB(db_2);
2✔
6039

6040
    TEST_DIR(dir);
2✔
6041
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6042
    fixture.start();
2✔
6043

6044
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6045
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6046

6047
    Timestamp now{std::chrono::system_clock::now()};
2✔
6048

6049
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6050
        auto& g = tr.get_group();
2✔
6051
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6052
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6053

6054
        auto foo = table->create_object_with_primary_key(123);
2✔
6055

6056
        // Create dictionary in Mixed property
6057
        foo.set_collection(col_any, CollectionType::Dictionary);
2✔
6058
        auto dict = foo.get_dictionary_ptr(col_any);
2✔
6059
        dict->insert("hello", "world");
2✔
6060
        dict->insert("cnt", 7);
2✔
6061
        dict->insert("when", now);
2✔
6062
        // Insert a List in a Dictionary
6063
        dict->insert_collection("collection", CollectionType::List);
2✔
6064
        auto l = dict->get_list("collection");
2✔
6065
        l->add(5);
2✔
6066

6067
        auto bar = table->create_object_with_primary_key(456);
2✔
6068

6069
        // Create list in Mixed property
6070
        bar.set_collection(col_any, CollectionType::List);
2✔
6071
        auto list = bar.get_list_ptr<Mixed>(col_any);
2✔
6072
        list->add("John");
2✔
6073
        list->insert(0, 5);
2✔
6074
        // Insert dictionary in List
6075
        list->insert_collection(2, CollectionType::Dictionary);
2✔
6076
        auto d = list->get_dictionary(2);
2✔
6077
        d->insert("One", 1);
2✔
6078
        d->insert("Two", 2);
2✔
6079
    });
2✔
6080

6081
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6082
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6083

6084
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6085
        auto table = tr.get_table("class_Table");
2✔
6086
        auto col_any = table->get_column_key("any");
2✔
6087
        CHECK_EQUAL(table->size(), 2);
2✔
6088

6089
        auto obj = table->get_object_with_primary_key(123);
2✔
6090
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6091
        CHECK(dict->get_value_data_type() == type_Mixed);
2✔
6092
        CHECK_EQUAL(dict->size(), 4);
2✔
6093

6094
        // Replace List with Dictionary
6095
        dict->insert_collection("collection", CollectionType::Dictionary);
2✔
6096
        auto d = dict->get_dictionary("collection");
2✔
6097
        d->insert("Three", 3);
2✔
6098
        d->insert("Four", 4);
2✔
6099

6100
        obj = table->get_object_with_primary_key(456);
2✔
6101
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6102
        // Replace Dictionary with List
6103
        list->set_collection(2, CollectionType::List);
2✔
6104
        auto l = list->get_list(2);
2✔
6105
        l->add(47);
2✔
6106
    });
2✔
6107

6108
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6109
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6110

6111
    {
2✔
6112
        ReadTransaction read_1{db_1};
2✔
6113
        ReadTransaction read_2{db_2};
2✔
6114

6115
        auto table = read_2.get_table("class_Table");
2✔
6116
        auto col_any = table->get_column_key("any");
2✔
6117

6118
        CHECK_EQUAL(table->size(), 2);
2✔
6119

6120
        auto obj = table->get_object_with_primary_key(123);
2✔
6121
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6122
        auto d = dict->get_dictionary("collection");
2✔
6123
        CHECK_EQUAL(d->get("Four").get_int(), 4);
2✔
6124

6125
        obj = table->get_object_with_primary_key(456);
2✔
6126
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6127
        auto l = list->get_list(2);
2✔
6128
        CHECK_EQUAL(l->get_any(0).get_int(), 47);
2✔
6129
    }
2✔
6130
}
2✔
6131

6132
TEST(Sync_DeleteCollectionInCollection)
6133
{
2✔
6134
    TEST_CLIENT_DB(db_1);
2✔
6135
    TEST_CLIENT_DB(db_2);
2✔
6136

6137
    TEST_DIR(dir);
2✔
6138
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6139
    fixture.start();
2✔
6140

6141
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6142
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6143

6144
    Timestamp now{std::chrono::system_clock::now()};
2✔
6145

6146
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6147
        auto& g = tr.get_group();
2✔
6148
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6149
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6150

6151
        auto foo = table->create_object_with_primary_key(123);
2✔
6152

6153
        // Create list in Mixed property
6154
        foo.set_json(col_any, R"([
2✔
6155
            {
2✔
6156
              "1_map": {
2✔
6157
                "2_string": "map value"
2✔
6158
               },
2✔
6159
              "1_list": ["list value"]
2✔
6160
            }
2✔
6161
        ])");
2✔
6162
    });
2✔
6163

6164
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6165
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6166

6167
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6168
        auto table = tr.get_table("class_Table");
2✔
6169
        auto col_any = table->get_column_key("any");
2✔
6170
        CHECK_EQUAL(table->size(), 1);
2✔
6171

6172
        auto obj = table->get_object_with_primary_key(123);
2✔
6173
        auto list = obj.get_list<Mixed>(col_any);
2✔
6174
        auto dict = list.get_dictionary(0);
2✔
6175
        dict->erase("1_map");
2✔
6176
    });
2✔
6177

6178
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6179
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6180

6181
    {
2✔
6182
        ReadTransaction read_1{db_1};
2✔
6183

6184
        auto table = read_1.get_table("class_Table");
2✔
6185
        auto col_any = table->get_column_key("any");
2✔
6186

6187
        CHECK_EQUAL(table->size(), 1);
2✔
6188

6189
        auto obj = table->get_object_with_primary_key(123);
2✔
6190
        auto list = obj.get_list<Mixed>(col_any);
2✔
6191
        auto dict = list.get_dictionary(0);
2✔
6192
        CHECK_EQUAL(dict->size(), 1);
2✔
6193
    }
2✔
6194
}
2✔
6195

6196
TEST(Sync_Dictionary_Links)
6197
{
2✔
6198
    TEST_CLIENT_DB(db_1);
2✔
6199
    TEST_CLIENT_DB(db_2);
2✔
6200

6201
    TEST_DIR(dir);
2✔
6202
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6203
    fixture.start();
2✔
6204

6205
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6206
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6207

6208
    // Test that we can transmit links.
6209

6210
    ColKey col_dict;
2✔
6211

6212
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6213
        auto& g = tr.get_group();
2✔
6214
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6215
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6216
        col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6217

6218
        auto foo = foos->create_object_with_primary_key(123);
2✔
6219
        auto a = bars->create_object_with_primary_key("a");
2✔
6220
        auto b = bars->create_object_with_primary_key("b");
2✔
6221

6222
        auto dict = foo.get_dictionary(col_dict);
2✔
6223
        dict.insert("a", a);
2✔
6224
        dict.insert("b", b);
2✔
6225
    });
2✔
6226

6227
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6228
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6229

6230
    {
2✔
6231
        ReadTransaction tr{db_2};
2✔
6232

6233
        auto foos = tr.get_table("class_Foo");
2✔
6234
        auto bars = tr.get_table("class_Bar");
2✔
6235

6236
        CHECK_EQUAL(foos->size(), 1);
2✔
6237
        CHECK_EQUAL(bars->size(), 2);
2✔
6238

6239
        auto foo = foos->get_object_with_primary_key(123);
2✔
6240
        auto a = bars->get_object_with_primary_key("a");
2✔
6241
        auto b = bars->get_object_with_primary_key("b");
2✔
6242

6243
        auto dict = foo.get_dictionary(foos->get_column_key("dict"));
2✔
6244
        CHECK_EQUAL(dict.size(), 2);
2✔
6245

6246
        auto dict_a = dict.get("a");
2✔
6247
        auto dict_b = dict.get("b");
2✔
6248
        CHECK(dict_a == Mixed{a.get_link()});
2✔
6249
        CHECK(dict_b == Mixed{b.get_link()});
2✔
6250
    }
2✔
6251

6252
    // Test that we can create tombstones for objects in dictionaries.
6253

6254
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6255
        auto& g = tr.get_group();
2✔
6256

6257
        auto bars = g.get_table("class_Bar");
2✔
6258
        auto a = bars->get_object_with_primary_key("a");
2✔
6259
        a.invalidate();
2✔
6260

6261
        auto foos = g.get_table("class_Foo");
2✔
6262
        auto foo = foos->get_object_with_primary_key(123);
2✔
6263
        auto dict = foo.get_dictionary(col_dict);
2✔
6264

6265
        CHECK_EQUAL(dict.size(), 2);
2✔
6266
        CHECK((*dict.find("a")).second.is_null());
2✔
6267

6268
        CHECK(dict.find("b") != dict.end());
2✔
6269
    });
2✔
6270

6271
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6272
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6273

6274
    {
2✔
6275
        ReadTransaction tr{db_2};
2✔
6276

6277
        auto foos = tr.get_table("class_Foo");
2✔
6278
        auto bars = tr.get_table("class_Bar");
2✔
6279

6280
        CHECK_EQUAL(foos->size(), 1);
2✔
6281
        CHECK_EQUAL(bars->size(), 1);
2✔
6282

6283
        auto b = bars->get_object_with_primary_key("b");
2✔
6284

6285
        auto foo = foos->get_object_with_primary_key(123);
2✔
6286
        auto dict = foo.get_dictionary(col_dict);
2✔
6287

6288
        CHECK_EQUAL(dict.size(), 2);
2✔
6289
        CHECK((*dict.find("a")).second.is_null());
2✔
6290

6291
        CHECK(dict.find("b") != dict.end());
2✔
6292
        CHECK((*dict.find("b")).second == Mixed{b.get_link()});
2✔
6293
    }
2✔
6294
}
2✔
6295

6296
TEST(Sync_Set)
6297
{
2✔
6298
    // Test replication and synchronization of Set values.
6299

6300
    TEST_CLIENT_DB(db_1);
2✔
6301
    TEST_CLIENT_DB(db_2);
2✔
6302

6303
    TEST_DIR(dir);
2✔
6304
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6305
    fixture.start();
2✔
6306

6307
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6308
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6309

6310
    ColKey col_ints, col_strings, col_mixeds;
2✔
6311
    {
2✔
6312
        WriteTransaction wt{db_1};
2✔
6313
        auto t = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "pk");
2✔
6314
        col_ints = t->add_column_set(type_Int, "ints");
2✔
6315
        col_strings = t->add_column_set(type_String, "strings");
2✔
6316
        col_mixeds = t->add_column_set(type_Mixed, "mixeds");
2✔
6317

6318
        auto obj = t->create_object_with_primary_key(0);
2✔
6319

6320
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6321
        auto strings = obj.get_set<StringData>(col_strings);
2✔
6322
        auto mixeds = obj.get_set<Mixed>(col_mixeds);
2✔
6323

6324
        ints.insert(123);
2✔
6325
        ints.insert(456);
2✔
6326
        ints.insert(789);
2✔
6327
        ints.insert(123);
2✔
6328
        ints.insert(456);
2✔
6329
        ints.insert(789);
2✔
6330

6331
        CHECK_EQUAL(ints.size(), 3);
2✔
6332
        CHECK_EQUAL(ints.find(123), 0);
2✔
6333
        CHECK_EQUAL(ints.find(456), 1);
2✔
6334
        CHECK_EQUAL(ints.find(789), 2);
2✔
6335

6336
        strings.insert("a");
2✔
6337
        strings.insert("b");
2✔
6338
        strings.insert("c");
2✔
6339
        strings.insert("a");
2✔
6340
        strings.insert("b");
2✔
6341
        strings.insert("c");
2✔
6342

6343
        CHECK_EQUAL(strings.size(), 3);
2✔
6344
        CHECK_EQUAL(strings.find("a"), 0);
2✔
6345
        CHECK_EQUAL(strings.find("b"), 1);
2✔
6346
        CHECK_EQUAL(strings.find("c"), 2);
2✔
6347

6348
        mixeds.insert(Mixed{123});
2✔
6349
        mixeds.insert(Mixed{"a"});
2✔
6350
        mixeds.insert(Mixed{456.0});
2✔
6351
        mixeds.insert(Mixed{123});
2✔
6352
        mixeds.insert(Mixed{"a"});
2✔
6353
        mixeds.insert(Mixed{456.0});
2✔
6354

6355
        CHECK_EQUAL(mixeds.size(), 3);
2✔
6356
        CHECK_EQUAL(mixeds.find(123), 0);
2✔
6357
        CHECK_EQUAL(mixeds.find(456.0), 1);
2✔
6358
        CHECK_EQUAL(mixeds.find("a"), 2);
2✔
6359

6360
        wt.commit();
2✔
6361
    }
2✔
6362

6363
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6364
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6365

6366
    // Create a conflict. Session 1 should lose, because it has a lower peer ID.
6367
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6368
        auto t = wt.get_table("class_Foo");
2✔
6369
        auto obj = t->get_object_with_primary_key(0);
2✔
6370

6371
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6372
        ints.insert(999);
2✔
6373
    });
2✔
6374

6375
    write_transaction(db_2, [=](WriteTransaction& wt) {
2✔
6376
        auto t = wt.get_table("class_Foo");
2✔
6377
        auto obj = t->get_object_with_primary_key(0);
2✔
6378

6379
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6380
        ints.insert(999);
2✔
6381
        ints.erase(999);
2✔
6382
    });
2✔
6383

6384
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6385
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6386
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6387
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6388

6389
    {
2✔
6390
        ReadTransaction read_1{db_1};
2✔
6391
        ReadTransaction read_2{db_2};
2✔
6392
        CHECK(compare_groups(read_1, read_2));
2✔
6393
    }
2✔
6394

6395
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6396
        auto t = wt.get_table("class_Foo");
2✔
6397
        auto obj = t->get_object_with_primary_key(0);
2✔
6398
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6399
        ints.clear();
2✔
6400
    });
2✔
6401

6402
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6403
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6404

6405
    {
2✔
6406
        ReadTransaction read_1{db_1};
2✔
6407
        ReadTransaction read_2{db_2};
2✔
6408
        CHECK(compare_groups(read_1, read_2));
2✔
6409
    }
2✔
6410
}
2✔
6411

6412
TEST(Sync_BundledRealmFile)
6413
{
2✔
6414
    TEST_CLIENT_DB(db);
2✔
6415
    SHARED_GROUP_TEST_PATH(path);
2✔
6416

6417
    TEST_DIR(dir);
2✔
6418
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6419
    fixture.start();
2✔
6420

6421
    Session session = fixture.make_bound_session(db);
2✔
6422

6423
    write_transaction(db, [](WriteTransaction& tr) {
2✔
6424
        auto foos = tr.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6425
        foos->create_object_with_primary_key(123);
2✔
6426
    });
2✔
6427

6428
    // We cannot write out file if changes are not synced to server
6429
    CHECK_THROW_ANY(db->write_copy(path.c_str(), nullptr));
2✔
6430

6431
    session.wait_for_upload_complete_or_client_stopped();
2✔
6432
    session.wait_for_download_complete_or_client_stopped();
2✔
6433

6434
    // Now we can
6435
    db->write_copy(path.c_str(), nullptr);
2✔
6436
}
2✔
6437

6438
TEST(Sync_UpgradeToClientHistory)
6439
{
2✔
6440
    DBOptions options;
2✔
6441
    options.logger = test_context.logger;
2✔
6442
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
6443
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
6444
    auto db_1 = DB::create(make_in_realm_history(), db_1_path, options);
2✔
6445
    auto db_2 = DB::create(make_in_realm_history(), db_2_path, options);
2✔
6446
    {
2✔
6447
        auto tr = db_1->start_write();
2✔
6448

6449
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
6450
        auto col_float = embedded->add_column(type_Float, "float");
2✔
6451
        auto col_additional = embedded->add_column_dictionary(*embedded, "additional");
2✔
6452

6453
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6454
        auto col_list = baas->add_column_list(type_Int, "list");
2✔
6455
        auto col_set = baas->add_column_set(type_Int, "set");
2✔
6456
        auto col_dict = baas->add_column_dictionary(type_Int, "dictionary");
2✔
6457
        auto col_child = baas->add_column(*embedded, "child");
2✔
6458

6459
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6460
        auto col_str = foos->add_column(type_String, "str");
2✔
6461
        auto col_children = foos->add_column_list(*embedded, "children");
2✔
6462

6463
        auto foobaas = tr->add_table_with_primary_key("class_FooBaa", type_ObjectId, "_id");
2✔
6464
        auto col_time = foobaas->add_column(type_Timestamp, "time");
2✔
6465

6466
        auto col_link = baas->add_column(*foos, "link");
2✔
6467

6468
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Hello");
2✔
6469
        auto children = foo.get_linklist(col_children);
2✔
6470
        children.create_and_insert_linked_object(0);
2✔
6471
        auto baa = baas->create_object_with_primary_key(999).set(col_link, foo.get_key());
2✔
6472
        auto obj = baa.create_and_set_linked_object(col_child);
2✔
6473
        obj.set(col_float, 42.f);
2✔
6474
        auto additional = obj.get_dictionary(col_additional);
2✔
6475
        additional.create_and_insert_linked_object("One").set(col_float, 1.f);
2✔
6476
        additional.create_and_insert_linked_object("Two").set(col_float, 2.f);
2✔
6477
        additional.create_and_insert_linked_object("Three").set(col_float, 3.f);
2✔
6478

6479
        auto list = baa.get_list<Int>(col_list);
2✔
6480
        list.add(1);
2✔
6481
        list.add(0);
2✔
6482
        list.add(2);
2✔
6483
        list.add(3);
2✔
6484
        list.set(1, 5);
2✔
6485
        list.remove(1);
2✔
6486
        auto set = baa.get_set<Int>(col_set);
2✔
6487
        set.insert(4);
2✔
6488
        set.insert(2);
2✔
6489
        set.insert(5);
2✔
6490
        set.insert(6);
2✔
6491
        set.erase(2);
2✔
6492
        auto dict = baa.get_dictionary(col_dict);
2✔
6493
        dict.insert("key6", 6);
2✔
6494
        dict.insert("key7", 7);
2✔
6495
        dict.insert("key8", 8);
2✔
6496
        dict.insert("key9", 9);
2✔
6497
        dict.erase("key6");
2✔
6498

6499
        for (int i = 0; i < 100; i++) {
202✔
6500
            foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i));
200✔
6501
        }
200✔
6502

6503
        tr->commit();
2✔
6504
    }
2✔
6505
    {
2✔
6506
        auto tr = db_2->start_write();
2✔
6507
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6508
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6509
        auto col_str = foos->add_column(type_String, "str");
2✔
6510
        auto col_link = baas->add_column(*foos, "link");
2✔
6511

6512
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Goodbye");
2✔
6513
        baas->create_object_with_primary_key(888).set(col_link, foo.get_key());
2✔
6514

6515
        tr->commit();
2✔
6516
    }
2✔
6517

6518
    db_1->create_new_history(make_client_replication());
2✔
6519
    db_2->create_new_history(make_client_replication());
2✔
6520

6521
    TEST_DIR(dir);
2✔
6522
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6523
    fixture.start();
2✔
6524

6525
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6526
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6527

6528
    write_transaction(db_1, [](WriteTransaction& tr) {
2✔
6529
        auto foos = tr.get_group().get_table("class_Foo");
2✔
6530
        foos->create_object_with_primary_key("456");
2✔
6531
    });
2✔
6532
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6533
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6534
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6535
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6536

6537
    // db_2->start_read()->to_json(std::cout);
6538
}
2✔
6539

6540
// This test is extracted from ClientReset_ThreeClients
6541
// because it uncovers a bug in how MSVC 2019 compiles
6542
// things in Changeset::get_key()
6543
TEST(Sync_MergeStringPrimaryKey)
6544
{
2✔
6545
    TEST_DIR(dir_1); // The server.
2✔
6546
    TEST_CLIENT_DB(db_1);
2✔
6547
    TEST_CLIENT_DB(db_2);
2✔
6548
    TEST_DIR(metadata_dir_1);
2✔
6549
    TEST_DIR(metadata_dir_2);
2✔
6550

6551
    const std::string server_path = "/data";
2✔
6552

6553
    std::string real_path_1, real_path_2;
2✔
6554

6555
    auto create_schema = [&](Transaction& group) {
4✔
6556
        TableRef table_0 = group.add_table_with_primary_key("class_table_0", type_Int, "id");
4✔
6557
        table_0->add_column(type_Int, "int");
4✔
6558
        table_0->add_column(type_Bool, "bool");
4✔
6559
        table_0->add_column(type_Float, "float");
4✔
6560
        table_0->add_column(type_Double, "double");
4✔
6561
        table_0->add_column(type_Timestamp, "timestamp");
4✔
6562

6563
        TableRef table_1 = group.add_table_with_primary_key("class_table_1", type_Int, "pk_int");
4✔
6564
        table_1->add_column(type_String, "String");
4✔
6565

6566
        TableRef table_2 = group.add_table_with_primary_key("class_table_2", type_String, "pk_string");
4✔
6567
        table_2->add_column_list(type_String, "array_string");
4✔
6568
    };
4✔
6569

6570
    // First we make changesets. Then we upload them.
6571
    {
2✔
6572
        ClientServerFixture fixture(dir_1, test_context);
2✔
6573
        fixture.start();
2✔
6574
        real_path_1 = fixture.map_virtual_to_real_path(server_path);
2✔
6575

6576
        {
2✔
6577
            WriteTransaction wt{db_1};
2✔
6578
            create_schema(wt);
2✔
6579
            wt.commit();
2✔
6580
        }
2✔
6581
        {
2✔
6582
            WriteTransaction wt{db_2};
2✔
6583
            create_schema(wt);
2✔
6584

6585
            TableRef table_2 = wt.get_table("class_table_2");
2✔
6586
            auto col = table_2->get_column_key("array_string");
2✔
6587
            auto list_string = table_2->create_object_with_primary_key("aaa").get_list<String>(col);
2✔
6588
            list_string.add("a");
2✔
6589
            list_string.add("b");
2✔
6590

6591
            wt.commit();
2✔
6592
        }
2✔
6593

6594
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
6595
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
6596

6597
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
6598
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
6599
        // Download completion is not important.
6600
    }
2✔
6601
}
2✔
6602

6603
TEST(Sync_DifferentUsersMultiplexing)
6604
{
2✔
6605
    ClientServerFixture::Config fixture_config;
2✔
6606
    fixture_config.one_connection_per_session = false;
2✔
6607

6608
    TEST_DIR(server_dir);
2✔
6609
    ClientServerFixture fixture(server_dir, test_context, std::move(fixture_config));
2✔
6610

6611
    struct SessionBundle {
2✔
6612
        test_util::DBTestPathGuard path_guard;
2✔
6613
        DBRef db;
2✔
6614
        Session sess;
2✔
6615

6616
        SessionBundle(unit_test::TestContext& ctx, ClientServerFixture& fixture, std::string name,
2✔
6617
                      std::string signed_token, std::string user_id)
2✔
6618
            : path_guard(realm::test_util::get_test_path(ctx.get_test_name(), "." + name + ".realm"))
5✔
6619
            , db(DB::create(make_client_replication(), path_guard))
5✔
6620
        {
8✔
6621
            Session::Config config;
8✔
6622
            config.signed_user_token = signed_token;
8✔
6623
            config.user_id = user_id;
8✔
6624
            sess = fixture.make_bound_session(db, "/test", std::move(config));
8✔
6625
            sess.wait_for_download_complete_or_client_stopped();
8✔
6626
        }
8✔
6627
    };
2✔
6628

6629
    fixture.start();
2✔
6630

6631
    SessionBundle user_1_sess_1(test_context, fixture, "user_1_db_1", g_user_0_token, "user_0");
2✔
6632
    SessionBundle user_2_sess_1(test_context, fixture, "user_2_db_1", g_user_1_token, "user_1");
2✔
6633
    SessionBundle user_1_sess_2(test_context, fixture, "user_1_db_2", g_user_0_token, "user_0");
2✔
6634
    SessionBundle user_2_sess_2(test_context, fixture, "user_2_db_2", g_user_1_token, "user_1");
2✔
6635

6636
    CHECK_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6637
                user_1_sess_2.sess.get_appservices_connection_id());
2✔
6638
    CHECK_EQUAL(user_2_sess_1.sess.get_appservices_connection_id(),
2✔
6639
                user_2_sess_2.sess.get_appservices_connection_id());
2✔
6640
    CHECK_NOT_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6641
                    user_2_sess_1.sess.get_appservices_connection_id());
2✔
6642
    CHECK_NOT_EQUAL(user_1_sess_2.sess.get_appservices_connection_id(),
2✔
6643
                    user_2_sess_2.sess.get_appservices_connection_id());
2✔
6644
}
2✔
6645

6646
TEST(Sync_TransformAgainstEmptyReciprocalChangeset)
6647
{
2✔
6648
    TEST_CLIENT_DB(seed_db);
2✔
6649
    TEST_CLIENT_DB(db_1);
2✔
6650
    TEST_CLIENT_DB(db_2);
2✔
6651

6652
    {
2✔
6653
        auto tr = seed_db->start_write();
2✔
6654
        // Create schema: single table with array of ints as property.
6655
        auto table = tr->add_table_with_primary_key("class_table", type_Int, "_id");
2✔
6656
        table->add_column_list(type_Int, "ints");
2✔
6657
        table->add_column(type_String, "string");
2✔
6658
        tr->commit_and_continue_writing();
2✔
6659

6660
        // Create object and initialize array.
6661
        table = tr->get_table("class_table");
2✔
6662
        auto obj = table->create_object_with_primary_key(42);
2✔
6663
        auto ints = obj.get_list<int64_t>("ints");
2✔
6664
        for (auto i = 0; i < 8; ++i) {
18✔
6665
            ints.insert(i, i);
16✔
6666
        }
16✔
6667
        tr->commit();
2✔
6668
    }
2✔
6669

6670
    {
2✔
6671
        TEST_DIR(dir);
2✔
6672
        MultiClientServerFixture fixture(3, 1, dir, test_context);
2✔
6673
        fixture.start();
2✔
6674

6675
        util::Optional<Session> seed_session = fixture.make_bound_session(0, seed_db, 0, "/test");
2✔
6676
        util::Optional<Session> db_1_session = fixture.make_bound_session(1, db_1, 0, "/test");
2✔
6677
        util::Optional<Session> db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6678

6679
        seed_session->wait_for_upload_complete_or_client_stopped();
2✔
6680
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6681
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6682
        seed_session.reset();
2✔
6683
        db_2_session.reset();
2✔
6684

6685
        auto move_element = [&](const DBRef& db, size_t from, size_t to, size_t string_size = 0) {
8✔
6686
            auto wt = db->start_write();
8✔
6687
            auto table = wt->get_table("class_table");
8✔
6688
            auto obj = table->get_object_with_primary_key(42);
8✔
6689
            auto ints = obj.get_list<int64_t>("ints");
8✔
6690
            ints.move(from, to);
8✔
6691
            obj.set("string", std::string(string_size, 'a'));
8✔
6692
            wt->commit();
8✔
6693
        };
8✔
6694

6695
        // Client 1 uploads two move instructions.
6696
        move_element(db_1, 7, 2);
2✔
6697
        move_element(db_1, 7, 6);
2✔
6698

6699
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6700

6701
        std::this_thread::sleep_for(std::chrono::milliseconds{10});
2✔
6702

6703
        // Client 2 uploads two move instructions.
6704
        // The sync client uploads at most 128 KB of data so we make the first changeset large enough so two upload
6705
        // messages are sent to the server instead of one. Each change is transformed against the changes from
6706
        // Client 1.
6707

6708
        // First change discards the first change (move(7, 2)) of Client 1.
6709
        move_element(db_2, 7, 0, 200 * 1024);
2✔
6710
        // Second change is tranformed against an empty reciprocal changeset as result of the change above.
6711
        move_element(db_2, 7, 5);
2✔
6712
        db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6713

6714
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6715
        db_2_session->wait_for_upload_complete_or_client_stopped();
2✔
6716

6717
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6718
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6719
    }
2✔
6720

6721
    ReadTransaction rt_1(db_1);
2✔
6722
    ReadTransaction rt_2(db_2);
2✔
6723
    const Group& group_1 = rt_1;
2✔
6724
    const Group& group_2 = rt_2;
2✔
6725
    group_1.verify();
2✔
6726
    group_2.verify();
2✔
6727
    CHECK(compare_groups(rt_1, rt_2));
2✔
6728
}
2✔
6729

6730
#endif // !REALM_MOBILE
6731

6732
// Tests that an empty reciprocal changesets is set and retrieved correctly.
6733
TEST(Sync_SetAndGetEmptyReciprocalChangeset)
6734
{
2✔
6735
    using namespace realm;
2✔
6736
    using namespace realm::sync::instr;
2✔
6737
    using realm::sync::Changeset;
2✔
6738

6739
    TEST_CLIENT_DB(db);
2✔
6740

6741
    auto& history = get_history(db);
2✔
6742
    history.set_client_file_ident(SaltedFileIdent{1, 0x1234567812345678}, false);
2✔
6743
    timestamp_type timestamp{1};
2✔
6744
    history.set_local_origin_timestamp_source([&] {
6✔
6745
        return ++timestamp;
6✔
6746
    });
6✔
6747

6748
    auto latest_local_version = [&] {
2✔
6749
        auto tr = db->start_write();
2✔
6750
        // Create schema: single table with array of ints as property.
6751
        tr->add_table_with_primary_key("class_table", type_Int, "_id")->add_column_list(type_Int, "ints");
2✔
6752
        tr->commit_and_continue_writing();
2✔
6753

6754
        // Create object and initialize array.
6755
        TableRef table = tr->get_table("class_table");
2✔
6756
        auto obj = table->create_object_with_primary_key(42);
2✔
6757
        auto ints = obj.get_list<int64_t>("ints");
2✔
6758
        for (auto i = 0; i < 8; ++i) {
18✔
6759
            ints.insert(i, i);
16✔
6760
        }
16✔
6761
        tr->commit_and_continue_writing();
2✔
6762

6763
        // Move element in array.
6764
        ints.move(7, 2);
2✔
6765
        return tr->commit();
2✔
6766
    }();
2✔
6767

6768
    // Create changeset which moves element from index 7 to index 0 in array.
6769
    // This changeset will discard the previous move (reciprocal changeset), leaving the local reciprocal changesets
6770
    // with no instructions (empty).
6771
    Changeset changeset;
2✔
6772
    ArrayMove instr;
2✔
6773
    instr.table = changeset.intern_string("table");
2✔
6774
    instr.object = instr::PrimaryKey{42};
2✔
6775
    instr.field = changeset.intern_string("ints");
2✔
6776
    instr.path.push_back(7);
2✔
6777
    instr.ndx_2 = 0;
2✔
6778
    instr.prior_size = 8;
2✔
6779
    changeset.push_back(instr);
2✔
6780
    changeset.version = 1;
2✔
6781
    changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6782
    changeset.origin_timestamp = timestamp;
2✔
6783
    changeset.origin_file_ident = 2;
2✔
6784

6785
    ChangesetEncoder::Buffer encoded;
2✔
6786
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6787
    encode_changeset(changeset, encoded);
2✔
6788
    server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
2✔
6789
                                           BinaryData(encoded.data(), encoded.size()), changeset.origin_timestamp,
2✔
6790
                                           changeset.origin_file_ident);
2✔
6791

6792
    SyncProgress progress = {};
2✔
6793
    progress.download.server_version = changeset.version;
2✔
6794
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6795
    progress.latest_server_version.version = changeset.version;
2✔
6796
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6797

6798
    uint_fast64_t downloadable_bytes = 0;
2✔
6799
    VersionInfo version_info;
2✔
6800
    auto transact = db->start_read();
2✔
6801
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6802
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6803

6804
    bool is_compressed = false;
2✔
6805
    auto data = history.get_reciprocal_transform(latest_local_version, is_compressed);
2✔
6806
    Changeset reciprocal_changeset;
2✔
6807
    ChunkedBinaryInputStream in{data};
2✔
6808
    if (is_compressed) {
2✔
6809
        size_t total_size;
2✔
6810
        auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
2✔
6811
        CHECK(decompressed);
2✔
6812
        sync::parse_changeset(*decompressed, reciprocal_changeset); // Throws
2✔
6813
    }
2✔
6814
    else {
×
6815
        sync::parse_changeset(in, reciprocal_changeset); // Throws
×
6816
    }
×
6817
    // The only instruction in the reciprocal changeset was discarded during OT.
6818
    CHECK(reciprocal_changeset.empty());
2✔
6819
}
2✔
6820

6821
TEST(Sync_InvalidChangesetFromServer)
6822
{
2✔
6823
    TEST_CLIENT_DB(db);
2✔
6824

6825
    auto& history = get_history(db);
2✔
6826
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6827

6828
    instr::CreateObject bad_instr;
2✔
6829
    bad_instr.object = InternString{1};
2✔
6830
    bad_instr.table = InternString{2};
2✔
6831

6832
    Changeset changeset;
2✔
6833
    changeset.push_back(bad_instr);
2✔
6834

6835
    ChangesetEncoder::Buffer encoded;
2✔
6836
    encode_changeset(changeset, encoded);
2✔
6837
    RemoteChangeset server_changeset;
2✔
6838
    server_changeset.origin_file_ident = 1;
2✔
6839
    server_changeset.remote_version = 1;
2✔
6840
    server_changeset.data = BinaryData(encoded.data(), encoded.size());
2✔
6841

6842
    VersionInfo version_info;
2✔
6843
    auto transact = db->start_read();
2✔
6844
    CHECK_THROW_EX(history.integrate_server_changesets({}, nullptr, util::Span(&server_changeset, 1), version_info,
2✔
6845
                                                       DownloadBatchState::SteadyState, *test_context.logger,
2✔
6846
                                                       transact),
2✔
6847
                   sync::IntegrationException,
2✔
6848
                   StringData(e.what()).contains("Failed to parse received changeset: Invalid interned string"));
2✔
6849
}
2✔
6850

6851
TEST(Sync_ServerVersionsSkippedFromDownloadCursor)
6852
{
2✔
6853
    TEST_CLIENT_DB(db);
2✔
6854

6855
    auto& history = get_history(db);
2✔
6856
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6857
    timestamp_type timestamp{1};
2✔
6858
    history.set_local_origin_timestamp_source([&] {
2✔
6859
        return ++timestamp;
2✔
6860
    });
2✔
6861

6862
    auto latest_local_version = [&] {
2✔
6863
        auto tr = db->start_write();
2✔
6864
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6865
        return tr->commit();
2✔
6866
    }();
2✔
6867

6868
    Changeset server_changeset;
2✔
6869
    server_changeset.version = 10;
2✔
6870
    server_changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6871
    server_changeset.origin_timestamp = ++timestamp;
2✔
6872
    server_changeset.origin_file_ident = 1;
2✔
6873

6874
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6875
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6876
    encoded.emplace_back();
2✔
6877
    encode_changeset(server_changeset, encoded.back());
2✔
6878
    server_changesets_encoded.emplace_back(server_changeset.version, server_changeset.last_integrated_remote_version,
2✔
6879
                                           BinaryData(encoded.back().data(), encoded.back().size()),
2✔
6880
                                           server_changeset.origin_timestamp, server_changeset.origin_file_ident);
2✔
6881

6882
    SyncProgress progress = {};
2✔
6883
    // The server skips 10 server versions.
6884
    progress.download.server_version = server_changeset.version + 10;
2✔
6885
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6886
    progress.latest_server_version.version = server_changeset.version + 15;
2✔
6887
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6888

6889
    uint_fast64_t downloadable_bytes = 0;
2✔
6890
    VersionInfo version_info;
2✔
6891
    auto transact = db->start_read();
2✔
6892
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6893
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6894

6895
    version_type current_version;
2✔
6896
    SaltedFileIdent file_ident;
2✔
6897
    SyncProgress expected_progress;
2✔
6898
    history.get_status(current_version, file_ident, expected_progress);
2✔
6899

6900
    // Check progress is reported correctly.
6901
    CHECK_EQUAL(progress.latest_server_version.salt, expected_progress.latest_server_version.salt);
2✔
6902
    CHECK_EQUAL(progress.latest_server_version.version, expected_progress.latest_server_version.version);
2✔
6903
    CHECK_EQUAL(progress.download.last_integrated_client_version,
2✔
6904
                expected_progress.download.last_integrated_client_version);
2✔
6905
    CHECK_EQUAL(progress.download.server_version, expected_progress.download.server_version);
2✔
6906
    CHECK_EQUAL(progress.upload.client_version, expected_progress.upload.client_version);
2✔
6907
    CHECK_EQUAL(progress.upload.last_integrated_server_version,
2✔
6908
                expected_progress.upload.last_integrated_server_version);
2✔
6909
}
2✔
6910

6911
TEST(Sync_NonIncreasingServerVersions)
6912
{
2✔
6913
    TEST_CLIENT_DB(db);
2✔
6914

6915
    auto& history = get_history(db);
2✔
6916
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6917
    timestamp_type timestamp{1};
2✔
6918
    history.set_local_origin_timestamp_source([&] {
2✔
6919
        return ++timestamp;
2✔
6920
    });
2✔
6921

6922
    auto latest_local_version = [&] {
2✔
6923
        auto tr = db->start_write();
2✔
6924
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6925
        return tr->commit();
2✔
6926
    }();
2✔
6927

6928
    std::vector<Changeset> server_changesets;
2✔
6929
    auto prep_changeset = [&](auto pk_name, auto int_col_val) {
8✔
6930
        Changeset changeset;
8✔
6931
        changeset.version = 10;
8✔
6932
        changeset.last_integrated_remote_version = latest_local_version - 1;
8✔
6933
        changeset.origin_timestamp = ++timestamp;
8✔
6934
        changeset.origin_file_ident = 1;
8✔
6935
        instr::PrimaryKey pk{changeset.intern_string(pk_name)};
8✔
6936
        auto table_name = changeset.intern_string("foo");
8✔
6937
        auto col_name = changeset.intern_string("int_col");
8✔
6938
        instr::EraseObject erase_1;
8✔
6939
        erase_1.object = pk;
8✔
6940
        erase_1.table = table_name;
8✔
6941
        changeset.push_back(erase_1);
8✔
6942
        instr::CreateObject create_1;
8✔
6943
        create_1.object = pk;
8✔
6944
        create_1.table = table_name;
8✔
6945
        changeset.push_back(create_1);
8✔
6946
        instr::Update update_1;
8✔
6947
        update_1.table = table_name;
8✔
6948
        update_1.object = pk;
8✔
6949
        update_1.field = col_name;
8✔
6950
        update_1.value = instr::Payload{int64_t(int_col_val)};
8✔
6951
        changeset.push_back(update_1);
8✔
6952
        server_changesets.push_back(std::move(changeset));
8✔
6953
    };
8✔
6954
    prep_changeset("bizz", 1);
2✔
6955
    prep_changeset("buzz", 2);
2✔
6956
    prep_changeset("baz", 3);
2✔
6957
    prep_changeset("bar", 4);
2✔
6958
    ++server_changesets.back().version;
2✔
6959

6960
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6961
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6962
    for (const auto& changeset : server_changesets) {
8✔
6963
        encoded.emplace_back();
8✔
6964
        encode_changeset(changeset, encoded.back());
8✔
6965
        server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
8✔
6966
                                               BinaryData(encoded.back().data(), encoded.back().size()),
8✔
6967
                                               changeset.origin_timestamp, changeset.origin_file_ident);
8✔
6968
    }
8✔
6969

6970
    SyncProgress progress = {};
2✔
6971
    progress.download.server_version = server_changesets.back().version;
2✔
6972
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6973
    progress.latest_server_version.version = server_changesets.back().version;
2✔
6974
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6975

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
}
2✔
6982

6983
TEST(Sync_DanglingLinksCountInPriorSize)
6984
{
2✔
6985
    SHARED_GROUP_TEST_PATH(path);
2✔
6986
    ClientReplication repl;
2✔
6987
    auto local_db = realm::DB::create(repl, path);
2✔
6988
    auto& history = repl.get_history();
2✔
6989
    history.set_client_file_ident(sync::SaltedFileIdent{1, 123456}, true);
2✔
6990

6991
    version_type last_version, last_version_observed = 0;
2✔
6992
    auto dump_uploadable = [&] {
4✔
6993
        UploadCursor upload_cursor{last_version_observed, 0};
4✔
6994
        std::vector<sync::ClientHistory::UploadChangeset> changesets_to_upload;
4✔
6995
        version_type locked_server_version = 0;
4✔
6996
        history.find_uploadable_changesets(upload_cursor, last_version, changesets_to_upload, locked_server_version);
4✔
6997
        CHECK_EQUAL(changesets_to_upload.size(), static_cast<size_t>(1));
4✔
6998
        realm::sync::Changeset parsed_changeset;
4✔
6999
        auto unparsed_changeset = changesets_to_upload[0].changeset.get_first_chunk();
4✔
7000
        realm::util::SimpleInputStream changeset_stream(unparsed_changeset);
4✔
7001
        realm::sync::parse_changeset(changeset_stream, parsed_changeset);
4✔
7002
        test_context.logger->info("changeset at version %1: %2", last_version, parsed_changeset);
4✔
7003
        last_version_observed = last_version;
4✔
7004
        return parsed_changeset;
4✔
7005
    };
4✔
7006

7007
    TableKey source_table_key, target_table_key;
2✔
7008
    {
2✔
7009
        auto wt = local_db->start_write();
2✔
7010
        auto source_table = wt->add_table_with_primary_key("class_source", type_String, "_id");
2✔
7011
        auto target_table = wt->add_table_with_primary_key("class_target", type_String, "_id");
2✔
7012
        source_table->add_column_list(*target_table, "links");
2✔
7013

7014
        source_table_key = source_table->get_key();
2✔
7015
        target_table_key = target_table->get_key();
2✔
7016

7017
        auto obj_to_keep = target_table->create_object_with_primary_key(std::string{"target1"});
2✔
7018
        auto obj_to_delete = target_table->create_object_with_primary_key(std::string{"target2"});
2✔
7019
        auto source_obj = source_table->create_object_with_primary_key(std::string{"source"});
2✔
7020

7021
        auto links_list = source_obj.get_linklist("links");
2✔
7022
        links_list.add(obj_to_keep.get_key());
2✔
7023
        links_list.add(obj_to_delete.get_key());
2✔
7024
        last_version = wt->commit();
2✔
7025
    }
2✔
7026

7027
    dump_uploadable();
2✔
7028

7029
    {
2✔
7030
        // Simulate removing the object via the sync client so we get a dangling link
7031
        TempShortCircuitReplication disable_repl(repl);
2✔
7032
        auto wt = local_db->start_write();
2✔
7033
        auto target_table = wt->get_table(target_table_key);
2✔
7034
        auto obj = target_table->get_object_with_primary_key(std::string{"target2"});
2✔
7035
        obj.invalidate();
2✔
7036
        last_version = wt->commit();
2✔
7037
    }
2✔
7038

7039
    {
2✔
7040
        auto wt = local_db->start_write();
2✔
7041
        auto source_table = wt->get_table(source_table_key);
2✔
7042
        auto target_table = wt->get_table(target_table_key);
2✔
7043

7044
        auto obj_to_add = target_table->create_object_with_primary_key(std::string{"target3"});
2✔
7045

7046
        auto source_obj = source_table->get_object_with_primary_key(std::string{"source"});
2✔
7047
        auto links_list = source_obj.get_linklist("links");
2✔
7048
        links_list.add(obj_to_add.get_key());
2✔
7049
        last_version = wt->commit();
2✔
7050
    }
2✔
7051

7052
    auto changeset = dump_uploadable();
2✔
7053
    CHECK_EQUAL(changeset.size(), static_cast<size_t>(2));
2✔
7054
    auto changeset_it = changeset.end();
2✔
7055
    --changeset_it;
2✔
7056
    auto last_instr = *changeset_it;
2✔
7057
    CHECK_EQUAL(last_instr->type(), Instruction::Type::ArrayInsert);
2✔
7058
    auto arr_insert_instr = last_instr->get_as<Instruction::ArrayInsert>();
2✔
7059
    CHECK_EQUAL(changeset.get_string(arr_insert_instr.table), StringData("source"));
2✔
7060
    CHECK(arr_insert_instr.value.type == sync::instr::Payload::Type::Link);
2✔
7061
    CHECK_EQUAL(changeset.get_string(mpark::get<InternString>(arr_insert_instr.value.data.link.target)),
2✔
7062
                StringData("target3"));
2✔
7063
    CHECK_EQUAL(arr_insert_instr.prior_size, 2);
2✔
7064
}
2✔
7065

7066
// This test calls row_for_object_id() for various object ids and tests that
7067
// the right value is returned including that no assertions are hit.
7068
TEST(Sync_RowForGlobalKey)
7069
{
2✔
7070
    TEST_CLIENT_DB(db);
2✔
7071

7072
    {
2✔
7073
        WriteTransaction wt(db);
2✔
7074
        TableRef table = wt.add_table("class_foo");
2✔
7075
        table->add_column(type_Int, "i");
2✔
7076
        wt.commit();
2✔
7077
    }
2✔
7078

7079
    // Check that various object_ids are not in the table.
7080
    {
2✔
7081
        ReadTransaction rt(db);
2✔
7082
        ConstTableRef table = rt.get_table("class_foo");
2✔
7083
        CHECK(table);
2✔
7084

7085
        // Default constructed GlobalKey
7086
        {
2✔
7087
            GlobalKey object_id;
2✔
7088
            auto row_ndx = table->get_objkey(object_id);
2✔
7089
            CHECK_NOT(row_ndx);
2✔
7090
        }
2✔
7091

7092
        // GlobalKey with small lo and hi values
7093
        {
2✔
7094
            GlobalKey object_id{12, 24};
2✔
7095
            auto row_ndx = table->get_objkey(object_id);
2✔
7096
            CHECK_NOT(row_ndx);
2✔
7097
        }
2✔
7098

7099
        // GlobalKey with lo and hi values past the 32 bit limit.
7100
        {
2✔
7101
            GlobalKey object_id{uint_fast64_t(1) << 50, uint_fast64_t(1) << 52};
2✔
7102
            auto row_ndx = table->get_objkey(object_id);
2✔
7103
            CHECK_NOT(row_ndx);
2✔
7104
        }
2✔
7105
    }
2✔
7106
}
2✔
7107

7108
TEST(Sync_FirstPromoteToWriteAdvancesRead)
7109
{
2✔
7110
    TEST_CLIENT_DB(db);
2✔
7111
    auto db2 = DB::create(make_client_replication(), db_path);
2✔
7112
    auto read = db->start_read();
2✔
7113
    db2->start_write()->commit();
2✔
7114
    // This will hit `ClientHistory::update_from_ref_and_version()` with m_group
7115
    // unset since it's advancing the read transaction without ever having been
7116
    // in a write transaction before.
7117
    read->promote_to_write();
2✔
7118
}
2✔
7119

7120

7121
} // 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

© 2026 Coveralls, Inc