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

realm / realm-core / 1750

11 Oct 2023 08:13PM UTC coverage: 91.591% (+0.03%) from 91.563%
1750

push

Evergreen

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

Simplify internal commit notification

94312 of 173480 branches covered (0.0%)

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

47 existing lines in 12 files now uncovered.

230479 of 251640 relevant lines covered (91.59%)

6564628.95 hits per line

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

94.03
/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);                                                                             \
324✔
107
    auto name = DB::create(make_client_replication(), name##_path);
324✔
108

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

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

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

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

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

1✔
142
    int nerrors = 0;
2✔
143

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

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

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

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

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

173

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

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

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

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

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

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

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

212

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

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

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

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

236

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

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

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

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

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

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

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

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

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

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

298

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

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

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

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

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

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

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

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

347

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

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

372

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

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

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

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

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

408

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

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

430

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

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

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

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

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

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

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

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

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

482

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

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

507

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

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

528

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

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

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

557

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

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

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

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

591

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

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

621

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

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

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

647

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

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

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

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

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

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

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

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

695

696
TEST(Sync_Merge)
697
{
2✔
698

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

813

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

832

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

852

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

871

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

886

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

901

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

917

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

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

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

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

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

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

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

957

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

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

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

992

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

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

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

1025

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

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

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

1046

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

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

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

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

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

1087

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1319

1320
    // Check the end result.
1321

1322
    size_t size_link_list_a;
1323
    size_t size_link_list_b;
1324

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

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

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

1349

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1541

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

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

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

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

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

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

1601

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

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

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

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

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

1643

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

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

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

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

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

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

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

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

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

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

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

1682

1683
namespace {
1684

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

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

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

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

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

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

1744
} // namespace
1745

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

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

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

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

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

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

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

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

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

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

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

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

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

1791

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

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

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

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

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

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

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

1851

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

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

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

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

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

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

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

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

1926

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

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

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

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

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

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

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

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

1992

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

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

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

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

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

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

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

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

2073

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

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

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

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

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

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

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

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

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

2234

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

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

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

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

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

2292

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

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

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

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

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

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

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

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

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

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

2354

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

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

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

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

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

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

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

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

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

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

2418

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

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

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

1✔
2438
    TEST_CLIENT_DB(db_upload);
2✔
2439

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

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

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

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

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

1✔
2487

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

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

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

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

1✔
2508

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

1✔
2515

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

2524

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

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

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

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

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

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

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

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

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

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

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

2588

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

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

2602

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

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

2624

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

1✔
2629
    bool did_see_error_for_valid = false;
2✔
2630

1✔
2631
    TEST_DIR(server_dir);
2✔
2632

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

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

1✔
2642
    // Insert some dummy data
1✔
2643
    WriteTransaction wt_valid{db_valid};
2✔
2644
    wt_valid.get_group().add_table_with_primary_key("class_a", type_Int, "id");
2✔
2645
    session_valid.wait_for_upload_complete_or_client_stopped();
2✔
2646

1✔
2647
    CHECK_NOT(did_see_error_for_valid);
2✔
2648
}
2✔
2649

2650

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

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

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

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

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

1✔
2676
    fixture.start();
2✔
2677
    session.wait_for_download_complete_or_client_stopped();
2✔
2678
}
2✔
2679

2680

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

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

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

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

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

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

2716

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

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

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

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

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

2746

2747
#if REALM_HAVE_SECURE_TRANSPORT
2748

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

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

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

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

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

2772
    fixture.start();
1✔
2773
    session.wait_for_download_complete_or_client_stopped();
1✔
2774
}
1✔
2775

2776
#endif // REALM_HAVE_SECURE_TRANSPORT
2777

2778

2779
#if REALM_HAVE_OPENSSL
2780

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

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

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

1✔
2802
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2803

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

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

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

2818

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

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

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

1✔
2842
        CHECK_EQUAL(expected, pem.substr(0, expected.size()));
1✔
2843

1✔
2844
        return false;
1✔
2845
    };
1✔
2846

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

1✔
2852
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2853

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

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

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

2875

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

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

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

2✔
2903
        return true;
2✔
2904
    };
2✔
2905

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

1✔
2911
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2912

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

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

2926

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

2935
    TEST_CLIENT_DB(db);
2936

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

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

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

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

2967
    client.shutdown_and_wait();
2968
}
2969

2970
#endif // REALM_HAVE_OPENSSL
2971

2972

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

1✔
2987
    uint_fast64_t global_snapshot_version;
2✔
2988

1✔
2989
    {
2✔
2990
        int handler_entry = 0;
2✔
2991

1✔
2992
        bool cond_var_signaled = false;
2✔
2993
        std::mutex mutex;
2✔
2994
        std::condition_variable cond_var;
2✔
2995

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

1✔
3003
        ClientServerFixture fixture(server_dir, test_context);
2✔
3004
        fixture.start();
2✔
3005

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

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

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

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

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

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

1✔
3047
        session.wait_for_upload_complete_or_client_stopped();
2✔
3048
        session.wait_for_download_complete_or_client_stopped();
2✔
3049

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

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

1✔
3064
        session.wait_for_upload_complete_or_client_stopped();
2✔
3065
        session.wait_for_download_complete_or_client_stopped();
2✔
3066

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

1✔
3073
        global_snapshot_version = snapshot_version;
2✔
3074
    }
2✔
3075

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

1✔
3082
        bool cond_var_signaled = false;
2✔
3083
        std::mutex mutex;
2✔
3084
        std::condition_variable cond_var;
2✔
3085

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

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

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

1✔
3101
        int number_of_handler_calls = 0;
2✔
3102

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

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

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

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

3132

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

1✔
3145
    ClientServerFixture fixture(server_dir, test_context);
2✔
3146
    fixture.start();
2✔
3147

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

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

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

1✔
3169
    session_1.set_progress_handler(progress_handler_1);
2✔
3170

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

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

1✔
3189
    session_2.set_progress_handler(progress_handler_2);
2✔
3190

1✔
3191
    session_1.bind();
2✔
3192
    session_2.bind();
2✔
3193

1✔
3194
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3195
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3196
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3197
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3198

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

1✔
3206
    CHECK_EQUAL(uploaded_bytes_1, 0);
2✔
3207
    CHECK_EQUAL(uploadable_bytes_1, 0);
2✔
3208

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

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

1✔
3219
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3220
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3221
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3222
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3223

1✔
3224
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3225
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3226

1✔
3227
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3228
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3229

1✔
3230
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3231
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3232

1✔
3233
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3234
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3235

1✔
3236
    CHECK_GREATER(snapshot_version_1, 1);
2✔
3237
    CHECK_GREATER(snapshot_version_2, 1);
2✔
3238

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

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

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

1✔
3254
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3255
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3256
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3257
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3258

1✔
3259
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3260
    CHECK_NOT_EQUAL(downloadable_bytes_1, 0);
2✔
3261

1✔
3262
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3263
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3264

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

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

1✔
3271
    CHECK_GREATER(snapshot_version_1, 4);
2✔
3272
    CHECK_GREATER(snapshot_version_2, 3);
2✔
3273

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

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

1✔
3284
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3285
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3286
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3287
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3288

1✔
3289
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3290

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

1✔
3294
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3295
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3296

1✔
3297
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3298
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3299

1✔
3300
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3301
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3302

1✔
3303
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3304
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3305

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

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

3317

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

1✔
3332
    std::string server_address = "localhost";
2✔
3333

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

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

1✔
3345
    ThreadWrapper server_thread;
2✔
3346

1✔
3347
    // The server is not running.
1✔
3348

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

1✔
3356

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

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

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

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

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

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

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

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

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

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

4✔
3417
        entry++;
8✔
3418
    };
8✔
3419

1✔
3420
    session.set_progress_handler(progress_handler);
2✔
3421

1✔
3422
    session.bind();
2✔
3423

1✔
3424
    session.wait_for_upload_complete_or_client_stopped();
2✔
3425
    session.wait_for_download_complete_or_client_stopped();
2✔
3426

1✔
3427
    // Now the server is running.
1✔
3428

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

1✔
3436
    server.stop();
2✔
3437

1✔
3438
    // The server is stopped
1✔
3439

1✔
3440
    should_signal_cond_var = true;
2✔
3441

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

1✔
3450
    signal_pf.future.get();
2✔
3451

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

1✔
3458
    server_thread.join();
2✔
3459
}
2✔
3460

3461

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

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

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

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

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

1✔
3503
    int entry_1 = 0;
2✔
3504

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

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

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

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

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

4✔
3545
        ++entry_1;
8✔
3546
    };
8✔
3547

1✔
3548
    session_1.set_progress_handler(progress_handler_1);
2✔
3549

1✔
3550
    session_1.bind();
2✔
3551

1✔
3552
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3553
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3554

1✔
3555
    CHECK_EQUAL(entry_1, 4);
2✔
3556

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

1✔
3559
    int entry_2 = 0;
2✔
3560

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

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

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

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

1✔
3598
    session_2.set_progress_handler(progress_handler_2);
2✔
3599

1✔
3600
    session_2.bind();
2✔
3601

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

3607

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

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

1✔
3618
    ClientServerFixture fixture(server_dir, test_context);
2✔
3619
    fixture.start();
2✔
3620

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

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

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

1✔
3638
    session.set_progress_handler(progress_handler);
2✔
3639

1✔
3640
    session.bind();
2✔
3641
    progress_handled.get();
2✔
3642

1✔
3643
    // The check is that we reach this point.
1✔
3644
}
2✔
3645

3646

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

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

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

1✔
3664
    auto server_port = server.listen_endpoint().port();
2✔
3665

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

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

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

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

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

1✔
3703
    session->set_progress_handler(progress_handler);
2✔
3704

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

1✔
3714
    client.shutdown_and_wait();
2✔
3715
    server.stop();
2✔
3716
    server_thread.join();
2✔
3717

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

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

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

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

1✔
3742
    auto server_port = server.listen_endpoint().port();
2✔
3743

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

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

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

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

1✔
3766
    client.shutdown_and_wait();
2✔
3767
    server.stop();
2✔
3768
    server_thread.join();
2✔
3769

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

3774
TEST(Sync_UploadProgress_EmptyCommits)
3775
{
2✔
3776
    TEST_DIR(server_dir);
2✔
3777
    TEST_CLIENT_DB(db);
2✔
3778

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

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

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

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

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

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

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

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

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

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

1✔
3876
    socket_provider->start();
2✔
3877
}
2✔
3878

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

1✔
3885
    ClientServerFixture::Config fixture_config;
2✔
3886
    fixture_config.one_connection_per_session = false;
2✔
3887

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

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

1✔
3905
        session.cancel_reconnect_delay();
2✔
3906
        session.wait_for_download_complete_or_client_stopped();
2✔
3907
    }
2✔
3908

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

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

1✔
3927
        fixture.cancel_reconnect_delay();
2✔
3928
        session.wait_for_download_complete_or_client_stopped();
2✔
3929
    }
2✔
3930

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

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

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

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

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

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

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

1✔
3984
        session.cancel_reconnect_delay();
2✔
3985
        bowl.get_stone();
2✔
3986
    }
2✔
3987

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

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

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

1✔
4007
        fixture.cancel_reconnect_delay();
2✔
4008
        bowl.get_stone();
2✔
4009
    }
2✔
4010
}
2✔
4011

4012

4013
#ifndef REALM_PLATFORM_WIN32
4014

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

1✔
4029
    TEST_CLIENT_DB(db_1);
2✔
4030
    TEST_CLIENT_DB(db_2);
2✔
4031

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

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

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

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

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

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

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

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

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

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

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

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

