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

realm / realm-core / james.stone_543

10 May 2024 08:59PM UTC coverage: 90.808% (-0.03%) from 90.837%
james.stone_543

Pull #7689

Evergreen

ironage
fix a test on windows
Pull Request #7689: RNET-1141 multiprocess encryption for writers with different page sizes

102068 of 181122 branches covered (56.35%)

202 of 223 new or added lines in 3 files covered. (90.58%)

119 existing lines in 13 files now uncovered.

214742 of 236479 relevant lines covered (90.81%)

5817458.76 hits per line

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

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

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

46
#include "sync_fixtures.hpp"
47

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

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

59

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

89

90
namespace {
91

92
using ErrorInfo = SessionErrorInfo;
93

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

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

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

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

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

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

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

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

143
    int nerrors = 0;
2✔
144

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

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

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

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

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

174

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

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

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

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

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

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

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

213

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

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

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

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

237

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

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

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

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

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

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

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

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

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

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

299

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

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

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

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

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

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

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

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

348

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

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

373

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

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

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

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

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

409

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

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

431

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

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

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

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

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

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

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

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

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

483

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

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

508

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

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

529

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

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

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

558

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

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

576
        fixture.start();
2✔
577

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

592

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

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

622

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

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

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

648

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

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

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

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

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

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

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

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

696

697
TEST(Sync_Merge)
698
{
2✔
699

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

814

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

833

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

853

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

872

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

887

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

902

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

918

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

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

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

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

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

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

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

958

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

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

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

993

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

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

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

1026

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

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

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

1047

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

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

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

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

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

1088

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1320

1321
    // Check the end result.
1322

1323
    size_t size_link_list_a;
1324
    size_t size_link_list_b;
1325

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

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

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

1350

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1542

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

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

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

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

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

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

1602

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

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

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

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

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

1644

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

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

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

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

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

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

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

1673
    server.stop();
2✔
1674

1675
    server_thread.join();
2✔
1676

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

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

1683

1684
namespace {
1685

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

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

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

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

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

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

1745
} // namespace
1746

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

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

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

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

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

1771
    network::Service service;
2✔
1772

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

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

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

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

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

1786
    service.run();
2✔
1787

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

1792

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

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

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

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

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

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

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

1852

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

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

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

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

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

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

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

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

1927

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

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

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

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

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

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

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

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

1993

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

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

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

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

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

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

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

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

2074

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

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

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

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

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

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

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

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

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

2235

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

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

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

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

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

2293

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

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

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

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

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

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

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

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

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

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

2355

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

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

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

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

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

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

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

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

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

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

2419

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

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

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

2439
    TEST_CLIENT_DB(db_upload);
2✔
2440

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

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

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

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

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

2488

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

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

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

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

2509

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

2516

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

2525

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

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

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

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

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

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

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

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

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

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

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

2589

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

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

2603

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

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

2625

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

2630
    bool did_see_error_for_valid = false;
2✔
2631

2632
    TEST_DIR(server_dir);
2✔
2633

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

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

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

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

2652

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

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

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

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

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

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

2682

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

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

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

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

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

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

2718

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

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

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

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

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

2748

2749
#if REALM_HAVE_SECURE_TRANSPORT
2750

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

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

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

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

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

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

2778
#endif // REALM_HAVE_SECURE_TRANSPORT
2779

2780

2781
#if REALM_HAVE_OPENSSL
2782

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

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

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

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

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

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

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

2820

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

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

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

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

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

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

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

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

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

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

2877

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

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

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

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

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

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

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

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

2928

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

2937
    TEST_CLIENT_DB(db);
2938

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

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

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

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

2969
    client.shutdown_and_wait();
2970
}
2971

2972
#endif // REALM_HAVE_OPENSSL
2973

2974

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

2989
    std::atomic<uint_fast64_t> downloaded_bytes;
2✔
2990
    std::atomic<uint_fast64_t> downloadable_bytes;
2✔
2991
    std::atomic<uint_fast64_t> uploaded_bytes;
2✔
2992
    std::atomic<uint_fast64_t> uploadable_bytes;
2✔
2993
    std::atomic<uint_fast64_t> snapshot_version;
2✔
2994
    {
2✔
2995
        int handler_entry = 0;
2✔
2996

2997
        bool cond_var_signaled = false;
2✔
2998
        std::mutex mutex;
2✔
2999
        std::condition_variable cond_var;
2✔
3000

3001
        ClientServerFixture fixture(server_dir, test_context);
2✔
3002
        fixture.start();
2✔
3003

3004
        Session session = fixture.make_session(db, "/test");
2✔
3005

3006
        auto progress_handler = [&](uint_fast64_t downloaded, uint_fast64_t downloadable, uint_fast64_t uploaded,
2✔
3007
                                    uint_fast64_t uploadable, uint_fast64_t snapshot, double, double) {
8✔
3008
            downloaded_bytes = downloaded;
8✔
3009
            downloadable_bytes = downloadable;
8✔
3010
            uploaded_bytes = uploaded;
8✔
3011
            uploadable_bytes = uploadable;
8✔
3012
            snapshot_version = snapshot;
8✔
3013
            ++handler_entry;
8✔
3014
        };
8✔
3015

3016
        std::unique_lock<std::mutex> lock(mutex);
2✔
3017
        session.set_progress_handler(progress_handler);
2✔
3018
        session.set_connection_state_change_listener([&](ConnectionState state, util::Optional<ErrorInfo>) {
4✔
3019
            if (state == ConnectionState::connected) {
4✔
3020
                std::unique_lock<std::mutex> lock(mutex);
2✔
3021
                cond_var_signaled = true;
2✔
3022
                lock.unlock();
2✔
3023
                cond_var.notify_one();
2✔
3024
            }
2✔
3025
        });
4✔
3026
        session.bind();
2✔
3027
        cond_var.wait(lock, [&] {
4✔
3028
            return cond_var_signaled;
4✔
3029
        });
4✔
3030
        CHECK_EQUAL(handler_entry, 0);
2✔
3031

3032
        auto commit_version = write_transaction(db, [](WriteTransaction& wt) {
2✔
3033
            wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3034
        });
2✔
3035

3036
        session.wait_for_upload_complete_or_client_stopped();
2✔
3037
        session.wait_for_download_complete_or_client_stopped();
2✔
3038

3039
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3040
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3041
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3042
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3043
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
3044

3045

3046
        commit_version = write_transaction(db, [](WriteTransaction& wt) {
2✔
3047
            wt.get_table("class_table")->create_object_with_primary_key(1);
2✔
3048
        });
2✔
3049

3050
        session.wait_for_upload_complete_or_client_stopped();
2✔
3051
        session.wait_for_download_complete_or_client_stopped();
2✔
3052

3053
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3054
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3055
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3056
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3057
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
3058
    }
2✔
3059

3060
    {
2✔
3061
        // Here we check that the progress handler is called
3062
        // after the session is bound, and that the values
3063
        // are the ones stored in the Realm in the previous
3064
        // session.
3065

3066
        bool cond_var_signaled = false;
2✔
3067
        std::mutex mutex;
2✔
3068
        std::condition_variable cond_var;
2✔
3069

3070
        ClientServerFixture fixture(server_dir, test_context);
2✔
3071
        fixture.start();
2✔
3072
        Session session = fixture.make_session(db, "/test");
2✔
3073

3074
        int number_of_handler_calls = 0;
2✔
3075

3076
        auto progress_handler = [&](uint_fast64_t downloaded, uint_fast64_t downloadable, uint_fast64_t uploaded,
2✔
3077
                                    uint_fast64_t uploadable, uint_fast64_t snapshot, double, double) {
2✔
3078
            CHECK_EQUAL(downloaded, downloaded_bytes);
2✔
3079
            CHECK_EQUAL(downloadable, downloaded_bytes);
2✔
3080
            CHECK_EQUAL(uploaded, uploaded_bytes);
2✔
3081
            CHECK_GREATER(uploadable, uploaded_bytes);
2✔
3082
            CHECK_GREATER(snapshot, snapshot_version);
2✔
3083
            number_of_handler_calls++;
2✔
3084

3085
            std::unique_lock<std::mutex> lock(mutex);
2✔
3086
            cond_var_signaled = true;
2✔
3087
            lock.unlock();
2✔
3088
            cond_var.notify_one();
2✔
3089
        };
2✔
3090

3091
        std::unique_lock<std::mutex> lock(mutex);
2✔
3092
        session.set_progress_handler(progress_handler);
2✔
3093
        session.bind();
2✔
3094
        write_transaction(db, [](WriteTransaction& wt) {
2✔
3095
            wt.get_table("class_table")->create_object_with_primary_key(2);
2✔
3096
        });
2✔
3097
        cond_var.wait(lock, [&] {
4✔
3098
            return cond_var_signaled;
4✔
3099
        });
4✔
3100

3101
        CHECK_EQUAL(number_of_handler_calls, 1);
2✔
3102
    }
2✔
3103
}
2✔
3104

3105

3106
// This test creates one server and a client with
3107
// two sessions that synchronizes with the same server Realm.
3108
// The clients generate changesets, uploads and downloads, and
3109
// waits for upload/download completion. Both sessions have a
3110
// progress handler registered, and it is checked that the
3111
// progress handlers report the correct values.
3112
TEST(Sync_UploadDownloadProgress_2)
3113
{
2✔
3114
    TEST_DIR(server_dir);
2✔
3115
    TEST_CLIENT_DB(db_1);
2✔
3116
    TEST_CLIENT_DB(db_2);
2✔
3117

3118
    ClientServerFixture fixture(server_dir, test_context);
2✔
3119
    fixture.start();
2✔
3120

3121
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
3122
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
3123

3124
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3125
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3126
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3127
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3128
    uint_fast64_t snapshot_version_1 = 0;
2✔
3129

3130
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3131
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3132
                                  uint_fast64_t snapshot_version, double, double) {
22✔
3133
        downloaded_bytes_1 = downloaded_bytes;
22✔
3134
        downloadable_bytes_1 = downloadable_bytes;
22✔
3135
        uploaded_bytes_1 = uploaded_bytes;
22✔
3136
        uploadable_bytes_1 = uploadable_bytes;
22✔
3137
        snapshot_version_1 = snapshot_version;
22✔
3138
    };
22✔
3139

3140
    session_1.set_progress_handler(progress_handler_1);
2✔
3141

3142
    uint_fast64_t downloaded_bytes_2 = 123;
2✔
3143
    uint_fast64_t downloadable_bytes_2 = 123;
2✔
3144
    uint_fast64_t uploaded_bytes_2 = 123;
2✔
3145
    uint_fast64_t uploadable_bytes_2 = 123;
2✔
3146
    uint_fast64_t snapshot_version_2 = 0;
2✔
3147

3148
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3149
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3150
                                  uint_fast64_t snapshot_version, double, double) {
23✔
3151
        downloaded_bytes_2 = downloaded_bytes;
23✔
3152
        downloadable_bytes_2 = downloadable_bytes;
23✔
3153
        uploaded_bytes_2 = uploaded_bytes;
23✔
3154
        uploadable_bytes_2 = uploadable_bytes;
23✔
3155
        snapshot_version_2 = snapshot_version;
23✔
3156
    };
23✔
3157

3158
    session_2.set_progress_handler(progress_handler_2);
2✔
3159

3160
    session_1.bind();
2✔
3161
    session_2.bind();
2✔
3162

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

3168
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3169
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3170
    CHECK_EQUAL(downloaded_bytes_1, downloaded_bytes_2);
2✔
3171
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3172
    CHECK_GREATER(snapshot_version_1, 0);
2✔
3173

3174
    CHECK_EQUAL(uploaded_bytes_1, 0);
2✔
3175
    CHECK_EQUAL(uploadable_bytes_1, 0);
2✔
3176

3177
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3178
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3179
    CHECK_GREATER(snapshot_version_2, 0);
2✔
3180

3181
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3182
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3183
        tr->add_column(type_Int, "integer column");
2✔
3184
    });
2✔
3185

3186
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3187
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3188
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3189
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3190

3191
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3192
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3193

3194
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3195
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3196

3197
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3198
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3199

3200
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3201
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3202

3203
    CHECK_GREATER(snapshot_version_1, 1);
2✔
3204
    CHECK_GREATER(snapshot_version_2, 1);
2✔
3205

3206
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3207
        TableRef tr = wt.get_table("class_table");
2✔
3208
        tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3209
    });
2✔
3210

3211
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
3212
        TableRef tr = wt.get_table("class_table");
2✔
3213
        tr->create_object_with_primary_key(2).set("integer column", 44);
2✔
3214
    });
2✔
3215

3216
    write_transaction(db_2, [](WriteTransaction& wt) {
2✔
3217
        TableRef tr = wt.get_table("class_table");
2✔
3218
        tr->create_object_with_primary_key(3).set("integer column", 43);
2✔
3219
    });
2✔
3220

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

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

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

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

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

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

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

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

3251
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3252
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3253
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3254
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3255

3256
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3257

3258
    // uncertainty due to merge
3259
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3260

3261
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3262
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3263

3264
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3265
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3266

3267
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3268
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3269

3270
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3271
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3272

3273
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3274
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3275

3276
    // Check convergence.
3277
    {
2✔
3278
        ReadTransaction rt_1(db_1);
2✔
3279
        ReadTransaction rt_2(db_2);
2✔
3280
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
3281
    }
2✔
3282
}
2✔
3283

3284

3285
// This test creates a server and a client. Initially, the server is not running.
3286
// The client generates changes and binds a session. It is verified that the
3287
// progress_handler() is called and that the four arguments of progress_handler()
3288
// have the correct values. The server is started in the first call to
3289
// progress_handler() and it is checked that after upload and download completion,
3290
// the upload_progress_handler has been called again, and that the four arguments
3291
// have the correct values. After this, the server is stopped and the client produces
3292
// more changes. It is checked that the progress_handler() is called and that the
3293
// final values are correct.
3294
TEST(Sync_UploadDownloadProgress_3)
3295
{
2✔
3296
    TEST_DIR(server_dir);
2✔
3297
    TEST_CLIENT_DB(db);
2✔
3298

3299
    std::string server_address = "localhost";
2✔
3300

3301
    Server::Config server_config;
2✔
3302
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3303
    server_config.listen_address = server_address;
2✔
3304
    server_config.listen_port = "";
2✔
3305
    server_config.tcp_no_delay = true;
2✔
3306

3307
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3308
    Server server(server_dir, std::move(public_key), server_config);
2✔
3309
    server.start();
2✔
3310
    auto server_port = server.listen_endpoint().port();
2✔
3311

3312
    ThreadWrapper server_thread;
2✔
3313

3314
    // The server is not running.
3315

3316
    {
2✔
3317
        WriteTransaction wt{db};
2✔
3318
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3319
        tr->add_column(type_Int, "integer column");
2✔
3320
        wt.commit();
2✔
3321
    }
2✔
3322

3323
    Client::Config client_config;
2✔
3324
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3325
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3326
    client_config.socket_provider = socket_provider;
2✔
3327
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3328
    Client client(client_config);
2✔
3329

3330
    // when connecting to the C++ server, use URL prefix:
3331
    Session::Config config;
2✔
3332
    config.service_identifier = "/realm-sync";
2✔
3333
    config.server_address = server_address;
2✔
3334
    config.signed_user_token = g_signed_test_user_token;
2✔
3335
    config.server_port = server_port;
2✔
3336
    config.realm_identifier = "/test";
2✔
3337

3338
    Session session(client, db, nullptr, nullptr, std::move(config));
2✔
3339

3340
    // entry is used to count the number of calls to
3341
    // progress_handler. At the first call, the server is
3342
    // not running, and it is started by progress_handler().
3343

3344
    bool should_signal_cond_var = false;
2✔
3345
    auto signal_pf = util::make_promise_future<void>();
2✔
3346

3347
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3348
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3349
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3350
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3351
    uint_fast64_t snapshot_version_1 = 0;
2✔
3352

3353
    auto progress_handler = [&, entry = int(0), promise = util::CopyablePromiseHolder(std::move(signal_pf.promise))](
2✔
3354
                                uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3355
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3356
                                uint_fast64_t snapshot_version, double, double) mutable {
6✔
3357
        downloaded_bytes_1 = downloaded_bytes;
6✔
3358
        downloadable_bytes_1 = downloadable_bytes;
6✔
3359
        uploaded_bytes_1 = uploaded_bytes;
6✔
3360
        uploadable_bytes_1 = uploadable_bytes;
6✔
3361
        snapshot_version_1 = snapshot_version;
6✔
3362

3363
        if (entry == 0) {
6✔
3364
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3365
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3366
            CHECK_EQUAL(uploaded_bytes, 0);
2✔
3367
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3368
            CHECK_EQUAL(snapshot_version, 4);
2✔
3369
        }
2✔
3370

3371
        if (should_signal_cond_var) {
6✔
3372
            promise.get_promise().emplace_value();
2✔
3373
        }
2✔
3374

3375
        entry++;
6✔
3376
    };
6✔
3377

3378
    session.set_progress_handler(progress_handler);
2✔
3379

3380
    server_thread.start([&] {
2✔
3381
        server.run();
2✔
3382
    });
2✔
3383

3384
    session.bind();
2✔
3385

3386
    session.wait_for_upload_complete_or_client_stopped();
2✔
3387
    session.wait_for_download_complete_or_client_stopped();
2✔
3388

3389
    // Now the server is running.
3390

3391
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3392
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3393
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3394
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3395
    CHECK_GREATER_EQUAL(snapshot_version_1, 2);
2✔
3396

3397
    server.stop();
2✔
3398

3399
    // The server is stopped
3400

3401
    should_signal_cond_var = true;
2✔
3402

3403
    uint_fast64_t commited_version;
2✔
3404
    {
2✔
3405
        WriteTransaction wt{db};
2✔
3406
        TableRef tr = wt.get_table("class_table");
2✔
3407
        tr->create_object_with_primary_key(123).set("integer column", 42);
2✔
3408
        commited_version = wt.commit();
2✔
3409
    }
2✔
3410

3411
    signal_pf.future.get();
2✔
3412

3413
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3414
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3415
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3416
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3417
    CHECK_EQUAL(snapshot_version_1, commited_version);
2✔
3418

3419
    server_thread.join();
2✔
3420
}
2✔
3421

3422

3423
// This test creates a server and two clients. The first client uploads two
3424
// large changesets. The other client downloads them. The download messages to
3425
// the second client contains one changeset because the changesets are larger
3426
// than the soft size limit for changesets in the DOWNLOAD message. This implies
3427
// that after receiving the first DOWNLOAD message, the second client will have
3428
// downloaded_bytes < downloadable_bytes.
3429
TEST(Sync_UploadDownloadProgress_4)
3430
{
2✔
3431
    TEST_DIR(server_dir);
2✔
3432
    TEST_CLIENT_DB(db_1);
2✔
3433
    TEST_CLIENT_DB(db_2);
2✔
3434

3435
    {
2✔
3436
        WriteTransaction wt{db_1};
2✔
3437
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3438
        auto col = tr->add_column(type_Binary, "binary column");
2✔
3439
        tr->create_object_with_primary_key(1);
2✔
3440
        std::string str(size_t(5e5), 'a');
2✔
3441
        BinaryData bd(str.data(), str.size());
2✔
3442
        tr->begin()->set(col, bd);
2✔
3443
        wt.commit();
2✔
3444
    }
2✔
3445

3446
    {
2✔
3447
        WriteTransaction wt{db_1};
2✔
3448
        TableRef tr = wt.get_table("class_table");
2✔
3449
        auto col = tr->get_column_key("binary column");
2✔
3450
        tr->create_object_with_primary_key(2);
2✔
3451
        std::string str(size_t(1e6), 'a');
2✔
3452
        BinaryData bd(str.data(), str.size());
2✔
3453
        tr->begin()->set(col, bd);
2✔
3454
        wt.commit();
2✔
3455
    }
2✔
3456

3457
    ClientServerFixture::Config config;
2✔
3458
    config.max_download_size = size_t(1e5);
2✔
3459
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3460
    fixture.start();
2✔
3461

3462
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
3463

3464
    int entry_1 = 0;
2✔
3465

3466
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3467
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3468
                                  uint_fast64_t snapshot_version, double, double) {
6✔
3469
        CHECK_EQUAL(downloaded_bytes, 0);
6✔
3470
        CHECK_EQUAL(downloadable_bytes, 0);
6✔
3471
        CHECK_NOT_EQUAL(uploadable_bytes, 0);
6✔
3472

3473
        switch (entry_1) {
6✔
3474
            case 0:
2✔
3475
                // We've received the empty DOWNLOAD message and now have reliable
3476
                // download progress
3477
                CHECK_EQUAL(uploaded_bytes, 0);
2✔
3478
                CHECK_EQUAL(snapshot_version, 5);
2✔
3479
                break;
2✔
3480

3481
            case 1:
2✔
3482
                // First UPLOAD is complete, but we still have more to upload
3483
                // because the changesets are too large to batch into a single upload
3484
                CHECK_GREATER(uploaded_bytes, 0);
2✔
3485
                CHECK_LESS(uploaded_bytes, uploadable_bytes);
2✔
3486
                CHECK_EQUAL(snapshot_version, 6);
2✔
3487
                break;
2✔
3488

3489
            case 2:
2✔
3490
                // Second UPLOAD is complete and we're done uploading
3491
                CHECK_EQUAL(uploaded_bytes, uploadable_bytes);
2✔
3492
                CHECK_EQUAL(snapshot_version, 7);
2✔
3493
                break;
2✔
3494
        }
6✔
3495

3496
        ++entry_1;
6✔
3497
    };
