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

realm / realm-core / 1730

04 Oct 2023 01:46PM UTC coverage: 91.575% (-0.04%) from 91.615%
1730

push

Evergreen

web-flow
Initial ObjectStore Class class (#6521)

94264 of 173446 branches covered (0.0%)

30 of 70 new or added lines in 10 files covered. (42.86%)

96 existing lines in 17 files now uncovered.

230350 of 251543 relevant lines covered (91.57%)

6843633.6 hits per line

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

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

109
template <typename Function>
110
void write_transaction_notifying_session(DBRef db, Session& session, Function&& function)
111
{
8,102✔
112
    WriteTransaction wt(db);
8,102✔
113
    function(wt);
8,102✔
114
    auto new_version = wt.commit();
8,102✔
115
    session.nonsync_transact_notify(new_version);
8,102✔
116
}
8,102✔
117

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

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

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

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

1✔
143
    int nerrors = 0;
2✔
144

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

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

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

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

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

174

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

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

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

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

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

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

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

213

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

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

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

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

237

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

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

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

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

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

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

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

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

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

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

299

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

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

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

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

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

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

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

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

348

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

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

373

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

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

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

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

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

409

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

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

433

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

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

1✔
446
    // Again
1✔
447
    session_1.wait_for_download_complete_or_client_stopped();
2✔
448

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

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

1✔
464
    // Again
1✔
465
    session_1.wait_for_download_complete_or_client_stopped();
2✔
466

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

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

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

485

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

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

510

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

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

531

532
TEST(Sync_WaitForSessionTerminations)
533
{
2✔
534
    TEST_DIR(server_dir);
2✔
535
    TEST_CLIENT_DB(db);
2✔
536

1✔
537
    ClientServerFixture fixture(server_dir, test_context);
2✔
538
    fixture.start();
2✔
539

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

560

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

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

1✔
578
        fixture.start();
2✔
579

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

594

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

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

624

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

1✔
632
    Session session = fixture.make_bound_session(db);
2✔
633

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

651

652
TEST(Sync_Replication)
653
{
2✔
654
    // Replicate changes in file 1 to file 2.
1✔
655

1✔
656
    TEST_CLIENT_DB(db_1);
2✔
657
    TEST_CLIENT_DB(db_2);
2✔
658

1✔
659
    {
2✔
660
        TEST_DIR(dir);
2✔
661
        ClientServerFixture fixture(dir, test_context);
2✔
662
        fixture.start();
2✔
663

1✔
664
        version_type sync_transact_callback_version = 0;
2✔
665
        auto sync_transact_callback = [&](VersionID, VersionID new_version) {
68✔
666
            // May be called once or multiple times depending on timing
31✔
667
            sync_transact_callback_version = new_version.version;
68✔
668
        };
68✔
669

1✔
670
        Session session_1 = fixture.make_bound_session(db_1);
2✔
671

1✔
672
        Session session_2 = fixture.make_session(db_2, "/test");
2✔
673
        session_2.set_sync_transact_callback(std::move(sync_transact_callback));
2✔
674
        session_2.bind();
2✔
675

1✔
676
        // Create schema
1✔
677
        write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
678
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
679
            table->add_column(type_Int, "i");
2✔
680
        });
2✔
681
        Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
682
        for (int i = 0; i < 100; ++i) {
202✔
683
            WriteTransaction wt(db_1);
200✔
684
            TableRef table = wt.get_table("class_foo");
200✔
685
            table->create_object_with_primary_key(i);
200✔
686
            Obj obj = *(table->begin() + random.draw_int_mod(table->size()));
200✔
687
            obj.set<int64_t>("i", random.draw_int_max(0x7FFFFFFFFFFFFFFF));
200✔
688
            version_type new_version = wt.commit();
200✔
689
            session_1.nonsync_transact_notify(new_version);
200✔
690
        }
200✔
691

1✔
692
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
693
        session_2.wait_for_download_complete_or_client_stopped();
2✔
694

1✔
695
        {
2✔
696
            ReadTransaction rt(db_2);
2✔
697
            CHECK_EQUAL(rt.get_version(), sync_transact_callback_version);
2✔
698
        }
2✔
699
    }
2✔
700

1✔
701
    ReadTransaction rt_1(db_1);
2✔
702
    ReadTransaction rt_2(db_2);
2✔
703
    const Group& group_1 = rt_1;
2✔
704
    const Group& group_2 = rt_2;
2✔
705
    group_1.verify();
2✔
706
    group_2.verify();
2✔
707
    CHECK(compare_groups(rt_1, rt_2));
2✔
708
    ConstTableRef table = group_1.get_table("class_foo");
2✔
709
    CHECK_EQUAL(100, table->size());
2✔
710
}
2✔
711

712

713
TEST(Sync_Merge)
714
{
2✔
715

1✔
716
    TEST_CLIENT_DB(db_1);
2✔
717
    TEST_CLIENT_DB(db_2);
2✔
718

1✔
719
    {
2✔
720
        TEST_DIR(dir);
2✔
721
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
722
        fixture.start();
2✔
723

1✔
724
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
725
        session_1.bind();
2✔
726

1✔
727
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
728
        session_2.bind();
2✔
729

1✔
730
        // Create schema on both clients.
1✔
731
        auto create_schema = [](Session& sess, DBRef db) {
4✔
732
            WriteTransaction wt(db);
4✔
733
            if (wt.has_table("class_foo"))
4✔
734
                return;
×
735
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
4✔
736
            table->add_column(type_Int, "i");
4✔
737
            version_type new_version = wt.commit();
4✔
738
            sess.nonsync_transact_notify(new_version);
4✔
739
        };
4✔
740
        create_schema(session_1, db_1);
2✔
741
        create_schema(session_2, db_2);
2✔
742

1✔
743
        write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
744
            TableRef table = wt.get_table("class_foo");
2✔
745
            table->create_object_with_primary_key(1).set("i", 5);
2✔
746
            table->create_object_with_primary_key(2).set("i", 6);
2✔
747
        });
2✔
748
        write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
749
            TableRef table = wt.get_table("class_foo");
2✔
750
            table->create_object_with_primary_key(3).set("i", 7);
2✔
751
            table->create_object_with_primary_key(4).set("i", 8);
2✔
752
        });
2✔
753

1✔
754
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
755
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
756
        session_1.wait_for_download_complete_or_client_stopped();
2✔
757
        session_2.wait_for_download_complete_or_client_stopped();
2✔
758
    }
2✔
759

1✔
760
    ReadTransaction rt_1(db_1);
2✔
761
    ReadTransaction rt_2(db_2);
2✔
762
    const Group& group_1 = rt_1;
2✔
763
    const Group& group_2 = rt_2;
2✔
764
    group_1.verify();
2✔
765
    group_2.verify();
2✔
766
    CHECK(compare_groups(rt_1, rt_2));
2✔
767
    ConstTableRef table = group_1.get_table("class_foo");
2✔
768
    CHECK_EQUAL(4, table->size());
2✔
769
}
2✔
770

771
struct ExpectChangesetError {
772
    unit_test::TestContext& test_context;
773
    MultiClientServerFixture& fixture;
774
    std::string expected_error;
775

776
    void operator()(ConnectionState state, util::Optional<ErrorInfo> error_info) const noexcept
777
    {
60✔
778
        if (state == ConnectionState::disconnected) {
60✔
UNCOV
779
            return;
×
UNCOV
780
        }
×
781
        if (!error_info)
60✔
782
            return;
45✔
783
        REALM_ASSERT(error_info);
15✔
784
        CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
15✔
785
        CHECK(!error_info->is_fatal);
15✔
786
        CHECK_EQUAL(error_info->status.reason(),
15✔
787
                    "Failed to transform received changeset: Schema mismatch: " + expected_error);
15✔
788
        fixture.stop();
15✔
789
    }
15✔
790
};
791

792
void test_schema_mismatch(unit_test::TestContext& test_context, util::FunctionRef<void(WriteTransaction&)>&& fn_1,
793
                          util::FunctionRef<void(WriteTransaction&)>&& fn_2, const char* expected_error_1,
794
                          const char* expected_error_2 = nullptr)
795
{
12✔
796
    auto perform_write_transaction = [](DBRef db, util::FunctionRef<void(WriteTransaction&)>&& function) {
24✔
797
        WriteTransaction wt(db);
24✔
798
        function(wt);
24✔
799
        return wt.commit();
24✔
800
    };
24✔
801

6✔
802
    TEST_CLIENT_DB(db_1);
12✔
803
    TEST_CLIENT_DB(db_2);
12✔
804

6✔
805
    TEST_DIR(dir);
12✔
806
    MultiClientServerFixture fixture(2, 1, dir, test_context);
12✔
807
    fixture.allow_server_errors(0, 1);
12✔
808
    fixture.start();
12✔
809

6✔
810
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
12✔
811
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
12✔
812

6✔
813
    if (!expected_error_2)
12✔
814
        expected_error_2 = expected_error_1;
4✔
815

6✔
816
    session_1.set_connection_state_change_listener(ExpectChangesetError{test_context, fixture, expected_error_1});
12✔
817
    session_2.set_connection_state_change_listener(ExpectChangesetError{test_context, fixture, expected_error_2});
12✔
818

6✔
819
    session_1.bind();
12✔
820
    session_2.bind();
12✔
821

6✔
822
    // NOTE: There was a race condition with `write_transaction_notifying_session` where session_2
6✔
823
    // was completing sync before the write transaction was completed, leading to a
6✔
824
    // `realm::TableNameInUse` exception. Broke up this function and moved the call to
6✔
825
    // `nonsync_transact_notify()` to after the write transactions.
6✔
826
    auto version_1 = perform_write_transaction(db_1, std::move(fn_1));
12✔
827
    auto version_2 = perform_write_transaction(db_2, std::move(fn_2));
12✔
828
    session_1.nonsync_transact_notify(version_1);
12✔
829
    session_2.nonsync_transact_notify(version_2);
12✔
830

6✔
831
    session_1.wait_for_upload_complete_or_client_stopped();
12✔
832
    session_2.wait_for_upload_complete_or_client_stopped();
12✔
833
    session_1.wait_for_download_complete_or_client_stopped();
12✔
834
    session_2.wait_for_download_complete_or_client_stopped();
12✔
835
}
12✔
836

837

838
TEST(Sync_DetectSchemaMismatch_ColumnType)
839
{
2✔
840
    test_schema_mismatch(
2✔
841
        test_context,
2✔
842
        [](WriteTransaction& wt) {
2✔
843
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
844
            ColKey col_ndx = table->add_column(type_Int, "column");
2✔
845
            table->create_object_with_primary_key(1).set<int64_t>(col_ndx, 123);
2✔
846
        },
2✔
847
        [](WriteTransaction& wt) {
2✔
848
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
849
            ColKey col_ndx = table->add_column(type_String, "column");
2✔
850
            table->create_object_with_primary_key(2).set(col_ndx, "Hello, World!");
2✔
851
        },
2✔
852
        "Property 'column' in class 'foo' is of type Int on one side and type String on the other.",
2✔
853
        "Property 'column' in class 'foo' is of type String on one side and type Int on the other.");
2✔
854
}
2✔
855

856

857
TEST(Sync_DetectSchemaMismatch_Nullability)
858
{
2✔
859
    test_schema_mismatch(
2✔
860
        test_context,
2✔
861
        [](WriteTransaction& wt) {
2✔
862
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
863
            bool nullable = false;
2✔
864
            ColKey col_ndx = table->add_column(type_Int, "column", nullable);
2✔
865
            table->create_object_with_primary_key(1).set<int64_t>(col_ndx, 123);
2✔
866
        },
2✔
867
        [](WriteTransaction& wt) {
2✔
868
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
869
            bool nullable = true;
2✔
870
            ColKey col_ndx = table->add_column(type_Int, "column", nullable);
2✔
871
            table->create_object_with_primary_key(2).set<int64_t>(col_ndx, 123);
2✔
872
        },
2✔
873
        "Property 'column' in class 'foo' is nullable on one side and not on the other.");
2✔
874
}
2✔
875

876

877
TEST(Sync_DetectSchemaMismatch_Links)
878
{
2✔
879
    test_schema_mismatch(
2✔
880
        test_context,
2✔
881
        [](WriteTransaction& wt) {
2✔
882
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
883
            TableRef target = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
884
            table->add_column(*target, "column");
2✔
885
        },
2✔
886
        [](WriteTransaction& wt) {
2✔
887
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
888
            TableRef target = wt.get_group().add_table_with_primary_key("class_baz", type_Int, "id");
2✔
889
            table->add_column(*target, "column");
2✔
890
        },
2✔
891
        "Link property 'column' in class 'foo' points to class 'bar' on one side and to 'baz' on the other.",
2✔
892
        "Link property 'column' in class 'foo' points to class 'baz' on one side and to 'bar' on the other.");
2✔
893
}
2✔
894

895

896
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Name)
897
{
2✔
898
    test_schema_mismatch(
2✔
899
        test_context,
2✔
900
        [](WriteTransaction& wt) {
2✔
901
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a");
2✔
902
        },
2✔
903
        [](WriteTransaction& wt) {
2✔
904
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "b");
2✔
905
        },
2✔
906
        "'foo' has primary key 'a' on one side, but primary key 'b' on the other.",
2✔
907
        "'foo' has primary key 'b' on one side, but primary key 'a' on the other.");
2✔
908
}
2✔
909

910

911
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Type)
912
{
2✔
913
    test_schema_mismatch(
2✔
914
        test_context,
2✔
915
        [](WriteTransaction& wt) {
2✔
916
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a");
2✔
917
        },
2✔
918
        [](WriteTransaction& wt) {
2✔
919
            wt.get_group().add_table_with_primary_key("class_foo", type_String, "a");
2✔
920
        },
2✔
921
        "'foo' has primary key 'a', which is of type Int on one side and type String on the other.",
2✔
922
        "'foo' has primary key 'a', which is of type String on one side and type Int on the other.");
2✔
923
}
2✔
924

925

926
TEST(Sync_DetectSchemaMismatch_PrimaryKeys_Nullability)
927
{
2✔
928
    test_schema_mismatch(
2✔
929
        test_context,
2✔
930
        [](WriteTransaction& wt) {
2✔
931
            bool nullable = false;
2✔
932
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a", nullable);
2✔
933
        },
2✔
934
        [](WriteTransaction& wt) {
2✔
935
            bool nullable = true;
2✔
936
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "a", nullable);
2✔
937
        },
2✔
938
        "'foo' has primary key 'a', which is nullable on one side, but not the other.");
2✔
939
}
2✔
940

941

942
TEST(Sync_LateBind)
943
{
2✔
944
    // Test that a session can be initiated at a point in time where the client
1✔
945
    // already has established a connection to the server.
1✔
946

1✔
947
    TEST_CLIENT_DB(db_1);
2✔
948
    TEST_CLIENT_DB(db_2);
2✔
949

1✔
950
    {
2✔
951
        TEST_DIR(dir);
2✔
952
        ClientServerFixture fixture(dir, test_context);
2✔
953
        fixture.start();
2✔
954

1✔
955
        Session session_1 = fixture.make_bound_session(db_1);
2✔
956
        write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
957
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
958
        });
2✔
959
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
960

1✔
961
        Session session_2 = fixture.make_bound_session(db_2);
2✔
962
        write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
963
            wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
964
        });
2✔
965
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
966

1✔
967
        session_1.wait_for_download_complete_or_client_stopped();
2✔
968
        session_2.wait_for_download_complete_or_client_stopped();
2✔
969
    }
2✔
970

1✔
971
    ReadTransaction rt_1(db_1);
2✔
972
    ReadTransaction rt_2(db_2);
2✔
973
    const Group& group_1 = rt_1;
2✔
974
    const Group& group_2 = rt_2;
2✔
975
    group_1.verify();
2✔
976
    group_2.verify();
2✔
977
    CHECK(compare_groups(rt_1, rt_2));
2✔
978
    CHECK_EQUAL(2, group_1.size());
2✔
979
}
2✔
980

981

982
TEST(Sync_EarlyUnbind)
983
{
2✔
984
    // Verify that it is possible to unbind one session while another session
1✔
985
    // keeps the connection to the server open.
1✔
986

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

1✔
994
    // Session 1 is here only to keep the connection alive
1✔
995
    Session session_1 = fixture.make_bound_session(db_1, "/dummy");
2✔
996
    {
2✔
997
        Session session_2 = fixture.make_bound_session(db_2);
2✔
998
        write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
999
            wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1000
        });
2✔
1001
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1002
        // Session 2 is now connected, but will be abandoned at end of scope
1✔
1003
    }
2✔
1004
    {
2✔
1005
        // Starting a new session 3 forces closure of all previously abandoned
1✔
1006
        // sessions, in turn forcing session 2 to be enlisted for writing its
1✔
1007
        // UNBIND before session 3 is enlisted for writing BIND.
1✔
1008
        Session session_3 = fixture.make_bound_session(db_3);
2✔
1009
        // We now use MARK messages to wait for a complete unbind of session
1✔
1010
        // 2. The client is guaranteed to receive the UNBIND response for session
1✔
1011
        // 2 before it receives the MARK response for session 3.
1✔
1012
        session_3.wait_for_download_complete_or_client_stopped();
2✔
1013
    }
2✔
1014
}
2✔
1015

1016

1017
TEST(Sync_FastRebind)
1018
{
2✔
1019
    // Verify that it is possible to create multiple immediately consecutive
1✔
1020
    // sessions for the same Realm file.
1✔
1021

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

1✔
1028
    // Session 1 is here only to keep the connection alive
1✔
1029
    Session session_1 = fixture.make_bound_session(db_1, "/dummy");
2✔
1030
    {
2✔
1031
        Session session_2 = fixture.make_bound_session(db_2, "/test");
2✔
1032
        WriteTransaction wt(db_2);
2✔
1033
        TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1034
        table->add_column(type_Int, "i");
2✔
1035
        table->create_object_with_primary_key(1);
2✔
1036
        version_type new_version = wt.commit();
2✔
1037
        session_2.nonsync_transact_notify(new_version);
2✔
1038
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1039
    }
2✔
1040
    for (int i = 0; i < 100; ++i) {
202✔
1041
        Session session_2 = fixture.make_bound_session(db_2, "/test");
200✔
1042
        WriteTransaction wt(db_2);
200✔
1043
        TableRef table = wt.get_table("class_foo");
200✔
1044
        table->begin()->set<int64_t>("i", i);
200✔
1045
        version_type new_version = wt.commit();
200✔
1046
        session_2.nonsync_transact_notify(new_version);
200✔
1047
        session_2.wait_for_upload_complete_or_client_stopped();
200✔
1048
    }
200✔
1049
}
2✔
1050

1051

1052
TEST(Sync_UnbindBeforeActivation)
1053
{
2✔
1054
    // This test tries to make it likely that the server receives an UNBIND
1✔
1055
    // message for a session that is still not activated, i.e., before the
1✔
1056
    // server receives the IDENT message.
1✔
1057

1✔
1058
    TEST_DIR(dir);
2✔
1059
    TEST_CLIENT_DB(db_1);
2✔
1060
    TEST_CLIENT_DB(db_2);
2✔
1061
    ClientServerFixture fixture(dir, test_context);
2✔
1062
    fixture.start();
2✔
1063

1✔
1064
    // Session 1 is here only to keep the connection alive
1✔
1065
    Session session_1 = fixture.make_bound_session(db_1);
2✔
1066
    for (int i = 0; i < 1000; ++i) {
2,002✔
1067
        Session session_2 = fixture.make_bound_session(db_2);
2,000✔
1068
        session_2.wait_for_upload_complete_or_client_stopped();
2,000✔
1069
    }
2,000✔
1070
}
2✔
1071

1072

1073
TEST(Sync_AbandonUnboundSessions)
1074
{
2✔
1075
    TEST_DIR(dir);
2✔
1076
    TEST_CLIENT_DB(db_1);
2✔
1077
    TEST_CLIENT_DB(db_2);
2✔
1078
    TEST_CLIENT_DB(db_3);
2✔
1079
    ClientServerFixture fixture(dir, test_context);
2✔
1080
    fixture.start();
2✔
1081

1✔
1082
    int n = 32;
2✔
1083
    for (int i = 0; i < n; ++i) {
66✔
1084
        fixture.make_session(db_1, "/test");
64✔
1085
        fixture.make_session(db_2, "/test");
64✔
1086
        fixture.make_session(db_3, "/test");
64✔
1087
    }
64✔
1088

1✔
1089
    for (int i = 0; i < n; ++i) {
66✔
1090
        fixture.make_session(db_1, "/test");
64✔
1091
        Session session = fixture.make_session(db_2, "/test");
64✔
1092
        fixture.make_session(db_3, "/test");
64✔
1093
        session.bind();
64✔
1094
    }
64✔
1095

1✔
1096
    for (int i = 0; i < n; ++i) {
66✔
1097
        fixture.make_session(db_1, "/test");
64✔
1098
        Session session = fixture.make_session(db_2, "/test");
64✔
1099
        fixture.make_session(db_3, "/test");
64✔
1100
        session.bind();
64✔
1101
        session.wait_for_upload_complete_or_client_stopped();
64✔
1102
    }
64✔
1103

1✔
1104
    for (int i = 0; i < n; ++i) {
66✔
1105
        fixture.make_session(db_1, "/test");
64✔
1106
        Session session = fixture.make_session(db_2, "/test");
64✔
1107
        fixture.make_session(db_3, "/test");
64✔
1108
        session.bind();
64✔
1109
        session.wait_for_download_complete_or_client_stopped();
64✔
1110
    }
64✔
1111
}
2✔
1112

1113

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

1116
// This test illustrates that our instruction set and merge rules
1117
// do not have higher order convergence. The final merge result depends
1118
// on the order with which the changesets reach the server. This example
1119
// employs three clients operating on the same state. The state consists
1120
// of two tables, "source" and "target". "source" has a link list pointing
1121
// to target. Target contains three rows 0, 1, and 2. Source contains one
1122
// row with a link list whose value is 2.
1123
//
1124
// The three clients produce changesets with client 1 having the earliest time
1125
// stamp, client 2 the middle time stamp, and client 3 the latest time stamp.
1126
// The clients produce the following changesets.
1127
//
1128
// client 1: target.move_last_over(0)
1129
// client 2: source.link_list.set(0, 0);
1130
// client 3: source.link_list.set(0, 1);
1131
//
1132
// In part a of the test, the order of the clients reaching the server is
1133
// 1, 2, 3. The result is an empty link list since the merge of client 1 and 2
1134
// produces a nullify link list instruction.
1135
//
1136
// In part b, the order of the clients reaching the server is 3, 1, 2. The
1137
// result is a link list of size 1, since client 3 wins due to having the
1138
// latest time stamp.
1139
//
1140
// If the "natural" peer to peer system of these merge rules were built, the
1141
// transition from server a to server b involves an insert link instruction. In
1142
// other words, the diff between two servers differing in the order of one
1143
// move_last_over and two link_list_set instructions is an insert instruction.
1144
// Repeated application of the pairwise merge rules would never produce this
1145
// result.
1146
//
1147
// The test is not run in general since it just checks that we do not have
1148
// higher order convergence, and the absence of higher order convergence is not
1149
// a desired feature in itself.
1150
TEST_IF(Sync_NonDeterministicMerge, false)
1151
{
1152
    TEST_DIR(dir);
1153
    TEST_CLIENT_DB(db_a1);
1154
    TEST_CLIENT_DB(db_a2);
1155
    TEST_CLIENT_DB(db_a3);
1156
    TEST_CLIENT_DB(db_b1);
1157
    TEST_CLIENT_DB(db_b2);
1158
    TEST_CLIENT_DB(db_b3);
1159

1160
    ClientServerFixture fixture{dir, test_context};
1161
    fixture.start();
1162

1163
    // Part a of the test.
1164
    {
1165
        WriteTransaction wt{db_a1};
1166

1167
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target");
1168
        ColKey col_ndx = table_target->add_column(type_Int, "value");
1169
        CHECK_EQUAL(col_ndx, 1);
1170
        Obj row0 = table_target->create_object_with_primary_key(i);
1171
        Obj row1 = table_target->create_object_with_primary_key(i);
1172
        Obj row2 = table_target->create_object_with_primary_key(i);
1173
        row0.set(col_ndx, 123);
1174
        row1.set(col_ndx, 456);
1175
        row2.set(col_ndx, 789);
1176

1177
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source");
1178
        col_ndx = table_source->add_column_link(type_LinkList, "target_link",
1179
                                                *table_target);
1180
        CHECK_EQUAL(col_ndx, 1);
1181
        Obj obj = table_source->create_object_with_primary_key(i);
1182
        auto ll = obj.get_linklist(col_ndx);
1183
        ll.insert(0, row2.get_key());
1184
        CHECK_EQUAL(ll.size(), 1);
1185
        wt.commit();
1186
    }
1187

1188
    {
1189
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1190
        session.wait_for_upload_complete_or_client_stopped();
1191
    }
1192

1193
    {
1194
        Session session = fixture.make_bound_session(db_a2, "/server-path-a");
1195
        session.wait_for_download_complete_or_client_stopped();
1196
    }
1197

1198
    {
1199
        Session session = fixture.make_bound_session(db_a3, "/server-path-a");
1200
        session.wait_for_download_complete_or_client_stopped();
1201
    }
1202

1203
    {
1204
        WriteTransaction wt{db_a1};
1205
        TableRef table = wt.get_table("class_target");
1206
        table->remove_object(table->begin());
1207
        CHECK_EQUAL(table->size(), 2);
1208
        wt.commit();
1209
    }
1210

1211
    {
1212
        WriteTransaction wt{db_a2};
1213
        TableRef table = wt.get_table("class_source");
1214
        auto ll = table->get_linklist(1, 0);
1215
        CHECK_EQUAL(ll->size(), 1);
1216
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1217
        ll->set(0, 0);
1218
        CHECK_EQUAL(ll->get(0).get_int(1), 123);
1219
        wt.commit();
1220
    }
1221

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

1233
    {
1234
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1235
        session.wait_for_upload_complete_or_client_stopped();
1236
    }
1237

1238
    {
1239
        Session session = fixture.make_bound_session(db_a2, "/server-path-a");
1240
        session.wait_for_upload_complete_or_client_stopped();
1241
    }
1242

1243
    {
1244
        Session session = fixture.make_bound_session(db_a3, "/server-path-a");
1245
        session.wait_for_upload_complete_or_client_stopped();
1246
    }
1247

1248
    {
1249
        Session session = fixture.make_bound_session(db_a1, "/server-path-a");
1250
        session.wait_for_download_complete_or_client_stopped();
1251
    }
1252

1253
    // Part b of the test.
1254
    {
1255
        WriteTransaction wt{db_b1};
1256

1257
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target");
1258
        ColKey col_ndx = table_target->add_column(type_Int, "value");
1259
        CHECK_EQUAL(col_ndx, 1);
1260
        table_target->create_object_with_primary_key(i);
1261
        table_target->create_object_with_primary_key(i);
1262
        table_target->create_object_with_primary_key(i);
1263
        table_target->begin()->set(col_ndx, 123);
1264
        table_target->get_object(1).set(col_ndx, 456);
1265
        table_target->get_object(2).set(col_ndx, 789);
1266

1267
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source");
1268
        col_ndx = table_source->add_column_link(type_LinkList, "target_link",
1269
                                                *table_target);
1270
        CHECK_EQUAL(col_ndx, 1);
1271
        table_source->create_object_with_primary_key(i);
1272
        auto ll = table_source->get_linklist(col_ndx, 0);
1273
        ll->insert(0, 2);
1274
        CHECK_EQUAL(ll->size(), 1);
1275
        wt.commit();
1276
    }
1277

1278
    {
1279
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1280
        session.wait_for_upload_complete_or_client_stopped();
1281
    }
1282

1283
    {
1284
        Session session = fixture.make_bound_session(db_b2, "/server-path-b");
1285
        session.wait_for_download_complete_or_client_stopped();
1286
    }
1287

1288
    {
1289
        Session session = fixture.make_bound_session(db_b3, "/server-path-b");
1290
        session.wait_for_download_complete_or_client_stopped();
1291
    }
1292

1293
    {
1294
        WriteTransaction wt{db_b1};
1295
        TableRef table = wt.get_table("class_target");
1296
        table->move_last_over(0);
1297
        CHECK_EQUAL(table->size(), 2);
1298
        wt.commit();
1299
    }
1300

1301
    {
1302
        WriteTransaction wt{db_b2};
1303
        TableRef table = wt.get_table("class_source");
1304
        auto ll = table->get_linklist(1, 0);
1305
        CHECK_EQUAL(ll->size(), 1);
1306
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1307
        ll->set(0, 0);
1308
        CHECK_EQUAL(ll->get(0).get_int(1), 123);
1309
        wt.commit();
1310
    }
1311

1312
    {
1313
        WriteTransaction wt{db_b3};
1314
        TableRef table = wt.get_table("class_source");
1315
        auto ll = table->get_linklist(1, 0);
1316
        CHECK_EQUAL(ll->size(), 1);
1317
        CHECK_EQUAL(ll->get(0).get_int(1), 789);
1318
        ll->set(0, 1);
1319
        CHECK_EQUAL(ll->get(0).get_int(1), 456);
1320
        wt.commit();
1321
    }
1322

1323
    // The crucial difference between part a and b is that client 3
1324
    // uploads it changes first in part b and last in part a.
1325
    {
1326
        Session session = fixture.make_bound_session(db_b3, "/server-path-b");
1327
        session.wait_for_upload_complete_or_client_stopped();
1328
    }
1329

1330
    {
1331
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1332
        session.wait_for_upload_complete_or_client_stopped();
1333
    }
1334

1335
    {
1336
        Session session = fixture.make_bound_session(db_b2, "/server-path-b");
1337
        session.wait_for_upload_complete_or_client_stopped();
1338
    }
1339

1340
    {
1341
        Session session = fixture.make_bound_session(db_b1, "/server-path-b");
1342
        session.wait_for_download_complete_or_client_stopped();
1343
    }
1344

1345

1346
    // Check the end result.
1347

1348
    size_t size_link_list_a;
1349
    size_t size_link_list_b;
1350

1351
    {
1352
        ReadTransaction wt{db_a1};
1353
        ConstTableRef table = wt.get_table("class_source");
1354
        auto ll = table->get_linklist(1, 0);
1355
        size_link_list_a = ll->size();
1356
    }
1357

1358
    {
1359
        ReadTransaction wt{db_b1};
1360
        ConstTableRef table = wt.get_table("class_source");
1361
        auto ll = table->get_linklist(1, 0);
1362
        size_link_list_b = ll->size();
1363
        CHECK_EQUAL(ll->size(), 1);
1364
    }
1365

1366
    // The final link list has size 0 in part a and size 1 in part b.
1367
    // These checks confirm that the OT system behaves as expected.
1368
    // The expected behavior is higher order divergence.
1369
    CHECK_EQUAL(size_link_list_a, 0);
1370
    CHECK_EQUAL(size_link_list_b, 1);
1371
    CHECK_NOT_EQUAL(size_link_list_a, size_link_list_b);
1372
}
1373
#endif // 0
1374

1375

