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

realm / realm-core / 2445

27 Jun 2024 10:49PM UTC coverage: 90.953% (-0.005%) from 90.958%
2445

push

Evergreen

ironage
remove unused method now that FlatMap uses the < comparator

102162 of 180396 branches covered (56.63%)

214763 of 236124 relevant lines covered (90.95%)

5974974.91 hits per line

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

92.91
/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,122✔
112
    WriteTransaction wt(db);
8,122✔
113
    function(wt);
8,122✔
114
    return wt.commit();
8,122✔
115
}
8,122✔
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;
48✔
755
        REALM_ASSERT(error_info);
11✔
756
        CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
11✔
757
        CHECK(!error_info->is_fatal);
11✔
758
        CHECK_EQUAL(error_info->status.reason(),
11✔
759
                    "Failed to transform received changeset: Schema mismatch: " + expected_error);
11✔
760
        fixture.stop();
11✔
761
    }
11✔
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,129✔
1317
                if (random.chance(1, 5)) {
1,129✔
1318
                    table->create_object_with_primary_key(i);
234✔
1319
                }
234✔
1320
                int value = random.draw_int(-32767, 32767);
1,129✔
1321
                size_t row_ndx = random.draw_int_mod(table->size());
1,129✔
1322
                table->get_object(row_ndx).set("i", value);
1,129✔
1323
            }
1,129✔
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) {
454✔
1436
            if (CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read")) {
454✔
1437
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
454✔
1438
                CHECK_NOT(is_fatal);
454✔
1439
                fixture.cancel_reconnect_delay();
454✔
1440
            }
454✔
1441
        };
454✔
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) {
520✔
1499
            CHECK_NOT(is_fatal);
520✔
1500
            fixture.cancel_reconnect_delay();
520✔
1501
        };
520✔
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) {
96✔
2039
        std::ostringstream out;
96✔
2040
        out << server_index << "_" << realm_index << "_" << file_index << ".realm";
96✔
2041
        return util::File::resolve(out.str(), dir_2);
96✔
2042
    };
96✔
2043
    std::atomic<int> id = 0;
2✔
2044

2045
    auto run = [&](int server_index, int realm_index, int file_index) {
32✔
2046
        try {
32✔
2047
            std::string path = get_file_path(server_index, realm_index, file_index);
32✔
2048
            DBRef db = DB::create(make_client_replication(), path);
32✔
2049
            {
32✔
2050
                WriteTransaction wt(db);
32✔
2051
                TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
32✔
2052
                table->add_column(type_Int, "server_index");
32✔
2053
                table->add_column(type_Int, "realm_index");
32✔
2054
                table->add_column(type_Int, "file_index");
32✔
2055
                table->add_column(type_Int, "session_index");
32✔
2056
                table->add_column(type_Int, "transact_index");
32✔
2057
                wt.commit();
32✔
2058
            }
32✔
2059
            std::string server_path = "/" + std::to_string(realm_index);
32✔
2060
            for (int i = 0; i < num_sessions_per_file; ++i) {
288✔
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
        }
32✔
2077
        catch (...) {
32✔
2078
            fixture.stop();
×
2079
            throw;
×
2080
        }
×
2081
    };
32✔
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);
32✔
2106
                    });
32✔
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
    struct ProgressInfo {
2✔
2931
        uint64_t downloaded_bytes = 0;
2✔
2932
        uint64_t downloadable_bytes = 0;
2✔
2933
        uint64_t uploaded_bytes = 0;
2✔
2934
        uint64_t uploadable_bytes = 0;
2✔
2935
        uint64_t snapshot_version = 0;
2✔
2936
    };
2✔
2937

2938
    ProgressInfo first_run_progress;
2✔
2939

2940
    {
2✔
2941
        ClientServerFixture fixture(server_dir, test_context);
2✔
2942
        fixture.start();
2✔
2943

2944
        Session::Config config;
2✔
2945
        std::mutex progress_mutex;
2✔
2946
        std::condition_variable progress_cv;
2✔
2947
        std::optional<ProgressInfo> observed_progress;
2✔
2948
        auto wait_for_progress_info = [&] {
6✔
2949
            std::unique_lock lk(progress_mutex);
6✔
2950
            progress_cv.wait(lk, [&] {
8✔
2951
                return observed_progress;
8✔
2952
            });
8✔
2953
            auto ret = std::exchange(observed_progress, std::optional<ProgressInfo>{});
6✔
2954
            return *ret;
6✔
2955
        };
6✔
2956
        config.progress_handler = [&](uint64_t downloaded, uint64_t downloadable, uint64_t uploaded,
2✔
2957
                                      uint64_t uploadable, uint64_t snapshot, double, double, int64_t) {
10✔
2958
            std::lock_guard lk(progress_mutex);
10✔
2959
            observed_progress = ProgressInfo{downloaded, downloadable, uploaded, uploadable, snapshot};
10✔
2960
            progress_cv.notify_one();
10✔
2961
        };
10✔
2962

2963

2964
        Session session = fixture.make_session(db, "/test", std::move(config));
2✔
2965
        auto progress_info = wait_for_progress_info();
2✔
2966

2967
        CHECK_EQUAL(progress_info.downloaded_bytes, uint_fast64_t(0));
2✔
2968
        CHECK_EQUAL(progress_info.downloadable_bytes, uint_fast64_t(0));
2✔
2969
        CHECK_EQUAL(progress_info.uploaded_bytes, uint_fast64_t(0));
2✔
2970
        CHECK_EQUAL(progress_info.uploadable_bytes, uint_fast64_t(0));
2✔
2971
        CHECK_GREATER_EQUAL(progress_info.snapshot_version, 1);
2✔
2972

2973
        auto commit_version = write_transaction(db, [](WriteTransaction& wt) {
2✔
2974
            auto tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2975
            tr->add_column(type_Int, "integer column");
2✔
2976
        });
2✔
2977

2978
        session.wait_for_upload_complete_or_client_stopped();
2✔
2979
        session.wait_for_download_complete_or_client_stopped();
2✔
2980

2981
        auto old_progress_info = progress_info;
2✔
2982
        progress_info = wait_for_progress_info();
2✔
2983
        CHECK_EQUAL(progress_info.downloaded_bytes, uint_fast64_t(0));
2✔
2984
        CHECK_EQUAL(progress_info.downloadable_bytes, uint_fast64_t(0));
2✔
2985
        CHECK_GREATER(progress_info.uploaded_bytes, old_progress_info.uploaded_bytes);
2✔
2986
        CHECK_GREATER(progress_info.uploadable_bytes, old_progress_info.uploadable_bytes);
2✔
2987
        CHECK_GREATER_EQUAL(progress_info.snapshot_version, commit_version);
2✔
2988

2989
        commit_version = write_transaction(db, [](WriteTransaction& wt) {
2✔
2990
            wt.get_table("class_table")->create_object_with_primary_key(1).set("integer column", 42);
2✔
2991
        });
2✔
2992

2993
        session.wait_for_upload_complete_or_client_stopped();
2✔
2994
        session.wait_for_download_complete_or_client_stopped();
2✔
2995

2996
        old_progress_info = progress_info;
2✔
2997
        progress_info = wait_for_progress_info();
2✔
2998
        CHECK_EQUAL(progress_info.downloaded_bytes, uint_fast64_t(0));
2✔
2999
        CHECK_EQUAL(progress_info.downloadable_bytes, uint_fast64_t(0));
2✔
3000
        CHECK_GREATER(progress_info.uploaded_bytes, old_progress_info.uploaded_bytes);
2✔
3001
        CHECK_GREATER(progress_info.uploadable_bytes, old_progress_info.uploadable_bytes);
2✔
3002
        CHECK_GREATER_EQUAL(progress_info.snapshot_version, commit_version);
2✔
3003
        first_run_progress = progress_info;
2✔
3004
    }
2✔
3005

3006
    {
2✔
3007
        // Here we check that the progress handler is called
3008
        // after the session is bound, and that the values
3009
        // are the ones stored in the Realm in the previous
3010
        // session.
3011

3012
        ClientServerFixture fixture(server_dir, test_context);
2✔
3013
        fixture.start();
2✔
3014

3015
        int number_of_handler_calls = 0;
2✔
3016
        auto pf = util::make_promise_future<int>();
2✔
3017
        Session::Config config;
2✔
3018
        config.progress_handler = [&](uint64_t downloaded, uint64_t downloadable, uint64_t uploaded,
2✔
3019
                                      uint64_t uploadable, uint64_t snapshot, double, double, int64_t) {
2✔
3020
            CHECK_EQUAL(downloaded, first_run_progress.downloaded_bytes);
2✔
3021
            CHECK_EQUAL(downloadable, first_run_progress.downloadable_bytes);
2✔
3022
            CHECK_EQUAL(uploaded, first_run_progress.uploaded_bytes);
2✔
3023
            CHECK_EQUAL(uploadable, first_run_progress.uploadable_bytes);
2✔
3024
            CHECK_GREATER(snapshot, first_run_progress.snapshot_version);
2✔
3025
            number_of_handler_calls++;
2✔
3026
            pf.promise.emplace_value(number_of_handler_calls);
2✔
3027
        };
2✔
3028

3029
        Session session = fixture.make_session(db, "/test", std::move(config));
2✔
3030
        CHECK_EQUAL(pf.future.get(), 1);
2✔
3031
    }
2✔
3032
}
2✔
3033

3034

3035
// This test creates one server and a client with
3036
// two sessions that synchronizes with the same server Realm.
3037
// The clients generate changesets, uploads and downloads, and
3038
// waits for upload/download completion. Both sessions have a
3039
// progress handler registered, and it is checked that the
3040
// progress handlers report the correct values.
3041
TEST(Sync_UploadDownloadProgress_2)
3042
{
2✔
3043
    TEST_DIR(server_dir);
2✔
3044
    TEST_CLIENT_DB(db_1);
2✔
3045
    TEST_CLIENT_DB(db_2);
2✔
3046

3047
    ClientServerFixture fixture(server_dir, test_context);
2✔
3048
    fixture.start();
2✔
3049

3050
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3051
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3052
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3053
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3054
    uint_fast64_t snapshot_version_1 = 0;
2✔
3055

3056
    Session::Config config_1;
2✔
3057
    config_1.progress_handler = [&](uint64_t downloaded_bytes, uint64_t downloadable_bytes, uint64_t uploaded_bytes,
2✔
3058
                                    uint64_t uploadable_bytes, uint64_t snapshot_version, double, double, int64_t) {
21✔
3059
        downloaded_bytes_1 = downloaded_bytes;
21✔
3060
        downloadable_bytes_1 = downloadable_bytes;
21✔
3061
        uploaded_bytes_1 = uploaded_bytes;
21✔
3062
        uploadable_bytes_1 = uploadable_bytes;
21✔
3063
        snapshot_version_1 = snapshot_version;
21✔
3064
    };
21✔
3065

3066
    uint_fast64_t downloaded_bytes_2 = 123;
2✔
3067
    uint_fast64_t downloadable_bytes_2 = 123;
2✔
3068
    uint_fast64_t uploaded_bytes_2 = 123;
2✔
3069
    uint_fast64_t uploadable_bytes_2 = 123;
2✔
3070
    uint_fast64_t snapshot_version_2 = 0;
2✔
3071

3072
    Session::Config config_2;
2✔
3073
    config_2.progress_handler = [&](uint64_t downloaded_bytes, uint64_t downloadable_bytes, uint64_t uploaded_bytes,
2✔
3074
                                    uint64_t uploadable_bytes, uint64_t snapshot_version, double, double, int64_t) {
14✔
3075
        downloaded_bytes_2 = downloaded_bytes;
14✔
3076
        downloadable_bytes_2 = downloadable_bytes;
14✔
3077
        uploaded_bytes_2 = uploaded_bytes;
14✔
3078
        uploadable_bytes_2 = uploadable_bytes;
14✔
3079
        snapshot_version_2 = snapshot_version;
14✔
3080
    };
14✔
3081

3082
    Session session_1 = fixture.make_session(db_1, "/test", std::move(config_1));
2✔
3083
    Session session_2 = fixture.make_session(db_2, "/test", std::move(config_2));
2✔
3084

3085
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3086
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3087
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3088
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3089

3090
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3091
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3092
    CHECK_EQUAL(downloaded_bytes_1, downloaded_bytes_2);
2✔
3093
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3094
    CHECK_GREATER(snapshot_version_1, 0);
2✔
3095

3096
    CHECK_EQUAL(uploaded_bytes_1, 0);
2✔
3097
    CHECK_EQUAL(uploadable_bytes_1, 0);
2✔
3098

3099
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3100
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3101
    CHECK_GREATER(snapshot_version_2, 0);
2✔
3102

3103
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3104
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3105
        tr->add_column(type_Int, "integer column");
2✔
3106
    });
2✔
3107

3108
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3109
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3110
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3111
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3112

3113
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3114
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3115

3116
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3117
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3118

3119
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3120
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3121

3122
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3123
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3124

3125
    CHECK_GREATER(snapshot_version_1, 1);
2✔
3126
    CHECK_GREATER(snapshot_version_2, 1);
2✔
3127

3128
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3129
        TableRef tr = wt.get_table("class_table");
2✔
3130
        tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3131
    });
2✔
3132

3133
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3134
        TableRef tr = wt.get_table("class_table");
2✔
3135
        tr->create_object_with_primary_key(2).set("integer column", 44);
2✔
3136
    });
2✔
3137

3138
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3139
        TableRef tr = wt.get_table("class_table");
2✔
3140
        tr->create_object_with_primary_key(3).set("integer column", 43);
2✔
3141
    });
2✔
3142

3143
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3144
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3145
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3146
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3147

3148
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3149
    CHECK_NOT_EQUAL(downloadable_bytes_1, 0);
2✔
3150

3151
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3152
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3153

3154
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3155
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3156

3157
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3158
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3159

3160
    CHECK_GREATER(snapshot_version_1, 4);
2✔
3161
    CHECK_GREATER(snapshot_version_2, 3);
2✔
3162

3163
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3164
        TableRef tr = wt.get_table("class_table");
2✔
3165
        tr->begin()->set("integer column", 101);
2✔
3166
    });
2✔
3167

3168
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3169
        TableRef tr = wt.get_table("class_table");
2✔
3170
        tr->begin()->set("integer column", 102);
2✔
3171
    });
2✔
3172

3173
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3174
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3175
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3176
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3177

3178
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3179

3180
    // uncertainty due to merge
3181
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3182

3183
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3184
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3185

3186
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3187
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3188

3189
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3190
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3191

3192
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3193
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3194

3195
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3196
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3197

3198
    // Check convergence.
3199
    {
2✔
3200
        ReadTransaction rt_1(db_1);
2✔
3201
        ReadTransaction rt_2(db_2);
2✔
3202
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
3203
    }
2✔
3204
}
2✔
3205

3206

3207
// This test creates a server and a client. Initially, the server is not running.
3208
// The client generates changes and binds a session. It is verified that the
3209
// progress_handler() is called and that the four arguments of progress_handler()
3210
// have the correct values. The server is started in the first call to
3211
// progress_handler() and it is checked that after upload and download completion,
3212
// the upload_progress_handler has been called again, and that the four arguments
3213
// have the correct values. After this, the server is stopped and the client produces
3214
// more changes. It is checked that the progress_handler() is called and that the
3215
// final values are correct.
3216
TEST(Sync_UploadDownloadProgress_3)
3217
{
2✔
3218
    TEST_DIR(server_dir);
2✔
3219
    TEST_CLIENT_DB(db);
2✔
3220

3221
    std::string server_address = "localhost";
2✔
3222

3223
    Server::Config server_config;
2✔
3224
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3225
    server_config.listen_address = server_address;
2✔
3226
    server_config.listen_port = "";
2✔
3227
    server_config.tcp_no_delay = true;
2✔
3228

3229
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3230
    Server server(server_dir, std::move(public_key), server_config);
2✔
3231
    server.start();
2✔
3232
    auto server_port = server.listen_endpoint().port();
2✔
3233

3234
    ThreadWrapper server_thread;
2✔
3235

3236
    // The server is not running.
3237

3238
    {
2✔
3239
        WriteTransaction wt{db};
2✔
3240
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3241
        tr->add_column(type_Int, "integer column");
2✔
3242
        wt.commit();
2✔
3243
    }
2✔
3244

3245
    Client::Config client_config;
2✔
3246
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3247
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3248
    client_config.socket_provider = socket_provider;
2✔
3249
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3250
    Client client(client_config);
2✔
3251

3252
    // entry is used to count the number of calls to
3253
    // progress_handler. At the first call, the server is
3254
    // not running, and it is started by progress_handler().
3255

3256
    bool should_signal_cond_var = false;
2✔
3257
    auto signal_pf = util::make_promise_future<void>();
2✔
3258

3259
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3260
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3261
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3262
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3263
    uint_fast64_t snapshot_version_1 = 0;
2✔
3264

3265
    Session::Config config;
2✔
3266
    config.service_identifier = "/realm-sync";
2✔
3267
    config.server_address = server_address;
2✔
3268
    config.signed_user_token = g_signed_test_user_token;
2✔
3269
    config.server_port = server_port;
2✔
3270
    config.realm_identifier = "/test";
2✔
3271
    config.progress_handler = [&, entry = 0](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3272
                                             uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3273
                                             uint_fast64_t snapshot_version, double, double, int64_t) mutable {
6✔
3274
        downloaded_bytes_1 = downloaded_bytes;
6✔
3275
        downloadable_bytes_1 = downloadable_bytes;
6✔
3276
        uploaded_bytes_1 = uploaded_bytes;
6✔
3277
        uploadable_bytes_1 = uploadable_bytes;
6✔
3278
        snapshot_version_1 = snapshot_version;
6✔
3279

3280
        if (entry == 0) {
6✔
3281
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3282
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3283
            CHECK_EQUAL(uploaded_bytes, 0);
2✔
3284
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3285
            CHECK_EQUAL(snapshot_version, 4);
2✔
3286
        }
2✔
3287

3288
        if (should_signal_cond_var) {
6✔
3289
            signal_pf.promise.emplace_value();
2✔
3290
        }
2✔
3291

3292
        entry++;
6✔
3293
    };
6✔
3294

3295
    server_thread.start([&] {
2✔
3296
        server.run();
2✔
3297
    });
2✔
3298

3299
    Session session(client, db, nullptr, nullptr, std::move(config));
2✔
3300

3301
    session.wait_for_upload_complete_or_client_stopped();
2✔
3302
    session.wait_for_download_complete_or_client_stopped();
2✔
3303

3304
    // Now the server is running.
3305

3306
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3307
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3308
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3309
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3310
    CHECK_GREATER_EQUAL(snapshot_version_1, 2);
2✔
3311

3312
    server.stop();
2✔
3313

3314
    // The server is stopped
3315

3316
    should_signal_cond_var = true;
2✔
3317

3318
    uint_fast64_t commited_version;
2✔
3319
    {
2✔
3320
        WriteTransaction wt{db};
2✔
3321
        TableRef tr = wt.get_table("class_table");
2✔
3322
        tr->create_object_with_primary_key(123).set("integer column", 42);
2✔
3323
        commited_version = wt.commit();
2✔
3324
    }
2✔
3325

3326
    signal_pf.future.get();
2✔
3327

3328
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3329
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3330
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3331
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3332
    CHECK_EQUAL(snapshot_version_1, commited_version);
2✔
3333

3334
    server_thread.join();
2✔
3335
}
2✔
3336

3337