6✔
3498

3499
    session_1.set_progress_handler(progress_handler_1);
2✔
3500

3501
    session_1.bind();
2✔
3502

3503
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3504
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3505

3506
    CHECK_EQUAL(entry_1, 3);
2✔
3507

3508
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
3509

3510
    int entry_2 = 0;
2✔
3511

3512
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3513
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3514
                                  uint_fast64_t snapshot_version, double, double) {
4✔
3515
        CHECK_EQUAL(uploaded_bytes, 0);
4✔
3516
        CHECK_EQUAL(uploadable_bytes, 0);
4✔
3517

3518
        switch (entry_2) {
4✔
3519
            case 0:
2✔
3520
                // First DOWNLOAD message received. Some data is downloaded, but
3521
                // download isn't compelte
3522
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3523
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3524
                CHECK_LESS(downloaded_bytes, downloadable_bytes);
2✔
3525
                CHECK_EQUAL(snapshot_version, 3);
2✔
3526
                break;
2✔
3527

3528
            case 1:
2✔
3529
                // Second DOWNLOAD message received. Download is now complete.
3530
                CHECK_GREATER(downloaded_bytes, 0);
2✔
3531
                CHECK_GREATER(downloadable_bytes, 0);
2✔
3532
                CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
3533
                CHECK_EQUAL(snapshot_version, 4);
2✔
3534
                break;
2✔
3535
        }
4✔
3536
        ++entry_2;
4✔
3537
    };
4✔
3538

3539
    session_2.set_progress_handler(progress_handler_2);
2✔
3540

3541
    session_2.bind();
2✔
3542

3543
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3544
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3545
    CHECK_EQUAL(entry_2, 2);
2✔
3546
}
2✔
3547

3548

3549
// This test has a single client connected to a server with one session. The
3550
// client does not create any changesets. The test verifies that the client gets
3551
// a confirmation from the server of downloadable_bytes = 0.
3552
TEST(Sync_UploadDownloadProgress_5)
3553
{
2✔
3554
    TEST_DIR(server_dir);
2✔
3555
    TEST_CLIENT_DB(db);
2✔
3556

3557
    std::mutex mutex;
2✔
3558
    std::condition_variable session_cv;
2✔
3559
    bool signaled = false;
2✔
3560

3561
    ClientServerFixture fixture(server_dir, test_context);
2✔
3562
    fixture.start();
2✔
3563

3564
    Session session = fixture.make_session(db, "/test");
2✔
3565

3566
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3567
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3568
                                uint_fast64_t snapshot_version, double, double) mutable {
2✔
3569
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3570
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3571
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3572
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3573
        CHECK_EQUAL(snapshot_version, 3);
2✔
3574
        std::lock_guard lock{mutex};
2✔
3575
        signaled = true;
2✔
3576
        session_cv.notify_one();
2✔
3577
    };
2✔
3578

3579
    session.set_progress_handler(progress_handler);
2✔
3580

3581
    {
2✔
3582
        std::unique_lock lock{mutex};
2✔
3583
        session.bind();
2✔
3584
        // Wait until the progress handler is called on the session before tearing down the client
3585
        session_cv.wait_for(lock, std::chrono::seconds(5), [&]() {
4✔
3586
            return signaled;
4✔
3587
        });
4✔
3588
    }
2✔
3589
    CHECK(signaled);
2✔
3590

3591
    // The check is that we reach this point.
3592
}
2✔
3593

3594

3595
// This test has a single client connected to a server with one session.
3596
// The session has a registered progress handler.
3597
TEST(Sync_UploadDownloadProgress_6)
3598
{
2✔
3599
    TEST_DIR(server_dir);
2✔
3600
    TEST_CLIENT_DB(db);
2✔
3601

3602
    Server::Config server_config;
2✔
3603
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3604
    server_config.listen_address = "localhost";
2✔
3605
    server_config.listen_port = "";
2✔
3606
    server_config.tcp_no_delay = true;
2✔
3607

3608
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3609
    Server server(server_dir, std::move(public_key), server_config);
2✔
3610
    server.start();
2✔
3611

3612
    auto server_port = server.listen_endpoint().port();
2✔
3613

3614
    ThreadWrapper server_thread;
2✔
3615
    server_thread.start([&] {
2✔
3616
        server.run();
2✔
3617
    });
2✔
3618

3619
    Client::Config client_config;
2✔
3620
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3621
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3622
    client_config.socket_provider = socket_provider;
2✔
3623
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3624
    client_config.one_connection_per_session = false;
2✔
3625
    Client client(client_config);
2✔
3626

3627
    util::ScopeExit cleanup([&]() noexcept {
2✔
3628
        client.shutdown_and_wait();
2✔
3629
        server.stop();
2✔
3630
        server_thread.join();
2✔
3631
    });
2✔
3632

3633
    Session::Config session_config;
2✔
3634
    session_config.server_address = "localhost";
2✔
3635
    session_config.server_port = server_port;
2✔
3636
    session_config.realm_identifier = "/test";
2✔
3637
    session_config.service_identifier = "/realm-sync";
2✔
3638
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3639

3640
    std::mutex mutex;
2✔
3641
    std::condition_variable session_cv;
2✔
3642
    bool signaled = false;
2✔
3643
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3644

3645
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3646
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3647
                                uint_fast64_t snapshot_version, double, double) {
2✔
3648
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3649
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3650
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3651
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3652
        CHECK_EQUAL(snapshot_version, 3);
2✔
3653
        std::lock_guard lock{mutex};
2✔
3654
        session.reset();
2✔
3655
        signaled = true;
2✔
3656
        session_cv.notify_one();
2✔
3657
    };
2✔
3658

3659
    session->set_progress_handler(progress_handler);
2✔
3660

3661
    {
2✔
3662
        std::unique_lock lock{mutex};
2✔
3663
        session->bind();
2✔
3664
        // Wait until the progress handler is called on the session before tearing down the client
3665
        session_cv.wait_for(lock, std::chrono::seconds(5), [&]() {
4✔
3666
            return signaled;
4✔
3667
        });
4✔
3668
    }
2✔
3669
    CHECK(signaled);
2✔
3670
    CHECK(!(session));
2✔
3671

3672
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
3673
    // down the active session
3674
}
2✔
3675

3676
// This test has a single client starting to connect to the server with one session.
3677
// The client is torn down immediately after bind is called on the session.
3678
// The session will still be active and has an unactualized session wrapper when the
3679
// client is torn down, which leads to both calls to finalize_before_actualization() and
3680
// and finalize().
3681
TEST(Sync_UploadDownloadProgress_7)
3682
{
2✔
3683
    TEST_DIR(server_dir);
2✔
3684
    TEST_CLIENT_DB(db);
2✔
3685

3686
    Server::Config server_config;
2✔
3687
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3688
    server_config.listen_address = "localhost";
2✔
3689
    server_config.listen_port = "";
2✔
3690
    server_config.tcp_no_delay = true;
2✔
3691

3692
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3693
    Server server(server_dir, std::move(public_key), server_config);
2✔
3694
    server.start();
2✔
3695

3696
    auto server_port = server.listen_endpoint().port();
2✔
3697

3698
    ThreadWrapper server_thread;
2✔
3699
    server_thread.start([&] {
2✔
3700
        server.run();
2✔
3701
    });
2✔
3702

3703
    Client::Config client_config;
2✔
3704
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3705
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3706
    client_config.socket_provider = socket_provider;
2✔
3707
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3708
    client_config.one_connection_per_session = false;
2✔
3709
    Client client(client_config);
2✔
3710

3711
    Session::Config session_config;
2✔
3712
    session_config.server_address = "localhost";
2✔
3713
    session_config.server_port = server_port;
2✔
3714
    session_config.realm_identifier = "/test";
2✔
3715
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3716

3717
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3718
    session->bind();
2✔
3719

3720
    client.shutdown_and_wait();
2✔
3721
    server.stop();
2✔
3722
    server_thread.join();
2✔
3723

3724
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
3725
    // down the session that is in the process of being created.
3726
}
2✔
3727

3728
TEST(Sync_UploadProgress_EmptyCommits)
3729
{
2✔
3730
    TEST_DIR(server_dir);
2✔
3731
    TEST_CLIENT_DB(db);
2✔
3732

3733
    ClientServerFixture fixture(server_dir, test_context);
2✔
3734
    fixture.start();
2✔
3735
    Session session = fixture.make_session(db, "/test");
2✔
3736

3737
    {
2✔
3738
        WriteTransaction wt{db};
2✔
3739
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "_id");
2✔
3740
        wt.commit();
2✔
3741
    }
2✔
3742

3743
    std::atomic<int> entry = 0;
2✔
3744
    session.set_progress_handler(
2✔
3745
        [&](uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, uint_fast64_t, double, double) {
8✔
3746
            ++entry;
8✔
3747
        });
8✔
3748
    session.bind();
2✔
3749

3750
    // Each step calls wait_for_upload_complete twice because upload completion
3751
    // is fired before progress handlers, so we need another hop through the
3752
    // event loop after upload completion to know that the handler has been called
3753
    session.wait_for_upload_complete_or_client_stopped();
2✔
3754
    session.wait_for_upload_complete_or_client_stopped();
2✔
3755

3756
    // Binding produces two notifications: one after receiving
3757
    // the DOWNLOAD message, and one after uploading the schema
3758
    CHECK_EQUAL(entry, 2);
2✔
3759

3760
    // No notification sent because an empty commit doesn't change uploadable_bytes
3761
    {
2✔
3762
        WriteTransaction wt{db};
2✔
3763
        wt.commit();
2✔
3764
    }
2✔
3765
    session.wait_for_upload_complete_or_client_stopped();
2✔
3766
    session.wait_for_upload_complete_or_client_stopped();
2✔
3767
    CHECK_EQUAL(entry, 2);
2✔
3768

3769
    // Both the external and local commits are empty, so again no change in
3770
    // uploadable_bytes
3771
    {
2✔
3772
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3773
        WriteTransaction wt{db2};
2✔
3774
        wt.commit();
2✔
3775
        WriteTransaction wt2{db};
2✔
3776
        wt2.commit();
2✔
3777
    }
2✔
3778
    session.wait_for_upload_complete_or_client_stopped();
2✔
3779
    session.wait_for_upload_complete_or_client_stopped();
2✔
3780
    CHECK_EQUAL(entry, 2);
2✔
3781

3782
    // Local commit is empty, but the changeset created by the external write
3783
    // is discovered after the local write, resulting in two notifications (one
3784
    // before uploading and one after).
3785
    {
2✔
3786
        auto db2 = DB::create(make_client_replication(), db_path);
2✔
3787
        WriteTransaction wt{db2};
2✔
3788
        wt.get_table("class_table")->create_object_with_primary_key(0);
2✔
3789
        wt.commit();
2✔
3790
        WriteTransaction wt2{db};
2✔
3791
        wt2.commit();
2✔
3792
    }
2✔
3793
    session.wait_for_upload_complete_or_client_stopped();
2✔
3794
    session.wait_for_upload_complete_or_client_stopped();
2✔
3795
    CHECK_EQUAL(entry, 4);
2✔
3796
}
2✔
3797

3798
TEST(Sync_MultipleSyncAgentsNotAllowed)
3799
{
2✔
3800
    // At most one sync agent is allowed to participate in a Realm file access
3801
    // session at any particular point in time. Note that a Realm file access
3802
    // session is a group of temporally overlapping accesses to a Realm file,
3803
    // and that the group of participants is the transitive closure of a
3804
    // particular session participant over the "temporally overlapping access"
3805
    // relation.
3806

3807
    TEST_CLIENT_DB(db);
2✔
3808
    Client::Config config;
2✔
3809
    config.logger = test_context.logger;
2✔
3810
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(
2✔
3811
        config.logger, "", nullptr, websocket::DefaultSocketProvider::AutoStart{false});
2✔
3812
    config.socket_provider = socket_provider;
2✔
3813
    config.reconnect_mode = ReconnectMode::testing;
2✔
3814
    Client client{config};
2✔
3815
    {
2✔
3816
        Session::Config config_1;
2✔
3817
        config_1.realm_identifier = "blablabla";
2✔
3818
        Session::Config config_2;
2✔
3819
        config_2.realm_identifier = config_1.realm_identifier;
2✔
3820
        Session session_1{client, db, nullptr, nullptr, std::move(config_1)};
2✔
3821
        Session session_2{client, db, nullptr, nullptr, std::move(config_2)};
2✔
3822
        session_1.bind();
2✔
3823
        session_2.bind();
2✔
3824
        CHECK_THROW(
2✔
3825
            websocket::DefaultSocketProvider::OnlyForTesting::run_event_loop_on_current_thread(socket_provider.get()),
2✔
3826
            MultipleSyncAgents);
2✔
3827
        websocket::DefaultSocketProvider::OnlyForTesting::prep_event_loop_for_restart(socket_provider.get());
2✔
3828
    }
2✔
3829

3830
    socket_provider->start();
2✔
3831
}
2✔
3832

3833
TEST(Sync_CancelReconnectDelay)
3834
{
2✔
3835
    TEST_DIR(server_dir);
2✔
3836
    TEST_CLIENT_DB(db);
2✔
3837
    TEST_CLIENT_DB(db_x);
2✔
3838

3839
    ClientServerFixture::Config fixture_config;
2✔
3840
    fixture_config.one_connection_per_session = false;
2✔
3841

3842
    // After connection-level error, and at session-level.
3843
    {
2✔
3844
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3845
        fixture.start();
2✔
3846

3847
        BowlOfStonesSemaphore bowl;
2✔
3848
        auto handler = [&](const SessionErrorInfo& info) {
2✔
3849
            if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3850
                bowl.add_stone();
2✔
3851
        };
2✔
3852
        Session session = fixture.make_session(db, "/test");
2✔
3853
        session.set_error_handler(std::move(handler));
2✔
3854
        session.bind();
2✔
3855
        session.wait_for_download_complete_or_client_stopped();
2✔
3856
        fixture.close_server_side_connections();
2✔
3857
        bowl.get_stone();
2✔
3858

3859
        session.cancel_reconnect_delay();
2✔
3860
        session.wait_for_download_complete_or_client_stopped();
2✔
3861
    }
2✔
3862

3863
    // After connection-level error, and at client-level while connection
3864
    // object exists (ConnectionImpl in clinet.cpp).
3865
    {
2✔
3866
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3867
        fixture.start();
2✔
3868

3869
        BowlOfStonesSemaphore bowl;
2✔
3870
        auto handler = [&](const SessionErrorInfo& info) {
2✔
3871
            if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3872
                bowl.add_stone();
2✔
3873
        };
2✔
3874
        Session session = fixture.make_session(db, "/test");
2✔
3875
        session.set_error_handler(std::move(handler));
2✔
3876
        session.bind();
2✔
3877
        session.wait_for_download_complete_or_client_stopped();
2✔
3878
        fixture.close_server_side_connections();
2✔
3879
        bowl.get_stone();
2✔
3880

3881
        fixture.cancel_reconnect_delay();
2✔
3882
        session.wait_for_download_complete_or_client_stopped();
2✔
3883
    }
2✔
3884

3885
    // After connection-level error, and at client-level while connection object
3886
    // does not exist (ConnectionImpl in clinet.cpp).
3887
    {
2✔
3888
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3889
        fixture.start();
2✔
3890

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

3905
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3906
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3907
        // The connection object no longer exists at this time. After the first
3908
        // of the two waits above, the invocation of ConnectionImpl::on_idle()
3909
        // (in client.cpp) has been scheduled. After the second wait, it has
3910
        // been called, and that destroys the connection object.
3911

3912
        fixture.cancel_reconnect_delay();
2✔
3913
        {
2✔
3914
            Session session = fixture.make_bound_session(db, "/test");
2✔
3915
            session.wait_for_download_complete_or_client_stopped();
2✔
3916
        }
2✔
3917
    }
2✔
3918

3919
    // After session-level error, and at session-level.
3920
    {
2✔
3921
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3922
        fixture.start();
2✔
3923

3924
        // Add a session for the purpose of keeping the connection open
3925
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3926
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3927

3928
        BowlOfStonesSemaphore bowl;
2✔
3929
        auto handler = [&](const SessionErrorInfo& info) {
4✔
3930
            if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue))
4✔
3931
                bowl.add_stone();
4✔
3932
        };
4✔
3933
        Session session = fixture.make_session(db, "/..");
2✔
3934
        session.set_error_handler(std::move(handler));
2✔
3935
        session.bind();
2✔
3936
        bowl.get_stone();
2✔
3937

3938
        session.cancel_reconnect_delay();
2✔
3939
        bowl.get_stone();
2✔
3940
    }
2✔
3941

3942
    // After session-level error, and at client-level.
3943
    {
2✔
3944
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3945
        fixture.start();
2✔
3946

3947
        // Add a session for the purpose of keeping the connection open
3948
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3949
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3950

3951
        BowlOfStonesSemaphore bowl;
2✔
3952
        auto handler = [&](const SessionErrorInfo& info) {
4✔
3953
            if (CHECK_EQUAL(info.status, ErrorCodes::BadSyncPartitionValue))
4✔
3954
                bowl.add_stone();
4✔
3955
        };
4✔
3956
        Session session = fixture.make_session(db, "/..");
2✔
3957
        session.set_error_handler(std::move(handler));
2✔
3958
        session.bind();
2✔
3959
        bowl.get_stone();
2✔
3960

3961
        fixture.cancel_reconnect_delay();
2✔
3962
        bowl.get_stone();
2✔
3963
    }
2✔
3964
}
2✔
3965

3966

3967
#ifndef REALM_PLATFORM_WIN32
3968

3969
// This test checks that it is possible to create, upload, download, and merge
3970
// changesets larger than 16 MB.
3971
//
3972
// Fails with 'bad alloc' around 1 GB mem usage on 32-bit Windows + 32-bit Linux
3973
TEST_IF(Sync_MergeLargeBinary, !(REALM_ARCHITECTURE_X86_32))
3974
{
2✔
3975
    // Two binaries are inserted in each transaction such that the total size
3976
    // of the changeset exceeds 16 MB. A single set_binary operation does not
3977
    // accept a binary larger than 16 MB.
3978
    size_t binary_sizes[] = {
2✔
3979
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e6), static_cast<size_t>(11e6),
2✔
3980
        static_cast<size_t>(6e6), static_cast<size_t>(12e6), static_cast<size_t>(5e6), static_cast<size_t>(13e6),
2✔
3981
    };
2✔
3982

3983
    TEST_CLIENT_DB(db_1);
2✔
3984
    TEST_CLIENT_DB(db_2);
2✔
3985

3986
    {
2✔
3987
        WriteTransaction wt(db_1);
2✔
3988
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
3989
        table->add_column(type_Binary, "column name");
2✔
3990
        std::string str_1(binary_sizes[0], 'a');
2✔
3991
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3992
        std::string str_2(binary_sizes[1], 'b');
2✔
3993
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3994
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
3995
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
3996
        wt.commit();
2✔
3997
    }
2✔
3998

3999
    {
2✔
4000
        WriteTransaction wt(db_1);
2✔
4001
        TableRef table = wt.get_table("class_table name");
2✔
4002
        std::string str_1(binary_sizes[2], 'c');
2✔
4003
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4004
        std::string str_2(binary_sizes[3], 'd');
2✔
4005
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4006
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4007
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4008
        wt.commit();
2✔
4009
    }
2✔
4010

4011
    {
2✔
4012
        WriteTransaction wt(db_2);
2✔
4013
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4014
        table->add_column(type_Binary, "column name");
2✔
4015
        std::string str_1(binary_sizes[4], 'e');
2✔
4016
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4017
        std::string str_2(binary_sizes[5], 'f');
2✔
4018
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4019
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4020
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4021
        wt.commit();
2✔
4022
    }
2✔
4023

4024
    {
2✔
4025
        WriteTransaction wt(db_2);
2✔
4026
        TableRef table = wt.get_table("class_table name");
2✔
4027
        std::string str_1(binary_sizes[6], 'g');
2✔
4028
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4029
        std::string str_2(binary_sizes[7], 'h');
2✔
4030
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4031
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4032
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4033
        wt.commit();
2✔
4034
    }
2✔
4035

4036
    std::uint_fast64_t downloaded_bytes_1 = 0;
2✔
4037
    std::uint_fast64_t downloadable_bytes_1 = 0;
2✔
4038
    std::uint_fast64_t uploaded_bytes_1 = 0;
2✔
4039
    std::uint_fast64_t uploadable_bytes_1 = 0;
2✔
4040

4041
    auto progress_handler_1 = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
4042
                                  std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
4043
                                  std::uint_fast64_t, double, double) {
12✔
4044
        downloaded_bytes_1 = downloaded_bytes;
12✔
4045
        downloadable_bytes_1 = downloadable_bytes;
12✔
4046
        uploaded_bytes_1 = uploaded_bytes;
12✔
4047
        uploadable_bytes_1 = uploadable_bytes;
12✔
4048
    };
12✔
4049

4050
    std::uint_fast64_t downloaded_bytes_2 = 0;
2✔
4051
    std::uint_fast64_t downloadable_bytes_2 = 0;
2✔
4052
    std::uint_fast64_t uploaded_bytes_2 = 0;
2✔
4053
    std::uint_fast64_t uploadable_bytes_2 = 0;
2✔
4054