1376
TEST(Sync_Randomized)
1377
{
2✔
1378
    constexpr size_t num_clients = 7;
2✔
1379

1✔
1380
    auto client_test_program = [](DBRef db, Session& session) {
14✔
1381
        // Create the schema
7✔
1382
        write_transaction_notifying_session(db, session, [](WriteTransaction& wt) {
14✔
1383
            if (wt.has_table("class_foo"))
14✔
1384
                return;
×
1385
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
14✔
1386
            table->add_column(type_Int, "i");
14✔
1387
            table->create_object_with_primary_key(1);
14✔
1388
        });
14✔
1389

7✔
1390
        Random random(random_int<unsigned long>()); // Seed from slow global generator
14✔
1391
        for (int i = 0; i < 100; ++i) {
1,414✔
1392
            WriteTransaction wt(db);
1,400✔
1393
            if (random.chance(4, 5)) {
1,400✔
1394
                TableRef table = wt.get_table("class_foo");
1,132✔
1395
                if (random.chance(1, 5)) {
1,132✔
1396
                    table->create_object_with_primary_key(i);
217✔
1397
                }
217✔
1398
                int value = random.draw_int(-32767, 32767);
1,132✔
1399
                size_t row_ndx = random.draw_int_mod(table->size());
1,132✔
1400
                table->get_object(row_ndx).set("i", value);
1,132✔
1401
            }
1,132✔
1402
            version_type new_version = wt.commit();
1,400✔
1403
            session.nonsync_transact_notify(new_version);
1,400✔
1404
        }
1,400✔
1405
    };
14✔
1406

1✔
1407
    TEST_DIR(dir);
2✔
1408
    MultiClientServerFixture fixture(num_clients, 1, dir, test_context);
2✔
1409
    fixture.start();
2✔
1410

1✔
1411
    std::unique_ptr<DBTestPathGuard> client_path_guards[num_clients];
2✔
1412
    DBRef client_shared_groups[num_clients];
2✔
1413
    for (size_t i = 0; i < num_clients; ++i) {
16✔
1414
        std::string suffix = util::format(".client_%1.realm", i);
14✔
1415
        std::string test_path = get_test_path(test_context.get_test_name(), suffix);
14✔
1416
        client_path_guards[i].reset(new DBTestPathGuard(test_path));
14✔
1417
        client_shared_groups[i] = DB::create(make_client_replication(), test_path);
14✔
1418
    }
14✔
1419

1✔
1420
    std::vector<std::unique_ptr<Session>> sessions(num_clients);
2✔
1421
    for (size_t i = 0; i < num_clients; ++i) {
16✔
1422
        auto db = client_shared_groups[i];
14✔
1423
        sessions[i] = std::make_unique<Session>(fixture.make_session(int(i), 0, db, "/test"));
14✔
1424
        sessions[i]->bind();
14✔
1425
    }
14✔
1426

1✔
1427
    auto run_client_test_program = [&](size_t i) {
14✔
1428
        try {
14✔
1429
            client_test_program(client_shared_groups[i], *sessions[i]);
14✔
1430
        }
14✔
1431
        catch (...) {
7✔
1432
            fixture.stop();
×
1433
            throw;
×
1434
        }
×
1435
    };
14✔
1436

1✔
1437
    ThreadWrapper client_program_threads[num_clients];
2✔
1438
    for (size_t i = 0; i < num_clients; ++i)
16✔
1439
        client_program_threads[i].start([=] {
14✔
1440
            run_client_test_program(i);
14✔
1441
        });
14✔
1442

1✔
1443
    for (size_t i = 0; i < num_clients; ++i)
16✔
1444
        CHECK(!client_program_threads[i].join());
14✔
1445

1✔
1446
    log("All client programs completed");
2✔
1447

1✔
1448
    // Wait until all local changes are uploaded, and acknowledged by the
1✔
1449
    // server.
1✔
1450
    for (size_t i = 0; i < num_clients; ++i)
16✔
1451
        sessions[i]->wait_for_upload_complete_or_client_stopped();
14✔
1452

1✔
1453
    log("Everything uploaded");
2✔
1454

1✔
1455
    // Now wait for all previously uploaded changes to be downloaded by all
1✔
1456
    // others.
1✔
1457
    for (size_t i = 0; i < num_clients; ++i)
16✔
1458
        sessions[i]->wait_for_download_complete_or_client_stopped();
14✔
1459

1✔
1460
    log("Everything downloaded");
2✔
1461

1✔
1462
    REALM_ASSERT(num_clients > 0);
2✔
1463
    ReadTransaction rt_0(client_shared_groups[0]);
2✔
1464
    rt_0.get_group().verify();
2✔
1465
    for (size_t i = 1; i < num_clients; ++i) {
14✔
1466
        ReadTransaction rt(client_shared_groups[i]);
12✔
1467
        rt.get_group().verify();
12✔
1468
        // Logger is guaranteed to be defined
6✔
1469
        CHECK(compare_groups(rt_0, rt, *test_context.logger));
12✔
1470
    }
12✔
1471
}
2✔
1472

1473
#ifdef REALM_DEBUG // Failure simulation only works in debug mode
1474

1475
TEST(Sync_ReadFailureSimulation)
1476
{
2✔
1477
    TEST_DIR(server_dir);
2✔
1478
    TEST_CLIENT_DB(db);
2✔
1479

1✔
1480
    // Check that read failure simulation works on the client-side
1✔
1481
    {
2✔
1482
        bool client_side_read_did_fail = false;
2✔
1483
        {
2✔
1484
            ClientServerFixture fixture(server_dir, test_context);
2✔
1485
            fixture.set_client_side_error_rate(1, 1); // 100% chance of failure
2✔
1486
            auto error_handler = [&](Status status, bool is_fatal) {
2✔
1487
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
2✔
1488
                CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read");
2✔
1489
                CHECK_NOT(is_fatal);
2✔
1490
                client_side_read_did_fail = true;
2✔
1491
                fixture.stop();
2✔
1492
            };
2✔
1493
            fixture.set_client_side_error_handler(error_handler);
2✔
1494
            Session session = fixture.make_bound_session(db, "/test");
2✔
1495
            fixture.start();
2✔
1496
            session.wait_for_download_complete_or_client_stopped();
2✔
1497
        }
2✔
1498
        CHECK(client_side_read_did_fail);
2✔
1499
    }
2✔
1500

1✔
1501
    // FIXME: Figure out a way to check that read failure simulation works on
1✔
1502
    // the server-side
1✔
1503
}
2✔
1504

1505
#endif // REALM_DEBUG
1506
TEST(Sync_FailingReadsOnClientSide)
1507
{
2✔
1508
    TEST_CLIENT_DB(db_1);
2✔
1509
    TEST_CLIENT_DB(db_2);
2✔
1510

1✔
1511
    {
2✔
1512
        TEST_DIR(dir);
2✔
1513
        ClientServerFixture fixture{dir, test_context};
2✔
1514
        fixture.set_client_side_error_rate(5, 100); // 5% chance of failure
2✔
1515
        auto error_handler = [&](Status status, bool is_fatal) {
432✔
1516
            if (CHECK_EQUAL(status.reason(), "Simulated failure during sync client websocket read")) {
432✔
1517
                CHECK_EQUAL(status, ErrorCodes::RuntimeError);
432✔
1518
                CHECK_NOT(is_fatal);
432✔
1519
                fixture.cancel_reconnect_delay();
432✔
1520
            }
432✔
1521
        };
432✔
1522
        fixture.set_client_side_error_handler(error_handler);
2✔
1523
        fixture.start();
2✔
1524

1✔
1525
        Session session_1 = fixture.make_bound_session(db_1);
2✔
1526

1✔
1527
        Session session_2 = fixture.make_bound_session(db_2);
2✔
1528

1✔
1529
        write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
1530
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1531
            table->add_column(type_Int, "i");
2✔
1532
            table->create_object_with_primary_key(1);
2✔
1533
        });
2✔
1534
        write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
1535
            TableRef table = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
1536
            table->add_column(type_Int, "i");
2✔
1537
            table->create_object_with_primary_key(2);
2✔
1538
        });
2✔
1539
        for (int i = 0; i < 100; ++i) {
202✔
1540
            session_1.wait_for_upload_complete_or_client_stopped();
200✔
1541
            session_2.wait_for_upload_complete_or_client_stopped();
200✔
1542
            for (int i = 0; i < 10; ++i) {
2,200✔
1543
                write_transaction_notifying_session(db_1, session_1, [=](WriteTransaction& wt) {
2,000✔
1544
                    TableRef table = wt.get_table("class_foo");
2,000✔
1545
                    table->begin()->set("i", i);
2,000✔
1546
                });
2,000✔
1547
                write_transaction_notifying_session(db_2, session_2, [=](WriteTransaction& wt) {
2,000✔
1548
                    TableRef table = wt.get_table("class_bar");
2,000✔
1549
                    table->begin()->set("i", i);
2,000✔
1550
                });
2,000✔
1551
            }
2,000✔
1552
        }
200✔
1553
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1554
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1555
        session_1.wait_for_download_complete_or_client_stopped();
2✔
1556
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1557
    }
2✔
1558

1✔
1559
    ReadTransaction rt_1(db_1);
2✔
1560
    ReadTransaction rt_2(db_2);
2✔
1561
    const Group& group_1 = rt_1;
2✔
1562
    const Group& group_2 = rt_2;
2✔
1563
    group_1.verify();
2✔
1564
    group_2.verify();
2✔
1565
    CHECK(compare_groups(rt_1, rt_2));
2✔
1566
}
2✔
1567

1568

1569
TEST(Sync_FailingReadsOnServerSide)
1570
{
2✔
1571
    TEST_CLIENT_DB(db_1);
2✔
1572
    TEST_CLIENT_DB(db_2);
2✔
1573

1✔
1574
    {
2✔
1575
        TEST_DIR(dir);
2✔
1576
        ClientServerFixture fixture{dir, test_context};
2✔
1577
        fixture.set_server_side_error_rate(5, 100); // 5% chance of failure
2✔
1578
        auto error_handler = [&](Status, bool is_fatal) {
526✔
1579
            CHECK_NOT(is_fatal);
526✔
1580
            fixture.cancel_reconnect_delay();
526✔
1581
        };
526✔
1582
        fixture.set_client_side_error_handler(error_handler);
2✔
1583
        fixture.start();
2✔
1584

1✔
1585
        Session session_1 = fixture.make_bound_session(db_1);
2✔
1586

1✔
1587
        Session session_2 = fixture.make_bound_session(db_2);
2✔
1588

1✔
1589
        write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
1590
            TableRef table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
1591
            table->add_column(type_Int, "i");
2✔
1592
            table->create_object_with_primary_key(1);
2✔
1593
        });
2✔
1594
        write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
1595
            TableRef table = wt.get_group().add_table_with_primary_key("class_bar", type_Int, "id");
2✔
1596
            table->add_column(type_Int, "i");
2✔
1597
            table->create_object_with_primary_key(2);
2✔
1598
        });
2✔
1599
        for (int i = 0; i < 100; ++i) {
202✔
1600
            session_1.wait_for_upload_complete_or_client_stopped();
200✔
1601
            session_2.wait_for_upload_complete_or_client_stopped();
200✔
1602
            for (int i = 0; i < 10; ++i) {
2,200✔
1603
                write_transaction_notifying_session(db_1, session_1, [=](WriteTransaction& wt) {
2,000✔
1604
                    TableRef table = wt.get_table("class_foo");
2,000✔
1605
                    table->begin()->set("i", i);
2,000✔
1606
                });
2,000✔
1607
                write_transaction_notifying_session(db_2, session_2, [=](WriteTransaction& wt) {
2,000✔
1608
                    TableRef table = wt.get_table("class_bar");
2,000✔
1609
                    table->begin()->set("i", i);
2,000✔
1610
                });
2,000✔
1611
            }
2,000✔
1612
        }
200✔
1613
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1614
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
1615
        session_1.wait_for_download_complete_or_client_stopped();
2✔
1616
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1617
    }
2✔
1618

1✔
1619
    ReadTransaction rt_1(db_1);
2✔
1620
    ReadTransaction rt_2(db_2);
2✔
1621
    const Group& group_1 = rt_1;
2✔
1622
    const Group& group_2 = rt_2;
2✔
1623
    group_1.verify();
2✔
1624
    group_2.verify();
2✔
1625
    CHECK(compare_groups(rt_1, rt_2));
2✔
1626
}
2✔
1627

1628

1629
TEST(Sync_ErrorAfterServerRestore_BadClientFileIdent)
1630
{
2✔
1631
    TEST_DIR(server_dir);
2✔
1632
    TEST_CLIENT_DB(db);
2✔
1633

1✔
1634
    std::string server_path = "/test";
2✔
1635
    std::string server_realm_path;
2✔
1636

1✔
1637
    // Make a change and synchronize with server
1✔
1638
    {
2✔
1639
        ClientServerFixture fixture(server_dir, test_context);
2✔
1640
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1641
        Session session = fixture.make_bound_session(db, server_path);
2✔
1642
        WriteTransaction wt{db};
2✔
1643
        wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1644
        auto new_version = wt.commit();
2✔
1645
        session.nonsync_transact_notify(new_version);
2✔
1646
        fixture.start();
2✔
1647
        session.wait_for_upload_complete_or_client_stopped();
2✔
1648
    }
2✔
1649

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

1✔
1653
    // Provoke error by attempting to resynchronize
1✔
1654
    bool did_fail = false;
2✔
1655
    {
2✔
1656
        ClientServerFixture fixture(server_dir, test_context);
2✔
1657
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1658
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1659
            CHECK(is_fatal);
2✔
1660
            did_fail = true;
2✔
1661
            fixture.stop();
2✔
1662
        };
2✔
1663
        fixture.set_client_side_error_handler(error_handler);
2✔
1664
        Session session = fixture.make_bound_session(db, server_path);
2✔
1665
        fixture.start();
2✔
1666
        session.wait_for_download_complete_or_client_stopped();
2✔
1667
    }
2✔
1668
    CHECK(did_fail);
2✔
1669
}
2✔
1670

1671

1672
TEST(Sync_HTTP404NotFound)
1673
{
2✔
1674
    TEST_DIR(server_dir);
2✔
1675

1✔
1676
    std::string server_address = "localhost";
2✔
1677

1✔
1678
    Server::Config server_config;
2✔
1679
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
1680
    server_config.listen_address = server_address;
2✔
1681
    server_config.listen_port = "";
2✔
1682
    server_config.tcp_no_delay = true;
2✔
1683

1✔
1684
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
1685
    Server server(server_dir, std::move(public_key), server_config);
2✔
1686
    server.start();
2✔
1687
    network::Endpoint endpoint = server.listen_endpoint();
2✔
1688

1✔
1689
    ThreadWrapper server_thread;
2✔
1690
    server_thread.start([&] {
2✔
1691
        server.run();
2✔
1692
    });
2✔
1693

1✔
1694
    HTTPRequest request;
2✔
1695
    request.path = "/not-found";
2✔
1696

1✔
1697
    HTTPRequestClient client(test_context.logger, endpoint, request);
2✔
1698
    client.fetch_response();
2✔
1699

1✔
1700
    server.stop();
2✔
1701

1✔
1702
    server_thread.join();
2✔
1703

1✔
1704
    const HTTPResponse& response = client.get_response();
2✔
1705

1✔
1706
    CHECK(response.status == HTTPStatus::NotFound);
2✔
1707
    CHECK(response.headers.find("Server")->second == "RealmSync/" REALM_VERSION_STRING);
2✔
1708
}
2✔
1709

1710

1711
namespace {
1712

1713
class RequestWithContentLength {
1714
public:
1715
    RequestWithContentLength(test_util::unit_test::TestContext& test_context, network::Service& service,
1716
                             const network::Endpoint& endpoint, const std::string& content_length,
1717
                             const std::string& expected_response_line)
1718
        : test_context{test_context}
1719
        , m_socket{service}
1720
        , m_endpoint{endpoint}
1721
        , m_content_length{content_length}
1722
        , m_expected_response_line{expected_response_line}
1723
    {
8✔
1724
        m_request = "POST /does-not-exist-1234 HTTP/1.1\r\n"
8✔
1725
                    "Content-Length: " +
8✔
1726
                    m_content_length +
8✔
1727
                    "\r\n"
8✔
1728
                    "\r\n";
8✔
1729
    }
8✔
1730

1731
    void write_completion_handler(std::error_code ec, size_t nbytes)
1732
    {
8✔
1733
        CHECK_NOT(ec);
8✔
1734
        CHECK_EQUAL(m_request.size(), nbytes);
8✔
1735
        auto handler = [&](std::error_code ec, size_t nbytes) {
8✔
1736
            this->read_completion_handler(ec, nbytes);
8✔
1737
        };
8✔
1738
        m_socket.async_read_until(m_buffer, m_buf_size, '\n', m_read_ahead_buffer, handler);
8✔
1739
    }
8✔
1740

1741
    void read_completion_handler(std::error_code ec, size_t nbytes)
1742
    {
8✔
1743
        CHECK_NOT(ec);
8✔
1744
        std::string response_line{m_buffer, nbytes};
8✔
1745
        CHECK_EQUAL(response_line, m_expected_response_line);
8✔
1746
    }
8✔
1747

1748
    void start()
1749
    {
8✔
1750
        std::error_code ec;
8✔
1751
        m_socket.connect(m_endpoint, ec);
8✔
1752
        CHECK_NOT(ec);
8✔
1753

4✔
1754
        auto handler = [&](std::error_code ec, size_t nbytes) {
8✔
1755
            this->write_completion_handler(ec, nbytes);
8✔
1756
        };
8✔
1757
        m_socket.async_write(m_request.data(), m_request.size(), handler);
8✔
1758
    }
8✔
1759

1760
private:
1761
    test_util::unit_test::TestContext& test_context;
1762
    network::Socket m_socket;
1763
    network::ReadAheadBuffer m_read_ahead_buffer;
1764
    static constexpr size_t m_buf_size = 1000;
1765
    char m_buffer[m_buf_size];
1766
    const network::Endpoint& m_endpoint;
1767
    const std::string m_content_length;
1768
    std::string m_request;
1769
    const std::string m_expected_response_line;
1770
};
1771

1772
} // namespace
1773

1774
// Test the server's HTTP response to a Content-Length header of zero, empty,
1775
// and a non-number string.
1776
TEST(Sync_HTTP_ContentLength)
1777
{
2✔
1778
    TEST_DIR(server_dir);
2✔
1779

1✔
1780
    std::string server_address = "localhost";
2✔
1781

1✔
1782
    Server::Config server_config;
2✔
1783
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
1784
    server_config.listen_address = server_address;
2✔
1785
    server_config.listen_port = "";
2✔
1786
    server_config.tcp_no_delay = true;
2✔
1787

1✔
1788
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
1789
    Server server(server_dir, std::move(public_key), server_config);
2✔
1790
    server.start();
2✔
1791
    network::Endpoint endpoint = server.listen_endpoint();
2✔
1792

1✔
1793
    ThreadWrapper server_thread;
2✔
1794
    server_thread.start([&] {
2✔
1795
        server.run();
2✔
1796
    });
2✔
1797

1✔
1798
    network::Service service;
2✔
1799

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

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

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

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

1✔
1808
    req_0.start();
2✔
1809
    req_1.start();
2✔
1810
    req_2.start();
2✔
1811
    req_3.start();
2✔
1812

1✔
1813
    service.run();
2✔
1814

1✔
1815
    server.stop();
2✔
1816
    server_thread.join();
2✔
1817
}
2✔
1818

1819

1820
TEST(Sync_ErrorAfterServerRestore_BadServerVersion)
1821
{
2✔
1822
    TEST_DIR(server_dir);
2✔
1823
    TEST_DIR(backup_dir);
2✔
1824
    TEST_CLIENT_DB(db);
2✔
1825

1✔
1826
    std::string server_path = "/test";
2✔
1827
    std::string server_realm_path;
2✔
1828
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1829

1✔
1830
    // Create schema and synchronize with server
1✔
1831
    {
2✔
1832
        ClientServerFixture fixture(server_dir, test_context);
2✔
1833
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1834
        Session session = fixture.make_bound_session(db, server_path);
2✔
1835
        WriteTransaction wt{db};
2✔
1836
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1837
        table->add_column(type_Int, "column");
2✔
1838
        auto new_version = wt.commit();
2✔
1839
        session.nonsync_transact_notify(new_version);
2✔
1840
        fixture.start();
2✔
1841
        session.wait_for_upload_complete_or_client_stopped();
2✔
1842
    }
2✔
1843

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

1✔
1847
    // Make change in which will be lost when restoring snapshot
1✔
1848
    {
2✔
1849
        ClientServerFixture fixture(server_dir, test_context);
2✔
1850
        Session session = fixture.make_bound_session(db, server_path);
2✔
1851
        WriteTransaction wt{db};
2✔
1852
        TableRef table = wt.get_table("class_table");
2✔
1853
        table->create_object_with_primary_key(1);
2✔
1854
        auto new_version = wt.commit();
2✔
1855
        session.nonsync_transact_notify(new_version);
2✔
1856
        fixture.start();
2✔
1857
        session.wait_for_upload_complete_or_client_stopped();
2✔
1858
    }
2✔
1859

1✔
1860
    // Restore the snapshot
1✔
1861
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1862

1✔
1863
    // Provoke error by resynchronizing
1✔
1864
    bool did_fail = false;
2✔
1865
    {
2✔
1866
        ClientServerFixture fixture(server_dir, test_context);
2✔
1867
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1868
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1869
            CHECK(is_fatal);
2✔
1870
            did_fail = true;
2✔
1871
            fixture.stop();
2✔
1872
        };
2✔
1873
        fixture.set_client_side_error_handler(error_handler);
2✔
1874
        Session session = fixture.make_bound_session(db, server_path);
2✔
1875
        fixture.start();
2✔
1876
        session.wait_for_download_complete_or_client_stopped();
2✔
1877
    }
2✔
1878
    CHECK(did_fail);
2✔
1879
}
2✔
1880

1881

1882
TEST(Sync_ErrorAfterServerRestore_BadClientVersion)
1883
{
2✔
1884
    TEST_DIR(server_dir);
2✔
1885
    TEST_DIR(backup_dir);
2✔
1886
    TEST_CLIENT_DB(db_1);
2✔
1887
    TEST_CLIENT_DB(db_2);
2✔
1888

1✔
1889
    std::string server_path = "/test";
2✔
1890
    std::string server_realm_path;
2✔
1891
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1892

1✔
1893
    // Create schema and synchronize client files
1✔
1894
    {
2✔
1895
        ClientServerFixture fixture(server_dir, test_context);
2✔
1896
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1897
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
1898
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
1899
        WriteTransaction wt{db_1};
2✔
1900
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
1901
        table->add_column(type_Int, "column");
2✔
1902
        auto new_version = wt.commit();
2✔
1903
        session_1.nonsync_transact_notify(new_version);
2✔
1904
        fixture.start();
2✔
1905
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
1906
        session_2.wait_for_download_complete_or_client_stopped();
2✔
1907
    }
2✔
1908

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

1✔
1912
    // Make change in 1st file which will be lost when restoring snapshot
1✔
1913
    {
2✔
1914
        ClientServerFixture fixture(server_dir, test_context);
2✔
1915
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1916
        WriteTransaction wt{db_1};
2✔
1917
        TableRef table = wt.get_table("class_table");
2✔
1918
        table->create_object_with_primary_key(1);
2✔
1919
        auto new_version = wt.commit();
2✔
1920
        session.nonsync_transact_notify(new_version);
2✔
1921
        fixture.start();
2✔
1922
        session.wait_for_upload_complete_or_client_stopped();
2✔
1923
    }
2✔
1924

1✔
1925
    // Restore the snapshot
1✔
1926
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1927

1✔
1928
    // Make a conflicting change in 2nd file relative to reverted server state
1✔
1929
    {
2✔
1930
        ClientServerFixture fixture(server_dir, test_context);
2✔
1931
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1932
        WriteTransaction wt{db_2};
2✔
1933
        TableRef table = wt.get_table("class_table");
2✔
1934
        table->create_object_with_primary_key(2);
2✔
1935
        auto new_version = wt.commit();
2✔
1936
        session.nonsync_transact_notify(new_version);
2✔
1937
        fixture.start();
2✔
1938
        session.wait_for_upload_complete_or_client_stopped();
2✔
1939
    }
2✔
1940

1✔
1941
    // Provoke error by synchronizing 1st file
1✔
1942
    bool did_fail = false;
2✔
1943
    {
2✔
1944
        ClientServerFixture fixture(server_dir, test_context);
2✔
1945
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
1946
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
1947
            CHECK(is_fatal);
2✔
1948
            did_fail = true;
2✔
1949
            fixture.stop();
2✔
1950
        };
2✔
1951
        fixture.set_client_side_error_handler(error_handler);
2✔
1952
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1953
        fixture.start();
2✔
1954
        session.wait_for_download_complete_or_client_stopped();
2✔
1955
    }
2✔
1956
    CHECK(did_fail);
2✔
1957
}
2✔
1958

1959

1960
TEST(Sync_ErrorAfterServerRestore_BadClientFileIdentSalt)
1961
{
2✔
1962
    TEST_DIR(server_dir);
2✔
1963
    TEST_DIR(backup_dir);
2✔
1964
    TEST_CLIENT_DB(db_1);
2✔
1965
    TEST_CLIENT_DB(db_2);
2✔
1966
    TEST_CLIENT_DB(db_3);
2✔
1967

1✔
1968
    std::string server_path = "/test";
2✔
1969
    std::string server_realm_path;
2✔
1970
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
1971

1✔
1972
    // Register 1st file with server
1✔
1973
    {
2✔
1974
        ClientServerFixture fixture(server_dir, test_context);
2✔
1975
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
1976
        Session session = fixture.make_bound_session(db_1, server_path);
2✔
1977
        WriteTransaction wt{db_1};
2✔
1978
        TableRef table = wt.get_group().add_table_with_primary_key("class_table_1", type_Int, "id");
2✔
1979
        table->add_column(type_Int, "column");
2✔
1980
        auto new_version = wt.commit();
2✔
1981
        session.nonsync_transact_notify(new_version);
2✔
1982
        fixture.start();
2✔
1983
        session.wait_for_upload_complete_or_client_stopped();
2✔
1984
    }
2✔
1985

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

1✔
1989
    // Register 2nd file with server
1✔
1990
    {
2✔
1991
        ClientServerFixture fixture(server_dir, test_context);
2✔
1992
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
1993
        fixture.start();
2✔
1994
        session.wait_for_download_complete_or_client_stopped();
2✔
1995
    }
2✔
1996

1✔
1997
    // Restore the snapshot
1✔
1998
    util::File::copy(backup_realm_path, server_realm_path);
2✔
1999

1✔
2000
    // Register 3rd conflicting file with server
1✔
2001
    {
2✔
2002
        ClientServerFixture fixture(server_dir, test_context);
2✔
2003
        Session session = fixture.make_bound_session(db_3, server_path);
2✔
2004
        fixture.start();
2✔
2005
        session.wait_for_download_complete_or_client_stopped();
2✔
2006
    }
2✔
2007

1✔
2008
    // Provoke error by resynchronizing 2nd file
1✔
2009
    bool did_fail = false;
2✔
2010
    {
2✔
2011
        ClientServerFixture fixture(server_dir, test_context);
2✔
2012
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
2013
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
2014
            CHECK(is_fatal);
2✔
2015
            did_fail = true;
2✔
2016
            fixture.stop();
2✔
2017
        };
2✔
2018
        fixture.set_client_side_error_handler(error_handler);
2✔
2019
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
2020
        fixture.start();
2✔
2021
        session.wait_for_download_complete_or_client_stopped();
2✔
2022
    }
2✔
2023
    CHECK(did_fail);
2✔
2024
}
2✔
2025

2026

2027
TEST(Sync_ErrorAfterServerRestore_BadServerVersionSalt)
2028
{
2✔
2029
    TEST_DIR(server_dir);
2✔
2030
    TEST_DIR(backup_dir);
2✔
2031
    TEST_CLIENT_DB(db_1);
2✔
2032
    TEST_CLIENT_DB(db_2);
2✔
2033
    TEST_CLIENT_DB(db_3);
2✔
2034

1✔
2035
    std::string server_path = "/test";
2✔
2036
    std::string server_realm_path;
2✔
2037
    std::string backup_realm_path = util::File::resolve("test.realm", backup_dir);
2✔
2038

1✔
2039
    // Create schema and synchronize client files
1✔
2040
    {
2✔
2041
        ClientServerFixture fixture(server_dir, test_context);
2✔
2042
        server_realm_path = fixture.map_virtual_to_real_path(server_path);
2✔
2043
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
2044
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
2045
        Session session_3 = fixture.make_bound_session(db_3, server_path);
2✔
2046
        WriteTransaction wt{db_1};
2✔
2047
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2048
        table->add_column(type_Int, "column");
2✔
2049
        auto new_version = wt.commit();
2✔
2050
        session_1.nonsync_transact_notify(new_version);
2✔
2051
        fixture.start();
2✔
2052
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
2053
        session_2.wait_for_download_complete_or_client_stopped();
2✔
2054
        session_3.wait_for_download_complete_or_client_stopped();
2✔
2055
    }
2✔
2056

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

1✔
2060
    // Make change in 1st file which will be lost when restoring snapshot, and
1✔
2061
    // make 2nd file download it.
1✔
2062
    {
2✔
2063
        ClientServerFixture fixture(server_dir, test_context);
2✔
2064
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
2065
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
2066
        WriteTransaction wt{db_1};
2✔
2067
        TableRef table = wt.get_table("class_table");
2✔
2068
        table->create_object_with_primary_key(1);
2✔
2069
        auto new_version = wt.commit();
2✔
2070
        session_1.nonsync_transact_notify(new_version);
2✔
2071
        fixture.start();
2✔
2072
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
2073
        session_2.wait_for_download_complete_or_client_stopped();
2✔
2074
    }
2✔
2075

1✔
2076
    // Restore the snapshot
1✔
2077
    util::File::copy(backup_realm_path, server_realm_path);
2✔
2078

1✔
2079
    // Make a conflicting change in 3rd file relative to reverted server state
1✔
2080
    {
2✔
2081
        ClientServerFixture fixture(server_dir, test_context);
2✔
2082
        Session session = fixture.make_bound_session(db_3, server_path);
2✔
2083
        WriteTransaction wt{db_3};
2✔
2084
        TableRef table = wt.get_table("class_table");
2✔
2085
        table->create_object_with_primary_key(2);
2✔
2086
        auto new_version = wt.commit();
2✔
2087
        session.nonsync_transact_notify(new_version);
2✔
2088
        fixture.start();
2✔
2089
        session.wait_for_upload_complete_or_client_stopped();
2✔
2090
    }
2✔
2091

1✔
2092
    // Provoke error by synchronizing 2nd file
1✔
2093
    bool did_fail = false;
2✔
2094
    {
2✔
2095
        ClientServerFixture fixture(server_dir, test_context);
2✔
2096
        auto error_handler = [&](Status status, bool is_fatal) {
2✔
2097
            CHECK_EQUAL(status, ErrorCodes::SyncClientResetRequired);
2✔
2098
            CHECK(is_fatal);
2✔
2099
            did_fail = true;
2✔
2100
            fixture.stop();
2✔
2101
        };
2✔
2102
        fixture.set_client_side_error_handler(error_handler);
2✔
2103
        Session session = fixture.make_bound_session(db_2, server_path);
2✔
2104
        fixture.start();
2✔
2105
        session.wait_for_download_complete_or_client_stopped();
2✔
2106
    }
2✔
2107
    CHECK(did_fail);
2✔
2108
}
2✔
2109

2110

2111
TEST(Sync_MultipleServers)
2112
{
2✔
2113
    // Check that a client can make lots of connection to lots of servers in a
1✔
2114
    // concurrent manner.
1✔
2115

1✔
2116
    const int num_servers = 2;
2✔
2117
    const int num_realms_per_server = 2;
2✔
2118
    const int num_files_per_realm = 4;
2✔
2119
    const int num_sessions_per_file = 8;
2✔
2120
    const int num_transacts_per_session = 2;
2✔
2121

1✔
2122
    TEST_DIR(dir);
2✔
2123
    int num_clients = 1;
2✔
2124
    MultiClientServerFixture fixture(num_clients, num_servers, dir, test_context);
2✔
2125
    fixture.start();
2✔
2126

1✔
2127
    TEST_DIR(dir_2);
2✔
2128
    auto get_file_path = [&](int server_index, int realm_index, int file_index) {
95✔
2129
        std::ostringstream out;
95✔
2130
        out << server_index << "_" << realm_index << "_" << file_index << ".realm";
95✔
2131
        return util::File::resolve(out.str(), dir_2);
95✔
2132
    };
95✔
2133
    std::atomic<int> id = 0;
2✔
2134

1✔
2135
    auto run = [&](int server_index, int realm_index, int file_index) {
32✔
2136
        try {
32✔
2137
            std::string path = get_file_path(server_index, realm_index, file_index);
32✔
2138
            DBRef db = DB::create(make_client_replication(), path);
32✔
2139
            {
32✔
2140
                WriteTransaction wt(db);
32✔
2141
                TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
32✔
2142
                table->add_column(type_Int, "server_index");
32✔
2143
                table->add_column(type_Int, "realm_index");
32✔
2144
                table->add_column(type_Int, "file_index");
32✔
2145
                table->add_column(type_Int, "session_index");
32✔
2146
                table->add_column(type_Int, "transact_index");
32✔
2147
                wt.commit();
32✔
2148
            }
32✔
2149
            std::string server_path = "/" + std::to_string(realm_index);
32✔
2150
            for (int i = 0; i < num_sessions_per_file; ++i) {
288✔
2151
                int client_index = 0;
256✔
2152
                Session session = fixture.make_session(client_index, server_index, db, server_path);
256✔
2153
                session.bind();
256✔
2154
                for (int j = 0; j < num_transacts_per_session; ++j) {
768✔
2155
                    WriteTransaction wt(db);
512✔
2156
                    TableRef table = wt.get_table("class_table");
512✔
2157
                    Obj obj = table->create_object_with_primary_key(id.fetch_add(1));
512✔
2158
                    obj.set("server_index", server_index);
512✔
2159
                    obj.set("realm_index", realm_index);
512✔
2160
                    obj.set("file_index", file_index);
512✔
2161
                    obj.set("session_index", i);
512✔
2162
                    obj.set("transact_index", j);
512✔
2163
                    version_type new_version = wt.commit();
512✔
2164
                    session.nonsync_transact_notify(new_version);
512✔
2165
                }
512✔
2166
                session.wait_for_upload_complete_or_client_stopped();
256✔
2167
            }
256✔
2168
        }
32✔
2169
        catch (...) {
16✔
2170
            fixture.stop();
×
2171
            throw;
×
2172
        }
×
2173
    };
32✔
2174

1✔
2175
    auto finish_download = [&](int server_index, int realm_index, int file_index) {
32✔
2176
        try {
32✔
2177
            int client_index = 0;
32✔
2178
            std::string path = get_file_path(server_index, realm_index, file_index);
32✔
2179
            DBRef db = DB::create(make_client_replication(), path);
32✔
2180
            std::string server_path = "/" + std::to_string(realm_index);
32✔
2181
            Session session = fixture.make_session(client_index, server_index, db, server_path);
32✔
2182
            session.bind();
32✔
2183
            session.wait_for_download_complete_or_client_stopped();
32✔
2184
        }
32✔
2185
        catch (...) {
16✔
2186
            fixture.stop();
×
2187
            throw;
×
2188
        }
×
2189
    };
32✔
2190

1✔
2191
    // Make and upload changes
1✔
2192
    {
2✔
2193
        ThreadWrapper threads[num_servers][num_realms_per_server][num_files_per_realm];
2✔
2194
        for (int i = 0; i < num_servers; ++i) {
6✔
2195
            for (int j = 0; j < num_realms_per_server; ++j) {
12✔
2196
                for (int k = 0; k < num_files_per_realm; ++k)
40✔
2197
                    threads[i][j][k].start([=] {
32✔
2198
                        run(i, j, k);
32✔
2199
                    });
32✔
2200
            }
8✔
2201
        }
4✔
2202
        for (size_t i = 0; i < num_servers; ++i) {
6✔
2203
            for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2204
                for (size_t k = 0; k < num_files_per_realm; ++k)
40✔
2205
                    CHECK_NOT(threads[i][j][k].join());
32✔
2206
            }
8✔
2207
        }
4✔
2208
    }