3338
// This test creates a server and two clients. The first client uploads two
3339
// large changesets. The other client downloads them. The download messages to
3340
// the second client contains one changeset because the changesets are larger
3341
// than the soft size limit for changesets in the DOWNLOAD message. This implies
3342
// that after receiving the first DOWNLOAD message, the second client will have
3343
// downloaded_bytes < downloadable_bytes.
3344
TEST(Sync_UploadDownloadProgress_4)
3345
{
2✔
3346
    TEST_DIR(server_dir);
2✔
3347
    TEST_CLIENT_DB(db_1);
2✔
3348
    TEST_CLIENT_DB(db_2);
2✔
3349

3350
    {
2✔
3351
        WriteTransaction wt{db_1};
2✔
3352
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3353
        auto col = tr->add_column(type_Binary, "binary column");
2✔
3354
        tr->create_object_with_primary_key(1);
2✔
3355
        std::string str(size_t(5e5), '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
    {
2✔
3362
        WriteTransaction wt{db_1};
2✔
3363
        TableRef tr = wt.get_table("class_table");
2✔
3364
        auto col = tr->get_column_key("binary column");
2✔
3365
        tr->create_object_with_primary_key(2);
2✔
3366
        std::string str(size_t(1e6), 'a');
2✔
3367
        BinaryData bd(str.data(), str.size());
2✔
3368
        tr->begin()->set(col, bd);
2✔
3369
        wt.commit();
2✔
3370
    }
2✔
3371

3372
    ClientServerFixture::Config config;
2✔
3373
    config.max_download_size = size_t(1e5);
2✔
3374
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3375
    fixture.start();
2✔
3376

3377
    int entry_1 = 0;
2✔
3378
    Session::Config config_1;
2✔
3379
    config_1.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3380
                                    uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3381
                                    uint_fast64_t snapshot_version, double, double, int64_t) {
6✔
3382
        CHECK_EQUAL(downloaded_bytes, 0);
6✔
3383
        CHECK_EQUAL(downloadable_bytes, 0);
6✔
3384
        CHECK_NOT_EQUAL(uploadable_bytes, 0);
6✔
3385

3386
        switch (entry_1) {
6✔
3387
            case 0:
2✔
3388
                // We've received the empty DOWNLOAD message and now have reliable
3389
                // download progress
3390
                CHECK_EQUAL(uploaded_bytes, 0);
2✔
3391
                CHECK_EQUAL(snapshot_version, 5);
2✔
3392
                break;
2✔
3393

3394
            case 1:
2✔
3395
                // First UPLOAD is complete, but we still have more to upload
3396
                // because the changesets are too large to batch into a single upload
3397
                CHECK_GREATER(uploaded_bytes, 0);
2✔
3398
                CHECK_LESS(uploaded_bytes, uploadable_bytes);
2✔
3399
                CHECK_EQUAL(snapshot_version, 6);
2✔
3400
                break;
2✔
3401

3402
            case 2:
2✔
3403
                // Second UPLOAD is complete and we're done uploading
3404
                CHECK_EQUAL(uploaded_bytes, uploadable_bytes);
2✔
3405
                CHECK_EQUAL(snapshot_version, 7);
2✔
3406
                break;
2✔
3407
        }
6✔
3408

3409
        ++entry_1;
6✔
3410
    };
6✔
3411

3412
    Session session_1 = fixture.make_session(db_1, "/test", std::move(config_1));
2✔
3413
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3414
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3415

3416
    CHECK_EQUAL(entry_1, 3);
2✔
3417

3418
    int entry_2 = 0;
2✔
3419

3420
    Session::Config config_2;
2✔
3421
    config_2.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3422
                                    uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3423
                                    uint_fast64_t snapshot_version, double, double, int64_t) {
4✔
3424
        CHECK_EQUAL(uploaded_bytes, 0);
4✔
3425
        CHECK_EQUAL(uploadable_bytes, 0);
4✔
3426

3427
        switch (entry_2) {
4✔
3428
            case 0:
2✔
3429
                // First DOWNLOAD message received. Some data is downloaded, but
3430
                // download isn't compelte
3431
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3432
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3433
                CHECK_LESS(downloaded_bytes, downloadable_bytes);
2✔
3434
                CHECK_EQUAL(snapshot_version, 3);
2✔
3435
                break;
2✔
3436

3437
            case 1:
2✔
3438
                // Second DOWNLOAD message received. Download is now complete.
3439
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3440
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3441
                CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
3442
                CHECK_EQUAL(snapshot_version, 4);
2✔
3443
                break;
2✔
3444
        }
4✔
3445
        ++entry_2;
4✔
3446
    };
4✔
3447

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

3450
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3451
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3452
    CHECK_EQUAL(entry_2, 2);
2✔
3453
}
2✔
3454

3455

3456
// This test has a single client connected to a server with one session. The
3457
// client does not create any changesets. The test verifies that the client gets
3458
// a confirmation from the server of downloadable_bytes = 0.
3459
TEST(Sync_UploadDownloadProgress_5)
3460
{
2✔
3461
    TEST_DIR(server_dir);
2✔
3462
    TEST_CLIENT_DB(db);
2✔
3463

3464
    ClientServerFixture fixture(server_dir, test_context);
2✔
3465
    fixture.start();
2✔
3466

3467
    auto pf = util::make_promise_future();
2✔
3468
    Session::Config config;
2✔
3469
    config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3470
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3471
                                  uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
3472
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3473
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3474
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3475
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3476
        CHECK_EQUAL(snapshot_version, 3);
2✔
3477
        pf.promise.emplace_value();
2✔
3478
    };
2✔
3479

3480
    Session session = fixture.make_session(db, "/test", std::move(config));
2✔
3481
    pf.future.get();
2✔
3482

3483
    // The check is that we reach this point.
3484
}
2✔
3485

3486

3487
// This test has a single client connected to a server with one session.
3488
// The session has a registered progress handler.
3489
TEST(Sync_UploadDownloadProgress_6)
3490
{
2✔
3491
    TEST_DIR(server_dir);
2✔
3492
    TEST_CLIENT_DB(db);
2✔
3493

3494
    Server::Config server_config;
2✔
3495
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3496
    server_config.listen_address = "localhost";
2✔
3497
    server_config.listen_port = "";
2✔
3498
    server_config.tcp_no_delay = true;
2✔
3499

3500
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3501
    Server server(server_dir, std::move(public_key), server_config);
2✔
3502
    server.start();
2✔
3503

3504
    auto server_port = server.listen_endpoint().port();
2✔
3505

3506
    ThreadWrapper server_thread;
2✔
3507
    server_thread.start([&] {
2✔
3508
        server.run();
2✔
3509
    });
2✔
3510

3511
    Client::Config client_config;
2✔
3512
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3513
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3514
    client_config.socket_provider = socket_provider;
2✔
3515
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3516
    client_config.one_connection_per_session = false;
2✔
3517
    Client client(client_config);
2✔
3518

3519
    util::ScopeExit cleanup([&]() noexcept {
2✔
3520
        client.shutdown_and_wait();
2✔
3521
        server.stop();
2✔
3522
        server_thread.join();
2✔
3523
    });
2✔
3524

3525
    auto session_pf = util::make_promise_future<std::unique_ptr<Session>*>();
2✔
3526
    auto complete_pf = util::make_promise_future();
2✔
3527
    Session::Config session_config;
2✔
3528
    session_config.server_address = "localhost";
2✔
3529
    session_config.server_port = server_port;
2✔
3530
    session_config.realm_identifier = "/test";
2✔
3531
    session_config.service_identifier = "/realm-sync";
2✔
3532
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3533
    session_config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3534
                                          uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3535
                                          uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
3536
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3537
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3538
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3539
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3540
        CHECK_EQUAL(snapshot_version, 3);
2✔
3541
        session_pf.future.get()->reset();
2✔
3542
        complete_pf.promise.emplace_value();
2✔
3543
    };
2✔
3544
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3545
    session_pf.promise.emplace_value(&session);
2✔
3546
    complete_pf.future.get();
2✔
3547
    CHECK(!session);
2✔
3548

3549
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
3550
    // down the active session
3551
}
2✔
3552

3553
// This test has a single client starting to connect to the server with one session.
3554
// The client is torn down immediately after bind is called on the session.
3555
// The session will still be active and has an unactualized session wrapper when the
3556
// client is torn down, which leads to both calls to finalize_before_actualization() and
3557
// and finalize().
3558
TEST(Sync_UploadDownloadProgress_7)
3559
{
2✔
3560
    TEST_DIR(server_dir);
2✔
3561
    TEST_CLIENT_DB(db);
2✔
3562

3563
    Server::Config server_config;
2✔
3564
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3565
    server_config.listen_address = "localhost";
2✔
3566
    server_config.listen_port = "";
2✔
3567
    server_config.tcp_no_delay = true;
2✔
3568

3569
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3570
    Server server(server_dir, std::move(public_key), server_config);
2✔
3571
    server.start();
2✔
3572

3573
    auto server_port = server.listen_endpoint().port();
2✔
3574

3575
    ThreadWrapper server_thread;
2✔
3576
    server_thread.start([&] {
2✔
3577
        server.run();
2✔
3578
    });
2✔
3579

3580
    Client::Config client_config;
2✔
3581
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3582
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3583
    client_config.socket_provider = socket_provider;
2✔
3584
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3585
    client_config.one_connection_per_session = false;
2✔
3586
    Client client(client_config);
2✔
3587

3588
    Session::Config session_config;
2✔
3589
    session_config.server_address = "localhost";
2✔
3590
    session_config.server_port = server_port;
2✔
3591
    session_config.realm_identifier = "/test";
2✔
3592
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3593

3594
    Session session(client, db, nullptr, nullptr, std::move(session_config));
2✔
3595

3596
    client.shutdown_and_wait();
2✔
3597
    server.stop();
2✔
3598
    server_thread.join();
2✔
3599

3600
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
3601
    // down the session that is in the process of being created.
3602
}
2✔
3603

3604
TEST(Sync_UploadProgress_EmptyCommits)
3605
{
2✔
3606
    TEST_DIR(server_dir);
2✔
3607
    TEST_CLIENT_DB(db);
2✔
3608

3609
    ClientServerFixture fixture(server_dir, test_context);
2✔
3610
    fixture.start();
2✔
3611

3612
    {
2✔
3613
        WriteTransaction wt{db};
2✔
3614
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "_id");
2✔
3615
        wt.commit();
2✔
3616
    }
2✔
3617

3618
    std::atomic<int> entry = 0;
2✔
3619
    Session::Config config;
2✔
3620
    config.progress_handler = [&](uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, double,
2✔
3621
                                  double, int64_t) {
8✔
3622
        ++entry;
8✔
3623
    };
8✔
3624

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

3627
    // Each step calls wait_for_upload_complete twice because upload completion
3628
    // is fired before progress handlers, so we need another hop through the
3629
    // event loop after upload completion to know that the handler has been called
3630
    session.wait_for_upload_complete_or_client_stopped();
2✔
3631
    session.wait_for_upload_complete_or_client_stopped();
2✔
3632

3633
    // Binding produces two notifications: one after receiving
3634
    // the DOWNLOAD message, and one after uploading the schema
3635
    CHECK_EQUAL(entry, 2);
2✔
3636

3637
    // No notification sent because an empty commit doesn't change uploadable_bytes
3638
    {
2✔
3639
        WriteTransaction wt{db};
2✔
3640
        wt.commit();
2✔
3641
    }
2✔
3642
    session.wait_for_upload_complete_or_client_stopped();
2✔
3643
    session.wait_for_upload_complete_or_client_stopped();
2✔
3644
    CHECK_EQUAL(entry, 2);
2✔
3645

3646
    // Both the external and local commits are empty, so again no change in
3647
    // uploadable_bytes
3648
    {
2✔
3649
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3650
        WriteTransaction wt{db2};
2✔
3651
        wt.commit();
2✔
3652
        WriteTransaction wt2{db};
2✔
3653
        wt2.commit();
2✔
3654
    }
2✔
3655
    session.wait_for_upload_complete_or_client_stopped();
2✔
3656
    session.wait_for_upload_complete_or_client_stopped();
2✔
3657
    CHECK_EQUAL(entry, 2);
2✔
3658

3659
    // Local commit is empty, but the changeset created by the external write
3660
    // is discovered after the local write, resulting in two notifications (one
3661
    // before uploading and one after).
3662
    {
2✔
3663
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3664
        WriteTransaction wt{db2};
2✔
3665
        wt.get_table("class_table")->create_object_with_primary_key(0);
2✔
3666
        wt.commit();
2✔
3667
        WriteTransaction wt2{db};
2✔
3668
        wt2.commit();
2✔
3669
    }
2✔
3670
    session.wait_for_upload_complete_or_client_stopped();
2✔
3671
    session.wait_for_upload_complete_or_client_stopped();
2✔
3672
    CHECK_EQUAL(entry, 4);
2✔
3673
}
2✔
3674

3675
TEST(Sync_MultipleSyncAgentsNotAllowed)
3676
{
2✔
3677
    // At most one sync agent is allowed to participate in a Realm file access
3678
    // session at any particular point in time. Note that a Realm file access
3679
    // session is a group of temporally overlapping accesses to a Realm file,
3680
    // and that the group of participants is the transitive closure of a
3681
    // particular session participant over the "temporally overlapping access"
3682
    // relation.
3683

3684
    TEST_DIR(server_dir);
2✔
3685
    TEST_CLIENT_DB(db);
2✔
3686

3687
    auto pf = util::make_promise_future();
2✔
3688
    struct Observer : BindingCallbackThreadObserver {
2✔
3689
        unit_test::TestContext& test_context;
2✔
3690
        util::Promise<void>& got_error;
2✔
3691
        Observer(unit_test::TestContext& test_context, util::Promise<void>& got_error)
2✔
3692
            : test_context(test_context)
2✔
3693
            , got_error(got_error)
2✔
3694
        {
2✔
3695
        }
2✔
3696

3697
        bool has_handle_error() override
2✔
3698
        {
4✔
3699
            return true;
4✔
3700
        }
4✔
3701
        bool handle_error(const std::exception& e) override
2✔
3702
        {
2✔
3703
            CHECK(dynamic_cast<const MultipleSyncAgents*>(&e));
2✔
3704
            got_error.emplace_value();
2✔
3705
            return true;
2✔
3706
        }
2✔
3707
    };
2✔
3708

3709
    auto observer = std::make_shared<Observer>(test_context, pf.promise);
2✔
3710
    ClientServerFixture::Config config;
2✔
3711
    config.socket_provider_observer = observer;
2✔
3712
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3713
    fixture.start();
2✔
3714

3715
    {
2✔
3716
        Session session = fixture.make_session(db, "/test");
2✔
3717
        Session session2 = fixture.make_session(db, "/test");
2✔
3718
        pf.future.get();
2✔
3719

3720
        // The exception caused the event loop to stop so we need to restart it
3721
        fixture.start_client(0);
2✔
3722
    }
2✔
3723

3724
    // Verify that after the error occurs (and is ignored) things are still
3725
    // in a functional state
3726
    Session session = fixture.make_session(db, "/test");
2✔
3727
    session.wait_for_upload_complete_or_client_stopped();
2✔
3728
}
2✔
3729

3730
TEST(Sync_CancelReconnectDelay)
3731
{
2✔
3732
    TEST_DIR(server_dir);
2✔
3733
    TEST_CLIENT_DB(db);
2✔
3734
    TEST_CLIENT_DB(db_x);
2✔
3735

3736
    ClientServerFixture::Config fixture_config;
2✔
3737
    fixture_config.one_connection_per_session = false;
2✔
3738

3739
    auto expect_status = [&](BowlOfStonesSemaphore& bowl, ErrorCodes::Error code) {
10✔
3740
        Session::Config config;
10✔
3741
        config.connection_state_change_listener = [&, code](ConnectionState state,
10✔
3742
                                                            std::optional<SessionErrorInfo> error) {
50✔
3743
            if (state != ConnectionState::disconnected)
50✔
3744
                return;
36✔
3745
            CHECK(error);
14✔
3746
            if (CHECK_EQUAL(error->status, code))
14✔
3747
                bowl.add_stone();
14✔
3748
        };
14✔
3749
        return config;
10✔
3750
    };
10✔
3751

3752
    // After connection-level error, and at session-level.
3753
    {
2✔
3754
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3755
        fixture.start();
2✔
3756

3757
        BowlOfStonesSemaphore bowl;
2✔
3758
        Session session = fixture.make_session(db, "/test", expect_status(bowl, ErrorCodes::ConnectionClosed));
2✔
3759
        session.wait_for_download_complete_or_client_stopped();
2✔
3760
        fixture.close_server_side_connections();
2✔
3761
        bowl.get_stone();
2✔
3762

3763
        session.cancel_reconnect_delay();
2✔
3764
        session.wait_for_download_complete_or_client_stopped();
2✔
3765
    }
2✔
3766

3767
    // After connection-level error, and at client-level while connection
3768
    // object exists (ConnectionImpl in clinet.cpp).
3769
    {
2✔
3770
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3771
        fixture.start();
2✔
3772

3773
        BowlOfStonesSemaphore bowl;
2✔
3774
        Session session = fixture.make_session(db, "/test", expect_status(bowl, ErrorCodes::ConnectionClosed));
2✔
3775
        session.wait_for_download_complete_or_client_stopped();
2✔
3776
        fixture.close_server_side_connections();
2✔
3777
        bowl.get_stone();
2✔
3778

3779
        fixture.cancel_reconnect_delay();
2✔
3780
        session.wait_for_download_complete_or_client_stopped();
2✔
3781
    }
2✔
3782

3783
    // After connection-level error, and at client-level while connection object
3784
    // does not exist (ConnectionImpl in clinet.cpp).
3785
    {
2✔
3786
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3787
        fixture.start();
2✔
3788

3789
        {
2✔
3790
            BowlOfStonesSemaphore bowl;
2✔
3791
            Session session = fixture.make_session(db, "/test", expect_status(bowl, ErrorCodes::ConnectionClosed));
2✔
3792
            session.wait_for_download_complete_or_client_stopped();
2✔
3793
            fixture.close_server_side_connections();
2✔
3794
            bowl.get_stone();
2✔
3795
        }
2✔
3796

3797
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3798
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3799
        // The connection object no longer exists at this time. After the first
3800
        // of the two waits above, the invocation of ConnectionImpl::on_idle()
3801
        // (in client.cpp) has been scheduled. After the second wait, it has
3802
        // been called, and that destroys the connection object.
3803

3804
        fixture.cancel_reconnect_delay();
2✔
3805
        {
2✔
3806
            Session session = fixture.make_bound_session(db, "/test");
2✔
3807
            session.wait_for_download_complete_or_client_stopped();
2✔
3808
        }
2✔
3809
    }
2✔
3810

3811
    // After session-level error, and at session-level.
3812
    {
2✔
3813
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3814
        fixture.start();
2✔
3815

3816
        // Add a session for the purpose of keeping the connection open
3817
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3818
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3819

3820
        BowlOfStonesSemaphore bowl;
2✔
3821
        Session session = fixture.make_session(db, "/..", expect_status(bowl, ErrorCodes::BadSyncPartitionValue));
2✔
3822
        bowl.get_stone();
2✔
3823

3824
        session.cancel_reconnect_delay();
2✔
3825
        bowl.get_stone();
2✔
3826
    }
2✔
3827

3828
    // After session-level error, and at client-level.
3829
    {
2✔
3830
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3831
        fixture.start();
2✔
3832

3833
        // Add a session for the purpose of keeping the connection open
3834
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3835
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3836

3837
        BowlOfStonesSemaphore bowl;
2✔
3838
        Session session = fixture.make_session(db, "/..", expect_status(bowl, ErrorCodes::BadSyncPartitionValue));
2✔
3839
        bowl.get_stone();
2✔
3840

3841
        fixture.cancel_reconnect_delay();
2✔
3842
        bowl.get_stone();
2✔
3843
    }
2✔
3844
}
2✔
3845

3846

3847
#ifndef REALM_PLATFORM_WIN32
3848

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

3863
    TEST_CLIENT_DB(db_1);
2✔
3864
    TEST_CLIENT_DB(db_2);
2✔
3865

3866
    {
2✔
3867
        WriteTransaction wt(db_1);
2✔
3868
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
3869
        table->add_column(type_Binary, "column name");
2✔
3870
        std::string str_1(binary_sizes[0], 'a');
2✔
3871
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3872
        std::string str_2(binary_sizes[1], 'b');
2✔
3873
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3874
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
3875
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
3876
        wt.commit();
2✔
3877
    }
2✔
3878

3879
    {
2✔
3880
        WriteTransaction wt(db_1);
2✔
3881
        TableRef table = wt.get_table("class_table name");
2✔
3882
        std::string str_1(binary_sizes[2], 'c');
2✔
3883
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3884
        std::string str_2(binary_sizes[3], 'd');
2✔
3885
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3886
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
3887
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
3888
        wt.commit();
2✔
3889
    }
2✔
3890

3891
    {
2✔
3892
        WriteTransaction wt(db_2);
2✔
3893
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
3894
        table->add_column(type_Binary, "column name");
2✔
3895
        std::string str_1(binary_sizes[4], 'e');
2✔
3896
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3897
        std::string str_2(binary_sizes[5], 'f');
2✔
3898
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3899
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
3900
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
3901
        wt.commit();
2✔
3902
    }
2✔
3903

3904
    {
2✔
3905
        WriteTransaction wt(db_2);
2✔
3906
        TableRef table = wt.get_table("class_table name");
2✔
3907
        std::string str_1(binary_sizes[6], 'g');
2✔
3908
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3909
        std::string str_2(binary_sizes[7], 'h');
2✔
3910
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3911
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
3912
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
3913
        wt.commit();
2✔
3914
    }
2✔
3915

3916
    std::uint_fast64_t downloaded_bytes_1 = 0;
2✔
3917
    std::uint_fast64_t downloadable_bytes_1 = 0;