4055
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4056
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t, double,
2✔
4057
                                  double) {
8✔
4058
        downloaded_bytes_2 = downloaded_bytes;
8✔
4059
        downloadable_bytes_2 = downloadable_bytes;
8✔
4060
        uploaded_bytes_2 = uploaded_bytes;
8✔
4061
        uploadable_bytes_2 = uploadable_bytes;
8✔
4062
    };
8✔
4063

4064
    {
2✔
4065
        TEST_DIR(dir);
2✔
4066
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4067
        fixture.start();
2✔
4068

4069
        {
2✔
4070
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4071
            session_1.set_progress_handler(progress_handler_1);
2✔
4072
            session_1.bind();
2✔
4073
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4074
        }
2✔
4075

4076
        {
2✔
4077
            Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4078
            session_2.set_progress_handler(progress_handler_2);
2✔
4079
            session_2.bind();
2✔
4080
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4081
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4082
        }
2✔
4083

4084
        {
2✔
4085
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4086
            session_1.set_progress_handler(progress_handler_1);
2✔
4087
            session_1.bind();
2✔
4088
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4089
        }
2✔
4090
    }
2✔
4091

4092
    ReadTransaction read_1(db_1);
2✔
4093
    ReadTransaction read_2(db_2);
2✔
4094

4095
    const Group& group = read_1;
2✔
4096
    CHECK(compare_groups(read_1, read_2));
2✔
4097
    ConstTableRef table = group.get_table("class_table name");
2✔
4098
    CHECK_EQUAL(table->size(), 8);
2✔
4099
    {
2✔
4100
        const Obj obj = *table->begin();
2✔
4101
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
4102
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4103
    }
2✔
4104
    {
2✔
4105
        const Obj obj = *(table->begin() + 7);
2✔
4106
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
4107
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2✔
4108
    }
2✔
4109

4110
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4111
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4112
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4113

4114
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4115
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4116
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4117

4118
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4119
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4120
}
2✔
4121

4122

4123
// This test checks that it is possible to create, upload, download, and merge
4124
// changesets larger than 16 MB. This test uses less memory than
4125
// Sync_MergeLargeBinary.
4126
TEST(Sync_MergeLargeBinaryReducedMemory)
4127
{
2✔
4128
    // Two binaries are inserted in a transaction such that the total size
4129
    // of the changeset exceeds 16MB. A single set_binary operation does not
4130
    // accept a binary larger than 16MB. Only one changeset is larger than
4131
    // 16 MB in this test.
4132
    size_t binary_sizes[] = {
2✔
4133
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e4), static_cast<size_t>(11e4),
2✔
4134
        static_cast<size_t>(6e4), static_cast<size_t>(12e4), static_cast<size_t>(5e4), static_cast<size_t>(13e4),
2✔
4135
    };
2✔
4136

4137
    TEST_CLIENT_DB(db_1);
2✔
4138
    TEST_CLIENT_DB(db_2);
2✔
4139

4140
    {
2✔
4141
        WriteTransaction wt(db_1);
2✔
4142
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4143
        table->add_column(type_Binary, "column name");
2✔
4144
        std::string str_1(binary_sizes[0], 'a');
2✔
4145
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4146
        std::string str_2(binary_sizes[1], 'b');
2✔
4147
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4148
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
4149
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
4150
        wt.commit();
2✔
4151
    }
2✔
4152

4153
    {
2✔
4154
        WriteTransaction wt(db_1);
2✔
4155
        TableRef table = wt.get_table("class_table name");
2✔
4156
        std::string str_1(binary_sizes[2], 'c');
2✔
4157
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4158
        std::string str_2(binary_sizes[3], 'd');
2✔
4159
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4160
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4161
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4162
        wt.commit();
2✔
4163
    }
2✔
4164

4165
    {
2✔
4166
        WriteTransaction wt(db_2);
2✔
4167
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4168
        table->add_column(type_Binary, "column name");
2✔
4169
        std::string str_1(binary_sizes[4], 'e');
2✔
4170
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4171
        std::string str_2(binary_sizes[5], 'f');
2✔
4172
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4173
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4174
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4175
        wt.commit();
2✔
4176
    }
2✔
4177

4178
    {
2✔
4179
        WriteTransaction wt(db_2);
2✔
4180
        TableRef table = wt.get_table("class_table name");
2✔
4181
        std::string str_1(binary_sizes[6], 'g');
2✔
4182
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4183
        std::string str_2(binary_sizes[7], 'h');
2✔
4184
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4185
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4186
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4187
        wt.commit();
2✔
4188
    }
2✔
4189

4190
    uint_fast64_t downloaded_bytes_1 = 0;
2✔
4191
    uint_fast64_t downloadable_bytes_1 = 0;
2✔
4192
    uint_fast64_t uploaded_bytes_1 = 0;
2✔
4193
    uint_fast64_t uploadable_bytes_1 = 0;
2✔
4194

4195
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4196
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4197
                                  uint_fast64_t /* snapshot_version */, double, double) {
10✔
4198
        downloaded_bytes_1 = downloaded_bytes;
10✔
4199
        downloadable_bytes_1 = downloadable_bytes;
10✔
4200
        uploaded_bytes_1 = uploaded_bytes;
10✔
4201
        uploadable_bytes_1 = uploadable_bytes;
10✔
4202
    };
10✔
4203

4204
    uint_fast64_t downloaded_bytes_2 = 0;
2✔
4205
    uint_fast64_t downloadable_bytes_2 = 0;
2✔
4206
    uint_fast64_t uploaded_bytes_2 = 0;
2✔
4207
    uint_fast64_t uploadable_bytes_2 = 0;
2✔
4208

4209
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4210
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4211
                                  uint_fast64_t /* snapshot_version */, double, double) {
8✔
4212
        downloaded_bytes_2 = downloaded_bytes;
8✔
4213
        downloadable_bytes_2 = downloadable_bytes;
8✔
4214
        uploaded_bytes_2 = uploaded_bytes;
8✔
4215
        uploadable_bytes_2 = uploadable_bytes;
8✔
4216
    };
8✔
4217

4218
    {
2✔
4219
        TEST_DIR(dir);
2✔
4220
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4221
        fixture.start();
2✔
4222

4223
        {
2✔
4224
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4225
            session_1.set_progress_handler(progress_handler_1);
2✔
4226
            session_1.bind();
2✔
4227
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4228
        }
2✔
4229

4230
        {
2✔
4231
            Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4232
            session_2.set_progress_handler(progress_handler_2);
2✔
4233
            session_2.bind();
2✔
4234
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4235
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4236
        }
2✔
4237

4238
        {
2✔
4239
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4240
            session_1.set_progress_handler(progress_handler_1);
2✔
4241
            session_1.bind();
2✔
4242
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4243
        }
2✔
4244
    }
2✔
4245

4246
    ReadTransaction read_1(db_1);
2✔
4247
    ReadTransaction read_2(db_2);
2✔
4248

4249
    const Group& group = read_1;
2✔
4250
    CHECK(compare_groups(read_1, read_2));
2✔
4251
    ConstTableRef table = group.get_table("class_table name");
2✔
4252
    CHECK_EQUAL(table->size(), 8);
2✔
4253
    {
2✔
4254
        const Obj obj = *table->begin();
2✔
4255
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4256
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2✔
4257
    }
2✔
4258
    {
2✔
4259
        const Obj obj = *(table->begin() + 7);
2✔
4260
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4261
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2✔
4262
    }
2✔
4263

4264
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4265
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4266
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4267

4268
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4269
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4270
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4271

4272
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4273
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4274
}
2✔
4275

4276

4277
// This test checks that it is possible to create, upload, download, and merge
4278
// changesets larger than 16MB.
4279
TEST(Sync_MergeLargeChangesets)
4280
{
2✔
4281
    constexpr int number_of_rows = 200;
2✔
4282

4283
    TEST_CLIENT_DB(db_1);
2✔
4284
    TEST_CLIENT_DB(db_2);
2✔
4285

4286
    {
2✔
4287
        WriteTransaction wt(db_1);
2✔
4288
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4289
        table->add_column(type_Binary, "column name");
2✔
4290
        table->add_column(type_Int, "integer column");
2✔
4291
        wt.commit();
2✔
4292
    }
2✔
4293

4294
    {
2✔
4295
        WriteTransaction wt(db_2);
2✔
4296
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4297
        table->add_column(type_Binary, "column name");
2✔
4298
        table->add_column(type_Int, "integer column");
2✔
4299
        wt.commit();
2✔
4300
    }
2✔
4301

4302
    {
2✔
4303
        WriteTransaction wt(db_1);
2✔
4304
        TableRef table = wt.get_table("class_table name");
2✔
4305
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4306
            table->create_object_with_primary_key(i);
400✔
4307
        }
400✔
4308
        std::string str(100000, 'a');
2✔
4309
        BinaryData bd(str.data(), str.size());
2✔
4310
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4311
            table->get_object(size_t(row)).set("column name", bd);
400✔
4312
            table->get_object(size_t(row)).set("integer column", 2 * row);
400✔
4313
        }
400✔
4314
        wt.commit();
2✔
4315
    }
2✔
4316

4317
    {
2✔
4318
        WriteTransaction wt(db_2);
2✔
4319
        TableRef table = wt.get_table("class_table name");
2✔
4320
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4321
            table->create_object_with_primary_key(i + number_of_rows);
400✔
4322
        }
400✔
4323
        std::string str(100000, 'b');
2✔
4324
        BinaryData bd(str.data(), str.size());
2✔
4325
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4326
            table->get_object(size_t(row)).set("column name", bd);
400✔
4327
            table->get_object(size_t(row)).set("integer column", 2 * row + 1);
400✔
4328
        }
400✔
4329
        wt.commit();
2✔
4330
    }
2✔
4331

4332
    {
2✔
4333
        TEST_DIR(dir);
2✔
4334
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4335

4336
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4337
        session_1.bind();
2✔
4338
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4339
        session_2.bind();
2✔
4340

4341
        fixture.start();
2✔
4342

4343
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4344
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4345
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4346
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4347
    }
2✔
4348

4349
    ReadTransaction read_1(db_1);
2✔
4350
    ReadTransaction read_2(db_2);
2✔
4351
    const Group& group = read_1;
2✔
4352
    CHECK(compare_groups(read_1, read_2));
2✔
4353
    ConstTableRef table = group.get_table("class_table name");
2✔
4354
    CHECK_EQUAL(table->size(), 2 * number_of_rows);
2✔
4355
}
2✔
4356

4357

4358
TEST(Sync_MergeMultipleChangesets)
4359
{
2✔
4360
    constexpr int number_of_changesets = 100;
2✔
4361
    constexpr int number_of_instructions = 10;
2✔
4362

4363
    TEST_CLIENT_DB(db_1);
2✔
4364
    TEST_CLIENT_DB(db_2);
2✔
4365

4366
    std::atomic<int> id = 0;
2✔
4367

4368
    {
2✔
4369
        WriteTransaction wt(db_1);
2✔
4370
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4371
        table->add_column(type_Int, "integer column");
2✔
4372
        wt.commit();
2✔
4373
    }
2✔
4374

4375
    {
2✔
4376
        WriteTransaction wt(db_2);
2✔
4377
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4378
        table->add_column(type_Int, "integer column");
2✔
4379
        wt.commit();
2✔
4380
    }
2✔
4381

4382
    {
2✔
4383
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4384
            WriteTransaction wt(db_1);
200✔
4385
            TableRef table = wt.get_table("class_table name");
200✔
4386
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4387
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4388
                obj.set("integer column", 2 * j);
2,000✔
4389
            }
2,000✔
4390
            wt.commit();
200✔
4391
        }
200✔
4392
    }
2✔
4393

4394
    {
2✔
4395
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4396
            WriteTransaction wt(db_2);
200✔
4397
            TableRef table = wt.get_table("class_table name");
200✔
4398
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4399
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4400
                obj.set("integer column", 2 * j + 1);
2,000✔
4401
            }
2,000✔
4402
            wt.commit();
200✔
4403
        }
200✔
4404
    }
2✔
4405

4406
    {
2✔
4407
        TEST_DIR(dir);
2✔
4408
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4409

4410

4411
        // Start server and upload changes of first client.
4412
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4413
        session_1.bind();
2✔
4414
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4415
        session_2.bind();
2✔
4416

4417
        fixture.start_server(0);
2✔
4418
        fixture.start_client(0);
2✔
4419
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4420
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4421
        session_1.detach();
2✔
4422
        // Stop first client.
4423
        fixture.stop_client(0);
2✔
4424

4425
        // Start the second client and upload their changes.
4426
        // Wait to integrate changes from the first client.
4427
        fixture.start_client(1);
2✔
4428
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4429
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4430
    }
2✔
4431

4432
    ReadTransaction read_1(db_1);
2✔
4433
    ReadTransaction read_2(db_2);
2✔
4434
    const Group& group1 = read_1;
2✔
4435
    const Group& group2 = read_2;
2✔
4436
    ConstTableRef table1 = group1.get_table("class_table name");
2✔
4437
    ConstTableRef table2 = group2.get_table("class_table name");
2✔
4438
    CHECK_EQUAL(table1->size(), number_of_changesets * number_of_instructions);
2✔
4439
    CHECK_EQUAL(table2->size(), 2 * number_of_changesets * number_of_instructions);
2✔
4440
}
2✔
4441

4442

4443
#endif // REALM_PLATFORM_WIN32
4444

4445

4446
TEST(Sync_PingTimesOut)
4447
{
2✔
4448
    bool did_fail = false;
2✔
4449
    {
2✔
4450
        TEST_DIR(dir);
2✔
4451
        TEST_CLIENT_DB(db);
2✔
4452

4453
        ClientServerFixture::Config config;
2✔
4454
        config.client_ping_period = 0;  // send ping immediately
2✔
4455
        config.client_pong_timeout = 0; // time out immediately
2✔
4456
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4457

4458
        auto error_handler = [&](Status status, bool) {
2✔
4459
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4460
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4461
            did_fail = true;
2✔
4462
            fixture.stop();
2✔
4463
        };
2✔
4464
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4465

4466
        fixture.start();
2✔
4467

4468
        Session session = fixture.make_bound_session(db);
2✔
4469
        session.wait_for_download_complete_or_client_stopped();
2✔
4470
    }
2✔
4471
    CHECK(did_fail);
2✔
4472
}
2✔
4473

4474

4475
TEST(Sync_ReconnectAfterPingTimeout)
4476
{
2✔
4477
    TEST_DIR(dir);
2✔
4478
    TEST_CLIENT_DB(db);
2✔
4479

4480
    ClientServerFixture::Config config;
2✔
4481
    config.client_ping_period = 0;  // send ping immediately
2✔
4482
    config.client_pong_timeout = 0; // time out immediately
2✔
4483

4484
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4485

4486
    BowlOfStonesSemaphore bowl;
2✔
4487
    auto error_handler = [&](Status status, bool) {
2✔
4488
        if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) {
2✔
4489
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4490
            bowl.add_stone();
2✔
4491
        }
2✔
4492
    };
2✔
4493
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4494
    fixture.start();
2✔
4495

4496
    Session session = fixture.make_bound_session(db, "/test");
2✔
4497
    bowl.get_stone();
2✔
4498
}
2✔
4499

4500

4501
TEST(Sync_UrgentPingIsSent)
4502
{
2✔
4503
    bool did_fail = false;
2✔
4504
    {
2✔
4505
        TEST_DIR(dir);
2✔
4506
        TEST_CLIENT_DB(db);
2✔
4507

4508
        ClientServerFixture::Config config;
2✔
4509
        config.client_pong_timeout = 0; // urgent pings time out immediately
2✔
4510

4511
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4512

4513
        auto error_handler = [&](Status status, bool) {
2✔
4514
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4515
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4516
            did_fail = true;
2✔
4517
            fixture.stop();
2✔
4518
        };
2✔
4519
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4520

4521
        fixture.start();
2✔
4522

4523
        Session session = fixture.make_bound_session(db);
2✔
4524
        session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4525
        session.cancel_reconnect_delay();                       // send an urgent ping
2✔
4526
        session.wait_for_download_complete_or_client_stopped();
2✔
4527
    }
2✔
4528
    CHECK(did_fail);
2✔
4529
}
2✔
4530

4531

4532
TEST(Sync_ServerDiscardDeadConnections)
4533
{
2✔
4534
    TEST_DIR(dir);
2✔
4535
    TEST_CLIENT_DB(db);
2✔
4536

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

4540
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4541

4542
    BowlOfStonesSemaphore bowl;
2✔
4543
    auto error_handler = [&](Status status, bool) {
2✔
4544
        CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4545
        bowl.add_stone();
2✔
4546
    };
2✔
4547
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4548
    fixture.start();
2✔
4549

4550
    Session session = fixture.make_bound_session(db);
2✔
4551
    session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4552
    fixture.set_server_connection_reaper_timeout(0);        // all connections will now be considered dead
2✔
4553
    bowl.get_stone();
2✔
4554
}
2✔
4555

4556

4557
TEST(Sync_Quadratic_Merge)
4558
{
2✔
4559
    size_t num_instructions_1 = 100;
2✔
4560
    size_t num_instructions_2 = 200;
2✔
4561
    REALM_ASSERT(num_instructions_1 >= 3 && num_instructions_2 >= 3);
2✔
4562

4563
    TEST_DIR(server_dir);
2✔
4564
    TEST_CLIENT_DB(db_1);
2✔
4565
    TEST_CLIENT_DB(db_2);
2✔
4566

4567
    // The schema and data is created with
4568
    // n_operations instructions. The instructions are:
4569
    // create table
4570
    // add column
4571
    // create object
4572
    // n_operations - 3 add_int instructions.
4573
    auto create_data = [](DBRef db, size_t n_operations) {
4✔
4574
        WriteTransaction wt(db);
4✔
4575
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
4✔
4576
        table->add_column(type_Int, "i");
4✔
4577
        Obj obj = table->create_object_with_primary_key(1);
4✔
4578
        for (size_t i = 0; i < n_operations - 3; ++i)
592✔
4579
            obj.add_int("i", 1);
588✔
4580
        wt.commit();
4✔
4581
    };
4✔
4582

4583
    create_data(db_1, num_instructions_1);
2✔
4584
    create_data(db_2, num_instructions_2);
2✔
4585

4586
    int num_clients = 2;
2✔
4587
    int num_servers = 1;
2✔
4588
    MultiClientServerFixture fixture{num_clients, num_servers, server_dir, test_context};
2✔
4589
    fixture.start();
2✔
4590

4591
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4592
    session_1.bind();
2✔
4593
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4594

4595
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4596
    session_2.bind();
2✔
4597
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
4598

4599
    session_1.wait_for_download_complete_or_client_stopped();
2✔
4600
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4601
}
2✔
4602

4603

4604
TEST(Sync_BatchedUploadMessages)
4605
{
2✔
4606
    TEST_DIR(server_dir);
2✔
4607
    TEST_CLIENT_DB(db);
2✔
4608

4609
    ClientServerFixture fixture(server_dir, test_context);
2✔
4610
    fixture.start();
2✔
4611

4612
    Session session = fixture.make_session(db, "/test");
2✔
4613

4614
    {
2✔
4615
        WriteTransaction wt{db};
2✔
4616
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4617
        tr->add_column(type_Int, "integer column");
2✔
4618
        wt.commit();
2✔
4619
    }
2✔
4620

4621
    // Create a lot of changesets. We will attempt to check that
4622
    // they are uploaded in a few upload messages.
4623
    for (int i = 0; i < 400; ++i) {
802✔
4624
        WriteTransaction wt{db};
800✔
4625
        TableRef tr = wt.get_table("class_foo");
800✔
4626
        tr->create_object_with_primary_key(i).set("integer column", i);
800✔
4627
        wt.commit();
800✔
4628
    }
800✔
4629

4630
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4631
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4632
                                uint_fast64_t snapshot_version, double, double) {
4✔
4633
        CHECK_GREATER(uploadable_bytes, 1000);
4✔
4634

4635
        // This is the important check. If the changesets were not batched,
4636
        // there would be callbacks with partial uploaded_bytes.
4637
        // With batching, all uploadable_bytes are uploaded in the same message.
4638
        CHECK(uploaded_bytes == 0 || uploaded_bytes == uploadable_bytes);
4✔
4639
        CHECK_EQUAL(0, downloaded_bytes);
4✔
4640
        CHECK_EQUAL(0, downloadable_bytes);
4✔
4641
        static_cast<void>(snapshot_version);
4✔
4642
    };
4✔
4643

4644
    session.set_progress_handler(progress_handler);
2✔
4645
    session.bind();
2✔
4646
    session.wait_for_upload_complete_or_client_stopped();
2✔
4647
}
2✔
4648

4649