2✔
2209

1✔
2210
    // Finish downloading
1✔
2211
    {
2✔
2212
        ThreadWrapper threads[num_servers][num_realms_per_server][num_files_per_realm];
2✔
2213
        for (int i = 0; i < num_servers; ++i) {
6✔
2214
            for (int j = 0; j < num_realms_per_server; ++j) {
12✔
2215
                for (int k = 0; k < num_files_per_realm; ++k)
40✔
2216
                    threads[i][j][k].start([=] {
32✔
2217
                        finish_download(i, j, k);
32✔
2218
                    });
32✔
2219
            }
8✔
2220
        }
4✔
2221
        for (size_t i = 0; i < num_servers; ++i) {
6✔
2222
            for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2223
                for (size_t k = 0; k < num_files_per_realm; ++k)
40✔
2224
                    CHECK_NOT(threads[i][j][k].join());
32✔
2225
            }
8✔
2226
        }
4✔
2227
    }
2✔
2228

1✔
2229
    // Check that all client side Realms have been correctly synchronized
1✔
2230
    std::set<std::tuple<int, int, int>> expected_rows;
2✔
2231
    for (int i = 0; i < num_files_per_realm; ++i) {
10✔
2232
        for (int j = 0; j < num_sessions_per_file; ++j) {
72✔
2233
            for (int k = 0; k < num_transacts_per_session; ++k)
192✔
2234
                expected_rows.emplace(i, j, k);
128✔
2235
        }
64✔
2236
    }
8✔
2237
    for (size_t i = 0; i < num_servers; ++i) {
6✔
2238
        for (size_t j = 0; j < num_realms_per_server; ++j) {
12✔
2239
            REALM_ASSERT(num_files_per_realm > 0);
8✔
2240
            int file_index_0 = 0;
8✔
2241
            std::string path_0 = get_file_path(int(i), int(j), file_index_0);
8✔
2242
            std::unique_ptr<Replication> history_0 = make_client_replication();
8✔
2243
            DBRef db_0 = DB::create(*history_0, path_0);
8✔
2244
            ReadTransaction rt_0(db_0);
8✔
2245
            {
8✔
2246
                ConstTableRef table = rt_0.get_table("class_table");
8✔
2247
                if (CHECK(table)) {
8✔
2248
                    std::set<std::tuple<int, int, int>> rows;
8✔
2249
                    for (const Obj& obj : *table) {
512✔
2250
                        int server_index = int(obj.get<int64_t>("server_index"));
512✔
2251
                        int realm_index = int(obj.get<int64_t>("realm_index"));
512✔
2252
                        int file_index = int(obj.get<int64_t>("file_index"));
512✔
2253
                        int session_index = int(obj.get<int64_t>("session_index"));
512✔
2254
                        int transact_index = int(obj.get<int64_t>("transact_index"));
512✔
2255
                        CHECK_EQUAL(i, server_index);
512✔
2256
                        CHECK_EQUAL(j, realm_index);
512✔
2257
                        rows.emplace(file_index, session_index, transact_index);
512✔
2258
                    }
512✔
2259
                    CHECK(rows == expected_rows);
8✔
2260
                }
8✔
2261
            }
8✔
2262
            for (int k = 1; k < num_files_per_realm; ++k) {
32✔
2263
                std::string path = get_file_path(int(i), int(j), k);
24✔
2264
                DBRef db = DB::create(make_client_replication(), path);
24✔
2265
                ReadTransaction rt(db);
24✔
2266
                CHECK(compare_groups(rt_0, rt));
24✔
2267
            }
24✔
2268
        }
8✔
2269
    }
4✔
2270
}
2✔
2271

2272

2273
TEST_IF(Sync_ReadOnlyClient, false)
2274
{
×
2275
    TEST_CLIENT_DB(db_1);
×
2276
    TEST_CLIENT_DB(db_2);
×
2277

2278
    TEST_DIR(server_dir);
×
2279
    MultiClientServerFixture fixture(2, 1, server_dir, test_context);
×
2280
    bool did_get_permission_denied = false;
×
2281
    fixture.set_client_side_error_handler(1, [&](Status status, bool) {
×
2282
        CHECK_EQUAL(status, ErrorCodes::SyncPermissionDenied);
×
2283
        did_get_permission_denied = true;
×
2284
        fixture.get_client(1).shutdown();
×
2285
    });
×
2286
    fixture.start();
×
2287

2288
    // Write some stuff from the client that can upload
2289
    {
×
2290
        Session session_1 = fixture.make_bound_session(0, db_1, 0, "/test");
×
2291
        WriteTransaction wt(db_1);
×
2292
        auto table = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
×
2293
        table->add_column(type_Int, "i");
×
2294
        table->create_object_with_primary_key(1);
×
2295
        table->begin()->set("i", 123);
×
2296
        session_1.nonsync_transact_notify(wt.commit());
×
2297
        session_1.wait_for_upload_complete_or_client_stopped();
×
2298
    }
×
2299

2300
    // Check that the stuff was received on the read-only client
2301
    {
×
2302
        Session session_2 = fixture.make_bound_session(1, db_2, 0, "/test", g_signed_test_user_token_readonly);
×
2303
        session_2.wait_for_download_complete_or_client_stopped();
×
2304
        {
×
2305
            ReadTransaction rt(db_2);
×
2306
            auto table = rt.get_table("class_foo");
×
2307
            CHECK_EQUAL(table->begin()->get<Int>("i"), 123);
×
2308
        }
×
2309
        // Try to upload something
2310
        {
×
2311
            WriteTransaction wt(db_2);
×
2312
            auto table = wt.get_table("class_foo");
×
2313
            table->begin()->set("i", 456);
×
2314
            session_2.nonsync_transact_notify(wt.commit());
×
2315
        }
×
2316
        session_2.wait_for_upload_complete_or_client_stopped();
×
2317
        CHECK(did_get_permission_denied);
×
2318
    }
×
2319

2320
    // Check that the original client was unchanged
2321
    {
×
2322
        Session session_1 = fixture.make_bound_session(0, db_1, 0, "/test");
×
2323
        session_1.wait_for_download_complete_or_client_stopped();
×
2324
        ReadTransaction rt(db_1);
×
2325
        auto table = rt.get_table("class_foo");
×
2326
        CHECK_EQUAL(table->begin()->get<Int>("i"), 123);
×
2327
    }
×
2328
}
×
2329

2330

2331
// This test is a performance study. A single client keeps creating
2332
// transactions that creates new objects and uploads them. The time to perform
2333
// upload completion is measured and logged at info level.
2334
TEST(Sync_SingleClientUploadForever_CreateObjects)
2335
{
2✔
2336
    int_fast32_t number_of_transactions = 100; // Set to low number in ordinary testing.
2✔
2337

1✔
2338
    util::Logger& logger = *(test_context.logger);
2✔
2339

1✔
2340
    logger.info("Sync_SingleClientUploadForever_CreateObjects test. Number of transactions = %1",
2✔
2341
                number_of_transactions);
2✔
2342

1✔
2343
    TEST_DIR(server_dir);
2✔
2344
    TEST_CLIENT_DB(db);
2✔
2345

1✔
2346
    ClientServerFixture fixture(server_dir, test_context);
2✔
2347
    fixture.start();
2✔
2348

1✔
2349
    ColKey col_int;
2✔
2350
    ColKey col_str;
2✔
2351
    ColKey col_dbl;
2✔
2352
    ColKey col_time;
2✔
2353

1✔
2354
    {
2✔
2355
        WriteTransaction wt{db};
2✔
2356
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2357
        col_int = tr->add_column(type_Int, "integer column");
2✔
2358
        col_str = tr->add_column(type_String, "string column");
2✔
2359
        col_dbl = tr->add_column(type_Double, "double column");
2✔
2360
        col_time = tr->add_column(type_Timestamp, "timestamp column");
2✔
2361
        wt.commit();
2✔
2362
    }
2✔
2363

1✔
2364
    Session session = fixture.make_bound_session(db);
2✔
2365
    session.wait_for_upload_complete_or_client_stopped();
2✔
2366

1✔
2367
    for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
202✔
2368
        WriteTransaction wt{db};
200✔
2369
        TableRef tr = wt.get_table("class_table");
200✔
2370
        auto obj = tr->create_object_with_primary_key(i);
200✔
2371
        int_fast32_t number = i;
200✔
2372
        obj.set<Int>(col_int, number);
200✔
2373
        std::string str = "str: " + std::to_string(number);
200✔
2374
        StringData str_data = StringData(str);
200✔
2375
        obj.set(col_str, str_data);
200✔
2376
        obj.set(col_dbl, double(number));
200✔
2377
        obj.set(col_time, Timestamp{123, 456});
200✔
2378
        version_type version = wt.commit();
200✔
2379
        auto before_upload = std::chrono::steady_clock::now();
200✔
2380
        session.nonsync_transact_notify(version);
200✔
2381
        session.wait_for_upload_complete_or_client_stopped();
200✔
2382
        auto after_upload = std::chrono::steady_clock::now();
200✔
2383

100✔
2384
        // We only log the duration every 1000 transactions. The duration is for a single changeset.
100✔
2385
        if (i % 1000 == 0) {
200✔
2386
            auto duration =
2✔
2387
                std::chrono::duration_cast<std::chrono::milliseconds>(after_upload - before_upload).count();
2✔
2388
            logger.info("Duration of single changeset upload(%1) = %2 ms", i, duration);
2✔
2389
        }
2✔
2390
    }
200✔
2391
}
2✔
2392

2393

2394
// This test is a performance study. A single client keeps creating
2395
// transactions that changes the value of an existing object and uploads them.
2396
// The time to perform upload completion is measured and logged at info level.
2397
TEST(Sync_SingleClientUploadForever_MutateObject)
2398
{
2✔
2399
    int_fast32_t number_of_transactions = 100; // Set to low number in ordinary testing.
2✔
2400

1✔
2401
    util::Logger& logger = *(test_context.logger);
2✔
2402

1✔
2403
    logger.info("Sync_SingleClientUploadForever_MutateObject test. Number of transactions = %1",
2✔
2404
                number_of_transactions);
2✔
2405

1✔
2406
    TEST_DIR(server_dir);
2✔
2407
    TEST_CLIENT_DB(db);
2✔
2408

1✔
2409
    ClientServerFixture fixture(server_dir, test_context);
2✔
2410
    fixture.start();
2✔
2411

1✔
2412
    ColKey col_int;
2✔
2413
    ColKey col_str;
2✔
2414
    ColKey col_dbl;
2✔
2415
    ColKey col_time;
2✔
2416
    ObjKey obj_key;
2✔
2417

1✔
2418
    {
2✔
2419
        WriteTransaction wt{db};
2✔
2420
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2421
        col_int = tr->add_column(type_Int, "integer column");
2✔
2422
        col_str = tr->add_column(type_String, "string column");
2✔
2423
        col_dbl = tr->add_column(type_Double, "double column");
2✔
2424
        col_time = tr->add_column(type_Timestamp, "timestamp column");
2✔
2425
        obj_key = tr->create_object_with_primary_key(1).get_key();
2✔
2426
        wt.commit();
2✔
2427
    }
2✔
2428

1✔
2429
    Session session = fixture.make_bound_session(db);
2✔
2430
    session.wait_for_upload_complete_or_client_stopped();
2✔
2431

1✔
2432
    for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
202✔
2433
        WriteTransaction wt{db};
200✔
2434
        TableRef tr = wt.get_table("class_table");
200✔
2435
        int_fast32_t number = i;
200✔
2436
        auto obj = tr->get_object(obj_key);
200✔
2437
        obj.set<Int>(col_int, number);
200✔
2438
        std::string str = "str: " + std::to_string(number);
200✔
2439
        StringData str_data = StringData(str);
200✔
2440
        obj.set(col_str, str_data);
200✔
2441
        obj.set(col_dbl, double(number));
200✔
2442
        obj.set(col_time, Timestamp{123, 456});
200✔
2443
        version_type version = wt.commit();
200✔
2444
        auto before_upload = std::chrono::steady_clock::now();
200✔
2445
        session.nonsync_transact_notify(version);
200✔
2446
        session.wait_for_upload_complete_or_client_stopped();
200✔
2447
        auto after_upload = std::chrono::steady_clock::now();
200✔
2448

100✔
2449
        // We only log the duration every 1000 transactions. The duration is for a single changeset.
100✔
2450
        if (i % 1000 == 0) {
200✔
2451
            auto duration =
2✔
2452
                std::chrono::duration_cast<std::chrono::milliseconds>(after_upload - before_upload).count();
2✔
2453
            logger.info("Duration of single changeset upload(%1) = %2 ms", i, duration);
2✔
2454
        }
2✔
2455
    }
200✔
2456
}
2✔
2457

2458

2459
// This test is used to time upload and download.
2460
// The test might be moved to a performance test directory later.
2461
TEST(Sync_LargeUploadDownloadPerformance)
2462
{
2✔
2463
    int_fast32_t number_of_transactions = 2;         // Set to low number in ordinary testing.
2✔
2464
    int_fast32_t number_of_rows_per_transaction = 5; // Set to low number in ordinary testing.
2✔
2465
    int number_of_download_clients = 1;              // Set to low number in ordinary testing
2✔
2466
    bool print_durations = false;                    // Set to false in ordinary testing.
2✔
2467

1✔
2468
    if (print_durations) {
2✔
2469
        std::cerr << "Number of transactions = " << number_of_transactions << std::endl;
×
2470
        std::cerr << "Number of rows per transaction = " << number_of_rows_per_transaction << std::endl;
×
2471
        std::cerr << "Number of download clients = " << number_of_download_clients << std::endl;
×
2472
    }
×
2473

1✔
2474
    TEST_DIR(server_dir);
2✔
2475
    ClientServerFixture fixture(server_dir, test_context);
2✔
2476
    fixture.start();
2✔
2477

1✔
2478
    TEST_CLIENT_DB(db_upload);
2✔
2479

1✔
2480
    // Populate path_upload realm with data.
1✔
2481
    auto start_data_creation = std::chrono::steady_clock::now();
2✔
2482
    {
2✔
2483
        {
2✔
2484
            WriteTransaction wt{db_upload};
2✔
2485
            TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
2486
            tr->add_column(type_Int, "integer column");
2✔
2487
            tr->add_column(type_String, "string column");
2✔
2488
            tr->add_column(type_Double, "double column");
2✔
2489
            tr->add_column(type_Timestamp, "timestamp column");
2✔
2490
            wt.commit();
2✔
2491
        }
2✔
2492

1✔
2493
        for (int_fast32_t i = 0; i < number_of_transactions; ++i) {
6✔
2494
            WriteTransaction wt{db_upload};
4✔
2495
            TableRef tr = wt.get_table("class_table");
4✔
2496
            for (int_fast32_t j = 0; j < number_of_rows_per_transaction; ++j) {
24✔
2497
                Obj obj = tr->create_object_with_primary_key(i);
20✔
2498
                int_fast32_t number = i * number_of_rows_per_transaction + j;
20✔
2499
                obj.set("integer column", number);
20✔
2500
                std::string str = "str: " + std::to_string(number);
20✔
2501
                StringData str_data = StringData(str);
20✔
2502
                obj.set("string column", str_data);
20✔
2503
                obj.set("double column", double(number));
20✔
2504
                obj.set("timestamp column", Timestamp{123, 456});
20✔
2505
            }
20✔
2506
            wt.commit();
4✔
2507
        }
4✔
2508
    }
2✔
2509
    auto end_data_creation = std::chrono::steady_clock::now();
2✔
2510
    auto duration_data_creation =
2✔
2511
        std::chrono::duration_cast<std::chrono::milliseconds>(end_data_creation - start_data_creation).count();
2✔
2512
    if (print_durations)
2✔
2513
        std::cerr << "Duration of data creation = " << duration_data_creation << " ms" << std::endl;
×
2514

1✔
2515
    // Upload the data.
1✔
2516
    auto start_session_upload = std::chrono::steady_clock::now();
2✔
2517

1✔
2518
    Session session_upload = fixture.make_bound_session(db_upload);
2✔
2519
    session_upload.wait_for_upload_complete_or_client_stopped();
2✔
2520

1✔
2521
    auto end_session_upload = std::chrono::steady_clock::now();
2✔
2522
    auto duration_upload =
2✔
2523
        std::chrono::duration_cast<std::chrono::milliseconds>(end_session_upload - start_session_upload).count();
2✔
2524
    if (print_durations)
2✔
2525
        std::cerr << "Duration of uploading = " << duration_upload << " ms" << std::endl;
×
2526

1✔
2527

1✔
2528
    // Download the data to the download realms.
1✔
2529
    auto start_sesion_download = std::chrono::steady_clock::now();
2✔
2530

1✔
2531
    std::vector<DBTestPathGuard> shared_group_test_path_guards;
2✔
2532
    std::vector<DBRef> dbs;
2✔
2533
    std::vector<Session> sessions;
2✔
2534

1✔
2535
    for (int i = 0; i < number_of_download_clients; ++i) {
4✔
2536
        std::string path = get_test_path(test_context.get_test_name(), std::to_string(i));
2✔
2537
        shared_group_test_path_guards.emplace_back(path);
2✔
2538
        dbs.push_back(DB::create(make_client_replication(), path));
2✔
2539
        sessions.push_back(fixture.make_bound_session(dbs.back()));
2✔
2540
    }
2✔
2541

1✔
2542
    // Wait for all Realms to finish. They might finish in another order than
1✔
2543
    // started, but calling download_complete on a client after it finished only
1✔
2544
    // adds a tiny amount of extra mark messages.
1✔
2545
    for (auto& session : sessions)
2✔
2546
        session.wait_for_download_complete_or_client_stopped();
2✔
2547

1✔
2548

1✔
2549
    auto end_session_download = std::chrono::steady_clock::now();
2✔
2550
    auto duration_download =
2✔
2551
        std::chrono::duration_cast<std::chrono::milliseconds>(end_session_download - start_sesion_download).count();
2✔
2552
    if (print_durations)
2✔
2553
        std::cerr << "Duration of downloading = " << duration_download << " ms" << std::endl;
×
2554

1✔
2555

1✔
2556
    // Check convergence.
1✔
2557
    for (int i = 0; i < number_of_download_clients; ++i) {
4✔
2558
        ReadTransaction rt_1(db_upload);
2✔
2559
        ReadTransaction rt_2(dbs[i]);
2✔
2560
        CHECK(compare_groups(rt_1, rt_2));
2✔
2561
    }
2✔
2562
}
2✔
2563

2564

2565
// This test creates a changeset that is larger than 4GB, uploads it and downloads it to another client.
2566
// The test checks that compression and other aspects of large changeset handling works.
2567
// The test is disabled since it requires a powerful machine to run.
2568
TEST_IF(Sync_4GB_Messages, false)
2569
{
×
2570
    // The changeset will be slightly larger.
2571
    const uint64_t approximate_changeset_size = uint64_t(1) << 32;
×
2572

2573
    TEST_DIR(dir);
×
2574
    TEST_CLIENT_DB(db_1);
×
2575
    TEST_CLIENT_DB(db_2);
×
2576
    ClientServerFixture fixture(dir, test_context);
×
2577
    fixture.start();
×
2578

2579
    Session session_1 = fixture.make_bound_session(db_1);
×
2580
    session_1.wait_for_download_complete_or_client_stopped();
×
2581

2582
    Session session_2 = fixture.make_bound_session(db_2);
×
2583
    session_2.wait_for_download_complete_or_client_stopped();
×
2584

2585
    const size_t single_object_data_size = size_t(1e7); // 10 MB which is below the 16 MB limit
×
2586
    const int num_objects = approximate_changeset_size / single_object_data_size + 1;
×
2587

2588
    const std::string str_a(single_object_data_size, 'a');
×
2589
    BinaryData bd_a(str_a.data(), single_object_data_size);
×
2590

2591
    const std::string str_b(single_object_data_size, 'b');
×
2592
    BinaryData bd_b(str_b.data(), single_object_data_size);
×
2593

2594
    const std::string str_c(single_object_data_size, 'c');
×
2595
    BinaryData bd_c(str_c.data(), single_object_data_size);
×
2596

2597
    {
×
2598
        WriteTransaction wt{db_1};
×
2599

2600
        TableRef tr = wt.get_group().add_table_with_primary_key("class_simple_data", type_Int, "id");
×
2601
        auto col_key = tr->add_column(type_Binary, "binary column");
×
2602
        for (int i = 0; i < num_objects; ++i) {
×
2603
            Obj obj = tr->create_object_with_primary_key(i);
×
2604
            switch (i % 3) {
×
2605
                case 0:
×
2606
                    obj.set(col_key, bd_a);
×
2607
                    break;
×
2608
                case 1:
×
2609
                    obj.set(col_key, bd_b);
×
2610
                    break;
×
2611
                default:
×
2612
                    obj.set(col_key, bd_c);
×
2613
            }
×
2614
        }
×
2615
        version_type new_version = wt.commit();
×
2616
        session_1.nonsync_transact_notify(new_version);
×
2617
    }
×
2618
    session_1.wait_for_upload_complete_or_client_stopped();
×
2619
    session_2.wait_for_download_complete_or_client_stopped();
×
2620

2621
    // Check convergence.
2622
    {
×
2623
        ReadTransaction rt_1(db_1);
×
2624
        ReadTransaction rt_2(db_2);
×
2625
        CHECK(compare_groups(rt_1, rt_2));
×
2626
    }
×
2627
}
×
2628

2629

2630
TEST(Sync_RefreshSignedUserToken)
2631
{
2✔
2632
    TEST_DIR(dir);
2✔
2633
    TEST_CLIENT_DB(db);
2✔
2634
    ClientServerFixture fixture(dir, test_context);
2✔
2635
    fixture.start();
2✔
2636

1✔
2637
    Session session = fixture.make_bound_session(db);
2✔
2638
    session.wait_for_download_complete_or_client_stopped();
2✔
2639
    session.refresh(g_signed_test_user_token);
2✔
2640
    session.wait_for_download_complete_or_client_stopped();
2✔
2641
}
2✔
2642

2643

2644
// This test refreshes the user token multiple times right after binding
2645
// the session. The test tries to achieve a situation where a session is
2646
// enlisted to send after sending BIND but before receiving ALLOC.
2647
// The token is refreshed multiple times to increase the probability that the
2648
// refresh took place after BIND. The check of the test is just the absence of
2649
// errors.
2650
TEST(Sync_RefreshRightAfterBind)
2651
{
2✔
2652
    TEST_DIR(dir);
2✔
2653
    TEST_CLIENT_DB(db);
2✔
2654
    ClientServerFixture fixture(dir, test_context);
2✔
2655
    fixture.start();
2✔
2656

1✔
2657
    Session session = fixture.make_bound_session(db);
2✔
2658
    for (int i = 0; i < 50; ++i) {
102✔
2659
        session.refresh(g_signed_test_user_token_readonly);
100✔
2660
        std::this_thread::sleep_for(std::chrono::milliseconds{1});
100✔
2661
    }
100✔
2662
    session.wait_for_download_complete_or_client_stopped();
2✔
2663
}
2✔
2664

2665

2666
TEST(Sync_Permissions)
2667
{
2✔
2668
    TEST_CLIENT_DB(db_valid);
2✔
2669

1✔
2670
    bool did_see_error_for_valid = false;
2✔
2671

1✔
2672
    TEST_DIR(server_dir);
2✔
2673

1✔
2674
    ClientServerFixture fixture{server_dir, test_context};
2✔
2675
    fixture.set_client_side_error_handler([&](Status status, bool) {
1✔
2676
        CHECK_EQUAL("", status.reason());
×
2677
        did_see_error_for_valid = true;
×
2678
    });
×
2679
    fixture.start();
2✔
2680

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

1✔
2683
    // Insert some dummy data
1✔
2684
    WriteTransaction wt_valid{db_valid};
2✔
2685
    wt_valid.get_group().add_table_with_primary_key("class_a", type_Int, "id");
2✔
2686
    session_valid.nonsync_transact_notify(wt_valid.commit());
2✔
2687
    session_valid.wait_for_upload_complete_or_client_stopped();
2✔
2688

1✔
2689
    CHECK_NOT(did_see_error_for_valid);
2✔
2690
}
2✔
2691

2692

2693
// This test checks that a client SSL connection to localhost succeeds when the
2694
// server presents a certificate issued to localhost signed by a CA whose
2695
// certificate the client loads.
2696
TEST(Sync_SSL_Certificate_1)
2697
{
2✔
2698
    TEST_DIR(server_dir);
2✔
2699
    TEST_CLIENT_DB(db);
2✔
2700
    std::string ca_dir = get_test_resource_path();
2✔
2701

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

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

1✔
2709
    Session::Config session_config;
2✔
2710
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2711
    session_config.verify_servers_ssl_certificate = true;
2✔
2712
    session_config.ssl_trust_certificate_path = ca_dir + "crt.pem";
2✔
2713
    session_config.signed_user_token = g_signed_test_user_token;
2✔
2714

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

1✔
2718
    fixture.start();
2✔
2719
    session.wait_for_download_complete_or_client_stopped();
2✔
2720
}
2✔
2721

2722

2723
// This test checks that a client SSL connection to localhost does not succeed
2724
// when the server presents a certificate issued to localhost signed by a CA whose
2725
// certificate does not match the certificate loaded by the client.
2726
TEST(Sync_SSL_Certificate_2)
2727
{
2✔
2728
    bool did_fail = false;
2✔
2729
    TEST_DIR(server_dir);
2✔
2730
    TEST_CLIENT_DB(db);
2✔
2731
    std::string ca_dir = get_test_resource_path();
2✔
2732

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

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

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

1✔
2745
    auto error_handler = [&](Status status, bool) {
2✔
2746
        CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed);
2✔
2747
        did_fail = true;
2✔
2748
        fixture.stop();
2✔
2749
    };
2✔
2750
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
2751

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

2758

2759
// This test checks that a client SSL connection to localhost succeeds
2760
// if verify_servers_ssl_certificate = false, even when
2761
// when the server presents a certificate issued to localhost signed by a CA whose
2762
// certificate does not match the certificate loaded by the client.
2763
// This test is identical to Sync_SSL_Certificate_2 except for
2764
// the value of verify_servers_ssl_certificate.
2765
TEST(Sync_SSL_Certificate_3)
2766
{
2✔
2767
    TEST_DIR(server_dir);
2✔
2768
    TEST_CLIENT_DB(db);
2✔
2769
    std::string ca_dir = get_test_resource_path();
2✔
2770

1✔
2771
    ClientServerFixture::Config config;
2✔
2772
    config.enable_server_ssl = true;
2✔
2773
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
2✔
2774
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
2✔
2775

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

1✔
2778
    Session::Config session_config;
2✔
2779
    session_config.protocol_envelope = ProtocolEnvelope::realms;
2✔
2780
    session_config.verify_servers_ssl_certificate = false;
2✔
2781
    session_config.ssl_trust_certificate_path = ca_dir + "dns-chain.crt.pem";
2✔
2782

1✔
2783
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
2✔
2784
    fixture.start();
2✔
2785
    session.wait_for_download_complete_or_client_stopped();
2✔
2786
}
2✔
2787

2788

2789
#if REALM_HAVE_SECURE_TRANSPORT
2790

2791
// This test checks that the client can also use a certificate in DER format.
2792
TEST(Sync_SSL_Certificate_DER)
2793
{
1✔
2794
    TEST_DIR(server_dir);
1✔
2795
    TEST_CLIENT_DB(db);
1✔
2796
    std::string ca_dir = get_test_resource_path();
1✔
2797

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

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

2805
    Session::Config session_config;
1✔
2806
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2807
    session_config.verify_servers_ssl_certificate = true;
1✔
2808
    session_config.ssl_trust_certificate_path = ca_dir + "localhost-chain.crt.cer";
1✔
2809
    session_config.signed_user_token = g_signed_test_user_token;
1✔
2810

2811
    Session session = fixture.make_session(db, "/test", std::move(session_config));
1✔
2812
    session.bind();
1✔
2813

2814
    fixture.start();
1✔
2815
    session.wait_for_download_complete_or_client_stopped();
1✔
2816
}
1✔
2817

2818
#endif // REALM_HAVE_SECURE_TRANSPORT
2819

2820

2821
#if REALM_HAVE_OPENSSL
2822

2823
// This test checks that the SSL connection is accepted if the verify callback
2824
// always returns true.
2825
TEST(Sync_SSL_Certificate_Verify_Callback_1)
2826
{
1✔
2827
    TEST_DIR(server_dir);
1✔
2828
    TEST_CLIENT_DB(db);
1✔
2829
    std::string ca_dir = get_test_resource_path();
1✔
2830

1✔
2831
    Session::port_type server_port_ssl;
1✔
2832
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port, const char*,
1✔
2833
                                   size_t, int, int) {
2✔
2834
        CHECK_EQUAL(server_address, "localhost");
2✔
2835
        server_port_ssl = server_port;
2✔
2836
        return true;
2✔
2837
    };
2✔
2838

1✔
2839
    ClientServerFixture::Config config;
1✔
2840
    config.enable_server_ssl = true;
1✔
2841
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2842
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2843

1✔
2844
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2845

1✔
2846
    Session::Config session_config;
1✔
2847
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2848
    session_config.verify_servers_ssl_certificate = true;
1✔
2849
    session_config.ssl_trust_certificate_path = util::none;
1✔
2850
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2851

1✔
2852
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2853
    fixture.start();
1✔
2854
    session.wait_for_download_complete_or_client_stopped();
1✔
2855

1✔
2856
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2857
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2858
}
1✔
2859

2860

2861
// This test checks that the SSL connection is rejected if the verify callback
2862
// always returns false. It also checks that preverify_ok and depth have
2863
// the expected values.
2864
TEST(Sync_SSL_Certificate_Verify_Callback_2)
2865
{
1✔
2866
    bool did_fail = false;
1✔
2867
    TEST_DIR(server_dir);
1✔
2868
    TEST_CLIENT_DB(db);
1✔
2869
    std::string ca_dir = get_test_resource_path();
1✔
2870

1✔
2871
    Session::port_type server_port_ssl;
1✔
2872
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
1✔
2873
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
1✔
2874
        CHECK_EQUAL(server_address, "localhost");
1✔
2875
        server_port_ssl = server_port;
1✔
2876
        CHECK_EQUAL(preverify_ok, 0);
1✔
2877
        CHECK_EQUAL(depth, 1);
1✔
2878
        CHECK_EQUAL(pem_size, 2082);
1✔
2879
        std::string pem(pem_data, pem_size);
1✔
2880

1✔
2881
        std::string expected = "-----BEGIN CERTIFICATE-----\n"
1✔
2882
                               "MIIF0zCCA7ugAwIBAgIBCDANBgkqhkiG9w0BAQsFADB1MRIwEAYKCZImiZPyLGQB\n";
1✔
2883

1✔
2884
        CHECK_EQUAL(expected, pem.substr(0, expected.size()));
1✔
2885

1✔
2886
        return false;
1✔
2887
    };
1✔
2888

1✔
2889
    ClientServerFixture::Config config;
1✔
2890
    config.enable_server_ssl = true;
1✔
2891
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2892
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2893

1✔
2894
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2895

1✔
2896
    auto error_handler = [&](Status status, bool) {
1✔
2897
        CHECK_EQUAL(status, ErrorCodes::TlsHandshakeFailed);
1✔
2898
        did_fail = true;
1✔
2899
        fixture.stop();
1✔
2900
    };
1✔
2901
    fixture.set_client_side_error_handler(std::move(error_handler));
1✔
2902

1✔
2903
    Session::Config session_config;
1✔
2904
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2905
    session_config.verify_servers_ssl_certificate = true;
1✔
2906
    session_config.ssl_trust_certificate_path = util::none;
1✔
2907
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2908

1✔
2909
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2910
    fixture.start();
1✔
2911
    session.wait_for_download_complete_or_client_stopped();
1✔
2912
    CHECK(did_fail);