2✔
3918
    std::uint_fast64_t uploaded_bytes_1 = 0;
2✔
3919
    std::uint_fast64_t uploadable_bytes_1 = 0;
2✔
3920

3921
    auto progress_handler_1 = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
3922
                                  std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
3923
                                  std::uint_fast64_t, double, double, int64_t) {
10✔
3924
        downloaded_bytes_1 = downloaded_bytes;
10✔
3925
        downloadable_bytes_1 = downloadable_bytes;
10✔
3926
        uploaded_bytes_1 = uploaded_bytes;
10✔
3927
        uploadable_bytes_1 = uploadable_bytes;
10✔
3928
    };
10✔
3929

3930
    std::uint_fast64_t downloaded_bytes_2 = 0;
2✔
3931
    std::uint_fast64_t downloadable_bytes_2 = 0;
2✔
3932
    std::uint_fast64_t uploaded_bytes_2 = 0;
2✔
3933
    std::uint_fast64_t uploadable_bytes_2 = 0;
2✔
3934

3935
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3936
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t, double,
2✔
3937
                                  double, int64_t) {
8✔
3938
        downloaded_bytes_2 = downloaded_bytes;
8✔
3939
        downloadable_bytes_2 = downloadable_bytes;
8✔
3940
        uploaded_bytes_2 = uploaded_bytes;
8✔
3941
        uploadable_bytes_2 = uploadable_bytes;
8✔
3942
    };
8✔
3943

3944
    {
2✔
3945
        TEST_DIR(dir);
2✔
3946
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
3947
        fixture.start();
2✔
3948

3949
        {
2✔
3950
            Session::Config config;
2✔
3951
            config.progress_handler = progress_handler_1;
2✔
3952
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
3953
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
3954
        }
2✔
3955

3956
        {
2✔
3957
            Session::Config config;
2✔
3958
            config.progress_handler = progress_handler_2;
2✔
3959
            Session session_2 = fixture.make_session(1, 0, db_2, "/test", std::move(config));
2✔
3960
            session_2.wait_for_download_complete_or_client_stopped();
2✔
3961
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
3962
        }
2✔
3963

3964
        {
2✔
3965
            Session::Config config;
2✔
3966
            config.progress_handler = progress_handler_1;
2✔
3967
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
3968
            session_1.wait_for_download_complete_or_client_stopped();
2✔
3969
        }
2✔
3970
    }
2✔
3971

3972
    ReadTransaction read_1(db_1);
2✔
3973
    ReadTransaction read_2(db_2);
2✔
3974

3975
    const Group& group = read_1;
2✔
3976
    CHECK(compare_groups(read_1, read_2));
2✔
3977
    ConstTableRef table = group.get_table("class_table name");
2✔
3978
    CHECK_EQUAL(table->size(), 8);
2✔
3979
    {
2✔
3980
        const Obj obj = *table->begin();
2✔
3981
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
3982
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
3983
    }
2✔
3984
    {
2✔
3985
        const Obj obj = *(table->begin() + 7);
2✔
3986
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
3987
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2✔
3988
    }
2✔
3989

3990
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
3991
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
3992
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3993

3994
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
3995
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
3996
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3997

3998
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
3999
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4000
}
2✔
4001

4002

4003
// This test checks that it is possible to create, upload, download, and merge
4004
// changesets larger than 16 MB. This test uses less memory than
4005
// Sync_MergeLargeBinary.
4006
TEST(Sync_MergeLargeBinaryReducedMemory)
4007
{
2✔
4008
    // Two binaries are inserted in a transaction such that the total size
4009
    // of the changeset exceeds 16MB. A single set_binary operation does not
4010
    // accept a binary larger than 16MB. Only one changeset is larger than
4011
    // 16 MB in this test.
4012
    size_t binary_sizes[] = {
2✔
4013
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e4), static_cast<size_t>(11e4),
2✔
4014
        static_cast<size_t>(6e4), static_cast<size_t>(12e4), static_cast<size_t>(5e4), static_cast<size_t>(13e4),
2✔
4015
    };
2✔
4016

4017
    TEST_CLIENT_DB(db_1);
2✔
4018
    TEST_CLIENT_DB(db_2);
2✔
4019

4020
    {
2✔
4021
        WriteTransaction wt(db_1);
2✔
4022
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4023
        table->add_column(type_Binary, "column name");
2✔
4024
        std::string str_1(binary_sizes[0], 'a');
2✔
4025
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4026
        std::string str_2(binary_sizes[1], 'b');
2✔
4027
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4028
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
4029
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
4030
        wt.commit();
2✔
4031
    }
2✔
4032

4033
    {
2✔
4034
        WriteTransaction wt(db_1);
2✔
4035
        TableRef table = wt.get_table("class_table name");
2✔
4036
        std::string str_1(binary_sizes[2], 'c');
2✔
4037
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4038
        std::string str_2(binary_sizes[3], 'd');
2✔
4039
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4040
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4041
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4042
        wt.commit();
2✔
4043
    }
2✔
4044

4045
    {
2✔
4046
        WriteTransaction wt(db_2);
2✔
4047
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4048
        table->add_column(type_Binary, "column name");
2✔
4049
        std::string str_1(binary_sizes[4], 'e');
2✔
4050
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4051
        std::string str_2(binary_sizes[5], 'f');
2✔
4052
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4053
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4054
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4055
        wt.commit();
2✔
4056
    }
2✔
4057

4058
    {
2✔
4059
        WriteTransaction wt(db_2);
2✔
4060
        TableRef table = wt.get_table("class_table name");
2✔
4061
        std::string str_1(binary_sizes[6], 'g');
2✔
4062
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4063
        std::string str_2(binary_sizes[7], 'h');
2✔
4064
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4065
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4066
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4067
        wt.commit();
2✔
4068
    }
2✔
4069

4070
    uint_fast64_t downloaded_bytes_1 = 0;
2✔
4071
    uint_fast64_t downloadable_bytes_1 = 0;
2✔
4072
    uint_fast64_t uploaded_bytes_1 = 0;
2✔
4073
    uint_fast64_t uploadable_bytes_1 = 0;
2✔
4074

4075
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4076
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4077
                                  uint_fast64_t /* snapshot_version */, double, double, int64_t) {
8✔
4078
        downloaded_bytes_1 = downloaded_bytes;
8✔
4079
        downloadable_bytes_1 = downloadable_bytes;
8✔
4080
        uploaded_bytes_1 = uploaded_bytes;
8✔
4081
        uploadable_bytes_1 = uploadable_bytes;
8✔
4082
    };
8✔
4083

4084
    uint_fast64_t downloaded_bytes_2 = 0;
2✔
4085
    uint_fast64_t downloadable_bytes_2 = 0;
2✔
4086
    uint_fast64_t uploaded_bytes_2 = 0;
2✔
4087
    uint_fast64_t uploadable_bytes_2 = 0;
2✔
4088

4089
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4090
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4091
                                  uint_fast64_t /* snapshot_version */, double, double, int64_t) {
8✔
4092
        downloaded_bytes_2 = downloaded_bytes;
8✔
4093
        downloadable_bytes_2 = downloadable_bytes;
8✔
4094
        uploaded_bytes_2 = uploaded_bytes;
8✔
4095
        uploadable_bytes_2 = uploadable_bytes;
8✔
4096
    };
8✔
4097

4098
    {
2✔
4099
        TEST_DIR(dir);
2✔
4100
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4101
        fixture.start();
2✔
4102

4103
        {
2✔
4104
            Session::Config config;
2✔
4105
            config.progress_handler = progress_handler_1;
2✔
4106
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
4107
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4108
        }
2✔
4109

4110
        {
2✔
4111
            Session::Config config;
2✔
4112
            config.progress_handler = progress_handler_2;
2✔
4113
            Session session_2 = fixture.make_session(1, 0, db_2, "/test", std::move(config));
2✔
4114
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4115
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4116
        }
2✔
4117

4118
        {
2✔
4119
            Session::Config config;
2✔
4120
            config.progress_handler = progress_handler_1;
2✔
4121
            Session session_1 = fixture.make_session(0, 0, db_1, "/test", std::move(config));
2✔
4122
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4123
        }
2✔
4124
    }
2✔
4125

4126
    ReadTransaction read_1(db_1);
2✔
4127
    ReadTransaction read_2(db_2);
2✔
4128

4129
    const Group& group = read_1;
2✔
4130
    CHECK(compare_groups(read_1, read_2));
2✔
4131
    ConstTableRef table = group.get_table("class_table name");
2✔
4132
    CHECK_EQUAL(table->size(), 8);
2✔
4133
    {
2✔
4134
        const Obj obj = *table->begin();
2✔
4135
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4136
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4137
    }
2✔
4138
    {
2✔
4139
        const Obj obj = *(table->begin() + 7);
2✔
4140
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4141
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2✔
4142
    }
2✔
4143

4144
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4145
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4146
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4147

4148
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4149
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4150
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4151

4152
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4153
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4154
}
2✔
4155

4156

4157
// This test checks that it is possible to create, upload, download, and merge
4158
// changesets larger than 16MB.
4159
TEST(Sync_MergeLargeChangesets)
4160
{
2✔
4161
    constexpr int number_of_rows = 200;
2✔
4162

4163
    TEST_CLIENT_DB(db_1);
2✔
4164
    TEST_CLIENT_DB(db_2);
2✔
4165

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

4174
    {
2✔
4175
        WriteTransaction wt(db_2);
2✔
4176
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4177
        table->add_column(type_Binary, "column name");
2✔
4178
        table->add_column(type_Int, "integer column");
2✔
4179
        wt.commit();
2✔
4180
    }
2✔
4181

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

4197
    {
2✔
4198
        WriteTransaction wt(db_2);
2✔
4199
        TableRef table = wt.get_table("class_table name");
2✔
4200
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4201
            table->create_object_with_primary_key(i + number_of_rows);
400✔
4202
        }
400✔
4203
        std::string str(100000, 'b');
2✔
4204
        BinaryData bd(str.data(), str.size());
2✔
4205
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4206
            table->get_object(size_t(row)).set("column name", bd);
400✔
4207
            table->get_object(size_t(row)).set("integer column", 2 * row + 1);
400✔
4208
        }
400✔
4209
        wt.commit();
2✔
4210
    }
2✔
4211

4212
    {
2✔
4213
        TEST_DIR(dir);
2✔
4214
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4215

4216
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4217
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4218

4219
        fixture.start();
2✔
4220

4221
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4222
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4223
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4224
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4225
    }
2✔
4226

4227
    ReadTransaction read_1(db_1);
2✔
4228
    ReadTransaction read_2(db_2);
2✔
4229
    const Group& group = read_1;
2✔
4230
    CHECK(compare_groups(read_1, read_2));
2✔
4231
    ConstTableRef table = group.get_table("class_table name");
2✔
4232
    CHECK_EQUAL(table->size(), 2 * number_of_rows);
2✔
4233
}
2✔
4234

4235

4236
TEST(Sync_MergeMultipleChangesets)
4237
{
2✔
4238
    constexpr int number_of_changesets = 100;
2✔
4239
    constexpr int number_of_instructions = 10;
2✔
4240

4241
    TEST_CLIENT_DB(db_1);
2✔
4242
    TEST_CLIENT_DB(db_2);
2✔
4243

4244
    std::atomic<int> id = 0;
2✔
4245

4246
    {
2✔
4247
        WriteTransaction wt(db_1);
2✔
4248
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4249
        table->add_column(type_Int, "integer column");
2✔
4250
        wt.commit();
2✔
4251
    }
2✔
4252

4253
    {
2✔
4254
        WriteTransaction wt(db_2);
2✔
4255
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4256
        table->add_column(type_Int, "integer column");
2✔
4257
        wt.commit();
2✔
4258
    }
2✔
4259

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

4272
    {
2✔
4273
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4274
            WriteTransaction wt(db_2);
200✔
4275
            TableRef table = wt.get_table("class_table name");
200✔
4276
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4277
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4278
                obj.set("integer column", 2 * j + 1);
2,000✔
4279
            }
2,000✔
4280
            wt.commit();
200✔
4281
        }
200✔
4282
    }
2✔
4283

4284
    {
2✔
4285
        TEST_DIR(dir);
2✔
4286
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4287

4288

4289
        // Start server and upload changes of first client.
4290
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4291
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4292

4293
        fixture.start_server(0);
2✔
4294
        fixture.start_client(0);
2✔
4295
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4296
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4297
        session_1.detach();
2✔
4298
        // Stop first client.
4299
        fixture.stop_client(0);
2✔
4300

4301
        // Start the second client and upload their changes.
4302
        // Wait to integrate changes from the first client.
4303
        fixture.start_client(1);
2✔
4304
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4305
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4306
    }
2✔
4307

4308
    ReadTransaction read_1(db_1);
2✔
4309
    ReadTransaction read_2(db_2);
2✔
4310
    const Group& group1 = read_1;
2✔
4311
    const Group& group2 = read_2;
2✔
4312
    ConstTableRef table1 = group1.get_table("class_table name");
2✔
4313
    ConstTableRef table2 = group2.get_table("class_table name");
2✔
4314
    CHECK_EQUAL(table1->size(), number_of_changesets * number_of_instructions);
2✔
4315
    CHECK_EQUAL(table2->size(), 2 * number_of_changesets * number_of_instructions);
2✔
4316
}
2✔
4317

4318

4319
#endif // REALM_PLATFORM_WIN32
4320

4321

4322
TEST(Sync_PingTimesOut)
4323
{
2✔
4324
    bool did_fail = false;
2✔
4325
    {
2✔
4326
        TEST_DIR(dir);
2✔
4327
        TEST_CLIENT_DB(db);
2✔
4328

4329
        ClientServerFixture::Config config;
2✔
4330
        config.client_ping_period = 0;  // send ping immediately
2✔
4331
        config.client_pong_timeout = 0; // time out immediately
2✔
4332
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4333

4334
        auto error_handler = [&](Status status, bool) {
2✔
4335
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4336
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4337
            did_fail = true;
2✔
4338
            fixture.stop();
2✔
4339
        };
2✔
4340
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4341

4342
        fixture.start();
2✔
4343

4344
        Session session = fixture.make_bound_session(db);
2✔
4345
        session.wait_for_download_complete_or_client_stopped();
2✔
4346
    }
2✔
4347
    CHECK(did_fail);
2✔
4348
}
2✔
4349

4350

4351
TEST(Sync_ReconnectAfterPingTimeout)
4352
{
2✔
4353
    TEST_DIR(dir);
2✔
4354
    TEST_CLIENT_DB(db);
2✔
4355

4356
    ClientServerFixture::Config config;
2✔
4357
    config.client_ping_period = 0;  // send ping immediately
2✔
4358
    config.client_pong_timeout = 0; // time out immediately
2✔
4359

4360
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4361

4362
    BowlOfStonesSemaphore bowl;
2✔
4363
    auto error_handler = [&](Status status, bool) {
2✔
4364
        if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) {
2✔
4365
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4366
            bowl.add_stone();
2✔
4367
        }
2✔
4368
    };
2✔
4369
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4370
    fixture.start();
2✔
4371

4372
    Session session = fixture.make_bound_session(db, "/test");
2✔
4373
    bowl.get_stone();
2✔
4374
}
2✔
4375

4376

4377
TEST(Sync_UrgentPingIsSent)
4378
{
2✔
4379
    bool did_fail = false;
2✔
4380
    {
2✔
4381
        TEST_DIR(dir);
2✔
4382
        TEST_CLIENT_DB(db);
2✔
4383

4384
        ClientServerFixture::Config config;
2✔
4385
        config.client_pong_timeout = 0; // urgent pings time out immediately
2✔
4386

4387
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4388

4389
        auto error_handler = [&](Status status, bool) {
2✔
4390
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4391
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4392
            did_fail = true;
2✔
4393
            fixture.stop();
2✔
4394
        };
2✔
4395
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4396

4397
        fixture.start();
2✔
4398

4399
        Session session = fixture.make_bound_session(db);
2✔
4400
        session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4401
        session.cancel_reconnect_delay();                       // send an urgent ping
2✔
4402
        session.wait_for_download_complete_or_client_stopped();
2✔
4403
    }
2✔
4404
    CHECK(did_fail);
2✔
4405
}
2✔
4406

4407

4408
TEST(Sync_ServerDiscardDeadConnections)
4409
{
2✔
4410
    TEST_DIR(dir);
2✔
4411
    TEST_CLIENT_DB(db);
2✔
4412

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

4416
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4417

4418
    BowlOfStonesSemaphore bowl;
2✔
4419
    auto error_handler = [&](Status status, bool) {
2✔
4420
        CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4421
        bowl.add_stone();
2✔
4422
    };
2✔
4423
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4424
    fixture.start();
2✔
4425

4426
    Session session = fixture.make_bound_session(db);
2✔
4427
    session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4428
    fixture.set_server_connection_reaper_timeout(0);        // all connections will now be considered dead
2✔
4429
    bowl.get_stone();
2✔
4430
}
2✔
4431

4432

4433
TEST(Sync_Quadratic_Merge)
4434
{
2✔
4435
    size_t num_instructions_1 = 100;
2✔
4436
    size_t num_instructions_2 = 200;
2✔
4437
    REALM_ASSERT(num_instructions_1 >= 3 && num_instructions_2 >= 3);
2✔
4438

4439
    TEST_DIR(server_dir);
2✔
4440
    TEST_CLIENT_DB(db_1);
2✔
4441
    TEST_CLIENT_DB(db_2);
2✔
4442

4443
    // The schema and data is created with
4444
    // n_operations instructions. The instructions are:
4445
    // create table
4446
    // add column
4447
    // create object
4448
    // n_operations - 3 add_int instructions.
4449
    auto create_data = [](DBRef db, size_t n_operations) {
4✔
4450
        WriteTransaction wt(db);
4✔
4451
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
4✔
4452
        table->add_column(type_Int, "i");
4✔
4453
        Obj obj = table->create_object_with_primary_key(1);
4✔
4454
        for (size_t i = 0; i < n_operations - 3; ++i)
592✔
4455
            obj.add_int("i", 1);
588✔
4456
        wt.commit();
4✔
4457
    };
4✔
4458

4459
    create_data(db_1, num_instructions_1);
2✔
4460
    create_data(db_2, num_instructions_2);
2✔
4461

4462
    int num_clients = 2;
2✔
4463
    int num_servers = 1;
2✔
4464
    MultiClientServerFixture fixture{num_clients, num_servers, server_dir, test_context};
2✔
4465
    fixture.start();
2✔
4466

4467
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4468
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4469

4470
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4471
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
4472

4473
    session_1.wait_for_download_complete_or_client_stopped();
2✔
4474
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4475
}
2✔
4476

4477

4478
TEST(Sync_BatchedUploadMessages)
4479
{
2✔
4480
    TEST_DIR(server_dir);
2✔
4481
    TEST_CLIENT_DB(db);
2✔
4482

4483
    ClientServerFixture fixture(server_dir, test_context);
2✔
4484
    fixture.start();
2✔
4485

4486
    {
2✔
4487
        WriteTransaction wt{db};
2✔
4488
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4489
        tr->add_column(type_Int, "integer column");
2✔
4490
        wt.commit();
2✔
4491
    }
2✔
4492

4493
    // Create a lot of changesets. We will attempt to check that
4494
    // they are uploaded in a few upload messages.
4495
    for (int i = 0; i < 400; ++i) {
802✔
4496
        WriteTransaction wt{db};
800✔
4497
        TableRef tr = wt.get_table("class_foo");
800✔
4498
        tr->create_object_with_primary_key(i).set("integer column", i);
800✔
4499
        wt.commit();
800✔
4500
    }
800✔
4501

4502
    Session::Config config;
2✔
4503
    auto pf = util::make_promise_future();
2✔
4504
    config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4505
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t, double,
2✔
4506
                                  double, int64_t) {
4✔
4507
        CHECK_GREATER(uploadable_bytes, 1000);
4✔
4508

4509
        // This is the important check. If the changesets were not batched,
4510
        // there would be callbacks with partial uploaded_bytes.
4511
        // With batching, all uploadable_bytes are uploaded in the same message.
4512
        CHECK(uploaded_bytes == 0 || uploaded_bytes == uploadable_bytes);
4✔
4513
        CHECK_EQUAL(0, downloaded_bytes);
4✔
4514
        CHECK_EQUAL(0, downloadable_bytes);
4✔
4515
        if (uploaded_bytes == uploadable_bytes) {
4✔
4516
            pf.promise.emplace_value();
2✔
4517
        }
2✔
4518
    };
4✔
4519

4520
    Session session = fixture.make_session(db, "/test", std::move(config));
2✔
4521
    session.wait_for_upload_complete_or_client_stopped();
2✔
4522
    pf.future.get();