1✔
4138
    ReadTransaction read_1(db_1);
2✔
4139
    ReadTransaction read_2(db_2);
2✔
4140

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

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

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

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

4168

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

1✔
4183
    TEST_CLIENT_DB(db_1);
2✔
4184
    TEST_CLIENT_DB(db_2);
2✔
4185

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

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

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

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

1✔
4236
    uint_fast64_t downloaded_bytes_1 = 0;
2✔
4237
    uint_fast64_t downloadable_bytes_1 = 0;
2✔
4238
    uint_fast64_t uploaded_bytes_1 = 0;
2✔
4239
    uint_fast64_t uploadable_bytes_1 = 0;
2✔
4240

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

1✔
4250
    uint_fast64_t downloaded_bytes_2 = 0;
2✔
4251
    uint_fast64_t downloadable_bytes_2 = 0;
2✔
4252
    uint_fast64_t uploaded_bytes_2 = 0;
2✔
4253
    uint_fast64_t uploadable_bytes_2 = 0;
2✔
4254

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

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

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

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

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

1✔
4292
    ReadTransaction read_1(db_1);
2✔
4293
    ReadTransaction read_2(db_2);
2✔
4294

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

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

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

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

4322

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

1✔
4329
    TEST_CLIENT_DB(db_1);
2✔
4330
    TEST_CLIENT_DB(db_2);
2✔
4331

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

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

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

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

1✔
4378
    {
2✔
4379
        TEST_DIR(dir);
2✔
4380
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4381

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

1✔
4387
        fixture.start();
2✔
4388

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

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

4403

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

1✔
4409
    TEST_CLIENT_DB(db_1);
2✔
4410
    TEST_CLIENT_DB(db_2);
2✔
4411

1✔
4412
    std::atomic<int> id = 0;
2✔
4413

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

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

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

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

1✔
4452
    {
2✔
4453
        TEST_DIR(dir);
2✔
4454
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4455

1✔
4456

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

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

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

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

4488

4489
#endif // REALM_PLATFORM_WIN32
4490

4491

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

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

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

1✔
4512
        fixture.start();
2✔
4513

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

4520

4521
TEST(Sync_ReconnectAfterPingTimeout)
4522
{
2✔
4523
    TEST_DIR(dir);
2✔
4524
    TEST_CLIENT_DB(db);
2✔
4525

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

1✔
4530
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4531

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

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

4546

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

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

1✔
4557
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4558

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

1✔
4567
        fixture.start();
2✔
4568

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

4577

4578
TEST(Sync_ServerDiscardDeadConnections)
4579
{
2✔
4580
    TEST_DIR(dir);
2✔
4581
    TEST_CLIENT_DB(db);
2✔
4582

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

1✔
4586
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4587

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

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

4602

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

1✔
4609
    TEST_DIR(server_dir);
2✔
4610
    TEST_CLIENT_DB(db_1);
2✔
4611
    TEST_CLIENT_DB(db_2);
2✔
4612

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

1✔
4629
    create_data(db_1, num_instructions_1);
2✔
4630
    create_data(db_2, num_instructions_2);
2✔
4631

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

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

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

1✔
4645
    session_1.wait_for_download_complete_or_client_stopped();
2✔
4646
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4647
}
2✔
4648

4649

4650
TEST(Sync_BatchedUploadMessages)
4651
{
2✔
4652
    TEST_DIR(server_dir);
2✔
4653
    TEST_CLIENT_DB(db);
2✔
4654

1✔
4655
    ClientServerFixture fixture(server_dir, test_context);
2✔
4656
    fixture.start();
2✔
4657

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

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

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

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

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

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

4696

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

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

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

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

1✔
4726
    session_1.bind();
2✔
4727
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4728

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

1✔
4740
    session_2.set_progress_handler(progress_handler);
2✔
4741

1✔
4742
    session_2.bind();
2✔
4743

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

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

4757

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

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

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

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

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

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

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

4815

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

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

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

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

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

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

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

1✔
4868
    {
2✔
4869
        WriteTransaction wt{db_1};
2✔
4870

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

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

1✔
4883
        table_target->remove_object(k1);
2✔
4884
        table_target->remove_object(k0);
2✔
4885

1✔
4886
        wt.commit();
2✔
4887
    }
2✔
4888

1✔
4889
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4890
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4891

1✔
4892
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4893
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4894

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

4902

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

1✔
4911
    {
2✔
4912
        WriteTransaction wt{db_1};
2✔
4913

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

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

1✔
4930
        wt.commit();
2✔
4931
    }
2✔
4932

1✔
4933
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4934
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4935

1✔
4936
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4937
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4938

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

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

4955

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

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

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

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

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

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

5001

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

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

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

1✔
5019
    fixture.close_server_side_connections();
2✔
5020
    bowl.get_stone();
2✔
5021
}
2✔
5022

5023

5024
TEST(Sync_VerifyServerHistoryAfterLargeUpload)
5025
{
2✔
5026
    TEST_DIR(server_dir);
2✔
5027
    TEST_CLIENT_DB(db);
2✔
5028

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

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

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

1✔
5045
        wt->commit();
2✔
5046

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

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

5064

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

1✔
5070
    TEST_DIR(server_dir);
2✔
5071
    TEST_CLIENT_DB(db_2);
2✔
5072

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

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

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

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

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

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

1✔
5128
    session.wait_for_upload_complete_or_client_stopped();
2✔
5129
    session.wait_for_download_complete_or_client_stopped();
2✔
5130

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

5136

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

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

5158
    size_t num_servers = sizeof(server_address) / sizeof(server_address[0]);
×
5159

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

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

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

5174
        // Invalid token for the cloud.
5175
        session_config.signed_user_token = g_signed_test_user_token;
×
5176

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

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

5190
        session.set_connection_state_change_listener(listener);
×
5191
        session.bind();
×
5192

5193
        session.wait_for_download_complete_or_client_stopped();
×
5194
    }
×
5195
}
×
5196

5197

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

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

1✔
5213
    Session::Config session_config;
2✔
5214
    session_config.authorization_header_name = authorization_header_name;
2✔
5215

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

1✔
5223
    session.wait_for_download_complete_or_client_stopped();
2✔
5224
}
2✔
5225

5226

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

1✔
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

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

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

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

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

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

5275

5276
TEST(Sync_GoodChangeset_AccentCharacterInFieldName)
5277
{
2✔
5278
    TEST_DIR(dir);
2✔
5279
    TEST_CLIENT_DB(db);
2✔
5280

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

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

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

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

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

1✔
5313
        session.wait_for_upload_complete_or_client_stopped();
2✔
5314
    }
2✔
5315
    CHECK_NOT(did_fail);
2✔
5316
}
2✔
5317

5318

5319
namespace issue2104 {
5320

5321
class ServerHistoryContext : public _impl::ServerHistory::Context {
5322
public:
5323
    ServerHistoryContext()
5324
        : m_transformer{make_transformer()}
5325
    {
×
5326
    }
×
5327

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

5333
    sync::Transformer& get_transformer() override
5334
    {
×
5335
        return *m_transformer;
×
5336
    }
×
5337

5338
    util::Buffer<char>& get_transform_buffer() override
5339
    {
×
5340
        return m_transform_buffer;
×
5341
    }
×
5342

5343
private:
5344
    std::mt19937_64 m_random;
5345
    std::unique_ptr<sync::Transformer> m_transformer;
5346
    util::Buffer<char> m_transform_buffer;
5347
};
5348

5349
} // namespace issue2104
5350

5351
// This test reproduces a slow merge seen in issue 2104.
5352
// The test uses a user supplied Realm and a changeset
5353
// from a client.
5354
// The test uses a user supplied Realm that is very large
5355
// and not kept in the repo. The realm has checksum 3693867489.
5356
//
5357
// This test might be modified to avoid having a large Realm
5358
// (96 MB uncompressed) in the repo.
5359
TEST_IF(Sync_Issue2104, false)
5360
{
×
5361
    TEST_DIR(dir);
×
5362

5363
    // Save a snapshot of the server Realm file.
5364
    std::string realm_path = "issue_2104_server.realm";
×
5365
    std::string realm_path_copy = util::File::resolve("issue_2104.realm", dir);
×
5366
    util::File::copy(realm_path, realm_path_copy);
×
5367

5368
    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 "
×
5369
                                "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 "
×
5370
                                "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 "
×
5371
                                "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 "
×
5372
                                "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 "
×
5373
                                "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 "
×
5374
                                "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 "
×
5375
                                "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 "
×
5376
                                "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 "
×
5377
                                "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 "
×
5378
                                "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 "
×
5379
                                "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 "
×
5380
                                "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 "
×
5381
                                "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 "
×
5382
                                "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 "
×
5383
                                "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 "
×
5384
                                "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 "
×
5385
                                "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 "
×
5386
                                "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 "
×
5387
                                "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 "
×
5388
                                "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 "
×
5389
                                "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 "
×
5390
                                "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 "
×
5391
                                "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 "
×
5392
                                "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 "
×
5393
                                "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 "
×
5394
                                "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 "
×
5395
                                "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 "
×
5396
                                "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 "
×
5397
                                "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 "
×
5398
                                "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 "
×
5399
                                "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 "
×
5400
                                "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 "
×
5401
                                "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 "
×
5402
                                "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 "
×
5403
                                "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 "
×
5404
                                "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 "
×
5405
                                "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 "
×
5406
                                "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 "
×
5407
                                "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 "
×
5408
                                "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 "
×
5409
                                "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 "
×
5410
                                "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 "
×
5411
                                "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 "
×
5412
                                "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 "
×
5413
                                "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 "
×
5414
                                "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 "
×
5415
                                "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 "
×
5416
                                "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 "
×
5417
                                "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 "
×
5418
                                "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 "
×
5419
                                "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 "
×
5420
                                "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 "
×
5421
                                "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 "
×
5422
                                "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 "
×
5423
                                "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 "
×
5424
                                "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 "
×
5425
                                "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 "
×
5426
                                "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 "
×
5427
                                "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 "
×
5428
                                "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 "
×
5429
                                "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 "
×
5430
                                "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 "
×
5431
                                "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 "
×
5432
                                "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 "
×
5433
                                "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 "
×
5434
                                "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 "
×
5435
                                "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 "
×
5436
                                "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 "
×
5437
                                "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 "
×
5438
                                "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 "
×
5439
                                "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 "
×
5440
                                "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 "
×
5441
                                "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 "
×
5442
                                "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 "
×
5443
                                "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 "
×
5444
                                "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 "
×
5445
                                "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 "
×
5446
                                "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 "
×
5447
                                "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 "
×
5448
                                "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 "
×
5449
                                "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 "
×
5450
                                "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 "
×
5451
                                "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 "
×
5452
                                "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 "
×
5453
                                "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 "
×
5454
                                "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 "
×
5455
                                "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 "
×
5456
                                "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 "
×
5457
                                "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 "
×
5458
                                "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 "
×
5459
                                "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 "
×
5460
                                "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 "
×
5461
                                "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 "
×
5462
                                "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 "
×
5463
                                "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 "
×
5464
                                "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 "
×
5465
                                "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 "
×
5466
                                "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 "
×
5467
                                "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 "
×
5468
                                "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 "
×
5469
                                "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 "
×
5470
                                "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 "
×
5471
                                "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 "
×
5472
                                "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 "
×
5473
                                "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 "
×
5474
                                "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 "
×
5475
                                "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 "
×
5476
                                "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 "
×
5477
                                "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 "
×
5478
                                "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 "
×
5479
                                "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 "
×
5480
                                "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 "
×
5481
                                "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 "
×
5482
                                "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 "
×
5483
                                "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 "
×
5484
                                "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 "
×
5485
                                "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 "
×
5486
                                "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 "
×
5487
                                "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 "
×
5488
                                "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 "
×
5489
                                "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 "
×
5490
                                "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 "
×
5491
                                "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 "
×
5492
                                "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 "
×
5493
                                "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 "
×
5494
                                "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 "
×
5495
                                "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 "
×
5496
                                "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 "
×
5497
                                "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 "
×
5498
                                "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 "
×
5499
                                "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 "
×
5500
                                "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 "
×
5501
                                "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 "
×
5502
                                "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 "
×
5503
                                "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 "
×
5504
                                "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 "
×
5505
                                "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 "
×
5506
                                "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 "
×
5507
                                "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 "
×
5508
                                "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 "
×
5509
                                "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 "
×
5510
                                "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 "
×
5511
                                "1D 0B AC 01 0D 00 1C 00 1E 0B B5 01 0D 00 1F 00 1F 0B AD 01 0C 00 1E";
×
5512

5513
    std::vector<char> changeset_vec;
×
5514
    {
×
5515
        std::istringstream in{changeset_hex};
×
5516
        int n;
×
5517
        in >> std::hex >> n;
×
5518
        while (in) {
×
5519
            REALM_ASSERT(n >= 0 && n <= 255);
×
5520
            changeset_vec.push_back(n);
×
5521
            in >> std::hex >> n;
×
5522
        }
×
5523
    }
×
5524

5525
    BinaryData changeset_bin{changeset_vec.data(), changeset_vec.size()};
×
5526

5527
    file_ident_type client_file_ident = 51;
×
5528
    timestamp_type origin_timestamp = 103573722140;
×
5529
    file_ident_type origin_file_ident = 0;
×
5530
    version_type client_version = 2;
×
5531
    version_type last_integrated_server_version = 0;
×
5532
    UploadCursor upload_cursor{client_version, last_integrated_server_version};
×
5533

5534
    _impl::ServerHistory::IntegratableChangeset integratable_changeset{
×
5535
        client_file_ident, origin_timestamp, origin_file_ident, upload_cursor, changeset_bin};
×
5536

5537
    _impl::ServerHistory::IntegratableChangesets integratable_changesets;
×
5538
    integratable_changesets[client_file_ident].changesets.push_back(integratable_changeset);
×
5539

5540
    issue2104::ServerHistoryContext history_context;
×
5541
    _impl::ServerHistory history{history_context};
×
5542
    DBRef db = DB::create(history, realm_path_copy);
×
5543

5544
    VersionInfo version_info;
×
5545
    bool backup_whole_realm;
×
5546
    _impl::ServerHistory::IntegrationResult result;
×
5547
    history.integrate_client_changesets(integratable_changesets, version_info, backup_whole_realm, result,
×
NEW
5548
                                        *test_context.logger);
×
5549
}
×
5550