1✔
2913
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2914
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2915
}
1✔
2916

2917

2918
// This test checks that the verify callback function receives the expected
2919
// certificates.
2920
TEST(Sync_SSL_Certificate_Verify_Callback_3)
2921
{
1✔
2922
    TEST_DIR(server_dir);
1✔
2923
    TEST_CLIENT_DB(db);
1✔
2924
    std::string ca_dir = get_test_resource_path();
1✔
2925

1✔
2926
    Session::port_type server_port_ssl = 0;
1✔
2927
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
1✔
2928
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
2✔
2929
        CHECK_EQUAL(server_address, "localhost");
2✔
2930
        server_port_ssl = server_port;
2✔
2931

2✔
2932
        CHECK(depth == 0 || depth == 1);
2✔
2933
        if (depth == 1) {
2✔
2934
            CHECK_EQUAL(pem_size, 2082);
1✔
2935
            CHECK_EQUAL(pem_data[93], 'G');
1✔
2936
        }
1✔
2937
        else {
1✔
2938
            CHECK_EQUAL(pem_size, 1700);
1✔
2939
            CHECK_EQUAL(preverify_ok, 1);
1✔
2940
            CHECK_EQUAL(pem_data[1667], '2');
1✔
2941
            CHECK_EQUAL(pem_data[1698], '-');
1✔
2942
            CHECK_EQUAL(pem_data[1699], '\n');
1✔
2943
        }
1✔
2944

2✔
2945
        return true;
2✔
2946
    };
2✔
2947

1✔
2948
    ClientServerFixture::Config config;
1✔
2949
    config.enable_server_ssl = true;
1✔
2950
    config.server_ssl_certificate_path = ca_dir + "localhost-chain.crt.pem";
1✔
2951
    config.server_ssl_certificate_key_path = ca_dir + "localhost-server.key.pem";
1✔
2952

1✔
2953
    ClientServerFixture fixture{server_dir, test_context, config};
1✔
2954

1✔
2955
    Session::Config session_config;
1✔
2956
    session_config.protocol_envelope = ProtocolEnvelope::realms;
1✔
2957
    session_config.verify_servers_ssl_certificate = true;
1✔
2958
    session_config.ssl_trust_certificate_path = util::none;
1✔
2959
    session_config.ssl_verify_callback = ssl_verify_callback;
1✔
2960

1✔
2961
    Session session = fixture.make_bound_session(db, "/test", g_signed_test_user_token, std::move(session_config));
1✔
2962
    fixture.start();
1✔
2963
    session.wait_for_download_complete_or_client_stopped();
1✔
2964
    Session::port_type server_port_actual = fixture.get_server().listen_endpoint().port();
1✔
2965
    CHECK_EQUAL(server_port_ssl, server_port_actual);
1✔
2966
}
1✔
2967

2968

2969
// This test is used to verify the ssl_verify_callback function against an
2970
// external server. The tests should only be used for debugging should normally
2971
// be disabled.
2972
TEST_IF(Sync_SSL_Certificate_Verify_Callback_External, false)
2973
{
2974
    const std::string server_address = "www.writeurl.com";
2975
    Session::port_type port = 443;
2976

2977
    TEST_CLIENT_DB(db);
2978

2979
    Client::Config config;
2980
    config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2981
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(config.logger, "");
2982
    config.socket_provider = socket_provider;
2983
    config.reconnect_mode = ReconnectMode::testing;
2984
    Client client(config);
2985

2986
    auto ssl_verify_callback = [&](const std::string server_address, Session::port_type server_port,
2987
                                   const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
2988
        StringData pem{pem_data, pem_size};
2989
        test_context.logger->info("server_address = %1, server_port = %2, pem =\n%3\n, "
2990
                                  " preverify_ok = %4, depth = %5",
2991
                                  server_address, server_port, pem, preverify_ok, depth);
2992
        if (depth == 0)
2993
            client.shutdown();
2994
        return true;
2995
    };
2996

2997
    Session::Config session_config;
2998
    session_config.server_address = server_address;
2999
    session_config.server_port = port;
3000
    session_config.protocol_envelope = ProtocolEnvelope::realms;
3001
    session_config.verify_servers_ssl_certificate = true;
3002
    session_config.ssl_trust_certificate_path = util::none;
3003
    session_config.ssl_verify_callback = ssl_verify_callback;
3004

3005
    Session session(client, db, nullptr, nullptr, std::move(session_config));
3006
    session.bind();
3007
    session.wait_for_download_complete_or_client_stopped();
3008

3009
    client.shutdown_and_wait();
3010
}
3011

3012
#endif // REALM_HAVE_OPENSSL
3013

3014

3015
// This test has a single client connected to a server with
3016
// one session.
3017
// The client creates four changesets at various times and
3018
// uploads them to the server. The session has a registered
3019
// progress_handler. It is checked that downloaded_bytes,
3020
// downloadable_bytes, uploaded_bytes, and uploadable_bytes
3021
// are correct. This client does not have any downloaded_bytes
3022
// or downloadable bytes because it created all the changesets
3023
// itself.
3024
TEST(Sync_UploadDownloadProgress_1)
3025
{
2✔
3026
    TEST_DIR(server_dir);
2✔
3027
    TEST_CLIENT_DB(db);
2✔
3028

1✔
3029
    uint_fast64_t global_snapshot_version;
2✔
3030

1✔
3031
    {
2✔
3032
        int handler_entry = 0;
2✔
3033

1✔
3034
        bool cond_var_signaled = false;
2✔
3035
        std::mutex mutex;
2✔
3036
        std::condition_variable cond_var;
2✔
3037

1✔
3038
        std::atomic<uint_fast64_t> downloaded_bytes;
2✔
3039
        std::atomic<uint_fast64_t> downloadable_bytes;
2✔
3040
        std::atomic<uint_fast64_t> uploaded_bytes;
2✔
3041
        std::atomic<uint_fast64_t> uploadable_bytes;
2✔
3042
        std::atomic<uint_fast64_t> progress_version;
2✔
3043
        std::atomic<uint_fast64_t> snapshot_version;
2✔
3044

1✔
3045
        ClientServerFixture fixture(server_dir, test_context);
2✔
3046
        fixture.start();
2✔
3047

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

1✔
3050
        auto progress_handler = [&](uint_fast64_t downloaded, uint_fast64_t downloadable, uint_fast64_t uploaded,
2✔
3051
                                    uint_fast64_t uploadable, uint_fast64_t progress, uint_fast64_t snapshot) {
12✔
3052
            downloaded_bytes = downloaded;
12✔
3053
            downloadable_bytes = downloadable;
12✔
3054
            uploaded_bytes = uploaded;
12✔
3055
            uploadable_bytes = uploadable;
12✔
3056
            progress_version = progress;
12✔
3057
            snapshot_version = snapshot;
12✔
3058

6✔
3059
            if (handler_entry == 0) {
12✔
3060
                std::unique_lock<std::mutex> lock(mutex);
2✔
3061
                cond_var_signaled = true;
2✔
3062
                lock.unlock();
2✔
3063
                cond_var.notify_one();
2✔
3064
            }
2✔
3065
            ++handler_entry;
12✔
3066
        };
12✔
3067

1✔
3068
        std::unique_lock<std::mutex> lock(mutex);
2✔
3069
        session.set_progress_handler(progress_handler);
2✔
3070
        session.bind();
2✔
3071
        cond_var.wait(lock, [&] {
4✔
3072
            return cond_var_signaled;
4✔
3073
        });
4✔
3074

1✔
3075
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3076
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3077
        CHECK_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3078
        CHECK_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3079
        CHECK_GREATER_EQUAL(snapshot_version, uint_fast64_t(1));
2✔
3080

1✔
3081
        uint_fast64_t commit_version;
2✔
3082
        {
2✔
3083
            WriteTransaction wt{db};
2✔
3084
            TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3085
            tr->add_column(type_Int, "integer column");
2✔
3086
            commit_version = wt.commit();
2✔
3087
            session.nonsync_transact_notify(commit_version);
2✔
3088
        }
2✔
3089

1✔
3090
        session.wait_for_upload_complete_or_client_stopped();
2✔
3091
        session.wait_for_download_complete_or_client_stopped();
2✔
3092

1✔
3093
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3094
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3095
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3096
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3097
        CHECK_GREATER(progress_version, uint_fast64_t(0));
2✔
3098
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
3099

1✔
3100
        {
2✔
3101
            WriteTransaction wt{db};
2✔
3102
            TableRef tr = wt.get_table("class_table");
2✔
3103
            tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3104
            commit_version = wt.commit();
2✔
3105
            session.nonsync_transact_notify(commit_version);
2✔
3106
        }
2✔
3107

1✔
3108
        session.wait_for_upload_complete_or_client_stopped();
2✔
3109
        session.wait_for_download_complete_or_client_stopped();
2✔
3110

1✔
3111
        CHECK_EQUAL(downloaded_bytes, uint_fast64_t(0));
2✔
3112
        CHECK_EQUAL(downloadable_bytes, uint_fast64_t(0));
2✔
3113
        CHECK_NOT_EQUAL(uploaded_bytes, uint_fast64_t(0));
2✔
3114
        CHECK_NOT_EQUAL(uploadable_bytes, uint_fast64_t(0));
2✔
3115
        CHECK_GREATER_EQUAL(snapshot_version, commit_version);
2✔
3116

1✔
3117
        global_snapshot_version = snapshot_version;
2✔
3118
    }
2✔
3119

1✔
3120
    {
2✔
3121
        // Here we check that the progress handler is called
1✔
3122
        // after the session is bound, and that the values
1✔
3123
        // are the ones stored in the Realm in the previous
1✔
3124
        // session.
1✔
3125

1✔
3126
        bool cond_var_signaled = false;
2✔
3127
        std::mutex mutex;
2✔
3128
        std::condition_variable cond_var;
2✔
3129

1✔
3130
        Client::Config config;
2✔
3131
        config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3132
        auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(config.logger, "");
2✔
3133
        config.socket_provider = socket_provider;
2✔
3134
        config.reconnect_mode = ReconnectMode::testing;
2✔
3135
        Client client(config);
2✔
3136

1✔
3137
        Session::Config sess_config;
2✔
3138
        sess_config.server_address = "no server";
2✔
3139
        sess_config.server_port = 8000;
2✔
3140
        sess_config.realm_identifier = "/test";
2✔
3141
        sess_config.signed_user_token = g_signed_test_user_token;
2✔
3142

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

1✔
3145
        int number_of_handler_calls = 0;
2✔
3146

1✔
3147
        auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3148
                                    uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3149
                                    uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
2✔
3150
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3151
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3152
            CHECK_NOT_EQUAL(uploaded_bytes, 0);
2✔
3153
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3154
            CHECK_EQUAL(progress_version, 0);
2✔
3155
            CHECK_EQUAL(snapshot_version, global_snapshot_version);
2✔
3156
            number_of_handler_calls++;
2✔
3157

1✔
3158
            std::unique_lock<std::mutex> lock(mutex);
2✔
3159
            cond_var_signaled = true;
2✔
3160
            lock.unlock();
2✔
3161
            cond_var.notify_one();
2✔
3162
        };
2✔
3163

1✔
3164
        std::unique_lock<std::mutex> lock(mutex);
2✔
3165
        session.set_progress_handler(progress_handler);
2✔
3166
        session.bind();
2✔
3167
        cond_var.wait(lock, [&] {
4✔
3168
            return cond_var_signaled;
4✔
3169
        });
4✔
3170

1✔
3171
        client.shutdown();
2✔
3172
        CHECK_EQUAL(number_of_handler_calls, 1);
2✔
3173
    }
2✔
3174
}
2✔
3175

3176

3177
// This test creates one server and a client with
3178
// two sessions that synchronizes with the same server Realm.
3179
// The clients generate changesets, uploads and downloads, and
3180
// waits for upload/download completion. Both sessions have a
3181
// progress handler registered, and it is checked that the
3182
// progress handlers report the correct values.
3183
TEST(Sync_UploadDownloadProgress_2)
3184
{
2✔
3185
    TEST_DIR(server_dir);
2✔
3186
    TEST_CLIENT_DB(db_1);
2✔
3187
    TEST_CLIENT_DB(db_2);
2✔
3188

1✔
3189
    ClientServerFixture fixture(server_dir, test_context);
2✔
3190
    fixture.start();
2✔
3191

1✔
3192
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
3193
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
3194

1✔
3195
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3196
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3197
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3198
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3199
    uint_fast64_t progress_version_1 = 123;
2✔
3200
    uint_fast64_t snapshot_version_1 = 0;
2✔
3201

1✔
3202
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3203
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3204
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
23✔
3205
        downloaded_bytes_1 = downloaded_bytes;
23✔
3206
        downloadable_bytes_1 = downloadable_bytes;
23✔
3207
        uploaded_bytes_1 = uploaded_bytes;
23✔
3208
        uploadable_bytes_1 = uploadable_bytes;
23✔
3209
        progress_version_1 = progress_version;
23✔
3210
        snapshot_version_1 = snapshot_version;
23✔
3211
    };
23✔
3212

1✔
3213
    session_1.set_progress_handler(progress_handler_1);
2✔
3214

1✔
3215
    uint_fast64_t downloaded_bytes_2 = 123;
2✔
3216
    uint_fast64_t downloadable_bytes_2 = 123;
2✔
3217
    uint_fast64_t uploaded_bytes_2 = 123;
2✔
3218
    uint_fast64_t uploadable_bytes_2 = 123;
2✔
3219
    uint_fast64_t progress_version_2 = 123;
2✔
3220
    uint_fast64_t snapshot_version_2 = 0;
2✔
3221

1✔
3222
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3223
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3224
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
19✔
3225
        downloaded_bytes_2 = downloaded_bytes;
19✔
3226
        downloadable_bytes_2 = downloadable_bytes;
19✔
3227
        uploaded_bytes_2 = uploaded_bytes;
19✔
3228
        uploadable_bytes_2 = uploadable_bytes;
19✔
3229
        progress_version_2 = progress_version;
19✔
3230
        snapshot_version_2 = snapshot_version;
19✔
3231
    };
19✔
3232

1✔
3233
    session_2.set_progress_handler(progress_handler_2);
2✔
3234

1✔
3235
    session_1.bind();
2✔
3236
    session_2.bind();
2✔
3237

1✔
3238
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3239
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3240
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3241
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3242

1✔
3243
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3244
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3245
    CHECK_EQUAL(downloaded_bytes_1, downloaded_bytes_2);
2✔
3246
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3247
    CHECK_GREATER(progress_version_1, 0);
2✔
3248
    CHECK_GREATER(snapshot_version_1, 0);
2✔
3249

1✔
3250
    CHECK_EQUAL(uploaded_bytes_1, 0);
2✔
3251
    CHECK_EQUAL(uploadable_bytes_1, 0);
2✔
3252

1✔
3253
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3254
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3255
    CHECK_GREATER(progress_version_2, 0);
2✔
3256
    CHECK_GREATER(snapshot_version_2, 0);
2✔
3257

1✔
3258
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
3259
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3260
        tr->add_column(type_Int, "integer column");
2✔
3261
    });
2✔
3262

1✔
3263
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3264
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3265
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3266
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3267

1✔
3268
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3269
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3270

1✔
3271
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3272
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3273

1✔
3274
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3275
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3276

1✔
3277
    CHECK_EQUAL(uploaded_bytes_2, 0);
2✔
3278
    CHECK_EQUAL(uploadable_bytes_2, 0);
2✔
3279

1✔
3280
    CHECK_GREATER(snapshot_version_1, 1);
2✔
3281
    CHECK_GREATER(snapshot_version_2, 1);
2✔
3282

1✔
3283
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
3284
        TableRef tr = wt.get_table("class_table");
2✔
3285
        tr->create_object_with_primary_key(1).set("integer column", 42);
2✔
3286
    });
2✔
3287

1✔
3288
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
3289
        TableRef tr = wt.get_table("class_table");
2✔
3290
        tr->create_object_with_primary_key(2).set("integer column", 44);
2✔
3291
    });
2✔
3292

1✔
3293
    write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
3294
        TableRef tr = wt.get_table("class_table");
2✔
3295
        tr->create_object_with_primary_key(3).set("integer column", 43);
2✔
3296
    });
2✔
3297

1✔
3298
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3299
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3300
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3301
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3302

1✔
3303
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3304
    CHECK_NOT_EQUAL(downloadable_bytes_1, 0);
2✔
3305

1✔
3306
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3307
    CHECK_NOT_EQUAL(downloadable_bytes_2, 0);
2✔
3308

1✔
3309
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3310
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3311

1✔
3312
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3313
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3314

1✔
3315
    CHECK_GREATER(snapshot_version_1, 4);
2✔
3316
    CHECK_GREATER(snapshot_version_2, 3);
2✔
3317

1✔
3318
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
3319
        TableRef tr = wt.get_table("class_table");
2✔
3320
        tr->begin()->set("integer column", 101);
2✔
3321
    });
2✔
3322

1✔
3323
    write_transaction_notifying_session(db_2, session_2, [](WriteTransaction& wt) {
2✔
3324
        TableRef tr = wt.get_table("class_table");
2✔
3325
        tr->begin()->set("integer column", 102);
2✔
3326
    });
2✔
3327

1✔
3328
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3329
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3330
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3331
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3332

1✔
3333
    CHECK_EQUAL(downloaded_bytes_1, downloadable_bytes_1);
2✔
3334

1✔
3335
    // uncertainty due to merge
1✔
3336
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
3337

1✔
3338
    CHECK_EQUAL(downloaded_bytes_2, downloadable_bytes_2);
2✔
3339
    CHECK_NOT_EQUAL(downloaded_bytes_2, 0);
2✔
3340

1✔
3341
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3342
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3343

1✔
3344
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
3345
    CHECK_NOT_EQUAL(uploadable_bytes_2, 0);
2✔
3346

1✔
3347
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3348
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3349

1✔
3350
    CHECK_GREATER(snapshot_version_1, 6);
2✔
3351
    CHECK_GREATER(snapshot_version_2, 5);
2✔
3352

1✔
3353
    // Check convergence.
1✔
3354
    {
2✔
3355
        ReadTransaction rt_1(db_1);
2✔
3356
        ReadTransaction rt_2(db_2);
2✔
3357
        CHECK(compare_groups(rt_1, rt_2));
2✔
3358
    }
2✔
3359
}
2✔
3360

3361

3362
// This test creates a server and a client. Initially, the server is not running.
3363
// The client generates changes and binds a session. It is verified that the
3364
// progress_handler() is called and that the four arguments of progress_handler()
3365
// have the correct values. The server is started in the first call to
3366
// progress_handler() and it is checked that after upload and download completion,
3367
// the upload_progress_handler has been called again, and that the four arguments
3368
// have the correct values. After this, the server is stopped and the client produces
3369
// more changes. It is checked that the progress_handler() is called and that the
3370
// final values are correct.
3371
TEST(Sync_UploadDownloadProgress_3)
3372
{
2✔
3373
    TEST_DIR(server_dir);
2✔
3374
    TEST_CLIENT_DB(db);
2✔
3375

1✔
3376
    std::string server_address = "localhost";
2✔
3377

1✔
3378
    Server::Config server_config;
2✔
3379
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3380
    server_config.listen_address = server_address;
2✔
3381
    server_config.listen_port = "";
2✔
3382
    server_config.tcp_no_delay = true;
2✔
3383

1✔
3384
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3385
    Server server(server_dir, std::move(public_key), server_config);
2✔
3386
    server.start();
2✔
3387
    auto server_port = server.listen_endpoint().port();
2✔
3388

1✔
3389
    ThreadWrapper server_thread;
2✔
3390

1✔
3391
    // The server is not running.
1✔
3392

1✔
3393
    {
2✔
3394
        WriteTransaction wt{db};
2✔
3395
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3396
        tr->add_column(type_Int, "integer column");
2✔
3397
        wt.commit();
2✔
3398
    }
2✔
3399

1✔
3400

1✔
3401
    Client::Config client_config;
2✔
3402
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3403
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3404
    client_config.socket_provider = socket_provider;
2✔
3405
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3406
    Client client(client_config);
2✔
3407

1✔
3408
    // when connecting to the C++ server, use URL prefix:
1✔
3409
    Session::Config config;
2✔
3410
    config.service_identifier = "/realm-sync";
2✔
3411
    config.server_address = server_address;
2✔
3412
    config.signed_user_token = g_signed_test_user_token;
2✔
3413
    config.server_port = server_port;
2✔
3414
    config.realm_identifier = "/test";
2✔
3415

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

1✔
3418
    // entry is used to count the number of calls to
1✔
3419
    // progress_handler. At the first call, the server is
1✔
3420
    // not running, and it is started by progress_handler().
1✔
3421

1✔
3422
    bool should_signal_cond_var = false;
2✔
3423
    auto signal_pf = util::make_promise_future<void>();
2✔
3424

1✔
3425
    uint_fast64_t downloaded_bytes_1 = 123; // Not zero
2✔
3426
    uint_fast64_t downloadable_bytes_1 = 123;
2✔
3427
    uint_fast64_t uploaded_bytes_1 = 123;
2✔
3428
    uint_fast64_t uploadable_bytes_1 = 123;
2✔
3429
    uint_fast64_t progress_version_1 = 123;
2✔
3430
    uint_fast64_t snapshot_version_1 = 0;
2✔
3431

1✔
3432
    auto progress_handler = [&, entry = int(0), promise = util::CopyablePromiseHolder(std::move(signal_pf.promise))](
2✔
3433
                                uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3434
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3435
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) mutable {
8✔
3436
        downloaded_bytes_1 = downloaded_bytes;
8✔
3437
        downloadable_bytes_1 = downloadable_bytes;
8✔
3438
        uploaded_bytes_1 = uploaded_bytes;
8✔
3439
        uploadable_bytes_1 = uploadable_bytes;
8✔
3440
        progress_version_1 = progress_version;
8✔
3441
        snapshot_version_1 = snapshot_version;
8✔
3442

4✔
3443
        if (entry == 0) {
8✔
3444
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3445
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3446
            CHECK_EQUAL(uploaded_bytes, 0);
2✔
3447
            CHECK_NOT_EQUAL(uploadable_bytes, 0);
2✔
3448
            CHECK_EQUAL(snapshot_version, 2);
2✔
3449
        }
2✔
3450

4✔
3451
        if (entry == 0) {
8✔
3452
            server_thread.start([&] {
2✔
3453
                server.run();
2✔
3454
            });
2✔
3455
        }
2✔
3456

4✔
3457
        if (should_signal_cond_var) {
8✔
3458
            promise.get_promise().emplace_value();
2✔
3459
        }
2✔
3460

4✔
3461
        entry++;
8✔
3462
    };
8✔
3463

1✔
3464
    session.set_progress_handler(progress_handler);
2✔
3465

1✔
3466
    session.bind();
2✔
3467

1✔
3468
    session.wait_for_upload_complete_or_client_stopped();
2✔
3469
    session.wait_for_download_complete_or_client_stopped();
2✔
3470

1✔
3471
    // Now the server is running.
1✔
3472

1✔
3473
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3474
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3475
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3476
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3477
    CHECK_GREATER(progress_version_1, 0);
2✔
3478
    CHECK_GREATER_EQUAL(snapshot_version_1, 2);
2✔
3479

1✔
3480
    server.stop();
2✔
3481

1✔
3482
    // The server is stopped
1✔
3483

1✔
3484
    should_signal_cond_var = true;
2✔
3485

1✔
3486
    uint_fast64_t commited_version;
2✔
3487
    {
2✔
3488
        WriteTransaction wt{db};
2✔
3489
        TableRef tr = wt.get_table("class_table");
2✔
3490
        tr->create_object_with_primary_key(123).set("integer column", 42);
2✔
3491
        commited_version = wt.commit();
2✔
3492
        session.nonsync_transact_notify(commited_version);
2✔
3493
    }
2✔
3494

1✔
3495
    signal_pf.future.get();
2✔
3496

1✔
3497
    CHECK_EQUAL(downloaded_bytes_1, 0);
2✔
3498
    CHECK_EQUAL(downloadable_bytes_1, 0);
2✔
3499
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
3500
    CHECK_NOT_EQUAL(uploadable_bytes_1, 0);
2✔
3501
    CHECK_EQUAL(snapshot_version_1, commited_version);
2✔
3502

1✔
3503
    server_thread.join();
2✔
3504
}
2✔
3505

3506

3507
// This test creates a server and two clients. The first client uploads two
3508
// large changesets. The other client downloads them. The download messages to
3509
// the second client contains one changeset because the changesets are larger
3510
// than the soft size limit for changesets in the DOWNLOAD message. This implies
3511
// that after receiving the first DOWNLOAD message, the second client will have
3512
// downloaded_bytes < downloadable_bytes.
3513
TEST(Sync_UploadDownloadProgress_4)
3514
{
2✔
3515
    TEST_DIR(server_dir);
2✔
3516
    TEST_CLIENT_DB(db_1);
2✔
3517
    TEST_CLIENT_DB(db_2);
2✔
3518

1✔
3519
    {
2✔
3520
        WriteTransaction wt{db_1};
2✔
3521
        TableRef tr = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
3522
        auto col = tr->add_column(type_Binary, "binary column");
2✔
3523
        tr->create_object_with_primary_key(1);
2✔
3524
        std::string str(size_t(5e5), 'a');
2✔
3525
        BinaryData bd(str.data(), str.size());
2✔
3526
        tr->begin()->set(col, bd);
2✔
3527
        wt.commit();
2✔
3528
    }
2✔
3529

1✔
3530
    {
2✔
3531
        WriteTransaction wt{db_1};
2✔
3532
        TableRef tr = wt.get_table("class_table");
2✔
3533
        auto col = tr->get_column_key("binary column");
2✔
3534
        tr->create_object_with_primary_key(2);
2✔
3535
        std::string str(size_t(1e6), 'a');
2✔
3536
        BinaryData bd(str.data(), str.size());
2✔
3537
        tr->begin()->set(col, bd);
2✔
3538
        wt.commit();
2✔
3539
    }
2✔
3540

1✔
3541
    ClientServerFixture::Config config;
2✔
3542
    config.max_download_size = size_t(1e5);
2✔
3543
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
3544
    fixture.start();
2✔
3545

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

1✔
3548
    int entry_1 = 0;
2✔
3549

1✔
3550
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3551
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3552
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
8✔
3553
        CHECK_EQUAL(downloaded_bytes, 0);
8✔
3554
        CHECK_EQUAL(downloadable_bytes, 0);
8✔
3555
        CHECK_NOT_EQUAL(uploadable_bytes, 0);
8✔
3556

4✔
3557
        if (entry_1 == 0) {
8✔
3558
            CHECK_EQUAL(progress_version, 0);
2✔
3559
            CHECK_EQUAL(uploaded_bytes, 0);
2✔
3560
            CHECK_EQUAL(snapshot_version, 3);
2✔
3561
        }
2✔
3562
        else {
6✔
3563
            CHECK_GREATER(progress_version, 0);
6✔
3564
            CHECK_GREATER(snapshot_version, 3);
6✔
3565
        }
6✔
3566

4✔
3567
        ++entry_1;
8✔
3568
    };
8✔
3569

1✔
3570
    session_1.set_progress_handler(progress_handler_1);
2✔
3571

1✔
3572
    session_1.bind();
2✔
3573

1✔
3574
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
3575
    session_1.wait_for_download_complete_or_client_stopped();
2✔
3576

1✔
3577
    CHECK_NOT_EQUAL(entry_1, 0);
2✔
3578

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

1✔
3581
    int entry_2 = 0;
2✔
3582

1✔
3583
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3584
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3585
                                  uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
6✔
3586
        CHECK_EQUAL(uploaded_bytes, 0);
6✔
3587
        CHECK_EQUAL(uploadable_bytes, 0);
6✔
3588

3✔
3589
        if (entry_2 == 0) {
6✔
3590
            CHECK_EQUAL(progress_version, 0);
2✔
3591
            CHECK_EQUAL(downloaded_bytes, 0);
2✔
3592
            CHECK_EQUAL(downloadable_bytes, 0);
2✔
3593
            CHECK_EQUAL(snapshot_version, 1);
2✔
3594
        }
2✔
3595
        else if (entry_2 == 1) {
4✔
3596
            CHECK_GREATER(progress_version, 0);
2✔
3597
            CHECK_NOT_EQUAL(downloaded_bytes, 0);
2✔
3598
            CHECK_NOT_EQUAL(downloadable_bytes, 0);
2✔
3599
            CHECK_EQUAL(snapshot_version, 3);
2✔
3600
        }
2✔
3601
        else if (entry_2 == 2) {
2✔
3602
            CHECK_GREATER(progress_version, 0);
2✔
3603
            CHECK_NOT_EQUAL(downloaded_bytes, 0);
2✔
3604
            CHECK_NOT_EQUAL(downloadable_bytes, 0);
2✔
3605
            CHECK_EQUAL(snapshot_version, 4);
2✔
3606
        }
2✔
3607

3✔
3608
        ++entry_2;
6✔
3609
    };
6✔
3610

1✔
3611
    session_2.set_progress_handler(progress_handler_2);
2✔
3612

1✔
3613
    session_2.bind();
2✔
3614

1✔
3615
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
3616
    session_2.wait_for_download_complete_or_client_stopped();
2✔
3617
}
2✔
3618

3619

3620
// This test has a single client connected to a server with one session. The
3621
// client does not create any changesets. The test verifies that the client gets
3622
// a confirmation from the server of downloadable_bytes = 0.
3623
TEST(Sync_UploadDownloadProgress_5)
3624
{
2✔
3625
    TEST_DIR(server_dir);
2✔
3626
    TEST_CLIENT_DB(db);
2✔
3627

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

1✔
3630
    ClientServerFixture fixture(server_dir, test_context);
2✔
3631
    fixture.start();
2✔
3632

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

1✔
3635
    auto progress_handler = [&, promise = util::CopyablePromiseHolder(std::move(progress_handled_promise))](
2✔
3636
                                uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3637
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3638
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) mutable {
4✔
3639
        CHECK_EQUAL(downloaded_bytes, 0);
4✔
3640
        CHECK_EQUAL(downloadable_bytes, 0);
4✔
3641
        CHECK_EQUAL(uploaded_bytes, 0);
4✔
3642
        CHECK_EQUAL(uploadable_bytes, 0);
4✔
3643

2✔
3644
        if (progress_version > 0) {
4✔
3645
            CHECK_EQUAL(snapshot_version, 3);
2✔
3646
            promise.get_promise().emplace_value();
2✔
3647
        }
2✔
3648
    };
4✔
3649

1✔
3650
    session.set_progress_handler(progress_handler);
2✔
3651

1✔
3652
    session.bind();
2✔
3653
    progress_handled.get();
2✔
3654

1✔
3655
    // The check is that we reach this point.
1✔
3656
}
2✔
3657

3658

3659
// This test has a single client connected to a server with one session.
3660
// The session has a registered progress handler.
3661
TEST(Sync_UploadDownloadProgress_6)
3662
{
2✔
3663
    TEST_DIR(server_dir);
2✔
3664
    TEST_CLIENT_DB(db);
2✔
3665

1✔
3666
    Server::Config server_config;
2✔
3667
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3668
    server_config.listen_address = "localhost";
2✔
3669
    server_config.listen_port = "";
2✔
3670
    server_config.tcp_no_delay = true;
2✔
3671

1✔
3672
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3673
    Server server(server_dir, std::move(public_key), server_config);
2✔
3674
    server.start();
2✔
3675

1✔
3676
    auto server_port = server.listen_endpoint().port();
2✔
3677

1✔
3678
    ThreadWrapper server_thread;
2✔
3679
    server_thread.start([&] {
2✔
3680
        server.run();
2✔
3681
    });
2✔
3682

1✔
3683
    Client::Config client_config;
2✔
3684
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3685
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3686
    client_config.socket_provider = socket_provider;
2✔
3687
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3688
    client_config.one_connection_per_session = false;
2✔
3689
    Client client(client_config);
2✔
3690

1✔
3691
    Session::Config session_config;
2✔
3692
    session_config.server_address = "localhost";
2✔
3693
    session_config.server_port = server_port;
2✔
3694
    session_config.realm_identifier = "/test";
2✔
3695
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3696

1✔
3697
    std::mutex mutex;
2✔
3698
    std::condition_variable session_cv;
2✔
3699
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3700

1✔
3701
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
3702
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
3703
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
2✔
3704
        CHECK_EQUAL(downloaded_bytes, 0);
2✔
3705
        CHECK_EQUAL(downloadable_bytes, 0);
2✔
3706
        CHECK_EQUAL(uploaded_bytes, 0);
2✔
3707
        CHECK_EQUAL(uploadable_bytes, 0);
2✔
3708
        CHECK_EQUAL(progress_version, 0);
2✔
3709
        CHECK_EQUAL(snapshot_version, 1);
2✔
3710
        std::lock_guard lock{mutex};
2✔
3711
        session.reset();
2✔
3712
        session_cv.notify_one();
2✔
3713
    };
2✔
3714

1✔
3715
    session->set_progress_handler(progress_handler);
2✔
3716