4650
TEST(Sync_UploadLogCompactionEnabled)
4651
{
2✔
4652
    TEST_DIR(server_dir);
2✔
4653
    TEST_CLIENT_DB(db_1);
2✔
4654
    TEST_CLIENT_DB(db_2);
2✔
4655

4656
    ClientServerFixture::Config config;
2✔
4657
    config.disable_upload_compaction = false;
2✔
4658
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
4659
    fixture.start();
2✔
4660

4661
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
4662
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
4663

4664
    // Create a changeset with lots of overwrites of the
4665
    // same fields.
4666
    {
2✔
4667
        WriteTransaction wt{db_1};
2✔
4668
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4669
        tr->add_column(type_Int, "integer column");
2✔
4670
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4671
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4672
        for (int i = 0; i < 10000; ++i) {
20,002✔
4673
            obj0.set("integer column", i);
20,000✔
4674
            obj1.set("integer column", 2 * i);
20,000✔
4675
        }
20,000✔
4676
        wt.commit();
2✔
4677
    }
2✔
4678

4679
    session_1.bind();
2✔
4680
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4681

4682
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4683
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4684
                                uint_fast64_t snapshot_version, double, double) {
2✔
4685
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
4686
        CHECK_EQUAL(0, uploaded_bytes);
2✔
4687
        CHECK_EQUAL(0, uploadable_bytes);
2✔
4688
        static_cast<void>(snapshot_version);
2✔
4689
        CHECK_NOT_EQUAL(downloadable_bytes, 0);
2✔
4690
    };
2✔
4691

4692
    session_2.set_progress_handler(progress_handler);
2✔
4693

4694
    session_2.bind();
2✔
4695

4696
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4697

4698
    {
2✔
4699
        ReadTransaction rt_1(db_1);
2✔
4700
        ReadTransaction rt_2(db_2);
2✔
4701
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4702
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4703
        CHECK_EQUAL(2, table->size());
2✔
4704
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4705
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4706
    }
2✔
4707
}
2✔
4708

4709

4710
TEST(Sync_UploadLogCompactionDisabled)
4711
{
2✔
4712
    TEST_DIR(server_dir);
2✔
4713
    TEST_CLIENT_DB(db_1);
2✔
4714
    TEST_CLIENT_DB(db_2);
2✔
4715

4716
    ClientServerFixture::Config config;
2✔
4717
    config.disable_upload_compaction = true;
2✔
4718
    config.disable_history_compaction = true;
2✔
4719
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4720
    fixture.start();
2✔
4721

4722
    // Create a changeset with lots of overwrites of the
4723
    // same fields.
4724
    {
2✔
4725
        WriteTransaction wt{db_1};
2✔
4726
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4727
        auto col_int = tr->add_column(type_Int, "integer column");
2✔
4728
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4729
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4730
        for (int i = 0; i < 10000; ++i) {
20,002✔
4731
            obj0.set(col_int, i);
20,000✔
4732
            obj1.set(col_int, 2 * i);
20,000✔
4733
        }
20,000✔
4734
        wt.commit();
2✔
4735
    }
2✔
4736

4737
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
4738
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4739

4740
    auto progress_handler = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
4741
                                std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
4742
                                std::uint_fast64_t snapshot_version, double, double) {
2✔
4743
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
2✔
4744
        CHECK_EQUAL(0, uploaded_bytes);
2✔
4745
        CHECK_EQUAL(0, uploadable_bytes);
2✔
4746
        static_cast<void>(snapshot_version);
2✔
4747
        CHECK_NOT_EQUAL(0, downloadable_bytes);
2✔
4748
    };
2✔
4749

4750
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
4751
    session_2.set_progress_handler(progress_handler);
2✔
4752
    session_2.bind();
2✔
4753
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4754

4755
    {
2✔
4756
        ReadTransaction rt_1(db_1);
2✔
4757
        ReadTransaction rt_2(db_2);
2✔
4758
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4759
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4760
        CHECK_EQUAL(2, table->size());
2✔
4761
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4762
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4763
    }
2✔
4764
}
2✔
4765

4766

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

4773
    ClientServerFixture fixture{dir, test_context};
2✔
4774
    fixture.start();
2✔
4775

4776
    ColKey col_ndx_blob_data;
2✔
4777
    {
2✔
4778
        WriteTransaction wt{db_1};
2✔
4779
        TableRef blobs = wt.get_group().add_table_with_primary_key("class_Blob", type_Int, "id");
2✔
4780
        col_ndx_blob_data = blobs->add_column(type_Binary, "data");
2✔
4781
        blobs->create_object_with_primary_key(1);
2✔
4782
        wt.commit();
2✔
4783
    }
2✔
4784

4785
    Session session_1 = fixture.make_bound_session(db_1, "/foo");
2✔
4786
    Session session_2 = fixture.make_bound_session(db_2, "/foo");
2✔
4787

4788
    std::string blob(0x4000, '\0');
2✔
4789
    for (long i = 0; i < 1024; ++i) {
2,050✔
4790
        {
2,048✔
4791
            WriteTransaction wt{db_1};
2,048✔
4792
            TableRef blobs = wt.get_table("class_Blob");
2,048✔
4793
            blobs->begin()->set(col_ndx_blob_data, BinaryData{blob});
2,048✔
4794
            wt.commit();
2,048✔
4795
        }
2,048✔
4796
        session_1.wait_for_upload_complete_or_client_stopped();
2,048✔
4797
        session_2.wait_for_download_complete_or_client_stopped();
2,048✔
4798
    }
2,048✔
4799

4800
    // Check that the file size is less than 4 MiB. If it is, then the history
4801
    // must have been trimmed, as the combined size of all the blobs is at least
4802
    // 16 MiB.
4803
    CHECK_LESS(util::File{db_1_path}.get_size(), 0x400000);
2✔
4804
}
2✔
4805

4806
// This test creates two objects in a target table and a link list
4807
// in a source table. The first target object is inserted in the link list,
4808
// and later the link is set to the second target object.
4809
// Both the target objects are deleted afterwards. The tests verifies that
4810
// sync works with log compaction turned on.
4811
TEST(Sync_ContainerInsertAndSetLogCompaction)
4812
{
2✔
4813
    TEST_DIR(dir);
2✔
4814
    TEST_CLIENT_DB(db_1);
2✔
4815
    TEST_CLIENT_DB(db_2);
2✔
4816
    ClientServerFixture fixture(dir, test_context);
2✔
4817
    fixture.start();
2✔
4818

4819
    {
2✔
4820
        WriteTransaction wt{db_1};
2✔
4821

4822
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
4823
        ColKey col_ndx = table_target->add_column(type_Int, "value");
2✔
4824
        auto k0 = table_target->create_object_with_primary_key(1).set(col_ndx, 123).get_key();
2✔
4825
        auto k1 = table_target->create_object_with_primary_key(2).set(col_ndx, 456).get_key();
2✔
4826

4827
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
4828
        col_ndx = table_source->add_column_list(*table_target, "target_link");
2✔
4829
        Obj obj = table_source->create_object_with_primary_key(1);
2✔
4830
        LnkLst ll = obj.get_linklist(col_ndx);
2✔
4831
        ll.insert(0, k0);
2✔
4832
        ll.set(0, k1);
2✔
4833

4834
        table_target->remove_object(k1);
2✔
4835
        table_target->remove_object(k0);
2✔
4836

4837
        wt.commit();
2✔
4838
    }
2✔
4839

4840
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4841
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4842

4843
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4844
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4845

4846
    {
2✔
4847
        ReadTransaction rt_1(db_1);
2✔
4848
        ReadTransaction rt_2(db_2);
2✔
4849
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4850
    }
2✔
4851
}
2✔
4852

4853

4854
TEST(Sync_MultipleContainerColumns)
4855
{
2✔
4856
    TEST_DIR(dir);
2✔
4857
    TEST_CLIENT_DB(db_1);
2✔
4858
    TEST_CLIENT_DB(db_2);
2✔
4859
    ClientServerFixture fixture(dir, test_context);
2✔
4860
    fixture.start();
2✔
4861

4862
    {
2✔
4863
        WriteTransaction wt{db_1};
2✔
4864

4865
        TableRef table = wt.get_group().add_table_with_primary_key("class_Table", type_Int, "id");
2✔
4866
        table->add_column_list(type_String, "array1");
2✔
4867
        table->add_column_list(type_String, "array2");
2✔
4868

4869
        Obj row = table->create_object_with_primary_key(1);
2✔
4870
        {
2✔
4871
            Lst<StringData> array1 = row.get_list<StringData>("array1");
2✔
4872
            array1.clear();
2✔
4873
            array1.add("Hello");
2✔
4874
        }
2✔
4875
        {
2✔
4876
            Lst<StringData> array2 = row.get_list<StringData>("array2");
2✔
4877
            array2.clear();
2✔
4878
            array2.add("World");
2✔
4879
        }
2✔
4880

4881
        wt.commit();
2✔
4882
    }
2✔
4883

4884
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4885
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4886

4887
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4888
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4889

4890
    {
2✔
4891
        ReadTransaction rt_1(db_1);
2✔
4892
        ReadTransaction rt_2(db_2);
2✔
4893
        CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
4894

4895
        ConstTableRef table = rt_1.get_table("class_Table");
2✔
4896
        const Obj row = *table->begin();
2✔
4897
        auto array1 = row.get_list<StringData>("array1");
2✔
4898
        auto array2 = row.get_list<StringData>("array2");
2✔
4899
        CHECK_EQUAL(array1.size(), 1);
2✔
4900
        CHECK_EQUAL(array2.size(), 1);
2✔
4901
        CHECK_EQUAL(array1.get(0), "Hello");
2✔
4902
        CHECK_EQUAL(array2.get(0), "World");
2✔
4903
    }
2✔
4904
}
2✔
4905

4906

4907
TEST(Sync_ConnectionStateChange)
4908
{
2✔
4909
    TEST_DIR(dir);
2✔
4910
    TEST_CLIENT_DB(db_1);
2✔
4911
    TEST_CLIENT_DB(db_2);
2✔
4912

4913
    std::vector<ConnectionState> states_1, states_2;
2✔
4914
    {
2✔
4915
        ClientServerFixture fixture(dir, test_context);
2✔
4916
        fixture.start();
2✔
4917

4918
        BowlOfStonesSemaphore bowl_1, bowl_2;
2✔
4919
        auto listener_1 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4920
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4921
            states_1.push_back(state);
6✔
4922
            if (state == ConnectionState::disconnected)
6✔
4923
                bowl_1.add_stone();
2✔
4924
        };
6✔
4925
        auto listener_2 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4926
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4927
            states_2.push_back(state);
6✔
4928
            if (state == ConnectionState::disconnected)
6✔
4929
                bowl_2.add_stone();
2✔
4930
        };
6✔
4931

4932
        Session session_1 = fixture.make_session(db_1, "/test");
2✔
4933
        session_1.set_connection_state_change_listener(listener_1);
2✔
4934
        session_1.bind();
2✔
4935
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4936

4937
        Session session_2 = fixture.make_session(db_2, "/test");
2✔
4938
        session_2.set_connection_state_change_listener(listener_2);
2✔
4939
        session_2.bind();
2✔
4940
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4941

4942
        fixture.close_server_side_connections();
2✔
4943
        bowl_1.get_stone();
2✔
4944
        bowl_2.get_stone();
2✔
4945
    }
2✔
4946
    std::vector<ConnectionState> reference{ConnectionState::connecting, ConnectionState::connected,
2✔
4947
                                           ConnectionState::disconnected};
2✔
4948
    CHECK(states_1 == reference);
2✔
4949
    CHECK(states_2 == reference);
2✔
4950
}
2✔
4951

4952

4953
TEST(Sync_ClientErrorHandler)
4954
{
2✔
4955
    TEST_DIR(dir);
2✔
4956
    TEST_CLIENT_DB(db);
2✔
4957
    ClientServerFixture fixture(dir, test_context);
2✔
4958
    fixture.start();
2✔
4959

4960
    BowlOfStonesSemaphore bowl;
2✔
4961
    auto handler = [&](const SessionErrorInfo&) {
2✔
4962
        bowl.add_stone();
2✔
4963
    };
2✔
4964

4965
    Session session = fixture.make_session(db, "/test");
2✔
4966
    session.set_error_handler(std::move(handler));
2✔
4967
    session.bind();
2✔
4968
    session.wait_for_download_complete_or_client_stopped();
2✔
4969

4970
    fixture.close_server_side_connections();
2✔
4971
    bowl.get_stone();
2✔
4972
}
2✔
4973

4974

4975
TEST(Sync_VerifyServerHistoryAfterLargeUpload)
4976
{
2✔
4977
    TEST_DIR(server_dir);
2✔
4978
    TEST_CLIENT_DB(db);
2✔
4979

4980
    ClientServerFixture fixture{server_dir, test_context};
2✔
4981
    fixture.start();
2✔
4982

4983
    {
2✔
4984
        auto wt = db->start_write();
2✔
4985
        auto table = wt->add_table_with_primary_key("class_table", type_Int, "id");
2✔
4986
        ColKey col = table->add_column(type_Binary, "data");
2✔
4987

4988
        // Create enough data that our changeset cannot be stored contiguously
4989
        // by BinaryColumn (> 16MB).
4990
        std::size_t data_size = 8 * 1024 * 1024;
2✔
4991
        std::string data(data_size, '\0');
2✔
4992
        for (int i = 0; i < 8; ++i) {
18✔
4993
            table->create_object_with_primary_key(i).set(col, BinaryData{data.data(), data.size()});
16✔
4994
        }
16✔
4995

4996
        wt->commit();
2✔
4997

4998
        Session session = fixture.make_session(db, "/test");
2✔
4999
        session.bind();
2✔
5000
        session.wait_for_upload_complete_or_client_stopped();
2✔
5001
    }
2✔
5002

5003
    {
2✔
5004
        std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
5005
        TestServerHistoryContext context;
2✔
5006
        _impl::ServerHistory history{context};
2✔
5007
        DBRef db = DB::create(history, server_path);
2✔
5008
        {
2✔
5009
            ReadTransaction rt{db};
2✔
5010
            rt.get_group().verify();
2✔
5011
        }
2✔
5012
    }
2✔
5013
}
2✔
5014

5015

5016
TEST(Sync_ServerSideModify_Randomize)
5017
{
2✔
5018
    int num_server_side_transacts = 1200;
2✔
5019
    int num_client_side_transacts = 1200;
2✔
5020

5021
    TEST_DIR(server_dir);
2✔
5022
    TEST_CLIENT_DB(db_2);
2✔
5023

5024
    ClientServerFixture::Config config;
2✔
5025
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
5026
    fixture.start();
2✔
5027

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

5030
    std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
5031
    TestServerHistoryContext context;
2✔
5032
    _impl::ServerHistory history_1{context};
2✔
5033
    DBRef db_1 = DB::create(history_1, server_path);
2✔
5034

5035
    auto server_side_program = [num_server_side_transacts, &db_1, &fixture, &session] {
2✔
5036
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
5037
        for (int i = 0; i < num_server_side_transacts; ++i) {
2,402✔
5038
            WriteTransaction wt{db_1};
2,400✔
5039
            TableRef table = wt.get_table("class_foo");
2,400✔
5040
            if (!table) {
2,400✔
5041
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
5042
                table->add_column(type_Int, "i");
2✔
5043
            }
2✔
5044
            if (i % 2 == 0)
2,400✔
5045
                table->create_object_with_primary_key(0 - i);
1,200✔
5046
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
5047
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
5048
            wt.commit();
2,400✔
5049
            fixture.inform_server_about_external_change("/test");
2,400✔
5050
            session.wait_for_download_complete_or_client_stopped();
2,400✔
5051
        }
2,400✔
5052
    };
2✔
5053

5054
    auto client_side_program = [num_client_side_transacts, &db_2, &session] {
2✔
5055
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
5056
        for (int i = 0; i < num_client_side_transacts; ++i) {
2,402✔
5057
            WriteTransaction wt{db_2};
2,400✔
5058
            TableRef table = wt.get_table("class_foo");
2,400✔
5059
            if (!table) {
2,400✔
5060
                table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
5061
                table->add_column(type_Int, "i");
2✔
5062
            }
2✔
5063
            if (i % 2 == 0)
2,400✔
5064
                table->create_object_with_primary_key(i);
1,200✔
5065
            ;
2,400✔
5066
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
2,400✔
5067
            obj.set<int64_t>("i", random.draw_int_max(0x0'7FFF'FFFF'FFFF'FFFF));
2,400✔
5068
            wt.commit();
2,400✔
5069
            if (i % 16 == 0)
2,400✔
5070
                session.wait_for_upload_complete_or_client_stopped();
150✔
5071
        }
2,400✔
5072
    };
2✔
5073

5074
    ThreadWrapper server_program_thread;
2✔
5075
    server_program_thread.start(std::move(server_side_program));
2✔
5076
    client_side_program();
2✔
5077
    CHECK(!server_program_thread.join());
2✔
5078

5079
    session.wait_for_upload_complete_or_client_stopped();
2✔
5080
    session.wait_for_download_complete_or_client_stopped();
2✔
5081

5082
    ReadTransaction rt_1{db_1};
2✔
5083
    ReadTransaction rt_2{db_2};
2✔
5084
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
5085
}
2✔
5086

5087

5088
// This test connects a sync client to the realm cloud service using a SSL
5089
// connection. The purpose of the test is to check that the server's SSL
5090
// certificate is accepted by the client.  The client will connect with an
5091
// invalid token and get an error code back.  The check is that the error is
5092
// not rejected certificate.  The test should be disabled under normal
5093
// circumstances since it requires network access and cloud availability. The
5094
// test might be enabled during testing of SSL functionality.
5095
TEST_IF(Sync_SSL_Certificates, false)
5096
{
×
5097
    TEST_CLIENT_DB(db);
×
5098

5099
    const char* server_address[] = {
×
5100
        "morten-krogh.us1.cloud.realm.io",
×
5101
        "fantastic-cotton-shoes.us1.cloud.realm.io",
×
5102
        "www.realm.io",
×
5103
        "www.yahoo.com",
×
5104
        "www.nytimes.com",
×
5105
        "www.ibm.com",
×
5106
        "www.ssllabs.com",
×
5107
    };
×
5108

5109
    size_t num_servers = sizeof(server_address) / sizeof(server_address[0]);
×
5110

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

5113
    for (size_t i = 0; i < num_servers; ++i) {
×
5114
        Client::Config client_config;
×
5115
        client_config.logger = client_logger;
×
5116
        client_config.reconnect_mode = ReconnectMode::testing;
×
5117
        Client client(client_config);
×
5118

5119
        Session::Config session_config;
×
5120
        session_config.server_address = server_address[i];
×
5121
        session_config.server_port = 443;
×
5122
        session_config.realm_identifier = "/anything";
×
5123
        session_config.protocol_envelope = ProtocolEnvelope::realms;
×
5124

5125
        // Invalid token for the cloud.
5126
        session_config.signed_user_token = g_signed_test_user_token;
×
5127

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

5130
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>& error_info) {
×
5131
            if (state == ConnectionState::disconnected) {
×
5132
                CHECK(error_info);
×
5133
                client_logger->debug("State change: disconnected, error_code = %1, is_fatal = %2", error_info->status,
×
5134
                                     error_info->is_fatal);
×
5135
                // We expect to get through the SSL handshake but will hit an error due to the wrong token.
5136
                CHECK_NOT_EQUAL(error_info->status, ErrorCodes::TlsHandshakeFailed);
×
5137
                client.shutdown();
×
5138
            }
×
5139
        };
×
5140

5141
        session.set_connection_state_change_listener(listener);
×
5142
        session.bind();
×
5143

5144
        session.wait_for_download_complete_or_client_stopped();
×
5145
    }
×
5146
}
×
5147

5148

5149
// Testing the custom authorization header name.  The sync protocol does not
5150
// currently use the HTTP Authorization header, so the test is to watch the
5151
// logs and see that the client use the right header name. Proxies and the sync
5152
// server HTTP api use the Authorization header.
5153
TEST(Sync_AuthorizationHeaderName)
5154
{
2✔
5155
    TEST_DIR(dir);
2✔
5156
    TEST_CLIENT_DB(db);
2✔
5157

5158
    const char* authorization_header_name = "X-Alternative-Name";
2✔
5159
    ClientServerFixture::Config config;
2✔
5160
    config.authorization_header_name = authorization_header_name;
2✔
5161
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5162
    fixture.start();
2✔
5163

5164
    Session::Config session_config;
2✔
5165
    session_config.authorization_header_name = authorization_header_name;
2✔
5166

5167
    std::map<std::string, std::string> custom_http_headers;
2✔
5168
    custom_http_headers["Header-Name-1"] = "Header-Value-1";
2✔
5169
    custom_http_headers["Header-Name-2"] = "Header-Value-2";
2✔
5170
    session_config.custom_http_headers = std::move(custom_http_headers);
2✔
5171
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5172
    session.bind();
2✔
5173

5174
    session.wait_for_download_complete_or_client_stopped();
2✔
5175
}
2✔
5176

5177

5178
TEST(Sync_BadChangeset)
5179
{
2✔
5180
    TEST_DIR(dir);
2✔
5181
    TEST_CLIENT_DB(db);
2✔
5182

5183
    bool did_fail = false;
2✔
5184
    {
2✔
5185
        ClientServerFixture::Config config;
2✔
5186
        config.disable_upload_compaction = true;
2✔
5187
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5188
        fixture.start();
2✔
5189

5190
        {
2✔
5191
            Session session = fixture.make_bound_session(db);
2✔
5192
            session.wait_for_download_complete_or_client_stopped();
2✔
5193
        }
2✔
5194

5195
        {
2✔
5196
            WriteTransaction wt(db);
2✔
5197
            TableRef table = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5198
            table->add_column(type_Int, "i");
2✔
5199
            table->create_object_with_primary_key(5).set_all(123);
2✔
5200
            const ChangesetEncoder::Buffer& buffer = get_replication(db).get_instruction_encoder().buffer();
2✔
5201
            char bad_instruction = 0x3e;
2✔
5202
            const_cast<ChangesetEncoder::Buffer&>(buffer).append(&bad_instruction, 1);
2✔
5203
            wt.commit();
2✔
5204
        }
2✔
5205

5206
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>& error_info) {
6✔
5207
            if (state != ConnectionState::disconnected)
6✔
5208
                return;
4✔
5209
            REALM_ASSERT(error_info);
2✔
5210
            CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
2✔
5211
            CHECK(error_info->is_fatal);
2✔
5212
            did_fail = true;
2✔
5213
            fixture.stop();
2✔
5214
        };
2✔
5215

5216
        Session session = fixture.make_session(db, "/test");
2✔
5217
        session.set_connection_state_change_listener(listener);
2✔
5218
        session.bind();
2✔
5219

5220
        session.wait_for_upload_complete_or_client_stopped();
2✔
5221
        session.wait_for_download_complete_or_client_stopped();
2✔
5222
    }