5551

5552
TEST(Sync_RunServerWithoutPublicKey)
5553
{
2✔
5554
    TEST_CLIENT_DB(db);
2✔
5555
    TEST_DIR(server_dir);
2✔
5556
    ClientServerFixture::Config config;
2✔
5557
    config.server_public_key_path = {};
2✔
5558
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5559
    fixture.start();
2✔
5560

1✔
5561
    // Server must accept an unsigned token when a public key is not passed to
1✔
5562
    // it
1✔
5563
    {
2✔
5564
        Session session = fixture.make_bound_session(db, "/test", g_unsigned_test_user_token);
2✔
5565
        session.wait_for_download_complete_or_client_stopped();
2✔
5566
    }
2✔
5567

1✔
5568
    // Server must also accept a signed token when a public key is not passed to
1✔
5569
    // it
1✔
5570
    {
2✔
5571
        Session session = fixture.make_bound_session(db, "/test");
2✔
5572
        session.wait_for_download_complete_or_client_stopped();
2✔
5573
    }
2✔
5574
}
2✔
5575

5576

5577
TEST(Sync_ServerSideEncryption)
5578
{
2✔
5579
    TEST_CLIENT_DB(db);
2✔
5580
    {
2✔
5581
        WriteTransaction wt(db);
2✔
5582
        wt.get_group().add_table_with_primary_key("class_Test", type_Int, "id");
2✔
5583
        wt.commit();
2✔
5584
    }
2✔
5585

1✔
5586
    TEST_DIR(server_dir);
2✔
5587
    bool always_encrypt = true;
2✔
5588
    std::string server_path;
2✔
5589
    {
2✔
5590
        ClientServerFixture::Config config;
2✔
5591
        config.server_encryption_key = crypt_key_2(always_encrypt);
2✔
5592
        ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5593
        fixture.start();
2✔
5594

1✔
5595
        Session session = fixture.make_bound_session(db, "/test");
2✔
5596
        session.wait_for_upload_complete_or_client_stopped();
2✔
5597

1✔
5598
        server_path = fixture.map_virtual_to_real_path("/test");
2✔
5599
    }
2✔
5600

1✔
5601
    const char* encryption_key = crypt_key(always_encrypt);
2✔
5602
    Group group{server_path, encryption_key};
2✔
5603
    CHECK(group.has_table("class_Test"));
2✔
5604
}
2✔
5605

5606

5607
// This test calls row_for_object_id() for various object ids and tests that
5608
// the right value is returned including that no assertions are hit.
5609
TEST(Sync_RowForGlobalKey)
5610
{
2✔
5611
    TEST_CLIENT_DB(db);
2✔
5612

1✔
5613
    {
2✔
5614
        WriteTransaction wt(db);
2✔
5615
        TableRef table = wt.add_table("class_foo");
2✔
5616
        table->add_column(type_Int, "i");
2✔
5617
        wt.commit();
2✔
5618
    }
2✔
5619

1✔
5620
    // Check that various object_ids are not in the table.
1✔
5621
    {
2✔
5622
        ReadTransaction rt(db);
2✔
5623
        ConstTableRef table = rt.get_table("class_foo");
2✔
5624
        CHECK(table);
2✔
5625

1✔
5626
        // Default constructed GlobalKey
1✔
5627
        {
2✔
5628
            GlobalKey object_id;
2✔
5629
            auto row_ndx = table->get_objkey(object_id);
2✔
5630
            CHECK_NOT(row_ndx);
2✔
5631
        }
2✔
5632

1✔
5633
        // GlobalKey with small lo and hi values
1✔
5634
        {
2✔
5635
            GlobalKey object_id{12, 24};
2✔
5636
            auto row_ndx = table->get_objkey(object_id);
2✔
5637
            CHECK_NOT(row_ndx);
2✔
5638
        }
2✔
5639

1✔
5640
        // GlobalKey with lo and hi values past the 32 bit limit.
1✔
5641
        {
2✔
5642
            GlobalKey object_id{uint_fast64_t(1) << 50, uint_fast64_t(1) << 52};
2✔
5643
            auto row_ndx = table->get_objkey(object_id);
2✔
5644
            CHECK_NOT(row_ndx);
2✔
5645
        }
2✔
5646
    }
2✔
5647
}
2✔
5648

5649

5650
TEST(Sync_LogCompaction_EraseObject_LinkList)
5651
{
2✔
5652
    TEST_DIR(dir);
2✔
5653
    TEST_CLIENT_DB(db_1);
2✔
5654
    TEST_CLIENT_DB(db_2);
2✔
5655
    ClientServerFixture::Config config;
2✔
5656

1✔
5657
    // Log comapction is true by default, but we emphasize it.
1✔
5658
    config.disable_upload_compaction = false;
2✔
5659
    config.disable_download_compaction = false;
2✔
5660

1✔
5661
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5662
    fixture.start();
2✔
5663

1✔
5664
    {
2✔
5665
        WriteTransaction wt{db_1};
2✔
5666

1✔
5667
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
5668
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
5669
        auto col_key = table_source->add_column_list(*table_target, "target_link");
2✔
5670

1✔
5671
        auto k0 = table_target->create_object_with_primary_key(1).get_key();
2✔
5672
        auto k1 = table_target->create_object_with_primary_key(2).get_key();
2✔
5673

1✔
5674
        auto ll = table_source->create_object_with_primary_key(0).get_linklist_ptr(col_key);
2✔
5675
        ll->add(k0);
2✔
5676
        ll->add(k1);
2✔
5677
        CHECK_EQUAL(ll->size(), 2);
2✔
5678
        wt.commit();
2✔
5679
    }
2✔
5680

1✔
5681
    {
2✔
5682
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5683
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5684

1✔
5685
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5686
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5687
    }
2✔
5688

1✔
5689
    {
2✔
5690
        WriteTransaction wt{db_1};
2✔
5691

1✔
5692
        TableRef table_source = wt.get_table("class_source");
2✔
5693
        TableRef table_target = wt.get_table("class_target");
2✔
5694

1✔
5695
        CHECK_EQUAL(table_source->size(), 1);
2✔
5696
        CHECK_EQUAL(table_target->size(), 2);
2✔
5697

1✔
5698
        table_target->get_object(1).remove();
2✔
5699
        table_target->get_object(0).remove();
2✔
5700

1✔
5701
        table_source->get_object(0).remove();
2✔
5702
        wt.commit();
2✔
5703
    }
2✔
5704

1✔
5705
    {
2✔
5706
        WriteTransaction wt{db_2};
2✔
5707

1✔
5708
        TableRef table_source = wt.get_table("class_source");
2✔
5709
        TableRef table_target = wt.get_table("class_target");
2✔
5710
        auto col_key = table_source->get_column_key("target_link");
2✔
5711

1✔
5712
        CHECK_EQUAL(table_source->size(), 1);
2✔
5713
        CHECK_EQUAL(table_target->size(), 2);
2✔
5714

1✔
5715
        auto k0 = table_target->begin()->get_key();
2✔
5716

1✔
5717
        auto ll = table_source->get_object(0).get_linklist_ptr(col_key);
2✔
5718
        ll->add(k0);
2✔
5719
        wt.commit();
2✔
5720
    }
2✔
5721

1✔
5722
    {
2✔
5723
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5724
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5725
    }
2✔
5726

1✔
5727
    {
2✔
5728
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5729
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
5730
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5731
    }
2✔
5732

1✔
5733
    {
2✔
5734
        ReadTransaction rt{db_2};
2✔
5735

1✔
5736
        ConstTableRef table_source = rt.get_group().get_table("class_source");
2✔
5737
        ConstTableRef table_target = rt.get_group().get_table("class_target");
2✔
5738

1✔
5739
        CHECK_EQUAL(table_source->size(), 0);
2✔
5740
        CHECK_EQUAL(table_target->size(), 0);
2✔
5741
    }
2✔
5742
}
2✔
5743

5744

5745
// This test could trigger the assertion that the row_for_object_id cache is
5746
// valid before the cache was properly invalidated in the case of a short
5747
// circuited sync replicator.
5748
TEST(Sync_CreateObjects_EraseObjects)
5749
{
2✔
5750
    TEST_DIR(dir);
2✔
5751
    TEST_CLIENT_DB(db_1);
2✔
5752
    TEST_CLIENT_DB(db_2);
2✔
5753
    ClientServerFixture fixture(dir, test_context);
2✔
5754
    fixture.start();
2✔
5755

1✔
5756
    Session session_1 = fixture.make_bound_session(db_1);
2✔
5757
    Session session_2 = fixture.make_bound_session(db_2);
2✔
5758

1✔
5759
    write_transaction(db_1, [](WriteTransaction& wt) {
2✔
5760
        TableRef table = wt.get_group().add_table_with_primary_key("class_persons", type_Int, "id");
2✔
5761
        table->create_object_with_primary_key(1);
2✔
5762
        table->create_object_with_primary_key(2);
2✔
5763
    });
2✔
5764
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5765
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5766

1✔
5767
    write_transaction(db_1, [&](WriteTransaction& wt) {
2✔
5768
        TableRef table = wt.get_table("class_persons");
2✔
5769
        CHECK_EQUAL(table->size(), 2);
2✔
5770
        table->get_object(0).remove();
2✔
5771
        table->get_object(0).remove();
2✔
5772
    });
2✔
5773
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5774
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5775
}
2✔
5776