1✔
3717
    {
2✔
3718
        std::unique_lock lock{mutex};
2✔
3719
        session->bind();
2✔
3720
        // Wait until the progress handler is called on the session before tearing down the client
1✔
3721
        session_cv.wait_for(lock, std::chrono::seconds(30), [&session]() {
4✔
3722
            return !bool(session);
4✔
3723
        });
4✔
3724
    }
2✔
3725

1✔
3726
    client.shutdown_and_wait();
2✔
3727
    server.stop();
2✔
3728
    server_thread.join();
2✔
3729

1✔
3730
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
1✔
3731
    // down the active session
1✔
3732
}
2✔
3733

3734
// This test has a single client starting to connect to the server with one session.
3735
// The client is torn down immediately after bind is called on the session.
3736
// The session will still be active and has an unactualized session wrapper when the
3737
// client is torn down, which leads to both calls to finalize_before_actualization() and
3738
// and finalize().
3739
TEST(Sync_UploadDownloadProgress_7)
3740
{
2✔
3741
    TEST_DIR(server_dir);
2✔
3742
    TEST_CLIENT_DB(db);
2✔
3743

1✔
3744
    Server::Config server_config;
2✔
3745
    server_config.logger = std::make_shared<util::PrefixLogger>("Server: ", test_context.logger);
2✔
3746
    server_config.listen_address = "localhost";
2✔
3747
    server_config.listen_port = "";
2✔
3748
    server_config.tcp_no_delay = true;
2✔
3749

1✔
3750
    util::Optional<PKey> public_key = PKey::load_public(test_server_key_path());
2✔
3751
    Server server(server_dir, std::move(public_key), server_config);
2✔
3752
    server.start();
2✔
3753

1✔
3754
    auto server_port = server.listen_endpoint().port();
2✔
3755

1✔
3756
    ThreadWrapper server_thread;
2✔
3757
    server_thread.start([&] {
2✔
3758
        server.run();
2✔
3759
    });
2✔
3760

1✔
3761
    Client::Config client_config;
2✔
3762
    client_config.logger = std::make_shared<util::PrefixLogger>("Client: ", test_context.logger);
2✔
3763
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(client_config.logger, "");
2✔
3764
    client_config.socket_provider = socket_provider;
2✔
3765
    client_config.reconnect_mode = ReconnectMode::testing;
2✔
3766
    client_config.one_connection_per_session = false;
2✔
3767
    Client client(client_config);
2✔
3768

1✔
3769
    Session::Config session_config;
2✔
3770
    session_config.server_address = "localhost";
2✔
3771
    session_config.server_port = server_port;
2✔
3772
    session_config.realm_identifier = "/test";
2✔
3773
    session_config.signed_user_token = g_signed_test_user_token;
2✔
3774

1✔
3775
    auto session = std::make_unique<Session>(client, db, nullptr, nullptr, std::move(session_config));
2✔
3776
    session->bind();
2✔
3777

1✔
3778
    client.shutdown_and_wait();
2✔
3779
    server.stop();
2✔
3780
    server_thread.join();
2✔
3781

1✔
3782
    // The check is that we reach this point without deadlocking or throwing an assert while tearing
1✔
3783
    // down the session that is in the process of being created.
1✔
3784
}
2✔
3785

3786
TEST(Sync_MultipleSyncAgentsNotAllowed)
3787
{
2✔
3788
    // At most one sync agent is allowed to participate in a Realm file access
1✔
3789
    // session at any particular point in time. Note that a Realm file access
1✔
3790
    // session is a group of temporally overlapping accesses to a Realm file,
1✔
3791
    // and that the group of participants is the transitive closure of a
1✔
3792
    // particular session participant over the "temporally overlapping access"
1✔
3793
    // relation.
1✔
3794

1✔
3795
    TEST_CLIENT_DB(db);
2✔
3796
    Client::Config config;
2✔
3797
    config.logger = test_context.logger;
2✔
3798
    auto socket_provider = std::make_shared<websocket::DefaultSocketProvider>(
2✔
3799
        config.logger, "", nullptr, websocket::DefaultSocketProvider::AutoStart{false});
2✔
3800
    config.socket_provider = socket_provider;
2✔
3801
    config.reconnect_mode = ReconnectMode::testing;
2✔
3802
    Client client{config};
2✔
3803
    {
2✔
3804
        Session::Config config_1;
2✔
3805
        config_1.realm_identifier = "blablabla";
2✔
3806
        Session::Config config_2;
2✔
3807
        config_2.realm_identifier = config_1.realm_identifier;
2✔
3808
        Session session_1{client, db, nullptr, nullptr, std::move(config_1)};
2✔
3809
        Session session_2{client, db, nullptr, nullptr, std::move(config_2)};
2✔
3810
        session_1.bind();
2✔
3811
        session_2.bind();
2✔
3812
        CHECK_THROW(
2✔
3813
            websocket::DefaultSocketProvider::OnlyForTesting::run_event_loop_on_current_thread(socket_provider.get()),
2✔
3814
            MultipleSyncAgents);
2✔
3815
        websocket::DefaultSocketProvider::OnlyForTesting::prep_event_loop_for_restart(socket_provider.get());
2✔
3816
    }
2✔
3817

1✔
3818
    socket_provider->start();
2✔
3819
}
2✔
3820

3821
TEST(Sync_CancelReconnectDelay)
3822
{
2✔
3823
    TEST_DIR(server_dir);
2✔
3824
    TEST_CLIENT_DB(db);
2✔
3825
    TEST_CLIENT_DB(db_x);
2✔
3826

1✔
3827
    ClientServerFixture::Config fixture_config;
2✔
3828
    fixture_config.one_connection_per_session = false;
2✔
3829

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

1✔
3835
        BowlOfStonesSemaphore bowl;
2✔
3836
        auto handler = [&](const SessionErrorInfo& info) {
2✔
3837
            if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3838
                bowl.add_stone();
2✔
3839
        };
2✔
3840
        Session session = fixture.make_session(db, "/test");
2✔
3841
        session.set_error_handler(std::move(handler));
2✔
3842
        session.bind();
2✔
3843
        session.wait_for_download_complete_or_client_stopped();
2✔
3844
        fixture.close_server_side_connections();
2✔
3845
        bowl.get_stone();
2✔
3846

1✔
3847
        session.cancel_reconnect_delay();
2✔
3848
        session.wait_for_download_complete_or_client_stopped();
2✔
3849
    }
2✔
3850

1✔
3851
    // After connection-level error, and at client-level while connection
1✔
3852
    // object exists (ConnectionImpl in clinet.cpp).
1✔
3853
    {
2✔
3854
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3855
        fixture.start();
2✔
3856

1✔
3857
        BowlOfStonesSemaphore bowl;
2✔
3858
        auto handler = [&](const SessionErrorInfo& info) {
2✔
3859
            if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3860
                bowl.add_stone();
2✔
3861
        };
2✔
3862
        Session session = fixture.make_session(db, "/test");
2✔
3863
        session.set_error_handler(std::move(handler));
2✔
3864
        session.bind();
2✔
3865
        session.wait_for_download_complete_or_client_stopped();
2✔
3866
        fixture.close_server_side_connections();
2✔
3867
        bowl.get_stone();
2✔
3868

1✔
3869
        fixture.cancel_reconnect_delay();
2✔
3870
        session.wait_for_download_complete_or_client_stopped();
2✔
3871
    }
2✔
3872

1✔
3873
    // After connection-level error, and at client-level while connection object
1✔
3874
    // does not exist (ConnectionImpl in clinet.cpp).
1✔
3875
    {
2✔
3876
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3877
        fixture.start();
2✔
3878

1✔
3879
        {
2✔
3880
            BowlOfStonesSemaphore bowl;
2✔
3881
            auto handler = [&](const SessionErrorInfo& info) {
2✔
3882
                if (CHECK_EQUAL(info.status, ErrorCodes::ConnectionClosed))
2✔
3883
                    bowl.add_stone();
2✔
3884
            };
2✔
3885
            Session session = fixture.make_session(db, "/test");
2✔
3886
            session.set_error_handler(std::move(handler));
2✔
3887
            session.bind();
2✔
3888
            session.wait_for_download_complete_or_client_stopped();
2✔
3889
            fixture.close_server_side_connections();
2✔
3890
            bowl.get_stone();
2✔
3891
        }
2✔
3892

1✔
3893
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3894
        fixture.wait_for_session_terminations_or_client_stopped();
2✔
3895
        // The connection object no longer exists at this time. After the first
1✔
3896
        // of the two waits above, the invocation of ConnectionImpl::on_idle()
1✔
3897
        // (in client.cpp) has been scheduled. After the second wait, it has
1✔
3898
        // been called, and that destroys the connection object.
1✔
3899

1✔
3900
        fixture.cancel_reconnect_delay();
2✔
3901
        {
2✔
3902
            Session session = fixture.make_bound_session(db, "/test");
2✔
3903
            session.wait_for_download_complete_or_client_stopped();
2✔
3904
        }
2✔
3905
    }
2✔
3906

1✔
3907
    // After session-level error, and at session-level.
1✔
3908
    {
2✔
3909
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3910
        fixture.start();
2✔
3911

1✔
3912
        // Add a session for the purpose of keeping the connection open
1✔
3913
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3914
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3915

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

1✔
3926
        session.cancel_reconnect_delay();
2✔
3927
        bowl.get_stone();
2✔
3928
    }
2✔
3929

1✔
3930
    // After session-level error, and at client-level.
1✔
3931
    {
2✔
3932
        ClientServerFixture fixture{server_dir, test_context, std::move(fixture_config)};
2✔
3933
        fixture.start();
2✔
3934

1✔
3935
        // Add a session for the purpose of keeping the connection open
1✔
3936
        Session session_x = fixture.make_bound_session(db_x, "/x");
2✔
3937
        session_x.wait_for_download_complete_or_client_stopped();
2✔
3938

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

1✔
3949
        fixture.cancel_reconnect_delay();
2✔
3950
        bowl.get_stone();
2✔
3951
    }
2✔
3952
}
2✔
3953

3954

3955
#ifndef REALM_PLATFORM_WIN32
3956

3957
// This test checks that it is possible to create, upload, download, and merge
3958
// changesets larger than 16 MB.
3959
//
3960
// Fails with 'bad alloc' around 1 GB mem usage on 32-bit Windows + 32-bit Linux
3961
TEST_IF(Sync_MergeLargeBinary, !(REALM_ARCHITECTURE_X86_32))
3962
{
2✔
3963
    // Two binaries are inserted in each transaction such that the total size
1✔
3964
    // of the changeset exceeds 16 MB. A single set_binary operation does not
1✔
3965
    // accept a binary larger than 16 MB.
1✔
3966
    size_t binary_sizes[] = {
2✔
3967
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e6), static_cast<size_t>(11e6),
2✔
3968
        static_cast<size_t>(6e6), static_cast<size_t>(12e6), static_cast<size_t>(5e6), static_cast<size_t>(13e6),
2✔
3969
    };
2✔
3970

1✔
3971
    TEST_CLIENT_DB(db_1);
2✔
3972
    TEST_CLIENT_DB(db_2);
2✔
3973

1✔
3974
    {
2✔
3975
        WriteTransaction wt(db_1);
2✔
3976
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
3977
        table->add_column(type_Binary, "column name");
2✔
3978
        std::string str_1(binary_sizes[0], 'a');
2✔
3979
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3980
        std::string str_2(binary_sizes[1], 'b');
2✔
3981
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3982
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
3983
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
3984
        wt.commit();
2✔
3985
    }
2✔
3986

1✔
3987
    {
2✔
3988
        WriteTransaction wt(db_1);
2✔
3989
        TableRef table = wt.get_table("class_table name");
2✔
3990
        std::string str_1(binary_sizes[2], 'c');
2✔
3991
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
3992
        std::string str_2(binary_sizes[3], 'd');
2✔
3993
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
3994
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
3995
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
3996
        wt.commit();
2✔
3997
    }
2✔
3998

1✔
3999
    {
2✔
4000
        WriteTransaction wt(db_2);
2✔
4001
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4002
        table->add_column(type_Binary, "column name");
2✔
4003
        std::string str_1(binary_sizes[4], 'e');
2✔
4004
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4005
        std::string str_2(binary_sizes[5], 'f');
2✔
4006
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4007
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4008
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4009
        wt.commit();
2✔
4010
    }
2✔
4011

1✔
4012
    {
2✔
4013
        WriteTransaction wt(db_2);
2✔
4014
        TableRef table = wt.get_table("class_table name");
2✔
4015
        std::string str_1(binary_sizes[6], 'g');
2✔
4016
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4017
        std::string str_2(binary_sizes[7], 'h');
2✔
4018
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4019
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4020
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4021
        wt.commit();
2✔
4022
    }
2✔
4023

1✔
4024
    std::uint_fast64_t downloaded_bytes_1 = 0;
2✔
4025
    std::uint_fast64_t downloadable_bytes_1 = 0;
2✔
4026
    std::uint_fast64_t uploaded_bytes_1 = 0;
2✔
4027
    std::uint_fast64_t uploadable_bytes_1 = 0;
2✔
4028

1✔
4029
    auto progress_handler_1 = [&](std::uint_fast64_t downloaded_bytes, std::uint_fast64_t downloadable_bytes,
2✔
4030
                                  std::uint_fast64_t uploaded_bytes, std::uint_fast64_t uploadable_bytes,
2✔
4031
                                  std::uint_fast64_t, std::uint_fast64_t) {
14✔
4032
        downloaded_bytes_1 = downloaded_bytes;
14✔
4033
        downloadable_bytes_1 = downloadable_bytes;
14✔
4034
        uploaded_bytes_1 = uploaded_bytes;
14✔
4035
        uploadable_bytes_1 = uploadable_bytes;
14✔
4036
    };
14✔
4037

1✔
4038
    std::uint_fast64_t downloaded_bytes_2 = 0;
2✔
4039
    std::uint_fast64_t downloadable_bytes_2 = 0;
2✔
4040
    std::uint_fast64_t uploaded_bytes_2 = 0;
2✔
4041
    std::uint_fast64_t uploadable_bytes_2 = 0;
2✔
4042

1✔
4043
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4044
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes, uint_fast64_t,
2✔
4045
                                  uint_fast64_t) {
10✔
4046
        downloaded_bytes_2 = downloaded_bytes;
10✔
4047
        downloadable_bytes_2 = downloadable_bytes;
10✔
4048
        uploaded_bytes_2 = uploaded_bytes;
10✔
4049
        uploadable_bytes_2 = uploadable_bytes;
10✔
4050
    };
10✔
4051

1✔
4052
    {
2✔
4053
        TEST_DIR(dir);
2✔
4054
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4055
        fixture.start();
2✔
4056

1✔
4057
        {
2✔
4058
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4059
            session_1.set_progress_handler(progress_handler_1);
2✔
4060
            session_1.bind();
2✔
4061
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4062
        }
2✔
4063

1✔
4064
        {
2✔
4065
            Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4066
            session_2.set_progress_handler(progress_handler_2);
2✔
4067
            session_2.bind();
2✔
4068
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4069
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4070
        }
2✔
4071

1✔
4072
        {
2✔
4073
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4074
            session_1.set_progress_handler(progress_handler_1);
2✔
4075
            session_1.bind();
2✔
4076
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4077
        }
2✔
4078
    }
2✔
4079

1✔
4080
    ReadTransaction read_1(db_1);
2✔
4081
    ReadTransaction read_2(db_2);
2✔
4082

1✔
4083
    const Group& group = read_1;
2✔
4084
    CHECK(compare_groups(read_1, read_2));
2✔
4085
    ConstTableRef table = group.get_table("class_table name");
2✔
4086
    CHECK_EQUAL(table->size(), 8);
2✔
4087
    {
2✔
4088
        const Obj obj = *table->begin();
2✔
4089
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
4090
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4091
    }
2✔
4092
    {
2✔
4093
        const Obj obj = *(table->begin() + 7);
2✔
4094
        ChunkedBinaryData cb{obj.get<BinaryData>("column name")};
2✔
4095
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2!
4096
    }
2✔
4097

1✔
4098
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4099
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4100
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4101

1✔
4102
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4103
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4104
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4105

1✔
4106
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4107
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4108
}
2✔
4109

4110

4111
// This test checks that it is possible to create, upload, download, and merge
4112
// changesets larger than 16 MB. This test uses less memory than
4113
// Sync_MergeLargeBinary.
4114
TEST(Sync_MergeLargeBinaryReducedMemory)
4115
{
2✔
4116
    // Two binaries are inserted in a transaction such that the total size
1✔
4117
    // of the changeset exceeds 16MB. A single set_binary operation does not
1✔
4118
    // accept a binary larger than 16MB. Only one changeset is larger than
1✔
4119
    // 16 MB in this test.
1✔
4120
    size_t binary_sizes[] = {
2✔
4121
        static_cast<size_t>(8e6), static_cast<size_t>(9e6),  static_cast<size_t>(7e4), static_cast<size_t>(11e4),
2✔
4122
        static_cast<size_t>(6e4), static_cast<size_t>(12e4), static_cast<size_t>(5e4), static_cast<size_t>(13e4),
2✔
4123
    };
2✔
4124

1✔
4125
    TEST_CLIENT_DB(db_1);
2✔
4126
    TEST_CLIENT_DB(db_2);
2✔
4127

1✔
4128
    {
2✔
4129
        WriteTransaction wt(db_1);
2✔
4130
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4131
        table->add_column(type_Binary, "column name");
2✔
4132
        std::string str_1(binary_sizes[0], 'a');
2✔
4133
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4134
        std::string str_2(binary_sizes[1], 'b');
2✔
4135
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4136
        table->create_object_with_primary_key(1).set("column name", bd_1);
2✔
4137
        table->create_object_with_primary_key(2).set("column name", bd_2);
2✔
4138
        wt.commit();
2✔
4139
    }
2✔
4140

1✔
4141
    {
2✔
4142
        WriteTransaction wt(db_1);
2✔
4143
        TableRef table = wt.get_table("class_table name");
2✔
4144
        std::string str_1(binary_sizes[2], 'c');
2✔
4145
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4146
        std::string str_2(binary_sizes[3], 'd');
2✔
4147
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4148
        table->create_object_with_primary_key(3).set("column name", bd_1);
2✔
4149
        table->create_object_with_primary_key(4).set("column name", bd_2);
2✔
4150
        wt.commit();
2✔
4151
    }
2✔
4152

1✔
4153
    {
2✔
4154
        WriteTransaction wt(db_2);
2✔
4155
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4156
        table->add_column(type_Binary, "column name");
2✔
4157
        std::string str_1(binary_sizes[4], 'e');
2✔
4158
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4159
        std::string str_2(binary_sizes[5], 'f');
2✔
4160
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4161
        table->create_object_with_primary_key(5).set("column name", bd_1);
2✔
4162
        table->create_object_with_primary_key(6).set("column name", bd_2);
2✔
4163
        wt.commit();
2✔
4164
    }
2✔
4165

1✔
4166
    {
2✔
4167
        WriteTransaction wt(db_2);
2✔
4168
        TableRef table = wt.get_table("class_table name");
2✔
4169
        std::string str_1(binary_sizes[6], 'g');
2✔
4170
        BinaryData bd_1(str_1.data(), str_1.size());
2✔
4171
        std::string str_2(binary_sizes[7], 'h');
2✔
4172
        BinaryData bd_2(str_2.data(), str_2.size());
2✔
4173
        table->create_object_with_primary_key(7).set("column name", bd_1);
2✔
4174
        table->create_object_with_primary_key(8).set("column name", bd_2);
2✔
4175
        wt.commit();
2✔
4176
    }
2✔
4177

1✔
4178
    uint_fast64_t downloaded_bytes_1 = 0;
2✔
4179
    uint_fast64_t downloadable_bytes_1 = 0;
2✔
4180
    uint_fast64_t uploaded_bytes_1 = 0;
2✔
4181
    uint_fast64_t uploadable_bytes_1 = 0;
2✔
4182

1✔
4183
    auto progress_handler_1 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4184
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4185
                                  uint_fast64_t /* progress_version */, uint_fast64_t /* snapshot_version */) {
12✔
4186
        downloaded_bytes_1 = downloaded_bytes;
12✔
4187
        downloadable_bytes_1 = downloadable_bytes;
12✔
4188
        uploaded_bytes_1 = uploaded_bytes;
12✔
4189
        uploadable_bytes_1 = uploadable_bytes;
12✔
4190
    };
12✔
4191

1✔
4192
    uint_fast64_t downloaded_bytes_2 = 0;
2✔
4193
    uint_fast64_t downloadable_bytes_2 = 0;
2✔
4194
    uint_fast64_t uploaded_bytes_2 = 0;
2✔
4195
    uint_fast64_t uploadable_bytes_2 = 0;
2✔
4196

1✔
4197
    auto progress_handler_2 = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4198
                                  uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4199
                                  uint_fast64_t /* progress_version */, uint_fast64_t /* snapshot_version */) {
10✔
4200
        downloaded_bytes_2 = downloaded_bytes;
10✔
4201
        downloadable_bytes_2 = downloadable_bytes;
10✔
4202
        uploaded_bytes_2 = uploaded_bytes;
10✔
4203
        uploadable_bytes_2 = uploadable_bytes;
10✔
4204
    };
10✔
4205

1✔
4206
    {
2✔
4207
        TEST_DIR(dir);
2✔
4208
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4209
        fixture.start();
2✔
4210

1✔
4211
        {
2✔
4212
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4213
            session_1.set_progress_handler(progress_handler_1);
2✔
4214
            session_1.bind();
2✔
4215
            session_1.wait_for_upload_complete_or_client_stopped();
2✔
4216
        }
2✔
4217

1✔
4218
        {
2✔
4219
            Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4220
            session_2.set_progress_handler(progress_handler_2);
2✔
4221
            session_2.bind();
2✔
4222
            session_2.wait_for_download_complete_or_client_stopped();
2✔
4223
            session_2.wait_for_upload_complete_or_client_stopped();
2✔
4224
        }
2✔
4225

1✔
4226
        {
2✔
4227
            Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4228
            session_1.set_progress_handler(progress_handler_1);
2✔
4229
            session_1.bind();
2✔
4230
            session_1.wait_for_download_complete_or_client_stopped();
2✔
4231
        }
2✔
4232
    }
2✔
4233

1✔
4234
    ReadTransaction read_1(db_1);
2✔
4235
    ReadTransaction read_2(db_2);
2✔
4236

1✔
4237
    const Group& group = read_1;
2✔
4238
    CHECK(compare_groups(read_1, read_2));
2✔
4239
    ConstTableRef table = group.get_table("class_table name");
2✔
4240
    CHECK_EQUAL(table->size(), 8);
2✔
4241
    {
2✔
4242
        const Obj obj = *table->begin();
2✔
4243
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4244
        CHECK((cb.size() == binary_sizes[0] && cb[0] == 'a') || (cb.size() == binary_sizes[4] && cb[0] == 'e'));
2!
4245
    }
2✔
4246
    {
2✔
4247
        const Obj obj = *(table->begin() + 7);
2✔
4248
        ChunkedBinaryData cb(obj.get<BinaryData>("column name"));
2✔
4249
        CHECK((cb.size() == binary_sizes[3] && cb[0] == 'd') || (cb.size() == binary_sizes[7] && cb[0] == 'h'));
2!
4250
    }
2✔
4251

1✔
4252
    CHECK_EQUAL(downloadable_bytes_1, downloaded_bytes_1);
2✔
4253
    CHECK_EQUAL(uploadable_bytes_1, uploaded_bytes_1);
2✔
4254
    CHECK_NOT_EQUAL(uploaded_bytes_1, 0);
2✔
4255

1✔
4256
    CHECK_EQUAL(downloadable_bytes_2, downloaded_bytes_2);
2✔
4257
    CHECK_EQUAL(uploadable_bytes_2, uploaded_bytes_2);
2✔
4258
    CHECK_NOT_EQUAL(uploaded_bytes_2, 0);
2✔
4259

1✔
4260
    CHECK_EQUAL(uploaded_bytes_1, downloaded_bytes_2);
2✔
4261
    CHECK_NOT_EQUAL(downloaded_bytes_1, 0);
2✔
4262
}
2✔
4263

4264

4265
// This test checks that it is possible to create, upload, download, and merge
4266
// changesets larger than 16MB.
4267
TEST(Sync_MergeLargeChangesets)
4268
{
2✔
4269
    constexpr int number_of_rows = 200;
2✔
4270

1✔
4271
    TEST_CLIENT_DB(db_1);
2✔
4272
    TEST_CLIENT_DB(db_2);
2✔
4273

1✔
4274
    {
2✔
4275
        WriteTransaction wt(db_1);
2✔
4276
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4277
        table->add_column(type_Binary, "column name");
2✔
4278
        table->add_column(type_Int, "integer column");
2✔
4279
        wt.commit();
2✔
4280
    }
2✔
4281

1✔
4282
    {
2✔
4283
        WriteTransaction wt(db_2);
2✔
4284
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4285
        table->add_column(type_Binary, "column name");
2✔
4286
        table->add_column(type_Int, "integer column");
2✔
4287
        wt.commit();
2✔
4288
    }
2✔
4289

1✔
4290
    {
2✔
4291
        WriteTransaction wt(db_1);
2✔
4292
        TableRef table = wt.get_table("class_table name");
2✔
4293
        for (int i = 0; i < number_of_rows; ++i) {
402✔
4294
            table->create_object_with_primary_key(i);
400✔
4295
        }
400✔
4296
        std::string str(100000, 'a');
2✔
4297
        BinaryData bd(str.data(), str.size());
2✔
4298
        for (int row = 0; row < number_of_rows; ++row) {
402✔
4299
            table->get_object(size_t(row)).set("column name", bd);
400✔
4300
            table->get_object(size_t(row)).set("integer column", 2 * row);
400✔
4301
        }
400✔
4302
        wt.commit();
2✔
4303
    }
2✔
4304

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

1✔
4320
    {
2✔
4321
        TEST_DIR(dir);
2✔
4322
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4323

1✔
4324
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4325
        session_1.bind();
2✔
4326
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4327
        session_2.bind();
2✔
4328

1✔
4329
        fixture.start();
2✔
4330

1✔
4331
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4332
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4333
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4334
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4335
    }
2✔
4336

1✔
4337
    ReadTransaction read_1(db_1);
2✔
4338
    ReadTransaction read_2(db_2);
2✔
4339
    const Group& group = read_1;
2✔
4340
    CHECK(compare_groups(read_1, read_2));
2✔
4341
    ConstTableRef table = group.get_table("class_table name");
2✔
4342
    CHECK_EQUAL(table->size(), 2 * number_of_rows);
2✔
4343
}
2✔
4344

4345

4346
TEST(Sync_MergeMultipleChangesets)
4347
{
2✔
4348
    constexpr int number_of_changesets = 100;
2✔
4349
    constexpr int number_of_instructions = 10;
2✔
4350

1✔
4351
    TEST_CLIENT_DB(db_1);
2✔
4352
    TEST_CLIENT_DB(db_2);
2✔
4353

1✔
4354
    std::atomic<int> id = 0;
2✔
4355

1✔
4356
    {
2✔
4357
        WriteTransaction wt(db_1);
2✔
4358
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4359
        table->add_column(type_Int, "integer column");
2✔
4360
        wt.commit();
2✔
4361
    }
2✔
4362

1✔
4363
    {
2✔
4364
        WriteTransaction wt(db_2);
2✔
4365
        TableRef table = wt.get_group().add_table_with_primary_key("class_table name", type_Int, "id");
2✔
4366
        table->add_column(type_Int, "integer column");
2✔
4367
        wt.commit();
2✔
4368
    }
2✔
4369

1✔
4370
    {
2✔
4371
        for (int i = 0; i < number_of_changesets; ++i) {
202✔
4372
            WriteTransaction wt(db_1);
200✔
4373
            TableRef table = wt.get_table("class_table name");
200✔
4374
            for (int j = 0; j < number_of_instructions; ++j) {
2,200✔
4375
                auto obj = table->create_object_with_primary_key(id.fetch_add(1));
2,000✔
4376
                obj.set("integer column", 2 * j);
2,000✔
4377
            }
2,000✔
4378
            wt.commit();
200✔
4379
        }
200✔
4380
    }
2✔
4381

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

1✔
4394
    {
2✔
4395
        TEST_DIR(dir);
2✔
4396
        MultiClientServerFixture fixture(2, 1, dir, test_context);
2✔
4397

1✔
4398

1✔
4399
        // Start server and upload changes of first client.
1✔
4400
        Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4401
        session_1.bind();
2✔
4402
        Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4403
        session_2.bind();
2✔
4404

1✔
4405
        fixture.start_server(0);
2✔
4406
        fixture.start_client(0);
2✔
4407
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
4408
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4409
        session_1.detach();
2✔
4410
        // Stop first client.
1✔
4411
        fixture.stop_client(0);
2✔
4412

1✔
4413
        // Start the second client and upload their changes.
1✔
4414
        // Wait to integrate changes from the first client.
1✔
4415
        fixture.start_client(1);
2✔
4416
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
4417
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4418
    }
2✔
4419

1✔
4420
    ReadTransaction read_1(db_1);
2✔
4421
    ReadTransaction read_2(db_2);
2✔
4422
    const Group& group1 = read_1;
2✔
4423
    const Group& group2 = read_2;
2✔
4424
    ConstTableRef table1 = group1.get_table("class_table name");
2✔
4425
    ConstTableRef table2 = group2.get_table("class_table name");
2✔
4426
    CHECK_EQUAL(table1->size(), number_of_changesets * number_of_instructions);
2✔
4427
    CHECK_EQUAL(table2->size(), 2 * number_of_changesets * number_of_instructions);
2✔
4428
}
2✔
4429

4430

4431
#endif // REALM_PLATFORM_WIN32
4432

4433

4434
TEST(Sync_PingTimesOut)
4435
{
2✔
4436
    bool did_fail = false;
2✔
4437
    {
2✔
4438
        TEST_DIR(dir);
2✔
4439
        TEST_CLIENT_DB(db);
2✔
4440

1✔
4441
        ClientServerFixture::Config config;
2✔
4442
        config.client_ping_period = 0;  // send ping immediately
2✔
4443
        config.client_pong_timeout = 0; // time out immediately
2✔
4444
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4445

1✔
4446
        auto error_handler = [&](Status status, bool) {
2✔
4447
            CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4448
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4449
            did_fail = true;
2✔
4450
            fixture.stop();
2✔
4451
        };
2✔
4452
        fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4453

1✔
4454
        fixture.start();
2✔
4455

1✔
4456
        Session session = fixture.make_bound_session(db);
2✔
4457
        session.wait_for_download_complete_or_client_stopped();
2✔
4458
    }
2✔
4459
    CHECK(did_fail);
2✔
4460
}
2✔
4461

4462

4463
TEST(Sync_ReconnectAfterPingTimeout)
4464
{
2✔
4465
    TEST_DIR(dir);
2✔
4466
    TEST_CLIENT_DB(db);
2✔
4467

1✔
4468
    ClientServerFixture::Config config;
2✔
4469
    config.client_ping_period = 0;  // send ping immediately
2✔
4470
    config.client_pong_timeout = 0; // time out immediately
2✔
4471

1✔
4472
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4473

1✔
4474
    BowlOfStonesSemaphore bowl;
2✔
4475
    auto error_handler = [&](Status status, bool) {
2✔
4476
        if (CHECK_EQUAL(status, ErrorCodes::ConnectionClosed)) {
2✔
4477
            CHECK_EQUAL(status.reason(), "Timed out waiting for PONG response from server");
2✔
4478
            bowl.add_stone();
2✔
4479
        }
2✔
4480
    };
2✔
4481
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4482
    fixture.start();
2✔
4483

1✔
4484
    Session session = fixture.make_bound_session(db, "/test");
2✔
4485
    bowl.get_stone();
2✔
4486
}
2✔
4487

4488

4489
TEST(Sync_UrgentPingIsSent)
4490
{
2✔
4491
    bool did_fail = false;
2✔
4492
    {
2✔
4493
        TEST_DIR(dir);
2✔
4494
        TEST_CLIENT_DB(db);
2✔
4495

1✔
4496
        ClientServerFixture::Config config;
2✔
4497
        config.client_pong_timeout = 0; // urgent pings time out immediately
2✔
4498

1✔
4499
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4500

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

1✔
4509
        fixture.start();
2✔
4510

1✔
4511
        Session session = fixture.make_bound_session(db);
2✔
4512
        session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4513
        session.cancel_reconnect_delay();                       // send an urgent ping
2✔
4514
        session.wait_for_download_complete_or_client_stopped();
2✔
4515
    }
2✔
4516
    CHECK(did_fail);
2✔
4517
}
2✔
4518

4519

4520
TEST(Sync_ServerDiscardDeadConnections)
4521
{
2✔
4522
    TEST_DIR(dir);
2✔
4523
    TEST_CLIENT_DB(db);
2✔
4524

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

1✔
4528
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
4529

1✔
4530
    BowlOfStonesSemaphore bowl;
2✔
4531
    auto error_handler = [&](Status status, bool) {
2✔
4532
        CHECK_EQUAL(status, ErrorCodes::ConnectionClosed);
2✔
4533
        bowl.add_stone();
2✔
4534
    };
2✔
4535
    fixture.set_client_side_error_handler(std::move(error_handler));
2✔
4536
    fixture.start();
2✔
4537

1✔
4538
    Session session = fixture.make_bound_session(db);
2✔
4539
    session.wait_for_download_complete_or_client_stopped(); // ensure connection established
2✔
4540
    fixture.set_server_connection_reaper_timeout(0);        // all connections will now be considered dead
2✔
4541
    bowl.get_stone();
2✔
4542
}
2✔
4543