2✔
5223
    CHECK(did_fail);
2✔
5224
}
2✔
5225

5226

5227
TEST(Sync_GoodChangeset_AccentCharacterInFieldName)
5228
{
2✔
5229
    TEST_DIR(dir);
2✔
5230
    TEST_CLIENT_DB(db);
2✔
5231

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

5239
        {
2✔
5240
            Session session = fixture.make_bound_session(db);
2✔
5241
        }
2✔
5242

5243
        {
2✔
5244
            WriteTransaction wt(db);
2✔
5245
            TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
5246
            table->add_column(type_Int, "prógram");
2✔
5247
            table->add_column(type_Int, "program");
2✔
5248
            auto obj = table->create_object_with_primary_key(1);
2✔
5249
            obj.add_int("program", 42);
2✔
5250
            wt.commit();
2✔
5251
        }
2✔
5252

5253
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>) {
4✔
5254
            if (state != ConnectionState::disconnected)
4✔
5255
                return;
4✔
5256
            did_fail = true;
×
5257
            fixture.stop();
×
5258
        };
×
5259

5260
        Session session = fixture.make_session(db, "/test");
2✔
5261
        session.set_connection_state_change_listener(listener);
2✔
5262
        session.bind();
2✔
5263

5264
        session.wait_for_upload_complete_or_client_stopped();
2✔
5265
    }
2✔
5266
    CHECK_NOT(did_fail);
2✔
5267
}
2✔
5268

5269

5270
namespace issue2104 {
5271

5272
class ServerHistoryContext : public _impl::ServerHistory::Context {
5273
public:
5274
    ServerHistoryContext() {}
×
5275

5276
    std::mt19937_64& server_history_get_random() noexcept override
5277
    {
×
5278
        return m_random;
×
5279
    }
×
5280

5281
private:
5282
    std::mt19937_64 m_random;
5283
};
5284

5285
} // namespace issue2104
5286

5287
// This test reproduces a slow merge seen in issue 2104.
5288
// The test uses a user supplied Realm and a changeset
5289
// from a client.
5290
// The test uses a user supplied Realm that is very large
5291
// and not kept in the repo. The realm has checksum 3693867489.
5292
//
5293
// This test might be modified to avoid having a large Realm
5294
// (96 MB uncompressed) in the repo.
5295
TEST_IF(Sync_Issue2104, false)
5296
{
×
5297
    TEST_DIR(dir);
×
5298

5299
    // Save a snapshot of the server Realm file.
5300
    std::string realm_path = "issue_2104_server.realm";
×
5301
    std::string realm_path_copy = util::File::resolve("issue_2104.realm", dir);
×
5302
    util::File::copy(realm_path, realm_path_copy);
×
5303

5304
    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 "
×
5305
                                "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 "
×
5306
                                "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 "
×
5307
                                "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 "
×
5308
                                "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 "
×
5309
                                "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 "
×
5310
                                "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 "
×
5311
                                "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 "
×
5312
                                "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 "
×
5313
                                "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 "
×
5314
                                "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 "
×
5315
                                "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 "
×
5316
                                "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 "
×
5317
                                "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 "
×
5318
                                "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 "
×
5319
                                "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 "
×
5320
                                "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 "
×
5321
                                "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 "
×
5322
                                "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 "
×
5323
                                "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 "
×
5324
                                "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 "
×
5325
                                "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 "
×
5326
                                "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 "
×
5327
                                "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 "
×
5328
                                "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 "
×
5329
                                "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 "
×
5330
                                "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 "
×
5331
                                "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 "
×
5332
                                "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 "
×
5333
                                "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 "
×
5334
                                "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 "
×
5335
                                "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 "
×
5336
                                "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 "
×
5337
                                "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 "
×
5338
                                "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 "
×
5339
                                "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 "
×
5340
                                "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 "
×
5341
                                "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 "
×
5342
                                "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 "
×
5343
                                "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 "
×
5344
                                "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 "
×
5345
                                "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 "
×
5346
                                "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 "
×
5347
                                "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 "
×
5348
                                "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 "
×
5349
                                "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 "
×
5350
                                "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 "
×
5351
                                "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 "
×
5352
                                "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 "
×
5353
                                "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 "
×
5354
                                "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 "
×
5355
                                "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 "
×
5356
                                "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 "
×
5357
                                "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 "
×
5358
                                "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 "
×
5359
                                "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 "
×
5360
                                "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 "
×
5361
                                "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 "
×
5362
                                "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 "
×
5363
                                "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 "
×
5364
                                "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 "
×
5365
                                "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 "
×
5366
                                "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 "
×
5367
                                "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 "
×
5368
                                "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 "
×
5369
                                "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 "
×
5370
                                "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 "
×
5371
                                "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 "
×
5372
                                "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 "
×
5373
                                "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 "
×
5374
                                "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 "
×
5375
                                "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 "
×
5376
                                "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 "
×
5377
                                "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 "
×
5378
                                "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 "
×
5379
                                "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 "
×
5380
                                "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 "
×
5381
                                "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 "
×
5382
                                "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 "
×
5383
                                "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 "
×
5384
                                "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 "
×
5385
                                "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 "
×
5386
                                "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 "
×
5387
                                "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 "
×
5388
                                "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 "
×
5389
                                "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 "
×
5390
                                "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 "
×
5391
                                "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 "
×
5392
                                "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 "
×
5393
                                "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 "
×
5394
                                "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 "
×
5395
                                "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 "
×
5396
                                "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 "
×
5397
                                "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 "
×
5398
                                "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 "
×
5399
                                "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 "
×
5400
                                "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 "
×
5401
                                "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 "
×
5402
                                "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 "
×
5403
                                "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 "
×
5404
                                "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 "
×
5405
                                "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 "
×
5406
                                "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 "
×
5407
                                "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 "
×
5408
                                "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 "
×
5409
                                "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 "
×
5410
                                "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 "
×
5411
                                "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 "
×
5412
                                "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 "
×
5413
                                "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 "
×
5414
                                "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 "
×
5415
                                "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 "
×
5416
                                "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 "
×
5417
                                "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 "
×
5418
                                "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 "
×
5419
                                "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 "
×
5420
                                "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 "
×
5421
                                "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 "
×
5422
                                "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 "
×
5423
                                "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 "
×
5424
                                "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 "
×
5425
                                "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 "
×
5426
                                "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 "
×
5427
                                "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 "
×
5428
                                "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 "
×
5429
                                "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 "
×
5430
                                "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 "
×
5431
                                "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 "
×
5432
                                "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 "
×
5433
                                "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 "
×
5434
                                "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 "
×
5435
                                "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 "
×
5436
                                "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 "
×
5437
                                "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 "
×
5438
                                "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 "
×
5439
                                "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 "
×
5440
                                "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 "
×
5441
                                "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 "
×
5442
                                "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 "
×
5443
                                "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 "
×
5444
                                "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 "
×
5445
                                "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 "
×
5446
                                "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 "
×
5447
                                "1D 0B AC 01 0D 00 1C 00 1E 0B B5 01 0D 00 1F 00 1F 0B AD 01 0C 00 1E";
×
5448

5449
    std::vector<char> changeset_vec;
×
5450
    {
×
5451
        std::istringstream in{changeset_hex};
×
5452
        int n;
×
5453
        in >> std::hex >> n;
×
5454
        while (in) {
×
5455
            REALM_ASSERT(n >= 0 && n <= 255);
×
5456
            changeset_vec.push_back(n);
×
5457
            in >> std::hex >> n;
×
5458
        }
×
5459
    }
×
5460

5461
    BinaryData changeset_bin{changeset_vec.data(), changeset_vec.size()};
×
5462

5463
    file_ident_type client_file_ident = 51;
×
5464
    timestamp_type origin_timestamp = 103573722140;
×
5465
    file_ident_type origin_file_ident = 0;
×
5466
    version_type client_version = 2;
×
5467
    version_type last_integrated_server_version = 0;
×
5468
    UploadCursor upload_cursor{client_version, last_integrated_server_version};
×
5469

5470
    _impl::ServerHistory::IntegratableChangeset integratable_changeset{
×
5471
        client_file_ident, origin_timestamp, origin_file_ident, upload_cursor, changeset_bin};
×
5472

5473
    _impl::ServerHistory::IntegratableChangesets integratable_changesets;
×
5474
    integratable_changesets[client_file_ident].changesets.push_back(integratable_changeset);
×
5475

5476
    issue2104::ServerHistoryContext history_context;
×
5477
    _impl::ServerHistory history{history_context};
×
5478
    DBRef db = DB::create(history, realm_path_copy);
×
5479

5480
    VersionInfo version_info;
×
5481
    bool backup_whole_realm;
×
5482
    _impl::ServerHistory::IntegrationResult result;
×
5483
    history.integrate_client_changesets(integratable_changesets, version_info, backup_whole_realm, result,
×
5484
                                        *test_context.logger);
×
5485
}
×
5486

5487

5488
TEST(Sync_RunServerWithoutPublicKey)
5489
{
2✔
5490
    TEST_CLIENT_DB(db);
2✔
5491
    TEST_DIR(server_dir);
2✔
5492
    ClientServerFixture::Config config;
2✔
5493
    config.server_public_key_path = {};
2✔
5494
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5495
    fixture.start();
2✔
5496

5497
    // Server must accept an unsigned token when a public key is not passed to
5498
    // it
5499
    {
2✔
5500
        Session session = fixture.make_bound_session(db, "/test", g_unsigned_test_user_token);
2✔
5501
        session.wait_for_download_complete_or_client_stopped();
2✔
5502
    }
2✔
5503

5504
    // Server must also accept a signed token when a public key is not passed to
5505
    // it
5506
    {
2✔
5507
        Session session = fixture.make_bound_session(db, "/test");
2✔
5508
        session.wait_for_download_complete_or_client_stopped();
2✔
5509
    }
2✔
5510
}
2✔
5511

5512

5513
TEST(Sync_ServerSideEncryption)
5514
{
2✔
5515
    TEST_CLIENT_DB(db);
2✔
5516
    {
2✔
5517
        WriteTransaction wt(db);
2✔
5518
        wt.get_group().add_table_with_primary_key("class_Test", type_Int, "id");
2✔
5519
        wt.commit();
2✔
5520
    }
2✔
5521

5522
    TEST_DIR(server_dir);
2✔
5523
    bool always_encrypt = true;
2✔
5524
    std::string server_path;
2✔
5525
    {
2✔
5526
        ClientServerFixture::Config config;
2✔
5527
        config.server_encryption_key = crypt_key_2(always_encrypt);
2✔
5528
        ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5529
        fixture.start();
2✔
5530

5531
        Session session = fixture.make_bound_session(db, "/test");
2✔
5532
        session.wait_for_upload_complete_or_client_stopped();
2✔
5533

5534
        server_path = fixture.map_virtual_to_real_path("/test");
2✔
5535
    }
2✔
5536

5537
    const char* encryption_key = crypt_key(always_encrypt);
2✔
5538
    Group group{server_path, encryption_key};
2✔
5539
    CHECK(group.has_table("class_Test"));
2✔
5540
}
2✔
5541

5542
TEST(Sync_LogCompaction_EraseObject_LinkList)
5543
{
2✔
5544
    TEST_DIR(dir);
2✔
5545
    TEST_CLIENT_DB(db_1);
2✔
5546
    TEST_CLIENT_DB(db_2);
2✔
5547
    ClientServerFixture::Config config;
2✔
5548

5549
    // Log comapction is true by default, but we emphasize it.
5550
    config.disable_upload_compaction = false;
2✔
5551
    config.disable_download_compaction = false;
2✔
5552

5553
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5554
    fixture.start();
2✔
5555

5556
    {
2✔
5557
        WriteTransaction wt{db_1};
2✔
5558

5559
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
5560
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
5561
        auto col_key = table_source->add_column_list(*table_target, "target_link");
2✔
5562

5563
        auto k0 = table_target->create_object_with_primary_key(1).get_key();
2✔
5564
        auto k1 = table_target->create_object_with_primary_key(2).get_key();
2✔
5565

5566
        auto ll = table_source->create_object_with_primary_key(0).get_linklist_ptr(col_key);
2✔
5567
        ll->add(k0);
2✔
5568
        ll->add(k1);
2✔
5569
        CHECK_EQUAL(ll->size(), 2);
2✔
5570
        wt.commit();
2✔
5571
    }
2✔
5572

5573
    {
2✔
5574
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5575
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5576

5577
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5578
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5579
    }
2✔
5580

5581
    {
2✔
5582
        WriteTransaction wt{db_1};
2✔
5583

5584
        TableRef table_source = wt.get_table("class_source");
2✔
5585
        TableRef table_target = wt.get_table("class_target");
2✔
5586

5587
        CHECK_EQUAL(table_source->size(), 1);
2✔
5588
        CHECK_EQUAL(table_target->size(), 2);
2✔
5589

5590
        table_target->get_object(1).remove();
2✔
5591
        table_target->get_object(0).remove();
2✔
5592

5593
        table_source->get_object(0).remove();
2✔
5594
        wt.commit();
2✔
5595
    }
2✔
5596

5597
    {
2✔
5598
        WriteTransaction wt{db_2};
2✔
5599

5600
        TableRef table_source = wt.get_table("class_source");
2✔
5601
        TableRef table_target = wt.get_table("class_target");
2✔
5602
        auto col_key = table_source->get_column_key("target_link");
2✔
5603

5604
        CHECK_EQUAL(table_source->size(), 1);
2✔
5605
        CHECK_EQUAL(table_target->size(), 2);
2✔
5606

5607
        auto k0 = table_target->begin()->get_key();
2✔
5608

5609
        auto ll = table_source->get_object(0).get_linklist_ptr(col_key);
2✔
5610
        ll->add(k0);
2✔
5611
        wt.commit();
2✔
5612
    }
2✔
5613

5614
    {
2✔
5615
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5616
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5617
    }
2✔
5618

5619
    {
2✔
5620
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5621
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
5622
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5623
    }
2✔
5624

5625
    {
2✔
5626
        ReadTransaction rt{db_2};
2✔
5627

5628
        ConstTableRef table_source = rt.get_group().get_table("class_source");
2✔
5629
        ConstTableRef table_target = rt.get_group().get_table("class_target");
2✔
5630

5631
        CHECK_EQUAL(table_source->size(), 0);
2✔
5632
        CHECK_EQUAL(table_target->size(), 0);
2✔
5633
    }
2✔
5634
}
2✔
5635

5636

5637
// This test could trigger the assertion that the row_for_object_id cache is
5638
// valid before the cache was properly invalidated in the case of a short
5639
// circuited sync replicator.
5640
TEST(Sync_CreateObjects_EraseObjects)
5641
{
2✔
5642
    TEST_DIR(dir);
2✔
5643
    TEST_CLIENT_DB(db_1);
2✔
5644
    TEST_CLIENT_DB(db_2);
2✔
5645
    ClientServerFixture fixture(dir, test_context);
2✔
5646
    fixture.start();
2✔
5647

5648
    Session session_1 = fixture.make_bound_session(db_1);
2✔
5649
    Session session_2 = fixture.make_bound_session(db_2);
2✔
5650

5651
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
5652
        TableRef table = wt.get_group().add_table_with_primary_key("class_persons", type_Int, "id");
2✔
5653
        table->create_object_with_primary_key(1);
2✔
5654
        table->create_object_with_primary_key(2);
2✔
5655
    });
2✔
5656
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5657
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5658

5659
    write_transaction(db_1, [&](WriteTransaction& wt) {
2✔
5660
        TableRef table = wt.get_table("class_persons");
2✔
5661
        CHECK_EQUAL(table->size(), 2);
2✔
5662
        table->get_object(0).remove();
2✔
5663
        table->get_object(0).remove();
2✔
5664
    });
2✔
5665
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5666
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5667
}
2✔
5668

5669

5670
TEST(Sync_CreateDeleteCreateTableWithPrimaryKey)
5671
{
2✔
5672
    TEST_DIR(dir);
2✔
5673
    TEST_CLIENT_DB(db);
2✔
5674
    ClientServerFixture fixture(dir, test_context);
2✔
5675
    fixture.start();
2✔
5676

5677
    Session session = fixture.make_bound_session(db);
2✔
5678

5679
    write_transaction(db, [](WriteTransaction& wt) {
2✔
5680
        TableRef table = wt.get_group().add_table_with_primary_key("class_t", type_Int, "pk");
2✔
5681
        wt.get_group().remove_table(table->get_key());
2✔
5682
        table = wt.get_group().add_table_with_primary_key("class_t", type_String, "pk");
2✔
5683
    });
2✔
5684
    session.wait_for_upload_complete_or_client_stopped();
2✔
5685
    session.wait_for_download_complete_or_client_stopped();
2✔
5686
}
2✔
5687

5688
template <typename T>
5689
T sequence_next()
5690
{
5691
    REALM_UNREACHABLE();
5692
}
5693

5694
template <>
5695
ObjectId sequence_next()
5696
{
8✔
5697
    return ObjectId::gen();
8✔
5698
}
8✔
5699

5700
template <>
5701
UUID sequence_next()
5702
{
8✔
5703
    union {
8✔
5704
        struct {
8✔
5705
            uint64_t upper;
8✔
5706
            uint64_t lower;
8✔
5707
        } ints;
8✔
5708
        UUID::UUIDBytes bytes;
8✔
5709
    } u;
8✔
5710
    static uint64_t counter = test_util::random_int(0, 1000);
8✔
5711
    u.ints.upper = ++counter;
8✔
5712
    u.ints.lower = ++counter;
8✔
5713
    return UUID{u.bytes};
8✔
5714
}
8✔
5715

5716
template <>
5717
Int sequence_next()
5718
{
8✔
5719
    static Int count = test_util::random_int(-1000, 1000);
8✔
5720
    return ++count;
8✔
5721
}
8✔
5722

5723
template <>
5724
String sequence_next()
5725
{
4✔
5726
    static std::string str;
4✔
5727
    static Int sequence = test_util::random_int(-1000, 1000);
4✔
5728
    str = util::format("string sequence %1", ++sequence);
4✔
5729
    return String(str);
4✔
5730
}
4✔
5731

5732
NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util::Optional<Int>,
5733
                         util::Optional<ObjectId>, util::Optional<UUID>)
5734
{
14✔
5735
    using underlying_type = typename util::RemoveOptional<TEST_TYPE>::type;
14✔
5736
    constexpr bool is_optional = !std::is_same_v<underlying_type, TEST_TYPE>;
14✔
5737
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
14✔
5738

5739
    TEST_CLIENT_DB(db_1);
14✔
5740
    TEST_CLIENT_DB(db_2);
14✔
5741

5742
    TEST_DIR(dir);
14✔
5743
    fixtures::ClientServerFixture fixture{dir, test_context};
14✔
5744
    fixture.start();
14✔
5745

5746
    Session session_1 = fixture.make_session(db_1, "/test");
14✔
5747
    Session session_2 = fixture.make_session(db_2, "/test");
14✔
5748
    session_1.bind();
14✔
5749
    session_2.bind();
14✔
5750

5751
    TEST_TYPE obj_1_id;
14✔
5752
    TEST_TYPE obj_2_id;
14✔
5753

5754
    TEST_TYPE default_or_null{};
14✔
5755
    if constexpr (std::is_same_v<TEST_TYPE, String>) {
14✔
5756
        default_or_null = "";
2✔
5757
    }
2✔
5758
    if constexpr (is_optional) {
14✔
5759
        CHECK(!default_or_null);
6✔
5760
    }
6✔
5761

5762
    {
14✔
5763
        WriteTransaction tr{db_1};
14✔
5764
        auto table_1 = tr.get_group().add_table_with_primary_key("class_Table1", type, "id", is_optional);
14✔
5765
        auto table_2 = tr.get_group().add_table_with_primary_key("class_Table2", type, "id", is_optional);
14✔
5766
        table_1->add_column_list(type, "oids", is_optional);
14✔
5767

5768
        auto obj_1 = table_1->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5769
        auto obj_2 = table_2->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5770
        if constexpr (is_optional) {
14✔
5771
            table_2->create_object_with_primary_key(default_or_null);
6✔
5772
        }
6✔
5773

5774
        auto list = obj_1.template get_list<TEST_TYPE>("oids");
14✔
5775
        obj_1_id = obj_1.template get<TEST_TYPE>("id");
14✔
5776
        obj_2_id = obj_2.template get<TEST_TYPE>("id");
14✔
5777
        list.insert(0, obj_2_id);
14✔
5778
        list.insert(1, default_or_null);
14✔
5779
        list.add(default_or_null);
14✔
5780

5781
        tr.commit();
14✔
5782
    }
14✔
5783

5784
    session_1.wait_for_upload_complete_or_client_stopped();
14✔
5785
    session_2.wait_for_download_complete_or_client_stopped();
14✔
5786

5787
    {
14✔
5788
        ReadTransaction tr{db_2};
14✔
5789
        auto table_1 = tr.get_table("class_Table1");
14✔
5790
        auto table_2 = tr.get_table("class_Table2");
14✔
5791
        auto obj_1 = *table_1->begin();
14✔
5792
        auto obj_2 = table_2->find_first(table_2->get_column_key("id"), obj_2_id);
14✔
5793
        CHECK(obj_2);
14✔
5794
        auto list = obj_1.get_list<TEST_TYPE>("oids");
14✔
5795
        CHECK_EQUAL(obj_1.template get<TEST_TYPE>("id"), obj_1_id);
14✔
5796
        CHECK_EQUAL(list.size(), 3);
14✔
5797
        CHECK_NOT(list.is_null(0));
14✔
5798
        CHECK_EQUAL(list.get(0), obj_2_id);
14✔
5799
        CHECK_EQUAL(list.get(1), default_or_null);
14✔
5800
        CHECK_EQUAL(list.get(2), default_or_null);
14✔
5801
        if constexpr (is_optional) {
14✔
5802
            auto obj_3 = table_2->find_first_null(table_2->get_column_key("id"));
6✔
5803
            CHECK(obj_3);
6✔
5804
            CHECK(list.is_null(1));
6✔
5805
            CHECK(list.is_null(2));
6✔
5806
        }
6✔
5807
    }
14✔
5808
}
14✔
5809