2✔
4523
}
2✔
4524

4525

4526
TEST(Sync_UploadLogCompactionEnabled)
4527
{
2✔
4528
    TEST_DIR(server_dir);
2✔
4529
    TEST_CLIENT_DB(db_1);
2✔
4530
    TEST_CLIENT_DB(db_2);
2✔
4531

4532
    ClientServerFixture::Config config;
2✔
4533
    config.disable_upload_compaction = false;
2✔
4534
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
4535
    fixture.start();
2✔
4536

4537
    // Create a changeset with lots of overwrites of the
4538
    // same fields.
4539
    {
2✔
4540
        WriteTransaction wt{db_1};
2✔
4541
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4542
        tr->add_column(type_Int, "integer column");
2✔
4543
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4544
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4545
        for (int i = 0; i < 10000; ++i) {
20,002✔
4546
            obj0.set("integer column", i);
20,000✔
4547
            obj1.set("integer column", 2 * i);
20,000✔
4548
        }
20,000✔
4549
        wt.commit();
2✔
4550
    }
2✔
4551

4552
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
4553
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4554

4555
    Session::Config session_config;
2✔
4556
    session_config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4557
                                          uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4558
                                          uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
4559
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
4560
        CHECK_EQUAL(0, uploaded_bytes);
2✔
4561
        CHECK_EQUAL(0, uploadable_bytes);
2✔
4562
        static_cast<void>(snapshot_version);
2✔
4563
        CHECK_NOT_EQUAL(downloadable_bytes, 0);
2✔
4564
    };
2✔
4565

4566
    Session session_2 = fixture.make_session(db_2, "/test", std::move(session_config));
2✔
4567
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4568

4569
    {
2✔
4570
        ReadTransaction rt_1(db_1);
2✔
4571
        ReadTransaction rt_2(db_2);
2✔
4572
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4573
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4574
        CHECK_EQUAL(2, table->size());
2✔
4575
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4576
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4577
    }
2✔
4578
}
2✔
4579

4580

4581
TEST(Sync_UploadLogCompactionDisabled)
4582
{
2✔
4583
    TEST_DIR(server_dir);
2✔
4584
    TEST_CLIENT_DB(db_1);
2✔
4585
    TEST_CLIENT_DB(db_2);
2✔
4586

4587
    ClientServerFixture::Config config;
2✔
4588
    config.disable_upload_compaction = true;
2✔
4589
    config.disable_history_compaction = true;
2✔
4590
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4591
    fixture.start();
2✔
4592

4593
    // Create a changeset with lots of overwrites of the
4594
    // same fields.
4595
    {
2✔
4596
        WriteTransaction wt{db_1};
2✔
4597
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4598
        auto col_int = tr->add_column(type_Int, "integer column");
2✔
4599
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4600
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4601
        for (int i = 0; i < 10000; ++i) {
20,002✔
4602
            obj0.set(col_int, i);
20,000✔
4603
            obj1.set(col_int, 2 * i);
20,000✔
4604
        }
20,000✔
4605
        wt.commit();
2✔
4606
    }
2✔
4607

4608
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
4609
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4610

4611
    Session::Config session_config;
2✔
4612
    session_config.progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4613
                                          uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4614
                                          uint_fast64_t snapshot_version, double, double, int64_t) {
2✔
4615
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
4616
        CHECK_EQUAL(0, uploaded_bytes);
2✔
4617
        CHECK_EQUAL(0, uploadable_bytes);
2✔
4618
        static_cast<void>(snapshot_version);
2✔
4619
        CHECK_NOT_EQUAL(0, downloadable_bytes);
2✔
4620
    };
2✔
4621

4622
    Session session_2 = fixture.make_session(db_2, "/test", std::move(session_config));
2✔
4623
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4624

4625
    {
2✔
4626
        ReadTransaction rt_1(db_1);
2✔
4627
        ReadTransaction rt_2(db_2);
2✔
4628
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4629
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4630
        CHECK_EQUAL(2, table->size());
2✔
4631
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4632
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4633
    }
2✔
4634
}
2✔
4635

4636

4637
TEST(Sync_ReadOnlyClientSideHistoryTrim)
4638
{
2✔
4639
    TEST_DIR(dir);
2✔
4640
    TEST_CLIENT_DB(db_1);
2✔
4641
    TEST_CLIENT_DB(db_2);
2✔
4642

4643
    ClientServerFixture fixture{dir, test_context};
2✔
4644
    fixture.start();
2✔
4645

4646
    ColKey col_ndx_blob_data;
2✔
4647
    {
2✔
4648
        WriteTransaction wt{db_1};
2✔
4649
        TableRef blobs = wt.get_group().add_table_with_primary_key("class_Blob", type_Int, "id");
2✔
4650
        col_ndx_blob_data = blobs->add_column(type_Binary, "data");
2✔
4651
        blobs->create_object_with_primary_key(1);
2✔
4652
        wt.commit();
2✔
4653
    }
2✔
4654

4655
    Session session_1 = fixture.make_bound_session(db_1, "/foo");
2✔
4656
    Session session_2 = fixture.make_bound_session(db_2, "/foo");
2✔
4657

4658
    std::string blob(0x4000, '\0');
2✔
4659
    for (long i = 0; i < 1024; ++i) {
2,050✔
4660
        {
2,048✔
4661
            WriteTransaction wt{db_1};
2,048✔
4662
            TableRef blobs = wt.get_table("class_Blob");
2,048✔
4663
            blobs->begin()->set(col_ndx_blob_data, BinaryData{blob});
2,048✔
4664
            wt.commit();
2,048✔
4665
        }
2,048✔
4666
        session_1.wait_for_upload_complete_or_client_stopped();
2,048✔
4667
        session_2.wait_for_download_complete_or_client_stopped();
2,048✔
4668
    }
2,048✔
4669

4670
    // Check that the file size is less than 4 MiB. If it is, then the history
4671
    // must have been trimmed, as the combined size of all the blobs is at least
4672
    // 16 MiB.
4673
    CHECK_LESS(util::File{db_1_path}.get_size(), 0x400000);
2✔
4674
}
2✔
4675

4676
// This test creates two objects in a target table and a link list
4677
// in a source table. The first target object is inserted in the link list,
4678
// and later the link is set to the second target object.
4679
// Both the target objects are deleted afterwards. The tests verifies that
4680
// sync works with log compaction turned on.
4681
TEST(Sync_ContainerInsertAndSetLogCompaction)
4682
{
2✔
4683
    TEST_DIR(dir);
2✔
4684
    TEST_CLIENT_DB(db_1);
2✔
4685
    TEST_CLIENT_DB(db_2);
2✔
4686
    ClientServerFixture fixture(dir, test_context);
2✔
4687
    fixture.start();
2✔
4688

4689
    {
2✔
4690
        WriteTransaction wt{db_1};
2✔
4691

4692
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
4693
        ColKey col_ndx = table_target->add_column(type_Int, "value");
2✔
4694
        auto k0 = table_target->create_object_with_primary_key(1).set(col_ndx, 123).get_key();
2✔
4695
        auto k1 = table_target->create_object_with_primary_key(2).set(col_ndx, 456).get_key();
2✔
4696

4697
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
4698
        col_ndx = table_source->add_column_list(*table_target, "target_link");
2✔
4699
        Obj obj = table_source->create_object_with_primary_key(1);
2✔
4700
        LnkLst ll = obj.get_linklist(col_ndx);
2✔
4701
        ll.insert(0, k0);
2✔
4702
        ll.set(0, k1);
2✔
4703

4704
        table_target->remove_object(k1);
2✔
4705
        table_target->remove_object(k0);
2✔
4706

4707
        wt.commit();
2✔
4708
    }
2✔
4709

4710
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4711
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4712

4713
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4714
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4715

4716
    {
2✔
4717
        ReadTransaction rt_1(db_1);
2✔
4718
        ReadTransaction rt_2(db_2);
2✔
4719
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4720
    }
2✔
4721
}
2✔
4722

4723

4724
TEST(Sync_MultipleContainerColumns)
4725
{
2✔
4726
    TEST_DIR(dir);
2✔
4727
    TEST_CLIENT_DB(db_1);
2✔
4728
    TEST_CLIENT_DB(db_2);
2✔
4729
    ClientServerFixture fixture(dir, test_context);
2✔
4730
    fixture.start();
2✔
4731

4732
    {
2✔
4733
        WriteTransaction wt{db_1};
2✔
4734

4735
        TableRef table = wt.get_group().add_table_with_primary_key("class_Table", type_Int, "id");
2✔
4736
        table->add_column_list(type_String, "array1");
2✔
4737
        table->add_column_list(type_String, "array2");
2✔
4738

4739
        Obj row = table->create_object_with_primary_key(1);
2✔
4740
        {
2✔
4741
            Lst<StringData> array1 = row.get_list<StringData>("array1");
2✔
4742
            array1.clear();
2✔
4743
            array1.add("Hello");
2✔
4744
        }
2✔
4745
        {
2✔
4746
            Lst<StringData> array2 = row.get_list<StringData>("array2");
2✔
4747
            array2.clear();
2✔
4748
            array2.add("World");
2✔
4749
        }
2✔
4750

4751
        wt.commit();
2✔
4752
    }
2✔
4753

4754
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4755
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4756

4757
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4758
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4759

4760
    {
2✔
4761
        ReadTransaction rt_1(db_1);
2✔
4762
        ReadTransaction rt_2(db_2);
2✔
4763
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4764

4765
        ConstTableRef table = rt_1.get_table("class_Table");
2✔
4766
        const Obj row = *table->begin();
2✔
4767
        auto array1 = row.get_list<StringData>("array1");
2✔
4768
        auto array2 = row.get_list<StringData>("array2");
2✔
4769
        CHECK_EQUAL(array1.size(), 1);
2✔
4770
        CHECK_EQUAL(array2.size(), 1);
2✔
4771
        CHECK_EQUAL(array1.get(0), "Hello");
2✔
4772
        CHECK_EQUAL(array2.get(0), "World");
2✔
4773
    }
2✔
4774
}
2✔
4775

4776

4777
TEST(Sync_ConnectionStateChange)
4778
{
2✔
4779
    TEST_DIR(dir);
2✔
4780
    TEST_CLIENT_DB(db_1);
2✔
4781
    TEST_CLIENT_DB(db_2);
2✔
4782

4783
    std::vector<ConnectionState> states_1, states_2;
2✔
4784
    {
2✔
4785
        ClientServerFixture fixture(dir, test_context);
2✔
4786
        fixture.start();
2✔
4787

4788
        BowlOfStonesSemaphore bowl_1, bowl_2;
2✔
4789
        auto listener_1 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4790
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4791
            states_1.push_back(state);
6✔
4792
            if (state == ConnectionState::disconnected)
6✔
4793
                bowl_1.add_stone();
2✔
4794
        };
6✔
4795
        auto listener_2 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4796
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4797
            states_2.push_back(state);
6✔
4798
            if (state == ConnectionState::disconnected)
6✔
4799
                bowl_2.add_stone();
2✔
4800
        };
6✔
4801

4802
        Session::Config config_1;
2✔
4803
        config_1.connection_state_change_listener = listener_1;
2✔
4804
        Session session_1 = fixture.make_session(db_1, "/test", std::move(config_1));
2✔
4805
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4806

4807
        Session::Config config_2;
2✔
4808
        config_2.connection_state_change_listener = listener_2;
2✔
4809
        Session session_2 = fixture.make_session(db_2, "/test", std::move(config_2));
2✔
4810
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4811

4812
        fixture.close_server_side_connections();
2✔
4813
        bowl_1.get_stone();
2✔
4814
        bowl_2.get_stone();
2✔
4815
    }
2✔
4816
    std::vector<ConnectionState> reference{ConnectionState::connecting, ConnectionState::connected,
2✔
4817
                                           ConnectionState::disconnected};
2✔
4818
    CHECK(states_1 == reference);
2✔
4819
    CHECK(states_2 == reference);
2✔
4820
}
2✔
4821

4822

4823
TEST(Sync_VerifyServerHistoryAfterLargeUpload)
4824
{
2✔
4825
    TEST_DIR(server_dir);
2✔
4826
    TEST_CLIENT_DB(db);
2✔
4827

4828
    ClientServerFixture fixture{server_dir, test_context};
2✔
4829
    fixture.start();
2✔
4830

4831
    {
2✔
4832
        auto wt = db->start_write();
2✔
4833
        auto table = wt->add_table_with_primary_key("class_table", type_Int, "id");
2✔
4834
        ColKey col = table->add_column(type_Binary, "data");
2✔
4835

4836
        // Create enough data that our changeset cannot be stored contiguously
4837
        // by BinaryColumn (> 16MB).
4838
        std::size_t data_size = 8 * 1024 * 1024;
2✔
4839
        std::string data(data_size, '\0');
2✔
4840
        for (int i = 0; i < 8; ++i) {
18✔
4841
            table->create_object_with_primary_key(i).set(col, BinaryData{data.data(), data.size()});
16✔
4842
        }
16✔
4843

4844
        wt->commit();
2✔
4845

4846
        Session session = fixture.make_session(db, "/test");
2✔
4847
        session.wait_for_upload_complete_or_client_stopped();
2✔
4848
    }
2✔
4849

4850
    {
2✔
4851
        std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
4852
        TestServerHistoryContext context;
2✔
4853
        _impl::ServerHistory history{context};
2✔
4854
        DBRef db = DB::create(history, server_path);
2✔
4855
        {
2✔
4856
            ReadTransaction rt{db};
2✔
4857
            rt.get_group().verify();
2✔
4858
        }
2✔
4859
    }
2✔
4860
}
2✔
4861

4862

4863
TEST(Sync_ServerSideModify_Randomize)
4864
{
2✔
4865
    int num_server_side_transacts = 1200;
2✔
4866
    int num_client_side_transacts = 1200;
2✔
4867

4868
    TEST_DIR(server_dir);
2✔
4869
    TEST_CLIENT_DB(db_2);
2✔
4870

4871
    ClientServerFixture::Config config;
2✔
4872
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4873
    fixture.start();
2✔
4874

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

4877
    std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
4878
    TestServerHistoryContext context;
2✔
4879
    _impl::ServerHistory history_1{context};
2✔
4880
    DBRef db_1 = DB::create(history_1, server_path);
2✔
4881

4882
    auto server_side_program = [num_server_side_transacts, &db_1, &fixture, &session] {
2✔
4883
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
4884
        for (int i = 0; i < num_server_side_transacts; ++i) {
2,402✔
4885
            WriteTransaction wt{db_1};
2,400✔
4886
            TableRef table = wt.get_table("class_foo");
2,400✔
4887
            if (!table) {
2,400✔
4888
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4889
                table->add_column(type_Int, "i");
2✔
4890
            }
2✔
4891
            if (i % 2 == 0)
2,400✔
4892
                table->create_object_with_primary_key(0 - i);
1,200✔
4893
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
4894
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
4895
            wt.commit();
2,400✔
4896
            fixture.inform_server_about_external_change("/test");
2,400✔
4897
            session.wait_for_download_complete_or_client_stopped();
2,400✔
4898
        }
2,400✔
4899
    };
2✔
4900

4901
    auto client_side_program = [num_client_side_transacts, &db_2, &session] {
2✔
4902
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
4903
        for (int i = 0; i < num_client_side_transacts; ++i) {
2,402✔
4904
            WriteTransaction wt{db_2};
2,400✔
4905
            TableRef table = wt.get_table("class_foo");
2,400✔
4906
            if (!table) {
2,400✔
4907
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4908
                table->add_column(type_Int, "i");
2✔
4909
            }
2✔
4910
            if (i % 2 == 0)
2,400✔
4911
                table->create_object_with_primary_key(i);
1,200✔
4912
            ;
2,400✔
4913
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
4914
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
4915
            wt.commit();
2,400✔
4916
            if (i % 16 == 0)
2,400✔
4917
                session.wait_for_upload_complete_or_client_stopped();
150✔
4918
        }
2,400✔
4919
    };
2✔
4920

4921
    ThreadWrapper server_program_thread;
2✔
4922
    server_program_thread.start(std::move(server_side_program));
2✔
4923
    client_side_program();
2✔
4924
    CHECK(!server_program_thread.join());
2✔
4925

4926
    session.wait_for_upload_complete_or_client_stopped();
2✔
4927
    session.wait_for_download_complete_or_client_stopped();
2✔
4928

4929
    ReadTransaction rt_1{db_1};
2✔
4930
    ReadTransaction rt_2{db_2};
2✔
4931
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4932
}
2✔
4933

4934

4935
// This test connects a sync client to the realm cloud service using a SSL
4936
// connection. The purpose of the test is to check that the server's SSL
4937
// certificate is accepted by the client.  The client will connect with an
4938
// invalid token and get an error code back.  The check is that the error is
4939
// not rejected certificate.  The test should be disabled under normal
4940
// circumstances since it requires network access and cloud availability. The
4941
// test might be enabled during testing of SSL functionality.
4942
TEST_IF(Sync_SSL_Certificates, false)
4943
{
×
4944
    TEST_CLIENT_DB(db);
×
4945

4946
    const char* server_address[] = {
×
4947
        "morten-krogh.us1.cloud.realm.io",
×
4948
        "fantastic-cotton-shoes.us1.cloud.realm.io",
×
4949
        "www.realm.io",
×
4950
        "www.yahoo.com",
×
4951
        "www.nytimes.com",
×
4952
        "www.ibm.com",
×
4953
        "www.ssllabs.com",
×
4954
    };
×
4955

4956
    size_t num_servers = sizeof(server_address) / sizeof(server_address[0]);
×
4957

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

4960
    for (size_t i = 0; i < num_servers; ++i) {
×
4961
        Client::Config client_config;
×
4962
        client_config.logger = client_logger;
×
4963
        client_config.reconnect_mode = ReconnectMode::testing;
×
4964
        Client client(client_config);
×
4965

4966
        Session::Config session_config;
×
4967
        session_config.server_address = server_address[i];
×
4968
        session_config.server_port = 443;
×
4969
        session_config.realm_identifier = "/anything";
×
4970
        session_config.protocol_envelope = ProtocolEnvelope::realms;
×
4971

4972
        // Invalid token for the cloud.
4973
        session_config.signed_user_token = g_signed_test_user_token;
×
4974

4975
        session_config.connection_state_change_listener = [&](ConnectionState state,
×
4976
                                                              const util::Optional<ErrorInfo>& error_info) {
×
4977
            if (state == ConnectionState::disconnected) {
×
4978
                CHECK(error_info);
×
4979
                client_logger->debug("State change: disconnected, error_code = %1, is_fatal = %2", error_info->status,
×
4980
                                     error_info->is_fatal);
×
4981
                // We expect to get through the SSL handshake but will hit an error due to the wrong token.
4982
                CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed);
×
4983
                client.shutdown();
×
4984
            }
×
4985
        };
×
4986

4987
        Session session{client, db, nullptr, nullptr, std::move(session_config)};
×
4988
        session.wait_for_download_complete_or_client_stopped();
×
4989
    }
×
4990
}
×
4991

4992

4993
// Testing the custom authorization header name.  The sync protocol does not
4994
// currently use the HTTP Authorization header, so the test is to watch the
4995
// logs and see that the client use the right header name. Proxies and the sync
4996
// server HTTP api use the Authorization header.
4997
TEST(Sync_AuthorizationHeaderName)
4998
{
2✔
4999
    TEST_DIR(dir);
2✔
5000
    TEST_CLIENT_DB(db);
2✔
5001

5002
    const char* authorization_header_name = "X-Alternative-Name";
2✔
5003
    ClientServerFixture::Config config;
2✔
5004
    config.authorization_header_name = authorization_header_name;
2✔
5005
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5006
    fixture.start();
2✔
5007

5008
    Session::Config session_config;
2✔
5009
    session_config.authorization_header_name = authorization_header_name;
2✔
5010

5011
    std::map<std::string, std::string> custom_http_headers;
2✔
5012
    custom_http_headers["Header-Name-1"] = "Header-Value-1";
2✔
5013
    custom_http_headers["Header-Name-2"] = "Header-Value-2";
2✔
5014
    session_config.custom_http_headers = std::move(custom_http_headers);
2✔
5015
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5016

5017
    session.wait_for_download_complete_or_client_stopped();
2✔
5018
}
2✔
5019

5020