4544

4545
TEST(Sync_Quadratic_Merge)
4546
{
2✔
4547
    size_t num_instructions_1 = 100;
2✔
4548
    size_t num_instructions_2 = 200;
2✔
4549
    REALM_ASSERT(num_instructions_1 >= 3 && num_instructions_2 >= 3);
2✔
4550

1✔
4551
    TEST_DIR(server_dir);
2✔
4552
    TEST_CLIENT_DB(db_1);
2✔
4553
    TEST_CLIENT_DB(db_2);
2✔
4554

1✔
4555
    // The schema and data is created with
1✔
4556
    // n_operations instructions. The instructions are:
1✔
4557
    // create table
1✔
4558
    // add column
1✔
4559
    // create object
1✔
4560
    // n_operations - 3 add_int instructions.
1✔
4561
    auto create_data = [](DBRef db, size_t n_operations) {
4✔
4562
        WriteTransaction wt(db);
4✔
4563
        TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
4✔
4564
        table->add_column(type_Int, "i");
4✔
4565
        Obj obj = table->create_object_with_primary_key(1);
4✔
4566
        for (size_t i = 0; i < n_operations - 3; ++i)
592✔
4567
            obj.add_int("i", 1);
588✔
4568
        wt.commit();
4✔
4569
    };
4✔
4570

1✔
4571
    create_data(db_1, num_instructions_1);
2✔
4572
    create_data(db_2, num_instructions_2);
2✔
4573

1✔
4574
    int num_clients = 2;
2✔
4575
    int num_servers = 1;
2✔
4576
    MultiClientServerFixture fixture{num_clients, num_servers, server_dir, test_context};
2✔
4577
    fixture.start();
2✔
4578

1✔
4579
    Session session_1 = fixture.make_session(0, 0, db_1, "/test");
2✔
4580
    session_1.bind();
2✔
4581
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4582

1✔
4583
    Session session_2 = fixture.make_session(1, 0, db_2, "/test");
2✔
4584
    session_2.bind();
2✔
4585
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
4586

1✔
4587
    session_1.wait_for_download_complete_or_client_stopped();
2✔
4588
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4589
}
2✔
4590

4591

4592
TEST(Sync_BatchedUploadMessages)
4593
{
2✔
4594
    TEST_DIR(server_dir);
2✔
4595
    TEST_CLIENT_DB(db);
2✔
4596

1✔
4597
    ClientServerFixture fixture(server_dir, test_context);
2✔
4598
    fixture.start();
2✔
4599

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

1✔
4602
    {
2✔
4603
        WriteTransaction wt{db};
2✔
4604
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4605
        tr->add_column(type_Int, "integer column");
2✔
4606
        wt.commit();
2✔
4607
    }
2✔
4608

1✔
4609
    // Create a lot of changesets. We will attempt to check that
1✔
4610
    // they are uploaded in a few upload messages.
1✔
4611
    for (int i = 0; i < 400; ++i) {
802✔
4612
        WriteTransaction wt{db};
800✔
4613
        TableRef tr = wt.get_table("class_foo");
800✔
4614
        tr->create_object_with_primary_key(i).set("integer column", i);
800✔
4615
        wt.commit();
800✔
4616
    }
800✔
4617

1✔
4618
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4619
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4620
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
6✔
4621
        CHECK_GREATER(uploadable_bytes, 1000);
6✔
4622

3✔
4623
        // This is the important check. If the changesets were not batched,
3✔
4624
        // there would be callbacks with partial uploaded_bytes.
3✔
4625
        // With batching, all uploadable_bytes are uploaded in the same message.
3✔
4626
        CHECK(uploaded_bytes == 0 || uploaded_bytes == uploadable_bytes);
6✔
4627
        CHECK_EQUAL(0, downloaded_bytes);
6✔
4628
        CHECK_EQUAL(0, downloadable_bytes);
6✔
4629
        static_cast<void>(progress_version);
6✔
4630
        static_cast<void>(snapshot_version);
6✔
4631
    };
6✔
4632

1✔
4633
    session.set_progress_handler(progress_handler);
2✔
4634
    session.bind();
2✔
4635
    session.wait_for_upload_complete_or_client_stopped();
2✔
4636
}
2✔
4637

4638

4639
TEST(Sync_UploadLogCompactionEnabled)
4640
{
2✔
4641
    TEST_DIR(server_dir);
2✔
4642
    TEST_CLIENT_DB(db_1);
2✔
4643
    TEST_CLIENT_DB(db_2);
2✔
4644

1✔
4645
    ClientServerFixture::Config config;
2✔
4646
    config.disable_upload_compaction = false;
2✔
4647
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
4648
    fixture.start();
2✔
4649

1✔
4650
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
4651
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
4652

1✔
4653
    // Create a changeset with lots of overwrites of the
1✔
4654
    // same fields.
1✔
4655
    {
2✔
4656
        WriteTransaction wt{db_1};
2✔
4657
        TableRef tr = wt.get_group().add_table_with_primary_key("class_foo", type_Int, "id");
2✔
4658
        tr->add_column(type_Int, "integer column");
2✔
4659
        Obj obj0 = tr->create_object_with_primary_key(0);
2✔
4660
        Obj obj1 = tr->create_object_with_primary_key(1);
2✔
4661
        for (int i = 0; i < 10000; ++i) {
20,002✔
4662
            obj0.set("integer column", i);
20,000✔
4663
            obj1.set("integer column", 2 * i);
20,000✔
4664
        }
20,000✔
4665
        wt.commit();
2✔
4666
    }
2✔
4667

1✔
4668
    session_1.bind();
2✔
4669
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4670

1✔
4671
    auto progress_handler = [&](uint_fast64_t downloaded_bytes, uint_fast64_t downloadable_bytes,
2✔
4672
                                uint_fast64_t uploaded_bytes, uint_fast64_t uploadable_bytes,
2✔
4673
                                uint_fast64_t progress_version, uint_fast64_t snapshot_version) {
4✔
4674
        CHECK_EQUAL(downloaded_bytes, downloadable_bytes);
4✔
4675
        CHECK_EQUAL(0, uploaded_bytes);
4✔
4676
        CHECK_EQUAL(0, uploadable_bytes);
4✔
4677
        static_cast<void>(snapshot_version);
4✔
4678
        if (progress_version > 0)
4✔
4679
            CHECK_NOT_EQUAL(downloadable_bytes, 0);
3✔
4680
    };
4✔
4681

1✔
4682
    session_2.set_progress_handler(progress_handler);
2✔
4683

1✔
4684
    session_2.bind();
2✔
4685

1✔
4686
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4687

1✔
4688
    {
2✔
4689
        ReadTransaction rt_1(db_1);
2✔
4690
        ReadTransaction rt_2(db_2);
2✔
4691
        CHECK(compare_groups(rt_1, rt_2));
2✔
4692
        ConstTableRef table = rt_1.get_table("class_foo");
2✔
4693
        CHECK_EQUAL(2, table->size());
2✔
4694
        CHECK_EQUAL(9999, table->begin()->get<Int>("integer column"));
2✔
4695
        CHECK_EQUAL(19998, table->get_object(1).get<Int>("integer column"));
2✔
4696
    }
2✔
4697
}
2✔
4698

4699

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

1✔
4706
    ClientServerFixture::Config config;
2✔
4707
    config.disable_upload_compaction = true;
2✔
4708
    config.disable_history_compaction = true;
2✔
4709
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
4710
    fixture.start();
2✔
4711

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

1✔
4727
    Session session_1 = fixture.make_bound_session(db_1, "/test");
2✔
4728
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4729

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

1✔
4741
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
4742
    session_2.set_progress_handler(progress_handler);
2✔
4743
    session_2.bind();
2✔
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));
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_ReadOnlyClientSideHistoryTrim)
4759
{
2✔
4760
    TEST_DIR(dir);
2✔
4761
    TEST_CLIENT_DB(db_1);
2✔
4762
    TEST_CLIENT_DB(db_2);
2✔
4763

1✔
4764
    ClientServerFixture fixture{dir, test_context};
2✔
4765
    fixture.start();
2✔
4766

1✔
4767
    ColKey col_ndx_blob_data;
2✔
4768
    {
2✔
4769
        WriteTransaction wt{db_1};
2✔
4770
        TableRef blobs = wt.get_group().add_table_with_primary_key("class_Blob", type_Int, "id");
2✔
4771
        col_ndx_blob_data = blobs->add_column(type_Binary, "data");
2✔
4772
        blobs->create_object_with_primary_key(1);
2✔
4773
        wt.commit();
2✔
4774
    }
2✔
4775

1✔
4776
    Session session_1 = fixture.make_bound_session(db_1, "/foo");
2✔
4777
    Session session_2 = fixture.make_bound_session(db_2, "/foo");
2✔
4778

1✔
4779
    std::string blob(0x4000, '\0');
2✔
4780
    for (long i = 0; i < 1024; ++i) {
2,050✔
4781
        {
2,048✔
4782
            WriteTransaction wt{db_1};
2,048✔
4783
            TableRef blobs = wt.get_table("class_Blob");
2,048✔
4784
            blobs->begin()->set(col_ndx_blob_data, BinaryData{blob});
2,048✔
4785
            version_type new_version = wt.commit();
2,048✔
4786
            session_1.nonsync_transact_notify(new_version);
2,048✔
4787
        }
2,048✔
4788
        session_1.wait_for_upload_complete_or_client_stopped();
2,048✔
4789
        session_2.wait_for_download_complete_or_client_stopped();
2,048✔
4790
    }
2,048✔
4791

1✔
4792
    // Check that the file size is less than 4 MiB. If it is, then the history
1✔
4793
    // must have been trimmed, as the combined size of all the blobs is at least
1✔
4794
    // 16 MiB.
1✔
4795
    CHECK_LESS(util::File{db_1_path}.get_size(), 0x400000);
2✔
4796
}
2✔
4797

4798
// This test creates two objects in a target table and a link list
4799
// in a source table. The first target object is inserted in the link list,
4800
// and later the link is set to the second target object.
4801
// Both the target objects are deleted afterwards. The tests verifies that
4802
// sync works with log compaction turned on.
4803
TEST(Sync_ContainerInsertAndSetLogCompaction)
4804
{
2✔
4805
    TEST_DIR(dir);
2✔
4806
    TEST_CLIENT_DB(db_1);
2✔
4807
    TEST_CLIENT_DB(db_2);
2✔
4808
    ClientServerFixture fixture(dir, test_context);
2✔
4809
    fixture.start();
2✔
4810

1✔
4811
    {
2✔
4812
        WriteTransaction wt{db_1};
2✔
4813

1✔
4814
        TableRef table_target = wt.get_group().add_table_with_primary_key("class_target", type_Int, "id");
2✔
4815
        ColKey col_ndx = table_target->add_column(type_Int, "value");
2✔
4816
        auto k0 = table_target->create_object_with_primary_key(1).set(col_ndx, 123).get_key();
2✔
4817
        auto k1 = table_target->create_object_with_primary_key(2).set(col_ndx, 456).get_key();
2✔
4818

1✔
4819
        TableRef table_source = wt.get_group().add_table_with_primary_key("class_source", type_Int, "id");
2✔
4820
        col_ndx = table_source->add_column_list(*table_target, "target_link");
2✔
4821
        Obj obj = table_source->create_object_with_primary_key(1);
2✔
4822
        LnkLst ll = obj.get_linklist(col_ndx);
2✔
4823
        ll.insert(0, k0);
2✔
4824
        ll.set(0, k1);
2✔
4825

1✔
4826
        table_target->remove_object(k1);
2✔
4827
        table_target->remove_object(k0);
2✔
4828

1✔
4829
        wt.commit();
2✔
4830
    }
2✔
4831

1✔
4832
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4833
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4834

1✔
4835
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4836
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4837

1✔
4838
    {
2✔
4839
        ReadTransaction rt_1(db_1);
2✔
4840
        ReadTransaction rt_2(db_2);
2✔
4841
        CHECK(compare_groups(rt_1, rt_2));
2✔
4842
    }
2✔
4843
}
2✔
4844

4845

4846
TEST(Sync_MultipleContainerColumns)
4847
{
2✔
4848
    TEST_DIR(dir);
2✔
4849
    TEST_CLIENT_DB(db_1);
2✔
4850
    TEST_CLIENT_DB(db_2);
2✔
4851
    ClientServerFixture fixture(dir, test_context);
2✔
4852
    fixture.start();
2✔
4853

1✔
4854
    {
2✔
4855
        WriteTransaction wt{db_1};
2✔
4856

1✔
4857
        TableRef table = wt.get_group().add_table_with_primary_key("class_Table", type_Int, "id");
2✔
4858
        table->add_column_list(type_String, "array1");
2✔
4859
        table->add_column_list(type_String, "array2");
2✔
4860

1✔
4861
        Obj row = table->create_object_with_primary_key(1);
2✔
4862
        {
2✔
4863
            Lst<StringData> array1 = row.get_list<StringData>("array1");
2✔
4864
            array1.clear();
2✔
4865
            array1.add("Hello");
2✔
4866
        }
2✔
4867
        {
2✔
4868
            Lst<StringData> array2 = row.get_list<StringData>("array2");
2✔
4869
            array2.clear();
2✔
4870
            array2.add("World");
2✔
4871
        }
2✔
4872

1✔
4873
        wt.commit();
2✔
4874
    }
2✔
4875

1✔
4876
    Session session_1 = fixture.make_bound_session(db_1);
2✔
4877
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
4878

1✔
4879
    Session session_2 = fixture.make_bound_session(db_2);
2✔
4880
    session_2.wait_for_download_complete_or_client_stopped();
2✔
4881

1✔
4882
    {
2✔
4883
        ReadTransaction rt_1(db_1);
2✔
4884
        ReadTransaction rt_2(db_2);
2✔
4885
        CHECK(compare_groups(rt_1, rt_2));
2✔
4886

1✔
4887
        ConstTableRef table = rt_1.get_table("class_Table");
2✔
4888
        const Obj row = *table->begin();
2✔
4889
        auto array1 = row.get_list<StringData>("array1");
2✔
4890
        auto array2 = row.get_list<StringData>("array2");
2✔
4891
        CHECK_EQUAL(array1.size(), 1);
2✔
4892
        CHECK_EQUAL(array2.size(), 1);
2✔
4893
        CHECK_EQUAL(array1.get(0), "Hello");
2✔
4894
        CHECK_EQUAL(array2.get(0), "World");
2✔
4895
    }
2✔
4896
}
2✔
4897

4898

4899
TEST(Sync_ConnectionStateChange)
4900
{
2✔
4901
    TEST_DIR(dir);
2✔
4902
    TEST_CLIENT_DB(db_1);
2✔
4903
    TEST_CLIENT_DB(db_2);
2✔
4904

1✔
4905
    std::vector<ConnectionState> states_1, states_2;
2✔
4906
    {
2✔
4907
        ClientServerFixture fixture(dir, test_context);
2✔
4908
        fixture.start();
2✔
4909

1✔
4910
        BowlOfStonesSemaphore bowl_1, bowl_2;
2✔
4911
        auto listener_1 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4912
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4913
            states_1.push_back(state);
6✔
4914
            if (state == ConnectionState::disconnected)
6✔
4915
                bowl_1.add_stone();
2✔
4916
        };
6✔
4917
        auto listener_2 = [&](ConnectionState state, util::Optional<ErrorInfo> error_info) {
6✔
4918
            CHECK_EQUAL(state == ConnectionState::disconnected, bool(error_info));
6✔
4919
            states_2.push_back(state);
6✔
4920
            if (state == ConnectionState::disconnected)
6✔
4921
                bowl_2.add_stone();
2✔
4922
        };
6✔
4923

1✔
4924
        Session session_1 = fixture.make_session(db_1, "/test");
2✔
4925
        session_1.set_connection_state_change_listener(listener_1);
2✔
4926
        session_1.bind();
2✔
4927
        session_1.wait_for_download_complete_or_client_stopped();
2✔
4928

1✔
4929
        Session session_2 = fixture.make_session(db_2, "/test");
2✔
4930
        session_2.set_connection_state_change_listener(listener_2);
2✔
4931
        session_2.bind();
2✔
4932
        session_2.wait_for_download_complete_or_client_stopped();
2✔
4933

1✔
4934
        fixture.close_server_side_connections();
2✔
4935
        bowl_1.get_stone();
2✔
4936
        bowl_2.get_stone();
2✔
4937
    }
2✔
4938
    std::vector<ConnectionState> reference{ConnectionState::connecting, ConnectionState::connected,
2✔
4939
                                           ConnectionState::disconnected};
2✔
4940
    CHECK(states_1 == reference);
2✔
4941
    CHECK(states_2 == reference);
2✔
4942
}
2✔
4943

4944

4945
TEST(Sync_ClientErrorHandler)
4946
{
2✔
4947
    TEST_DIR(dir);
2✔
4948
    TEST_CLIENT_DB(db);
2✔
4949
    ClientServerFixture fixture(dir, test_context);
2✔
4950
    fixture.start();
2✔
4951

1✔
4952
    BowlOfStonesSemaphore bowl;
2✔
4953
    auto handler = [&](const SessionErrorInfo&) {
2✔
4954
        bowl.add_stone();
2✔
4955
    };
2✔
4956

1✔
4957
    Session session = fixture.make_session(db, "/test");
2✔
4958
    session.set_error_handler(std::move(handler));
2✔
4959
    session.bind();
2✔
4960
    session.wait_for_download_complete_or_client_stopped();
2✔
4961

1✔
4962
    fixture.close_server_side_connections();
2✔
4963
    bowl.get_stone();
2✔
4964
}
2✔
4965

4966

4967
TEST(Sync_VerifyServerHistoryAfterLargeUpload)
4968
{
2✔
4969
    TEST_DIR(server_dir);
2✔
4970
    TEST_CLIENT_DB(db);
2✔
4971

1✔
4972
    ClientServerFixture fixture{server_dir, test_context};
2✔
4973
    fixture.start();
2✔
4974

1✔
4975
    {
2✔
4976
        auto wt = db->start_write();
2✔
4977
        auto table = wt->add_table_with_primary_key("class_table", type_Int, "id");
2✔
4978
        ColKey col = table->add_column(type_Binary, "data");
2✔
4979

1✔
4980
        // Create enough data that our changeset cannot be stored contiguously
1✔
4981
        // by BinaryColumn (> 16MB).
1✔
4982
        std::size_t data_size = 8 * 1024 * 1024;
2✔
4983
        std::string data(data_size, '\0');
2✔
4984
        for (int i = 0; i < 8; ++i) {
18✔
4985
            table->create_object_with_primary_key(i).set(col, BinaryData{data.data(), data.size()});
16✔
4986
        }
16✔
4987

1✔
4988
        wt->commit();
2✔
4989

1✔
4990
        Session session = fixture.make_session(db, "/test");
2✔
4991
        session.bind();
2✔
4992
        session.wait_for_upload_complete_or_client_stopped();
2✔
4993
    }
2✔
4994

1✔
4995
    {
2✔
4996
        std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
4997
        TestServerHistoryContext context;
2✔
4998
        _impl::ServerHistory history{context};
2✔
4999
        DBRef db = DB::create(history, server_path);
2✔
5000
        {
2✔
5001
            ReadTransaction rt{db};
2✔
5002
            rt.get_group().verify();
2✔
5003
        }
2✔
5004
    }
2✔
5005
}
2✔
5006

5007

5008
TEST(Sync_ServerSideModify_Randomize)
5009
{
2✔
5010
    int num_server_side_transacts = 1200;
2✔
5011
    int num_client_side_transacts = 1200;
2✔
5012

1✔
5013
    TEST_DIR(server_dir);
2✔
5014
    TEST_CLIENT_DB(db_2);
2✔
5015

1✔
5016
    ClientServerFixture::Config config;
2✔
5017
    ClientServerFixture fixture{server_dir, test_context, std::move(config)};
2✔
5018
    fixture.start();
2✔
5019

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

1✔
5022
    std::string server_path = fixture.map_virtual_to_real_path("/test");
2✔
5023
    TestServerHistoryContext context;
2✔
5024
    _impl::ServerHistory history_1{context};
2✔
5025
    DBRef db_1 = DB::create(history_1, server_path);
2✔
5026

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

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

1✔
5067
    ThreadWrapper server_program_thread;
2✔
5068
    server_program_thread.start(std::move(server_side_program));
2✔
5069
    client_side_program();
2✔
5070
    CHECK(!server_program_thread.join());
2✔
5071

1✔
5072
    session.wait_for_upload_complete_or_client_stopped();
2✔
5073
    session.wait_for_download_complete_or_client_stopped();
2✔
5074

1✔
5075
    ReadTransaction rt_1{db_1};
2✔
5076
    ReadTransaction rt_2{db_2};
2✔
5077
    CHECK(compare_groups(rt_1, rt_2, *(test_context.logger)));
2✔
5078
}
2✔
5079

5080

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

5092
    const char* server_address[] = {
×
5093
        "morten-krogh.us1.cloud.realm.io",
×
5094
        "fantastic-cotton-shoes.us1.cloud.realm.io",
×
5095
        "www.realm.io",
×
5096
        "www.yahoo.com",
×
5097
        "www.nytimes.com",
×
5098
        "www.ibm.com",
×
5099
        "www.ssllabs.com",
×
5100
    };
×
5101

5102
    size_t num_servers = sizeof(server_address) / sizeof(server_address[0]);
×
5103

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

5106
    for (size_t i = 0; i < num_servers; ++i) {
×
5107
        Client::Config client_config;
×
5108
        client_config.logger = client_logger;
×
5109
        client_config.reconnect_mode = ReconnectMode::testing;
×
5110
        Client client(client_config);
×
5111

5112
        Session::Config session_config;
×
5113
        session_config.server_address = server_address[i];
×
5114
        session_config.server_port = 443;
×
5115
        session_config.realm_identifier = "/anything";
×
5116
        session_config.protocol_envelope = ProtocolEnvelope::realms;
×
5117

5118
        // Invalid token for the cloud.
5119
        session_config.signed_user_token = g_signed_test_user_token;
×
5120

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

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

5134
        session.set_connection_state_change_listener(listener);
×
5135
        session.bind();
×
5136

5137
        session.wait_for_download_complete_or_client_stopped();
×
5138
    }
×
5139
}
×
5140

5141

5142
// Testing the custom authorization header name.  The sync protocol does not
5143
// currently use the HTTP Authorization header, so the test is to watch the
5144
// logs and see that the client use the right header name. Proxies and the sync
5145
// server HTTP api use the Authorization header.
5146
TEST(Sync_AuthorizationHeaderName)
5147
{
2✔
5148
    TEST_DIR(dir);
2✔
5149
    TEST_CLIENT_DB(db);
2✔
5150

1✔
5151
    const char* authorization_header_name = "X-Alternative-Name";
2✔
5152
    ClientServerFixture::Config config;
2✔
5153
    config.authorization_header_name = authorization_header_name;
2✔
5154
    ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5155
    fixture.start();
2✔
5156

1✔
5157
    Session::Config session_config;
2✔
5158
    session_config.authorization_header_name = authorization_header_name;
2✔
5159

1✔
5160
    std::map<std::string, std::string> custom_http_headers;
2✔
5161
    custom_http_headers["Header-Name-1"] = "Header-Value-1";
2✔
5162
    custom_http_headers["Header-Name-2"] = "Header-Value-2";
2✔
5163
    session_config.custom_http_headers = std::move(custom_http_headers);
2✔
5164
    Session session = fixture.make_session(db, "/test", std::move(session_config));
2✔
5165
    session.bind();
2✔
5166

1✔
5167
    session.wait_for_download_complete_or_client_stopped();
2✔
5168
}
2✔
5169

5170

5171
TEST(Sync_BadChangeset)
5172
{
2✔
5173
    TEST_DIR(dir);
2✔
5174
    TEST_CLIENT_DB(db);
2✔
5175

1✔
5176
    bool did_fail = false;
2✔
5177
    {
2✔
5178
        ClientServerFixture::Config config;
2✔
5179
        config.disable_upload_compaction = true;
2✔
5180
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5181
        fixture.start();
2✔
5182

1✔
5183
        {
2✔
5184
            Session session = fixture.make_bound_session(db);
2✔
5185
            session.wait_for_download_complete_or_client_stopped();
2✔
5186
        }
2✔
5187

1✔
5188
        {
2✔
5189
            WriteTransaction wt(db);
2✔
5190
            TableRef table = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5191
            table->add_column(type_Int, "i");
2✔
5192
            table->create_object_with_primary_key(5).set_all(123);
2✔
5193
            const ChangesetEncoder::Buffer& buffer = get_replication(db).get_instruction_encoder().buffer();
2✔
5194
            char bad_instruction = 0x3e;
2✔
5195
            const_cast<ChangesetEncoder::Buffer&>(buffer).append(&bad_instruction, 1);
2✔
5196
            wt.commit();
2✔
5197
        }
2✔
5198

1✔
5199
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>& error_info) {
6✔
5200
            if (state != ConnectionState::disconnected)
6✔
5201
                return;
4✔
5202
            REALM_ASSERT(error_info);
2✔
5203
            CHECK_EQUAL(error_info->status, ErrorCodes::BadChangeset);
2✔
5204
            CHECK(error_info->is_fatal);
2✔
5205
            did_fail = true;
2✔
5206
            fixture.stop();
2✔
5207
        };
2✔
5208

1✔
5209
        Session session = fixture.make_session(db, "/test");
2✔
5210
        session.set_connection_state_change_listener(listener);
2✔
5211
        session.bind();
2✔
5212

1✔
5213
        session.wait_for_upload_complete_or_client_stopped();
2✔
5214
        session.wait_for_download_complete_or_client_stopped();
2✔
5215
    }
2✔
5216
    CHECK(did_fail);
2✔
5217
}
2✔
5218

5219

5220
TEST(Sync_GoodChangeset_AccentCharacterInFieldName)
5221
{
2✔
5222
    TEST_DIR(dir);
2✔
5223
    TEST_CLIENT_DB(db);
2✔
5224

1✔
5225
    bool did_fail = false;
2✔
5226
    {
2✔
5227
        ClientServerFixture::Config config;
2✔
5228
        config.disable_upload_compaction = true;
2✔
5229
        ClientServerFixture fixture(dir, test_context, std::move(config));
2✔
5230
        fixture.start();
2✔
5231

1✔
5232
        {
2✔
5233
            Session session = fixture.make_bound_session(db);
2✔
5234
        }
2✔
5235

1✔
5236
        {
2✔
5237
            WriteTransaction wt(db);
2✔
5238
            TableRef table = wt.get_group().add_table_with_primary_key("class_table", type_Int, "id");
2✔
5239
            table->add_column(type_Int, "prógram");
2✔
5240
            table->add_column(type_Int, "program");
2✔
5241
            auto obj = table->create_object_with_primary_key(1);
2✔
5242
            obj.add_int("program", 42);
2✔
5243
            wt.commit();
2✔
5244
        }
2✔
5245

1✔
5246
        auto listener = [&](ConnectionState state, const util::Optional<ErrorInfo>) {
4✔
5247
            if (state != ConnectionState::disconnected)
4✔
5248
                return;
4✔
5249
            did_fail = true;
×
5250
            fixture.stop();
×
5251
        };
×
5252

1✔
5253
        Session session = fixture.make_session(db, "/test");
2✔
5254
        session.set_connection_state_change_listener(listener);
2✔
5255
        session.bind();
2✔
5256

1✔
5257
        session.wait_for_upload_complete_or_client_stopped();
2✔
5258
    }
2✔
5259
    CHECK_NOT(did_fail);
2✔
5260
}
2✔
5261

5262

5263
namespace issue2104 {
5264

5265
class ServerHistoryContext : public _impl::ServerHistory::Context {
5266
public:
5267
    ServerHistoryContext()
5268
        : m_transformer{make_transformer()}
5269
    {
×
5270
    }
×
5271

5272
    std::mt19937_64& server_history_get_random() noexcept override
5273
    {
×
5274
        return m_random;
×
5275
    }
×
5276

5277
    sync::Transformer& get_transformer() override
5278
    {
×
5279
        return *m_transformer;
×
5280
    }
×
5281

5282
    util::Buffer<char>& get_transform_buffer() override
5283
    {
×
5284
        return m_transform_buffer;
×
5285
    }
×
5286

5287
private:
5288
    std::mt19937_64 m_random;
5289
    std::unique_ptr<sync::Transformer> m_transformer;
5290
    util::Buffer<char> m_transform_buffer;
5291
};
5292

5293
} // namespace issue2104
5294

5295
// This test reproduces a slow merge seen in issue 2104.
5296
// The test uses a user supplied Realm and a changeset
5297
// from a client.
5298
// The test uses a user supplied Realm that is very large
5299
// and not kept in the repo. The realm has checksum 3693867489.
5300
//
5301
// This test might be modified to avoid having a large Realm
5302
// (96 MB uncompressed) in the repo.
5303
TEST_IF(Sync_Issue2104, false)
5304
{
×
5305
    TEST_DIR(dir);
×
5306

5307
    // Save a snapshot of the server Realm file.
5308
    std::string realm_path = "issue_2104_server.realm";
×
5309
    std::string realm_path_copy = util::File::resolve("issue_2104.realm", dir);
×
5310
    util::File::copy(realm_path, realm_path_copy);
×
5311

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

5457
    std::vector<char> changeset_vec;
×
5458
    {
×
5459
        std::istringstream in{changeset_hex};
×
5460
        int n;
×
5461
        in >> std::hex >> n;
×
5462
        while (in) {
×
5463
            REALM_ASSERT(n >= 0 && n <= 255);
×
5464
            changeset_vec.push_back(n);
×
5465
            in >> std::hex >> n;
×
5466
        }
×
5467
    }
×
5468

5469
    BinaryData changeset_bin{changeset_vec.data(), changeset_vec.size()};
×
5470

5471
    file_ident_type client_file_ident = 51;
×
5472
    timestamp_type origin_timestamp = 103573722140;
×
5473
    file_ident_type origin_file_ident = 0;
×
5474
    version_type client_version = 2;
×
5475
    version_type last_integrated_server_version = 0;
×
5476
    UploadCursor upload_cursor{client_version, last_integrated_server_version};
×
5477

5478
    _impl::ServerHistory::IntegratableChangeset integratable_changeset{
×
5479
        client_file_ident, origin_timestamp, origin_file_ident, upload_cursor, changeset_bin};
×
5480

5481
    _impl::ServerHistory::IntegratableChangesets integratable_changesets;
×
5482
    integratable_changesets[client_file_ident].changesets.push_back(integratable_changeset);
×
5483

5484
    issue2104::ServerHistoryContext history_context;
×
5485
    _impl::ServerHistory history{history_context};
×
5486
    DBRef db = DB::create(history, realm_path_copy);
×
5487

5488
    VersionInfo version_info;
×
5489
    bool backup_whole_realm;
×
5490
    _impl::ServerHistory::IntegrationResult result;
×
5491
    history.integrate_client_changesets(integratable_changesets, version_info, backup_whole_realm, result,
×
5492
                                        *(test_context.logger));
×
5493
}
×
5494

5495

5496
TEST(Sync_RunServerWithoutPublicKey)
5497
{
2✔
5498
    TEST_CLIENT_DB(db);
2✔
5499
    TEST_DIR(server_dir);
2✔
5500
    ClientServerFixture::Config config;
2✔
5501
    config.server_public_key_path = {};
2✔
5502
    ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5503
    fixture.start();
2✔
5504

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

1✔
5512
    // Server must also accept a signed token when a public key is not passed to
1✔
5513
    // it
1✔
5514
    {
2✔
5515
        Session session = fixture.make_bound_session(db, "/test");
2✔
5516
        session.wait_for_download_complete_or_client_stopped();
2✔
5517
    }
2✔
5518
}
2✔
5519

5520

5521
TEST(Sync_ServerSideEncryption)
5522
{
2✔
5523
    TEST_CLIENT_DB(db);
2✔
5524
    {
2✔
5525
        WriteTransaction wt(db);
2✔
5526
        wt.get_group().add_table_with_primary_key("class_Test", type_Int, "id");
2✔
5527
        wt.commit();
2✔
5528
    }
2✔
5529

1✔
5530
    TEST_DIR(server_dir);
2✔
5531
    bool always_encrypt = true;
2✔
5532
    std::string server_path;
2✔
5533
    {
2✔
5534
        ClientServerFixture::Config config;
2✔
5535
        config.server_encryption_key = crypt_key_2(always_encrypt);
2✔
5536
        ClientServerFixture fixture(server_dir, test_context, std::move(config));
2✔
5537
        fixture.start();
2✔
5538

1✔
5539
        Session session = fixture.make_bound_session(db, "/test");
2✔
5540
        session.wait_for_upload_complete_or_client_stopped();
2✔
5541

1✔
5542
        server_path = fixture.map_virtual_to_real_path("/test");
2✔
5543
    }
2✔
5544

1✔
5545
    const char* encryption_key = crypt_key(always_encrypt);
2✔
5546
    Group group{server_path, encryption_key};
2✔
5547
    CHECK(group.has_table("class_Test"));
2✔
5548
}
2✔
5549