5777

5778
TEST(Sync_CreateDeleteCreateTableWithPrimaryKey)
5779
{
2✔
5780
    TEST_DIR(dir);
2✔
5781
    TEST_CLIENT_DB(db);
2✔
5782
    ClientServerFixture fixture(dir, test_context);
2✔
5783
    fixture.start();
2✔
5784

1✔
5785
    Session session = fixture.make_bound_session(db);
2✔
5786

1✔
5787
    write_transaction(db, [](WriteTransaction& wt) {
2✔
5788
        TableRef table = wt.get_group().add_table_with_primary_key("class_t", type_Int, "pk");
2✔
5789
        wt.get_group().remove_table(table->get_key());
2✔
5790
        table = wt.get_group().add_table_with_primary_key("class_t", type_String, "pk");
2✔
5791
    });
2✔
5792
    session.wait_for_upload_complete_or_client_stopped();
2✔
5793
    session.wait_for_download_complete_or_client_stopped();
2✔
5794
}
2✔
5795

5796
template <typename T>
5797
T sequence_next()
5798
{
5799
    REALM_UNREACHABLE();
5800
}
5801

5802
template <>
5803
ObjectId sequence_next()
5804
{
8✔
5805
    return ObjectId::gen();
8✔
5806
}
8✔
5807

5808
template <>
5809
UUID sequence_next()
5810
{
8✔
5811
    union {
8✔
5812
        struct {
8✔
5813
            uint64_t upper;
8✔
5814
            uint64_t lower;
8✔
5815
        } ints;
8✔
5816
        UUID::UUIDBytes bytes;
8✔
5817
    } u;
8✔
5818
    static uint64_t counter = test_util::random_int(0, 1000);
8✔
5819
    u.ints.upper = ++counter;
8✔
5820
    u.ints.lower = ++counter;
8✔
5821
    return UUID{u.bytes};
8✔
5822
}
8✔
5823

5824
template <>
5825
Int sequence_next()
5826
{
8✔
5827
    static Int count = test_util::random_int(-1000, 1000);
8✔
5828
    return ++count;
8✔
5829
}
8✔
5830

5831
template <>
5832
String sequence_next()
5833
{
4✔
5834
    static std::string str;
4✔
5835
    static Int sequence = test_util::random_int(-1000, 1000);
4✔
5836
    str = util::format("string sequence %1", ++sequence);
4✔
5837
    return String(str);
4✔
5838
}
4✔
5839

5840
NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util::Optional<Int>,
5841
                         util::Optional<ObjectId>, util::Optional<UUID>)
5842
{
14✔
5843
    using underlying_type = typename util::RemoveOptional<TEST_TYPE>::type;
14✔
5844
    constexpr bool is_optional = !std::is_same_v<underlying_type, TEST_TYPE>;
14✔
5845
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
14✔
5846

7✔
5847
    TEST_CLIENT_DB(db_1);
14✔
5848
    TEST_CLIENT_DB(db_2);
14✔
5849

7✔
5850
    TEST_DIR(dir);
14✔
5851
    fixtures::ClientServerFixture fixture{dir, test_context};
14✔
5852
    fixture.start();
14✔
5853

7✔
5854
    Session session_1 = fixture.make_session(db_1, "/test");
14✔
5855
    Session session_2 = fixture.make_session(db_2, "/test");
14✔
5856
    session_1.bind();
14✔
5857
    session_2.bind();
14✔
5858

7✔
5859
    TEST_TYPE obj_1_id;
14✔
5860
    TEST_TYPE obj_2_id;
14✔
5861

7✔
5862
    TEST_TYPE default_or_null{};
14✔
5863
    if constexpr (std::is_same_v<TEST_TYPE, String>) {
14✔
5864
        default_or_null = "";
2✔
5865
    }
2✔
5866
    if constexpr (is_optional) {
14✔
5867
        CHECK(!default_or_null);
6✔
5868
    }
6✔
5869

7✔
5870
    {
14✔
5871
        WriteTransaction tr{db_1};
14✔
5872
        auto table_1 = tr.get_group().add_table_with_primary_key("class_Table1", type, "id", is_optional);
14✔
5873
        auto table_2 = tr.get_group().add_table_with_primary_key("class_Table2", type, "id", is_optional);
14✔
5874
        table_1->add_column_list(type, "oids", is_optional);
14✔
5875

7✔
5876
        auto obj_1 = table_1->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5877
        auto obj_2 = table_2->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5878
        if constexpr (is_optional) {
14✔
5879
            table_2->create_object_with_primary_key(default_or_null);
6✔
5880
        }
6✔
5881

7✔
5882
        auto list = obj_1.template get_list<TEST_TYPE>("oids");
14✔
5883
        obj_1_id = obj_1.template get<TEST_TYPE>("id");
14✔
5884
        obj_2_id = obj_2.template get<TEST_TYPE>("id");
14✔
5885
        list.insert(0, obj_2_id);
14✔
5886
        list.insert(1, default_or_null);
14✔
5887
        list.add(default_or_null);
14✔
5888

7✔
5889
        tr.commit();
14✔
5890
    }
14✔
5891

7✔
5892
    session_1.wait_for_upload_complete_or_client_stopped();
14✔
5893
    session_2.wait_for_download_complete_or_client_stopped();
14✔
5894

7✔
5895
    {
14✔
5896
        ReadTransaction tr{db_2};
14✔
5897
        auto table_1 = tr.get_table("class_Table1");
14✔
5898
        auto table_2 = tr.get_table("class_Table2");
14✔
5899
        auto obj_1 = *table_1->begin();
14✔
5900
        auto obj_2 = table_2->find_first(table_2->get_column_key("id"), obj_2_id);
14✔
5901
        CHECK(obj_2);
14✔
5902
        auto list = obj_1.get_list<TEST_TYPE>("oids");
14✔
5903
        CHECK_EQUAL(obj_1.template get<TEST_TYPE>("id"), obj_1_id);
14✔
5904
        CHECK_EQUAL(list.size(), 3);
14✔
5905
        CHECK_NOT(list.is_null(0));
14✔
5906
        CHECK_EQUAL(list.get(0), obj_2_id);
14✔
5907
        CHECK_EQUAL(list.get(1), default_or_null);
14✔
5908
        CHECK_EQUAL(list.get(2), default_or_null);
14✔
5909
        if constexpr (is_optional) {
14✔
5910
            auto obj_3 = table_2->find_first_null(table_2->get_column_key("id"));
6✔
5911
            CHECK(obj_3);
6✔
5912
            CHECK(list.is_null(1));
6✔
5913
            CHECK(list.is_null(2));
6✔
5914
        }
6✔
5915
    }
14✔
5916
}
14✔
5917

5918
TEST(Sync_Mixed)
5919
{
2✔
5920
    // Test replication and synchronization of Mixed values and lists.
1✔
5921

1✔
5922
    TEST_CLIENT_DB(db_1);
2✔
5923
    TEST_CLIENT_DB(db_2);
2✔
5924

1✔
5925
    TEST_DIR(dir);
2✔
5926
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5927
    fixture.start();
2✔
5928

1✔
5929
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5930
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5931
    session_1.bind();
2✔
5932
    session_2.bind();
2✔
5933

1✔
5934
    {
2✔
5935
        WriteTransaction tr{db_1};
2✔
5936
        auto& g = tr.get_group();
2✔
5937
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5938
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5939
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5940
        foos->add_column(type_Mixed, "value", true);
2✔
5941
        foos->add_column_list(type_Mixed, "values");
2✔
5942

1✔
5943
        auto foo = foos->create_object_with_primary_key(123);
2✔
5944
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5945
        auto fop = fops->create_object_with_primary_key(456);
2✔
5946

1✔
5947
        foo.set("value", Mixed(6.2f));
2✔
5948
        auto values = foo.get_list<Mixed>("values");
2✔
5949
        values.insert(0, StringData("A"));
2✔
5950
        values.insert(1, ObjLink{bars->get_key(), bar.get_key()});
2✔
5951
        values.insert(2, ObjLink{fops->get_key(), fop.get_key()});
2✔
5952
        values.insert(3, 123.f);
2✔
5953

1✔
5954
        tr.commit();
2✔
5955
    }
2✔
5956

1✔
5957
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5958
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5959

1✔
5960
    {
2✔
5961
        ReadTransaction tr{db_2};
2✔
5962

1✔
5963
        auto foos = tr.get_table("class_Foo");
2✔
5964
        auto bars = tr.get_table("class_Bar");
2✔
5965
        auto fops = tr.get_table("class_Fop");
2✔
5966

1✔
5967
        CHECK_EQUAL(foos->size(), 1);
2✔
5968
        CHECK_EQUAL(bars->size(), 1);
2✔
5969
        CHECK_EQUAL(fops->size(), 1);
2✔
5970

1✔
5971
        auto foo = *foos->begin();
2✔
5972
        auto value = foo.get<Mixed>("value");
2✔
5973
        CHECK_EQUAL(value, Mixed{6.2f});
2✔
5974
        auto values = foo.get_list<Mixed>("values");
2✔
5975
        CHECK_EQUAL(values.size(), 4);
2✔
5976

1✔
5977
        auto v0 = values.get(0);
2✔
5978
        auto v1 = values.get(1);
2✔
5979
        auto v2 = values.get(2);
2✔
5980
        auto v3 = values.get(3);
2✔
5981

1✔
5982
        auto l1 = v1.get_link();
2✔
5983
        auto l2 = v2.get_link();
2✔
5984

1✔
5985
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5986
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5987

1✔
5988
        CHECK_EQUAL(v0, Mixed{"A"});
2✔
5989
        CHECK_EQUAL(l1_table, bars);
2✔
5990
        CHECK_EQUAL(l2_table, fops);
2✔
5991
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5992
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5993
        CHECK_EQUAL(v3, Mixed{123.f});
2✔
5994
    }
2✔
5995
}
2✔
5996

5997
TEST(Sync_TypedLinks)
5998
{
2✔
5999
    // Test replication and synchronization of Mixed values and lists.
1✔
6000

1✔
6001
    TEST_CLIENT_DB(db_1);
2✔
6002
    TEST_CLIENT_DB(db_2);
2✔
6003

1✔
6004
    TEST_DIR(dir);
2✔
6005
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6006
    fixture.start();
2✔
6007

1✔
6008
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6009
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6010
    session_1.bind();
2✔
6011
    session_2.bind();
2✔
6012

1✔
6013
    write_transaction(db_1, [](WriteTransaction& tr) {
2✔
6014
        auto& g = tr.get_group();
2✔
6015
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6016
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6017
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
6018
        foos->add_column(type_TypedLink, "link");
2✔
6019

1✔
6020
        auto foo1 = foos->create_object_with_primary_key(123);
2✔
6021
        auto foo2 = foos->create_object_with_primary_key(456);
2✔
6022
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
6023
        auto fop = fops->create_object_with_primary_key(456);
2✔
6024

1✔
6025
        foo1.set("link", ObjLink(bars->get_key(), bar.get_key()));
2✔
6026
        foo2.set("link", ObjLink(fops->get_key(), fop.get_key()));
2✔
6027
    });
2✔
6028

1✔
6029
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6030
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6031

1✔
6032
    {
2✔
6033
        ReadTransaction tr{db_2};
2✔
6034

1✔
6035
        auto foos = tr.get_table("class_Foo");
2✔
6036
        auto bars = tr.get_table("class_Bar");
2✔
6037
        auto fops = tr.get_table("class_Fop");
2✔
6038

1✔
6039
        CHECK_EQUAL(foos->size(), 2);
2✔
6040
        CHECK_EQUAL(bars->size(), 1);
2✔
6041
        CHECK_EQUAL(fops->size(), 1);
2✔
6042

1✔
6043
        auto it = foos->begin();
2✔
6044
        auto l1 = it->get<ObjLink>("link");
2✔
6045
        ++it;
2✔
6046
        auto l2 = it->get<ObjLink>("link");
2✔
6047

1✔
6048
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
6049
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
6050

1✔
6051
        CHECK_EQUAL(l1_table, bars);
2✔
6052
        CHECK_EQUAL(l2_table, fops);
2✔
6053
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
6054
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
6055
    }
2✔
6056
}
2✔
6057