5810
TEST(Sync_Mixed)
5811
{
2✔
5812
    // Test replication and synchronization of Mixed values and lists.
5813
    DBOptions options;
2✔
5814
    options.logger = test_context.logger;
2✔
5815
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
5816
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
5817
    auto db_1 = DB::create(make_client_replication(), db_1_path, options);
2✔
5818
    auto db_2 = DB::create(make_client_replication(), db_2_path, options);
2✔
5819

5820
    TEST_DIR(dir);
2✔
5821
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5822
    fixture.start();
2✔
5823

5824
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5825
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5826
    session_1.bind();
2✔
5827
    session_2.bind();
2✔
5828

5829
    {
2✔
5830
        WriteTransaction tr{db_1};
2✔
5831
        auto& g = tr.get_group();
2✔
5832
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5833
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5834
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5835
        foos->add_column(type_Mixed, "value", true);
2✔
5836
        foos->add_column_list(type_Mixed, "values");
2✔
5837

5838
        auto foo = foos->create_object_with_primary_key(123);
2✔
5839
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5840
        auto fop = fops->create_object_with_primary_key(456);
2✔
5841

5842
        foo.set("value", Mixed(6.2f));
2✔
5843
        auto values = foo.get_list<Mixed>("values");
2✔
5844
        values.insert(0, StringData("A"));
2✔
5845
        values.insert(1, ObjLink{bars->get_key(), bar.get_key()});
2✔
5846
        values.insert(2, ObjLink{fops->get_key(), fop.get_key()});
2✔
5847
        values.insert(3, 123.f);
2✔
5848

5849
        tr.commit();
2✔
5850
    }
2✔
5851

5852
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5853
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5854

5855
    {
2✔
5856
        ReadTransaction tr{db_2};
2✔
5857

5858
        auto foos = tr.get_table("class_Foo");
2✔
5859
        auto bars = tr.get_table("class_Bar");
2✔
5860
        auto fops = tr.get_table("class_Fop");
2✔
5861

5862
        CHECK_EQUAL(foos->size(), 1);
2✔
5863
        CHECK_EQUAL(bars->size(), 1);
2✔
5864
        CHECK_EQUAL(fops->size(), 1);
2✔
5865

5866
        auto foo = *foos->begin();
2✔
5867
        auto value = foo.get<Mixed>("value");
2✔
5868
        CHECK_EQUAL(value, Mixed{6.2f});
2✔
5869
        auto values = foo.get_list<Mixed>("values");
2✔
5870
        CHECK_EQUAL(values.size(), 4);
2✔
5871

5872
        auto v0 = values.get(0);
2✔
5873
        auto v1 = values.get(1);
2✔
5874
        auto v2 = values.get(2);
2✔
5875
        auto v3 = values.get(3);
2✔
5876

5877
        auto l1 = v1.get_link();
2✔
5878
        auto l2 = v2.get_link();
2✔
5879

5880
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5881
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5882

5883
        CHECK_EQUAL(v0, Mixed{"A"});
2✔
5884
        CHECK_EQUAL(l1_table, bars);
2✔
5885
        CHECK_EQUAL(l2_table, fops);
2✔
5886
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5887
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5888
        CHECK_EQUAL(v3, Mixed{123.f});
2✔
5889
    }
2✔
5890
}
2✔
5891

5892
/*
5893
TEST(Sync_TypedLinks)
5894
{
5895
    // Test replication and synchronization of Mixed values and lists.
5896

5897
    TEST_CLIENT_DB(db_1);
5898
    TEST_CLIENT_DB(db_2);
5899

5900
    TEST_DIR(dir);
5901
    fixtures::ClientServerFixture fixture{dir, test_context};
5902
    fixture.start();
5903

5904
    Session session_1 = fixture.make_session(db_1, "/test");
5905
    Session session_2 = fixture.make_session(db_2, "/test");
5906
    session_1.bind();
5907
    session_2.bind();
5908

5909
    write_transaction(db_1, [](WriteTransaction& tr) {
5910
        auto& g = tr.get_group();
5911
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
5912
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
5913
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
5914
        foos->add_column(type_TypedLink, "link");
5915

5916
        auto foo1 = foos->create_object_with_primary_key(123);
5917
        auto foo2 = foos->create_object_with_primary_key(456);
5918
        auto bar = bars->create_object_with_primary_key("Hello");
5919
        auto fop = fops->create_object_with_primary_key(456);
5920

5921
        foo1.set("link", ObjLink(bars->get_key(), bar.get_key()));
5922
        foo2.set("link", ObjLink(fops->get_key(), fop.get_key()));
5923
    });
5924

5925
    session_1.wait_for_upload_complete_or_client_stopped();
5926
    session_2.wait_for_download_complete_or_client_stopped();
5927

5928
    {
5929
        ReadTransaction tr{db_2};
5930

5931
        auto foos = tr.get_table("class_Foo");
5932
        auto bars = tr.get_table("class_Bar");
5933
        auto fops = tr.get_table("class_Fop");
5934

5935
        CHECK_EQUAL(foos->size(), 2);
5936
        CHECK_EQUAL(bars->size(), 1);
5937
        CHECK_EQUAL(fops->size(), 1);
5938

5939
        auto it = foos->begin();
5940
        auto l1 = it->get<ObjLink>("link");
5941
        ++it;
5942
        auto l2 = it->get<ObjLink>("link");
5943

5944
        auto l1_table = tr.get_table(l1.get_table_key());
5945
        auto l2_table = tr.get_table(l2.get_table_key());
5946

5947
        CHECK_EQUAL(l1_table, bars);
5948
        CHECK_EQUAL(l2_table, fops);
5949
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
5950
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
5951
    }
5952
}
5953
*/
5954

5955
TEST(Sync_Dictionary)
5956
{
2✔
5957
    // Test replication and synchronization of Mixed values and lists.
5958

5959
    TEST_CLIENT_DB(db_1);
2✔
5960
    TEST_CLIENT_DB(db_2);
2✔
5961

5962
    TEST_DIR(dir);
2✔
5963
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5964
    fixture.start();
2✔
5965

5966
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5967
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5968
    session_1.bind();
2✔
5969
    session_2.bind();
2✔
5970

5971
    Timestamp now{std::chrono::system_clock::now()};
2✔
5972

5973
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
5974
        auto& g = tr.get_group();
2✔
5975
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5976
        auto col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
5977
        auto col_dict_str = foos->add_column_dictionary(type_String, "str_dict", true);
2✔
5978

5979
        auto foo = foos->create_object_with_primary_key(123);
2✔
5980

5981
        auto dict = foo.get_dictionary(col_dict);
2✔
5982
        dict.insert("hello", "world");
2✔
5983
        dict.insert("cnt", 7);
2✔
5984
        dict.insert("when", now);
2✔
5985

5986
        auto dict_str = foo.get_dictionary(col_dict_str);
2✔
5987
        dict_str.insert("some", "text");
2✔
5988
        dict_str.insert("nothing", null());
2✔
5989
    });
2✔
5990

5991
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5992
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5993

5994
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
5995
        auto foos = tr.get_table("class_Foo");
2✔
5996
        CHECK_EQUAL(foos->size(), 1);
2✔
5997

5998
        auto it = foos->begin();
2✔
5999
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6000
        CHECK(dict.get_value_data_type() == type_Mixed);
2✔
6001
        CHECK_EQUAL(dict.size(), 3);
2✔
6002

6003
        auto col_dict_str = foos->get_column_key("str_dict");
2✔
6004
        auto dict_str = it->get_dictionary(col_dict_str);
2✔
6005
        CHECK(col_dict_str.is_nullable());
2✔
6006
        CHECK(dict_str.get_value_data_type() == type_String);
2✔
6007
        CHECK_EQUAL(dict_str.size(), 2);
2✔
6008

6009
        Mixed val = dict["hello"];
2✔
6010
        CHECK_EQUAL(val.get_string(), "world");
2✔
6011
        val = dict.get("cnt");
2✔
6012
        CHECK_EQUAL(val.get_int(), 7);
2✔
6013
        val = dict.get("when");
2✔
6014
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6015

6016
        dict.erase("cnt");
2✔
6017
        dict.insert("hello", "goodbye");
2✔
6018
    });
2✔
6019

6020
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6021
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6022

6023
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6024
        auto foos = tr.get_table("class_Foo");
2✔
6025
        CHECK_EQUAL(foos->size(), 1);
2✔
6026

6027
        auto it = foos->begin();
2✔
6028
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6029
        CHECK_EQUAL(dict.size(), 2);
2✔
6030

6031
        Mixed val = dict["hello"];
2✔
6032
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
6033
        val = dict.get("when");
2✔
6034
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6035

6036
        dict.clear();
2✔
6037
    });
2✔
6038

6039
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6040
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6041

6042
    {
2✔
6043
        ReadTransaction read_1{db_1};
2✔
6044
        ReadTransaction read_2{db_2};
2✔
6045
        // tr.get_group().to_json(std::cout);
6046

6047
        auto foos = read_2.get_table("class_Foo");
2✔
6048

6049
        CHECK_EQUAL(foos->size(), 1);
2✔
6050

6051
        auto it = foos->begin();
2✔
6052
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6053
        CHECK_EQUAL(dict.size(), 0);
2✔
6054

6055
        CHECK(compare_groups(read_1, read_2));
2✔
6056
    }
2✔
6057
}
2✔
6058

6059
TEST(Sync_CollectionInMixed)
6060
{
2✔
6061
    TEST_CLIENT_DB(db_1);
2✔
6062
    TEST_CLIENT_DB(db_2);
2✔
6063

6064
    TEST_DIR(dir);
2✔
6065
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6066
    fixture.start();
2✔
6067

6068
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6069
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6070
    session_1.bind();
2✔
6071
    session_2.bind();
2✔
6072

6073
    Timestamp now{std::chrono::system_clock::now()};
2✔
6074

6075
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6076
        auto& g = tr.get_group();
2✔
6077
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6078
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6079

6080
        auto foo = table->create_object_with_primary_key(123);
2✔
6081

6082
        // Create dictionary in Mixed property
6083
        foo.set_collection(col_any, CollectionType::Dictionary);
2✔
6084
        auto dict = foo.get_dictionary_ptr(col_any);
2✔
6085
        dict->insert("hello", "world");
2✔
6086
        dict->insert("cnt", 7);
2✔
6087
        dict->insert("when", now);
2✔
6088
        // Insert a List in a Dictionary
6089
        dict->insert_collection("list", CollectionType::List);
2✔
6090
        auto l = dict->get_list("list");
2✔
6091
        l->add(5);
2✔
6092
        l->insert_collection(1, CollectionType::List);
2✔
6093
        l->get_list(1)->add(7);
2✔
6094

6095
        auto bar = table->create_object_with_primary_key(456);
2✔
6096

6097
        // Create list in Mixed property
6098
        bar.set_collection(col_any, CollectionType::List);
2✔
6099
        auto list = bar.get_list_ptr<Mixed>(col_any);
2✔
6100
        list->add("John");
2✔
6101
        list->insert(0, 5);
2✔
6102
    });
2✔
6103

6104
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6105
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6106

6107
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6108
        auto table = tr.get_table("class_Table");
2✔
6109
        auto col_any = table->get_column_key("any");
2✔
6110
        CHECK_EQUAL(table->size(), 2);
2✔
6111

6112
        auto obj = table->get_object_with_primary_key(123);
2✔
6113
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6114
        CHECK(dict->get_value_data_type() == type_Mixed);
2✔
6115
        CHECK_EQUAL(dict->size(), 4);
2✔
6116

6117
        // Check that values are replicated
6118
        Mixed val = dict->get("hello");
2✔
6119
        CHECK_EQUAL(val.get_string(), "world");
2✔
6120
        val = dict->get("cnt");
2✔
6121
        CHECK_EQUAL(val.get_int(), 7);
2✔
6122
        val = dict->get("when");
2✔
6123
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6124
        CHECK_EQUAL(dict->get_list("list")->get(0).get_int(), 5);
2✔
6125

6126
        // Erase dictionary element
6127
        dict->erase("cnt");
2✔
6128
        // Replace dictionary element
6129
        dict->insert("hello", "goodbye");
2✔
6130

6131
        obj = table->get_object_with_primary_key(456);
2✔
6132
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6133
        // Check that values are replicated
6134
        CHECK_EQUAL(list->get(0).get_int(), 5);
2✔
6135
        CHECK_EQUAL(list->get(1).get_string(), "John");
2✔
6136
        // Replace list element
6137
        list->set(1, "Paul");
2✔
6138
        // Erase list element
6139
        list->remove(0);
2✔
6140
    });
2✔
6141

6142
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6143
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6144

6145
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6146
        auto table = tr.get_table("class_Table");
2✔
6147
        auto col_any = table->get_column_key("any");
2✔
6148
        CHECK_EQUAL(table->size(), 2);
2✔
6149

6150
        auto obj = table->get_object_with_primary_key(123);
2✔
6151
        auto dict = obj.get_dictionary(col_any);
2✔
6152
        CHECK_EQUAL(dict.size(), 3);
2✔
6153

6154
        Mixed val = dict["hello"];
2✔
6155
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
6156
        val = dict.get("when");
2✔
6157
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6158

6159
        // Dictionary clear
6160
        dict.clear();
2✔
6161

6162
        obj = table->get_object_with_primary_key(456);
2✔
6163
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6164
        CHECK_EQUAL(list->size(), 1);
2✔
6165
        CHECK_EQUAL(list->get(0).get_string(), "Paul");
2✔
6166
        // List clear
6167
        list->clear();
2✔
6168
    });
2✔
6169

6170
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6171
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6172

6173
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6174
        auto table = tr.get_table("class_Table");
2✔
6175
        auto col_any = table->get_column_key("any");
2✔
6176

6177
        CHECK_EQUAL(table->size(), 2);
2✔
6178

6179
        auto obj = table->get_object_with_primary_key(123);
2✔
6180
        auto dict = obj.get_dictionary(col_any);
2✔
6181
        CHECK_EQUAL(dict.size(), 0);
2✔
6182

6183
        // Replace dictionary with list on property
6184
        obj.set_collection(col_any, CollectionType::List);
2✔
6185

6186
        obj = table->get_object_with_primary_key(456);
2✔
6187
        auto list = obj.get_list<Mixed>(col_any);
2✔
6188
        CHECK_EQUAL(list.size(), 0);
2✔
6189
        // Replace list with Dictionary on property
6190
        obj.set_collection(col_any, CollectionType::Dictionary);
2✔
6191
    });
2✔
6192

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

6196
    {
2✔
6197
        ReadTransaction read_1{db_1};
2✔
6198
        ReadTransaction read_2{db_2};
2✔
6199

6200
        auto table = read_2.get_table("class_Table");
2✔
6201
        auto col_any = table->get_column_key("any");
2✔
6202

6203
        CHECK_EQUAL(table->size(), 2);
2✔
6204

6205
        auto obj = table->get_object_with_primary_key(123);
2✔
6206
        auto list = obj.get_list<Mixed>(col_any);
2✔
6207
        CHECK_EQUAL(list.size(), 0);
2✔
6208

6209
        obj = table->get_object_with_primary_key(456);
2✔
6210
        auto dict = obj.get_dictionary(col_any);
2✔
6211
        CHECK_EQUAL(dict.size(), 0);
2✔
6212

6213
        CHECK(compare_groups(read_1, read_2));
2✔
6214
    }
2✔
6215
}
2✔
6216

6217
TEST(Sync_CollectionInCollection)
6218
{
2✔
6219
    TEST_CLIENT_DB(db_1);
2✔
6220
    TEST_CLIENT_DB(db_2);
2✔
6221

6222
    TEST_DIR(dir);
2✔
6223
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6224
    fixture.start();
2✔
6225

6226
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6227
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6228
    session_1.bind();
2✔
6229
    session_2.bind();
2✔
6230

6231
    Timestamp now{std::chrono::system_clock::now()};
2✔
6232

6233
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6234
        auto& g = tr.get_group();
2✔
6235
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6236
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6237

6238
        auto foo = table->create_object_with_primary_key(123);
2✔
6239

6240
        // Create dictionary in Mixed property
6241
        foo.set_collection(col_any, CollectionType::Dictionary);
2✔
6242
        auto dict = foo.get_dictionary_ptr(col_any);
2✔
6243
        dict->insert("hello", "world");
2✔
6244
        dict->insert("cnt", 7);
2✔
6245
        dict->insert("when", now);
2✔
6246
        // Insert a List in a Dictionary
6247
        dict->insert_collection("collection", CollectionType::List);
2✔
6248
        auto l = dict->get_list("collection");
2✔
6249
        l->add(5);
2✔
6250

6251
        auto bar = table->create_object_with_primary_key(456);
2✔
6252

6253
        // Create list in Mixed property
6254
        bar.set_collection(col_any, CollectionType::List);
2✔
6255
        auto list = bar.get_list_ptr<Mixed>(col_any);
2✔
6256
        list->add("John");
2✔
6257
        list->insert(0, 5);
2✔
6258
        // Insert dictionary in List
6259
        list->insert_collection(2, CollectionType::Dictionary);
2✔
6260
        auto d = list->get_dictionary(2);
2✔
6261
        d->insert("One", 1);
2✔
6262
        d->insert("Two", 2);
2✔
6263
    });
2✔
6264

6265
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6266
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6267

6268
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6269
        auto table = tr.get_table("class_Table");
2✔
6270
        auto col_any = table->get_column_key("any");
2✔
6271
        CHECK_EQUAL(table->size(), 2);
2✔
6272

6273
        auto obj = table->get_object_with_primary_key(123);
2✔
6274
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6275
        CHECK(dict->get_value_data_type() == type_Mixed);
2✔
6276
        CHECK_EQUAL(dict->size(), 4);
2✔
6277

6278
        // Replace List with Dictionary
6279
        dict->insert_collection("collection", CollectionType::Dictionary);
2✔
6280
        auto d = dict->get_dictionary("collection");
2✔
6281
        d->insert("Three", 3);
2✔
6282
        d->insert("Four", 4);
2✔
6283

6284
        obj = table->get_object_with_primary_key(456);
2✔
6285
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6286
        // Replace Dictionary with List
6287
        list->set_collection(2, CollectionType::List);
2✔
6288
        auto l = list->get_list(2);
2✔
6289
        l->add(47);
2✔
6290
    });
2✔
6291

6292
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6293
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6294

6295
    {
2✔
6296
        ReadTransaction read_1{db_1};
2✔
6297
        ReadTransaction read_2{db_2};
2✔
6298

6299
        auto table = read_2.get_table("class_Table");
2✔
6300
        auto col_any = table->get_column_key("any");
2✔
6301

6302
        CHECK_EQUAL(table->size(), 2);
2✔
6303

6304
        auto obj = table->get_object_with_primary_key(123);
2✔
6305
        auto dict = obj.get_dictionary_ptr(col_any);
2✔
6306
        auto d = dict->get_dictionary("collection");
2✔
6307
        CHECK_EQUAL(d->get("Four").get_int(), 4);
2✔
6308

6309
        obj = table->get_object_with_primary_key(456);
2✔
6310
        auto list = obj.get_list_ptr<Mixed>(col_any);
2✔
6311
        auto l = list->get_list(2);
2✔
6312
        CHECK_EQUAL(l->get_any(0).get_int(), 47);
2✔
6313
    }
2✔
6314
}
2✔
6315