5550

5551
// This test calls row_for_object_id() for various object ids and tests that
5552
// the right value is returned including that no assertions are hit.
5553
TEST(Sync_RowForGlobalKey)
5554
{
2✔
5555
    TEST_CLIENT_DB(db);
2✔
5556

1✔
5557
    {
2✔
5558
        WriteTransaction wt(db);
2✔
5559
        TableRef table = wt.add_table("class_foo");
2✔
5560
        table->add_column(type_Int, "i");
2✔
5561
        wt.commit();
2✔
5562
    }
2✔
5563

1✔
5564
    // Check that various object_ids are not in the table.
1✔
5565
    {
2✔
5566
        ReadTransaction rt(db);
2✔
5567
        ConstTableRef table = rt.get_table("class_foo");
2✔
5568
        CHECK(table);
2✔
5569

1✔
5570
        // Default constructed GlobalKey
1✔
5571
        {
2✔
5572
            GlobalKey object_id;
2✔
5573
            auto row_ndx = table->get_objkey(object_id);
2✔
5574
            CHECK_NOT(row_ndx);
2✔
5575
        }
2✔
5576

1✔
5577
        // GlobalKey with small lo and hi values
1✔
5578
        {
2✔
5579
            GlobalKey object_id{12, 24};
2✔
5580
            auto row_ndx = table->get_objkey(object_id);
2✔
5581
            CHECK_NOT(row_ndx);
2✔
5582
        }
2✔
5583

1✔
5584
        // GlobalKey with lo and hi values past the 32 bit limit.
1✔
5585
        {
2✔
5586
            GlobalKey object_id{uint_fast64_t(1) << 50, uint_fast64_t(1) << 52};
2✔
5587
            auto row_ndx = table->get_objkey(object_id);
2✔
5588
            CHECK_NOT(row_ndx);
2✔
5589
        }
2✔
5590
    }
2✔
5591
}
2✔
5592

5593

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

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

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

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

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

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

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

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

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

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

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

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

1✔
5642
        table_target->get_object(1).remove();
2✔
5643
        table_target->get_object(0).remove();
2✔
5644

1✔
5645
        table_source->get_object(0).remove();
2✔
5646
        wt.commit();
2✔
5647
    }
2✔
5648

1✔
5649
    {
2✔
5650
        WriteTransaction wt{db_2};
2✔
5651

1✔
5652
        TableRef table_source = wt.get_table("class_source");
2✔
5653
        TableRef table_target = wt.get_table("class_target");
2✔
5654
        auto col_key = table_source->get_column_key("target_link");
2✔
5655

1✔
5656
        CHECK_EQUAL(table_source->size(), 1);
2✔
5657
        CHECK_EQUAL(table_target->size(), 2);
2✔
5658

1✔
5659
        auto k0 = table_target->begin()->get_key();
2✔
5660

1✔
5661
        auto ll = table_source->get_object(0).get_linklist_ptr(col_key);
2✔
5662
        ll->add(k0);
2✔
5663
        wt.commit();
2✔
5664
    }
2✔
5665

1✔
5666
    {
2✔
5667
        Session session_1 = fixture.make_bound_session(db_1);
2✔
5668
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
5669
    }
2✔
5670

1✔
5671
    {
2✔
5672
        Session session_2 = fixture.make_bound_session(db_2);
2✔
5673
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
5674
        session_2.wait_for_download_complete_or_client_stopped();
2✔
5675
    }
2✔
5676

1✔
5677
    {
2✔
5678
        ReadTransaction rt{db_2};
2✔
5679

1✔
5680
        ConstTableRef table_source = rt.get_group().get_table("class_source");
2✔
5681
        ConstTableRef table_target = rt.get_group().get_table("class_target");
2✔
5682

1✔
5683
        CHECK_EQUAL(table_source->size(), 0);
2✔
5684
        CHECK_EQUAL(table_target->size(), 0);
2✔
5685
    }
2✔
5686
}
2✔
5687

5688

5689
// This test could trigger the assertion that the row_for_object_id cache is
5690
// valid before the cache was properly invalidated in the case of a short
5691
// circuited sync replicator.
5692
TEST(Sync_CreateObjects_EraseObjects)
5693
{
2✔
5694
    TEST_DIR(dir);
2✔
5695
    TEST_CLIENT_DB(db_1);
2✔
5696
    TEST_CLIENT_DB(db_2);
2✔
5697
    ClientServerFixture fixture(dir, test_context);
2✔
5698
    fixture.start();
2✔
5699

1✔
5700
    Session session_1 = fixture.make_bound_session(db_1);
2✔
5701
    Session session_2 = fixture.make_bound_session(db_2);
2✔
5702

1✔
5703
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& wt) {
2✔
5704
        TableRef table = wt.get_group().add_table_with_primary_key("class_persons", type_Int, "id");
2✔
5705
        table->create_object_with_primary_key(1);
2✔
5706
        table->create_object_with_primary_key(2);
2✔
5707
    });
2✔
5708
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5709
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5710

1✔
5711
    write_transaction_notifying_session(db_1, session_1, [&](WriteTransaction& wt) {
2✔
5712
        TableRef table = wt.get_table("class_persons");
2✔
5713
        CHECK_EQUAL(table->size(), 2);
2✔
5714
        table->get_object(0).remove();
2✔
5715
        table->get_object(0).remove();
2✔
5716
    });
2✔
5717
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5718
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5719
}
2✔
5720

5721

5722
TEST(Sync_CreateDeleteCreateTableWithPrimaryKey)
5723
{
2✔
5724
    TEST_DIR(dir);
2✔
5725
    TEST_CLIENT_DB(db);
2✔
5726
    ClientServerFixture fixture(dir, test_context);
2✔
5727
    fixture.start();
2✔
5728

1✔
5729
    Session session = fixture.make_bound_session(db);
2✔
5730

1✔
5731
    write_transaction_notifying_session(db, session, [](WriteTransaction& wt) {
2✔
5732
        TableRef table = wt.get_group().add_table_with_primary_key("class_t", type_Int, "pk");
2✔
5733
        wt.get_group().remove_table(table->get_key());
2✔
5734
        table = wt.get_group().add_table_with_primary_key("class_t", type_String, "pk");
2✔
5735
    });
2✔
5736
    session.wait_for_upload_complete_or_client_stopped();
2✔
5737
    session.wait_for_download_complete_or_client_stopped();
2✔
5738
}
2✔
5739

5740
template <typename T>
5741
T sequence_next()
5742
{
5743
    REALM_UNREACHABLE();
5744
}
5745

5746
template <>
5747
ObjectId sequence_next()
5748
{
8✔
5749
    return ObjectId::gen();
8✔
5750
}
8✔
5751

5752
template <>
5753
UUID sequence_next()
5754
{
8✔
5755
    union {
8✔
5756
        struct {
8✔
5757
            uint64_t upper;
8✔
5758
            uint64_t lower;
8✔
5759
        } ints;
8✔
5760
        UUID::UUIDBytes bytes;
8✔
5761
    } u;
8✔
5762
    static uint64_t counter = test_util::random_int(0, 1000);
8✔
5763
    u.ints.upper = ++counter;
8✔
5764
    u.ints.lower = ++counter;
8✔
5765
    return UUID{u.bytes};
8✔
5766
}
8✔
5767

5768
template <>
5769
Int sequence_next()
5770
{
8✔
5771
    static Int count = test_util::random_int(-1000, 1000);
8✔
5772
    return ++count;
8✔
5773
}
8✔
5774

5775
template <>
5776
String sequence_next()
5777
{
4✔
5778
    static std::string str;
4✔
5779
    static Int sequence = test_util::random_int(-1000, 1000);
4✔
5780
    str = util::format("string sequence %1", ++sequence);
4✔
5781
    return String(str);
4✔
5782
}
4✔
5783

5784
NONCONCURRENT_TEST_TYPES(Sync_PrimaryKeyTypes, Int, String, ObjectId, UUID, util::Optional<Int>,
5785
                         util::Optional<ObjectId>, util::Optional<UUID>)
5786
{
14✔
5787
    using underlying_type = typename util::RemoveOptional<TEST_TYPE>::type;
14✔
5788
    constexpr bool is_optional = !std::is_same_v<underlying_type, TEST_TYPE>;
14✔
5789
    DataType type = ColumnTypeTraits<TEST_TYPE>::id;
14✔
5790

7✔
5791
    TEST_CLIENT_DB(db_1);
14✔
5792
    TEST_CLIENT_DB(db_2);
14✔
5793

7✔
5794
    TEST_DIR(dir);
14✔
5795
    fixtures::ClientServerFixture fixture{dir, test_context};
14✔
5796
    fixture.start();
14✔
5797

7✔
5798
    Session session_1 = fixture.make_session(db_1, "/test");
14✔
5799
    Session session_2 = fixture.make_session(db_2, "/test");
14✔
5800
    session_1.bind();
14✔
5801
    session_2.bind();
14✔
5802

7✔
5803
    TEST_TYPE obj_1_id;
14✔
5804
    TEST_TYPE obj_2_id;
14✔
5805

7✔
5806
    TEST_TYPE default_or_null{};
14✔
5807
    if constexpr (std::is_same_v<TEST_TYPE, String>) {
14✔
5808
        default_or_null = "";
2✔
5809
    }
2✔
5810
    if constexpr (is_optional) {
14✔
5811
        CHECK(!default_or_null);
6✔
5812
    }
6✔
5813

7✔
5814
    {
14✔
5815
        WriteTransaction tr{db_1};
14✔
5816
        auto table_1 = tr.get_group().add_table_with_primary_key("class_Table1", type, "id", is_optional);
14✔
5817
        auto table_2 = tr.get_group().add_table_with_primary_key("class_Table2", type, "id", is_optional);
14✔
5818
        table_1->add_column_list(type, "oids", is_optional);
14✔
5819

7✔
5820
        auto obj_1 = table_1->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5821
        auto obj_2 = table_2->create_object_with_primary_key(sequence_next<underlying_type>());
14✔
5822
        if constexpr (is_optional) {
14✔
5823
            table_2->create_object_with_primary_key(default_or_null);
6✔
5824
        }
6✔
5825

7✔
5826
        auto list = obj_1.template get_list<TEST_TYPE>("oids");
14✔
5827
        obj_1_id = obj_1.template get<TEST_TYPE>("id");
14✔
5828
        obj_2_id = obj_2.template get<TEST_TYPE>("id");
14✔
5829
        list.insert(0, obj_2_id);
14✔
5830
        list.insert(1, default_or_null);
14✔
5831
        list.add(default_or_null);
14✔
5832
        session_1.nonsync_transact_notify(tr.commit());
14✔
5833
    }
14✔
5834

7✔
5835
    session_1.wait_for_upload_complete_or_client_stopped();
14✔
5836
    session_2.wait_for_download_complete_or_client_stopped();
14✔
5837

7✔
5838
    {
14✔
5839
        ReadTransaction tr{db_2};
14✔
5840
        auto table_1 = tr.get_table("class_Table1");
14✔
5841
        auto table_2 = tr.get_table("class_Table2");
14✔
5842
        auto obj_1 = *table_1->begin();
14✔
5843
        auto obj_2 = table_2->find_first(table_2->get_column_key("id"), obj_2_id);
14✔
5844
        CHECK(obj_2);
14✔
5845
        auto list = obj_1.get_list<TEST_TYPE>("oids");
14✔
5846
        CHECK_EQUAL(obj_1.template get<TEST_TYPE>("id"), obj_1_id);
14✔
5847
        CHECK_EQUAL(list.size(), 3);
14✔
5848
        CHECK_NOT(list.is_null(0));
14✔
5849
        CHECK_EQUAL(list.get(0), obj_2_id);
14✔
5850
        CHECK_EQUAL(list.get(1), default_or_null);
14✔
5851
        CHECK_EQUAL(list.get(2), default_or_null);
14✔
5852
        if constexpr (is_optional) {
14✔
5853
            auto obj_3 = table_2->find_first_null(table_2->get_column_key("id"));
6✔
5854
            CHECK(obj_3);
6✔
5855
            CHECK(list.is_null(1));
6✔
5856
            CHECK(list.is_null(2));
6✔
5857
        }
6✔
5858
    }
14✔
5859
}
14✔
5860

5861
TEST(Sync_Mixed)
5862
{
2✔
5863
    // Test replication and synchronization of Mixed values and lists.
1✔
5864

1✔
5865
    TEST_CLIENT_DB(db_1);
2✔
5866
    TEST_CLIENT_DB(db_2);
2✔
5867

1✔
5868
    TEST_DIR(dir);
2✔
5869
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5870
    fixture.start();
2✔
5871

1✔
5872
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5873
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5874
    session_1.bind();
2✔
5875
    session_2.bind();
2✔
5876

1✔
5877
    {
2✔
5878
        WriteTransaction tr{db_1};
2✔
5879
        auto& g = tr.get_group();
2✔
5880
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5881
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5882
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5883
        foos->add_column(type_Mixed, "value", true);
2✔
5884
        foos->add_column_list(type_Mixed, "values");
2✔
5885

1✔
5886
        auto foo = foos->create_object_with_primary_key(123);
2✔
5887
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5888
        auto fop = fops->create_object_with_primary_key(456);
2✔
5889

1✔
5890
        foo.set("value", Mixed(6.2f));
2✔
5891
        auto values = foo.get_list<Mixed>("values");
2✔
5892
        values.insert(0, StringData("A"));
2✔
5893
        values.insert(1, ObjLink{bars->get_key(), bar.get_key()});
2✔
5894
        values.insert(2, ObjLink{fops->get_key(), fop.get_key()});
2✔
5895
        values.insert(3, 123.f);
2✔
5896

1✔
5897
        session_1.nonsync_transact_notify(tr.commit());
2✔
5898
    }
2✔
5899

1✔
5900
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5901
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5902

1✔
5903
    {
2✔
5904
        ReadTransaction tr{db_2};
2✔
5905

1✔
5906
        auto foos = tr.get_table("class_Foo");
2✔
5907
        auto bars = tr.get_table("class_Bar");
2✔
5908
        auto fops = tr.get_table("class_Fop");
2✔
5909

1✔
5910
        CHECK_EQUAL(foos->size(), 1);
2✔
5911
        CHECK_EQUAL(bars->size(), 1);
2✔
5912
        CHECK_EQUAL(fops->size(), 1);
2✔
5913

1✔
5914
        auto foo = *foos->begin();
2✔
5915
        auto value = foo.get<Mixed>("value");
2✔
5916
        CHECK_EQUAL(value, Mixed{6.2f});
2✔
5917
        auto values = foo.get_list<Mixed>("values");
2✔
5918
        CHECK_EQUAL(values.size(), 4);
2✔
5919

1✔
5920
        auto v0 = values.get(0);
2✔
5921
        auto v1 = values.get(1);
2✔
5922
        auto v2 = values.get(2);
2✔
5923
        auto v3 = values.get(3);
2✔
5924

1✔
5925
        auto l1 = v1.get_link();
2✔
5926
        auto l2 = v2.get_link();
2✔
5927

1✔
5928
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5929
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5930

1✔
5931
        CHECK_EQUAL(v0, Mixed{"A"});
2✔
5932
        CHECK_EQUAL(l1_table, bars);
2✔
5933
        CHECK_EQUAL(l2_table, fops);
2✔
5934
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5935
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5936
        CHECK_EQUAL(v3, Mixed{123.f});
2✔
5937
    }
2✔
5938
}
2✔
5939

5940
TEST(Sync_TypedLinks)
5941
{
2✔
5942
    // Test replication and synchronization of Mixed values and lists.
1✔
5943

1✔
5944
    TEST_CLIENT_DB(db_1);
2✔
5945
    TEST_CLIENT_DB(db_2);
2✔
5946

1✔
5947
    TEST_DIR(dir);
2✔
5948
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
5949
    fixture.start();
2✔
5950

1✔
5951
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
5952
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
5953
    session_1.bind();
2✔
5954
    session_2.bind();
2✔
5955

1✔
5956
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& tr) {
2✔
5957
        auto& g = tr.get_group();
2✔
5958
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
5959
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
5960
        auto fops = g.add_table_with_primary_key("class_Fop", type_Int, "id");
2✔
5961
        foos->add_column(type_TypedLink, "link");
2✔
5962

1✔
5963
        auto foo1 = foos->create_object_with_primary_key(123);
2✔
5964
        auto foo2 = foos->create_object_with_primary_key(456);
2✔
5965
        auto bar = bars->create_object_with_primary_key("Hello");
2✔
5966
        auto fop = fops->create_object_with_primary_key(456);
2✔
5967

1✔
5968
        foo1.set("link", ObjLink(bars->get_key(), bar.get_key()));
2✔
5969
        foo2.set("link", ObjLink(fops->get_key(), fop.get_key()));
2✔
5970
    });
2✔
5971

1✔
5972
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
5973
    session_2.wait_for_download_complete_or_client_stopped();
2✔
5974

1✔
5975
    {
2✔
5976
        ReadTransaction tr{db_2};
2✔
5977

1✔
5978
        auto foos = tr.get_table("class_Foo");
2✔
5979
        auto bars = tr.get_table("class_Bar");
2✔
5980
        auto fops = tr.get_table("class_Fop");
2✔
5981

1✔
5982
        CHECK_EQUAL(foos->size(), 2);
2✔
5983
        CHECK_EQUAL(bars->size(), 1);
2✔
5984
        CHECK_EQUAL(fops->size(), 1);
2✔
5985

1✔
5986
        auto it = foos->begin();
2✔
5987
        auto l1 = it->get<ObjLink>("link");
2✔
5988
        ++it;
2✔
5989
        auto l2 = it->get<ObjLink>("link");
2✔
5990

1✔
5991
        auto l1_table = tr.get_table(l1.get_table_key());
2✔
5992
        auto l2_table = tr.get_table(l2.get_table_key());
2✔
5993

1✔
5994
        CHECK_EQUAL(l1_table, bars);
2✔
5995
        CHECK_EQUAL(l2_table, fops);
2✔
5996
        CHECK_EQUAL(l1.get_obj_key(), bars->begin()->get_key());
2✔
5997
        CHECK_EQUAL(l2.get_obj_key(), fops->begin()->get_key());
2✔
5998
    }
2✔
5999
}
2✔
6000

6001
TEST(Sync_Dictionary)
6002
{
2✔
6003
    // Test replication and synchronization of Mixed values and lists.
1✔
6004

1✔
6005
    TEST_CLIENT_DB(db_1);
2✔
6006
    TEST_CLIENT_DB(db_2);
2✔
6007

1✔
6008
    TEST_DIR(dir);
2✔
6009
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6010
    fixture.start();
2✔
6011

1✔
6012
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6013
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6014
    session_1.bind();
2✔
6015
    session_2.bind();
2✔
6016

1✔
6017
    Timestamp now{std::chrono::system_clock::now()};
2✔
6018

1✔
6019
    write_transaction_notifying_session(db_1, session_1, [&](WriteTransaction& tr) {
2✔
6020
        auto& g = tr.get_group();
2✔
6021
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6022
        auto col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6023
        auto col_dict_str = foos->add_column_dictionary(type_String, "str_dict", true);
2✔
6024

1✔
6025
        auto foo = foos->create_object_with_primary_key(123);
2✔
6026

1✔
6027
        auto dict = foo.get_dictionary(col_dict);
2✔
6028
        dict.insert("hello", "world");
2✔
6029
        dict.insert("cnt", 7);
2✔
6030
        dict.insert("when", now);
2✔
6031

1✔
6032
        auto dict_str = foo.get_dictionary(col_dict_str);
2✔
6033
        dict_str.insert("some", "text");
2✔
6034
        dict_str.insert("nothing", null());
2✔
6035
    });
2✔
6036

1✔
6037
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6038
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6039

1✔
6040
    write_transaction_notifying_session(db_2, session_2, [&](WriteTransaction& tr) {
2✔
6041
        auto foos = tr.get_table("class_Foo");
2✔
6042
        CHECK_EQUAL(foos->size(), 1);
2✔
6043

1✔
6044
        auto it = foos->begin();
2✔
6045
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6046
        CHECK(dict.get_value_data_type() == type_Mixed);
2✔
6047
        CHECK_EQUAL(dict.size(), 3);
2✔
6048

1✔
6049
        auto col_dict_str = foos->get_column_key("str_dict");
2✔
6050
        auto dict_str = it->get_dictionary(col_dict_str);
2✔
6051
        CHECK(col_dict_str.is_nullable());
2✔
6052
        CHECK(dict_str.get_value_data_type() == type_String);
2✔
6053
        CHECK_EQUAL(dict_str.size(), 2);
2✔
6054

1✔
6055
        Mixed val = dict["hello"];
2✔
6056
        CHECK_EQUAL(val.get_string(), "world");
2✔
6057
        val = dict.get("cnt");
2✔
6058
        CHECK_EQUAL(val.get_int(), 7);
2✔
6059
        val = dict.get("when");
2✔
6060
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6061

1✔
6062
        dict.erase("cnt");
2✔
6063
        dict.insert("hello", "goodbye");
2✔
6064
    });
2✔
6065

1✔
6066
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6067
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6068

1✔
6069
    write_transaction_notifying_session(db_1, session_1, [&](WriteTransaction& tr) {
2✔
6070
        auto foos = tr.get_table("class_Foo");
2✔
6071
        CHECK_EQUAL(foos->size(), 1);
2✔
6072

1✔
6073
        auto it = foos->begin();
2✔
6074
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6075
        CHECK_EQUAL(dict.size(), 2);
2✔
6076

1✔
6077
        Mixed val = dict["hello"];
2✔
6078
        CHECK_EQUAL(val.get_string(), "goodbye");
2✔
6079
        val = dict.get("when");
2✔
6080
        CHECK_EQUAL(val.get<Timestamp>(), now);
2✔
6081

1✔
6082
        dict.clear();
2✔
6083
    });
2✔
6084

1✔
6085
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6086
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6087

1✔
6088
    {
2✔
6089
        ReadTransaction read_1{db_1};
2✔
6090
        ReadTransaction read_2{db_2};
2✔
6091
        // tr.get_group().to_json(std::cout);
1✔
6092

1✔
6093
        auto foos = read_2.get_table("class_Foo");
2✔
6094

1✔
6095
        CHECK_EQUAL(foos->size(), 1);
2✔
6096

1✔
6097
        auto it = foos->begin();
2✔
6098
        auto dict = it->get_dictionary(foos->get_column_key("dict"));
2✔
6099
        CHECK_EQUAL(dict.size(), 0);
2✔
6100

1✔
6101
        CHECK(compare_groups(read_1, read_2));
2✔
6102
    }
2✔
6103
}
2✔
6104

6105
TEST(Sync_Dictionary_Links)
6106
{
2✔
6107
    TEST_CLIENT_DB(db_1);
2✔
6108
    TEST_CLIENT_DB(db_2);
2✔
6109

1✔
6110
    TEST_DIR(dir);
2✔
6111
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6112
    fixture.start();
2✔
6113

1✔
6114
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6115
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6116
    session_1.bind();
2✔
6117
    session_2.bind();
2✔
6118

1✔
6119
    // Test that we can transmit links.
1✔
6120

1✔
6121
    ColKey col_dict;
2✔
6122

1✔
6123
    write_transaction_notifying_session(db_1, session_1, [&](WriteTransaction& tr) {
2✔
6124
        auto& g = tr.get_group();
2✔
6125
        auto foos = g.add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6126
        auto bars = g.add_table_with_primary_key("class_Bar", type_String, "id");
2✔
6127
        col_dict = foos->add_column_dictionary(type_Mixed, "dict");
2✔
6128

1✔
6129
        auto foo = foos->create_object_with_primary_key(123);
2✔
6130
        auto a = bars->create_object_with_primary_key("a");
2✔
6131
        auto b = bars->create_object_with_primary_key("b");
2✔
6132

1✔
6133
        auto dict = foo.get_dictionary(col_dict);
2✔
6134
        dict.insert("a", a);
2✔
6135
        dict.insert("b", b);
2✔
6136
    });
2✔
6137

1✔
6138
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6139
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6140

1✔
6141
    {
2✔
6142
        ReadTransaction tr{db_2};
2✔
6143

1✔
6144
        auto foos = tr.get_table("class_Foo");
2✔
6145
        auto bars = tr.get_table("class_Bar");
2✔
6146

1✔
6147
        CHECK_EQUAL(foos->size(), 1);
2✔
6148
        CHECK_EQUAL(bars->size(), 2);
2✔
6149

1✔
6150
        auto foo = foos->get_object_with_primary_key(123);
2✔
6151
        auto a = bars->get_object_with_primary_key("a");
2✔
6152
        auto b = bars->get_object_with_primary_key("b");
2✔
6153

1✔
6154
        auto dict = foo.get_dictionary(foos->get_column_key("dict"));
2✔
6155
        CHECK_EQUAL(dict.size(), 2);
2✔
6156

1✔
6157
        auto dict_a = dict.get("a");
2✔
6158
        auto dict_b = dict.get("b");
2✔
6159
        CHECK(dict_a == Mixed{a.get_link()});
2✔
6160
        CHECK(dict_b == Mixed{b.get_link()});
2✔
6161
    }
2✔
6162

1✔
6163
    // Test that we can create tombstones for objects in dictionaries.
1✔
6164

1✔
6165
    write_transaction_notifying_session(db_1, session_1, [&](WriteTransaction& tr) {
2✔
6166
        auto& g = tr.get_group();
2✔
6167

1✔
6168
        auto bars = g.get_table("class_Bar");
2✔
6169
        auto a = bars->get_object_with_primary_key("a");
2✔
6170
        a.invalidate();
2✔
6171

1✔
6172
        auto foos = g.get_table("class_Foo");
2✔
6173
        auto foo = foos->get_object_with_primary_key(123);
2✔
6174
        auto dict = foo.get_dictionary(col_dict);
2✔
6175

1✔
6176
        CHECK_EQUAL(dict.size(), 2);
2✔
6177
        CHECK((*dict.find("a")).second.is_null());
2✔
6178

1✔
6179
        CHECK(dict.find("b") != dict.end());
2✔
6180
    });
2✔
6181

1✔
6182
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6183
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6184

1✔
6185
    {
2✔
6186
        ReadTransaction tr{db_2};
2✔
6187

1✔
6188
        auto foos = tr.get_table("class_Foo");
2✔
6189
        auto bars = tr.get_table("class_Bar");
2✔
6190

1✔
6191
        CHECK_EQUAL(foos->size(), 1);
2✔
6192
        CHECK_EQUAL(bars->size(), 1);
2✔
6193

1✔
6194
        auto b = bars->get_object_with_primary_key("b");
2✔
6195

1✔
6196
        auto foo = foos->get_object_with_primary_key(123);
2✔
6197
        auto dict = foo.get_dictionary(col_dict);
2✔
6198

1✔
6199
        CHECK_EQUAL(dict.size(), 2);
2✔
6200
        CHECK((*dict.find("a")).second.is_null());
2✔
6201

1✔
6202
        CHECK(dict.find("b") != dict.end());
2✔
6203
        CHECK((*dict.find("b")).second == Mixed{b.get_link()});
2✔
6204
    }
2✔
6205
}
2✔
6206

6207
TEST(Sync_Set)
6208
{
2✔
6209
    // Test replication and synchronization of Set values.
1✔
6210

1✔
6211
    TEST_CLIENT_DB(db_1);
2✔
6212
    TEST_CLIENT_DB(db_2);
2✔
6213

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

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

1✔
6223
    ColKey col_ints, col_strings, col_mixeds;
2✔
6224
    {
2✔
6225
        WriteTransaction wt{db_1};
2✔
6226
        auto t = wt.get_group().add_table_with_primary_key("class_Foo", type_Int, "pk");
2✔
6227
        col_ints = t->add_column_set(type_Int, "ints");
2✔
6228
        col_strings = t->add_column_set(type_String, "strings");
2✔
6229
        col_mixeds = t->add_column_set(type_Mixed, "mixeds");
2✔
6230

1✔
6231
        auto obj = t->create_object_with_primary_key(0);
2✔
6232

1✔
6233
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6234
        auto strings = obj.get_set<StringData>(col_strings);
2✔
6235
        auto mixeds = obj.get_set<Mixed>(col_mixeds);
2✔
6236

1✔
6237
        ints.insert(123);
2✔
6238
        ints.insert(456);
2✔
6239
        ints.insert(789);
2✔
6240
        ints.insert(123);
2✔
6241
        ints.insert(456);
2✔
6242
        ints.insert(789);
2✔
6243

1✔
6244
        CHECK_EQUAL(ints.size(), 3);
2✔
6245
        CHECK_EQUAL(ints.find(123), 0);
2✔
6246
        CHECK_EQUAL(ints.find(456), 1);
2✔
6247
        CHECK_EQUAL(ints.find(789), 2);
2✔
6248

1✔
6249
        strings.insert("a");
2✔
6250
        strings.insert("b");
2✔
6251
        strings.insert("c");
2✔
6252
        strings.insert("a");
2✔
6253
        strings.insert("b");
2✔
6254
        strings.insert("c");
2✔
6255

1✔
6256
        CHECK_EQUAL(strings.size(), 3);
2✔
6257
        CHECK_EQUAL(strings.find("a"), 0);
2✔
6258
        CHECK_EQUAL(strings.find("b"), 1);
2✔
6259
        CHECK_EQUAL(strings.find("c"), 2);
2✔
6260

1✔
6261
        mixeds.insert(Mixed{123});
2✔
6262
        mixeds.insert(Mixed{"a"});
2✔
6263
        mixeds.insert(Mixed{456.0});
2✔
6264
        mixeds.insert(Mixed{123});
2✔
6265
        mixeds.insert(Mixed{"a"});
2✔
6266
        mixeds.insert(Mixed{456.0});
2✔
6267

1✔
6268
        CHECK_EQUAL(mixeds.size(), 3);
2✔
6269
        CHECK_EQUAL(mixeds.find(123), 0);
2✔
6270
        CHECK_EQUAL(mixeds.find(456.0), 1);
2✔
6271
        CHECK_EQUAL(mixeds.find("a"), 2);
2✔
6272

1✔
6273
        session_1.nonsync_transact_notify(wt.commit());
2✔
6274
    }
2✔
6275

1✔
6276
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6277
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6278

1✔
6279
    // Create a conflict. Session 1 should lose, because it has a lower peer ID.
1✔
6280
    write_transaction_notifying_session(db_1, session_1, [=](WriteTransaction& wt) {
2✔
6281
        auto t = wt.get_table("class_Foo");
2✔
6282
        auto obj = t->get_object_with_primary_key(0);
2✔
6283

1✔
6284
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6285
        ints.insert(999);
2✔
6286
    });
2✔
6287

1✔
6288
    write_transaction_notifying_session(db_2, session_2, [=](WriteTransaction& wt) {
2✔
6289
        auto t = wt.get_table("class_Foo");
2✔
6290
        auto obj = t->get_object_with_primary_key(0);
2✔
6291

1✔
6292
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6293
        ints.insert(999);
2✔
6294
        ints.erase(999);
2✔
6295
    });
2✔
6296

1✔
6297
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6298
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6299
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6300
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6301

1✔
6302
    {
2✔
6303
        ReadTransaction read_1{db_1};
2✔
6304
        ReadTransaction read_2{db_2};
2✔
6305
        CHECK(compare_groups(read_1, read_2));
2✔
6306
    }
2✔
6307

1✔
6308
    write_transaction_notifying_session(db_1, session_1, [=](WriteTransaction& wt) {
2✔
6309
        auto t = wt.get_table("class_Foo");
2✔
6310
        auto obj = t->get_object_with_primary_key(0);
2✔
6311
        auto ints = obj.get_set<int64_t>(col_ints);
2✔
6312
        ints.clear();
2✔
6313
    });
2✔
6314

1✔
6315
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6316
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6317

1✔
6318
    {
2✔
6319
        ReadTransaction read_1{db_1};
2✔
6320
        ReadTransaction read_2{db_2};
2✔
6321
        CHECK(compare_groups(read_1, read_2));
2✔
6322
    }
2✔
6323
}
2✔
6324