5021
TEST(Sync_BadChangeset)
5022
{
2✔
5023
    TEST_DIR(dir);
2✔
5024
    TEST_CLIENT_DB(db);
2✔
5025

5026
    bool did_fail = false;
2✔
5027
    {
2✔
5028
        ClientServerFixture::Config config;
2✔
5029
        config.disable_upload_compaction = true;
2✔
5030
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5031
        fixture.start();
2✔
5032

5033
        {
2✔
5034
            Session session = fixture.make_bound_session(db);
2✔
5035
            session.wait_for_download_complete_or_client_stopped();
2✔
5036
        }
2✔
5037

5038
        {
2✔
5039
            WriteTransaction wt(db);
2✔
5040
            TableRef table = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5041
            table->add_column(type_Int, "i");
2✔
5042
            table->create_object_with_primary_key(5).set_all(123);
2✔
5043
            const ChangesetEncoder::Buffer& buffer = get_replication(db).get_instruction_encoder().buffer();
2✔
5044
            char bad_instruction = 0x3e;
2✔
5045
            const_cast<ChangesetEncoder::Buffer&>(buffer).append(&bad_instruction, 1);
2✔
5046
            wt.commit();
2✔
5047
        }
2✔
5048

5049
        Session::Config session_config;
2✔
5050
        session_config.connection_state_change_listener = [&](ConnectionState state,
2✔
5051
                                                              const util::Optional<ErrorInfo>& error_info) {
6✔
5052
            if (state != ConnectionState::disconnected)
6✔
5053
                return;
4✔
5054
            REALM_ASSERT(error_info);
2✔
5055
            CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
2✔
5056
            CHECK(error_info->is_fatal);
2✔
5057
            did_fail = true;
2✔
5058
            fixture.stop();
2✔
5059
        };
2✔
5060
        Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5061
        session.wait_for_upload_complete_or_client_stopped();
2✔
5062
        session.wait_for_download_complete_or_client_stopped();
2✔
5063
    }
2✔
5064
    CHECK(did_fail);
2✔
5065
}
2✔
5066

5067

5068
TEST(Sync_GoodChangeset_AccentCharacterInFieldName)
5069
{
2✔
5070
    TEST_DIR(dir);
2✔
5071
    TEST_CLIENT_DB(db);
2✔
5072

5073
    bool did_fail = false;
2✔
5074
    {
2✔
5075
        ClientServerFixture::Config config;
2✔
5076
        config.disable_upload_compaction = true;
2✔
5077
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5078
        fixture.start();
2✔
5079

5080
        {
2✔
5081
            Session session = fixture.make_bound_session(db);
2✔
5082
        }
2✔
5083

5084
        {
2✔
5085
            WriteTransaction wt(db);
2✔
5086
            TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
5087
            table->add_column(type_Int, "prógram");
2✔
5088
            table->add_column(type_Int, "program");
2✔
5089
            auto obj = table->create_object_with_primary_key(1);
2✔
5090
            obj.add_int("program", 42);
2✔
5091
            wt.commit();
2✔
5092
        }
2✔
5093

5094
        Session::Config session_config;
2✔
5095
        session_config.connection_state_change_listener = [&](ConnectionState state,
2✔
5096
                                                              const util::Optional<ErrorInfo>) {
4✔
5097
            if (state != ConnectionState::disconnected)
4✔
5098
                return;
4✔
5099
            did_fail = true;
×
5100
            fixture.stop();
×
5101
        };
×
5102
        Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5103
        session.wait_for_upload_complete_or_client_stopped();
2✔
5104
    }
2✔
5105
    CHECK_NOT(did_fail);
2✔
5106
}
2✔
5107

5108

5109
namespace issue2104 {
5110

5111
class ServerHistoryContext : public _impl::ServerHistory::Context {
5112
public:
5113
    ServerHistoryContext() {}
×
5114

5115
    std::mt19937_64& server_history_get_random() noexcept override
5116
    {
×
5117
        return m_random;
×
5118
    }
×
5119

5120
private:
5121
    std::mt19937_64 m_random;
5122
};
5123

5124
} // namespace issue2104
5125

5126
// This test reproduces a slow merge seen in issue 2104.
5127
// The test uses a user supplied Realm and a changeset
5128
// from a client.
5129
// The test uses a user supplied Realm that is very large
5130
// and not kept in the repo. The realm has checksum 3693867489.
5131
//
5132
// This test might be modified to avoid having a large Realm
5133
// (96 MB uncompressed) in the repo.
5134
TEST_IF(Sync_Issue2104, false)
5135
{
×
5136
    TEST_DIR(dir);
×
5137

5138
    // Save a snapshot of the server Realm file.
5139
    std::string realm_path = "issue_2104_server.realm";
×
5140
    std::string realm_path_copy = util::File::resolve("issue_2104.realm", dir);
×
5141
    util::File::copy(realm_path, realm_path_copy);
×
5142

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

5288
    std::vector<char> changeset_vec;
×
5289
    {
×
5290
        std::istringstream in{changeset_hex};
×
5291
        int n;
×
5292
        in >> std::hex >> n;
×
5293
        while (in) {
×
5294
            REALM_ASSERT(n >= 0 && n <= 255);
×
5295
            changeset_vec.push_back(n);
×
5296
            in >> std::hex >> n;
×
5297
        }
×
5298
    }
×
5299

5300
    BinaryData changeset_bin{changeset_vec.data(), changeset_vec.size()};
×
5301

5302
    file_ident_type client_file_ident = 51;
×
5303
    timestamp_type origin_timestamp = 103573722140;
×
5304
    file_ident_type origin_file_ident = 0;
×
5305
    version_type client_version = 2;
×
5306
    version_type last_integrated_server_version = 0;
×
5307
    UploadCursor upload_cursor{client_version, last_integrated_server_version};
×
5308

5309
    _impl::ServerHistory::IntegratableChangeset integratable_changeset{
×
5310
        client_file_ident, origin_timestamp, origin_file_ident, upload_cursor, changeset_bin};
×
5311

5312
    _impl::ServerHistory::IntegratableChangesets integratable_changesets;
×
5313
    integratable_changesets[client_file_ident].changesets.push_back(integratable_changeset);
×
5314

5315
    issue2104::ServerHistoryContext history_context;
×
5316
    _impl::ServerHistory history{history_context};
×
5317
    DBRef db = DB::create(history, realm_path_copy);
×
5318

5319
    VersionInfo version_info;
×
5320
    bool backup_whole_realm;
×
5321
    _impl::ServerHistory::IntegrationResult result;
×
5322
    history.integrate_client_changesets(integratable_changesets, version_info, backup_whole_realm, result,
×
5323
                                        *test_context.logger);
×
5324
}
×
5325

5326

5327
TEST(Sync_RunServerWithoutPublicKey)
5328
{
2✔
5329
    TEST_CLIENT_DB(db);
2✔
5330
    TEST_DIR(server_dir);
2✔
5331
    ClientServerFixture::Config config;
2✔
5332
    config.server_public_key_path = {};
2✔
5333
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5334
    fixture.start();
2✔
5335

5336
    // Server must accept an unsigned token when a public key is not passed to
5337
    // it
5338
    {
2✔
5339
        Session session = fixture.make_bound_session(db, "/test", g_unsigned_test_user_token);
2✔
5340
        session.wait_for_download_complete_or_client_stopped();
2✔
5341
    }
2✔
5342

5343
    // Server must also accept a signed token when a public key is not passed to
5344
    // it
5345
    {
2✔
5346
        Session session = fixture.make_bound_session(db, "/test");
2✔
5347
        session.wait_for_download_complete_or_client_stopped();
2✔
5348
    }
2✔
5349
}
2✔
5350

5351

5352
TEST(Sync_ServerSideEncryption)
5353
{
2✔
5354
    TEST_CLIENT_DB(db);
2✔
5355
    {
2✔
5356
        WriteTransaction wt(db);
2✔
5357
        wt.get_group().add_table_with_primary_key("class_Test", type_Int, "id");
2✔
5358
        wt.commit();
2✔
5359
    }
2✔
5360

5361
    TEST_DIR(server_dir);
2✔
5362
    bool always_encrypt = true;
2✔
5363
    std::string server_path;
2✔
5364
    {
2✔
5365
        ClientServerFixture::Config config;
2✔
5366
        if (auto key = crypt_key(always_encrypt)) {
2✔
5367
            config.server_encryption_key.emplace();
2✔
5368
            memcpy(config.server_encryption_key->data(), key, config.server_encryption_key->size());
2✔
5369
        }
2✔
5370
        ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5371
        fixture.start();
2✔
5372

5373
        Session session = fixture.make_bound_session(db, "/test");
2✔
5374
        session.wait_for_upload_complete_or_client_stopped();
2✔
5375

5376
        server_path = fixture.map_virtual_to_real_path("/test");
2✔
5377
    }
2✔
5378

5379
    const char* encryption_key = crypt_key(always_encrypt);
2✔
5380
    Group group{server_path, encryption_key};
2✔
5381
    CHECK(group.has_table("class_Test"));
2✔
5382
}
2✔
5383

5384
TEST(Sync_LogCompaction_EraseObject_LinkList)
5385
{
2✔
5386
    TEST_DIR(dir);
2✔
5387
    TEST_CLIENT_DB(db_1);
2✔
5388
    TEST_CLIENT_DB(db_2);
2✔
5389
    ClientServerFixture::Config config;
2✔
5390

5391
    // Log comapction is true by default, but we emphasize it.
5392
    config.disable_upload_compaction = false;
2✔
5393
    config.disable_download_compaction = false;
2✔
5394

5395
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5396
    fixture.start();
2✔
5397

5398
    {
2✔
5399
        WriteTransaction wt{db_1};
2✔
5400

5401
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
5402
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
5403
        auto col_key = table_source->add_column_list(*table_target, "target_link");
2✔
5404

5405
        auto k0 = table_target->create_object_with_primary_key(1).get_key();
2✔
5406
        auto k1 = table_target->create_object_with_primary_key(2).get_key();
2✔
5407

5408
        auto ll = table_source->create_object_with_primary_key(0).get_linklist_ptr(col_key);
2✔
5409
        ll->add(k0);
2✔
5410
        ll->add(k1);
2✔
5411
        CHECK_EQUAL(ll->size(), 2);
2✔
5412
        wt.commit();
2✔
5413
    }
2✔
5414

5415
    {
2✔
5416
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5417
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5418

5419
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5420
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5421
    }
2✔
5422

5423
    {
2✔
5424
        WriteTransaction wt{db_1};
2✔
5425

5426
        TableRef table_source = wt.get_table("class_source");
2✔
5427
        TableRef table_target = wt.get_table("class_target");
2✔
5428

5429
        CHECK_EQUAL(table_source->size(), 1);
2✔
5430
        CHECK_EQUAL(table_target->size(), 2);
2✔
5431

5432
        table_target->get_object(1).remove();
2✔
5433
        table_target->get_object(0).remove();
2✔
5434

5435
        table_source->get_object(0).remove();
2✔
5436
        wt.commit();
2✔
5437
    }
2✔
5438

5439
    {
2✔
5440
        WriteTransaction wt{db_2};
2✔
5441

5442
        TableRef table_source = wt.get_table("class_source");
2✔
5443
        TableRef table_target = wt.get_table("class_target");
2✔
5444
        auto col_key = table_source->get_column_key("target_link");
2✔
5445

5446
        CHECK_EQUAL(table_source->size(), 1);
2✔
5447
        CHECK_EQUAL(table_target->size(), 2);
2✔
5448

5449
        auto k0 = table_target->begin()->get_key();
2✔
5450

5451
        auto ll = table_source->get_object(0).get_linklist_ptr(col_key);
2✔
5452
        ll->add(k0);
2✔
5453
        wt.commit();
2✔
5454
    }
2✔
5455

5456
    {
2✔
5457
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5458
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5459
    }
2✔
5460

5461
    {
2✔
5462
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5463
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
5464
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5465
    }
2✔
5466

5467
    {
2✔
5468
        ReadTransaction rt{db_2};
2✔
5469

5470
        ConstTableRef table_source = rt.get_group().get_table("class_source");
2✔
5471
        ConstTableRef table_target = rt.get_group().get_table("class_target");
2✔
5472

5473
        CHECK_EQUAL(table_source->size(), 0);
2✔
5474
        CHECK_EQUAL(table_target->size(), 0);
2✔
5475
    }
2✔
5476
}
2✔
5477

5478

5479
// This test could trigger the assertion that the row_for_object_id cache is
5480
// valid before the cache was properly invalidated in the case of a short
5481
// circuited sync replicator.
5482
TEST(Sync_CreateObjects_EraseObjects)
5483
{
2✔
5484
    TEST_DIR(dir);
2✔
5485
    TEST_CLIENT_DB(db_1);
2✔
5486
    TEST_CLIENT_DB(db_2);
2✔
5487
    ClientServerFixture fixture(dir, test_context);
2✔
5488
    fixture.start();
2✔
5489

5490
    Session session_1 = fixture.make_bound_session(db_1);
2✔
5491
    Session session_2 = fixture.make_bound_session(db_2);
2✔
5492

5493
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
5494
        TableRef table = wt.get_group().add_table_with_primary_key("class_persons", type_Int, "id");
2✔
5495
        table->create_object_with_primary_key(1);
2✔
5496
        table->create_object_with_primary_key(2);
2✔
5497
    });
2✔
5498
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5499
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5500

5501
    write_transaction(db_1, [&](WriteTransaction& wt) {
2✔
5502
        TableRef table = wt.get_table("class_persons");
2✔
5503
        CHECK_EQUAL(table->size(), 2);
2✔
5504
        table->get_object(0).remove();
2✔
5505
        table->get_object(0).remove();
2✔
5506
    });
2✔
5507
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5508
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5509
}
2✔
5510

5511

5512
TEST(Sync_CreateDeleteCreateTableWithPrimaryKey)
5513
{
2✔
5514
    TEST_DIR(dir);
2✔
5515
    TEST_CLIENT_DB(db);
2✔
5516
    ClientServerFixture fixture(dir, test_context);
2✔
5517
    fixture.start();
2✔
5518

5519
    Session session = fixture.make_bound_session(db);
2✔
5520

5521
    write_transaction(db, [](WriteTransaction& wt) {
2✔
5522
        TableRef table = wt.get_group().add_table_with_primary_key("class_t", type_Int, "pk");
2✔
5523
        wt.get_group().remove_table(table->get_key());
2✔
5524
        table = wt.get_group().add_table_with_primary_key("class_t", type_String, "pk");
2✔
5525
    });
2✔
5526
    session.wait_for_upload_complete_or_client_stopped();
2✔
5527
    session.wait_for_download_complete_or_client_stopped();
2✔
5528
}
2✔
5529

5530
template <typename T>
5531
T sequence_next()
5532
{
5533
    REALM_UNREACHABLE();
5534
}
5535

5536
template <>
5537
ObjectId sequence_next()
5538
{
8✔
5539
    return ObjectId::gen();
8✔
5540
}
8✔
5541

5542
template <>
5543
UUID sequence_next()
5544
{
8✔
5545
    union {
8✔
5546
        struct {
8✔
5547
            uint64_t upper;
8✔
5548
            uint64_t lower;
8✔
5549
        } ints;
8✔
5550
        UUID::UUIDBytes bytes;
8✔
5551
    } u;
8✔
5552
    static uint64_t counter = test_util::random_int(0, 1000);
8✔
5553
    u.ints.upper = ++counter;
8✔
5554
    u.ints.lower = ++counter;
8✔
5555
    return UUID{u.bytes};
8✔
5556
}
8✔
5557

5558
template <>
5559
Int sequence_next()
5560
{
8✔
5561
    static Int count = test_util::random_int(-1000, 1000);
8✔
5562
    return ++count;
8✔
5563
}
8✔
5564

5565
template <>
5566
String sequence_next()
5567
{
4✔
5568
    static std::string str;
4✔
5569
    static Int sequence = test_util::random_int(-1000, 1000);
4✔
5570
    str = util::format("string sequence %1", ++sequence);
4✔
5571
    return String(str);
4✔
5572
}
4✔
5573

5574
NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util::Optional<Int>,
5575
                         util::Optional<ObjectId>, util::Optional<UUID>)
5576
{
14✔
5577
    using underlying_type = typename util::RemoveOptional<TEST_TYPE>::type;
14✔
5578
    constexpr bool is_optional = !std::is_same_v<underlying_type, TEST_TYPE>;
14✔
5579
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
14✔
5580

5581
    TEST_CLIENT_DB(db_1);
14✔
5582
    TEST_CLIENT_DB(db_2);
14✔
5583

5584
    TEST_DIR(dir);
14✔
5585
    fixtures::ClientServerFixture fixture{dir, test_context};
14✔
5586
    fixture.start();
14✔
5587

5588
    Session session_1 = fixture.make_session(db_1, "/test");
14✔
5589
    Session session_2 = fixture.make_session(db_2, "/test");
14✔
5590

5591
    TEST_TYPE obj_1_id;
14✔
5592
    TEST_TYPE obj_2_id;
14✔
5593

5594
    TEST_TYPE default_or_null{};
14✔
5595
    if constexpr (std::is_same_v<TEST_TYPE, String>) {
14✔
5596
        default_or_null = "";
2✔
5597
    }
2✔
5598
    if constexpr (is_optional) {
14✔
5599
        CHECK(!default_or_null);
6✔
5600
    }
6✔
5601

5602
    {
14✔
5603
        WriteTransaction tr{db_1};
14✔
5604
        auto table_1 = tr.get_group().add_table_with_primary_key("class_Table1", type, "id", is_optional);
14✔
5605
        auto table_2 = tr.get_group().add_table_with_primary_key("class_Table2", type, "id", is_optional);
14✔
5606
        table_1->add_column_list(type, "oids", is_optional);
14✔
5607

5608
        auto obj_1 = table_1->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5609
        auto obj_2 = table_2->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5610
        if constexpr (is_optional) {
14✔
5611
            table_2->create_object_with_primary_key(default_or_null);
6✔
5612
        }
6✔
5613

5614
        auto list = obj_1.template get_list<TEST_TYPE>("oids");
14✔
5615
        obj_1_id = obj_1.template get<TEST_TYPE>("id");
14✔
5616
        obj_2_id = obj_2.template get<TEST_TYPE>("id");
14✔
5617
        list.insert(0, obj_2_id);
14✔
5618
        list.insert(1, default_or_null);
14✔
5619
        list.add(default_or_null);
14✔
5620

5621
        tr.commit();
14✔
5622
    }
14✔
5623

5624
    session_1.wait_for_upload_complete_or_client_stopped();
14✔
5625
    session_2.wait_for_download_complete_or_client_stopped();
14✔
5626

5627
    {
14✔
5628
        ReadTransaction tr{db_2};
14✔
5629
        auto table_1 = tr.get_table("class_Table1");
14✔
5630
        auto table_2 = tr.get_table("class_Table2");
14✔
5631
        auto obj_1 = *table_1->begin();
14✔
5632
        auto obj_2 = table_2->find_first(table_2->get_column_key("id"), obj_2_id);
14✔
5633
        CHECK(obj_2);
14✔
5634
        auto list = obj_1.get_list<TEST_TYPE>("oids");
14✔
5635
        CHECK_EQUAL(obj_1.template get<TEST_TYPE>("id"), obj_1_id);
14✔
5636
        CHECK_EQUAL(list.size(), 3);
14✔
5637
        CHECK_NOT(list.is_null(0));
14✔
5638
        CHECK_EQUAL(list.get(0), obj_2_id);
14✔
5639
        CHECK_EQUAL(list.get(1), default_or_null);
14✔
5640
        CHECK_EQUAL(list.get(2), default_or_null);
14✔
5641
        if constexpr (is_optional) {
14✔
5642
            auto obj_3 = table_2->find_first_null(table_2->get_column_key("id"));
6✔
5643
            CHECK(obj_3);
6✔
5644
            CHECK(list.is_null(1));
6✔
5645
            CHECK(list.is_null(2));
6✔
5646
        }
6✔
5647
    }
14✔
5648
}
14✔
5649