6058
TEST(Sync_Dictionary)
6059
{
2✔
6060
    // Test replication and synchronization of Mixed values and lists.
1✔
6061

1✔
6062
    TEST_CLIENT_DB(db_1);
2✔
6063
    TEST_CLIENT_DB(db_2);
2✔
6064

1✔
6065
    TEST_DIR(dir);
2✔
6066
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6067
    fixture.start();
2✔
6068

1✔
6069
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6070
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6071
    session_1.bind();
2✔
6072
    session_2.bind();
2✔
6073

1✔
6074
    Timestamp now{std::chrono::system_clock::now()};
2✔
6075

1✔
6076
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6077
        auto& g = tr.get_group();
2✔
6078
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6079
        auto col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6080
        auto col_dict_str = foos->add_column_dictionary(type_String, "str_dict", true);
2✔
6081

1✔
6082
        auto foo = foos->create_object_with_primary_key(123);
2✔
6083

1✔
6084
        auto dict = foo.get_dictionary(col_dict);
2✔
6085
        dict.insert("hello", "world");
2✔
6086
        dict.insert("cnt", 7);
2✔
6087
        dict.insert("when", now);
2✔
6088

1✔
6089
        auto dict_str = foo.get_dictionary(col_dict_str);
2✔
6090
        dict_str.insert("some", "text");
2✔
6091
        dict_str.insert("nothing", null());
2✔
6092
    });
2✔
6093

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

1✔
6097
    write_transaction(db_2, [&](WriteTransaction& tr) {
2✔
6098
        auto foos = tr.get_table("class_Foo");
2✔
6099
        CHECK_EQUAL(foos->size(), 1);
2✔
6100

1✔
6101
        auto it = foos->begin();
2✔
6102
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6103
        CHECK(dict.get_value_data_type() == type_Mixed);
2✔
6104
        CHECK_EQUAL(dict.size(), 3);
2✔
6105

1✔
6106
        auto col_dict_str = foos->get_column_key("str_dict");
2✔
6107
        auto dict_str = it->get_dictionary(col_dict_str);
2✔
6108
        CHECK(col_dict_str.is_nullable());
2✔
6109
        CHECK(dict_str.get_value_data_type() == type_String);
2✔
6110
        CHECK_EQUAL(dict_str.size(), 2);
2✔
6111

1✔
6112
        Mixed val = dict["hello"];
2✔
6113
        CHECK_EQUAL(val.get_string(), "world");
2✔
6114
        val = dict.get("cnt");
2✔
6115
        CHECK_EQUAL(val.get_int(), 7);
2✔
6116
        val = dict.get("when");
2✔
6117
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6118

1✔
6119
        dict.erase("cnt");
2✔
6120
        dict.insert("hello", "goodbye");
2✔
6121
    });
2✔
6122

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

1✔
6126
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6127
        auto foos = tr.get_table("class_Foo");
2✔
6128
        CHECK_EQUAL(foos->size(), 1);
2✔
6129

1✔
6130
        auto it = foos->begin();
2✔
6131
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6132
        CHECK_EQUAL(dict.size(), 2);
2✔
6133

1✔
6134
        Mixed val = dict["hello"];
2✔
6135
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
6136
        val = dict.get("when");
2✔
6137
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6138

1✔
6139
        dict.clear();
2✔
6140
    });
2✔
6141

1✔
6142
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6143
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6144

1✔
6145
    {
2✔
6146
        ReadTransaction read_1{db_1};
2✔
6147
        ReadTransaction read_2{db_2};
2✔
6148
        // tr.get_group().to_json(std::cout);
1✔
6149

1✔
6150
        auto foos = read_2.get_table("class_Foo");
2✔
6151

1✔
6152
        CHECK_EQUAL(foos->size(), 1);
2✔
6153

1✔
6154
        auto it = foos->begin();
2✔
6155
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6156
        CHECK_EQUAL(dict.size(), 0);
2✔
6157

1✔
6158
        CHECK(compare_groups(read_1, read_2));
2✔
6159
    }
2✔
6160
}
2✔
6161

6162
TEST(Sync_Dictionary_Links)
6163
{
2✔
6164
    TEST_CLIENT_DB(db_1);
2✔
6165
    TEST_CLIENT_DB(db_2);
2✔
6166

1✔
6167
    TEST_DIR(dir);
2✔
6168
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6169
    fixture.start();
2✔
6170

1✔
6171
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6172
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6173
    session_1.bind();
2✔
6174
    session_2.bind();
2✔
6175

1✔
6176
    // Test that we can transmit links.
1✔
6177

1✔
6178
    ColKey col_dict;
2✔
6179

1✔
6180
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6181
        auto& g = tr.get_group();
2✔
6182
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6183
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6184
        col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6185

1✔
6186
        auto foo = foos->create_object_with_primary_key(123);
2✔
6187
        auto a = bars->create_object_with_primary_key("a");
2✔
6188
        auto b = bars->create_object_with_primary_key("b");
2✔
6189

1✔
6190
        auto dict = foo.get_dictionary(col_dict);
2✔
6191
        dict.insert("a", a);
2✔
6192
        dict.insert("b", b);
2✔
6193
    });
2✔
6194

1✔
6195
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6196
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6197

1✔
6198
    {
2✔
6199
        ReadTransaction tr{db_2};
2✔
6200

1✔
6201
        auto foos = tr.get_table("class_Foo");
2✔
6202
        auto bars = tr.get_table("class_Bar");
2✔
6203

1✔
6204
        CHECK_EQUAL(foos->size(), 1);
2✔
6205
        CHECK_EQUAL(bars->size(), 2);
2✔
6206

1✔
6207
        auto foo = foos->get_object_with_primary_key(123);
2✔
6208
        auto a = bars->get_object_with_primary_key("a");
2✔
6209
        auto b = bars->get_object_with_primary_key("b");
2✔
6210

1✔
6211
        auto dict = foo.get_dictionary(foos->get_column_key("dict"));
2✔
6212
        CHECK_EQUAL(dict.size(), 2);
2✔
6213

1✔
6214
        auto dict_a = dict.get("a");
2✔
6215
        auto dict_b = dict.get("b");
2✔
6216
        CHECK(dict_a == Mixed{a.get_link()});
2✔
6217
        CHECK(dict_b == Mixed{b.get_link()});
2✔
6218
    }
2✔
6219

1✔
6220
    // Test that we can create tombstones for objects in dictionaries.
1✔
6221

1✔
6222
    write_transaction(db_1, [&](WriteTransaction& tr) {
2✔
6223
        auto& g = tr.get_group();
2✔
6224

1✔
6225
        auto bars = g.get_table("class_Bar");
2✔
6226
        auto a = bars->get_object_with_primary_key("a");
2✔
6227
        a.invalidate();
2✔
6228

1✔
6229
        auto foos = g.get_table("class_Foo");
2✔
6230
        auto foo = foos->get_object_with_primary_key(123);
2✔
6231
        auto dict = foo.get_dictionary(col_dict);
2✔
6232

1✔
6233
        CHECK_EQUAL(dict.size(), 2);
2✔
6234
        CHECK((*dict.find("a")).second.is_null());
2✔
6235

1✔
6236
        CHECK(dict.find("b") != dict.end());
2✔
6237
    });
2✔
6238

1✔
6239
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6240
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6241

1✔
6242
    {
2✔
6243
        ReadTransaction tr{db_2};
2✔
6244

1✔
6245
        auto foos = tr.get_table("class_Foo");
2✔
6246
        auto bars = tr.get_table("class_Bar");
2✔
6247

1✔
6248
        CHECK_EQUAL(foos->size(), 1);
2✔
6249
        CHECK_EQUAL(bars->size(), 1);
2✔
6250

1✔
6251
        auto b = bars->get_object_with_primary_key("b");
2✔
6252

1✔
6253
        auto foo = foos->get_object_with_primary_key(123);
2✔
6254
        auto dict = foo.get_dictionary(col_dict);
2✔
6255

1✔
6256
        CHECK_EQUAL(dict.size(), 2);
2✔
6257
        CHECK((*dict.find("a")).second.is_null());
2✔
6258

1✔
6259
        CHECK(dict.find("b") != dict.end());
2✔
6260
        CHECK((*dict.find("b")).second == Mixed{b.get_link()});
2✔
6261
    }
2✔
6262
}
2✔
6263

6264
TEST(Sync_Set)
6265
{
2✔
6266
    // Test replication and synchronization of Set values.
1✔
6267

1✔
6268
    TEST_CLIENT_DB(db_1);
2✔
6269
    TEST_CLIENT_DB(db_2);
2✔
6270

1✔
6271
    TEST_DIR(dir);
2✔
6272
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6273
    fixture.start();
2✔
6274

1✔
6275
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6276
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6277
    session_1.bind();
2✔
6278
    session_2.bind();
2✔
6279

1✔
6280
    ColKey col_ints, col_strings, col_mixeds;
2✔
6281
    {
2✔
6282
        WriteTransaction wt{db_1};
2✔
6283
        auto t = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "pk");
2✔
6284
        col_ints = t->add_column_set(type_Int, "ints");
2✔
6285
        col_strings = t->add_column_set(type_String, "strings");
2✔
6286
        col_mixeds = t->add_column_set(type_Mixed, "mixeds");
2✔
6287

1✔
6288
        auto obj = t->create_object_with_primary_key(0);
2✔
6289

1✔
6290
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6291
        auto strings = obj.get_set<StringData>(col_strings);
2✔
6292
        auto mixeds = obj.get_set<Mixed>(col_mixeds);
2✔
6293

1✔
6294
        ints.insert(123);
2✔
6295
        ints.insert(456);
2✔
6296
        ints.insert(789);
2✔
6297
        ints.insert(123);
2✔
6298
        ints.insert(456);
2✔
6299
        ints.insert(789);
2✔
6300

1✔
6301
        CHECK_EQUAL(ints.size(), 3);
2✔
6302
        CHECK_EQUAL(ints.find(123), 0);
2✔
6303
        CHECK_EQUAL(ints.find(456), 1);
2✔
6304
        CHECK_EQUAL(ints.find(789), 2);
2✔
6305

1✔
6306
        strings.insert("a");
2✔
6307
        strings.insert("b");
2✔
6308
        strings.insert("c");
2✔
6309
        strings.insert("a");
2✔
6310
        strings.insert("b");
2✔
6311
        strings.insert("c");
2✔
6312

1✔
6313
        CHECK_EQUAL(strings.size(), 3);
2✔
6314
        CHECK_EQUAL(strings.find("a"), 0);
2✔
6315
        CHECK_EQUAL(strings.find("b"), 1);
2✔
6316
        CHECK_EQUAL(strings.find("c"), 2);
2✔
6317

1✔
6318
        mixeds.insert(Mixed{123});
2✔
6319
        mixeds.insert(Mixed{"a"});
2✔
6320
        mixeds.insert(Mixed{456.0});
2✔
6321
        mixeds.insert(Mixed{123});
2✔
6322
        mixeds.insert(Mixed{"a"});
2✔
6323
        mixeds.insert(Mixed{456.0});
2✔
6324

1✔
6325
        CHECK_EQUAL(mixeds.size(), 3);
2✔
6326
        CHECK_EQUAL(mixeds.find(123), 0);
2✔
6327
        CHECK_EQUAL(mixeds.find(456.0), 1);
2✔
6328
        CHECK_EQUAL(mixeds.find("a"), 2);
2✔
6329

1✔
6330
        wt.commit();
2✔
6331
    }