6325
TEST(Sync_DanglingLinksCountInPriorSize)
6326
{
2✔
6327
    SHARED_GROUP_TEST_PATH(path);
2✔
6328
    ClientReplication repl;
2✔
6329
    auto local_db = realm::DB::create(repl, path);
2✔
6330
    auto& history = repl.get_history();
2✔
6331
    history.set_client_file_ident(sync::SaltedFileIdent{1, 123456}, true);
2✔
6332

1✔
6333
    version_type last_version, last_version_observed = 0;
2✔
6334
    auto dump_uploadable = [&] {
4✔
6335
        UploadCursor upload_cursor{last_version_observed, 0};
4✔
6336
        std::vector<sync::ClientHistory::UploadChangeset> changesets_to_upload;
4✔
6337
        version_type locked_server_version = 0;
4✔
6338
        history.find_uploadable_changesets(upload_cursor, last_version, changesets_to_upload, locked_server_version);
4✔
6339
        CHECK_EQUAL(changesets_to_upload.size(), static_cast<size_t>(1));
4✔
6340
        realm::sync::Changeset parsed_changeset;
4✔
6341
        auto unparsed_changeset = changesets_to_upload[0].changeset.get_first_chunk();
4✔
6342
        realm::util::SimpleInputStream changeset_stream(unparsed_changeset);
4✔
6343
        realm::sync::parse_changeset(changeset_stream, parsed_changeset);
4✔
6344
        test_context.logger->info("changeset at version %1: %2", last_version, parsed_changeset);
4✔
6345
        last_version_observed = last_version;
4✔
6346
        return parsed_changeset;
4✔
6347
    };
4✔
6348

1✔
6349
    TableKey source_table_key, target_table_key;
2✔
6350
    {
2✔
6351
        auto wt = local_db->start_write();
2✔
6352
        auto source_table = wt->add_table_with_primary_key("class_source", type_String, "_id");
2✔
6353
        auto target_table = wt->add_table_with_primary_key("class_target", type_String, "_id");
2✔
6354
        source_table->add_column_list(*target_table, "links");
2✔
6355

1✔
6356
        source_table_key = source_table->get_key();
2✔
6357
        target_table_key = target_table->get_key();
2✔
6358

1✔
6359
        auto obj_to_keep = target_table->create_object_with_primary_key(std::string{"target1"});
2✔
6360
        auto obj_to_delete = target_table->create_object_with_primary_key(std::string{"target2"});
2✔
6361
        auto source_obj = source_table->create_object_with_primary_key(std::string{"source"});
2✔
6362

1✔
6363
        auto links_list = source_obj.get_linklist("links");
2✔
6364
        links_list.add(obj_to_keep.get_key());
2✔
6365
        links_list.add(obj_to_delete.get_key());
2✔
6366
        last_version = wt->commit();
2✔
6367
    }
2✔
6368

1✔
6369
    dump_uploadable();
2✔
6370

1✔
6371
    {
2✔
6372
        // Simulate removing the object via the sync client so we get a dangling link
1✔
6373
        TempShortCircuitReplication disable_repl(repl);
2✔
6374
        auto wt = local_db->start_write();
2✔
6375
        auto target_table = wt->get_table(target_table_key);
2✔
6376
        auto obj = target_table->get_object_with_primary_key(std::string{"target2"});
2✔
6377
        obj.invalidate();
2✔
6378
        last_version = wt->commit();
2✔
6379
    }
2✔
6380

1✔
6381
    {
2✔
6382
        auto wt = local_db->start_write();
2✔
6383
        auto source_table = wt->get_table(source_table_key);
2✔
6384
        auto target_table = wt->get_table(target_table_key);
2✔
6385

1✔
6386
        auto obj_to_add = target_table->create_object_with_primary_key(std::string{"target3"});
2✔
6387

1✔
6388
        auto source_obj = source_table->get_object_with_primary_key(std::string{"source"});
2✔
6389
        auto links_list = source_obj.get_linklist("links");
2✔
6390
        links_list.add(obj_to_add.get_key());
2✔
6391
        last_version = wt->commit();
2✔
6392
    }
2✔
6393

1✔
6394
    auto changeset = dump_uploadable();
2✔
6395
    CHECK_EQUAL(changeset.size(), static_cast<size_t>(2));
2✔
6396
    auto changeset_it = changeset.end();
2✔
6397
    --changeset_it;
2✔
6398
    auto last_instr = *changeset_it;
2✔
6399
    CHECK_EQUAL(last_instr->type(), Instruction::Type::ArrayInsert);
2✔
6400
    auto arr_insert_instr = last_instr->get_as<Instruction::ArrayInsert>();
2✔
6401
    CHECK_EQUAL(changeset.get_string(arr_insert_instr.table), StringData("source"));
2✔
6402
    CHECK(arr_insert_instr.value.type == sync::instr::Payload::Type::Link);
2✔
6403
    CHECK_EQUAL(changeset.get_string(mpark::get<InternString>(arr_insert_instr.value.data.link.target)),
2✔
6404
                StringData("target3"));
2✔
6405
    CHECK_EQUAL(arr_insert_instr.prior_size, 2);
2✔
6406
}
2✔
6407

6408
TEST(Sync_BundledRealmFile)
6409
{
2✔
6410
    TEST_CLIENT_DB(db);
2✔
6411
    SHARED_GROUP_TEST_PATH(path);
2✔
6412

1✔
6413
    TEST_DIR(dir);
2✔
6414
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6415
    fixture.start();
2✔
6416

1✔
6417
    Session session = fixture.make_bound_session(db);
2✔
6418

1✔
6419
    write_transaction_notifying_session(db, session, [](WriteTransaction& tr) {
2✔
6420
        auto foos = tr.get_group().add_table_with_primary_key("class_Foo", type_Int, "id");
2✔
6421
        foos->create_object_with_primary_key(123);
2✔
6422
    });
2✔
6423

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

1✔
6427
    session.wait_for_upload_complete_or_client_stopped();
2✔
6428
    session.wait_for_download_complete_or_client_stopped();
2✔
6429

1✔
6430
    // Now we can
1✔
6431
    db->write_copy(path.c_str(), nullptr);
2✔
6432
}
2✔
6433

6434
TEST(Sync_UpgradeToClientHistory)
6435
{
2✔
6436
    SHARED_GROUP_TEST_PATH(db1_path);
2✔
6437
    SHARED_GROUP_TEST_PATH(db2_path);
2✔
6438
    auto db_1 = DB::create(make_in_realm_history(), db1_path);
2✔
6439
    auto db_2 = DB::create(make_in_realm_history(), db2_path);
2✔
6440
    {
2✔
6441
        auto tr = db_1->start_write();
2✔
6442

1✔
6443
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
6444
        auto col_float = embedded->add_column(type_Float, "float");
2✔
6445
        auto col_additional = embedded->add_column_dictionary(*embedded, "additional");
2✔
6446

1✔
6447
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6448
        auto col_list = baas->add_column_list(type_Int, "list");
2✔
6449
        auto col_set = baas->add_column_set(type_Int, "set");
2✔
6450
        auto col_dict = baas->add_column_dictionary(type_Int, "dictionary");
2✔
6451
        auto col_child = baas->add_column(*embedded, "child");
2✔
6452

1✔
6453
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6454
        auto col_str = foos->add_column(type_String, "str");
2✔
6455
        auto col_children = foos->add_column_list(*embedded, "children");
2✔
6456

1✔
6457
        auto foobaas = tr->add_table_with_primary_key("class_FooBaa", type_ObjectId, "_id");
2✔
6458
        auto col_time = foobaas->add_column(type_Timestamp, "time");
2✔
6459

1✔
6460
        auto col_link = baas->add_column(*foos, "link");
2✔
6461

1✔
6462
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Hello");
2✔
6463
        auto children = foo.get_linklist(col_children);
2✔
6464
        children.create_and_insert_linked_object(0);
2✔
6465
        auto baa = baas->create_object_with_primary_key(999).set(col_link, foo.get_key());
2✔
6466
        auto obj = baa.create_and_set_linked_object(col_child);
2✔
6467
        obj.set(col_float, 42.f);
2✔
6468
        auto additional = obj.get_dictionary(col_additional);
2✔
6469
        additional.create_and_insert_linked_object("One").set(col_float, 1.f);
2✔
6470
        additional.create_and_insert_linked_object("Two").set(col_float, 2.f);
2✔
6471
        additional.create_and_insert_linked_object("Three").set(col_float, 3.f);
2✔
6472

1✔
6473
        auto list = baa.get_list<Int>(col_list);
2✔
6474
        list.add(1);
2✔
6475
        list.add(2);
2✔
6476
        list.add(3);
2✔
6477
        auto set = baa.get_set<Int>(col_set);
2✔
6478
        set.insert(4);
2✔
6479
        set.insert(5);
2✔
6480
        set.insert(6);
2✔
6481
        auto dict = baa.get_dictionary(col_dict);
2✔
6482
        dict.insert("key7", 7);
2✔
6483
        dict.insert("key8", 8);
2✔
6484
        dict.insert("key9", 9);
2✔
6485

1✔
6486
        for (int i = 0; i < 100; i++) {
202✔
6487
            foobaas->create_object_with_primary_key(ObjectId::gen()).set(col_time, Timestamp(::time(nullptr), i));
200✔
6488
        }
200✔
6489

1✔
6490
        tr->commit();
2✔
6491
    }
2✔
6492
    {
2✔
6493
        auto tr = db_2->start_write();
2✔
6494
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
6495
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
6496
        auto col_str = foos->add_column(type_String, "str");
2✔
6497
        auto col_link = baas->add_column(*foos, "link");
2✔
6498

1✔
6499
        auto foo = foos->create_object_with_primary_key("123").set(col_str, "Goodbye");
2✔
6500
        baas->create_object_with_primary_key(888).set(col_link, foo.get_key());
2✔
6501

1✔
6502
        tr->commit();
2✔
6503
    }
2✔
6504

1✔
6505
    db_1->create_new_history(make_client_replication());
2✔
6506
    db_2->create_new_history(make_client_replication());
2✔
6507

1✔
6508
    TEST_DIR(dir);
2✔
6509
    fixtures::ClientServerFixture fixture{dir, test_context};
2✔
6510
    fixture.start();
2✔
6511

1✔
6512
    Session session_1 = fixture.make_session(db_1, "/test");
2✔
6513
    Session session_2 = fixture.make_session(db_2, "/test");
2✔
6514
    session_1.bind();
2✔
6515
    session_2.bind();
2✔
6516

1✔
6517
    write_transaction_notifying_session(db_1, session_1, [](WriteTransaction& tr) {
2✔
6518
        auto foos = tr.get_group().get_table("class_Foo");
2✔
6519
        foos->create_object_with_primary_key("456");
2✔
6520
    });
2✔
6521
    session_1.wait_for_upload_complete_or_client_stopped();
2✔
6522
    session_2.wait_for_upload_complete_or_client_stopped();
2✔
6523
    session_1.wait_for_download_complete_or_client_stopped();
2✔
6524
    session_2.wait_for_download_complete_or_client_stopped();
2✔
6525

1✔
6526
    // db_2->start_read()->to_json(std::cout);
1✔
6527
}
2✔
6528

6529
// This test is extracted from ClientReset_ThreeClients
6530
// because it uncovers a bug in how MSVC 2019 compiles
6531
// things in Changeset::get_key()
6532
TEST(Sync_MergeStringPrimaryKey)
6533
{
2✔
6534
    TEST_DIR(dir_1); // The server.
2✔
6535
    TEST_CLIENT_DB(db_1);
2✔
6536
    TEST_CLIENT_DB(db_2);
2✔
6537
    TEST_DIR(metadata_dir_1);
2✔
6538
    TEST_DIR(metadata_dir_2);
2✔
6539

1✔
6540
    const std::string server_path = "/data";
2✔
6541

1✔
6542
    std::string real_path_1, real_path_2;
2✔
6543

1✔
6544
    auto create_schema = [&](Transaction& group) {
4✔
6545
        TableRef table_0 = group.add_table_with_primary_key("class_table_0", type_Int, "id");
4✔
6546
        table_0->add_column(type_Int, "int");
4✔
6547
        table_0->add_column(type_Bool, "bool");
4✔
6548
        table_0->add_column(type_Float, "float");
4✔
6549
        table_0->add_column(type_Double, "double");
4✔
6550
        table_0->add_column(type_Timestamp, "timestamp");
4✔
6551

2✔
6552
        TableRef table_1 = group.add_table_with_primary_key("class_table_1", type_Int, "pk_int");
4✔
6553
        table_1->add_column(type_String, "String");
4✔
6554

2✔
6555
        TableRef table_2 = group.add_table_with_primary_key("class_table_2", type_String, "pk_string");
4✔
6556
        table_2->add_column_list(type_String, "array_string");
4✔
6557
    };
4✔
6558

1✔
6559
    // First we make changesets. Then we upload them.
1✔
6560
    {
2✔
6561
        ClientServerFixture fixture(dir_1, test_context);
2✔
6562
        fixture.start();
2✔
6563
        real_path_1 = fixture.map_virtual_to_real_path(server_path);
2✔
6564

1✔
6565
        {
2✔
6566
            WriteTransaction wt{db_1};
2✔
6567
            create_schema(wt);
2✔
6568
            wt.commit();
2✔
6569
        }
2✔
6570
        {
2✔
6571
            WriteTransaction wt{db_2};
2✔
6572
            create_schema(wt);
2✔
6573

1✔
6574
            TableRef table_2 = wt.get_table("class_table_2");
2✔
6575
            auto col = table_2->get_column_key("array_string");
2✔
6576
            auto list_string = table_2->create_object_with_primary_key("aaa").get_list<String>(col);
2✔
6577
            list_string.add("a");
2✔
6578
            list_string.add("b");
2✔
6579

1✔
6580
            wt.commit();
2✔
6581
        }
2✔
6582

1✔
6583
        Session session_1 = fixture.make_bound_session(db_1, server_path);
2✔
6584
        Session session_2 = fixture.make_bound_session(db_2, server_path);
2✔
6585

1✔
6586
        session_1.wait_for_upload_complete_or_client_stopped();
2✔
6587
        session_2.wait_for_upload_complete_or_client_stopped();
2✔
6588
        // Download completion is not important.
1✔
6589
    }
2✔
6590
}
2✔
6591

6592
TEST(Sync_NonIncreasingServerVersions)
6593
{
2✔
6594
    TEST_CLIENT_DB(db);
2✔
6595

1✔
6596
    auto& history = get_history(db);
2✔
6597
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6598
    timestamp_type timestamp{1};
2✔
6599
    history.set_local_origin_timestamp_source([&] {
2✔
6600
        return ++timestamp;
2✔
6601
    });
2✔
6602

1✔
6603
    auto latest_local_version = [&] {
2✔
6604
        auto tr = db->start_write();
2✔
6605
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6606
        return tr->commit();
2✔
6607
    }();
2✔
6608

1✔
6609
    std::vector<Changeset> server_changesets;
2✔
6610
    auto prep_changeset = [&](auto pk_name, auto int_col_val) {
8✔
6611
        Changeset changeset;
8✔
6612
        changeset.version = 10;
8✔
6613
        changeset.last_integrated_remote_version = latest_local_version - 1;
8✔
6614
        changeset.origin_timestamp = ++timestamp;
8✔
6615
        changeset.origin_file_ident = 1;
8✔
6616
        instr::PrimaryKey pk{changeset.intern_string(pk_name)};
8✔
6617
        auto table_name = changeset.intern_string("foo");
8✔
6618
        auto col_name = changeset.intern_string("int_col");
8✔
6619
        instr::EraseObject erase_1;
8✔
6620
        erase_1.object = pk;
8✔
6621
        erase_1.table = table_name;
8✔
6622
        changeset.push_back(erase_1);
8✔
6623
        instr::CreateObject create_1;
8✔
6624
        create_1.object = pk;
8✔
6625
        create_1.table = table_name;
8✔
6626
        changeset.push_back(create_1);
8✔
6627
        instr::Update update_1;
8✔
6628
        update_1.table = table_name;
8✔
6629
        update_1.object = pk;
8✔
6630
        update_1.field = col_name;
8✔
6631
        update_1.value = instr::Payload{int64_t(int_col_val)};
8✔
6632
        changeset.push_back(update_1);
8✔
6633
        server_changesets.push_back(std::move(changeset));
8✔
6634
    };
8✔
6635
    prep_changeset("bizz", 1);
2✔
6636
    prep_changeset("buzz", 2);
2✔
6637
    prep_changeset("baz", 3);
2✔
6638
    prep_changeset("bar", 4);
2✔
6639
    ++server_changesets.back().version;
2✔
6640

1✔
6641
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6642
    std::vector<Transformer::RemoteChangeset> server_changesets_encoded;
2✔
6643
    for (const auto& changeset : server_changesets) {
8✔
6644
        encoded.emplace_back();
8✔
6645
        encode_changeset(changeset, encoded.back());
8✔
6646
        server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
8✔
6647
                                               BinaryData(encoded.back().data(), encoded.back().size()),
8✔
6648
                                               changeset.origin_timestamp, changeset.origin_file_ident);
8✔
6649
    }
8✔
6650

1✔
6651
    SyncProgress progress = {};
2✔
6652
    progress.download.server_version = server_changesets.back().version;
2✔
6653
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6654
    progress.latest_server_version.version = server_changesets.back().version;
2✔
6655
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6656

1✔
6657
    uint_fast64_t downloadable_bytes = 0;
2✔
6658
    VersionInfo version_info;
2✔
6659
    auto transact = db->start_read();
2✔
6660
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6661
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6662
}
2✔
6663

6664
TEST(Sync_InvalidChangesetFromServer)
6665
{
2✔
6666
    TEST_CLIENT_DB(db);
2✔
6667

1✔
6668
    auto& history = get_history(db);
2✔
6669
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6670

1✔
6671
    instr::CreateObject bad_instr;
2✔
6672
    bad_instr.object = InternString{1};
2✔
6673
    bad_instr.table = InternString{2};
2✔
6674

1✔
6675
    Changeset changeset;
2✔
6676
    changeset.push_back(bad_instr);
2✔
6677

1✔
6678
    ChangesetEncoder::Buffer encoded;
2✔
6679
    encode_changeset(changeset, encoded);
2✔
6680
    Transformer::RemoteChangeset server_changeset;
2✔
6681
    server_changeset.origin_file_ident = 1;
2✔
6682
    server_changeset.remote_version = 1;
2✔
6683
    server_changeset.data = BinaryData(encoded.data(), encoded.size());
2✔
6684

1✔
6685
    VersionInfo version_info;
2✔
6686
    auto transact = db->start_read();
2✔
6687
    CHECK_THROW_EX(history.integrate_server_changesets({}, nullptr, util::Span(&server_changeset, 1), version_info,
2✔
6688
                                                       DownloadBatchState::SteadyState, *test_context.logger,
2✔
6689
                                                       transact),
2✔
6690
                   sync::IntegrationException,
2✔
6691
                   StringData(e.what()).contains("Failed to parse received changeset: Invalid interned string"));
2✔
6692
}
2✔
6693

6694
TEST(Sync_DifferentUsersMultiplexing)
6695
{
2✔
6696
    ClientServerFixture::Config fixture_config;
2✔
6697
    fixture_config.one_connection_per_session = false;
2✔
6698

1✔
6699
    TEST_DIR(server_dir);
2✔
6700
    ClientServerFixture fixture(server_dir, test_context, std::move(fixture_config));
2✔
6701

1✔
6702
    struct SessionBundle {
2✔
6703
        test_util::DBTestPathGuard path_guard;
2✔
6704
        DBRef db;
2✔
6705
        Session sess;
2✔
6706

1✔
6707
        SessionBundle(unit_test::TestContext& ctx, ClientServerFixture& fixture, std::string name,
2✔
6708
                      std::string signed_token, std::string user_id)
2✔
6709
            : path_guard(realm::test_util::get_test_path(ctx.get_test_name(), "." + name + ".realm"))
2✔
6710
            , db(DB::create(make_client_replication(), path_guard))
2✔
6711
        {
8✔
6712
            Session::Config config;
8✔
6713
            config.signed_user_token = signed_token;
8✔
6714
            config.user_id = user_id;
8✔
6715
            sess = fixture.make_bound_session(db, "/test", std::move(config));
8✔
6716
            sess.wait_for_download_complete_or_client_stopped();
8✔
6717
        }
8✔
6718
    };
2✔
6719

1✔
6720
    fixture.start();
2✔
6721

1✔
6722
    SessionBundle user_1_sess_1(test_context, fixture, "user_1_db_1", g_user_0_token, "user_0");
2✔
6723
    SessionBundle user_2_sess_1(test_context, fixture, "user_2_db_1", g_user_1_token, "user_1");
2✔
6724
    SessionBundle user_1_sess_2(test_context, fixture, "user_1_db_2", g_user_0_token, "user_0");
2✔
6725
    SessionBundle user_2_sess_2(test_context, fixture, "user_2_db_2", g_user_1_token, "user_1");
2✔
6726

1✔
6727
    CHECK_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6728
                user_1_sess_2.sess.get_appservices_connection_id());
2✔
6729
    CHECK_EQUAL(user_2_sess_1.sess.get_appservices_connection_id(),
2✔
6730
                user_2_sess_2.sess.get_appservices_connection_id());
2✔
6731
    CHECK_NOT_EQUAL(user_1_sess_1.sess.get_appservices_connection_id(),
2✔
6732
                    user_2_sess_1.sess.get_appservices_connection_id());
2✔
6733
    CHECK_NOT_EQUAL(user_1_sess_2.sess.get_appservices_connection_id(),
2✔
6734
                    user_2_sess_2.sess.get_appservices_connection_id());
2✔
6735
}
2✔
6736

6737
// Tests that an empty reciprocal changesets is set and retrieved correctly.
6738
TEST(Sync_SetAndGetEmptyReciprocalChangeset)
6739
{
2✔
6740
    using namespace realm;
2✔
6741
    using namespace realm::sync::instr;
2✔
6742
    using realm::sync::Changeset;
2✔
6743

1✔
6744
    TEST_CLIENT_DB(db);
2✔
6745

1✔
6746
    auto& history = get_history(db);
2✔
6747
    history.set_client_file_ident(SaltedFileIdent{1, 0x1234567812345678}, false);
2✔
6748
    timestamp_type timestamp{1};
2✔
6749
    history.set_local_origin_timestamp_source([&] {
6✔
6750
        return ++timestamp;
6✔
6751
    });
6✔
6752

1✔
6753
    auto latest_local_version = [&] {
2✔
6754
        auto tr = db->start_write();
2✔
6755
        // Create schema: single table with array of ints as property.
1✔
6756
        tr->add_table_with_primary_key("class_table", type_Int, "_id")->add_column_list(type_Int, "ints");
2✔
6757
        tr->commit_and_continue_writing();
2✔
6758

1✔
6759
        // Create object and initialize array.
1✔
6760
        TableRef table = tr->get_table("class_table");
2✔
6761
        auto obj = table->create_object_with_primary_key(42);
2✔
6762
        auto ints = obj.get_list<int64_t>("ints");
2✔
6763
        for (auto i = 0; i < 8; ++i) {
18✔
6764
            ints.insert(i, i);
16✔
6765
        }
16✔
6766
        tr->commit_and_continue_writing();
2✔
6767

1✔
6768
        // Move element in array.
1✔
6769
        ints.move(7, 2);
2✔
6770
        return tr->commit();
2✔
6771
    }();
2✔
6772

1✔
6773
    // Create changeset which moves element from index 7 to index 0 in array.
1✔
6774
    // This changeset will discard the previous move (reciprocal changeset), leaving the local reciprocal changesets
1✔
6775
    // with no instructions (empty).
1✔
6776
    Changeset changeset;
2✔
6777
    ArrayMove instr;
2✔
6778
    instr.table = changeset.intern_string("table");
2✔
6779
    instr.object = instr::PrimaryKey{42};
2✔
6780
    instr.field = changeset.intern_string("ints");
2✔
6781
    instr.path.push_back(7);
2✔
6782
    instr.ndx_2 = 0;
2✔
6783
    instr.prior_size = 8;
2✔
6784
    changeset.push_back(instr);
2✔
6785
    changeset.version = 1;
2✔
6786
    changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6787
    changeset.origin_timestamp = timestamp;
2✔
6788
    changeset.origin_file_ident = 2;
2✔
6789

1✔
6790
    ChangesetEncoder::Buffer encoded;
2✔
6791
    std::vector<Transformer::RemoteChangeset> server_changesets_encoded;
2✔
6792
    encode_changeset(changeset, encoded);
2✔
6793
    server_changesets_encoded.emplace_back(changeset.version, changeset.last_integrated_remote_version,
2✔
6794
                                           BinaryData(encoded.data(), encoded.size()), changeset.origin_timestamp,
2✔
6795
                                           changeset.origin_file_ident);
2✔
6796

1✔
6797
    SyncProgress progress = {};
2✔
6798
    progress.download.server_version = changeset.version;
2✔
6799
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6800
    progress.latest_server_version.version = changeset.version;
2✔
6801
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6802

1✔
6803
    uint_fast64_t downloadable_bytes = 0;
2✔
6804
    VersionInfo version_info;
2✔
6805
    auto transact = db->start_read();
2✔
6806
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6807
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6808

1✔
6809
    bool is_compressed = false;
2✔
6810
    auto data = history.get_reciprocal_transform(latest_local_version, is_compressed);
2✔
6811
    Changeset reciprocal_changeset;
2✔
6812
    ChunkedBinaryInputStream in{data};
2✔
6813
    if (is_compressed) {
2✔
6814
        size_t total_size;
2✔
6815
        auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
2✔
6816
        CHECK(decompressed);
2✔
6817
        sync::parse_changeset(*decompressed, reciprocal_changeset); // Throws
2✔
6818
    }
2✔
6819
    else {
×
6820
        sync::parse_changeset(in, reciprocal_changeset); // Throws
×
6821
    }
×
6822
    // The only instruction in the reciprocal changeset was discarded during OT.
1✔
6823
    CHECK(reciprocal_changeset.empty());
2✔
6824
}
2✔
6825

6826
TEST(Sync_TransformAgainstEmptyReciprocalChangeset)
6827
{
2✔
6828
    TEST_CLIENT_DB(seed_db);
2✔
6829
    TEST_CLIENT_DB(db_1);
2✔
6830
    TEST_CLIENT_DB(db_2);
2✔
6831

1✔
6832
    {
2✔
6833
        auto tr = seed_db->start_write();
2✔
6834
        // Create schema: single table with array of ints as property.
1✔
6835
        auto table = tr->add_table_with_primary_key("class_table", type_Int, "_id");
2✔
6836
        table->add_column_list(type_Int, "ints");
2✔
6837
        table->add_column(type_String, "string");
2✔
6838
        tr->commit_and_continue_writing();
2✔
6839

1✔
6840
        // Create object and initialize array.
1✔
6841
        table = tr->get_table("class_table");
2✔
6842
        auto obj = table->create_object_with_primary_key(42);
2✔
6843
        auto ints = obj.get_list<int64_t>("ints");
2✔
6844
        for (auto i = 0; i < 8; ++i) {
18✔
6845
            ints.insert(i, i);
16✔
6846
        }
16✔
6847
        tr->commit();
2✔
6848
    }
2✔
6849

1✔
6850
    {
2✔
6851
        TEST_DIR(dir);
2✔
6852
        MultiClientServerFixture fixture(3, 1, dir, test_context);
2✔
6853
        fixture.start();
2✔
6854

1✔
6855
        util::Optional<Session> seed_session = fixture.make_bound_session(0, seed_db, 0, "/test");
2✔
6856
        util::Optional<Session> db_1_session = fixture.make_bound_session(1, db_1, 0, "/test");
2✔
6857
        util::Optional<Session> db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6858

1✔
6859
        seed_session->wait_for_upload_complete_or_client_stopped();
2✔
6860
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6861
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6862
        seed_session.reset();
2✔
6863
        db_2_session.reset();
2✔
6864

1✔
6865
        auto move_element = [&](const DBRef& db, size_t from, size_t to, size_t string_size = 0) {
8✔
6866
            auto wt = db->start_write();
8✔
6867
            auto table = wt->get_table("class_table");
8✔
6868
            auto obj = table->get_object_with_primary_key(42);
8✔
6869
            auto ints = obj.get_list<int64_t>("ints");
8✔
6870
            ints.move(from, to);
8✔
6871
            obj.set("string", std::string(string_size, 'a'));
8✔
6872
            wt->commit();
8✔
6873
        };
8✔
6874

1✔
6875
        // Client 1 uploads two move instructions.
1✔
6876
        move_element(db_1, 7, 2);
2✔
6877
        move_element(db_1, 7, 6);
2✔
6878

1✔
6879
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6880

1✔
6881
        std::this_thread::sleep_for(std::chrono::milliseconds{10});
2✔
6882

1✔
6883
        // Client 2 uploads two move instructions.
1✔
6884
        // The sync client uploads at most 128 KB of data so we make the first changeset large enough so two upload
1✔
6885
        // messages are sent to the server instead of one. Each change is transformed against the changes from
1✔
6886
        // Client 1.
1✔
6887

1✔
6888
        // First change discards the first change (move(7, 2)) of Client 1.
1✔
6889
        move_element(db_2, 7, 0, 200 * 1024);
2✔
6890
        // Second change is tranformed against an empty reciprocal changeset as result of the change above.
1✔
6891
        move_element(db_2, 7, 5);
2✔
6892
        db_2_session = fixture.make_bound_session(2, db_2, 0, "/test");
2✔
6893

1✔
6894
        db_1_session->wait_for_upload_complete_or_client_stopped();
2✔
6895
        db_2_session->wait_for_upload_complete_or_client_stopped();
2✔
6896

1✔
6897
        db_1_session->wait_for_download_complete_or_client_stopped();
2✔
6898
        db_2_session->wait_for_download_complete_or_client_stopped();
2✔
6899
    }
2✔
6900

1✔
6901
    ReadTransaction rt_1(db_1);
2✔
6902
    ReadTransaction rt_2(db_2);
2✔
6903
    const Group& group_1 = rt_1;
2✔
6904
    const Group& group_2 = rt_2;
2✔
6905
    group_1.verify();
2✔
6906
    group_2.verify();
2✔
6907
    CHECK(compare_groups(rt_1, rt_2));
2✔
6908
}
2✔
6909

6910
TEST(Sync_ServerVersionsSkippedFromDownloadCursor)
6911
{
2✔
6912
    TEST_CLIENT_DB(db);
2✔
6913

1✔
6914
    auto& history = get_history(db);
2✔
6915
    history.set_client_file_ident(SaltedFileIdent{2, 0x1234567812345678}, false);
2✔
6916
    timestamp_type timestamp{1};
2✔
6917
    history.set_local_origin_timestamp_source([&] {
2✔
6918
        return ++timestamp;
2✔
6919
    });
2✔
6920

1✔
6921
    auto latest_local_version = [&] {
2✔
6922
        auto tr = db->start_write();
2✔
6923
        tr->add_table_with_primary_key("class_foo", type_String, "_id")->add_column(type_Int, "int_col");
2✔
6924
        return tr->commit();
2✔
6925
    }();
2✔
6926

1✔
6927
    Changeset server_changeset;
2✔
6928
    server_changeset.version = 10;
2✔
6929
    server_changeset.last_integrated_remote_version = latest_local_version - 1;
2✔
6930
    server_changeset.origin_timestamp = ++timestamp;
2✔
6931
    server_changeset.origin_file_ident = 1;
2✔
6932

1✔
6933
    std::vector<ChangesetEncoder::Buffer> encoded;
2✔
6934
    std::vector<Transformer::RemoteChangeset> server_changesets_encoded;
2✔
6935
    encoded.emplace_back();
2✔
6936
    encode_changeset(server_changeset, encoded.back());
2✔
6937
    server_changesets_encoded.emplace_back(server_changeset.version, server_changeset.last_integrated_remote_version,
2✔
6938
                                           BinaryData(encoded.back().data(), encoded.back().size()),
2✔
6939
                                           server_changeset.origin_timestamp, server_changeset.origin_file_ident);
2✔
6940

1✔
6941
    SyncProgress progress = {};
2✔
6942
    // The server skips 10 server versions.
1✔
6943
    progress.download.server_version = server_changeset.version + 10;
2✔
6944
    progress.download.last_integrated_client_version = latest_local_version - 1;
2✔
6945
    progress.latest_server_version.version = server_changeset.version + 15;
2✔
6946
    progress.latest_server_version.salt = 0x7876543217654321;
2✔
6947

1✔
6948
    uint_fast64_t downloadable_bytes = 0;
2✔
6949
    VersionInfo version_info;
2✔
6950
    auto transact = db->start_read();
2✔
6951
    history.integrate_server_changesets(progress, &downloadable_bytes, server_changesets_encoded, version_info,
2✔
6952
                                        DownloadBatchState::SteadyState, *test_context.logger, transact);
2✔
6953

1✔
6954
    version_type current_version;
2✔
6955
    SaltedFileIdent file_ident;
2✔
6956
    SyncProgress expected_progress;
2✔
6957
    history.get_status(current_version, file_ident, expected_progress);
2✔
6958

1✔
6959
    // Check progress is reported correctly.
1✔
6960
    CHECK_EQUAL(progress.latest_server_version.salt, expected_progress.latest_server_version.salt);
2✔
6961
    CHECK_EQUAL(progress.latest_server_version.version, expected_progress.latest_server_version.version);
2✔
6962
    CHECK_EQUAL(progress.download.last_integrated_client_version,
2✔
6963
                expected_progress.download.last_integrated_client_version);
2✔
6964
    CHECK_EQUAL(progress.download.server_version, expected_progress.download.server_version);
2✔
6965
    CHECK_EQUAL(progress.upload.client_version, expected_progress.upload.client_version);
2✔
6966
    CHECK_EQUAL(progress.upload.last_integrated_server_version,
2✔
6967
                expected_progress.upload.last_integrated_server_version);
2✔
6968
}
2✔
6969

6970
} // 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