5650
TEST(Sync_Mixed)
5651
{
2✔
5652
    // Test replication and synchronization of Mixed values and lists.
5653
    DBOptions options;
2✔
5654
    options.logger = test_context.logger;
2✔
5655
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
5656
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
5657
    auto db_1 = DB::create(make_client_replication(), db_1_path, options);
2✔
5658
    auto db_2 = DB::create(make_client_replication(), db_2_path, options);
2✔
5659

5660
    TEST_DIR(dir);
2✔
5661
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5662
    fixture.start();
2✔
5663

5664
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5665
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5666

5667
    {
2✔
5668
        WriteTransaction tr{db_1};
2✔
5669
        auto& g = tr.get_group();
2✔
5670
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5671
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5672
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5673
        foos->add_column(type_Mixed, "value", true);
2✔
5674
        foos->add_column_list(type_Mixed, "values");
2✔
5675

5676
        auto foo = foos->create_object_with_primary_key(123);
2✔
5677
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5678
        auto fop = fops->create_object_with_primary_key(456);
2✔
5679

5680
        foo.set("value", Mixed(6.2f));
2✔
5681
        auto values = foo.get_list<Mixed>("values");
2✔
5682
        values.insert(0, StringData("A"));
2✔
5683
        values.insert(1, ObjLink{bars->get_key(), bar.get_key()});
2✔
5684
        values.insert(2, ObjLink{fops->get_key(), fop.get_key()});
2✔
5685
        values.insert(3, 123.f);
2✔
5686

5687
        tr.commit();
2✔
5688
    }
2✔
5689

5690
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5691
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5692

5693
    {
2✔
5694
        ReadTransaction tr{db_2};
2✔
5695

5696
        auto foos = tr.get_table("class_Foo");
2✔
5697
        auto bars = tr.get_table("class_Bar");
2✔
5698
        auto fops = tr.get_table("class_Fop");
2✔
5699

5700
        CHECK_EQUAL(foos->size(), 1);
2✔
5701
        CHECK_EQUAL(bars->size(), 1);
2✔
5702
        CHECK_EQUAL(fops->size(), 1);
2✔
5703

5704
        auto foo = *foos->begin();
2✔
5705
        auto value = foo.get<Mixed>("value");
2✔
5706
        CHECK_EQUAL(value, Mixed{6.2f});
2✔
5707
        auto values = foo.get_list<Mixed>("values");
2✔
5708
        CHECK_EQUAL(values.size(), 4);
2✔
5709

5710
        auto v0 = values.get(0);
2✔
5711
        auto v1 = values.get(1);
2✔
5712
        auto v2 = values.get(2);
2✔
5713
        auto v3 = values.get(3);
2✔
5714

5715
        auto l1 = v1.get_link();
2✔
5716
        auto l2 = v2.get_link();
2✔
5717

5718
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5719
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5720

5721
        CHECK_EQUAL(v0, Mixed{"A"});
2✔
5722
        CHECK_EQUAL(l1_table, bars);
2✔
5723
        CHECK_EQUAL(l2_table, fops);
2✔
5724
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5725
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5726
        CHECK_EQUAL(v3, Mixed{123.f});
2✔
5727
    }
2✔
5728
}
2✔
5729

5730
/*
5731
TEST(Sync_TypedLinks)
5732
{
5733
    // Test replication and synchronization of Mixed values and lists.
5734

5735
    TEST_CLIENT_DB(db_1);
5736
    TEST_CLIENT_DB(db_2);
5737

5738
    TEST_DIR(dir);
5739
    fixtures::ClientServerFixture fixture{dir, test_context};
5740
    fixture.start();
5741

5742
    Session session_1 = fixture.make_session(db_1, "/test");
5743
    Session session_2 = fixture.make_session(db_2, "/test");
5744

5745
    write_transaction(db_1, [](WriteTransaction& tr) {
5746
        auto& g = tr.get_group();
5747
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
5748
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
5749
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
5750
        foos->add_column(type_TypedLink, "link");
5751

5752
        auto foo1 = foos->create_object_with_primary_key(123);
5753
        auto foo2 = foos->create_object_with_primary_key(456);
5754
        auto bar = bars->create_object_with_primary_key("Hello");
5755
        auto fop = fops->create_object_with_primary_key(456);
5756

5757
        foo1.set("link", ObjLink(bars->get_key(), bar.get_key()));
5758
        foo2.set("link", ObjLink(fops->get_key(), fop.get_key()));
5759
    });
5760

5761
    session_1.wait_for_upload_complete_or_client_stopped();
5762
    session_2.wait_for_download_complete_or_client_stopped();
5763

5764
    {
5765
        ReadTransaction tr{db_2};
5766

5767
        auto foos = tr.get_table("class_Foo");
5768
        auto bars = tr.get_table("class_Bar");
5769
        auto fops = tr.get_table("class_Fop");
5770

5771
        CHECK_EQUAL(foos->size(), 2);
5772
        CHECK_EQUAL(bars->size(), 1);
5773
        CHECK_EQUAL(fops->size(), 1);
5774

5775
        auto it = foos->begin();
5776
        auto l1 = it->get<ObjLink>("link");
5777
        ++it;
5778
        auto l2 = it->get<ObjLink>("link");
5779

5780
        auto l1_table = tr.get_table(l1.get_table_key());
5781
        auto l2_table = tr.get_table(l2.get_table_key());
5782

5783
        CHECK_EQUAL(l1_table, bars);
5784
        CHECK_EQUAL(l2_table, fops);
5785
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
5786
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
5787
    }
5788
}
5789
*/
5790

5791
TEST(Sync_Dictionary)
5792
{
2✔
5793
    // Test replication and synchronization of Mixed values and lists.
5794

5795
    TEST_CLIENT_DB(db_1);
2✔
5796
    TEST_CLIENT_DB(db_2);
2✔
5797

5798
    TEST_DIR(dir);
2✔
5799
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5800
    fixture.start();
2✔
5801

5802
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5803
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5804

5805
    Timestamp now{std::chrono::system_clock::now()};
2✔
5806

5807
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5808
        auto& g = tr.get_group();
2✔
5809
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5810
        auto col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
5811
        auto col_dict_str = foos->add_column_dictionary(type_String, "str_dict", true);
2✔
5812

5813
        auto foo = foos->create_object_with_primary_key(123);
2✔
5814

5815
        auto dict = foo.get_dictionary(col_dict);
2✔
5816
        dict.insert("hello", "world");
2✔
5817
        dict.insert("cnt", 7);
2✔
5818
        dict.insert("when", now);
2✔
5819

5820
        auto dict_str = foo.get_dictionary(col_dict_str);
2✔
5821
        dict_str.insert("some", "text");
2✔
5822
        dict_str.insert("nothing", null());
2✔
5823
    });
2✔
5824

5825
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5826
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5827

5828
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
5829
        auto foos = tr.get_table("class_Foo");
2✔
5830
        CHECK_EQUAL(foos->size(), 1);
2✔
5831

5832
        auto it = foos->begin();
2✔
5833
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
5834
        CHECK(dict.get_value_data_type() == type_Mixed);
2✔
5835
        CHECK_EQUAL(dict.size(), 3);
2✔
5836

5837
        auto col_dict_str = foos->get_column_key("str_dict");
2✔
5838
        auto dict_str = it->get_dictionary(col_dict_str);
2✔
5839
        CHECK(col_dict_str.is_nullable());
2✔
5840
        CHECK(dict_str.get_value_data_type() == type_String);
2✔
5841
        CHECK_EQUAL(dict_str.size(), 2);
2✔
5842

5843
        Mixed val = dict["hello"];
2✔
5844
        CHECK_EQUAL(val.get_string(), "world");
2✔
5845
        val = dict.get("cnt");
2✔
5846
        CHECK_EQUAL(val.get_int(), 7);
2✔
5847
        val = dict.get("when");
2✔
5848
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5849

5850
        dict.erase("cnt");
2✔
5851
        dict.insert("hello", "goodbye");
2✔
5852
    });
2✔
5853

5854
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
5855
    session_1.wait_for_download_complete_or_client_stopped();
2✔
5856

5857
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5858
        auto foos = tr.get_table("class_Foo");
2✔
5859
        CHECK_EQUAL(foos->size(), 1);
2✔
5860

5861
        auto it = foos->begin();
2✔
5862
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
5863
        CHECK_EQUAL(dict.size(), 2);
2✔
5864

5865
        Mixed val = dict["hello"];
2✔
5866
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
5867
        val = dict.get("when");
2✔
5868
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5869

5870
        dict.clear();
2✔
5871
    });
2✔
5872

5873
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5874
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5875

5876
    {
2✔
5877
        ReadTransaction read_1{db_1};
2✔
5878
        ReadTransaction read_2{db_2};
2✔
5879
        // tr.get_group().to_json(std::cout);
5880

5881
        auto foos = read_2.get_table("class_Foo");
2✔
5882

5883
        CHECK_EQUAL(foos->size(), 1);
2✔
5884

5885
        auto it = foos->begin();
2✔
5886
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
5887
        CHECK_EQUAL(dict.size(), 0);
2✔
5888

5889
        CHECK(compare_groups(read_1, read_2));
2✔
5890
    }
2✔
5891
}
2✔
5892

5893
TEST(Sync_CollectionInMixed)
5894
{
2✔
5895
    TEST_CLIENT_DB(db_1);
2✔
5896
    TEST_CLIENT_DB(db_2);
2✔
5897

5898
    TEST_DIR(dir);
2✔
5899
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5900
    fixture.start();
2✔
5901

5902
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5903
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5904

5905
    Timestamp now{std::chrono::system_clock::now()};
2✔
5906

5907
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5908
        auto& g = tr.get_group();
2✔
5909
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
5910
        auto col_any = table->add_column(type_Mixed, "any");
2✔
5911

5912
        auto foo = table->create_object_with_primary_key(123);
2✔
5913

5914
        // Create dictionary in Mixed property
5915
        foo.set_collection(col_any, CollectionType::Dictionary);
2✔
5916
        auto dict = foo.get_dictionary_ptr(col_any);
2✔
5917
        dict->insert("hello", "world");
2✔
5918
        dict->insert("cnt", 7);
2✔
5919
        dict->insert("when", now);
2✔
5920
        // Insert a List in a Dictionary
5921
        dict->insert_collection("list", CollectionType::List);
2✔
5922
        auto l = dict->get_list("list");
2✔
5923
        l->add(5);
2✔
5924
        l->insert_collection(1, CollectionType::List);
2✔
5925
        l->get_list(1)->add(7);
2✔
5926

5927
        auto bar = table->create_object_with_primary_key(456);
2✔
5928

5929
        // Create list in Mixed property
5930
        bar.set_collection(col_any, CollectionType::List);
2✔
5931
        auto list = bar.get_list_ptr<Mixed>(col_any);
2✔
5932
        list->add("John");
2✔
5933
        list->insert(0, 5);
2✔
5934
    });
2✔
5935

5936
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5937
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5938

5939
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
5940
        auto table = tr.get_table("class_Table");
2✔
5941
        auto col_any = table->get_column_key("any");
2✔
5942
        CHECK_EQUAL(table->size(), 2);
2✔
5943

5944
        auto obj = table->get_object_with_primary_key(123);
2✔
5945
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
5946
        CHECK(dict->get_value_data_type() == type_Mixed);
2✔
5947
        CHECK_EQUAL(dict->size(), 4);
2✔
5948

5949
        // Check that values are replicated
5950
        Mixed val = dict->get("hello");
2✔
5951
        CHECK_EQUAL(val.get_string(), "world");
2✔
5952
        val = dict->get("cnt");
2✔
5953
        CHECK_EQUAL(val.get_int(), 7);
2✔
5954
        val = dict->get("when");
2✔
5955
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5956
        CHECK_EQUAL(dict->get_list("list")->get(0).get_int(), 5);
2✔
5957

5958
        // Erase dictionary element
5959
        dict->erase("cnt");
2✔
5960
        // Replace dictionary element
5961
        dict->insert("hello", "goodbye");
2✔
5962

5963
        obj = table->get_object_with_primary_key(456);
2✔
5964
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
5965
        // Check that values are replicated
5966
        CHECK_EQUAL(list->get(0).get_int(), 5);
2✔
5967
        CHECK_EQUAL(list->get(1).get_string(), "John");
2✔
5968
        // Replace list element
5969
        list->set(1, "Paul");
2✔
5970
        // Erase list element
5971
        list->remove(0);
2✔
5972
    });
2✔
5973

5974
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
5975
    session_1.wait_for_download_complete_or_client_stopped();
2✔
5976

5977
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5978
        auto table = tr.get_table("class_Table");
2✔
5979
        auto col_any = table->get_column_key("any");
2✔
5980
        CHECK_EQUAL(table->size(), 2);
2✔
5981

5982
        auto obj = table->get_object_with_primary_key(123);
2✔
5983
        auto dict = obj.get_dictionary(col_any);
2✔
5984
        CHECK_EQUAL(dict.size(), 3);
2✔
5985

5986
        Mixed val = dict["hello"];
2✔
5987
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
5988
        val = dict.get("when");
2✔
5989
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
5990

5991
        // Dictionary clear
5992
        dict.clear();
2✔
5993

5994
        obj = table->get_object_with_primary_key(456);
2✔
5995
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
5996
        CHECK_EQUAL(list->size(), 1);
2✔
5997
        CHECK_EQUAL(list->get(0).get_string(), "Paul");
2✔
5998
        // List clear
5999
        list->clear();
2✔
6000
    });
2✔
6001

6002
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6003
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6004

6005
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6006
        auto table = tr.get_table("class_Table");
2✔
6007
        auto col_any = table->get_column_key("any");
2✔
6008

6009
        CHECK_EQUAL(table->size(), 2);
2✔
6010

6011
        auto obj = table->get_object_with_primary_key(123);
2✔
6012
        auto dict = obj.get_dictionary(col_any);
2✔
6013
        CHECK_EQUAL(dict.size(), 0);
2✔
6014

6015
        // Replace dictionary with list on property
6016
        obj.set_collection(col_any, CollectionType::List);
2✔
6017

6018
        obj = table->get_object_with_primary_key(456);
2✔
6019
        auto list = obj.get_list<Mixed>(col_any);
2✔
6020
        CHECK_EQUAL(list.size(), 0);
2✔
6021
        // Replace list with Dictionary on property
6022
        obj.set_collection(col_any, CollectionType::Dictionary);
2✔
6023
    });
2✔
6024

6025
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6026
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6027

6028
    {
2✔
6029
        ReadTransaction read_1{db_1};
2✔
6030
        ReadTransaction read_2{db_2};
2✔
6031

6032
        auto table = read_2.get_table("class_Table");
2✔
6033
        auto col_any = table->get_column_key("any");
2✔
6034

6035
        CHECK_EQUAL(table->size(), 2);
2✔
6036

6037
        auto obj = table->get_object_with_primary_key(123);
2✔
6038
        auto list = obj.get_list<Mixed>(col_any);
2✔
6039
        CHECK_EQUAL(list.size(), 0);
2✔
6040

6041
        obj = table->get_object_with_primary_key(456);
2✔
6042
        auto dict = obj.get_dictionary(col_any);
2✔
6043
        CHECK_EQUAL(dict.size(), 0);
2✔
6044

6045
        CHECK(compare_groups(read_1, read_2));
2✔
6046
    }
2✔
6047
}
2✔
6048

6049
TEST(Sync_CollectionInCollection)
6050
{
2✔
6051
    TEST_CLIENT_DB(db_1);
2✔
6052
    TEST_CLIENT_DB(db_2);
2✔
6053

6054
    TEST_DIR(dir);
2✔
6055
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6056
    fixture.start();
2✔
6057

6058
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6059
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6060

6061
    Timestamp now{std::chrono::system_clock::now()};
2✔
6062

6063
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6064
        auto& g = tr.get_group();
2✔
6065
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6066
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6067

6068
        auto foo = table->create_object_with_primary_key(123);
2✔
6069

6070
        // Create dictionary in Mixed property
6071
        foo.set_collection(col_any, CollectionType::Dictionary);
2✔
6072
        auto dict = foo.get_dictionary_ptr(col_any);
2✔
6073
        dict->insert("hello", "world");
2✔
6074
        dict->insert("cnt", 7);
2✔
6075
        dict->insert("when", now);
2✔
6076
        // Insert a List in a Dictionary
6077
        dict->insert_collection("collection", CollectionType::List);
2✔
6078
        auto l = dict->get_list("collection");
2✔
6079
        l->add(5);
2✔
6080

6081
        auto bar = table->create_object_with_primary_key(456);
2✔
6082

6083
        // Create list in Mixed property
6084
        bar.set_collection(col_any, CollectionType::List);
2✔
6085
        auto list = bar.get_list_ptr<Mixed>(col_any);
2✔
6086
        list->add("John");
2✔
6087
        list->insert(0, 5);
2✔
6088
        // Insert dictionary in List
6089
        list->insert_collection(2, CollectionType::Dictionary);
2✔
6090
        auto d = list->get_dictionary(2);
2✔
6091
        d->insert("One", 1);
2✔
6092
        d->insert("Two", 2);
2✔
6093
    });
2✔
6094

6095
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6096
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6097

6098
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6099
        auto table = tr.get_table("class_Table");
2✔
6100
        auto col_any = table->get_column_key("any");
2✔
6101
        CHECK_EQUAL(table->size(), 2);
2✔
6102

6103
        auto obj = table->get_object_with_primary_key(123);
2✔
6104
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6105
        CHECK(dict->get_value_data_type() == type_Mixed);
2✔
6106
        CHECK_EQUAL(dict->size(), 4);
2✔
6107

6108
        // Replace List with Dictionary
6109
        dict->insert_collection("collection", CollectionType::Dictionary);
2✔
6110
        auto d = dict->get_dictionary("collection");
2✔
6111
        d->insert("Three", 3);
2✔
6112
        d->insert("Four", 4);
2✔
6113

6114
        obj = table->get_object_with_primary_key(456);
2✔
6115
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6116
        // Replace Dictionary with List
6117
        list->set_collection(2, CollectionType::List);
2✔
6118
        auto l = list->get_list(2);
2✔
6119
        l->add(47);
2✔
6120
    });
2✔
6121

6122
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6123
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6124

6125
    {
2✔
6126
        ReadTransaction read_1{db_1};
2✔
6127
        ReadTransaction read_2{db_2};
2✔
6128

6129
        auto table = read_2.get_table("class_Table");
2✔
6130
        auto col_any = table->get_column_key("any");
2✔
6131

6132
        CHECK_EQUAL(table->size(), 2);
2✔
6133

6134
        auto obj = table->get_object_with_primary_key(123);
2✔
6135
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6136
        auto d = dict->get_dictionary("collection");
2✔
6137
        CHECK_EQUAL(d->get("Four").get_int(), 4);
2✔
6138

6139
        obj = table->get_object_with_primary_key(456);
2✔
6140
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6141
        auto l = list->get_list(2);
2✔
6142
        CHECK_EQUAL(l->get_any(0).get_int(), 47);
2✔
6143
    }
2✔
6144
}
2✔
6145

6146
TEST(Sync_DeleteCollectionInCollection)
6147
{
2✔
6148
    TEST_CLIENT_DB(db_1);
2✔
6149
    TEST_CLIENT_DB(db_2);
2✔
6150

6151
    TEST_DIR(dir);
2✔
6152
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6153
    fixture.start();
2✔
6154

6155
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6156
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6157

6158
    Timestamp now{std::chrono::system_clock::now()};
2✔
6159

6160
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6161
        auto& g = tr.get_group();
2✔
6162
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6163
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6164

6165
        auto foo = table->create_object_with_primary_key(123);
2✔
6166

6167
        // Create list in Mixed property
6168
        foo.set_json(col_any, R"([
2✔
6169
            {
2✔
6170
              "1_map": {
2✔
6171
                "2_string": "map value"
2✔
6172
               },
2✔
6173
              "1_list": ["list value"]
2✔
6174
            }
2✔
6175
        ])");
2✔
6176
    });
2✔
6177

6178
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6179
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6180

6181
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6182
        auto table = tr.get_table("class_Table");
2✔
6183
        auto col_any = table->get_column_key("any");
2✔
6184
        CHECK_EQUAL(table->size(), 1);
2✔
6185

6186
        auto obj = table->get_object_with_primary_key(123);
2✔
6187
        auto list = obj.get_list<Mixed>(col_any);
2✔
6188
        auto dict = list.get_dictionary(0);
2✔
6189
        dict->erase("1_map");
2✔
6190
    });
2✔
6191

6192
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6193
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6194

6195
    {
2✔
6196
        ReadTransaction read_1{db_1};
2✔
6197

6198
        auto table = read_1.get_table("class_Table");
2✔
6199
        auto col_any = table->get_column_key("any");
2✔
6200

6201
        CHECK_EQUAL(table->size(), 1);
2✔
6202

6203
        auto obj = table->get_object_with_primary_key(123);
2✔
6204
        auto list = obj.get_list<Mixed>(col_any);
2✔
6205
        auto dict = list.get_dictionary(0);
2✔
6206
        CHECK_EQUAL(dict->size(), 1);
2✔
6207
    }
2✔
6208
}
2✔
6209

6210
TEST(Sync_Dictionary_Links)
6211
{
2✔
6212
    TEST_CLIENT_DB(db_1);
2✔
6213
    TEST_CLIENT_DB(db_2);
2✔
6214

6215
    TEST_DIR(dir);
2✔
6216
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6217
    fixture.start();
2✔
6218

6219
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6220
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6221

6222
    // Test that we can transmit links.
6223

6224
    ColKey col_dict;
2✔
6225

6226
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6227
        auto& g = tr.get_group();
2✔
6228
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6229
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6230
        col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6231

6232
        auto foo = foos->create_object_with_primary_key(123);
2✔
6233
        auto a = bars->create_object_with_primary_key("a");
2✔
6234
        auto b = bars->create_object_with_primary_key("b");
2✔
6235

6236
        auto dict = foo.get_dictionary(col_dict);
2✔
6237
        dict.insert("a", a);
2✔
6238
        dict.insert("b", b);
2✔
6239
    });