2✔
6332

1✔
6333
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6334
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6335

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

1✔
6341
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6342
        ints.insert(999);
2✔
6343
    });
2✔
6344

1✔
6345
    write_transaction(db_2, [=](WriteTransaction& wt) {
2✔
6346
        auto t = wt.get_table("class_Foo");
2✔
6347
        auto obj = t->get_object_with_primary_key(0);
2✔
6348

1✔
6349
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6350
        ints.insert(999);
2✔
6351
        ints.erase(999);
2✔
6352
    });
2✔
6353

1✔
6354
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6355
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6356
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6357
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6358

1✔
6359
    {
2✔
6360
        ReadTransaction read_1{db_1};
2✔
6361
        ReadTransaction read_2{db_2};
2✔
6362
        CHECK(compare_groups(read_1, read_2));
2✔
6363
    }
2✔
6364

1✔
6365
    write_transaction(db_1, [=](WriteTransaction& wt) {
2✔
6366
        auto t = wt.get_table("class_Foo");
2✔
6367
        auto obj = t->get_object_with_primary_key(0);
2✔
6368
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6369
        ints.clear();
2✔
6370
    });
2✔
6371

1✔
6372
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6373
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6374

1✔
6375
    {
2✔
6376
        ReadTransaction read_1{db_1};
2✔
6377
        ReadTransaction read_2{db_2};
2✔
6378
        CHECK(compare_groups(read_1, read_2));
2✔
6379
    }
2✔
6380
}
2✔
6381

6382
TEST(Sync_DanglingLinksCountInPriorSize)
6383
{
2✔
6384
    SHARED_GROUP_TEST_PATH(path);
2✔
6385
    ClientReplication repl;
2✔
6386
    auto local_db = realm::DB::create(repl, path);
2✔
6387
    auto& history = repl.get_history();
2✔
6388
    history.set_client_file_ident(sync::SaltedFileIdent{1, 123456}, true);
2✔
6389

1✔
6390
    version_type last_version, last_version_observed = 0;
2✔
6391
    auto dump_uploadable = [&] {
4✔
6392
        UploadCursor upload_cursor{last_version_observed, 0};
4✔
6393
        std::vector<sync::ClientHistory::UploadChangeset> changesets_to_upload;
4✔
6394
        version_type locked_server_version = 0;
4✔
6395
        history.find_uploadable_changesets(upload_cursor, last_version, changesets_to_upload, locked_server_version);
4✔
6396
        CHECK_EQUAL(changesets_to_upload.size(), static_cast<size_t>(1));
4✔
6397
        realm::sync::Changeset parsed_changeset;
4✔
6398
        auto unparsed_changeset = changesets_to_upload[0].changeset.get_first_chunk();
4✔
6399
        realm::util::SimpleInputStream changeset_stream(unparsed_changeset);
4✔
6400
        realm::sync::parse_changeset(changeset_stream, parsed_changeset);
4✔
6401
        test_context.logger->info("changeset at version %1: %2", last_version, parsed_changeset);
4✔
6402
        last_version_observed = last_version;
4✔
6403
        return parsed_changeset;
4✔
6404
    };
4✔
6405

1✔
6406
    TableKey source_table_key, target_table_key;
2✔
6407
    {
2✔
6408
        auto wt = local_db->start_write();
2✔
6409
        auto source_table = wt->add_table_with_primary_key("class_source", type_String, "_id");
2✔
6410
        auto target_table = wt->add_table_with_primary_key("class_target", type_String, "_id");
2✔
6411
        source_table->add_column_list(*target_table, "links");
2✔
6412

1✔
6413
        source_table_key = source_table->get_key();
2✔
6414
        target_table_key = target_table->get_key();
2✔
6415

1✔
6416
        auto obj_to_keep = target_table->create_object_with_primary_key(std::string{"target1"});
2✔
6417
        auto obj_to_delete = target_table->create_object_with_primary_key(std::string{"target2"});
2✔
6418
        auto source_obj = source_table->create_object_with_primary_key(std::string{"source"});
2✔
6419

1✔
6420
        auto links_list = source_obj.get_linklist("links");
2✔
6421
        links_list.add(obj_to_keep.get_key());
2✔
6422
        links_list.add(obj_to_delete.get_key());
2✔
6423
        last_version = wt->commit();
2✔
6424
    }
2✔
6425

1✔
6426
    dump_uploadable();
2✔
6427

1✔
6428
    {
2✔
6429
        // Simulate removing the object via the sync client so we get a dangling link
1✔
6430
        TempShortCircuitReplication disable_repl(repl);
2✔
6431
        auto wt = local_db->start_write();
2✔
6432
        auto target_table = wt->get_table(target_table_key);
2✔
6433
        auto obj = target_table->get_object_with_primary_key(std::string{"target2"});
2✔
6434
        obj.invalidate();
2✔
6435
        last_version = wt->commit();
2✔
6436
    }
2✔
6437

1✔
6438
    {
2✔
6439
        auto wt = local_db->start_write();
2✔
6440
        auto source_table = wt->get_table(source_table_key);
2✔
6441
        auto target_table = wt->get_table(target_table_key);
2✔
6442

1✔
6443
        auto obj_to_add = target_table->create_object_with_primary_key(std::string{"target3"});
2✔
6444

1✔
6445
        auto source_obj = source_table->get_object_with_primary_key(std::string{"source"});
2✔
6446
        auto links_list = source_obj.get_linklist("links");
2✔
6447
        links_list.add(obj_to_add.get_key());
2✔
6448
        last_version = wt->commit();
2✔
6449
    }
2✔
6450

1✔
6451
    auto changeset = dump_uploadable();
2✔
6452
    CHECK_EQUAL(changeset.size(), static_cast<size_t>(2));
2✔
6453
    auto changeset_it = changeset.end();
2✔
6454
    --changeset_it;
2✔
6455
    auto last_instr = *changeset_it;
2✔
6456
    CHECK_EQUAL(last_instr->type(), Instruction::Type::ArrayInsert);
2✔
6457
    auto arr_insert_instr = last_instr->get_as<Instruction::ArrayInsert>();
2✔
6458
    CHECK_EQUAL(changeset.get_string(arr_insert_instr.table), StringData("source"));
2✔
6459
    CHECK(arr_insert_instr.value.type == sync::instr::Payload::Type::Link);
2✔
6460
    CHECK_EQUAL(changeset.get_string(mpark::get<InternString>(arr_insert_instr.value.data.link.target)),
2✔
6461
                StringData("target3"));
2✔
6462
    CHECK_EQUAL(arr_insert_instr.prior_size, 2);
2✔
6463
}
2✔
6464

6465
TEST(Sync_BundledRealmFile)
6466
{
2✔
6467
    TEST_CLIENT_DB(db);
2✔
6468
    SHARED_GROUP_TEST_PATH(path);
2✔
6469

1✔
6470
    TEST_DIR(dir);
2✔
6471
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6472
    fixture.start();
2✔
6473

1✔
6474
    Session session = fixture.make_bound_session(db);
2✔
6475

1✔
6476
    write_transaction(db, [](WriteTransaction& tr) {
2✔
6477
        auto foos = tr.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6478
        foos->create_object_with_primary_key(123);
2✔
6479
    });
2✔
6480

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

1✔
6484
    session.wait_for_upload_complete_or_client_stopped();
2✔
6485
    session.wait_for_download_complete_or_client_stopped();
2✔
6486

1✔
6487
    // Now we can
1✔
6488
    db->write_copy(path.c_str(), nullptr);
2✔
6489
}
2✔
6490

6491
TEST(Sync_UpgradeToClientHistory)
6492
{
2✔
6493
    SHARED_GROUP_TEST_PATH(db1_path);
2✔
6494
    SHARED_GROUP_TEST_PATH(db2_path);
2✔
6495
    auto db_1 = DB::create(make_in_realm_history(), db1_path);
2✔
6496
    auto db_2 = DB::create(make_in_realm_history(), db2_path);
2✔
6497
    {
2✔
6498
        auto tr = db_1->start_write();
2✔
6499

1✔
6500
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
6501
        auto col_float = embedded->add_column(type_Float, "float");
2✔
6502
        auto col_additional = embedded->add_column_dictionary(*embedded, "additional");
2✔
6503

1✔
6504
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6505
        auto col_list = baas->add_column_list(type_Int, "list");
2✔
6506
        auto col_set = baas->add_column_set(type_Int, "set");
2✔
6507
        auto col_dict = baas->add_column_dictionary(type_Int, "dictionary");
2✔
6508
        auto col_child = baas->add_column(*embedded, "child");
2✔
6509

1✔
6510
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6511
        auto col_str = foos->add_column(type_String, "str");
2✔
6512
        auto col_children = foos->add_column_list(*embedded, "children");
2✔
6513

1✔
6514
        auto foobaas = tr->add_table_with_primary_key("class_FooBaa", type_ObjectId, "_id");
2✔
6515
        auto col_time = foobaas->add_column(type_Timestamp, "time");
2✔
6516

1✔
6517
        auto col_link = baas->add_column(*foos, "link");
2✔
6518

1✔
6519
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Hello");
2✔
6520
        auto children = foo.get_linklist(col_children);
2✔
6521
        children.create_and_insert_linked_object(0);
2✔
6522
        auto baa = baas->create_object_with_primary_key(999).set(col_link, foo.get_key());
2✔
6523
        auto obj = baa.create_and_set_linked_object(col_child);
2✔
6524
        obj.set(col_float, 42.f);
2✔
6525
        auto additional = obj.get_dictionary(col_additional);
2✔
6526
        additional.create_and_insert_linked_object("One").set(col_float, 1.f);
2✔
6527
        additional.create_and_insert_linked_object("Two").set(col_float, 2.f);
2✔
6528
        additional.create_and_insert_linked_object("Three").set(col_float, 3.f);
2✔
6529

1✔
6530
        auto list = baa.get_list<Int>(col_list);
2✔
6531
        list.add(1);
2✔
6532
        list.add(2);
2✔
6533
        list.add(3);
2✔
6534
        auto set = baa.get_set<Int>(col_set);
2✔
6535
        set.insert(4);
2✔
6536
        set.insert(5);
2✔
6537
        set.insert(6);
2✔
6538
        auto dict = baa.get_dictionary(col_dict);
2✔
6539
        dict.insert("key7", 7);
2✔
6540
        dict.insert("key8", 8);
2✔
6541
        dict.insert("key9", 9);
2✔
6542

1✔
6543
        for (int i = 0; i < 100; i++) {
202✔
6544
            foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i));
200✔
6545
        }
200✔
6546