6316
TEST(Sync_DeleteCollectionInCollection)
6317
{
2✔
6318
    TEST_CLIENT_DB(db_1);
2✔
6319
    TEST_CLIENT_DB(db_2);
2✔
6320

6321
    TEST_DIR(dir);
2✔
6322
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6323
    fixture.start();
2✔
6324

6325
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6326
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6327
    session_1.bind();
2✔
6328
    session_2.bind();
2✔
6329

6330
    Timestamp now{std::chrono::system_clock::now()};
2✔
6331

6332
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6333
        auto& g = tr.get_group();
2✔
6334
        auto table = g.add_table_with_primary_key("class_Table", type_Int, "id");
2✔
6335
        auto col_any = table->add_column(type_Mixed, "any");
2✔
6336

6337
        auto foo = table->create_object_with_primary_key(123);
2✔
6338

6339
        // Create list in Mixed property
6340
        foo.set_json(col_any, R"([
2✔
6341
            {
2✔
6342
              "1_map": {
2✔
6343
                "2_string": "map value"
2✔
6344
               },
2✔
6345
              "1_list": ["list value"]
2✔
6346
            }
2✔
6347
        ])");
2✔
6348
    });
2✔
6349

6350
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6351
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6352

6353
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6354
        auto table = tr.get_table("class_Table");
2✔
6355
        auto col_any = table->get_column_key("any");
2✔
6356
        CHECK_EQUAL(table->size(), 1);
2✔
6357

6358
        auto obj = table->get_object_with_primary_key(123);
2✔
6359
        auto list = obj.get_list<Mixed>(col_any);
2✔
6360
        auto dict = list.get_dictionary(0);
2✔
6361
        dict->erase("1_map");
2✔
6362
    });
2✔
6363

6364
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6365
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6366

6367
    {
2✔
6368
        ReadTransaction read_1{db_1};
2✔
6369

6370
        auto table = read_1.get_table("class_Table");
2✔
6371
        auto col_any = table->get_column_key("any");
2✔
6372

6373
        CHECK_EQUAL(table->size(), 1);
2✔
6374

6375
        auto obj = table->get_object_with_primary_key(123);
2✔
6376
        auto list = obj.get_list<Mixed>(col_any);
2✔
6377
        auto dict = list.get_dictionary(0);
2✔
6378
        CHECK_EQUAL(dict->size(), 1);
2✔
6379
    }
2✔
6380
}
2✔
6381

6382
TEST(Sync_Dictionary_Links)
6383
{
2✔
6384
    TEST_CLIENT_DB(db_1);
2✔
6385
    TEST_CLIENT_DB(db_2);
2✔
6386

6387
    TEST_DIR(dir);
2✔
6388
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6389
    fixture.start();
2✔
6390

6391
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6392
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6393
    session_1.bind();
2✔
6394
    session_2.bind();
2✔
6395

6396
    // Test that we can transmit links.
6397

6398
    ColKey col_dict;
2✔
6399

6400
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6401
        auto& g = tr.get_group();
2✔
6402
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6403
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6404
        col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6405

6406
        auto foo = foos->create_object_with_primary_key(123);
2✔
6407
        auto a = bars->create_object_with_primary_key("a");
2✔
6408
        auto b = bars->create_object_with_primary_key("b");
2✔
6409

6410
        auto dict = foo.get_dictionary(col_dict);
2✔
6411
        dict.insert("a", a);
2✔
6412
        dict.insert("b", b);
2✔
6413
    });
2✔
6414

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

6418
    {
2✔
6419
        ReadTransaction tr{db_2};
2✔
6420

6421
        auto foos = tr.get_table("class_Foo");
2✔
6422
        auto bars = tr.get_table("class_Bar");
2✔
6423

6424
        CHECK_EQUAL(foos->size(), 1);
2✔
6425
        CHECK_EQUAL(bars->size(), 2);
2✔
6426

6427
        auto foo = foos->get_object_with_primary_key(123);
2✔
6428
        auto a = bars->get_object_with_primary_key("a");
2✔
6429
        auto b = bars->get_object_with_primary_key("b");
2✔
6430

6431
        auto dict = foo.get_dictionary(foos->get_column_key("dict"));
2✔
6432
        CHECK_EQUAL(dict.size(), 2);
2✔
6433

6434
        auto dict_a = dict.get("a");
2✔
6435
        auto dict_b = dict.get("b");
2✔
6436
        CHECK(dict_a == Mixed{a.get_link()});
2✔
6437
        CHECK(dict_b == Mixed{b.get_link()});
2✔
6438
    }
2✔
6439

6440
    // Test that we can create tombstones for objects in dictionaries.
6441

6442
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6443
        auto& g = tr.get_group();
2✔
6444

6445
        auto bars = g.get_table("class_Bar");
2✔
6446
        auto a = bars->get_object_with_primary_key("a");
2✔
6447
        a.invalidate();
2✔
6448

6449
        auto foos = g.get_table("class_Foo");
2✔
6450
        auto foo = foos->get_object_with_primary_key(123);
2✔
6451
        auto dict = foo.get_dictionary(col_dict);
2✔
6452

6453
        CHECK_EQUAL(dict.size(), 2);
2✔
6454
        CHECK((*dict.find("a")).second.is_null());
2✔
6455

6456
        CHECK(dict.find("b") != dict.end());
2✔
6457
    });
2✔
6458

6459
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6460
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6461

6462
    {
2✔
6463
        ReadTransaction tr{db_2};
2✔
6464

6465
        auto foos = tr.get_table("class_Foo");
2✔
6466
        auto bars = tr.get_table("class_Bar");
2✔
6467

6468
        CHECK_EQUAL(foos->size(), 1);
2✔
6469
        CHECK_EQUAL(bars->size(), 1);
2✔
6470

6471
        auto b = bars->get_object_with_primary_key("b");
2✔
6472

6473
        auto foo = foos->get_object_with_primary_key(123);
2✔
6474
        auto dict = foo.get_dictionary(col_dict);
2✔
6475

6476
        CHECK_EQUAL(dict.size(), 2);
2✔
6477
        CHECK((*dict.find("a")).second.is_null());
2✔
6478

6479
        CHECK(dict.find("b") != dict.end());
2✔
6480
        CHECK((*dict.find("b")).second == Mixed{b.get_link()});
2✔
6481
    }
2✔
6482
}
2✔
6483

6484
TEST(Sync_Set)
6485
{
2✔
6486
    // Test replication and synchronization of Set values.
6487

6488
    TEST_CLIENT_DB(db_1);
2✔
6489
    TEST_CLIENT_DB(db_2);
2✔
6490

6491
    TEST_DIR(dir);
2✔
6492
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6493
    fixture.start();
2✔
6494

6495
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6496
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6497
    session_1.bind();
2✔
6498
    session_2.bind();
2✔
6499

6500
    ColKey col_ints, col_strings, col_mixeds;
2✔
6501
    {
2✔
6502
        WriteTransaction wt{db_1};
2✔
6503
        auto t = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "pk");
2✔
6504
        col_ints = t->add_column_set(type_Int, "ints");
2✔
6505
        col_strings = t->add_column_set(type_String, "strings");
2✔
6506
        col_mixeds = t->add_column_set(type_Mixed, "mixeds");
2✔
6507

6508
        auto obj = t->create_object_with_primary_key(0);
2✔
6509

6510
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6511
        auto strings = obj.get_set<StringData>(col_strings);
2✔
6512
        auto mixeds = obj.get_set<Mixed>(col_mixeds);
2✔
6513

6514
        ints.insert(123);
2✔
6515
        ints.insert(456);
2✔
6516
        ints.insert(789);
2✔
6517
        ints.insert(123);
2✔
6518
        ints.insert(456);
2✔
6519
        ints.insert(789);
2✔
6520

6521
        CHECK_EQUAL(ints.size(), 3);
2✔
6522
        CHECK_EQUAL(ints.find(123), 0);
2✔
6523
        CHECK_EQUAL(ints.find(456), 1);
2✔
6524
        CHECK_EQUAL(ints.find(789), 2);
2✔
6525

6526
        strings.insert("a");
2✔
6527
        strings.insert("b");
2✔
6528
        strings.insert("c");
2✔
6529
        strings.insert("a");
2✔
6530
        strings.insert("b");
2✔
6531
        strings.insert("c");
2✔
6532

6533
        CHECK_EQUAL(strings.size(), 3);
2✔
6534
        CHECK_EQUAL(strings.find("a"), 0);
2✔
6535
        CHECK_EQUAL(strings.find("b"), 1);
2✔
6536
        CHECK_EQUAL(strings.find("c"), 2);
2✔
6537

6538
        mixeds.insert(Mixed{123});
2✔
6539
        mixeds.insert(Mixed{"a"});
2✔
6540
        mixeds.insert(Mixed{456.0});
2✔
6541
        mixeds.insert(Mixed{123});
2✔
6542
        mixeds.insert(Mixed{"a"});
2✔
6543
        mixeds.insert(Mixed{456.0});
2✔
6544

6545
        CHECK_EQUAL(mixeds.size(), 3);
2✔
6546
        CHECK_EQUAL(mixeds.find(123), 0);
2✔
6547
        CHECK_EQUAL(mixeds.find(456.0), 1);
2✔
6548
        CHECK_EQUAL(mixeds.find("a"), 2);
2✔
6549

6550
        wt.commit();
2✔
6551
    }
2✔
6552

6553
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6554
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6555

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

6561
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6562
        ints.insert(999);
2✔
6563
    });
2✔
6564

6565
    write_transaction(db_2, [=](WriteTransaction& wt) {
2✔
6566
        auto t = wt.get_table("class_Foo");
2✔
6567
        auto obj = t->get_object_with_primary_key(0);
2✔
6568

6569
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6570
        ints.insert(999);
2✔
6571
        ints.erase(999);
2✔
6572
    });
2✔
6573

6574
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6575
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6576
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6577
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6578

6579
    {
2✔
6580
        ReadTransaction read_1{db_1};
2✔
6581
        ReadTransaction read_2{db_2};
2✔
6582
        CHECK(compare_groups(read_1, read_2));
2✔
6583
    }
2✔
6584

6585
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6586
        auto t = wt.get_table("class_Foo");
2✔
6587
        auto obj = t->get_object_with_primary_key(0);
2✔
6588
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6589
        ints.clear();
2✔
6590
    });
2✔
6591

6592
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6593
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6594

6595
    {
2✔
6596
        ReadTransaction read_1{db_1};
2✔
6597
        ReadTransaction read_2{db_2};
2✔
6598
        CHECK(compare_groups(read_1, read_2));
2✔
6599
    }
2✔
6600
}
2✔
6601

6602
TEST(Sync_BundledRealmFile)
6603
{
2✔
6604
    TEST_CLIENT_DB(db);
2✔
6605
    SHARED_GROUP_TEST_PATH(path);
2✔
6606

6607
    TEST_DIR(dir);
2✔
6608
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6609
    fixture.start();
2✔
6610

6611
    Session session = fixture.make_bound_session(db);
2✔
6612

6613
    write_transaction(db, [](WriteTransaction& tr) {
2✔
6614
        auto foos = tr.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6615
        foos->create_object_with_primary_key(123);
2✔
6616
    });
2✔
6617

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

6621
    session.wait_for_upload_complete_or_client_stopped();
2✔
6622
    session.wait_for_download_complete_or_client_stopped();
2✔
6623

6624
    // Now we can
6625
    db->write_copy(path.c_str(), nullptr);
2✔
6626
}
2✔
6627

6628
TEST(Sync_UpgradeToClientHistory)
6629
{
2✔
6630
    DBOptions options;
2✔
6631
    options.logger = test_context.logger;
2✔
6632
    SHARED_GROUP_TEST_PATH(db_1_path);
2✔
6633
    SHARED_GROUP_TEST_PATH(db_2_path);
2✔
6634
    auto db_1 = DB::create(make_in_realm_history(), db_1_path, options);
2✔
6635
    auto db_2 = DB::create(make_in_realm_history(), db_2_path, options);
2✔
6636
    {
2✔
6637
        auto tr = db_1->start_write();
2✔
6638

6639
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
6640
        auto col_float = embedded->add_column(type_Float, "float");
2✔
6641
        auto col_additional = embedded->add_column_dictionary(*embedded, "additional");
2✔
6642

6643
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6644
        auto col_list = baas->add_column_list(type_Int, "list");
2✔
6645
        auto col_set = baas->add_column_set(type_Int, "set");
2✔
6646
        auto col_dict = baas->add_column_dictionary(type_Int, "dictionary");
2✔
6647
        auto col_child = baas->add_column(*embedded, "child");
2✔
6648

6649
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6650
        auto col_str = foos->add_column(type_String, "str");
2✔
6651
        auto col_children = foos->add_column_list(*embedded, "children");
2✔
6652

6653
        auto foobaas = tr->add_table_with_primary_key("class_FooBaa", type_ObjectId, "_id");
2✔
6654
        auto col_time = foobaas->add_column(type_Timestamp, "time");
2✔
6655

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

6658
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Hello");
2✔
6659
        auto children = foo.get_linklist(col_children);
2✔
6660
        children.create_and_insert_linked_object(0);
2✔
6661
        auto baa = baas->create_object_with_primary_key(999).set(col_link, foo.get_key());
2✔
6662
        auto obj = baa.create_and_set_linked_object(col_child);
2✔
6663
        obj.set(col_float, 42.f);
2✔
6664
        auto additional = obj.get_dictionary(col_additional);
2✔
6665
        additional.create_and_insert_linked_object("One").set(col_float, 1.f);
2✔
6666
        additional.create_and_insert_linked_object("Two").set(col_float, 2.f);
2✔
6667
        additional.create_and_insert_linked_object("Three").set(col_float, 3.f);
2✔
6668

6669
        auto list = baa.get_list<Int>(col_list);
2✔
6670
        list.add(1);
2✔
6671
        list.add(0);
2✔
6672
        list.add(2);
2✔
6673
        list.add(3);
2✔
6674
        list.set(1, 5);
2✔
6675
        list.remove(1);
2✔
6676
        auto set = baa.get_set<Int>(col_set);
2✔
6677
        set.insert(4);
2✔
6678
        set.insert(2);
2✔
6679
        set.insert(5);
2✔
6680
        set.insert(6);
2✔
6681
        set.erase(2);
2✔
6682
        auto dict = baa.get_dictionary(col_dict);
2✔
6683
        dict.insert("key6", 6);
2✔
6684
        dict.insert("key7", 7);
2✔
6685
        dict.insert("key8", 8);
2✔
6686
        dict.insert("key9", 9);
2✔
6687
        dict.erase("key6");
2✔
6688

6689
        for (int i = 0; i < 100; i++) {
202✔
6690
            foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i));
200✔
6691
        }
200✔
6692

6693
        tr->commit();
2✔
6694
    }
2✔
6695
    {
2✔
6696
        auto tr = db_2->start_write();
2✔
6697
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6698
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6699
        auto col_str = foos->add_column(type_String, "str");
2✔
6700
        auto col_link = baas->add_column(*foos, "link");
2✔
6701

6702
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Goodbye");
2✔
6703
        baas->create_object_with_primary_key(888).set(col_link, foo.get_key());
2✔
6704

6705
        tr->commit();
2✔
6706
    }
2✔
6707

6708
    db_1->create_new_history(make_client_replication());
2✔
6709
    db_2->create_new_history(make_client_replication());
2✔
6710

6711
    TEST_DIR(dir);
2✔
6712
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6713
    fixture.start();
2✔
6714

6715
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6716
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6717
    session_1.bind();
2✔
6718
    session_2.bind();
2✔
6719

6720
    write_transaction(db_1, [](WriteTransaction& tr) {
2✔
6721
        auto foos = tr.get_group().get_table("class_Foo");
2✔
6722
        foos->create_object_with_primary_key("456");
2✔
6723
    });
2✔
6724
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6725
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6726
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6727
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6728

6729
    // db_2->start_read()->to_json(std::cout);
6730
}
2✔
6731

6732
// This test is extracted from ClientReset_ThreeClients
6733
// because it uncovers a bug in how MSVC 2019 compiles
6734
// things in Changeset::get_key()
6735
TEST(Sync_MergeStringPrimaryKey)
6736
{
2✔
6737
    TEST_DIR(dir_1); // The server.
2✔
6738
    TEST_CLIENT_DB(db_1);
2✔
6739
    TEST_CLIENT_DB(db_2);
2✔
6740
    TEST_DIR(metadata_dir_1);
2✔
6741
    TEST_DIR(metadata_dir_2);
2✔
6742

6743
    const std::string server_path = "/data";
2✔
6744

6745
    std::string real_path_1, real_path_2;
2✔
6746

6747
    auto create_schema = [&](Transaction& group) {
4✔
6748
        TableRef table_0 = group.add_table_with_primary_key("class_table_0", type_Int, "id");
4✔
6749
        table_0->add_column(type_Int, "int");
4✔
6750
        table_0->add_column(type_Bool, "bool");
4✔
6751
        table_0->add_column(type_Float, "float");
4✔
6752
        table_0->add_column(type_Double, "double");
4✔
6753
        table_0->add_column(type_Timestamp, "timestamp");
4✔
6754

6755
        TableRef table_1 = group.add_table_with_primary_key("class_table_1", type_Int, "pk_int");
4✔
6756
        table_1->add_column(type_String, "String");
4✔
6757

6758
        TableRef table_2 = group.add_table_with_primary_key("class_table_2", type_String, "pk_string");
4✔
6759
        table_2->add_column_list(type_String, "array_string");
4✔
6760
    };
4✔
6761

6762
    // First we make changesets. Then we upload them.
6763
    {
2✔
6764
        ClientServerFixture fixture(dir_1, test_context);
2✔
6765
        fixture.start();
2✔
6766
        real_path_1 = fixture.map_virtual_to_real_path(server_path);
2✔
6767

6768
        {
2✔
6769
            WriteTransaction wt{db_1};
2✔
6770
            create_schema(wt);
2✔
6771
            wt.commit();
2✔
6772
        }
2✔
6773
        {
2✔
6774
            WriteTransaction wt{db_2};
2✔
6775
            create_schema(wt);
2✔
6776

6777
            TableRef table_2 = wt.get_table("class_table_2");
2✔
6778
            auto col = table_2->get_column_key("array_string");
2✔
6779
            auto list_string = table_2->create_object_with_primary_key("aaa").get_list<String>(col);
2✔
6780
            list_string.add("a");
2✔
6781
            list_string.add("b");
2✔
6782

6783
            wt.commit();
2✔
6784
        }
2✔
6785

6786
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
6787
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
6788

6789
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
6790
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
6791
        // Download completion is not important.
6792
    }
2✔
6793
}
2✔
6794

6795
TEST(Sync_DifferentUsersMultiplexing)
6796
{
2✔
6797
    ClientServerFixture::Config fixture_config;
2✔
6798
    fixture_config.one_connection_per_session = false;
2✔
6799

6800
    TEST_DIR(server_dir);
2✔
6801
    ClientServerFixture fixture(server_dir, test_context, std::move(fixture_config));
2✔
6802

6803
    struct SessionBundle {
2✔
6804
        test_util::DBTestPathGuard path_guard;
2✔
6805
        DBRef db;
2✔
6806
        Session sess;
2✔
6807

6808
        SessionBundle(unit_test::TestContext& ctx, ClientServerFixture& fixture, std::string name,
2✔
6809
                      std::string signed_token, std::string user_id)
2✔
6810
            : path_guard(realm::test_util::get_test_path(ctx.get_test_name(), "." + name + ".realm"))
5✔
6811
            , db(DB::create(make_client_replication(), path_guard))
5✔
6812
        {
8✔
6813
            Session::Config config;
8✔
6814
            config.signed_user_token = signed_token;
8✔
6815
            config.user_id = user_id;
8✔
6816
            sess = fixture.make_bound_session(db, "/test", std::move(config));
8✔
6817
            sess.wait_for_download_complete_or_client_stopped();
8✔
6818
        }
8✔
6819
    };
2✔
6820

6821
    fixture.start();
2✔
6822

6823
    SessionBundle user_1_sess_1(test_context, fixture, "user_1_db_1", g_user_0_token, "user_0");
2✔
6824
    SessionBundle user_2_sess_1(test_context, fixture, "user_2_db_1", g_user_1_token, "user_1");
2✔
6825
    SessionBundle user_1_sess_2(test_context, fixture, "user_1_db_2", g_user_0_token, "user_0");
2✔
6826
    SessionBundle user_2_sess_2(test_context, fixture, "user_2_db_2", g_user_1_token, "user_1");
2✔
6827

6828
    CHECK_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6829
                user_1_sess_2.sess.get_appservices_connection_id());
2✔
6830
    CHECK_EQUAL(user_2_sess_1.sess.get_appservices_connection_id(),
2✔
6831
                user_2_sess_2.sess.get_appservices_connection_id());
2✔
6832
    CHECK_NOT_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6833
                    user_2_sess_1.sess.get_appservices_connection_id());
2✔
6834
    CHECK_NOT_EQUAL(user_1_sess_2.sess.get_appservices_connection_id(),
2✔
6835
                    user_2_sess_2.sess.get_appservices_connection_id());
2✔
6836
}
2✔
6837