2✔
6240

6241
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6242
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6243

6244
    {
2✔
6245
        ReadTransaction tr{db_2};
2✔
6246

6247
        auto foos = tr.get_table("class_Foo");
2✔
6248
        auto bars = tr.get_table("class_Bar");
2✔
6249

6250
        CHECK_EQUAL(foos->size(), 1);
2✔
6251
        CHECK_EQUAL(bars->size(), 2);
2✔
6252

6253
        auto foo = foos->get_object_with_primary_key(123);
2✔
6254
        auto a = bars->get_object_with_primary_key("a");
2✔
6255
        auto b = bars->get_object_with_primary_key("b");
2✔
6256

6257
        auto dict = foo.get_dictionary(foos->get_column_key("dict"));
2✔
6258
        CHECK_EQUAL(dict.size(), 2);
2✔
6259

6260
        auto dict_a = dict.get("a");
2✔
6261
        auto dict_b = dict.get("b");
2✔
6262
        CHECK(dict_a == Mixed{a.get_link()});
2✔
6263
        CHECK(dict_b == Mixed{b.get_link()});
2✔
6264
    }
2✔
6265

6266
    // Test that we can create tombstones for objects in dictionaries.
6267

6268
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6269
        auto& g = tr.get_group();
2✔
6270

6271
        auto bars = g.get_table("class_Bar");
2✔
6272
        auto a = bars->get_object_with_primary_key("a");
2✔
6273
        a.invalidate();
2✔
6274

6275
        auto foos = g.get_table("class_Foo");
2✔
6276
        auto foo = foos->get_object_with_primary_key(123);
2✔
6277
        auto dict = foo.get_dictionary(col_dict);
2✔
6278

6279
        CHECK_EQUAL(dict.size(), 2);
2✔
6280
        CHECK((*dict.find("a")).second.is_null());
2✔
6281

6282
        CHECK(dict.find("b") != dict.end());
2✔
6283
    });
2✔
6284

6285
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6286
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6287

6288
    {
2✔
6289
        ReadTransaction tr{db_2};
2✔
6290

6291
        auto foos = tr.get_table("class_Foo");
2✔
6292
        auto bars = tr.get_table("class_Bar");
2✔
6293

6294
        CHECK_EQUAL(foos->size(), 1);
2✔
6295
        CHECK_EQUAL(bars->size(), 1);
2✔
6296

6297
        auto b = bars->get_object_with_primary_key("b");
2✔
6298

6299
        auto foo = foos->get_object_with_primary_key(123);
2✔
6300
        auto dict = foo.get_dictionary(col_dict);
2✔
6301

6302
        CHECK_EQUAL(dict.size(), 2);
2✔
6303
        CHECK((*dict.find("a")).second.is_null());
2✔
6304

6305
        CHECK(dict.find("b") != dict.end());
2✔
6306
        CHECK((*dict.find("b")).second == Mixed{b.get_link()});
2✔
6307
    }
2✔
6308
}
2✔
6309

6310
TEST(Sync_Set)
6311
{
2✔
6312
    // Test replication and synchronization of Set values.
6313

6314
    TEST_CLIENT_DB(db_1);
2✔
6315
    TEST_CLIENT_DB(db_2);
2✔
6316

6317
    TEST_DIR(dir);
2✔
6318
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6319
    fixture.start();
2✔
6320

6321
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6322
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6323

6324
    ColKey col_ints, col_strings, col_mixeds;
2✔
6325
    {
2✔
6326
        WriteTransaction wt{db_1};
2✔
6327
        auto t = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "pk");
2✔
6328
        col_ints = t->add_column_set(type_Int, "ints");
2✔
6329
        col_strings = t->add_column_set(type_String, "strings");
2✔
6330
        col_mixeds = t->add_column_set(type_Mixed, "mixeds");
2✔
6331

6332
        auto obj = t->create_object_with_primary_key(0);
2✔
6333

6334
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6335
        auto strings = obj.get_set<StringData>(col_strings);
2✔
6336
        auto mixeds = obj.get_set<Mixed>(col_mixeds);
2✔
6337

6338
        ints.insert(123);
2✔
6339
        ints.insert(456);
2✔
6340
        ints.insert(789);
2✔
6341
        ints.insert(123);
2✔
6342
        ints.insert(456);
2✔
6343
        ints.insert(789);
2✔
6344

6345
        CHECK_EQUAL(ints.size(), 3);
2✔
6346
        CHECK_EQUAL(ints.find(123), 0);
2✔
6347
        CHECK_EQUAL(ints.find(456), 1);
2✔
6348
        CHECK_EQUAL(ints.find(789), 2);
2✔
6349

6350
        strings.insert("a");
2✔
6351
        strings.insert("b");
2✔
6352
        strings.insert("c");
2✔
6353
        strings.insert("a");
2✔
6354
        strings.insert("b");
2✔
6355
        strings.insert("c");
2✔
6356

6357
        CHECK_EQUAL(strings.size(), 3);
2✔
6358
        CHECK_EQUAL(strings.find("a"), 0);
2✔
6359
        CHECK_EQUAL(strings.find("b"), 1);
2✔
6360
        CHECK_EQUAL(strings.find("c"), 2);
2✔
6361

6362
        mixeds.insert(Mixed{123});
2✔
6363
        mixeds.insert(Mixed{"a"});
2✔
6364
        mixeds.insert(Mixed{456.0});
2✔
6365
        mixeds.insert(Mixed{123});
2✔
6366
        mixeds.insert(Mixed{"a"});
2✔
6367
        mixeds.insert(Mixed{456.0});
2✔
6368

6369
        CHECK_EQUAL(mixeds.size(), 3);
2✔
6370
        CHECK_EQUAL(mixeds.find(123), 0);
2✔
6371
        CHECK_EQUAL(mixeds.find(456.0), 1);
2✔
6372
        CHECK_EQUAL(mixeds.find("a"), 2);
2✔
6373

6374
        wt.commit();
2✔
6375
    }
2✔
6376

6377
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6378
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6379

6380
    // Create a conflict. Session 1 should lose, because it has a lower peer ID.
6381
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6382
        auto t = wt.get_table("class_Foo");
2✔
6383
        auto obj = t->get_object_with_primary_key(0);
2✔
6384

6385
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6386
        ints.insert(999);
2✔
6387
    });
2✔
6388

6389
    write_transaction(db_2, [=](WriteTransaction& wt) {
2✔
6390
        auto t = wt.get_table("class_Foo");
2✔
6391
        auto obj = t->get_object_with_primary_key(0);
2✔
6392

6393
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6394
        ints.insert(999);
2✔
6395
        ints.erase(999);
2✔
6396
    });
2✔
6397

6398
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6399
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6400
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6401
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6402

6403
    {
2✔
6404
        ReadTransaction read_1{db_1};
2✔
6405
        ReadTransaction read_2{db_2};
2✔
6406
        CHECK(compare_groups(read_1, read_2));
2✔
6407
    }
2✔
6408

6409
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6410
        auto t = wt.get_table("class_Foo");
2✔
6411
        auto obj = t->get_object_with_primary_key(0);
2✔
6412
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6413
        ints.clear();
2✔
6414
    });
2✔
6415

6416
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6417
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6418

6419
    {
2✔
6420
        ReadTransaction read_1{db_1};
2✔
6421
        ReadTransaction read_2{db_2};
2✔
6422
        CHECK(compare_groups(read_1, read_2));
2✔
6423
    }
2✔
6424
}
2✔
6425

6426
TEST(Sync_BundledRealmFile)
6427
{
2✔
6428
    TEST_CLIENT_DB(db);
2✔
6429
    SHARED_GROUP_TEST_PATH(path);
2✔
6430

6431
    TEST_DIR(dir);
2✔
6432
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6433
    fixture.start();
2✔
6434

6435
    Session session = fixture.make_bound_session(db);
2✔
6436

6437
    write_transaction(db, [](WriteTransaction& tr) {
2✔
6438
        auto foos = tr.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6439
        foos->create_object_with_primary_key(123);
2✔
6440
    });
2✔
6441

6442
    // We cannot write out file if changes are not synced to server
6443
    CHECK_THROW_ANY(db->write_copy(path.c_str(), nullptr));
2✔
6444

6445
    session.wait_for_upload_complete_or_client_stopped();
2✔
6446
    session.wait_for_download_complete_or_client_stopped();
2✔
6447

6448
    // Now we can
6449
    db->write_copy(path.c_str(), nullptr);
2✔
6450
}
2✔
6451

6452
TEST(Sync_UpgradeToClientHistory)
6453
{
2✔
6454
    DBOptions options;
2✔
6455
    options.logger = test_context.logger;
2✔
6456
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
6457
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
6458
    auto db_1 = DB::create(make_in_realm_history(), db_1_path, options);
2✔
6459
    auto db_2 = DB::create(make_in_realm_history(), db_2_path, options);
2✔
6460
    {
2✔
6461
        auto tr = db_1->start_write();
2✔
6462

6463
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
6464
        auto col_float = embedded->add_column(type_Float, "float");
2✔
6465
        auto col_additional = embedded->add_column_dictionary(*embedded, "additional");
2✔
6466

6467
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6468
        auto col_list = baas->add_column_list(type_Int, "list");
2✔
6469
        auto col_set = baas->add_column_set(type_Int, "set");
2✔
6470
        auto col_dict = baas->add_column_dictionary(type_Int, "dictionary");
2✔
6471
        auto col_child = baas->add_column(*embedded, "child");
2✔
6472

6473
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6474
        auto col_str = foos->add_column(type_String, "str");
2✔
6475
        auto col_children = foos->add_column_list(*embedded, "children");
2✔
6476

6477
        auto foobaas = tr->add_table_with_primary_key("class_FooBaa", type_ObjectId, "_id");
2✔
6478
        auto col_time = foobaas->add_column(type_Timestamp, "time");
2✔
6479

6480
        auto col_link = baas->add_column(*foos, "link");
2✔
6481

6482
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Hello");
2✔
6483
        auto children = foo.get_linklist(col_children);
2✔
6484
        children.create_and_insert_linked_object(0);
2✔
6485
        auto baa = baas->create_object_with_primary_key(999).set(col_link, foo.get_key());
2✔
6486
        auto obj = baa.create_and_set_linked_object(col_child);
2✔
6487
        obj.set(col_float, 42.f);
2✔
6488
        auto additional = obj.get_dictionary(col_additional);
2✔
6489
        additional.create_and_insert_linked_object("One").set(col_float, 1.f);
2✔
6490
        additional.create_and_insert_linked_object("Two").set(col_float, 2.f);
2✔
6491
        additional.create_and_insert_linked_object("Three").set(col_float, 3.f);
2✔
6492

6493
        auto list = baa.get_list<Int>(col_list);
2✔
6494
        list.add(1);
2✔
6495
        list.add(0);
2✔
6496
        list.add(2);
2✔
6497
        list.add(3);
2✔
6498
        list.set(1, 5);
2✔
6499
        list.remove(1);
2✔
6500
        auto set = baa.get_set<Int>(col_set);
2✔
6501
        set.insert(4);
2✔
6502
        set.insert(2);
2✔
6503
        set.insert(5);
2✔
6504
        set.insert(6);
2✔
6505
        set.erase(2);
2✔
6506
        auto dict = baa.get_dictionary(col_dict);
2✔
6507
        dict.insert("key6", 6);
2✔
6508
        dict.insert("key7", 7);
2✔
6509
        dict.insert("key8", 8);
2✔
6510
        dict.insert("key9", 9);
2✔
6511
        dict.erase("key6");
2✔
6512

6513
        for (int i = 0; i < 100; i++) {
202✔
6514
            foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i));
200✔
6515
        }
200✔
6516

6517
        tr->commit();
2✔
6518
    }
2✔
6519
    {
2✔
6520
        auto tr = db_2->start_write();
2✔
6521
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6522
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6523
        auto col_str = foos->add_column(type_String, "str");
2✔
6524
        auto col_link = baas->add_column(*foos, "link");
2✔
6525

6526
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Goodbye");
2✔
6527
        baas->create_object_with_primary_key(888).set(col_link, foo.get_key());
2✔
6528

6529
        tr->commit();
2✔
6530
    }
2✔
6531

6532
    db_1->create_new_history(make_client_replication());
2✔
6533
    db_2->create_new_history(make_client_replication());
2✔
6534

6535
    TEST_DIR(dir);
2✔
6536
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6537
    fixture.start();
2✔
6538

6539
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6540
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6541

6542
    write_transaction(db_1, [](WriteTransaction& tr) {
2✔
6543
        auto foos = tr.get_group().get_table("class_Foo");
2✔
6544
        foos->create_object_with_primary_key("456");
2✔
6545
    });
2✔
6546
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6547
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6548
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6549
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6550

6551
    // db_2->start_read()->to_json(std::cout);
6552
}
2✔
6553

6554
// This test is extracted from ClientReset_ThreeClients
6555
// because it uncovers a bug in how MSVC 2019 compiles
6556
// things in Changeset::get_key()
6557
TEST(Sync_MergeStringPrimaryKey)
6558
{
2✔
6559
    TEST_DIR(dir_1); // The server.
2✔
6560
    TEST_CLIENT_DB(db_1);
2✔
6561
    TEST_CLIENT_DB(db_2);
2✔
6562
    TEST_DIR(metadata_dir_1);
2✔
6563
    TEST_DIR(metadata_dir_2);
2✔
6564

6565
    const std::string server_path = "/data";
2✔
6566

6567
    std::string real_path_1, real_path_2;
2✔
6568

6569
    auto create_schema = [&](Transaction& group) {
4✔
6570
        TableRef table_0 = group.add_table_with_primary_key("class_table_0", type_Int, "id");
4✔
6571
        table_0->add_column(type_Int, "int");
4✔
6572
        table_0->add_column(type_Bool, "bool");
4✔
6573
        table_0->add_column(type_Float, "float");
4✔
6574
        table_0->add_column(type_Double, "double");
4✔
6575
        table_0->add_column(type_Timestamp, "timestamp");
4✔
6576

6577
        TableRef table_1 = group.add_table_with_primary_key("class_table_1", type_Int, "pk_int");
4✔
6578
        table_1->add_column(type_String, "String");
4✔
6579

6580
        TableRef table_2 = group.add_table_with_primary_key("class_table_2", type_String, "pk_string");
4✔
6581
        table_2->add_column_list(type_String, "array_string");
4✔
6582
    };
4✔
6583

6584
    // First we make changesets. Then we upload them.
6585
    {
2✔
6586
        ClientServerFixture fixture(dir_1, test_context);
2✔
6587
        fixture.start();
2✔
6588
        real_path_1 = fixture.map_virtual_to_real_path(server_path);
2✔
6589

6590
        {
2✔
6591
            WriteTransaction wt{db_1};
2✔
6592
            create_schema(wt);
2✔
6593
            wt.commit();
2✔
6594
        }
2✔
6595
        {
2✔
6596
            WriteTransaction wt{db_2};
2✔
6597
            create_schema(wt);
2✔
6598

6599
            TableRef table_2 = wt.get_table("class_table_2");
2✔
6600
            auto col = table_2->get_column_key("array_string");
2✔
6601
            auto list_string = table_2->create_object_with_primary_key("aaa").get_list<String>(col);
2✔
6602
            list_string.add("a");
2✔
6603
            list_string.add("b");
2✔
6604

6605
            wt.commit();
2✔
6606
        }
2✔
6607

6608
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
6609
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
6610

6611
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
6612
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
6613
        // Download completion is not important.
6614
    }
2✔
6615
}
2✔
6616

6617
TEST(Sync_DifferentUsersMultiplexing)
6618
{
2✔
6619
    ClientServerFixture::Config fixture_config;
2✔
6620
    fixture_config.one_connection_per_session = false;
2✔
6621

6622
    TEST_DIR(server_dir);
2✔
6623
    ClientServerFixture fixture(server_dir, test_context, std::move(fixture_config));
2✔
6624

6625
    struct SessionBundle {
2✔
6626
        test_util::DBTestPathGuard path_guard;
2✔
6627
        DBRef db;
2✔
6628
        Session sess;
2✔
6629

6630
        SessionBundle(unit_test::TestContext& ctx, ClientServerFixture& fixture, std::string name,
2✔
6631
                      std::string signed_token, std::string user_id)
2✔
6632
            : path_guard(realm::test_util::get_test_path(ctx.get_test_name(), "." + name + ".realm"))
5✔
6633
            , db(DB::create(make_client_replication(), path_guard))
5✔
6634
        {
8✔
6635
            Session::Config config;
8✔
6636
            config.signed_user_token = signed_token;
8✔
6637
            config.user_id = user_id;
8✔
6638
            sess = fixture.make_bound_session(db, "/test", std::move(config));
8✔
6639
            sess.wait_for_download_complete_or_client_stopped();
8✔
6640
        }
8✔
6641
    };
2✔
6642

6643
    fixture.start();
2✔
6644

6645
    SessionBundle user_1_sess_1(test_context, fixture, "user_1_db_1", g_user_0_token, "user_0");
2✔
6646
    SessionBundle user_2_sess_1(test_context, fixture, "user_2_db_1", g_user_1_token, "user_1");
2✔
6647
    SessionBundle user_1_sess_2(test_context, fixture, "user_1_db_2", g_user_0_token, "user_0");
2✔
6648
    SessionBundle user_2_sess_2(test_context, fixture, "user_2_db_2", g_user_1_token, "user_1");
2✔
6649

6650
    CHECK_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6651
                user_1_sess_2.sess.get_appservices_connection_id());
2✔
6652
    CHECK_EQUAL(user_2_sess_1.sess.get_appservices_connection_id(),
2✔
6653
                user_2_sess_2.sess.get_appservices_connection_id());
2✔
6654
    CHECK_NOT_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6655
                    user_2_sess_1.sess.get_appservices_connection_id());
2✔
6656
    CHECK_NOT_EQUAL(user_1_sess_2.sess.get_appservices_connection_id(),
2✔
6657
                    user_2_sess_2.sess.get_appservices_connection_id());
2✔
6658
}
2✔
6659

6660
TEST(Sync_TransformAgainstEmptyReciprocalChangeset)
6661
{
2✔
6662
    TEST_CLIENT_DB(seed_db);
2✔
6663
    TEST_CLIENT_DB(db_1);
2✔
6664
    TEST_CLIENT_DB(db_2);
2✔
6665

6666
    {
2✔
6667
        auto tr = seed_db->start_write();
2✔
6668
        // Create schema: single table with array of ints as property.
6669
        auto table = tr->add_table_with_primary_key("class_table", type_Int, "_id");
2✔
6670
        table->add_column_list(type_Int, "ints");
2✔
6671
        table->add_column(type_String, "string");
2✔
6672
        tr->commit_and_continue_writing();
2✔
6673

6674
        // Create object and initialize array.
6675
        table = tr->get_table("class_table");
2✔
6676
        auto obj = table->create_object_with_primary_key(42);
2✔
6677
        auto ints = obj.get_list<int64_t>("ints");
2✔
6678
        for (auto i = 0; i < 8; ++i) {
18✔
6679
            ints.insert(i, i);
16✔
6680
        }
16✔
6681
        tr->commit();
2✔
6682
    }
2✔
6683

6684
    {
2✔
6685
        TEST_DIR(dir);
2✔
6686
        MultiClientServerFixture fixture(3, 1, dir, test_context);
2✔
6687
        fixture.start();
2✔
6688

6689
        util::Optional<Session> seed_session = fixture.make_bound_session(0, seed_db, 0, "/test");
2✔
6690
        util::Optional<Session> db_1_session = fixture.make_bound_session(1, db_1, 0, "/test");
2✔
6691
        util::Optional<Session> db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6692

6693
        seed_session->wait_for_upload_complete_or_client_stopped();
2✔
6694
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6695
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6696
        seed_session.reset();
2✔
6697
        db_2_session.reset();
2✔
6698

6699
        auto move_element = [&](const DBRef& db, size_t from, size_t to, size_t string_size = 0) {
8✔
6700
            auto wt = db->start_write();
8✔
6701
            auto table = wt->get_table("class_table");
8✔
6702
            auto obj = table->get_object_with_primary_key(42);
8✔
6703
            auto ints = obj.get_list<int64_t>("ints");
8✔
6704
            ints.move(from, to);
8✔
6705
            obj.set("string", std::string(string_size, 'a'));
8✔
6706
            wt->commit();
8✔
6707
        };
8✔
6708

6709
        // Client 1 uploads two move instructions.
6710
        move_element(db_1, 7, 2);
2✔
6711
        move_element(db_1, 7, 6);
2✔
6712

6713
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6714

6715
        std::this_thread::sleep_for(std::chrono::milliseconds{10});
2✔
6716

6717
        // Client 2 uploads two move instructions.
6718
        // The sync client uploads at most 128 KB of data so we make the first changeset large enough so two upload
6719
        // messages are sent to the server instead of one. Each change is transformed against the changes from
6720
        // Client 1.
6721

6722
        // First change discards the first change (move(7, 2)) of Client 1.
6723
        move_element(db_2, 7, 0, 200 * 1024);
2✔
6724
        // Second change is tranformed against an empty reciprocal changeset as result of the change above.
6725
        move_element(db_2, 7, 5);
2✔
6726
        db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6727

6728
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6729
        db_2_session->wait_for_upload_complete_or_client_stopped();
2✔
6730

6731
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6732
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6733
    }
2✔
6734

6735
    ReadTransaction rt_1(db_1);
2✔
6736
    ReadTransaction rt_2(db_2);
2✔
6737
    const Group& group_1 = rt_1;
2✔
6738
    const Group& group_2 = rt_2;
2✔
6739
    group_1.verify();
2✔
6740
    group_2.verify();
2✔
6741
    CHECK(compare_groups(rt_1, rt_2));
2✔
6742
}
2✔
6743