1✔
6547
        tr->commit();
2✔
6548
    }
2✔
6549
    {
2✔
6550
        auto tr = db_2->start_write();
2✔
6551
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6552
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6553
        auto col_str = foos->add_column(type_String, "str");
2✔
6554
        auto col_link = baas->add_column(*foos, "link");
2✔
6555

1✔
6556
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Goodbye");
2✔
6557
        baas->create_object_with_primary_key(888).set(col_link, foo.get_key());
2✔
6558

1✔
6559
        tr->commit();
2✔
6560
    }
2✔
6561

1✔
6562
    db_1->create_new_history(make_client_replication());
2✔
6563
    db_2->create_new_history(make_client_replication());
2✔
6564

1✔
6565
    TEST_DIR(dir);
2✔
6566
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6567
    fixture.start();
2✔
6568

1✔
6569
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6570
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6571
    session_1.bind();
2✔
6572
    session_2.bind();
2✔
6573

1✔
6574
    write_transaction(db_1, [](WriteTransaction& tr) {
2✔
6575
        auto foos = tr.get_group().get_table("class_Foo");
2✔
6576
        foos->create_object_with_primary_key("456");
2✔
6577
    });
2✔
6578
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6579
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6580
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6581
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6582

1✔
6583
    // db_2->start_read()->to_json(std::cout);
1✔
6584
}
2✔
6585

6586
// This test is extracted from ClientReset_ThreeClients
6587
// because it uncovers a bug in how MSVC 2019 compiles
6588
// things in Changeset::get_key()
6589
TEST(Sync_MergeStringPrimaryKey)
6590
{
2✔
6591
    TEST_DIR(dir_1); // The server.
2✔
6592
    TEST_CLIENT_DB(db_1);
2✔
6593
    TEST_CLIENT_DB(db_2);
2✔
6594
    TEST_DIR(metadata_dir_1);
2✔
6595
    TEST_DIR(metadata_dir_2);
2✔
6596

1✔
6597
    const std::string server_path = "/data";
2✔
6598

1✔
6599
    std::string real_path_1, real_path_2;
2✔
6600

1✔
6601
    auto create_schema = [&](Transaction& group) {
4✔
6602
        TableRef table_0 = group.add_table_with_primary_key("class_table_0", type_Int, "id");
4✔
6603
        table_0->add_column(type_Int, "int");
4✔
6604
        table_0->add_column(type_Bool, "bool");
4✔
6605
        table_0->add_column(type_Float, "float");
4✔
6606
        table_0->add_column(type_Double, "double");
4✔
6607
        table_0->add_column(type_Timestamp, "timestamp");
4✔
6608

2✔
6609
        TableRef table_1 = group.add_table_with_primary_key("class_table_1", type_Int, "pk_int");
4✔
6610
        table_1->add_column(type_String, "String");
4✔
6611

2✔
6612
        TableRef table_2 = group.add_table_with_primary_key("class_table_2", type_String, "pk_string");
4✔
6613
        table_2->add_column_list(type_String, "array_string");
4✔
6614
    };
4✔
6615

1✔
6616
    // First we make changesets. Then we upload them.
1✔
6617
    {
2✔
6618
        ClientServerFixture fixture(dir_1, test_context);
2✔
6619
        fixture.start();
2✔
6620
        real_path_1 = fixture.map_virtual_to_real_path(server_path);
2✔
6621

1✔
6622
        {
2✔
6623
            WriteTransaction wt{db_1};
2✔
6624
            create_schema(wt);
2✔
6625
            wt.commit();
2✔
6626
        }
2✔
6627
        {
2✔
6628
            WriteTransaction wt{db_2};
2✔
6629
            create_schema(wt);
2✔
6630

1✔
6631
            TableRef table_2 = wt.get_table("class_table_2");
2✔
6632
            auto col = table_2->get_column_key("array_string");
2✔
6633
            auto list_string = table_2->create_object_with_primary_key("aaa").get_list<String>(col);
2✔
6634
            list_string.add("a");
2✔
6635
            list_string.add("b");
2✔
6636

1✔
6637
            wt.commit();
2✔
6638
        }
2✔
6639

1✔
6640
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
6641
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
6642

1✔
6643
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
6644
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
6645
        // Download completion is not important.
1✔
6646
    }
2✔
6647
}
2✔
6648

6649
TEST(Sync_NonIncreasingServerVersions)
6650
{
2✔
6651
    TEST_CLIENT_DB(db);
2✔
6652

1✔
6653
    auto& history = get_history(db);
2✔
6654
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6655
    timestamp_type timestamp{1};
2✔
6656
    history.set_local_origin_timestamp_source([&] {
2✔
6657
        return ++timestamp;
2✔
6658
    });
2✔
6659

1✔
6660
    auto latest_local_version = [&] {
2✔
6661
        auto tr = db->start_write();
2✔
6662
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6663
        return tr->commit();
2✔
6664
    }();
2✔
6665

1✔
6666
    std::vector<Changeset> server_changesets;
2✔
6667
    auto prep_changeset = [&](auto pk_name, auto int_col_val) {
8✔
6668
        Changeset changeset;
8✔
6669
        changeset.version = 10;
8✔
6670
        changeset.last_integrated_remote_version = latest_local_version - 1;
8✔
6671
        changeset.origin_timestamp = ++timestamp;
8✔
6672
        changeset.origin_file_ident = 1;
8✔
6673
        instr::PrimaryKey pk{changeset.intern_string(pk_name)};
8✔
6674
        auto table_name = changeset.intern_string("foo");
8✔
6675
        auto col_name = changeset.intern_string("int_col");
8✔
6676
        instr::EraseObject erase_1;
8✔
6677
        erase_1.object = pk;
8✔
6678
        erase_1.table = table_name;
8✔
6679
        changeset.push_back(erase_1);
8✔
6680
        instr::CreateObject create_1;
8✔
6681
        create_1.object = pk;
8✔
6682
        create_1.table = table_name;
8✔
6683
        changeset.push_back(create_1);
8✔
6684
        instr::Update update_1;
8✔
6685
        update_1.table = table_name;
8✔
6686
        update_1.object = pk;
8✔
6687
        update_1.field = col_name;
8✔
6688
        update_1.value = instr::Payload{int64_t(int_col_val)};
8✔
6689
        changeset.push_back(update_1);
8✔
6690
        server_changesets.push_back(std::move(changeset));
8✔
6691
    };
8✔
6692
    prep_changeset("bizz", 1);
2✔
6693
    prep_changeset("buzz", 2);
2✔
6694
    prep_changeset("baz", 3);
2✔
6695
    prep_changeset("bar", 4);
2✔
6696
    ++server_changesets.back().version;
2✔
6697

1✔
6698
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6699
    std::vector<Transformer::RemoteChangeset> server_changesets_encoded;
2✔
6700
    for (const auto& changeset : server_changesets) {
8✔
6701
        encoded.emplace_back();
8✔
6702
        encode_changeset(changeset, encoded.back());
8✔
6703
        server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
8✔
6704
                                               BinaryData(encoded.back().data(), encoded.back().size()),
8✔
6705
                                               changeset.origin_timestamp, changeset.origin_file_ident);
8✔
6706
    }
8✔
6707

1✔
6708
    SyncProgress progress = {};
2✔
6709
    progress.download.server_version = server_changesets.back().version;
2✔
6710
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6711
    progress.latest_server_version.version = server_changesets.back().version;
2✔
6712
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6713

1✔
6714
    uint_fast64_t downloadable_bytes = 0;
2✔
6715
    VersionInfo version_info;
2✔
6716
    auto transact = db->start_read();
2✔
6717
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6718
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6719
}
2✔
6720

6721
TEST(Sync_InvalidChangesetFromServer)
6722
{
2✔
6723
    TEST_CLIENT_DB(db);
2✔
6724

1✔
6725
    auto& history = get_history(db);
2✔
6726
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6727

1✔
6728
    instr::CreateObject bad_instr;
2✔
6729
    bad_instr.object = InternString{1};
2✔
6730
    bad_instr.table = InternString{2};
2✔
6731

1✔
6732
    Changeset changeset;
2✔
6733
    changeset.push_back(bad_instr);
2✔
6734

1✔
6735
    ChangesetEncoder::Buffer encoded;
2✔
6736
    encode_changeset(changeset, encoded);
2✔
6737
    Transformer::RemoteChangeset server_changeset;
2✔
6738
    server_changeset.origin_file_ident = 1;
2✔
6739
    server_changeset.remote_version = 1;
2✔
6740
    server_changeset.data = BinaryData(encoded.data(), encoded.size());
2✔
6741

1✔
6742
    VersionInfo version_info;
2✔
6743
    auto transact = db->start_read();
2✔
6744
    CHECK_THROW_EX(history.integrate_server_changesets({}, nullptr, util::Span(&server_changeset, 1), version_info,
2✔
6745
                                                       DownloadBatchState::SteadyState, *test_context.logger,
2✔
6746
                                                       transact),
2✔
6747
                   sync::IntegrationException,
2✔
6748
                   StringData(e.what()).contains("Failed to parse received changeset: Invalid interned string"));
2✔
6749
}
2✔
6750

6751
TEST(Sync_DifferentUsersMultiplexing)
6752
{
2✔
6753
    ClientServerFixture::Config fixture_config;
2✔
6754
    fixture_config.one_connection_per_session = false;
2✔
6755

1✔
6756
    TEST_DIR(server_dir);
2✔
6757
    ClientServerFixture fixture(server_dir, test_context, std::move(fixture_config));
2✔
6758

1✔
6759
    struct SessionBundle {
2✔
6760
        test_util::DBTestPathGuard path_guard;
2✔
6761
        DBRef db;
2✔
6762
        Session sess;
2✔
6763

1✔
6764
        SessionBundle(unit_test::TestContext& ctx, ClientServerFixture& fixture, std::string name,
2✔
6765
                      std::string signed_token, std::string user_id)
2✔
6766
            : path_guard(realm::test_util::get_test_path(ctx.get_test_name(), "." + name + ".realm"))
2✔
6767
            , db(DB::create(make_client_replication(), path_guard))
2✔
6768
        {
8✔
6769
            Session::Config config;
8✔
6770
            config.signed_user_token = signed_token;
8✔
6771
            config.user_id = user_id;
8✔
6772
            sess = fixture.make_bound_session(db, "/test", std::move(config));
8✔
6773
            sess.wait_for_download_complete_or_client_stopped();
8✔
6774
        }
8✔
6775
    };
2✔
6776

1✔
6777
    fixture.start();
2✔
6778

1✔
6779
    SessionBundle user_1_sess_1(test_context, fixture, "user_1_db_1", g_user_0_token, "user_0");
2✔
6780
    SessionBundle user_2_sess_1(test_context, fixture, "user_2_db_1", g_user_1_token, "user_1");
2✔
6781
    SessionBundle user_1_sess_2(test_context, fixture, "user_1_db_2", g_user_0_token, "user_0");
2✔
6782
    SessionBundle user_2_sess_2(test_context, fixture, "user_2_db_2", g_user_1_token, "user_1");
2✔
6783

1✔
6784
    CHECK_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6785
                user_1_sess_2.sess.get_appservices_connection_id());
2✔
6786
    CHECK_EQUAL(user_2_sess_1.sess.get_appservices_connection_id(),
2✔
6787
                user_2_sess_2.sess.get_appservices_connection_id());
2✔
6788
    CHECK_NOT_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6789
                    user_2_sess_1.sess.get_appservices_connection_id());
2✔
6790
    CHECK_NOT_EQUAL(user_1_sess_2.sess.get_appservices_connection_id(),
2✔
6791
                    user_2_sess_2.sess.get_appservices_connection_id());
2✔
6792
}
2✔
6793

6794
// Tests that an empty reciprocal changesets is set and retrieved correctly.
6795
TEST(Sync_SetAndGetEmptyReciprocalChangeset)
6796
{
2✔
6797
    using namespace realm;
2✔
6798
    using namespace realm::sync::instr;
2✔
6799
    using realm::sync::Changeset;
2✔
6800

1✔
6801
    TEST_CLIENT_DB(db);
2✔
6802

1✔
6803
    auto& history = get_history(db);
2✔
6804
    history.set_client_file_ident(SaltedFileIdent{1, 0x1234567812345678}, false);
2✔
6805
    timestamp_type timestamp{1};
2✔
6806
    history.set_local_origin_timestamp_source([&] {
6✔
6807
        return ++timestamp;
6✔
6808
    });
6✔
6809

1✔
6810
    auto latest_local_version = [&] {
2✔
6811
        auto tr = db->start_write();
2✔
6812
        // Create schema: single table with array of ints as property.
1✔
6813
        tr->add_table_with_primary_key("class_table", type_Int, "_id")->add_column_list(type_Int, "ints");
2✔
6814
        tr->commit_and_continue_writing();
2✔
6815

1✔
6816
        // Create object and initialize array.
1✔
6817
        TableRef table = tr->get_table("class_table");
2✔
6818
        auto obj = table->create_object_with_primary_key(42);
2✔
6819
        auto ints = obj.get_list<int64_t>("ints");
2✔
6820
        for (auto i = 0; i < 8; ++i) {
18✔
6821
            ints.insert(i, i);
16✔
6822
        }
16✔
6823
        tr->commit_and_continue_writing();
2✔
6824

1✔
6825
        // Move element in array.
1✔
6826
        ints.move(7, 2);
2✔
6827
        return tr->commit();
2✔
6828
    }();
2✔
6829

1✔
6830
    // Create changeset which moves element from index 7 to index 0 in array.
1✔
6831
    // This changeset will discard the previous move (reciprocal changeset), leaving the local reciprocal changesets
1✔
6832
    // with no instructions (empty).
1✔
6833
    Changeset changeset;
2✔
6834
    ArrayMove instr;
2✔
6835
    instr.table = changeset.intern_string("table");
2✔
6836
    instr.object = instr::PrimaryKey{42};
2✔
6837
    instr.field = changeset.intern_string("ints");
2✔
6838
    instr.path.push_back(7);
2✔
6839
    instr.ndx_2 = 0;
2✔
6840
    instr.prior_size = 8;
2✔
6841
    changeset.push_back(instr);
2✔
6842
    changeset.version = 1;
2✔
6843
    changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6844
    changeset.origin_timestamp = timestamp;
2✔
6845
    changeset.origin_file_ident = 2;
2✔
6846

1✔
6847
    ChangesetEncoder::Buffer encoded;
2✔
6848
    std::vector<Transformer::RemoteChangeset> server_changesets_encoded;
2✔
6849
    encode_changeset(changeset, encoded);
2✔
6850
    server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
2✔
6851
                                           BinaryData(encoded.data(), encoded.size()), changeset.origin_timestamp,
2✔
6852
                                           changeset.origin_file_ident);
2✔
6853

1✔
6854
    SyncProgress progress = {};
2✔
6855
    progress.download.server_version = changeset.version;
2✔
6856
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6857
    progress.latest_server_version.version = changeset.version;
2✔
6858
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6859

1✔
6860
    uint_fast64_t downloadable_bytes = 0;
2✔
6861
    VersionInfo version_info;
2✔
6862
    auto transact = db->start_read();
2✔
6863
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6864
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6865

1✔
6866
    bool is_compressed = false;
2✔
6867
    auto data = history.get_reciprocal_transform(latest_local_version, is_compressed);
2✔
6868
    Changeset reciprocal_changeset;
2✔
6869
    ChunkedBinaryInputStream in{data};
2✔
6870
    if (is_compressed) {
2✔
6871
        size_t total_size;
2✔
6872
        auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
2✔
6873
        CHECK(decompressed);
2✔
6874
        sync::parse_changeset(*decompressed, reciprocal_changeset); // Throws
2✔
6875
    }
2✔
6876
    else {
×
6877
        sync::parse_changeset(in, reciprocal_changeset); // Throws
×
6878
    }
×
6879
    // The only instruction in the reciprocal changeset was discarded during OT.
1✔
6880
    CHECK(reciprocal_changeset.empty());
2✔
6881
}
2✔
6882

6883
TEST(Sync_TransformAgainstEmptyReciprocalChangeset)
6884
{
2✔
6885
    TEST_CLIENT_DB(seed_db);
2✔
6886
    TEST_CLIENT_DB(db_1);
2✔
6887
    TEST_CLIENT_DB(db_2);
2✔
6888

1✔
6889
    {
2✔
6890
        auto tr = seed_db->start_write();
2✔
6891
        // Create schema: single table with array of ints as property.
1✔
6892
        auto table = tr->add_table_with_primary_key("class_table", type_Int, "_id");
2✔
6893
        table->add_column_list(type_Int, "ints");
2✔
6894
        table->add_column(type_String, "string");
2✔
6895
        tr->commit_and_continue_writing();
2✔
6896

1✔
6897
        // Create object and initialize array.
1✔
6898
        table = tr->get_table("class_table");
2✔
6899
        auto obj = table->create_object_with_primary_key(42);
2✔
6900
        auto ints = obj.get_list<int64_t>("ints");
2✔
6901
        for (auto i = 0; i < 8; ++i) {
18✔
6902
            ints.insert(i, i);
16✔
6903
        }
16✔
6904
        tr->commit();
2✔
6905
    }
2✔
6906

1✔
6907
    {
2✔
6908
        TEST_DIR(dir);
2✔
6909
        MultiClientServerFixture fixture(3, 1, dir, test_context);
2✔
6910
        fixture.start();
2✔
6911

1✔
6912
        util::Optional<Session> seed_session = fixture.make_bound_session(0, seed_db, 0, "/test");
2✔
6913
        util::Optional<Session> db_1_session = fixture.make_bound_session(1, db_1, 0, "/test");
2✔
6914
        util::Optional<Session> db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6915

1✔
6916
        seed_session->wait_for_upload_complete_or_client_stopped();
2✔
6917
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6918
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6919
        seed_session.reset();
2✔
6920
        db_2_session.reset();
2✔
6921

1✔
6922
        auto move_element = [&](const DBRef& db, size_t from, size_t to, size_t string_size = 0) {
8✔
6923
            auto wt = db->start_write();
8✔
6924
            auto table = wt->get_table("class_table");
8✔
6925
            auto obj = table->get_object_with_primary_key(42);
8✔
6926
            auto ints = obj.get_list<int64_t>("ints");
8✔
6927
            ints.move(from, to);
8✔
6928
            obj.set("string", std::string(string_size, 'a'));
8✔
6929
            wt->commit();
8✔
6930
        };
8✔
6931

1✔
6932
        // Client 1 uploads two move instructions.
1✔
6933
        move_element(db_1, 7, 2);
2✔
6934
        move_element(db_1, 7, 6);
2✔
6935

1✔
6936
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6937

1✔
6938
        std::this_thread::sleep_for(std::chrono::milliseconds{10});
2✔
6939

1✔
6940
        // Client 2 uploads two move instructions.
1✔
6941
        // The sync client uploads at most 128 KB of data so we make the first changeset large enough so two upload
1✔
6942
        // messages are sent to the server instead of one. Each change is transformed against the changes from
1✔
6943
        // Client 1.
1✔
6944

1✔
6945
        // First change discards the first change (move(7, 2)) of Client 1.
1✔
6946
        move_element(db_2, 7, 0, 200 * 1024);
2✔
6947
        // Second change is tranformed against an empty reciprocal changeset as result of the change above.
1✔
6948
        move_element(db_2, 7, 5);
2✔
6949
        db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6950

1✔
6951
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6952
        db_2_session->wait_for_upload_complete_or_client_stopped();
2✔
6953

1✔
6954
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6955
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6956
    }
2✔
6957

1✔
6958
    ReadTransaction rt_1(db_1);
2✔
6959
    ReadTransaction rt_2(db_2);
2✔
6960
    const Group& group_1 = rt_1;
2✔
6961
    const Group& group_2 = rt_2;
2✔
6962
    group_1.verify();
2✔
6963
    group_2.verify();
2✔
6964
    CHECK(compare_groups(rt_1, rt_2, *test_context.logger));
2✔
6965
}
2✔
6966

6967
TEST(Sync_ServerVersionsSkippedFromDownloadCursor)
6968
{
2✔
6969
    TEST_CLIENT_DB(db);
2✔
6970

1✔
6971
    auto& history = get_history(db);
2✔
6972
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6973
    timestamp_type timestamp{1};
2✔
6974
    history.set_local_origin_timestamp_source([&] {
2✔
6975
        return ++timestamp;
2✔
6976
    });
2✔
6977

1✔
6978
    auto latest_local_version = [&] {
2✔
6979
        auto tr = db->start_write();
2✔
6980
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6981
        return tr->commit();
2✔
6982
    }();
2✔
6983

1✔
6984
    Changeset server_changeset;
2✔
6985
    server_changeset.version = 10;
2✔
6986
    server_changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6987
    server_changeset.origin_timestamp = ++timestamp;
2✔
6988
    server_changeset.origin_file_ident = 1;
2✔
6989

1✔
6990
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6991
    std::vector<Transformer::RemoteChangeset> server_changesets_encoded;
2✔
6992
    encoded.emplace_back();
2✔
6993
    encode_changeset(server_changeset, encoded.back());
2✔
6994
    server_changesets_encoded.emplace_back(server_changeset.version, server_changeset.last_integrated_remote_version,
2✔
6995
                                           BinaryData(encoded.back().data(), encoded.back().size()),
2✔
6996
                                           server_changeset.origin_timestamp, server_changeset.origin_file_ident);
2✔
6997

1✔
6998
    SyncProgress progress = {};
2✔
6999
    // The server skips 10 server versions.
1✔
7000
    progress.download.server_version = server_changeset.version + 10;
2✔
7001
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
7002
    progress.latest_server_version.version = server_changeset.version + 15;
2✔
7003
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
7004

1✔
7005
    uint_fast64_t downloadable_bytes = 0;
2✔
7006
    VersionInfo version_info;
2✔
7007
    auto transact = db->start_read();
2✔
7008
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
7009
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
7010

1✔
7011
    version_type current_version;
2✔
7012
    SaltedFileIdent file_ident;
2✔
7013
    SyncProgress expected_progress;
2✔
7014
    history.get_status(current_version, file_ident, expected_progress);
2✔
7015

1✔
7016
    // Check progress is reported correctly.
1✔
7017
    CHECK_EQUAL(progress.latest_server_version.salt, expected_progress.latest_server_version.salt);
2✔
7018
    CHECK_EQUAL(progress.latest_server_version.version, expected_progress.latest_server_version.version);
2✔
7019
    CHECK_EQUAL(progress.download.last_integrated_client_version,
2✔
7020
                expected_progress.download.last_integrated_client_version);
2✔
7021
    CHECK_EQUAL(progress.download.server_version, expected_progress.download.server_version);
2✔
7022
    CHECK_EQUAL(progress.upload.client_version, expected_progress.upload.client_version);
2✔
7023
    CHECK_EQUAL(progress.upload.last_integrated_server_version,
2✔
7024
                expected_progress.upload.last_integrated_server_version);
2✔
7025
}
2✔
7026

7027
} // 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