6838
TEST(Sync_TransformAgainstEmptyReciprocalChangeset)
6839
{
2✔
6840
    TEST_CLIENT_DB(seed_db);
2✔
6841
    TEST_CLIENT_DB(db_1);
2✔
6842
    TEST_CLIENT_DB(db_2);
2✔
6843

6844
    {
2✔
6845
        auto tr = seed_db->start_write();
2✔
6846
        // Create schema: single table with array of ints as property.
6847
        auto table = tr->add_table_with_primary_key("class_table", type_Int, "_id");
2✔
6848
        table->add_column_list(type_Int, "ints");
2✔
6849
        table->add_column(type_String, "string");
2✔
6850
        tr->commit_and_continue_writing();
2✔
6851

6852
        // Create object and initialize array.
6853
        table = tr->get_table("class_table");
2✔
6854
        auto obj = table->create_object_with_primary_key(42);
2✔
6855
        auto ints = obj.get_list<int64_t>("ints");
2✔
6856
        for (auto i = 0; i < 8; ++i) {
18✔
6857
            ints.insert(i, i);
16✔
6858
        }
16✔
6859
        tr->commit();
2✔
6860
    }
2✔
6861

6862
    {
2✔
6863
        TEST_DIR(dir);
2✔
6864
        MultiClientServerFixture fixture(3, 1, dir, test_context);
2✔
6865
        fixture.start();
2✔
6866

6867
        util::Optional<Session> seed_session = fixture.make_bound_session(0, seed_db, 0, "/test");
2✔
6868
        util::Optional<Session> db_1_session = fixture.make_bound_session(1, db_1, 0, "/test");
2✔
6869
        util::Optional<Session> db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6870

6871
        seed_session->wait_for_upload_complete_or_client_stopped();
2✔
6872
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6873
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6874
        seed_session.reset();
2✔
6875
        db_2_session.reset();
2✔
6876

6877
        auto move_element = [&](const DBRef& db, size_t from, size_t to, size_t string_size = 0) {
8✔
6878
            auto wt = db->start_write();
8✔
6879
            auto table = wt->get_table("class_table");
8✔
6880
            auto obj = table->get_object_with_primary_key(42);
8✔
6881
            auto ints = obj.get_list<int64_t>("ints");
8✔
6882
            ints.move(from, to);
8✔
6883
            obj.set("string", std::string(string_size, 'a'));
8✔
6884
            wt->commit();
8✔
6885
        };
8✔
6886

6887
        // Client 1 uploads two move instructions.
6888
        move_element(db_1, 7, 2);
2✔
6889
        move_element(db_1, 7, 6);
2✔
6890

6891
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6892

6893
        std::this_thread::sleep_for(std::chrono::milliseconds{10});
2✔
6894

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

6900
        // First change discards the first change (move(7, 2)) of Client 1.
6901
        move_element(db_2, 7, 0, 200 * 1024);
2✔
6902
        // Second change is tranformed against an empty reciprocal changeset as result of the change above.
6903
        move_element(db_2, 7, 5);
2✔
6904
        db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6905

6906
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6907
        db_2_session->wait_for_upload_complete_or_client_stopped();
2✔
6908

6909
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6910
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6911
    }
2✔
6912

6913
    ReadTransaction rt_1(db_1);
2✔
6914
    ReadTransaction rt_2(db_2);
2✔
6915
    const Group& group_1 = rt_1;
2✔
6916
    const Group& group_2 = rt_2;
2✔
6917
    group_1.verify();
2✔
6918
    group_2.verify();
2✔
6919
    CHECK(compare_groups(rt_1, rt_2));
2✔
6920
}
2✔
6921

6922
#endif // !REALM_MOBILE
6923

6924
// Tests that an empty reciprocal changesets is set and retrieved correctly.
6925
TEST(Sync_SetAndGetEmptyReciprocalChangeset)
6926
{
2✔
6927
    using namespace realm;
2✔
6928
    using namespace realm::sync::instr;
2✔
6929
    using realm::sync::Changeset;
2✔
6930

6931
    TEST_CLIENT_DB(db);
2✔
6932

6933
    auto& history = get_history(db);
2✔
6934
    history.set_client_file_ident(SaltedFileIdent{1, 0x1234567812345678}, false);
2✔
6935
    timestamp_type timestamp{1};
2✔
6936
    history.set_local_origin_timestamp_source([&] {
6✔
6937
        return ++timestamp;
6✔
6938
    });
6✔
6939

6940
    auto latest_local_version = [&] {
2✔
6941
        auto tr = db->start_write();
2✔
6942
        // Create schema: single table with array of ints as property.
6943
        tr->add_table_with_primary_key("class_table", type_Int, "_id")->add_column_list(type_Int, "ints");
2✔
6944
        tr->commit_and_continue_writing();
2✔
6945

6946
        // Create object and initialize array.
6947
        TableRef table = tr->get_table("class_table");
2✔
6948
        auto obj = table->create_object_with_primary_key(42);
2✔
6949
        auto ints = obj.get_list<int64_t>("ints");
2✔
6950
        for (auto i = 0; i < 8; ++i) {
18✔
6951
            ints.insert(i, i);
16✔
6952
        }
16✔
6953
        tr->commit_and_continue_writing();
2✔
6954

6955
        // Move element in array.
6956
        ints.move(7, 2);
2✔
6957
        return tr->commit();
2✔
6958
    }();
2✔
6959

6960
    // Create changeset which moves element from index 7 to index 0 in array.
6961
    // This changeset will discard the previous move (reciprocal changeset), leaving the local reciprocal changesets
6962
    // with no instructions (empty).
6963
    Changeset changeset;
2✔
6964
    ArrayMove instr;
2✔
6965
    instr.table = changeset.intern_string("table");
2✔
6966
    instr.object = instr::PrimaryKey{42};
2✔
6967
    instr.field = changeset.intern_string("ints");
2✔
6968
    instr.path.push_back(7);
2✔
6969
    instr.ndx_2 = 0;
2✔
6970
    instr.prior_size = 8;
2✔
6971
    changeset.push_back(instr);
2✔
6972
    changeset.version = 1;
2✔
6973
    changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6974
    changeset.origin_timestamp = timestamp;
2✔
6975
    changeset.origin_file_ident = 2;
2✔
6976

6977
    ChangesetEncoder::Buffer encoded;
2✔
6978
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
6979
    encode_changeset(changeset, encoded);
2✔
6980
    server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
2✔
6981
                                           BinaryData(encoded.data(), encoded.size()), changeset.origin_timestamp,
2✔
6982
                                           changeset.origin_file_ident);
2✔
6983

6984
    SyncProgress progress = {};
2✔
6985
    progress.download.server_version = changeset.version;
2✔
6986
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6987
    progress.latest_server_version.version = changeset.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

6996
    bool is_compressed = false;
2✔
6997
    auto data = history.get_reciprocal_transform(latest_local_version, is_compressed);
2✔
6998
    Changeset reciprocal_changeset;
2✔
6999
    ChunkedBinaryInputStream in{data};
2✔
7000
    if (is_compressed) {
2✔
7001
        size_t total_size;
2✔
7002
        auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
2✔
7003
        CHECK(decompressed);
2✔
7004
        sync::parse_changeset(*decompressed, reciprocal_changeset); // Throws
2✔
7005
    }
2✔
7006
    else {
×
7007
        sync::parse_changeset(in, reciprocal_changeset); // Throws
×
7008
    }
×
7009
    // The only instruction in the reciprocal changeset was discarded during OT.
7010
    CHECK(reciprocal_changeset.empty());
2✔
7011
}
2✔
7012

7013
TEST(Sync_InvalidChangesetFromServer)
7014
{
2✔
7015
    TEST_CLIENT_DB(db);
2✔
7016

7017
    auto& history = get_history(db);
2✔
7018
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
7019

7020
    instr::CreateObject bad_instr;
2✔
7021
    bad_instr.object = InternString{1};
2✔
7022
    bad_instr.table = InternString{2};
2✔
7023

7024
    Changeset changeset;
2✔
7025
    changeset.push_back(bad_instr);
2✔
7026

7027
    ChangesetEncoder::Buffer encoded;
2✔
7028
    encode_changeset(changeset, encoded);
2✔
7029
    RemoteChangeset server_changeset;
2✔
7030
    server_changeset.origin_file_ident = 1;
2✔
7031
    server_changeset.remote_version = 1;
2✔
7032
    server_changeset.data = BinaryData(encoded.data(), encoded.size());
2✔
7033

7034
    VersionInfo version_info;
2✔
7035
    auto transact = db->start_read();
2✔
7036
    CHECK_THROW_EX(history.integrate_server_changesets({}, nullptr, util::Span(&server_changeset, 1), version_info,
2✔
7037
                                                       DownloadBatchState::SteadyState, *test_context.logger,
2✔
7038
                                                       transact),
2✔
7039
                   sync::IntegrationException,
2✔
7040
                   StringData(e.what()).contains("Failed to parse received changeset: Invalid interned string"));
2✔
7041
}
2✔
7042

7043
TEST(Sync_ServerVersionsSkippedFromDownloadCursor)
7044
{
2✔
7045
    TEST_CLIENT_DB(db);
2✔
7046

7047
    auto& history = get_history(db);
2✔
7048
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
7049
    timestamp_type timestamp{1};
2✔
7050
    history.set_local_origin_timestamp_source([&] {
2✔
7051
        return ++timestamp;
2✔
7052
    });
2✔
7053

7054
    auto latest_local_version = [&] {
2✔
7055
        auto tr = db->start_write();
2✔
7056
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
7057
        return tr->commit();
2✔
7058
    }();
2✔
7059

7060
    Changeset server_changeset;
2✔
7061
    server_changeset.version = 10;
2✔
7062
    server_changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
7063
    server_changeset.origin_timestamp = ++timestamp;
2✔
7064
    server_changeset.origin_file_ident = 1;
2✔
7065

7066
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
7067
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
7068
    encoded.emplace_back();
2✔
7069
    encode_changeset(server_changeset, encoded.back());
2✔
7070
    server_changesets_encoded.emplace_back(server_changeset.version, server_changeset.last_integrated_remote_version,
2✔
7071
                                           BinaryData(encoded.back().data(), encoded.back().size()),
2✔
7072
                                           server_changeset.origin_timestamp, server_changeset.origin_file_ident);
2✔
7073

7074
    SyncProgress progress = {};
2✔
7075
    // The server skips 10 server versions.
7076
    progress.download.server_version = server_changeset.version + 10;
2✔
7077
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
7078
    progress.latest_server_version.version = server_changeset.version + 15;
2✔
7079
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
7080

7081
    uint_fast64_t downloadable_bytes = 0;
2✔
7082
    VersionInfo version_info;
2✔
7083
    auto transact = db->start_read();
2✔
7084
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
7085
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
7086

7087
    version_type current_version;
2✔
7088
    SaltedFileIdent file_ident;
2✔
7089
    SyncProgress expected_progress;
2✔
7090
    history.get_status(current_version, file_ident, expected_progress);
2✔
7091

7092
    // Check progress is reported correctly.
7093
    CHECK_EQUAL(progress.latest_server_version.salt, expected_progress.latest_server_version.salt);
2✔
7094
    CHECK_EQUAL(progress.latest_server_version.version, expected_progress.latest_server_version.version);
2✔
7095
    CHECK_EQUAL(progress.download.last_integrated_client_version,
2✔
7096
                expected_progress.download.last_integrated_client_version);
2✔
7097
    CHECK_EQUAL(progress.download.server_version, expected_progress.download.server_version);
2✔
7098
    CHECK_EQUAL(progress.upload.client_version, expected_progress.upload.client_version);
2✔
7099
    CHECK_EQUAL(progress.upload.last_integrated_server_version,
2✔
7100
                expected_progress.upload.last_integrated_server_version);
2✔
7101
}
2✔
7102

7103
TEST(Sync_NonIncreasingServerVersions)
7104
{
2✔
7105
    TEST_CLIENT_DB(db);
2✔
7106

7107
    auto& history = get_history(db);
2✔
7108
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
7109
    timestamp_type timestamp{1};
2✔
7110
    history.set_local_origin_timestamp_source([&] {
2✔
7111
        return ++timestamp;
2✔
7112
    });
2✔
7113

7114
    auto latest_local_version = [&] {
2✔
7115
        auto tr = db->start_write();
2✔
7116
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
7117
        return tr->commit();
2✔
7118
    }();
2✔
7119

7120
    std::vector<Changeset> server_changesets;
2✔
7121
    auto prep_changeset = [&](auto pk_name, auto int_col_val) {
8✔
7122
        Changeset changeset;
8✔
7123
        changeset.version = 10;
8✔
7124
        changeset.last_integrated_remote_version = latest_local_version - 1;
8✔
7125
        changeset.origin_timestamp = ++timestamp;
8✔
7126
        changeset.origin_file_ident = 1;
8✔
7127
        instr::PrimaryKey pk{changeset.intern_string(pk_name)};
8✔
7128
        auto table_name = changeset.intern_string("foo");
8✔
7129
        auto col_name = changeset.intern_string("int_col");
8✔
7130
        instr::EraseObject erase_1;
8✔
7131
        erase_1.object = pk;
8✔
7132
        erase_1.table = table_name;
8✔
7133
        changeset.push_back(erase_1);
8✔
7134
        instr::CreateObject create_1;
8✔
7135
        create_1.object = pk;
8✔
7136
        create_1.table = table_name;
8✔
7137
        changeset.push_back(create_1);
8✔
7138
        instr::Update update_1;
8✔
7139
        update_1.table = table_name;
8✔
7140
        update_1.object = pk;
8✔
7141
        update_1.field = col_name;
8✔
7142
        update_1.value = instr::Payload{int64_t(int_col_val)};
8✔
7143
        changeset.push_back(update_1);
8✔
7144
        server_changesets.push_back(std::move(changeset));
8✔
7145
    };
8✔
7146
    prep_changeset("bizz", 1);
2✔
7147
    prep_changeset("buzz", 2);
2✔
7148
    prep_changeset("baz", 3);
2✔
7149
    prep_changeset("bar", 4);
2✔
7150
    ++server_changesets.back().version;
2✔
7151

7152
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
7153
    std::vector<RemoteChangeset> server_changesets_encoded;
2✔
7154
    for (const auto& changeset : server_changesets) {
8✔
7155
        encoded.emplace_back();
8✔
7156
        encode_changeset(changeset, encoded.back());
8✔
7157
        server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
8✔
7158
                                               BinaryData(encoded.back().data(), encoded.back().size()),
8✔
7159
                                               changeset.origin_timestamp, changeset.origin_file_ident);
8✔
7160
    }
8✔
7161

7162
    SyncProgress progress = {};
2✔
7163
    progress.download.server_version = server_changesets.back().version;
2✔
7164
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
7165
    progress.latest_server_version.version = server_changesets.back().version;
2✔
7166
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
7167

7168
    uint_fast64_t downloadable_bytes = 0;
2✔
7169
    VersionInfo version_info;
2✔
7170
    auto transact = db->start_read();
2✔
7171
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
7172
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
7173
}
2✔
7174

7175
TEST(Sync_DanglingLinksCountInPriorSize)
7176
{
2✔
7177
    SHARED_GROUP_TEST_PATH(path);
2✔
7178
    ClientReplication repl;
2✔
7179
    auto local_db = realm::DB::create(repl, path);
2✔
7180
    auto& history = repl.get_history();
2✔
7181
    history.set_client_file_ident(sync::SaltedFileIdent{1, 123456}, true);
2✔
7182

7183
    version_type last_version, last_version_observed = 0;
2✔
7184
    auto dump_uploadable = [&] {
4✔
7185
        UploadCursor upload_cursor{last_version_observed, 0};
4✔
7186
        std::vector<sync::ClientHistory::UploadChangeset> changesets_to_upload;
4✔
7187
        version_type locked_server_version = 0;
4✔
7188
        history.find_uploadable_changesets(upload_cursor, last_version, changesets_to_upload, locked_server_version);
4✔
7189
        CHECK_EQUAL(changesets_to_upload.size(), static_cast<size_t>(1));
4✔
7190
        realm::sync::Changeset parsed_changeset;
4✔
7191
        auto unparsed_changeset = changesets_to_upload[0].changeset.get_first_chunk();
4✔
7192
        realm::util::SimpleInputStream changeset_stream(unparsed_changeset);
4✔
7193
        realm::sync::parse_changeset(changeset_stream, parsed_changeset);
4✔
7194
        test_context.logger->info("changeset at version %1: %2", last_version, parsed_changeset);
4✔
7195
        last_version_observed = last_version;
4✔
7196
        return parsed_changeset;
4✔
7197
    };
4✔
7198

7199
    TableKey source_table_key, target_table_key;
2✔
7200
    {
2✔
7201
        auto wt = local_db->start_write();
2✔
7202
        auto source_table = wt->add_table_with_primary_key("class_source", type_String, "_id");
2✔
7203
        auto target_table = wt->add_table_with_primary_key("class_target", type_String, "_id");
2✔
7204
        source_table->add_column_list(*target_table, "links");
2✔
7205

7206
        source_table_key = source_table->get_key();
2✔
7207
        target_table_key = target_table->get_key();
2✔
7208

7209
        auto obj_to_keep = target_table->create_object_with_primary_key(std::string{"target1"});
2✔
7210
        auto obj_to_delete = target_table->create_object_with_primary_key(std::string{"target2"});
2✔
7211
        auto source_obj = source_table->create_object_with_primary_key(std::string{"source"});
2✔
7212

7213
        auto links_list = source_obj.get_linklist("links");
2✔
7214
        links_list.add(obj_to_keep.get_key());
2✔
7215
        links_list.add(obj_to_delete.get_key());
2✔
7216
        last_version = wt->commit();
2✔
7217
    }
2✔
7218

7219
    dump_uploadable();
2✔
7220

7221
    {
2✔
7222
        // Simulate removing the object via the sync client so we get a dangling link
7223
        TempShortCircuitReplication disable_repl(repl);
2✔
7224
        auto wt = local_db->start_write();
2✔
7225
        auto target_table = wt->get_table(target_table_key);
2✔
7226
        auto obj = target_table->get_object_with_primary_key(std::string{"target2"});
2✔
7227
        obj.invalidate();
2✔
7228
        last_version = wt->commit();
2✔
7229
    }
2✔
7230

7231
    {
2✔
7232
        auto wt = local_db->start_write();
2✔
7233
        auto source_table = wt->get_table(source_table_key);
2✔
7234
        auto target_table = wt->get_table(target_table_key);
2✔
7235

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

7238
        auto source_obj = source_table->get_object_with_primary_key(std::string{"source"});
2✔
7239
        auto links_list = source_obj.get_linklist("links");
2✔
7240
        links_list.add(obj_to_add.get_key());
2✔
7241
        last_version = wt->commit();
2✔
7242
    }
2✔
7243

7244
    auto changeset = dump_uploadable();
2✔
7245
    CHECK_EQUAL(changeset.size(), static_cast<size_t>(2));
2✔
7246
    auto changeset_it = changeset.end();
2✔
7247
    --changeset_it;
2✔
7248
    auto last_instr = *changeset_it;
2✔
7249
    CHECK_EQUAL(last_instr->type(), Instruction::Type::ArrayInsert);
2✔
7250
    auto arr_insert_instr = last_instr->get_as<Instruction::ArrayInsert>();
2✔
7251
    CHECK_EQUAL(changeset.get_string(arr_insert_instr.table), StringData("source"));
2✔
7252
    CHECK(arr_insert_instr.value.type == sync::instr::Payload::Type::Link);
2✔
7253
    CHECK_EQUAL(changeset.get_string(mpark::get<InternString>(arr_insert_instr.value.data.link.target)),
2✔
7254
                StringData("target3"));
2✔
7255
    CHECK_EQUAL(arr_insert_instr.prior_size, 2);
2✔
7256
}
2✔
7257

7258
// This test calls row_for_object_id() for various object ids and tests that
7259
// the right value is returned including that no assertions are hit.
7260
TEST(Sync_RowForGlobalKey)
7261
{
2✔
7262
    TEST_CLIENT_DB(db);
2✔
7263

7264
    {
2✔
7265
        WriteTransaction wt(db);
2✔
7266
        TableRef table = wt.add_table("class_foo");
2✔
7267
        table->add_column(type_Int, "i");
2✔
7268
        wt.commit();
2✔
7269
    }
2✔
7270

7271
    // Check that various object_ids are not in the table.
7272
    {
2✔
7273
        ReadTransaction rt(db);
2✔
7274
        ConstTableRef table = rt.get_table("class_foo");
2✔
7275
        CHECK(table);
2✔
7276

7277
        // Default constructed GlobalKey
7278
        {
2✔
7279
            GlobalKey object_id;
2✔
7280
            auto row_ndx = table->get_objkey(object_id);
2✔
7281
            CHECK_NOT(row_ndx);
2✔
7282
        }
2✔
7283

7284
        // GlobalKey with small lo and hi values
7285
        {
2✔
7286
            GlobalKey object_id{12, 24};
2✔
7287
            auto row_ndx = table->get_objkey(object_id);
2✔
7288
            CHECK_NOT(row_ndx);
2✔
7289
        }
2✔
7290

7291
        // GlobalKey with lo and hi values past the 32 bit limit.
7292
        {
2✔
7293
            GlobalKey object_id{uint_fast64_t(1) << 50, uint_fast64_t(1) << 52};
2✔
7294
            auto row_ndx = table->get_objkey(object_id);
2✔
7295
            CHECK_NOT(row_ndx);
2✔
7296
        }
2✔
7297
    }
2✔
7298
}
2✔
7299

7300
TEST(Sync_FirstPromoteToWriteAdvancesRead)
7301
{
2✔
7302
    TEST_CLIENT_DB(db);
2✔
7303
    auto db2 = DB::create(make_client_replication(), db_path);
2✔
7304
    auto read = db->start_read();
2✔
7305
    db2->start_write()->commit();
2✔
7306
    // This will hit `ClientHistory::update_from_ref_and_version()` with m_group
7307
    // unset since it's advancing the read transaction without ever having been
7308
    // in a write transaction before.
7309
    read->promote_to_write();
2✔
7310
}
2✔
7311

7312

7313
} // 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