6744
#endif // !REALM_MOBILE
6745

6746
// Tests that an empty reciprocal changesets is set and retrieved correctly.
6747
TEST(Sync_SetAndGetEmptyReciprocalChangeset)
6748
{
2✔
6749
    using namespace realm;
2✔
6750
    using namespace realm::sync::instr;
2✔
6751
    using realm::sync::Changeset;
2✔
6752

6753
    TEST_CLIENT_DB(db);
2✔
6754

6755
    auto& history = get_history(db);
2✔
6756
    history.set_client_file_ident(SaltedFileIdent{1, 0x1234567812345678}, false);
2✔
6757
    timestamp_type timestamp{1};
2✔
6758
    history.set_local_origin_timestamp_source([&] {
6✔
6759
        return ++timestamp;
6✔
6760
    });
6✔
6761

6762
    auto latest_local_version = [&] {
2✔
6763
        auto tr = db->start_write();
2✔
6764
        // Create schema: single table with array of ints as property.
6765
        tr->add_table_with_primary_key("class_table", type_Int, "_id")->add_column_list(type_Int, "ints");
2✔
6766
        tr->commit_and_continue_writing();
2✔
6767

6768
        // Create object and initialize array.
6769
        TableRef table = tr->get_table("class_table");
2✔
6770
        auto obj = table->create_object_with_primary_key(42);
2✔
6771
        auto ints = obj.get_list<int64_t>("ints");
2✔
6772
        for (auto i = 0; i < 8; ++i) {
18✔
6773
            ints.insert(i, i);
16✔
6774
        }
16✔
6775
        tr->commit_and_continue_writing();
2✔
6776

6777
        // Move element in array.
6778
        ints.move(7, 2);
2✔
6779
        return tr->commit();
2✔
6780
    }();
2✔
6781

6782
    // Create changeset which moves element from index 7 to index 0 in array.
6783
    // This changeset will discard the previous move (reciprocal changeset), leaving the local reciprocal changesets
6784
    // with no instructions (empty).
6785
    Changeset changeset;
2✔
6786
    ArrayMove instr;
2✔
6787
    instr.table = changeset.intern_string("table");
2✔
6788
    instr.object = instr::PrimaryKey{42};
2✔
6789
    instr.field = changeset.intern_string("ints");
2✔
6790
    instr.path.push_back(7);
2✔
6791
    instr.ndx_2 = 0;
2✔
6792
    instr.prior_size = 8;
2✔
6793
    changeset.push_back(instr);
2✔
6794
    changeset.version = 1;
2✔
6795
    changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6796
    changeset.origin_timestamp = timestamp;
2✔
6797
    changeset.origin_file_ident = 2;
2✔
6798

6799
    ChangesetEncoder::Buffer encoded;
2✔
6800
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6801
    encode_changeset(changeset, encoded);
2✔
6802
    server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
2✔
6803
                                           BinaryData(encoded.data(), encoded.size()), changeset.origin_timestamp,
2✔
6804
                                           changeset.origin_file_ident);
2✔
6805

6806
    SyncProgress progress = {};
2✔
6807
    progress.download.server_version = changeset.version;
2✔
6808
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6809
    progress.latest_server_version.version = changeset.version;
2✔
6810
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6811

6812
    uint_fast64_t downloadable_bytes = 0;
2✔
6813
    VersionInfo version_info;
2✔
6814
    auto transact = db->start_read();
2✔
6815
    history.integrate_server_changesets(progress, downloadable_bytes, server_changesets_encoded, version_info,
2✔
6816
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6817

6818
    bool is_compressed = false;
2✔
6819
    auto data = history.get_reciprocal_transform(latest_local_version, is_compressed);
2✔
6820
    Changeset reciprocal_changeset;
2✔
6821
    ChunkedBinaryInputStream in{data};
2✔
6822
    if (is_compressed) {
2✔
6823
        size_t total_size;
2✔
6824
        auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
2✔
6825
        CHECK(decompressed);
2✔
6826
        sync::parse_changeset(*decompressed, reciprocal_changeset); // Throws
2✔
6827
    }
2✔
6828
    else {
×
6829
        sync::parse_changeset(in, reciprocal_changeset); // Throws
×
6830
    }
×
6831
    // The only instruction in the reciprocal changeset was discarded during OT.
6832
    CHECK(reciprocal_changeset.empty());
2✔
6833
}
2✔
6834

6835
TEST(Sync_InvalidChangesetFromServer)
6836
{
2✔
6837
    TEST_CLIENT_DB(db);
2✔
6838

6839
    auto& history = get_history(db);
2✔
6840
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6841

6842
    instr::CreateObject bad_instr;
2✔
6843
    bad_instr.object = InternString{1};
2✔
6844
    bad_instr.table = InternString{2};
2✔
6845

6846
    Changeset changeset;
2✔
6847
    changeset.push_back(bad_instr);
2✔
6848

6849
    ChangesetEncoder::Buffer encoded;
2✔
6850
    encode_changeset(changeset, encoded);
2✔
6851
    RemoteChangeset server_changeset;
2✔
6852
    server_changeset.origin_file_ident = 1;
2✔
6853
    server_changeset.remote_version = 1;
2✔
6854
    server_changeset.data = BinaryData(encoded.data(), encoded.size());
2✔
6855

6856
    VersionInfo version_info;
2✔
6857
    auto transact = db->start_read();
2✔
6858
    CHECK_THROW_EX(history.integrate_server_changesets({}, 0, util::Span(&server_changeset, 1), version_info,
2✔
6859
                                                       DownloadBatchState::SteadyState, *test_context.logger,
2✔
6860
                                                       transact),
2✔
6861
                   sync::IntegrationException,
2✔
6862
                   StringData(e.what()).contains("Failed to parse received changeset: Invalid interned string"));
2✔
6863
}
2✔
6864

6865
TEST(Sync_ServerVersionsSkippedFromDownloadCursor)
6866
{
2✔
6867
    TEST_CLIENT_DB(db);
2✔
6868

6869
    auto& history = get_history(db);
2✔
6870
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6871
    timestamp_type timestamp{1};
2✔
6872
    history.set_local_origin_timestamp_source([&] {
2✔
6873
        return ++timestamp;
2✔
6874
    });
2✔
6875

6876
    auto latest_local_version = [&] {
2✔
6877
        auto tr = db->start_write();
2✔
6878
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6879
        return tr->commit();
2✔
6880
    }();
2✔
6881

6882
    Changeset server_changeset;
2✔
6883
    server_changeset.version = 10;
2✔
6884
    server_changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6885
    server_changeset.origin_timestamp = ++timestamp;
2✔
6886
    server_changeset.origin_file_ident = 1;
2✔
6887

6888
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6889
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6890
    encoded.emplace_back();
2✔
6891
    encode_changeset(server_changeset, encoded.back());
2✔
6892
    server_changesets_encoded.emplace_back(server_changeset.version, server_changeset.last_integrated_remote_version,
2✔
6893
                                           BinaryData(encoded.back().data(), encoded.back().size()),
2✔
6894
                                           server_changeset.origin_timestamp, server_changeset.origin_file_ident);
2✔
6895

6896
    SyncProgress progress = {};
2✔
6897
    // The server skips 10 server versions.
6898
    progress.download.server_version = server_changeset.version + 10;
2✔
6899
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6900
    progress.latest_server_version.version = server_changeset.version + 15;
2✔
6901
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6902

6903
    uint_fast64_t downloadable_bytes = 0;
2✔
6904
    VersionInfo version_info;
2✔
6905
    auto transact = db->start_read();
2✔
6906
    history.integrate_server_changesets(progress, downloadable_bytes, server_changesets_encoded, version_info,
2✔
6907
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6908

6909
    version_type current_version;
2✔
6910
    SaltedFileIdent file_ident;
2✔
6911
    SyncProgress expected_progress;
2✔
6912
    history.get_status(current_version, file_ident, expected_progress);
2✔
6913

6914
    // Check progress is reported correctly.
6915
    CHECK_EQUAL(progress.latest_server_version.salt, expected_progress.latest_server_version.salt);
2✔
6916
    CHECK_EQUAL(progress.latest_server_version.version, expected_progress.latest_server_version.version);
2✔
6917
    CHECK_EQUAL(progress.download.last_integrated_client_version,
2✔
6918
                expected_progress.download.last_integrated_client_version);
2✔
6919
    CHECK_EQUAL(progress.download.server_version, expected_progress.download.server_version);
2✔
6920
    CHECK_EQUAL(progress.upload.client_version, expected_progress.upload.client_version);
2✔
6921
    CHECK_EQUAL(progress.upload.last_integrated_server_version,
2✔
6922
                expected_progress.upload.last_integrated_server_version);
2✔
6923
}
2✔
6924

6925
TEST(Sync_NonIncreasingServerVersions)
6926
{
2✔
6927
    TEST_CLIENT_DB(db);
2✔
6928

6929
    auto& history = get_history(db);
2✔
6930
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6931
    timestamp_type timestamp{1};
2✔
6932
    history.set_local_origin_timestamp_source([&] {
2✔
6933
        return ++timestamp;
2✔
6934
    });
2✔
6935

6936
    auto latest_local_version = [&] {
2✔
6937
        auto tr = db->start_write();
2✔
6938
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6939
        return tr->commit();
2✔
6940
    }();
2✔
6941

6942
    std::vector<Changeset> server_changesets;
2✔
6943
    auto prep_changeset = [&](auto pk_name, auto int_col_val) {
8✔
6944
        Changeset changeset;
8✔
6945
        changeset.version = 10;
8✔
6946
        changeset.last_integrated_remote_version = latest_local_version - 1;
8✔
6947
        changeset.origin_timestamp = ++timestamp;
8✔
6948
        changeset.origin_file_ident = 1;
8✔
6949
        instr::PrimaryKey pk{changeset.intern_string(pk_name)};
8✔
6950
        auto table_name = changeset.intern_string("foo");
8✔
6951
        auto col_name = changeset.intern_string("int_col");
8✔
6952
        instr::EraseObject erase_1;
8✔
6953
        erase_1.object = pk;
8✔
6954
        erase_1.table = table_name;
8✔
6955
        changeset.push_back(erase_1);
8✔
6956
        instr::CreateObject create_1;
8✔
6957
        create_1.object = pk;
8✔
6958
        create_1.table = table_name;
8✔
6959
        changeset.push_back(create_1);
8✔
6960
        instr::Update update_1;
8✔
6961
        update_1.table = table_name;
8✔
6962
        update_1.object = pk;
8✔
6963
        update_1.field = col_name;
8✔
6964
        update_1.value = instr::Payload{int64_t(int_col_val)};
8✔
6965
        changeset.push_back(update_1);
8✔
6966
        server_changesets.push_back(std::move(changeset));
8✔
6967
    };
8✔
6968
    prep_changeset("bizz", 1);
2✔
6969
    prep_changeset("buzz", 2);
2✔
6970
    prep_changeset("baz", 3);
2✔
6971
    prep_changeset("bar", 4);
2✔
6972
    ++server_changesets.back().version;
2✔
6973

6974
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6975
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6976
    for (const auto& changeset : server_changesets) {
8✔
6977
        encoded.emplace_back();
8✔
6978
        encode_changeset(changeset, encoded.back());
8✔
6979
        server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
8✔
6980
                                               BinaryData(encoded.back().data(), encoded.back().size()),
8✔
6981
                                               changeset.origin_timestamp, changeset.origin_file_ident);
8✔
6982
    }
8✔
6983

6984
    SyncProgress progress = {};
2✔
6985
    progress.download.server_version = server_changesets.back().version;
2✔
6986
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6987
    progress.latest_server_version.version = server_changesets.back().version;
2✔
6988
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6989

6990
    uint_fast64_t downloadable_bytes = 0;
2✔
6991
    VersionInfo version_info;
2✔
6992
    auto transact = db->start_read();
2✔
6993
    history.integrate_server_changesets(progress, downloadable_bytes, server_changesets_encoded, version_info,
2✔
6994
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6995
}
2✔
6996

6997
TEST(Sync_DanglingLinksCountInPriorSize)
6998
{
2✔
6999
    SHARED_GROUP_TEST_PATH(path);
2✔
7000
    ClientReplication repl;
2✔
7001
    auto local_db = realm::DB::create(repl, path);
2✔
7002
    auto& history = repl.get_history();
2✔
7003
    history.set_client_file_ident(sync::SaltedFileIdent{1, 123456}, true);
2✔
7004

7005
    version_type last_version, last_version_observed = 0;
2✔
7006
    auto dump_uploadable = [&] {
4✔
7007
        UploadCursor upload_cursor{last_version_observed, 0};
4✔
7008
        std::vector<sync::ClientHistory::UploadChangeset> changesets_to_upload;
4✔
7009
        version_type locked_server_version = 0;
4✔
7010
        history.find_uploadable_changesets(upload_cursor, last_version, changesets_to_upload, locked_server_version);
4✔
7011
        CHECK_EQUAL(changesets_to_upload.size(), static_cast<size_t>(1));
4✔
7012
        realm::sync::Changeset parsed_changeset;
4✔
7013
        auto unparsed_changeset = changesets_to_upload[0].changeset.get_first_chunk();
4✔
7014
        realm::util::SimpleInputStream changeset_stream(unparsed_changeset);
4✔
7015
        realm::sync::parse_changeset(changeset_stream, parsed_changeset);
4✔
7016
        test_context.logger->info("changeset at version %1: %2", last_version, parsed_changeset);
4✔
7017
        last_version_observed = last_version;
4✔
7018
        return parsed_changeset;
4✔
7019
    };
4✔
7020

7021
    TableKey source_table_key, target_table_key;
2✔
7022
    {
2✔
7023
        auto wt = local_db->start_write();
2✔
7024
        auto source_table = wt->add_table_with_primary_key("class_source", type_String, "_id");
2✔
7025
        auto target_table = wt->add_table_with_primary_key("class_target", type_String, "_id");
2✔
7026
        source_table->add_column_list(*target_table, "links");
2✔
7027

7028
        source_table_key = source_table->get_key();
2✔
7029
        target_table_key = target_table->get_key();
2✔
7030

7031
        auto obj_to_keep = target_table->create_object_with_primary_key(std::string{"target1"});
2✔
7032
        auto obj_to_delete = target_table->create_object_with_primary_key(std::string{"target2"});
2✔
7033
        auto source_obj = source_table->create_object_with_primary_key(std::string{"source"});
2✔
7034

7035
        auto links_list = source_obj.get_linklist("links");
2✔
7036
        links_list.add(obj_to_keep.get_key());
2✔
7037
        links_list.add(obj_to_delete.get_key());
2✔
7038
        last_version = wt->commit();
2✔
7039
    }
2✔
7040

7041
    dump_uploadable();
2✔
7042

7043
    {
2✔
7044
        // Simulate removing the object via the sync client so we get a dangling link
7045
        TempShortCircuitReplication disable_repl(repl);
2✔
7046
        auto wt = local_db->start_write();
2✔
7047
        auto target_table = wt->get_table(target_table_key);
2✔
7048
        auto obj = target_table->get_object_with_primary_key(std::string{"target2"});
2✔
7049
        obj.invalidate();
2✔
7050
        last_version = wt->commit();
2✔
7051
    }
2✔
7052

7053
    {
2✔
7054
        auto wt = local_db->start_write();
2✔
7055
        auto source_table = wt->get_table(source_table_key);
2✔
7056
        auto target_table = wt->get_table(target_table_key);
2✔
7057

7058
        auto obj_to_add = target_table->create_object_with_primary_key(std::string{"target3"});
2✔
7059

7060
        auto source_obj = source_table->get_object_with_primary_key(std::string{"source"});
2✔
7061
        auto links_list = source_obj.get_linklist("links");
2✔
7062
        links_list.add(obj_to_add.get_key());
2✔
7063
        last_version = wt->commit();
2✔
7064
    }
2✔
7065

7066
    auto changeset = dump_uploadable();
2✔
7067
    CHECK_EQUAL(changeset.size(), static_cast<size_t>(2));
2✔
7068
    auto changeset_it = changeset.end();
2✔
7069
    --changeset_it;
2✔
7070
    auto last_instr = *changeset_it;
2✔
7071
    CHECK_EQUAL(last_instr->type(), Instruction::Type::ArrayInsert);
2✔
7072
    auto arr_insert_instr = last_instr->get_as<Instruction::ArrayInsert>();
2✔
7073
    CHECK_EQUAL(changeset.get_string(arr_insert_instr.table), StringData("source"));
2✔
7074
    CHECK(arr_insert_instr.value.type == sync::instr::Payload::Type::Link);
2✔
7075
    CHECK_EQUAL(changeset.get_string(mpark::get<InternString>(arr_insert_instr.value.data.link.target)),
2✔
7076
                StringData("target3"));
2✔
7077
    CHECK_EQUAL(arr_insert_instr.prior_size, 2);
2✔
7078
}
2✔
7079

7080
// This test calls row_for_object_id() for various object ids and tests that
7081
// the right value is returned including that no assertions are hit.
7082
TEST(Sync_RowForGlobalKey)
7083
{
2✔
7084
    TEST_CLIENT_DB(db);
2✔
7085

7086
    {
2✔
7087
        WriteTransaction wt(db);
2✔
7088
        TableRef table = wt.add_table("class_foo");
2✔
7089
        table->add_column(type_Int, "i");
2✔
7090
        wt.commit();
2✔
7091
    }
2✔
7092

7093
    // Check that various object_ids are not in the table.
7094
    {
2✔
7095
        ReadTransaction rt(db);
2✔
7096
        ConstTableRef table = rt.get_table("class_foo");
2✔
7097
        CHECK(table);
2✔
7098

7099
        // Default constructed GlobalKey
7100
        {
2✔
7101
            GlobalKey object_id;
2✔
7102
            auto row_ndx = table->get_objkey(object_id);
2✔
7103
            CHECK_NOT(row_ndx);
2✔
7104
        }
2✔
7105

7106
        // GlobalKey with small lo and hi values
7107
        {
2✔
7108
            GlobalKey object_id{12, 24};
2✔
7109
            auto row_ndx = table->get_objkey(object_id);
2✔
7110
            CHECK_NOT(row_ndx);
2✔
7111
        }
2✔
7112

7113
        // GlobalKey with lo and hi values past the 32 bit limit.
7114
        {
2✔
7115
            GlobalKey object_id{uint_fast64_t(1) << 50, uint_fast64_t(1) << 52};
2✔
7116
            auto row_ndx = table->get_objkey(object_id);
2✔
7117
            CHECK_NOT(row_ndx);
2✔
7118
        }
2✔
7119
    }
2✔
7120
}
2✔
7121

7122
TEST(Sync_FirstPromoteToWriteAdvancesRead)
7123
{
2✔
7124
    TEST_CLIENT_DB(db);
2✔
7125
    auto db2 = DB::create(make_client_replication(), db_path);
2✔
7126
    auto read = db->start_read();
2✔
7127
    db2->start_write()->commit();
2✔
7128
    // This will hit `ClientHistory::update_from_ref_and_version()` with m_group
7129
    // unset since it's advancing the read transaction without ever having been
7130
    // in a write transaction before.
7131
    read->promote_to_write();
2✔
7132
}
2✔
7133

7134

7135
} // unnamed namespace
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc