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

realm / realm-core / 2032

13 Feb 2024 12:49PM UTC coverage: 91.866% (+0.02%) from 91.844%
2032

push

Evergreen

web-flow
Add RealmConfig::needs_file_format_upgrade (#7336)

93052 of 171506 branches covered (54.26%)

36 of 45 new or added lines in 6 files covered. (80.0%)

79 existing lines in 15 files now uncovered.

235424 of 256268 relevant lines covered (91.87%)

6438906.58 hits per line

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

97.27
/test/test_shared.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include "testsettings.hpp"
20
#ifdef TEST_SHARED
21

22
#include <condition_variable>
23
#include <fstream>
24
#include <iostream>
25
#include <memory>
26
#include <streambuf>
27
#include <thread>
28
#include <tuple>
29

30
// Need fork() and waitpid() for Shared_RobustAgainstDeathDuringWrite
31
#ifndef _WIN32
32
#include <unistd.h>
33
#include <sys/mman.h>
34
#include <sys/types.h>
35
#include <sys/wait.h>
36
#include <csignal>
37
#include <sched.h>
38
#define ENABLE_ROBUST_AGAINST_DEATH_DURING_WRITE
39
#else
40
#include <windows.h>
41
#endif
42

43
#include <realm.hpp>
44
#include <realm/util/encrypted_file_mapping.hpp>
45
#include <realm/util/features.h>
46
#include <realm/util/file.hpp>
47
#include <realm/util/safe_int_ops.hpp>
48
#include <realm/util/scope_exit.hpp>
49
#include <realm/util/terminate.hpp>
50
#include <realm/util/thread.hpp>
51
#include <realm/util/to_string.hpp>
52
#include <realm/impl/copy_replication.hpp>
53
#include <realm/impl/simulated_failure.hpp>
54

55
#include "fuzz_group.hpp"
56

57
#include "test.hpp"
58
#include "test_table_helper.hpp"
59
#include "util/spawned_process.hpp"
60

61
extern unsigned int unit_test_random_seed;
62

63
using namespace realm;
64
using namespace realm::util;
65
using namespace realm::test_util;
66
using unit_test::TestContext;
67

68

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

98
#if 0
99
// Sorting benchmark
100
ONLY(Query_QuickSort2)
101
{
102
    Random random(random_int<unsigned long>()); // Seed from slow global generator
103

104
    // Triggers QuickSort because range > len
105
    Table ttt;
106
    auto ints = ttt.add_column(type_Int, "1");
107
    auto strings = ttt.add_column(type_String, "2");
108

109
    for (size_t t = 0; t < 10000; t++) {
110
        Obj o = ttt.create_object();
111
        //        o.set<int64_t>(ints, random.draw_int_mod(1100));
112
        o.set<StringData>(strings, "a");
113
    }
114

115
    Query q = ttt.where();
116

117
    std::cerr << "GO";
118

119
    for (size_t t = 0; t < 1000; t++) {
120
        TableView tv = q.find_all();
121
        tv.sort(strings);
122
        //        tv.ints(strings);
123
    }
124
}
125
#endif
126

127
#if REALM_WINDOWS
128
namespace {
129
// NOTE: This does not work like on POSIX: The child will begin execution from
130
// the unit test entry point, not from where fork() took place.
131
//
132
DWORD winfork(std::string unit_test_name)
133
{
134
    if (getenv("REALM_SPAWNED"))
135
        return GetCurrentProcessId();
136

137
    wchar_t filename[MAX_PATH];
138
    DWORD success = GetModuleFileName(nullptr, filename, MAX_PATH);
139
    if (success == 0 || success == MAX_PATH) {
140
        DWORD err = GetLastError();
141
        REALM_ASSERT_EX(false, err, MAX_PATH, filename);
142
    }
143

144
    GetModuleFileName(nullptr, filename, MAX_PATH);
145

146
    std::string environment;
147
    environment.append("REALM_SPAWNED=1");
148
    environment.append("\0", 1);
149
    environment.append("UNITTEST_FILTER=" + unit_test_name);
150
    environment.append("\0\0", 2);
151

152
    PROCESS_INFORMATION process;
153
    ZeroMemory(&process, sizeof(process));
154
    STARTUPINFO info;
155
    ZeroMemory(&info, sizeof(info));
156
    info.cb = sizeof(info);
157

158
    BOOL b = CreateProcess(filename, nullptr, 0, 0, false, 0, environment.data(), nullptr, &info, &process);
159
    REALM_ASSERT_RELEASE(b);
160

161
    CloseHandle(process.hProcess);
162
    CloseHandle(process.hThread);
163
    return process.dwProcessId;
164
}
165
} // namespace
166
#endif
167

168

169
namespace {
170

171
namespace {
172

173
std::vector<ColKey> test_table_add_columns(TableRef t)
174
{
28✔
175
    std::vector<ColKey> res;
28✔
176
    res.push_back(t->add_column(type_Int, "first"));
28✔
177
    res.push_back(t->add_column(type_Int, "second"));
28✔
178
    res.push_back(t->add_column(type_Bool, "third"));
28✔
179
    res.push_back(t->add_column(type_String, "fourth"));
28✔
180
    res.push_back(t->add_column(type_Timestamp, "fifth"));
28✔
181
    return res;
28✔
182
}
28✔
183
} // namespace
184

185
void writer(DBRef sg, uint64_t id)
186
{
2✔
187
    // std::cerr << "Started writer " << std::endl;
1✔
188
    try {
2✔
189
        auto tr = sg->start_read();
2✔
190
        bool done = false;
2✔
191
        // std::cerr << "Opened sg " << std::endl;
1✔
192
        for (int i = 0; !done; ++i) {
10✔
193
            // std::cerr << "       - " << getpid() << std::endl;
4✔
194
            tr->promote_to_write();
8✔
195
            auto t1 = tr->get_table("test");
8✔
196
            ColKeys _cols = t1->get_column_keys();
8✔
197
            std::vector<ColKey> cols;
8✔
198
            for (auto e : _cols)
8✔
199
                cols.push_back(e);
40✔
200
            Obj obj = t1->get_object(ObjKey(id));
8✔
201
            done = obj.get<Bool>(cols[2]);
8✔
202
            if (i & 1) {
8✔
203
                obj.add_int(cols[0], 1);
4✔
204
            }
4✔
205
            std::this_thread::yield(); // increase chance of signal arriving in the middle of a transaction
8✔
206
            tr->commit_and_continue_as_read();
8✔
207
        }
8✔
208
        // std::cerr << "Ended pid " << getpid() << std::endl;
1✔
209
        tr->end_read();
2✔
210
    }
2✔
211
    catch (...) {
1✔
212
        // std::cerr << "Exception from " << getpid() << std::endl;
213
        REALM_ASSERT(false);
×
214
    }
×
215
}
2✔
216

217
#if !defined(_WIN32) && !REALM_ENABLE_ENCRYPTION
218
void killer(TestContext& test_context, int pid, std::string path, int id)
219
{
220
    {
221
        DBRef sg = DB::create(path, true, DBOptions(crypt_key()));
222
        bool done = false;
223
        do {
224
            sched_yield();
225
            // pseudo randomized wait (to prevent unwanted synchronization effects of yield):
226
            int n = random() % 10000;
227
            volatile int thing = 0;
228
            while (n--)
229
                thing += random();
230
            ReadTransaction rt(sg);
231
            rt.get_group().verify();
232
            auto t1 = rt.get_table("test");
233
            auto cols = t1->get_column_keys();
234
            auto obj = t1->get_object(ObjKey(id));
235
            done = 10 < obj.get<Int>(cols[0]);
236
        } while (!done);
237
    }
238
    kill(pid, 9);
239
    int stat_loc = 0;
240
    int options = 0;
241
    int ret_pid = waitpid(pid, &stat_loc, options);
242
    if (ret_pid == pid_t(-1)) {
243
        if (errno == EINTR)
244
            std::cerr << "waitpid was interrupted" << std::endl;
245
        if (errno == EINVAL)
246
            std::cerr << "waitpid got bad arguments" << std::endl;
247
        if (errno == ECHILD)
248
            std::cerr << "waitpid tried to wait for the wrong child: " << pid << std::endl;
249
        REALM_TERMINATE("waitpid failed");
250
    }
251
    bool child_exited_from_signal = WIFSIGNALED(stat_loc);
252
    CHECK(child_exited_from_signal);
253
    int child_exit_status = WEXITSTATUS(stat_loc);
254
    CHECK_EQUAL(0, child_exit_status);
255
    {
256
        // Verify that we surely did kill the process before it could do all it's commits.
257
        DBRef sg = DB::create(path, true);
258
        ReadTransaction rt(sg);
259
        rt.get_group().verify();
260
        auto t1 = rt.get_table("test");
261
        auto cols = t1->get_column_keys();
262
        auto obj = t1->get_object(ObjKey(id));
263
        CHECK(10 < obj.get<Int>(cols[0]));
264
    }
265
}
266

267
#endif
268
} // anonymous namespace
269

270

271
#if !defined(_WIN32) && !REALM_ENABLE_ENCRYPTION && !REALM_ANDROID
272

273
TEST_IF(Shared_PipelinedWritesWithKills, false)
274
{
275
    // FIXME: This test was disabled because it has a strong tendency to leave
276
    // rogue child processes behind after the root test process aborts. If these
277
    // orphanned child processes are not manually searched for and killed, they
278
    // will run indefinitely. Additionally, these child processes will typically
279
    // grow a Realm file to gigantic sizes over time (100 gigabytes per 20
280
    // minutes).
281
    //
282
    // Idea for solution: Install a custom signal handler for SIGABRT and
283
    // friends, and kill all spawned child processes from it. See `man abort`.
284

285
    CHECK(RobustMutex::is_robust_on_this_platform);
286
    const int num_processes = 50;
287
    SHARED_GROUP_TEST_PATH(path);
288
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
289
    {
290
        // Create table entries
291
        WriteTransaction wt(sg);
292
        auto t1 = wt.add_table("test");
293
        test_table_add_columns(t1);
294
        for (int i = 0; i < num_processes; ++i) {
295
            t1->create_object().set_all(0, i, false, "test");
296
        }
297
        wt.commit();
298
    }
299
    int pid = fork();
300
    if (pid == -1)
301
        REALM_TERMINATE("fork() failed");
302
    if (pid == 0) {
303
        // first writer!
304
        writer(sg, 0);
305
        _Exit(0);
306
    }
307
    else {
308
        for (int k = 1; k < num_processes; ++k) {
309
            int pid2 = pid;
310
            pid = fork();
311
            if (pid == pid_t(-1))
312
                REALM_TERMINATE("fork() failed");
313
            if (pid == 0) {
314
                writer(sg, k);
315
                _Exit(0);
316
            }
317
            else {
318
                // std::cerr << "New process " << pid << " killing old " << pid2 << std::endl;
319
                killer(test_context, pid2, path, k - 1);
320
            }
321
        }
322
        // std::cerr << "Killing last one: " << pid << std::endl;
323
        killer(test_context, pid, path, num_processes - 1);
324
    }
325
    // We need to wait cleaning up til the killed processes have exited.
326
    millisleep(1000);
327
}
328

329
#endif
330

331
#if 0
332

333
// This unit test will test the case where the .realm file exceeds the available disk space. To run it, do
334
// following:
335
//
336
// 1: Create a drive that has around 10 MB free disk space *after* the realm-tests binary has been copied to it
337
// (you can fill up the drive with random data files until you hit 10 MB).
338
//
339
// Repeatedly run the realm-tests binary in a loop, like from a bash script. You can even make the bash script
340
// invoke `pkill realm-tests` with some intervals to test robustness too (if so, start the unit tests with `&`,
341
// i.e. `realm-tests&` so it runs in the background.
342

343
ONLY(Shared_DiskSpace)
344
{
345
    for (;;) {
346
        if (!File::exists("x")) {
347
            File f("x", realm::util::File::mode_Write);
348
            f.write(std::string(18 * 1024 * 1024, 'x'));
349
            f.close();
350
        }
351

352
        std::string path = "test.realm";
353

354
        SharedGroup sg(path, false, DBOptions("1234567890123456789012345678901123456789012345678901234567890123"));
355
        //    SharedGroup sg(path, false, SharedGroupOptions(nullptr));
356

357
        int seed = time(0);
358
        fastrand(seed, true);
359

360
        int foo = fastrand(100);
361
        if (foo > 50) {
362
            const Group& g = sg.begin_read();
363
            g.verify();
364
            continue;
365
        }
366

367
        int action = fastrand(100);
368

369
        WriteTransaction wt(sg);
370
        auto t1 = wt.get_or_add_table("test");
371

372
        t1->verify();
373

374
        if (t1->size() == 0) {
375
            t1->add_column(type_String, "name");
376
        }
377

378
        std::string str(fastrand(3000), 'a');
379

380
        size_t rows = fastrand(3000);
381

382
        for (int64_t i = 0; i < rows; ++i) {
383
            if (action < 55) {
384
                t1->add_empty_row();
385
                t1->set_string(0, t1->size() - 1, str.c_str());
386
            }
387
            else {
388
                if (t1->size() > 0) {
389
                    t1->remove(0);
390
                }
391
            }
392
        }
393

394
        if (fastrand(100) < 5) {
395
            File::try_remove("y");
396
            t1->clear();
397
            File::copy("x", "y");
398
        }
399

400
        if (fastrand(100) < 90) {
401
            wt.commit();
402
        }
403

404
        if (fastrand(100) < 5) {
405
            // Sometimes a special situation occurs where we cannot commit a t1-clear() due to low disk space, and where
406
            // compact also won't work because it has no space to write the new compacted file. The only way out of this
407
            // is to temporarely free up some disk space
408
            File::try_remove("y");
409
            sg.compact();
410
            File::copy("x", "y");
411
        }
412

413
    }
414
}
415

416
#endif // Only disables above special unit test
417

418

419
TEST(Shared_CompactingOnTheFly)
420
{
2✔
421
    SHARED_GROUP_TEST_PATH(path);
2✔
422
    Thread writer_thread;
2✔
423
    {
2✔
424
        DBRef sg = get_test_db(path, crypt_key());
2✔
425
        // Create table entries
1✔
426
        std::vector<ColKey> cols; // unsafe to hold colkeys across transaction! FIXME: should be reported
2✔
427
        {
2✔
428
            WriteTransaction wt(sg);
2✔
429
            auto t1 = wt.add_table("test");
2✔
430
            test_table_add_columns(t1);
2✔
431
            ColKeys _cols = t1->get_column_keys();
2✔
432
            for (auto e : _cols)
2✔
433
                cols.push_back(e);
10✔
434
            for (int i = 0; i < 100; ++i) {
202✔
435
                t1->create_object(ObjKey(i)).set_all(0, i, false, "test");
200✔
436
            }
200✔
437
            wt.commit();
2✔
438
        }
2✔
439
        {
2✔
440
            writer_thread.start(std::bind(&writer, sg, 41));
2✔
441

1✔
442
            // make sure writer has started:
1✔
443
            bool waiting = true;
2✔
444
            while (waiting) {
54✔
445
                std::this_thread::yield();
52✔
446
                ReadTransaction rt(sg);
52✔
447
                auto t1 = rt.get_table("test");
52✔
448
                const Obj obj = t1->get_object(ObjKey(41));
52✔
449
                waiting = obj.get<Int>(cols[0]) == 0;
52✔
450
                // std::cerr << t1->get_int(0, 41) << std::endl;
10✔
451
            }
52✔
452

1✔
453
            // since the writer is running, we cannot compact:
1✔
454
            CHECK(sg->compact() == false);
2✔
455
        }
2✔
456
        {
2✔
457
            // make the writer thread terminate:
1✔
458
            WriteTransaction wt(sg);
2✔
459
            auto t1 = wt.get_table("test");
2✔
460
            t1->get_object(ObjKey(41)).set(cols[2], true);
2✔
461
            wt.commit();
2✔
462
        }
2✔
463
        // we must join before the DB object goes out of scope
1✔
464
        writer_thread.join();
2✔
465
    }
2✔
466
    {
2✔
467
        DBRef sg2 = get_test_db(path, crypt_key());
2✔
468
        {
2✔
469
            WriteTransaction wt(sg2);
2✔
470
            wt.commit();
2✔
471
        }
2✔
472
        CHECK_EQUAL(true, sg2->compact());
2✔
473

1✔
474
        ReadTransaction rt2(sg2);
2✔
475
        auto table = rt2.get_table("test");
2✔
476
        CHECK(table);
2✔
477
        CHECK_EQUAL(table->size(), 100);
2✔
478
        rt2.get_group().verify();
2✔
479
    }
2✔
480
    {
2✔
481
        DBRef sg2 = get_test_db(path, crypt_key());
2✔
482
        ReadTransaction rt2(sg2);
2✔
483
        auto table = rt2.get_table("test");
2✔
484
        CHECK(table);
2✔
485
        CHECK_EQUAL(table->size(), 100);
2✔
486
        rt2.get_group().verify();
2✔
487
    }
2✔
488
}
2✔
489

490

491
TEST(Shared_ReadAfterCompact)
492
{
2✔
493
    SHARED_GROUP_TEST_PATH(path);
2✔
494
    DBRef sg = get_test_db(path);
2✔
495
    {
2✔
496
        WriteTransaction wt(sg);
2✔
497
        auto table = wt.add_table("table");
2✔
498
        table->add_column(type_Int, "col");
2✔
499
        table->create_object().set_all(1);
2✔
500
        wt.commit();
2✔
501
    }
2✔
502
    sg->compact();
2✔
503
    auto rt = sg->start_read();
2✔
504
    auto table = rt->get_table("table");
2✔
505
    for (int i = 2; i < 4; ++i) {
6✔
506
        WriteTransaction wt(sg);
4✔
507
        wt.get_table("table")->create_object().set_all(i);
4✔
508
        wt.commit();
4✔
509
    }
4✔
510

1✔
511
    CHECK_EQUAL(table->size(), 1);
2✔
512
    CHECK_EQUAL(table->get_object(0).get<int64_t>("col"), 1);
2✔
513
}
2✔
514

515
TEST(Shared_ReadOverRead)
516
{
2✔
517
    SHARED_GROUP_TEST_PATH(path);
2✔
518
    DBRef sg = get_test_db(path);
2✔
519
    {
2✔
520
        WriteTransaction wt(sg);
2✔
521
        auto table = wt.add_table("table");
2✔
522
        table->add_column(type_Int, "col");
2✔
523
        table->create_object().set_all(1);
2✔
524
        wt.commit();
2✔
525
    }
2✔
526
    DBRef sg2 = get_test_db(path);
2✔
527
    auto rt2 = sg2->start_read();
2✔
528
    auto table2 = rt2->get_table("table");
2✔
529
    for (int i = 2; i < 4; ++i) {
6✔
530
        WriteTransaction wt(sg2);
4✔
531
        wt.get_table("table")->create_object().set_all(i);
4✔
532
        wt.commit();
4✔
533
    }
4✔
534
    CHECK_EQUAL(table2->size(), 1);
2✔
535
    CHECK_EQUAL(table2->get_object(0).get<int64_t>("col"), 1);
2✔
536
}
2✔
537

538
TEST(Shared_ReadOverReadAfterCompact)
539
{
2✔
540
    SHARED_GROUP_TEST_PATH(path);
2✔
541
    DBRef sg = get_test_db(path);
2✔
542
    {
2✔
543
        WriteTransaction wt(sg);
2✔
544
        auto table = wt.add_table("table");
2✔
545
        table->add_column(type_Int, "col");
2✔
546
        table->create_object().set_all(1);
2✔
547
        wt.commit();
2✔
548
    }
2✔
549
    sg->compact();
2✔
550
    DBRef sg2 = get_test_db(path);
2✔
551
    auto rt = sg->start_read();
2✔
552
    auto rt2 = sg2->start_read();
2✔
553
    auto table = rt->get_table("table");
2✔
554
    for (int i = 2; i < 4; ++i) {
6✔
555
        WriteTransaction wt(sg);
4✔
556
        wt.get_table("table")->create_object().set_all(i);
4✔
557
        wt.commit();
4✔
558
    }
4✔
559
    CHECK_EQUAL(table->size(), 1);
2✔
560
    CHECK_EQUAL(table->get_object(0).get<int64_t>("col"), 1);
2✔
561

1✔
562
    auto table2 = rt2->get_table("table");
2✔
563
    for (int i = 2; i < 4; ++i) {
6✔
564
        WriteTransaction wt(sg2);
4✔
565
        wt.get_table("table")->create_object().set_all(i);
4✔
566
        wt.commit();
4✔
567
    }
4✔
568
    CHECK_EQUAL(table2->size(), 1);
2✔
569
    CHECK_EQUAL(table2->get_object(0).get<int64_t>("col"), 1);
2✔
570
}
2✔
571

572

573
TEST(Shared_ReadOverRead2)
574
{
2✔
575
    SHARED_GROUP_TEST_PATH(path);
2✔
576
    {
2✔
577
        DBRef sg = get_test_db(path);
2✔
578
        {
2✔
579
            WriteTransaction wt(sg);
2✔
580
            auto table = wt.add_table("table");
2✔
581
            table->add_column(type_Int, "col");
2✔
582
            table->create_object().set_all(1);
2✔
583
            wt.commit();
2✔
584
        }
2✔
585
    }
2✔
586
    DBRef sg2 = get_test_db(path);
2✔
587
    DBRef sg3 = get_test_db(path);
2✔
588
    auto rt2 = sg2->start_read();
2✔
589
    auto table2 = rt2->get_table("table");
2✔
590
    for (int i = 2; i < 4; ++i) {
6✔
591
        WriteTransaction wt(sg2);
4✔
592
        wt.get_table("table")->create_object().set_all(i);
4✔
593
        wt.commit();
4✔
594
    }
4✔
595
    CHECK_EQUAL(table2->size(), 1);
2✔
596
    CHECK_EQUAL(table2->get_object(0).get<int64_t>("col"), 1);
2✔
597
}
2✔
598

599

600
TEST(Shared_EncryptedRemap)
601
{
2✔
602
    // Attempts to trigger code coverage in util::mremap() for the case where the file is encrypted.
1✔
603
    // This requires a "non-encrypted database size" (not physical file size) which is non-divisible
1✔
604
    // by page_size() *and* is bigger than current allocated section. Following row count and payload
1✔
605
    // seems to work on both Windows+Linux
1✔
606
    const int64_t rows = 12;
2✔
607
    SHARED_GROUP_TEST_PATH(path);
2✔
608
    {
2✔
609
        DBRef sg = get_test_db(path, crypt_key());
2✔
610

1✔
611
        // Create table entries
1✔
612

1✔
613
        WriteTransaction wt(sg);
2✔
614
        auto t1 = wt.add_table("test");
2✔
615
        test_table_add_columns(t1);
2✔
616
        std::string str(100000, 'a');
2✔
617
        for (int64_t i = 0; i < rows; ++i) {
26✔
618
            t1->create_object().set_all(0, i, false, str.c_str());
24✔
619
        }
24✔
620
        wt.commit();
2✔
621
    }
2✔
622

1✔
623
    DBRef sg2 = get_test_db(path, crypt_key());
2✔
624

1✔
625
    CHECK_EQUAL(true, sg2->compact());
2✔
626
    ReadTransaction rt2(sg2);
2✔
627
    auto table = rt2.get_table("test");
2✔
628
    CHECK(table);
2✔
629
    CHECK_EQUAL(table->size(), rows);
2✔
630
    rt2.get_group().verify();
2✔
631
}
2✔
632

633

634
TEST(Shared_Initial)
635
{
2✔
636
    SHARED_GROUP_TEST_PATH(path);
2✔
637
    std::vector<char> key;
2✔
638

1✔
639
    CHECK_NOT(DB::needs_file_format_upgrade(path, key)); // File not created yet
2✔
640

1✔
641
    auto key_str = crypt_key();
2✔
642
    {
2✔
643
        // Create a new shared db
1✔
644
        DBRef sg = DB::create(path, false, DBOptions(key_str));
2✔
645

1✔
646
        // Verify that new group is empty
1✔
647
        {
2✔
648
            ReadTransaction rt(sg);
2✔
649
            CHECK(rt.get_group().is_empty());
2✔
650
        }
2✔
651
    }
2✔
652
    if (key_str) {
2✔
NEW
653
        key.insert(key.end(), key_str, key_str + strlen(key_str));
×
NEW
654
    }
×
655
    CHECK_NOT(DB::needs_file_format_upgrade(path, key));
2✔
656
}
2✔
657

658

659
TEST(Shared_InitialMem)
660
{
2✔
661
    SHARED_GROUP_TEST_PATH(path);
2✔
662
    {
2✔
663
        // Create a new shared db
1✔
664
        bool no_create = false;
2✔
665
        DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
666

1✔
667
        // Verify that new group is empty
1✔
668
        {
2✔
669
            ReadTransaction rt(sg);
2✔
670
            CHECK(rt.get_group().is_empty());
2✔
671
        }
2✔
672
    }
2✔
673

1✔
674
    // In MemOnly mode, the database file must be automatically
1✔
675
    // removed.
1✔
676
    CHECK(!File::exists(path));
2✔
677
}
2✔
678

679

680
TEST(Shared_InitialMem_StaleFile)
681
{
2✔
682
    SHARED_GROUP_TEST_PATH(path);
2✔
683

1✔
684
    // On platforms which do not support automatically deleting a file when it's
1✔
685
    // closed, MemOnly files won't be deleted if the process crashes, and so any
1✔
686
    // existing file at the given path should be overwritten if no one has the
1✔
687
    // file open
1✔
688

1✔
689
    // Create a MemOnly realm at the path so that a lock file gets initialized
1✔
690
    {
2✔
691
        bool no_create = false;
2✔
692
        DBRef r = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
693
    }
2✔
694
    CHECK(!File::exists(path));
2✔
695
    CHECK(File::exists(path.get_lock_path()));
2✔
696

1✔
697
    // Create a file at the DB path to fake a process crashing and failing to
1✔
698
    // delete it
1✔
699
    {
2✔
700
        File f(path, File::mode_Write);
2✔
701
        f.write("text");
2✔
702
    }
2✔
703
    CHECK(File::exists(path));
2✔
704
    CHECK(File::exists(path.get_lock_path()));
2✔
705

1✔
706
    // Verify that we can still open the path as a MemOnly SharedGroup and that
1✔
707
    // it's cleaned up afterwards
1✔
708
    {
2✔
709
        bool no_create = false;
2✔
710
        DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
711
        CHECK(File::exists(path));
2✔
712
    }
2✔
713
    CHECK(!File::exists(path));
2✔
714
    CHECK(File::exists(path.get_lock_path()));
2✔
715
}
2✔
716

717

718
TEST(Shared_Initial2)
719
{
2✔
720
    SHARED_GROUP_TEST_PATH(path);
2✔
721
    {
2✔
722
        // Create a new shared db
1✔
723
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
724

1✔
725
        {
2✔
726
            // Open the same db again (in empty state)
1✔
727
            DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
728

1✔
729
            // Verify that new group is empty
1✔
730
            {
2✔
731
                ReadTransaction rt(sg2);
2✔
732
                CHECK(rt.get_group().is_empty());
2✔
733
            }
2✔
734

1✔
735
            // Add a new table
1✔
736
            {
2✔
737
                WriteTransaction wt(sg2);
2✔
738
                wt.get_group().verify();
2✔
739
                auto t1 = wt.add_table("test");
2✔
740
                test_table_add_columns(t1);
2✔
741
                t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
742
                wt.commit();
2✔
743
            }
2✔
744
        }
2✔
745

1✔
746
        // Verify that the new table has been added
1✔
747
        {
2✔
748
            ReadTransaction rt(sg);
2✔
749
            rt.get_group().verify();
2✔
750
            auto t1 = rt.get_table("test");
2✔
751
            auto cols = t1->get_column_keys();
2✔
752
            CHECK_EQUAL(1, t1->size());
2✔
753
            const Obj obj = t1->get_object(ObjKey(7));
2✔
754
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
755
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
756
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
757
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
758
        }
2✔
759
    }
2✔
760
}
2✔
761

762

763
TEST(Shared_Initial2_Mem)
764
{
2✔
765
    SHARED_GROUP_TEST_PATH(path);
2✔
766
    {
2✔
767
        // Create a new shared db
1✔
768
        bool no_create = false;
2✔
769
        DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
770

1✔
771
        {
2✔
772
            // Open the same db again (in empty state)
1✔
773
            DBRef sg2 = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
774

1✔
775
            // Verify that new group is empty
1✔
776
            {
2✔
777
                ReadTransaction rt(sg2);
2✔
778
                CHECK(rt.get_group().is_empty());
2✔
779
            }
2✔
780

1✔
781
            // Add a new table
1✔
782
            {
2✔
783
                WriteTransaction wt(sg2);
2✔
784
                wt.get_group().verify();
2✔
785
                auto t1 = wt.add_table("test");
2✔
786
                test_table_add_columns(t1);
2✔
787
                t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
788
                wt.commit();
2✔
789
            }
2✔
790
        }
2✔
791

1✔
792
        // Verify that the new table has been added
1✔
793
        {
2✔
794
            ReadTransaction rt(sg);
2✔
795
            rt.get_group().verify();
2✔
796
            auto t1 = rt.get_table("test");
2✔
797
            auto cols = t1->get_column_keys();
2✔
798
            CHECK_EQUAL(1, t1->size());
2✔
799
            const Obj obj = t1->get_object(ObjKey(7));
2✔
800
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
801
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
802
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
803
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
804
        }
2✔
805
    }
2✔
806
}
2✔
807

808
TEST(Shared_1)
809
{
2✔
810
    SHARED_GROUP_TEST_PATH(path);
2✔
811
    {
2✔
812
        // Create a new shared db
1✔
813
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
814
        Timestamp first_timestamp_value{1, 1};
2✔
815
        std::vector<ColKey> cols;
2✔
816

1✔
817
        // Create first table in group
1✔
818
        {
2✔
819
            WriteTransaction wt(sg);
2✔
820
            wt.get_group().verify();
2✔
821
            auto t1 = wt.add_table("test");
2✔
822
            cols = test_table_add_columns(t1);
2✔
823
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test", Timestamp{1, 1});
2✔
824
            wt.commit();
2✔
825
        }
2✔
826
        {
2✔
827
            ReadTransaction rt(sg);
2✔
828
            rt.get_group().verify();
2✔
829

1✔
830
            // Verify that last set of changes are commited
1✔
831
            auto t2 = rt.get_table("test");
2✔
832
            CHECK(t2->size() == 1);
2✔
833
            const Obj obj = t2->get_object(ObjKey(7));
2✔
834
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
835
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
836
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
837
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
838
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
839

1✔
840
            // Do a new change while still having current read transaction open
1✔
841
            {
2✔
842
                WriteTransaction wt(sg);
2✔
843
                wt.get_group().verify();
2✔
844
                auto t1 = wt.get_table("test");
2✔
845
                t1->create_object(ObjKey(8)).set_all(2, 3, true, "more test", Timestamp{2, 2});
2✔
846
                wt.commit();
2✔
847
            }
2✔
848

1✔
849
            // Verify that that the read transaction does not see
1✔
850
            // the change yet (is isolated)
1✔
851
            CHECK(t2->size() == 1);
2✔
852
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
853
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
854
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
855
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
856
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
857
            // Do one more new change while still having current read transaction open
1✔
858
            // so we know that it does not overwrite data held by
1✔
859
            {
2✔
860
                WriteTransaction wt(sg);
2✔
861
                wt.get_group().verify();
2✔
862
                auto t1 = wt.get_table("test");
2✔
863
                t1->create_object(ObjKey(9)).set_all(0, 1, false, "even more test", Timestamp{3, 3});
2✔
864
                wt.commit();
2✔
865
            }
2✔
866

1✔
867
            // Verify that that the read transaction does still not see
1✔
868
            // the change yet (is isolated)
1✔
869
            CHECK(t2->size() == 1);
2✔
870
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
871
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
872
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
873
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
874
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
875
        }
2✔
876

1✔
877
        // Start a new read transaction and verify that it can now see the changes
1✔
878
        {
2✔
879
            ReadTransaction rt(sg);
2✔
880
            rt.get_group().verify();
2✔
881
            auto t3 = rt.get_table("test");
2✔
882

1✔
883
            CHECK(t3->size() == 3);
2✔
884
            const Obj obj7 = t3->get_object(ObjKey(7));
2✔
885
            CHECK_EQUAL(1, obj7.get<Int>(cols[0]));
2✔
886
            CHECK_EQUAL(2, obj7.get<Int>(cols[1]));
2✔
887
            CHECK_EQUAL(false, obj7.get<Bool>(cols[2]));
2✔
888
            CHECK_EQUAL("test", obj7.get<String>(cols[3]));
2✔
889
            CHECK_EQUAL(first_timestamp_value, obj7.get<Timestamp>(cols[4]));
2✔
890

1✔
891
            const Obj obj8 = t3->get_object(ObjKey(8));
2✔
892
            CHECK_EQUAL(2, obj8.get<Int>(cols[0]));
2✔
893
            CHECK_EQUAL(3, obj8.get<Int>(cols[1]));
2✔
894
            CHECK_EQUAL(true, obj8.get<Bool>(cols[2]));
2✔
895
            CHECK_EQUAL("more test", obj8.get<String>(cols[3]));
2✔
896
            Timestamp second_timestamp_value{2, 2};
2✔
897
            CHECK_EQUAL(second_timestamp_value, obj8.get<Timestamp>(cols[4]));
2✔
898

1✔
899
            const Obj obj9 = t3->get_object(ObjKey(9));
2✔
900
            CHECK_EQUAL(0, obj9.get<Int>(cols[0]));
2✔
901
            CHECK_EQUAL(1, obj9.get<Int>(cols[1]));
2✔
902
            CHECK_EQUAL(false, obj9.get<Bool>(cols[2]));
2✔
903
            CHECK_EQUAL("even more test", obj9.get<String>(cols[3]));
2✔
904
            Timestamp third_timestamp_value{3, 3};
2✔
905
            CHECK_EQUAL(third_timestamp_value, obj9.get<Timestamp>(cols[4]));
2✔
906
        }
2✔
907
    }
2✔
908
}
2✔
909

910
TEST(Shared_try_begin_write)
911
{
2✔
912
    SHARED_GROUP_TEST_PATH(path);
2✔
913
    // Create a new shared db
1✔
914
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
915
    std::mutex thread_obtains_write_lock;
2✔
916
    std::condition_variable cv;
2✔
917
    std::mutex cv_lock;
2✔
918
    bool init_complete = false;
2✔
919

1✔
920
    auto do_async = [&]() {
2✔
921
        auto tr = sg->start_write(true);
2✔
922
        bool success = bool(tr);
2✔
923
        CHECK(success);
2✔
924
        {
2✔
925
            std::lock_guard<std::mutex> lock(cv_lock);
2✔
926
            init_complete = true;
2✔
927
        }
2✔
928
        cv.notify_one();
2✔
929
        TableRef t = tr->add_table(StringData("table"));
2✔
930
        t->add_column(type_String, StringData("string_col"));
2✔
931
        std::vector<ObjKey> keys;
2✔
932
        t->create_objects(1000, keys);
2✔
933
        thread_obtains_write_lock.lock();
2✔
934
        tr->commit();
2✔
935
        thread_obtains_write_lock.unlock();
2✔
936
    };
2✔
937

1✔
938
    thread_obtains_write_lock.lock();
2✔
939
    Thread async_writer;
2✔
940
    async_writer.start(do_async);
2✔
941

1✔
942
    // wait for the thread to start a write transaction
1✔
943
    std::unique_lock<std::mutex> lock(cv_lock);
2✔
944
    cv.wait(lock, [&] {
4✔
945
        return init_complete;
4✔
946
    });
4✔
947

1✔
948
    // Try to also obtain a write lock. This should fail but not block.
1✔
949
    auto tr = sg->start_write(true);
2✔
950
    bool success = bool(tr);
2✔
951
    CHECK(!success);
2✔
952

1✔
953
    // Let the async thread finish its write transaction.
1✔
954
    thread_obtains_write_lock.unlock();
2✔
955
    async_writer.join();
2✔
956

1✔
957
    {
2✔
958
        // Verify that the thread transaction commit succeeded.
1✔
959
        auto rt = sg->start_read();
2✔
960
        ConstTableRef t = rt->get_table(rt->get_table_keys()[0]);
2✔
961
        CHECK(t->get_name() == StringData("table"));
2✔
962
        CHECK(t->get_column_name(t->get_column_keys()[0]) == StringData("string_col"));
2✔
963
        CHECK(t->size() == 1000);
2✔
964
        CHECK(rt->size() == 1);
2✔
965
    }
2✔
966

1✔
967
    // Now try to start a transaction without any contenders.
1✔
968
    tr = sg->start_write(true);
2✔
969
    success = bool(tr);
2✔
970
    CHECK(success);
2✔
971
    CHECK(tr->size() == 1);
2✔
972
    tr->verify();
2✔
973

1✔
974
    // Add some data and finish the transaction.
1✔
975
    auto t2k = tr->add_table(StringData("table 2"))->get_key();
2✔
976
    CHECK(tr->size() == 2);
2✔
977
    tr->commit();
2✔
978

1✔
979
    {
2✔
980
        // Verify that the main thread transaction now succeeded.
1✔
981
        ReadTransaction rt(sg);
2✔
982
        const Group& gr = rt.get_group();
2✔
983
        CHECK(gr.size() == 2);
2✔
984
        CHECK(gr.get_table(t2k)->get_name() == StringData("table 2"));
2✔
985
    }
2✔
986
}
2✔
987

988
TEST(Shared_Rollback)
989
{
2✔
990
    SHARED_GROUP_TEST_PATH(path);
2✔
991
    {
2✔
992
        // Create a new shared db
1✔
993
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
994
        std::vector<ColKey> cols;
2✔
995

1✔
996
        // Create first table in group (but rollback)
1✔
997
        {
2✔
998
            WriteTransaction wt(sg);
2✔
999
            wt.get_group().verify();
2✔
1000
            auto t1 = wt.add_table("test");
2✔
1001
            cols = test_table_add_columns(t1);
2✔
1002
            t1->create_object().set_all(1, 2, false, "test");
2✔
1003
            // Note: Implicit rollback
1✔
1004
        }
2✔
1005

1✔
1006
        // Verify that no changes were made
1✔
1007
        {
2✔
1008
            ReadTransaction rt(sg);
2✔
1009
            rt.get_group().verify();
2✔
1010
            CHECK(!rt.get_group().has_table("test"));
2✔
1011
        }
2✔
1012

1✔
1013
        // Really create first table in group
1✔
1014
        {
2✔
1015
            WriteTransaction wt(sg);
2✔
1016
            wt.get_group().verify();
2✔
1017
            auto t1 = wt.add_table("test");
2✔
1018
            cols = test_table_add_columns(t1);
2✔
1019
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1020
            wt.commit();
2✔
1021
        }
2✔
1022

1✔
1023
        // Verify that the changes were made
1✔
1024
        {
2✔
1025
            ReadTransaction rt(sg);
2✔
1026
            rt.get_group().verify();
2✔
1027
            auto t = rt.get_table("test");
2✔
1028
            CHECK(t->size() == 1);
2✔
1029
            const Obj obj = t->get_object(ObjKey(7));
2✔
1030
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1031
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1032
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1033
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1034
        }
2✔
1035

1✔
1036
        // Greate more changes (but rollback)
1✔
1037
        {
2✔
1038
            WriteTransaction wt(sg);
2✔
1039
            wt.get_group().verify();
2✔
1040
            auto t1 = wt.get_table("test");
2✔
1041
            t1->create_object(ObjKey(8)).set_all(0, 0, true, "more test");
2✔
1042
            // Note: Implicit rollback
1✔
1043
        }
2✔
1044

1✔
1045
        // Verify that no changes were made
1✔
1046
        {
2✔
1047
            ReadTransaction rt(sg);
2✔
1048
            rt.get_group().verify();
2✔
1049
            auto t = rt.get_table("test");
2✔
1050
            CHECK(t->size() == 1);
2✔
1051
            const Obj obj = t->get_object(ObjKey(7));
2✔
1052
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1053
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1054
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1055
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1056
        }
2✔
1057
    }
2✔
1058
}
2✔
1059

1060
TEST(Shared_Writes)
1061
{
2✔
1062
    SHARED_GROUP_TEST_PATH(path);
2✔
1063
    {
2✔
1064
        // Create a new shared db
1✔
1065
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1066
        std::vector<ColKey> cols;
2✔
1067

1✔
1068
        // Create first table in group
1✔
1069
        {
2✔
1070
            WriteTransaction wt(sg);
2✔
1071
            wt.get_group().verify();
2✔
1072
            auto t1 = wt.add_table("test");
2✔
1073
            cols = test_table_add_columns(t1);
2✔
1074
            t1->create_object(ObjKey(7)).set_all(0, 2, false, "test");
2✔
1075
            wt.commit();
2✔
1076
        }
2✔
1077

1✔
1078
        // Do a lot of repeated write transactions
1✔
1079
        for (size_t i = 0; i < 100; ++i) {
202✔
1080
            WriteTransaction wt(sg);
200✔
1081
            wt.get_group().verify();
200✔
1082
            auto t1 = wt.get_table("test");
200✔
1083
            t1->get_object(ObjKey(7)).add_int(cols[0], 1);
200✔
1084
            wt.commit();
200✔
1085
        }
200✔
1086

1✔
1087
        // Verify that the changes were made
1✔
1088
        {
2✔
1089
            ReadTransaction rt(sg);
2✔
1090
            rt.get_group().verify();
2✔
1091
            auto t = rt.get_table("test");
2✔
1092
            const int64_t v = t->get_object(ObjKey(7)).get<Int>(cols[0]);
2✔
1093
            CHECK_EQUAL(100, v);
2✔
1094
        }
2✔
1095
    }
2✔
1096
}
2✔
1097

1098
#if !REALM_ANDROID // FIXME
1099
TEST(Shared_ManyReaders)
1100
{
2✔
1101
    // This test was written primarily to expose a former bug in
1✔
1102
    // SharedGroup::end_read(), where the lock-file was not remapped
1✔
1103
    // after ring-buffer expansion.
1✔
1104

1✔
1105
    const int chunk_1_size = 251;
2✔
1106
    char chunk_1[chunk_1_size];
2✔
1107
    for (int i = 0; i < chunk_1_size; ++i)
504✔
1108
        chunk_1[i] = (i + 3) % 251;
502✔
1109
    const int chunk_2_size = 123;
2✔
1110
    char chunk_2[chunk_2_size];
2✔
1111
    for (int i = 0; i < chunk_2_size; ++i)
248✔
1112
        chunk_2[i] = (i + 11) % 241;
246✔
1113

1✔
1114
#if TEST_DURATION < 1
2✔
1115
    // Mac OS X 10.8 cannot handle more than 15 due to its default ulimit settings.
1✔
1116
    int rounds[] = {3, 5, 7, 9, 11, 13};
2✔
1117
#elif REALM_DEBUG // this test is disproportionately slower in debug
1118
    int rounds[] = {3, 5, 7, 9, 11, 13, 15, 17, 23};
1119
#else
1120
    int rounds[] = {3, 5, 11, 15, 17, 23, 27, 31, 47, 59};
1121
#endif
1122
    const int num_rounds = sizeof rounds / sizeof *rounds;
2✔
1123

1✔
1124
    const int max_N = 64;
2✔
1125
    CHECK(max_N >= rounds[num_rounds - 1]);
2✔
1126
    DBRef shared_groups[8 * max_N];
2✔
1127
    TransactionRef read_transactions[8 * max_N];
2✔
1128
    ColKey col_int;
2✔
1129
    ColKey col_bin;
2✔
1130

1✔
1131
    auto add_int = [](Table& table, ColKey col, int64_t diff) {
1,536✔
1132
        for (auto& o : table) {
1,536✔
1133
            o.add_int(col, diff);
1,536✔
1134
        }
1,536✔
1135
    };
1,536✔
1136

1✔
1137
    for (int round = 0; round < num_rounds; ++round) {
14✔
1138
        int N = rounds[round];
12✔
1139

6✔
1140
        SHARED_GROUP_TEST_PATH(path);
12✔
1141

6✔
1142
        bool no_create = false;
12✔
1143
        auto root_sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
12✔
1144

6✔
1145
        // Add two tables
6✔
1146
        {
12✔
1147
            WriteTransaction wt(root_sg);
12✔
1148
            wt.get_group().verify();
12✔
1149
            bool was_added = false;
12✔
1150
            TableRef test_1 = wt.get_or_add_table("test_1", Table::Type::TopLevel, &was_added);
12✔
1151
            if (was_added) {
12✔
1152
                col_int = test_1->add_column(type_Int, "i");
12✔
1153
            }
12✔
1154
            test_1->create_object().set(col_int, 0);
12✔
1155
            TableRef test_2 = wt.get_or_add_table("test_2", Table::Type::TopLevel, &was_added);
12✔
1156
            if (was_added) {
12✔
1157
                col_bin = test_2->add_column(type_Binary, "b");
12✔
1158
            }
12✔
1159
            wt.commit();
12✔
1160
        }
12✔
1161

6✔
1162

6✔
1163
        // Create 8*N shared group accessors
6✔
1164
        for (int i = 0; i < 8 * N; ++i)
780✔
1165
            shared_groups[i] = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
768✔
1166

6✔
1167
        // Initiate 2*N read transactions with progressive changes
6✔
1168
        for (int i = 0; i < 2 * N; ++i) {
204✔
1169
            read_transactions[i] = shared_groups[i]->start_read();
192✔
1170
            read_transactions[i]->verify();
192✔
1171
            {
192✔
1172
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
192✔
1173
                CHECK_EQUAL(1u, test_1->size());
192✔
1174
                CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
192✔
1175
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
192✔
1176
                int n_1 = i * 1;
192✔
1177
                int n_2 = i * 18;
192✔
1178
                CHECK_EQUAL(n_1 + n_2, test_2->size());
192✔
1179
                for (int j = 0; j < n_1 + n_2; ++j) {
32,872✔
1180
                    if (j % 19 == 0) {
32,680✔
1181
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
1,720✔
1182
                    }
1,720✔
1183
                    else {
30,960✔
1184
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
30,960✔
1185
                    }
30,960✔
1186
                }
32,680✔
1187
            }
192✔
1188
            {
192✔
1189
                WriteTransaction wt(root_sg);
192✔
1190
                wt.get_group().verify();
192✔
1191
                TableRef test_1 = wt.get_table("test_1");
192✔
1192
                add_int(*test_1, col_int, 1);
192✔
1193
                TableRef test_2 = wt.get_table("test_2");
192✔
1194
                test_2->create_object().set(col_bin, BinaryData(chunk_1));
192✔
1195
                wt.commit();
192✔
1196
            }
192✔
1197
            {
192✔
1198
                WriteTransaction wt(root_sg);
192✔
1199
                wt.get_group().verify();
192✔
1200
                TableRef test_2 = wt.get_table("test_2");
192✔
1201
                for (int j = 0; j < 18; ++j) {
3,648✔
1202
                    test_2->create_object().set(col_bin, BinaryData(chunk_2));
3,456✔
1203
                }
3,456✔
1204
                wt.commit();
192✔
1205
            }
192✔
1206
        }
192✔
1207

6✔
1208
        // Check isolation between read transactions
6✔
1209
        for (int i = 0; i < 2 * N; ++i) {
204✔
1210
            ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
192✔
1211
            CHECK_EQUAL(1, test_1->size());
192✔
1212
            CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
192✔
1213
            ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
192✔
1214
            int n_1 = i * 1;
192✔
1215
            int n_2 = i * 18;
192✔
1216
            CHECK_EQUAL(n_1 + n_2, test_2->size());
192✔
1217
            for (int j = 0; j < n_1 + n_2; ++j) {
32,872✔
1218
                if (j % 19 == 0) {
32,680✔
1219
                    CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
1,720✔
1220
                }
1,720✔
1221
                else {
30,960✔
1222
                    CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
30,960✔
1223
                }
30,960✔
1224
            }
32,680✔
1225
        }
192✔
1226

6✔
1227
        // End the first half of the read transactions during further
6✔
1228
        // changes
6✔
1229
        for (int i = N - 1; i >= 0; --i) {
108✔
1230
            {
96✔
1231
                WriteTransaction wt(root_sg);
96✔
1232
#if !defined(_WIN32) || TEST_DURATION > 0 // These .verify() calls are horribly slow on Windows
96✔
1233
                wt.get_group().verify();
96✔
1234
#endif
96✔
1235
                TableRef test_1 = wt.get_table("test_1");
96✔
1236
                add_int(*test_1, col_int, 2);
96✔
1237
                wt.commit();
96✔
1238
            }
96✔
1239
            {
96✔
1240
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
96✔
1241
                CHECK_EQUAL(1, test_1->size());
96✔
1242
                CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
96✔
1243
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
96✔
1244
                int n_1 = i * 1;
96✔
1245
                int n_2 = i * 18;
96✔
1246
                CHECK_EQUAL(n_1 + n_2, test_2->size());
96✔
1247
                for (int j = 0; j < n_1 + n_2; ++j) {
7,810✔
1248
                    if (j % 19 == 0) {
7,714✔
1249
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
406✔
1250
                    }
406✔
1251
                    else {
7,308✔
1252
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
7,308✔
1253
                    }
7,308✔
1254
                }
7,714✔
1255
            }
96✔
1256
            read_transactions[i] = nullptr;
96✔
1257
        }
96✔
1258

6✔
1259
        // Initiate 6*N extra read transactionss with further progressive changes
6✔
1260
        for (int i = 2 * N; i < 8 * N; ++i) {
588✔
1261
            read_transactions[i] = shared_groups[i]->start_read();
576✔
1262
#if !defined(_WIN32) || TEST_DURATION > 0
576✔
1263
            read_transactions[i]->verify();
576✔
1264
#endif
576✔
1265
            {
576✔
1266
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
576✔
1267
                CHECK_EQUAL(1u, test_1->size());
576✔
1268
                int i_2 = 2 * N + i;
576✔
1269
                CHECK_EQUAL(i_2, test_1->begin()->get<Int>(col_int));
576✔
1270
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
576✔
1271
                int n_1 = i * 1;
576✔
1272
                int n_2 = i * 18;
576✔
1273
                CHECK_EQUAL(n_1 + n_2, test_2->size());
576✔
1274
                for (int j = 0; j < n_1 + n_2; ++j) {
512,664✔
1275
                    if (j % 19 == 0) {
512,088✔
1276
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
26,952✔
1277
                    }
26,952✔
1278
                    else {
485,136✔
1279
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
485,136✔
1280
                    }
485,136✔
1281
                }
512,088✔
1282
            }
576✔
1283
            {
576✔
1284
                WriteTransaction wt(root_sg);
576✔
1285
#if !defined(_WIN32) || TEST_DURATION > 0
576✔
1286
                wt.get_group().verify();
576✔
1287
#endif
576✔
1288
                TableRef test_1 = wt.get_table("test_1");
576✔
1289
                add_int(*test_1, col_int, 1);
576✔
1290
                TableRef test_2 = wt.get_table("test_2");
576✔
1291
                test_2->create_object().set(col_bin, BinaryData(chunk_1));
576✔
1292
                wt.commit();
576✔
1293
            }
576✔
1294
            {
576✔
1295
                WriteTransaction wt(root_sg);
576✔
1296
#if !defined(_WIN32) || TEST_DURATION > 0
576✔
1297
                wt.get_group().verify();
576✔
1298
#endif
576✔
1299
                TableRef test_2 = wt.get_table("test_2");
576✔
1300
                for (int j = 0; j < 18; ++j) {
10,944✔
1301
                    test_2->create_object().set(col_bin, BinaryData(chunk_2));
10,368✔
1302
                }
10,368✔
1303
                wt.commit();
576✔
1304
            }
576✔
1305
        }
576✔
1306

6✔
1307
        // End all remaining read transactions during further changes
6✔
1308
        for (int i = 1 * N; i < 8 * N; ++i) {
684✔
1309
            {
672✔
1310
                WriteTransaction wt(root_sg);
672✔
1311
#if !defined(_WIN32) || TEST_DURATION > 0
672✔
1312
                wt.get_group().verify();
672✔
1313
#endif
672✔
1314
                TableRef test_1 = wt.get_table("test_1");
672✔
1315
                add_int(*test_1, col_int, 2);
672✔
1316
                wt.commit();
672✔
1317
            }
672✔
1318
            {
672✔
1319
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
672✔
1320
                CHECK_EQUAL(1, test_1->size());
672✔
1321
                int i_2 = i < 2 * N ? i : 2 * N + i;
624✔
1322
                CHECK_EQUAL(i_2, test_1->begin()->get<Int>(col_int));
672✔
1323
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
672✔
1324
                int n_1 = i * 1;
672✔
1325
                int n_2 = i * 18;
672✔
1326
                CHECK_EQUAL(n_1 + n_2, test_2->size());
672✔
1327
                for (int j = 0; j < n_1 + n_2; ++j) {
537,726✔
1328
                    if (j % 19 == 0) {
537,054✔
1329
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
28,266✔
1330
                    }
28,266✔
1331
                    else {
508,788✔
1332
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
508,788✔
1333
                    }
508,788✔
1334
                }
537,054✔
1335
            }
672✔
1336
            read_transactions[i] = nullptr;
672✔
1337
        }
672✔
1338

6✔
1339
        // Check final state via each shared group, then destroy it
6✔
1340
        for (int i = 0; i < 8 * N; ++i) {
780✔
1341
            {
768✔
1342
                ReadTransaction rt(shared_groups[i]);
768✔
1343
#if !defined(_WIN32) || TEST_DURATION > 0
768✔
1344
                rt.get_group().verify();
768✔
1345
#endif
768✔
1346
                ConstTableRef test_1 = rt.get_table("test_1");
768✔
1347
                CHECK_EQUAL(1, test_1->size());
768✔
1348
                CHECK_EQUAL(3 * 8 * N, test_1->begin()->get<Int>(col_int));
768✔
1349
                ConstTableRef test_2 = rt.get_table("test_2");
768✔
1350
                int n_1 = 8 * N * 1;
768✔
1351
                int n_2 = 8 * N * 18;
768✔
1352
                CHECK_EQUAL(n_1 + n_2, test_2->size());
768✔
1353
                for (int j = 0; j < n_1 + n_2; ++j) {
1,104,896✔
1354
                    if (j % 19 == 0) {
1,104,128✔
1355
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
58,112✔
1356
                    }
58,112✔
1357
                    else {
1,046,016✔
1358
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
1,046,016✔
1359
                    }
1,046,016✔
1360
                }
1,104,128✔
1361
            }
768✔
1362
            shared_groups[i] = nullptr;
768✔
1363
        }
768✔
1364

6✔
1365
        // Check final state via new shared group
6✔
1366
        {
12✔
1367
            DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
12✔
1368
            ReadTransaction rt(sg);
12✔
1369
#if !defined(_WIN32) || TEST_DURATION > 0
12✔
1370
            rt.get_group().verify();
12✔
1371
#endif
12✔
1372
            ConstTableRef test_1 = rt.get_table("test_1");
12✔
1373
            CHECK_EQUAL(1, test_1->size());
12✔
1374
            CHECK_EQUAL(3 * 8 * N, test_1->begin()->get<Int>(col_int));
12✔
1375
            ConstTableRef test_2 = rt.get_table("test_2");
12✔
1376
            int n_1 = 8 * N * 1;
12✔
1377
            int n_2 = 8 * N * 18;
12✔
1378
            CHECK_EQUAL(n_1 + n_2, test_2->size());
12✔
1379
            for (int j = 0; j < n_1 + n_2; ++j) {
14,604✔
1380
                if (j % 19 == 0) {
14,592✔
1381
                    CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
768✔
1382
                }
768✔
1383
                else {
13,824✔
1384
                    CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
13,824✔
1385
                }
13,824✔
1386
            }
14,592✔
1387
        }
12✔
1388
    }
12✔
1389
}
2✔
1390
#endif
1391

1392
// This test is a minimal repro. of core issue #842.
1393
TEST(Many_ConcurrentReaders)
1394
{
2✔
1395
    SHARED_GROUP_TEST_PATH(path);
2✔
1396
    const std::string path_str = path;
2✔
1397

1✔
1398
    // setup
1✔
1399
    DBRef sg_w = DB::create(path_str);
2✔
1400
    WriteTransaction wt(sg_w);
2✔
1401
    TableRef t = wt.add_table("table");
2✔
1402
    auto col_ndx = t->add_column(type_String, "column");
2✔
1403
    t->create_object().set(col_ndx, StringData("string"));
2✔
1404
    wt.commit();
2✔
1405
    sg_w->close();
2✔
1406

1✔
1407
    auto reader = [path_str]() {
8✔
1408
        std::stringstream logs;
8✔
1409
        try {
8✔
1410
            auto logger = util::StreamLogger(logs);
8✔
1411
            DBOptions options;
8✔
1412
            options.logger = std::make_shared<util::StreamLogger>(logs);
8✔
1413
            options.logger->set_level_threshold(Logger::Level::all);
8✔
1414
            constexpr bool no_create = false;
8✔
1415
            for (int i = 0; i < 1000; ++i) {
8,008✔
1416
                DBRef sg_r = DB::create(path_str, no_create, options);
8,000✔
1417
                ReadTransaction rt(sg_r);
8,000✔
1418
                ConstTableRef t = rt.get_table("table");
8,000✔
1419
                auto col_key = t->get_column_key("column");
8,000✔
1420
                REALM_ASSERT(t->get_object(0).get<StringData>(col_key) == "string");
8,000✔
1421
                rt.get_group().verify();
8,000✔
1422
            }
8,000✔
1423
        }
8✔
1424
        catch (const std::exception& e) {
4✔
1425
            std::cerr << "Exception during Many_ConcurrentReaders:" << std::endl;
×
1426
            std::cerr << "Reason: '" << e.what() << "'" << std::endl;
×
1427
            std::cerr << logs.str();
×
1428
            constexpr bool unexpected_exception = false;
×
1429
            REALM_ASSERT_EX(unexpected_exception, e.what());
×
1430
        }
×
1431
    };
8✔
1432

1✔
1433
    constexpr int num_threads = 4;
2✔
1434
    Thread threads[num_threads];
2✔
1435
    for (int i = 0; i < num_threads; ++i) {
10✔
1436
        threads[i].start(reader);
8✔
1437
    }
8✔
1438
    for (int i = 0; i < num_threads; ++i) {
10✔
1439
        threads[i].join();
8✔
1440
    }
8✔
1441
}
2✔
1442

1443

1444
TEST(Shared_WritesSpecialOrder)
1445
{
2✔
1446
    SHARED_GROUP_TEST_PATH(path);
2✔
1447
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1448

1✔
1449
    const int num_rows =
2✔
1450
        5; // FIXME: Should be strictly greater than REALM_MAX_BPNODE_SIZE, but that takes too long time.
2✔
1451
    const int num_reps = 25;
2✔
1452

1✔
1453
    {
2✔
1454
        WriteTransaction wt(sg);
2✔
1455
        wt.get_group().verify();
2✔
1456
        auto table = wt.add_table("test");
2✔
1457
        auto col = table->add_column(type_Int, "first");
2✔
1458
        for (int i = 0; i < num_rows; ++i) {
12✔
1459
            table->create_object(ObjKey(i)).set(col, 0);
10✔
1460
        }
10✔
1461
        wt.commit();
2✔
1462
    }
2✔
1463

1✔
1464
    for (int i = 0; i < num_rows; ++i) {
12✔
1465
        for (int j = 0; j < num_reps; ++j) {
260✔
1466
            {
250✔
1467
                WriteTransaction wt(sg);
250✔
1468
                wt.get_group().verify();
250✔
1469
                auto table = wt.get_table("test");
250✔
1470
                auto col = table->get_column_key("first");
250✔
1471
                Obj obj = table->get_object(ObjKey(i));
250✔
1472
                CHECK_EQUAL(j, obj.get<Int>(col));
250✔
1473
                obj.add_int(col, 1);
250✔
1474
                wt.commit();
250✔
1475
            }
250✔
1476
        }
250✔
1477
    }
10✔
1478

1✔
1479
    {
2✔
1480
        ReadTransaction rt(sg);
2✔
1481
        rt.get_group().verify();
2✔
1482
        auto table = rt.get_table("test");
2✔
1483
        auto col = table->get_column_key("first");
2✔
1484
        for (int i = 0; i < num_rows; ++i) {
12✔
1485
            CHECK_EQUAL(num_reps, table->get_object(ObjKey(i)).get<Int>(col));
10✔
1486
        }
10✔
1487
    }
2✔
1488
}
2✔
1489

1490
namespace {
1491

1492
void writer_threads_thread(TestContext& test_context, const DBRef& sg, ObjKey key)
1493
{
16✔
1494

7✔
1495
    for (size_t i = 0; i < 100; ++i) {
2,011✔
1496
        // Increment cell
997✔
1497
        {
1,995✔
1498
            WriteTransaction wt(sg);
1,995✔
1499
            wt.get_group().verify();
1,995✔
1500
            auto t1 = wt.get_table("test");
1,995✔
1501
            auto cols = t1->get_column_keys();
1,995✔
1502
            t1->get_object(key).add_int(cols[0], 1);
1,995✔
1503
            // FIXME: For some reason this takes ages when running
997✔
1504
            // inside valgrind, it is probably due to the "extreme
997✔
1505
            // overallocation" bug. The 1000 transactions performed
997✔
1506
            // here can produce a final database file size of more
997✔
1507
            // than 1 GiB. Really! And that is a table with only 10
997✔
1508
            // rows. It is about 1 MiB per transaction.
997✔
1509
            wt.commit();
1,995✔
1510
        }
1,995✔
1511

997✔
1512
        // Verify in new transaction so that we interleave
997✔
1513
        // read and write transactions
997✔
1514
        {
1,995✔
1515
            ReadTransaction rt(sg);
1,995✔
1516
            // rt.get_group().verify(); // verify() is not supported on a read transaction
997✔
1517
            // concurrently with writes.
997✔
1518
            auto t = rt.get_table("test");
1,995✔
1519
            auto cols = t->get_column_keys();
1,995✔
1520
            int64_t v = t->get_object(key).get<Int>(cols[0]);
1,995✔
1521
            int64_t expected = i + 1;
1,995✔
1522
            CHECK_EQUAL(expected, v);
1,995✔
1523
        }
1,995✔
1524
    }
1,995✔
1525
}
16✔
1526

1527
} // anonymous namespace
1528

1529
TEST(Shared_WriterThreads)
1530
{
2✔
1531
    SHARED_GROUP_TEST_PATH(path);
2✔
1532
    {
2✔
1533
        // Create a new shared db
1✔
1534
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1535

1✔
1536
        const int thread_count = 10;
2✔
1537
        // Create first table in group
1✔
1538
        {
2✔
1539
            WriteTransaction wt(sg);
2✔
1540
            wt.get_group().verify();
2✔
1541
            auto t1 = wt.add_table("test");
2✔
1542
            test_table_add_columns(t1);
2✔
1543
            for (int i = 0; i < thread_count; ++i)
22✔
1544
                t1->create_object(ObjKey(i)).set_all(0, 2, false, "test");
20✔
1545
            wt.commit();
2✔
1546
        }
2✔
1547

1✔
1548
        Thread threads[thread_count];
2✔
1549

1✔
1550
        // Create all threads
1✔
1551
        for (int i = 0; i < thread_count; ++i)
22✔
1552
            threads[i].start([this, &sg, i] {
20✔
1553
                writer_threads_thread(test_context, sg, ObjKey(i));
19✔
1554
            });
19✔
1555

1✔
1556
        // Wait for all threads to complete
1✔
1557
        for (int i = 0; i < thread_count; ++i)
22✔
1558
            threads[i].join();
20✔
1559

1✔
1560
        // Verify that the changes were made
1✔
1561
        {
2✔
1562
            ReadTransaction rt(sg);
2✔
1563
            rt.get_group().verify();
2✔
1564
            auto t = rt.get_table("test");
2✔
1565
            auto col = t->get_column_keys()[0];
2✔
1566

1✔
1567
            for (int i = 0; i < thread_count; ++i) {
22✔
1568
                int64_t v = t->get_object(ObjKey(i)).get<Int>(col);
20✔
1569
                CHECK_EQUAL(100, v);
20✔
1570
            }
20✔
1571
        }
2✔
1572
    }
2✔
1573
}
2✔
1574

1575

1576
#if !REALM_ENABLE_ENCRYPTION && defined(ENABLE_ROBUST_AGAINST_DEATH_DURING_WRITE)
1577
// this unittest has issues that has not been fully understood, but could be
1578
// related to interaction between posix robust mutexes and the fork() system call.
1579
// it has so far only been seen failing on Linux, so we enable it on ios.
1580
#if REALM_PLATFORM_APPLE
1581

1582
// Not supported on Windows in particular? Keywords: winbug
1583
TEST(Shared_RobustAgainstDeathDuringWrite)
1584
{
1585
    // Abort if robust mutexes are not supported on the current
1586
    // platform. Otherwise we would probably get into a dead-lock.
1587
    if (!RobustMutex::is_robust_on_this_platform)
1588
        return;
1589

1590
    // This test can only be conducted by spawning independent
1591
    // processes which can then be terminated individually.
1592
    const int process_count = 100;
1593
    SHARED_GROUP_TEST_PATH(path);
1594
    ColKey col_int;
1595

1596
    auto add_int = [](Table& table, ColKey col, int64_t diff) {
1597
        for (auto& o : table) {
1598
            o.add_int(col, diff);
1599
        }
1600
    };
1601

1602
    for (int i = 0; i < process_count; ++i) {
1603
        pid_t pid = fork();
1604
        if (pid == pid_t(-1))
1605
            REALM_TERMINATE("fork() failed");
1606
        if (pid == 0) {
1607
            // Child
1608
            DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
1609
            WriteTransaction wt(sg);
1610
            wt.get_group().verify();
1611
            wt.get_or_add_table("alpha");
1612
            _Exit(42); // Die hard with an active write transaction
1613
        }
1614
        else {
1615
            // Parent
1616
            int stat_loc = 0;
1617
            int options = 0;
1618
            pid = waitpid(pid, &stat_loc, options);
1619
            if (pid == pid_t(-1))
1620
                REALM_TERMINATE("waitpid() failed");
1621
            bool child_exited_normaly = WIFEXITED(stat_loc);
1622
            CHECK(child_exited_normaly);
1623
            int child_exit_status = WEXITSTATUS(stat_loc);
1624
            CHECK_EQUAL(42, child_exit_status);
1625
        }
1626

1627
        // Check that we can continue without dead-locking
1628
        {
1629
            DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
1630
            WriteTransaction wt(sg);
1631
            wt.get_group().verify();
1632
            TableRef table = wt.get_or_add_table("beta");
1633
            if (table->is_empty()) {
1634
                col_int = table->add_column(type_Int, "i");
1635
                table->create_object().set(col_int, 0);
1636
            }
1637
            add_int(*table, col_int, 1);
1638
            wt.commit();
1639
        }
1640
    }
1641

1642
    {
1643
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
1644
        ReadTransaction rt(sg);
1645
        rt.get_group().verify();
1646
        CHECK(!rt.has_table("alpha"));
1647
        CHECK(rt.has_table("beta"));
1648
        ConstTableRef table = rt.get_table("beta");
1649
        CHECK_EQUAL(process_count, table->begin()->get<Int>(col_int));
1650
    }
1651
}
1652

1653
#endif // on apple
1654
#endif // encryption enabled
1655

1656
// not ios or android
1657
// #endif // defined TEST_ROBUSTNESS && defined ENABLE_ROBUST_AGAINST_DEATH_DURING_WRITE && !REALM_ENABLE_ENCRYPTION
1658

1659

1660
TEST(Shared_SpaceOveruse)
1661
{
2✔
1662
#if TEST_DURATION < 1
2✔
1663
    int n_outer = 300;
2✔
1664
    int n_inner = 21;
2✔
1665
#else
1666
    int n_outer = 3000;
1667
    int n_inner = 42;
1668
#endif
1669

1✔
1670
    // Many transactions
1✔
1671
    SHARED_GROUP_TEST_PATH(path);
2✔
1672
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1673

1✔
1674
    // Do a lot of sequential transactions
1✔
1675
    for (int i = 0; i != n_outer; ++i) {
602✔
1676
        WriteTransaction wt(sg);
600✔
1677
        wt.get_group().verify();
600✔
1678
        auto table = wt.get_or_add_table("my_table");
600✔
1679

300✔
1680
        if (table->is_empty()) {
600✔
1681
            REALM_ASSERT(table);
2✔
1682
            table->add_column(type_String, "text");
2✔
1683
        }
2✔
1684
        auto cols = table->get_column_keys();
600✔
1685

300✔
1686
        for (int j = 0; j != n_inner; ++j) {
13,200✔
1687
            REALM_ASSERT(table);
12,600✔
1688
            table->create_object().set(cols[0], "x");
12,600✔
1689
        }
12,600✔
1690
        wt.commit();
600✔
1691
    }
600✔
1692

1✔
1693
    // Verify that all was added correctly
1✔
1694
    {
2✔
1695
        ReadTransaction rt(sg);
2✔
1696
        rt.get_group().verify();
2✔
1697
        auto table = rt.get_table("my_table");
2✔
1698
        auto col = table->get_column_keys()[0];
2✔
1699
        size_t n = table->size();
2✔
1700
        CHECK_EQUAL(n_outer * n_inner, n);
2✔
1701

1✔
1702
        for (auto it : *table) {
12,600✔
1703
            CHECK_EQUAL("x", it.get<String>(col));
12,600✔
1704
        }
12,600✔
1705

1✔
1706
        table->verify();
2✔
1707
    }
2✔
1708
}
2✔
1709

1710

1711
TEST(Shared_Notifications)
1712
{
2✔
1713
    // Create a new shared db
1✔
1714
    SHARED_GROUP_TEST_PATH(path);
2✔
1715
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1716
    TransactionRef tr1 = sg->start_read();
2✔
1717

1✔
1718
    // No other instance have changed db since last transaction
1✔
1719
    CHECK(!sg->has_changed(tr1));
2✔
1720

1✔
1721
    {
2✔
1722
        // Open the same db again (in empty state)
1✔
1723
        DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
1724

1✔
1725
        // Verify that new group is empty
1✔
1726
        {
2✔
1727
            TransactionRef reader = sg2->start_read();
2✔
1728
            CHECK(reader->is_empty());
2✔
1729
            CHECK(!sg2->has_changed(reader));
2✔
1730
        }
2✔
1731

1✔
1732
        // No other instance have changed db since last transaction
1✔
1733

1✔
1734
        // Add a new table
1✔
1735
        {
2✔
1736
            WriteTransaction wt(sg2);
2✔
1737
            wt.get_group().verify();
2✔
1738
            auto t1 = wt.add_table("test");
2✔
1739
            test_table_add_columns(t1);
2✔
1740
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1741
            wt.commit();
2✔
1742
        }
2✔
1743
    }
2✔
1744

1✔
1745
    // Db has been changed by other instance
1✔
1746
    CHECK(sg->has_changed(tr1));
2✔
1747
    tr1 = sg->start_read();
2✔
1748
    // Verify that the new table has been added
1✔
1749
    {
2✔
1750
        ReadTransaction rt(sg);
2✔
1751
        rt.get_group().verify();
2✔
1752
        auto t1 = rt.get_table("test");
2✔
1753
        CHECK_EQUAL(1, t1->size());
2✔
1754
        const Obj obj = t1->get_object(ObjKey(7));
2✔
1755
        auto cols = t1->get_column_keys();
2✔
1756
        CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1757
        CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1758
        CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1759
        CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1760
    }
2✔
1761

1✔
1762
    // No other instance have changed db since last transaction
1✔
1763
    CHECK(!sg->has_changed(tr1));
2✔
1764
}
2✔
1765

1766

1767
TEST(Shared_FromSerialized)
1768
{
2✔
1769
    SHARED_GROUP_TEST_PATH(path);
2✔
1770

1✔
1771
    // Create new group and serialize to disk
1✔
1772
    {
2✔
1773
        Group g1;
2✔
1774
        auto t1 = g1.add_table("test");
2✔
1775
        test_table_add_columns(t1);
2✔
1776
        t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1777
        g1.write(path, crypt_key());
2✔
1778
    }
2✔
1779

1✔
1780
    // Open same file as shared group
1✔
1781
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1782

1✔
1783
    // Verify that contents is there when shared
1✔
1784
    {
2✔
1785
        ReadTransaction rt(sg);
2✔
1786
        rt.get_group().verify();
2✔
1787
        auto t1 = rt.get_table("test");
2✔
1788
        CHECK_EQUAL(1, t1->size());
2✔
1789
        const Obj obj = t1->get_object(ObjKey(7));
2✔
1790
        auto cols = t1->get_column_keys();
2✔
1791
        CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1792
        CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1793
        CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1794
        CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1795
    }
2✔
1796
}
2✔
1797

1798
TEST_IF(Shared_StringIndexBug1, TEST_DURATION >= 1)
1799
{
×
1800
    SHARED_GROUP_TEST_PATH(path);
×
1801
    DBRef db = DB::create(path, false, DBOptions(crypt_key()));
×
1802

1803
    {
×
1804
        auto tr = db->start_write();
×
1805
        TableRef table = tr->add_table("users");
×
1806
        auto col = table->add_column(type_String, "username");
×
1807
        table->add_search_index(col);
×
1808
        for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i)
×
1809
            table->create_object();
×
1810
        for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i)
×
1811
            table->remove_object(table->begin());
×
1812
        tr->commit();
×
1813
    }
×
1814

1815
    {
×
1816
        auto tr = db->start_write();
×
1817
        TableRef table = tr->get_table("users");
×
1818
        table->create_object();
×
1819
        tr->commit();
×
1820
    }
×
1821
}
×
1822

1823

1824
TEST(Shared_StringIndexBug2)
1825
{
2✔
1826
    SHARED_GROUP_TEST_PATH(path);
2✔
1827
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1828

1✔
1829
    {
2✔
1830
        WriteTransaction wt(sg);
2✔
1831
        wt.get_group().verify();
2✔
1832
        TableRef table = wt.add_table("a");
2✔
1833
        auto col = table->add_column(type_String, "b");
2✔
1834
        table->add_search_index(col); // Not adding index makes it work
2✔
1835
        table->create_object();
2✔
1836
        wt.commit();
2✔
1837
    }
2✔
1838

1✔
1839
    {
2✔
1840
        ReadTransaction rt(sg);
2✔
1841
        rt.get_group().verify();
2✔
1842
    }
2✔
1843
}
2✔
1844

1845

1846
namespace {
1847

1848
void rand_str(Random& random, char* res, size_t len)
1849
{
103✔
1850
    for (size_t i = 0; i < len; ++i)
927✔
1851
        res[i] = char(int('a') + random.draw_int_mod(10));
824✔
1852
}
103✔
1853

1854
} // anonymous namespace
1855

1856
TEST(Shared_StringIndexBug3)
1857
{
2✔
1858
    SHARED_GROUP_TEST_PATH(path);
2✔
1859
    DBRef db = DB::create(path, false, DBOptions(crypt_key()));
2✔
1860
    ColKey col;
2✔
1861
    {
2✔
1862
        auto tr = db->start_write();
2✔
1863
        TableRef table = tr->add_table("users");
2✔
1864
        col = table->add_column(type_String, "username");
2✔
1865
        table->add_search_index(col); // Disabling index makes it work
2✔
1866
        tr->commit();
2✔
1867
    }
2✔
1868

1✔
1869
    Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
1870
    std::vector<ObjKey> keys;
2✔
1871
    for (size_t n = 0; n < 100; ++n) {
202✔
1872
        const uint64_t action = random.draw_int_mod(1000);
200✔
1873

100✔
1874
        if (action <= 500) {
200✔
1875
            // delete random user
49✔
1876
            auto tr = db->start_write();
97✔
1877
            TableRef table = tr->get_table("users");
97✔
1878
            if (table->size() > 0) {
97✔
1879
                size_t del = random.draw_int_mod(table->size());
90✔
1880
                // cerr << "-" << del << ": " << table->get_string(0, del) << std::endl;
47✔
1881
                table->remove_object(keys[del]);
90✔
1882
                keys.erase(keys.begin() + del);
90✔
1883
                table->verify();
90✔
1884
            }
90✔
1885
            tr->commit();
97✔
1886
        }
97✔
1887
        else {
103✔
1888
            // add new user
51✔
1889
            auto tr = db->start_write();
103✔
1890
            TableRef table = tr->get_table("users");
103✔
1891
            char txt[100];
103✔
1892
            rand_str(random, txt, 8);
103✔
1893
            txt[8] = 0;
103✔
1894
            // cerr << "+" << txt << std::endl;
51✔
1895
            auto key = table->create_object().set_all(txt).get_key();
103✔
1896
            keys.push_back(key);
103✔
1897
            table->verify();
103✔
1898
            tr->commit();
103✔
1899
        }
103✔
1900
    }
200✔
1901
}
2✔
1902

1903
TEST(Shared_ClearColumnWithBasicArrayRootLeaf)
1904
{
2✔
1905
    SHARED_GROUP_TEST_PATH(path);
2✔
1906
    {
2✔
1907
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1908
        WriteTransaction wt(sg);
2✔
1909
        TableRef test = wt.add_table("Test");
2✔
1910
        auto col = test->add_column(type_Double, "foo");
2✔
1911
        test->clear();
2✔
1912
        test->create_object(ObjKey(7)).set(col, 727.2);
2✔
1913
        wt.commit();
2✔
1914
    }
2✔
1915
    {
2✔
1916
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1917
        ReadTransaction rt(sg);
2✔
1918
        ConstTableRef test = rt.get_table("Test");
2✔
1919
        auto col = test->get_column_key("foo");
2✔
1920
        CHECK_EQUAL(727.2, test->get_object(ObjKey(7)).get<Double>(col));
2✔
1921
    }
2✔
1922
}
2✔
1923

1924
TEST(Shared_ClearColumnWithLinksToSelf)
1925
{
2✔
1926
    // Reproduction of issue found by fuzzer
1✔
1927
    SHARED_GROUP_TEST_PATH(path);
2✔
1928
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1929
    {
2✔
1930
        WriteTransaction wt(sg);
2✔
1931
        TableRef test = wt.add_table("Test");
2✔
1932
        auto col = test->add_column(*test, "foo");
2✔
1933
        test->add_column(type_Int, "bar");
2✔
1934
        ObjKeys keys;
2✔
1935
        test->create_objects(400, keys);             // Ensure non root clusters
2✔
1936
        test->get_object(keys[3]).set(col, keys[8]); // Link must be even
2✔
1937
        wt.commit();
2✔
1938
    }
2✔
1939
    {
2✔
1940
        WriteTransaction wt(sg);
2✔
1941
        TableRef test = wt.get_table("Test");
2✔
1942
        // Ensure that cluster array is COW, but not expanded
1✔
1943
        test->remove_column(test->get_column_key("bar"));
2✔
1944
        test->clear();
2✔
1945
        wt.commit();
2✔
1946
    }
2✔
1947
}
2✔
1948

1949
#if 0 // FIXME: Reenable when it can pass reliably
1950
#ifdef _WIN32
1951

1952
TEST(Shared_WaitForChangeAfterOwnCommit)
1953
{
1954
    SHARED_GROUP_TEST_PATH(path);
1955

1956
    DB* sg = new DB(path);
1957
    sg->begin_write();
1958
    sg->commit();
1959
    bool b = sg->wait_for_change();
1960
}
1961

1962
NONCONCURRENT_TEST(Shared_InterprocessWaitForChange)
1963
{
1964
    // We can't use SHARED_GROUP_TEST_PATH() because it will attempt to clean up the .realm file at the end,
1965
    // and hence throw if the other processstill has the .realm file open
1966
    std::string path = get_test_path("Shared_InterprocessWaitForChange", ".realm");
1967

1968
    // This works differently from POSIX: Here, the child process begins execution from the start of this unit
1969
    // test and not from the place of fork().
1970
    DWORD pid = winfork("Shared_InterprocessWaitForChange");
1971

1972
    if (pid == -1) {
1973
        CHECK(false);
1974
        return;
1975
    }
1976

1977
    auto sg = DB::create(path);
1978

1979
    // An old .realm file with random contents can exist (such as a leftover from earlier crash) with random
1980
    // data, so we always initialize the database
1981
    {
1982
        auto tr = sg->start_write();
1983
        Group& g(*tr);
1984
        if (g.size() == 1) {
1985
            g.remove_table("data");
1986
            TableRef table = g.add_table("data");
1987
            auto col = table->add_column(type_Int, "ints");
1988
            table->create_object().set(col, 0);
1989
        }
1990
        tr->commit();
1991
        sg->wait_for_change(tr);
1992
    }
1993

1994
    bool first = false;
1995
    fastrand(time(0), true);
1996

1997
    // By turn, incremenet the counter and wait for the other to increment it too
1998
    for (int i = 0; i < 10; i++)
1999
    {
2000
        auto tr = sg->start_write();
2001
        Group& g(*tr);
2002
        if (g.size() == 1) {
2003
            TableRef table = g.get_table("data");
2004
            auto col = table->get_column_key("ints");
2005
            auto first_obj = table->begin();
2006
            int64_t v = first_obj->get<int64_t>(col);
2007

2008
            if (i == 0 && v == 0)
2009
                first = true;
2010

2011
            // Note: If this fails in child process (pid != 0) it might go undetected. This is not
2012
            // critical since it will most likely result in a failure in the parent process also.
2013
            CHECK_EQUAL(v - (first ? 0 : 1), 2 * i);
2014
            first_obj->set(col, v + 1);
2015
        }
2016

2017
        // millisleep(0) might yield time slice on certain OS'es, so we use fastrand() to get cases
2018
        // of 0 delay, because non-yieldig is also an important test case.
2019
        if(fastrand(1))
2020
            millisleep((time(0) % 10) * 10);
2021

2022
        tr->commit();
2023

2024
        if (fastrand(1))
2025
            millisleep((time(0) % 10) * 10);
2026

2027
        sg->wait_for_change(tr);
2028

2029
        if (fastrand(1))
2030
            millisleep((time(0) % 10) * 10);
2031
    }
2032

2033
    // Wake up other process so it will exit too
2034
    auto tr = sg->start_write();
2035
    tr->commit();
2036
}
2037
#endif
2038

2039
// This test will hang infinitely instead of failing!!!
2040
TEST(Shared_WaitForChange)
2041
{
2042
    const int num_threads = 3;
2043
    Mutex mutex;
2044
    int shared_state[num_threads];
2045
    for (int j = 0; j < num_threads; j++)
2046
        shared_state[j] = 0;
2047
    SHARED_GROUP_TEST_PATH(path);
2048
    DBRef sg = DB::create(path, false);
2049

2050
    auto waiter = [&](DBRef db, int i) {
2051
        TransactionRef tr;
2052
        {
2053
            LockGuard l(mutex);
2054
            shared_state[i] = 1;
2055
            tr = db->start_read();
2056
        }
2057
        db->wait_for_change(tr);
2058
        {
2059
            LockGuard l(mutex);
2060
            shared_state[i] = 2; // this state should not be observed by the writer
2061
        }
2062
        db->wait_for_change(tr); // we'll fall right through here, because we haven't advanced our readlock
2063
        {
2064
            LockGuard l(mutex);
2065
            tr->end_read();
2066
            tr = db->start_read();
2067
            shared_state[i] = 3;
2068
        }
2069
        db->wait_for_change(tr); // this time we'll wait because state hasn't advanced since we did.
2070
        {
2071
            tr = db->start_read();
2072
            {
2073
                LockGuard l(mutex);
2074
                shared_state[i] = 4;
2075
            }
2076
            db->wait_for_change(tr); // everybody waits in state 4
2077
            {
2078
                LockGuard l(mutex);
2079
                tr->end_read();
2080
                tr = db->start_read();
2081
                shared_state[i] = 5;
2082
            }
2083
        }
2084
        db->wait_for_change(tr); // wait until wait_for_change is released
2085
        {
2086
            LockGuard l(mutex);
2087
            shared_state[i] = 6;
2088
        }
2089
    };
2090

2091
    Thread threads[num_threads];
2092
    for (int j = 0; j < num_threads; j++)
2093
        threads[j].start([waiter, sg, j] { waiter(sg, j); });
2094
    bool try_again = true;
2095
    while (try_again) {
2096
        try_again = false;
2097
        for (int j = 0; j < num_threads; j++) {
2098
            LockGuard l(mutex);
2099
            if (shared_state[j] < 1) try_again = true;
2100
            CHECK(shared_state[j] < 2);
2101
        }
2102
    }
2103
    // At this point all transactions have progress to state 1,
2104
    // and none of them has progressed further.
2105
    // This write transaction should allow all readers to run again
2106
    {
2107
        WriteTransaction wt(sg);
2108
        wt.commit();
2109
    }
2110

2111
    // All readers should pass through state 2 to state 3, so wait
2112
    // for all to reach state 3:
2113
    try_again = true;
2114
    while (try_again) {
2115
        try_again = false;
2116
        for (int j = 0; j < num_threads; j++) {
2117
            LockGuard l(mutex);
2118
            if (3 != shared_state[j]) try_again = true;
2119
            CHECK(shared_state[j] < 4);
2120
        }
2121
    }
2122
    // all readers now waiting before entering state 4
2123
    {
2124
        WriteTransaction wt(sg);
2125
        wt.commit();
2126
    }
2127
    try_again = true;
2128
    while (try_again) {
2129
        try_again = false;
2130
        for (int j = 0; j < num_threads; j++) {
2131
            LockGuard l(mutex);
2132
            if (4 != shared_state[j]) try_again = true;
2133
        }
2134
    }
2135
    // all readers now waiting in stage 4
2136
    {
2137
        WriteTransaction wt(sg);
2138
        wt.commit();
2139
    }
2140
    // readers racing into stage 5
2141
    try_again = true;
2142
    while (try_again) {
2143
        try_again = false;
2144
        for (int j = 0; j < num_threads; j++) {
2145
            LockGuard l(mutex);
2146
            if (5 != shared_state[j]) try_again = true;
2147
        }
2148
    }
2149
    // everybod reached stage 5 and waiting
2150
    try_again = true;
2151
    sg->wait_for_change_release();
2152
    while (try_again) {
2153
        try_again = false;
2154
        for (int j = 0; j < num_threads; j++) {
2155
            LockGuard l(mutex);
2156
            if (6 != shared_state[j]) {
2157
                try_again = true;
2158
            }
2159
        }
2160
    }
2161
    for (int j = 0; j < num_threads; j++)
2162
        threads[j].join();
2163
}
2164
#endif
2165

2166
TEST(Shared_MultipleSharersOfStreamingFormat)
2167
{
2✔
2168
    SHARED_GROUP_TEST_PATH(path);
2✔
2169
    {
2✔
2170
        // Create non-empty file without free-space tracking
1✔
2171
        Group g;
2✔
2172
        g.add_table("x");
2✔
2173
        g.write(path, crypt_key());
2✔
2174
    }
2✔
2175
    {
2✔
2176
        // See if we can handle overlapped accesses through multiple shared groups
1✔
2177
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2178
        DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
2179
        {
2✔
2180
            ReadTransaction rt(sg);
2✔
2181
            rt.get_group().verify();
2✔
2182
            CHECK(rt.has_table("x"));
2✔
2183
            CHECK(!rt.has_table("gnyf"));
2✔
2184
            CHECK(!rt.has_table("baz"));
2✔
2185
        }
2✔
2186
        {
2✔
2187
            WriteTransaction wt(sg);
2✔
2188
            wt.get_group().verify();
2✔
2189
            wt.add_table("baz"); // Add table "baz"
2✔
2190
            wt.commit();
2✔
2191
        }
2✔
2192
        {
2✔
2193
            WriteTransaction wt2(sg2);
2✔
2194
            wt2.get_group().verify();
2✔
2195
            wt2.add_table("gnyf"); // Add table "gnyf"
2✔
2196
            wt2.commit();
2✔
2197
        }
2✔
2198
    }
2✔
2199
}
2✔
2200

2201
#if REALM_ENABLE_ENCRYPTION
2202
// verify that even though different threads share the same encrypted pages,
2203
// a thread will not get access without the key.
2204
TEST(Shared_EncryptionKeyCheck)
2205
{
2✔
2206
    SHARED_GROUP_TEST_PATH(path);
2✔
2207
    DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2✔
2208
    CHECK_THROW(DB::create(path, false, DBOptions()), InvalidDatabase);
2✔
2209
    DBRef sg3 = DB::create(path, false, DBOptions(crypt_key(true)));
2✔
2210
}
2✔
2211

2212
// opposite - if opened unencrypted, attempt to share it encrypted
2213
// will throw an error.
2214
TEST(Shared_EncryptionKeyCheck_2)
2215
{
2✔
2216
    SHARED_GROUP_TEST_PATH(path);
2✔
2217
    DBRef sg = DB::create(path, false, DBOptions());
2✔
2218
    CHECK_THROW(DB::create(path, false, DBOptions(crypt_key(true))), InvalidDatabase);
2✔
2219
    DBRef sg3 = DB::create(path, false, DBOptions());
2✔
2220
    CHECK(sg3);
2✔
2221
}
2✔
2222

2223
// if opened by one key, it cannot be opened by a different key
2224
TEST(Shared_EncryptionKeyCheck_3)
2225
{
2✔
2226
    SHARED_GROUP_TEST_PATH(path);
2✔
2227
    const char* first_key = crypt_key(true);
2✔
2228
    char second_key[64];
2✔
2229
    memcpy(second_key, first_key, 64);
2✔
2230
    second_key[3] = ~second_key[3];
2✔
2231
    DBRef sg = DB::create(path, false, DBOptions(first_key));
2✔
2232
    CHECK_THROW(DB::create(path, false, DBOptions(second_key)), InvalidDatabase);
2✔
2233
    DBRef sg3 = DB::create(path, false, DBOptions(first_key));
2✔
2234
}
2✔
2235

2236
TEST(Shared_EncryptionPageReadFailure)
2237
{
2✔
2238
    SHARED_GROUP_TEST_PATH(path);
2✔
2239
    constexpr size_t num_objects = 4096;
2✔
2240
    {
2✔
2241
        DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2✔
2242
        WriteTransaction wt(sg);
2✔
2243
        TableRef table = wt.get_group().add_table_with_primary_key("foo", type_ObjectId, "pk");
2✔
2244
        auto str_col = table->add_column(type_String, "string");
2✔
2245
        for (size_t i = 0; i < num_objects; ++i) {
8,194✔
2246
            auto obj = table->create_object_with_primary_key(ObjectId::gen());
8,192✔
2247
            obj.set<String>(str_col, "C");
8,192✔
2248
        }
8,192✔
2249
        wt.commit();
2✔
2250
    }
2✔
2251
    {
2✔
2252
        // make a corruption in the first data page
1✔
2253
        util::File f(path, File::Mode::mode_Update);
2✔
2254
        CHECK_GREATER(f.get_size(), 12288); // 4k iv page, then at least 2 pages
2✔
2255
        f.seek(5000);                       // somewhere on the first data page
2✔
2256
        constexpr std::string_view data = "an external corruption in the encrypted page";
2✔
2257
        f.write(data.data(), data.size());
2✔
2258
        f.sync();
2✔
2259
        f.close();
2✔
2260
    }
2✔
2261
    {
2✔
2262
        bool did_throw = false;
2✔
2263
        try {
2✔
2264
            DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2✔
2265
            WriteTransaction wt(sg);
2✔
2266
            TableRef table = wt.get_group().get_table("foo");
2✔
2267
            CHECK_EQUAL(table->size(), num_objects);
2✔
2268
        }
2✔
2269
        catch (const InvalidDatabase& e) {
2✔
2270
            CHECK_STRING_CONTAINS(e.what(), "decryption failed");
2✔
2271
            did_throw = true;
2✔
2272
        }
2✔
2273
        CHECK(did_throw);
2✔
2274
    }
2✔
2275
}
2✔
2276

2277
#endif // REALM_ENABLE_ENCRYPTION
2278

2279
TEST(Shared_VersionCount)
2280
{
2✔
2281
    SHARED_GROUP_TEST_PATH(path);
2✔
2282
    DBRef sg = get_test_db(path);
2✔
2283
    CHECK_EQUAL(1, sg->get_number_of_versions());
2✔
2284
    TransactionRef reader = sg->start_read();
2✔
2285
    {
2✔
2286
        WriteTransaction wt(sg);
2✔
2287
        CHECK_EQUAL(1, sg->get_number_of_versions());
2✔
2288
        wt.commit();
2✔
2289
    }
2✔
2290
    CHECK_EQUAL(2, sg->get_number_of_versions());
2✔
2291
    {
2✔
2292
        WriteTransaction wt(sg);
2✔
2293
        wt.commit();
2✔
2294
    }
2✔
2295
    CHECK_EQUAL(3, sg->get_number_of_versions());
2✔
2296
    reader->close();
2✔
2297
    CHECK_EQUAL(3, sg->get_number_of_versions());
2✔
2298
    {
2✔
2299
        WriteTransaction wt(sg);
2✔
2300
        wt.commit();
2✔
2301
    }
2✔
2302
    // both the last and the second-last commit is kept, so once
1✔
2303
    // you've committed anything, you will never get back to having
1✔
2304
    // just a single version.
1✔
2305
    CHECK_EQUAL(2, sg->get_number_of_versions());
2✔
2306
}
2✔
2307

2308
TEST(Shared_MultipleRollbacks)
2309
{
2✔
2310
    SHARED_GROUP_TEST_PATH(path);
2✔
2311
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2312
    TransactionRef wt = sg->start_write();
2✔
2313
    wt->rollback();
2✔
2314
    wt->rollback();
2✔
2315
}
2✔
2316

2317

2318
TEST(Shared_MultipleEndReads)
2319
{
2✔
2320
    SHARED_GROUP_TEST_PATH(path);
2✔
2321
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2322
    TransactionRef reader = sg->start_read();
2✔
2323
    reader->end_read();
2✔
2324
    reader->end_read();
2✔
2325
}
2✔
2326

2327
#ifdef REALM_DEBUG
2328
// SharedGroup::reserve() is a debug method only available in debug mode
2329
TEST(Shared_ReserveDiskSpace)
2330
{
2✔
2331
    SHARED_GROUP_TEST_PATH(path);
2✔
2332
    {
2✔
2333
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2334
        size_t orig_file_size = size_t(File(path).get_size());
2✔
2335

1✔
2336
        // Check that reserve() does not change the file size if the
1✔
2337
        // specified size is less than the actual file size.
1✔
2338
        size_t reserve_size_1 = orig_file_size / 2;
2✔
2339
        sg->reserve(reserve_size_1);
2✔
2340
        size_t new_file_size_1 = size_t(File(path).get_size());
2✔
2341
        CHECK_EQUAL(orig_file_size, new_file_size_1);
2✔
2342

1✔
2343
        // Check that reserve() does not change the file size if the
1✔
2344
        // specified size is equal to the actual file size.
1✔
2345
        size_t reserve_size_2 = orig_file_size;
2✔
2346
        sg->reserve(reserve_size_2);
2✔
2347
        size_t new_file_size_2 = size_t(File(path).get_size());
2✔
2348
        if (crypt_key()) {
2✔
2349
            // For encrypted files, reserve() may actually grow the file
2350
            // with a page sized header.
2351
            CHECK(orig_file_size <= new_file_size_2 && (orig_file_size + page_size()) >= new_file_size_2);
×
2352
        }
×
2353
        else {
2✔
2354
            CHECK_EQUAL(orig_file_size, new_file_size_2);
2✔
2355
        }
2✔
2356

1✔
2357
        // Check that reserve() does change the file size if the
1✔
2358
        // specified size is greater than the actual file size, and
1✔
2359
        // that the new size is at least as big as the requested size.
1✔
2360
        size_t reserve_size_3 = orig_file_size + 1;
2✔
2361
        sg->reserve(reserve_size_3);
2✔
2362
        size_t new_file_size_3 = size_t(File(path).get_size());
2✔
2363
        CHECK(new_file_size_3 >= reserve_size_3);
2✔
2364
        ObjKeys keys;
2✔
2365

1✔
2366
        // Check that disk space reservation is independent of transactions
1✔
2367
        {
2✔
2368
            WriteTransaction wt(sg);
2✔
2369
            wt.get_group().verify();
2✔
2370
            auto t = wt.add_table("table_1");
2✔
2371
            test_table_add_columns(t);
2✔
2372
            t->create_objects(2000, keys);
2✔
2373
            wt.commit();
2✔
2374
        }
2✔
2375
        orig_file_size = size_t(File(path).get_size());
2✔
2376
        size_t reserve_size_4 = 2 * orig_file_size + 1;
2✔
2377
        sg->reserve(reserve_size_4);
2✔
2378
        size_t new_file_size_4 = size_t(File(path).get_size());
2✔
2379
        CHECK(new_file_size_4 >= reserve_size_4);
2✔
2380
        {
2✔
2381
            WriteTransaction wt(sg);
2✔
2382
            wt.get_group().verify();
2✔
2383
            auto t = wt.add_table("table_2");
2✔
2384
            test_table_add_columns(t);
2✔
2385
            t->create_objects(2000, keys);
2✔
2386
            orig_file_size = size_t(File(path).get_size());
2✔
2387
            size_t reserve_size_5 = orig_file_size + 333;
2✔
2388
            sg->reserve(reserve_size_5);
2✔
2389
            size_t new_file_size_5 = size_t(File(path).get_size());
2✔
2390
            CHECK(new_file_size_5 >= reserve_size_5);
2✔
2391
            t = wt.add_table("table_3");
2✔
2392
            test_table_add_columns(t);
2✔
2393
            t->create_objects(2000, keys);
2✔
2394
            wt.commit();
2✔
2395
        }
2✔
2396
        orig_file_size = size_t(File(path).get_size());
2✔
2397
        size_t reserve_size_6 = orig_file_size + 459;
2✔
2398
        sg->reserve(reserve_size_6);
2✔
2399
        size_t new_file_size_6 = size_t(File(path).get_size());
2✔
2400
        CHECK(new_file_size_6 >= reserve_size_6);
2✔
2401
        {
2✔
2402
            WriteTransaction wt(sg);
2✔
2403
            wt.get_group().verify();
2✔
2404
            wt.commit();
2✔
2405
        }
2✔
2406
    }
2✔
2407
}
2✔
2408
#endif
2409

2410
TEST(Shared_MovingSearchIndex)
2411
{
2✔
2412
    // Test that the 'index in parent' property of search indexes is properly
1✔
2413
    // adjusted when columns are inserted or removed at a lower column_index.
1✔
2414

1✔
2415
    SHARED_GROUP_TEST_PATH(path);
2✔
2416
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2417

1✔
2418
    // Create an int column, regular string column, and an enumeration strings
1✔
2419
    // column, and equip them with search indexes.
1✔
2420
    ColKey int_col, str_col, enum_col, padding_col;
2✔
2421
    std::vector<ObjKey> obj_keys;
2✔
2422
    {
2✔
2423
        WriteTransaction wt(sg);
2✔
2424
        TableRef table = wt.add_table("foo");
2✔
2425
        padding_col = table->add_column(type_Int, "padding");
2✔
2426
        int_col = table->add_column(type_Int, "int");
2✔
2427
        str_col = table->add_column(type_String, "regular");
2✔
2428
        enum_col = table->add_column(type_String, "enum");
2✔
2429

1✔
2430
        table->create_objects(64, obj_keys);
2✔
2431
        for (int i = 0; i < 64; ++i) {
130✔
2432
            auto obj = table->get_object(obj_keys[i]);
128✔
2433
            std::string out(std::string("foo") + util::to_string(i));
128✔
2434
            obj.set<Int>(int_col, i);
128✔
2435
            obj.set<String>(str_col, out);
128✔
2436
            obj.set<String>(enum_col, "bar");
128✔
2437
        }
128✔
2438
        table->get_object(obj_keys.back()).set<String>(enum_col, "bar63");
2✔
2439
        table->enumerate_string_column(enum_col);
2✔
2440
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2441
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2442
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2443

1✔
2444
        table->add_search_index(int_col);
2✔
2445
        table->add_search_index(str_col);
2✔
2446
        table->add_search_index(enum_col);
2✔
2447

1✔
2448
        wt.get_group().verify();
2✔
2449

1✔
2450
        CHECK_EQUAL(obj_keys[61], table->find_first_int(int_col, 61));
2✔
2451
        CHECK_EQUAL(obj_keys[62], table->find_first_string(str_col, "foo62"));
2✔
2452
        CHECK_EQUAL(obj_keys[63], table->find_first_string(enum_col, "bar63"));
2✔
2453
        wt.commit();
2✔
2454
    }
2✔
2455

1✔
2456
    // Remove the padding column to shift the indexed columns
1✔
2457
    {
2✔
2458
        WriteTransaction wt(sg);
2✔
2459
        TableRef table = wt.get_table("foo");
2✔
2460

1✔
2461
        CHECK(table->has_search_index(int_col));
2✔
2462
        CHECK(table->has_search_index(str_col));
2✔
2463
        CHECK(table->has_search_index(enum_col));
2✔
2464
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2465
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2466
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2467
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2468
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2469
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2470
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2471
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2472
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2473

1✔
2474
        table->remove_column(padding_col);
2✔
2475
        wt.get_group().verify();
2✔
2476

1✔
2477
        CHECK(table->has_search_index(int_col));
2✔
2478
        CHECK(table->has_search_index(str_col));
2✔
2479
        CHECK(table->has_search_index(enum_col));
2✔
2480
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2481
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2482
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2483
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2484
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2485
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2486
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2487
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2488
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2489

1✔
2490
        auto obj = table->get_object(obj_keys[1]);
2✔
2491
        obj.set<Int>(int_col, 101);
2✔
2492
        obj.set<String>(str_col, "foo_Y");
2✔
2493
        obj.set<String>(enum_col, "bar_Y");
2✔
2494
        wt.get_group().verify();
2✔
2495

1✔
2496
        CHECK(table->has_search_index(int_col));
2✔
2497
        CHECK(table->has_search_index(str_col));
2✔
2498
        CHECK(table->has_search_index(enum_col));
2✔
2499
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2500
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2501
        CHECK_EQUAL(3, table->get_num_unique_values(enum_col));
2✔
2502
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2503
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2504
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2505
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2506
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2507
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2508
        CHECK_EQUAL(obj_keys[1], table->find_first_int(int_col, 101));
2✔
2509
        CHECK_EQUAL(obj_keys[1], table->find_first_string(str_col, "foo_Y"));
2✔
2510
        CHECK_EQUAL(obj_keys[1], table->find_first_string(enum_col, "bar_Y"));
2✔
2511
        CHECK_EQUAL(obj_keys[63], table->find_first_string(enum_col, "bar63"));
2✔
2512

1✔
2513
        wt.commit();
2✔
2514
    }
2✔
2515
}
2✔
2516

2517
TEST_IF(Shared_BeginReadFailure, _impl::SimulatedFailure::is_enabled())
2518
{
2✔
2519
    SHARED_GROUP_TEST_PATH(path);
2✔
2520
    DBRef sg = get_test_db(path);
2✔
2521
    using sf = _impl::SimulatedFailure;
2✔
2522
    sf::OneShotPrimeGuard pg(sf::shared_group__grow_reader_mapping);
2✔
2523
    CHECK_THROW(sg->start_read(), sf);
2✔
2524
}
2✔
2525

2526

2527
TEST(Shared_SessionDurabilityConsistency)
2528
{
2✔
2529
    // Check that we can reliably detect inconsist durability choices across
1✔
2530
    // concurrent session participants.
1✔
2531

1✔
2532
    // Errors of this kind are considered as incorrect API usage, and will lead
1✔
2533
    // to throwing of LogicError exceptions.
1✔
2534

1✔
2535
    SHARED_GROUP_TEST_PATH(path);
2✔
2536
    {
2✔
2537
        bool no_create = false;
2✔
2538
        DBOptions::Durability durability_1 = DBOptions::Durability::Full;
2✔
2539
        DBRef sg = DB::create(path, no_create, DBOptions(durability_1));
2✔
2540

1✔
2541
        DBOptions::Durability durability_2 = DBOptions::Durability::MemOnly;
2✔
2542
        CHECK_RUNTIME_ERROR(DB::create(path, no_create, DBOptions(durability_2)), ErrorCodes::IncompatibleSession);
2✔
2543
    }
2✔
2544
}
2✔
2545

2546

2547
TEST(Shared_WriteEmpty)
2548
{
2✔
2549
    SHARED_GROUP_TEST_PATH(path_1);
2✔
2550
    GROUP_TEST_PATH(path_2);
2✔
2551
    {
2✔
2552
        DBRef sg = DB::create(path_1);
2✔
2553
        ReadTransaction rt(sg);
2✔
2554
        rt.get_group().write(path_2);
2✔
2555
    }
2✔
2556
}
2✔
2557

2558

2559
TEST(Shared_CompactEmpty)
2560
{
2✔
2561
    SHARED_GROUP_TEST_PATH(path);
2✔
2562
    {
2✔
2563
        DBRef sg = get_test_db(path);
2✔
2564
        CHECK(sg->compact());
2✔
2565
    }
2✔
2566
}
2✔
2567

2568

2569
TEST(Shared_VersionOfBoundSnapshot)
2570
{
2✔
2571
    SHARED_GROUP_TEST_PATH(path);
2✔
2572
    DB::version_type version;
2✔
2573
    DBRef sg = get_test_db(path);
2✔
2574
    {
2✔
2575
        ReadTransaction rt(sg);
2✔
2576
        version = rt.get_version();
2✔
2577
    }
2✔
2578
    {
2✔
2579
        ReadTransaction rt(sg);
2✔
2580
        CHECK_EQUAL(version, rt.get_version());
2✔
2581
    }
2✔
2582
    {
2✔
2583
        WriteTransaction wt(sg);
2✔
2584
        CHECK_EQUAL(version, wt.get_version());
2✔
2585
    }
2✔
2586
    {
2✔
2587
        WriteTransaction wt(sg);
2✔
2588
        CHECK_EQUAL(version, wt.get_version());
2✔
2589
        wt.commit(); // Increment version
2✔
2590
    }
2✔
2591
    {
2✔
2592
        ReadTransaction rt(sg);
2✔
2593
        CHECK_LESS(version, rt.get_version());
2✔
2594
        version = rt.get_version();
2✔
2595
    }
2✔
2596
    {
2✔
2597
        WriteTransaction wt(sg);
2✔
2598
        CHECK_EQUAL(version, wt.get_version());
2✔
2599
        wt.commit(); // Increment version
2✔
2600
    }
2✔
2601
    {
2✔
2602
        ReadTransaction rt(sg);
2✔
2603
        CHECK_LESS(version, rt.get_version());
2✔
2604
    }
2✔
2605
}
2✔
2606

2607

2608
// This test is valid, but because it requests all available memory,
2609
// it does not play nicely with valgrind and so is disabled.
2610
/*
2611
#if !defined(_WIN32)
2612
// Check what happens when Realm cannot allocate more virtual memory
2613
// We should throw an AddressSpaceExhausted exception.
2614
// This will try to use all available memory allowed for this process
2615
// so don't run it concurrently with other tests.
2616
NONCONCURRENT_TEST(Shared_OutOfMemory)
2617
{
2618
    size_t string_length = 1024 * 1024;
2619
    SHARED_GROUP_TEST_PATH(path);
2620
    SharedGroup sg(path, false, SharedGroupOptions(crypt_key()));
2621
    {
2622
        WriteTransaction wt(sg);
2623
        TableRef table = wt.add_table("table");
2624
        table->add_column(type_String, "string_col");
2625
        std::string long_string(string_length, 'a');
2626
        table->add_empty_row();
2627
        table->set_string(0, 0, long_string);
2628
        wt.commit();
2629
    }
2630
    sg->close();
2631

2632
    std::vector<std::pair<void*, size_t>> memory_list;
2633
    // Reserve enough for 5*100000 Gb, but in practice the vector is only ever around size 10.
2634
    // Do this here to avoid the (small) chance that adding to the vector will request new virtual memory
2635
    memory_list.reserve(500);
2636
    size_t chunk_size = size_t(1024) * 1024 * 1024 * 100000;
2637
    while (chunk_size > string_length) {
2638
        void* addr = ::mmap(nullptr, chunk_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
2639
        if (addr == MAP_FAILED) {
2640
            chunk_size /= 2;
2641
        }
2642
        else {
2643
            memory_list.push_back(std::pair<void*, size_t>(addr, chunk_size));
2644
        }
2645
    }
2646

2647
    bool expected_exception_caught = false;
2648
    // Attempt to open Realm, should fail because we hold too much already.
2649
    try {
2650
        SharedGroup sg2(path, false, SharedGroupOptions(crypt_key()));
2651
    }
2652
    catch (AddressSpaceExhausted& e) {
2653
        expected_exception_caught = true;
2654
    }
2655
    CHECK(expected_exception_caught);
2656

2657
    // Release memory manually.
2658
    for (auto it = memory_list.begin(); it != memory_list.end(); ++it) {
2659
        ::munmap(it->first, it->second);
2660
    }
2661

2662
    // Realm should succeed to open now.
2663
    expected_exception_caught = false;
2664
    try {
2665
        SharedGroup sg2(path, false, SharedGroupOptions(crypt_key()));
2666
    }
2667
    catch (AddressSpaceExhausted& e) {
2668
        expected_exception_caught = true;
2669
    }
2670
    CHECK(!expected_exception_caught);
2671
}
2672
#endif // !win32
2673
*/
2674

2675
// Run some (repeatable) random checks through the fuzz tester.
2676
// For a comprehensive fuzz test, afl should be run. To do this see test/fuzzy/README.md
2677
// If this check fails for some reason, you can find the problem by changing
2678
// the parse_and_apply_instructions call to use std::cerr which will print out
2679
// the instructions used to duplicate the failure.
2680
NONCONCURRENT_TEST(Shared_StaticFuzzTestRunSanityCheck)
2681
{
2✔
2682
    // Either provide a crash file generated by AFL to reproduce a crash, or leave it blank in order to run
1✔
2683
    // a very simple fuzz test that just uses a random generator for generating Realm actions.
1✔
2684
    std::string filename = "";
2✔
2685
    // std::string filename = "/findings/hangs/id:000041,src:000000,op:havoc,rep:64";
1✔
2686
    // std::string filename = "d:/crash3";
1✔
2687

1✔
2688
    if (filename != "") {
2✔
2689
        const char* tmp[] = {"", filename.c_str(), "--log"};
×
2690
        run_fuzzy(sizeof(tmp) / sizeof(tmp[0]), tmp);
×
2691
    }
×
2692
    else {
2✔
2693
        // Number of fuzzy tests
1✔
2694
#if TEST_DURATION == 0
2✔
2695
        const size_t iterations = 3;
2✔
2696
#else
2697
        const size_t iterations = 1000;
2698
#endif
2699

1✔
2700
        // Number of instructions in each test
1✔
2701
        // Changing this strongly affects the test suite run time
1✔
2702
        const size_t instructions = 200;
2✔
2703

1✔
2704
        for (size_t counter = 0; counter < iterations; counter++) {
8✔
2705
            // You can use your own seed if you have observed a crashing unit test that
3✔
2706
            // printed out some specific seed (the "Unit test random seed:" part that appears).
3✔
2707
            FastRand generator(unit_test_random_seed + counter);
6✔
2708

3✔
2709
            std::string instr;
6✔
2710

3✔
2711
            // "fastlog" is because logging to a stream is very very slow. Logging the sequence of
3✔
2712
            // bytes lets you perform many more tests per second.
3✔
2713
            std::string fastlog = "char[] instr2 = {";
6✔
2714

3✔
2715
            for (size_t t = 0; t < instructions; t++) {
1,206✔
2716
                char c = static_cast<char>(generator());
1,200✔
2717
                instr += c;
1,200✔
2718
                std::string tmp;
1,200✔
2719
                unit_test::to_string(static_cast<int>(c), tmp);
1,200✔
2720
                fastlog += tmp;
1,200✔
2721
                if (t + 1 < instructions) {
1,200✔
2722
                    fastlog += ", ";
1,194✔
2723
                }
1,194✔
2724
                else {
6✔
2725
                    fastlog += "}; instr = string(instr2);";
6✔
2726
                }
6✔
2727
            }
1,200✔
2728

3✔
2729
            // Scope guard of "path" is inside the loop to clean up files per iteration
3✔
2730
            SHARED_GROUP_TEST_PATH(path);
6✔
2731
            // If using std::cerr, you can copy/paste the console output into a unit test
3✔
2732
            // to get a reproduction test case
3✔
2733
            // parse_and_apply_instructions(instr, path, std::cerr);
3✔
2734
            parse_and_apply_instructions(instr, path, nullptr);
6✔
2735
        }
6✔
2736
    }
2✔
2737
}
2✔
2738

2739
#if 0 // not suitable for automatic testing
2740
// This test checks what happens when a version is pinned and there are many
2741
// large write transactions that grow the file quickly. It takes a long time
2742
// and can make very very large files so it is not suited to automatic testing.
2743
TEST_IF(Shared_encrypted_pin_and_write, false)
2744
{
2745
    const size_t num_objects = 1000;
2746
    const size_t num_transactions = 1000000;
2747
    const size_t num_writer_threads = 8;
2748
    SHARED_GROUP_TEST_PATH(path);
2749

2750
    { // initial table structure setup on main thread
2751
        DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2752
        WriteTransaction wt(sg);
2753
        Group& group = wt.get_group();
2754
        TableRef t = group.add_table("table");
2755
        t->add_column(type_String, "string_col", true);
2756
        for (size_t i = 0; i < num_objects; ++i) {
2757
            t->create_object();
2758
        }
2759
        wt.commit();
2760
    }
2761

2762
    DBRef sg_reader = DB::create(path, false, DBOptions(crypt_key(true)));
2763

2764
    ReadTransaction rt(sg_reader); // hold first version
2765

2766
    auto do_many_writes = [&]() {
2767
        DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2768
        const size_t base_size = 100000;
2769
        std::string base(base_size, 'a');
2770
        // write many transactions to grow the file
2771
        // around 4.6 GB seems to be the breaking size
2772
        for (size_t t = 0; t < num_transactions; ++t) {
2773
            std::vector<std::string> rows(num_objects);
2774
            // change a character so there's no storage optimizations
2775
            for (size_t row = 0; row < num_objects; ++row) {
2776
                base[(t * num_objects + row)%base_size] = 'a' + (row % 52);
2777
                rows[row] = base;
2778
            }
2779
            WriteTransaction wt(sg);
2780
            Group& g = wt.get_group();
2781
            auto keys = g.get_table_keys();
2782
            TableRef table = g.get_table(keys[0]);
2783
            ColKey str_col = table->get_column_key("string_col");
2784
            size_t count = 0;
2785
            for (auto it = table->begin(); it != table->end(); ++it, ++count) {
2786
                StringData c(rows[count]);
2787
                it->set(str_col, c);
2788
            }
2789
            wt.commit();
2790
        }
2791
    };
2792

2793
    Thread threads[num_writer_threads];
2794
    for (size_t i = 0; i < num_writer_threads; ++i)
2795
        threads[i].start(do_many_writes);
2796

2797
    for (size_t i = 0; i < num_writer_threads; ++i) {
2798
        threads[i].join();
2799
    }
2800
}
2801
#endif
2802

2803

2804
// Scaled down stress test. (Use string length ~15MB for max stress)
2805
NONCONCURRENT_TEST(Shared_BigAllocations)
2806
{
2✔
2807
    size_t string_length = 64 * 1024;
2✔
2808
    SHARED_GROUP_TEST_PATH(path);
2✔
2809
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2810
    std::string long_string(string_length, 'a');
2✔
2811
    {
2✔
2812
        WriteTransaction wt(sg);
2✔
2813
        TableRef table = wt.add_table("table");
2✔
2814
        table->add_column(type_String, "string_col");
2✔
2815
        wt.commit();
2✔
2816
    }
2✔
2817
    {
2✔
2818
        WriteTransaction wt(sg);
2✔
2819
        TableRef table = wt.get_table("table");
2✔
2820
        auto cols = table->get_column_keys();
2✔
2821
        for (int i = 0; i < 32; ++i) {
66✔
2822
            table->create_object(ObjKey(i)).set(cols[0], long_string.c_str());
64✔
2823
        }
64✔
2824
        wt.commit();
2✔
2825
    }
2✔
2826
    for (int k = 0; k < 10; ++k) {
22✔
2827
        // sg.compact(); // <--- enable this if you want to stress with compact()
10✔
2828
        for (int j = 0; j < 20; ++j) {
420✔
2829
            WriteTransaction wt(sg);
400✔
2830
            TableRef table = wt.get_table("table");
400✔
2831
            auto cols = table->get_column_keys();
400✔
2832
            for (int i = 0; i < 20; ++i) {
8,400✔
2833
                table->get_object(ObjKey(i)).set(cols[0], long_string.c_str());
8,000✔
2834
            }
8,000✔
2835
            wt.commit();
400✔
2836
        }
400✔
2837
    }
20✔
2838
    sg->close();
2✔
2839
}
2✔
2840

2841
TEST_IF(Shared_CompactEncrypt, REALM_ENABLE_ENCRYPTION)
2842
{
2✔
2843
    SHARED_GROUP_TEST_PATH(path);
2✔
2844
    const char* key1 = "KdrL2ieWyspILXIPetpkLD6rQYKhYnS6lvGsgk4qsJAMr1adQnKsYo3oTEYJDIfa";
2✔
2845
    const char* key2 = "ti6rOKviXrwxSGMPVk35Dp9Q4eku8Cu8YTtnnZKAejOTNIEv7TvXrYdjOPSNexMR";
2✔
2846
    {
2✔
2847
        auto db = DB::create(path, false, DBOptions(key1));
2✔
2848
        auto tr = db->start_write();
2✔
2849
        TableRef t = tr->add_table("table");
2✔
2850
        auto col = t->add_column(type_String, "Strings");
2✔
2851
        for (size_t i = 0; i < 10000; i++) {
20,002✔
2852
            std::string str = "Shared_CompactEncrypt" + util::to_string(i);
20,000✔
2853
            t->create_object().set(col, StringData(str));
20,000✔
2854
        }
20,000✔
2855
        tr->commit();
2✔
2856

1✔
2857
        CHECK(db->compact());
2✔
2858
        {
2✔
2859
            auto rt = db->start_read();
2✔
2860
            CHECK(rt->has_table("table"));
2✔
2861
        }
2✔
2862

1✔
2863
        bool bump_version_number = true;
2✔
2864
        CHECK(db->compact(bump_version_number, key2));
2✔
2865
        {
2✔
2866
            auto rt = db->start_read();
2✔
2867
            CHECK(rt->has_table("table"));
2✔
2868
        }
2✔
2869

1✔
2870
        CHECK(db->compact(bump_version_number, nullptr));
2✔
2871
        {
2✔
2872
            auto rt = db->start_read();
2✔
2873
            CHECK(rt->has_table("table"));
2✔
2874
        }
2✔
2875
    }
2✔
2876
    {
2✔
2877
        auto db = DB::create(path, true, DBOptions());
2✔
2878
        {
2✔
2879
            auto rt = db->start_read();
2✔
2880
            CHECK(rt->has_table("table"));
2✔
2881
        }
2✔
2882
    }
2✔
2883
}
2✔
2884

2885
// Repro case for: Assertion failed: top_size == 3 || top_size == 5 || top_size == 7 [0, 3, 0, 5, 0, 7]
2886
NONCONCURRENT_TEST(Shared_BigAllocationsMinimized)
2887
{
2✔
2888
    // String length at 2K will not trigger the error.
1✔
2889
    // all lengths >= 4K (that were tried) trigger the error
1✔
2890
    size_t string_length = 4 * 1024;
2✔
2891
    SHARED_GROUP_TEST_PATH(path);
2✔
2892
    std::string long_string(string_length, 'a');
2✔
2893
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2894
    {
2✔
2895
        {
2✔
2896
            WriteTransaction wt(sg);
2✔
2897
            TableRef table = wt.add_table("table");
2✔
2898
            table->add_column(type_String, "string_col");
2✔
2899
            auto cols = table->get_column_keys();
2✔
2900
            table->create_object(ObjKey(0)).set(cols[0], long_string.c_str());
2✔
2901
            wt.commit();
2✔
2902
        }
2✔
2903
        sg->compact(); // <- required to provoke subsequent failures
2✔
2904
        {
2✔
2905
            WriteTransaction wt(sg);
2✔
2906
            wt.get_group().verify();
2✔
2907
            TableRef table = wt.get_table("table");
2✔
2908
            auto cols = table->get_column_keys();
2✔
2909
            table->get_object(ObjKey(0)).set(cols[0], long_string.c_str());
2✔
2910
            wt.get_group().verify();
2✔
2911
            wt.commit();
2✔
2912
        }
2✔
2913
    }
2✔
2914
    {
2✔
2915
        WriteTransaction wt(sg); // <---- fails here
2✔
2916
        wt.get_group().verify();
2✔
2917
        TableRef table = wt.get_table("table");
2✔
2918
        auto cols = table->get_column_keys();
2✔
2919
        table->get_object(ObjKey(0)).set(cols[0], long_string.c_str());
2✔
2920
        wt.get_group().verify();
2✔
2921
        wt.commit();
2✔
2922
    }
2✔
2923
    sg->close();
2✔
2924
}
2✔
2925

2926
// Found by AFL (on a heavy hint from Finn that we should add a compact() instruction
2927
NONCONCURRENT_TEST(Shared_TopSizeNotEqualNine)
2928
{
2✔
2929
    SHARED_GROUP_TEST_PATH(path);
2✔
2930
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2931
    {
2✔
2932
        TransactionRef writer = sg->start_write();
2✔
2933

1✔
2934
        TableRef t = writer->add_table("foo");
2✔
2935
        t->add_column(type_Double, "doubles");
2✔
2936
        std::vector<ObjKey> keys;
2✔
2937
        t->create_objects(241, keys);
2✔
2938
        writer->commit();
2✔
2939
    }
2✔
2940
    REALM_ASSERT_RELEASE(sg->compact());
2✔
2941
    DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
2942
    {
2✔
2943
        TransactionRef writer = sg2->start_write();
2✔
2944
        writer->commit();
2✔
2945
    }
2✔
2946
    TransactionRef reader2 = sg2->start_read();
2✔
2947
    DBRef sg3 = DB::create(path, false, DBOptions(crypt_key()));
2✔
2948
    TransactionRef reader3 = sg3->start_read();
2✔
2949
    TransactionRef reader = sg->start_read();
2✔
2950
}
2✔
2951

2952
// Found by AFL after adding the compact instruction
2953
// after further manual simplification, this test no longer triggers
2954
// the double free, but crashes in a different way
2955
TEST(Shared_Bptree_insert_failure)
2956
{
2✔
2957
    SHARED_GROUP_TEST_PATH(path);
2✔
2958
    DBRef sg_w = DB::create(path, false, DBOptions(crypt_key()));
2✔
2959
    TransactionRef writer = sg_w->start_write();
2✔
2960

1✔
2961
    auto tk = writer->add_table("")->get_key();
2✔
2962
    writer->get_table(tk)->add_column(type_Double, "dgrpn", true);
2✔
2963
    std::vector<ObjKey> keys;
2✔
2964
    writer->get_table(tk)->create_objects(246, keys);
2✔
2965
    writer->commit();
2✔
2966
    REALM_ASSERT_RELEASE(sg_w->compact());
2✔
2967
#if 0
2968
    {
2969
        // This intervening sg can do the same operation as the one doing compact,
2970
        // but without failing:
2971
        DB sg2(path, false, DBOptions(crypt_key()));
2972
        Group& g2 = const_cast<Group&>(sg2.begin_write());
2973
        g2.get_table(tk)->add_empty_row(396);
2974
    }
2975
#endif
2976
    {
2✔
2977
        TransactionRef writer2 = sg_w->start_write();
2✔
2978
        writer2->get_table(tk)->create_objects(396, keys);
2✔
2979
    }
2✔
2980
}
2✔
2981

2982
NONCONCURRENT_TEST(SharedGroupOptions_tmp_dir)
2983
{
2✔
2984
    const std::string initial_system_dir = DBOptions::get_sys_tmp_dir();
2✔
2985

1✔
2986
    const std::string test_dir = "/test-temp";
2✔
2987
    DBOptions::set_sys_tmp_dir(test_dir);
2✔
2988
    CHECK_EQUAL(DBOptions::get_sys_tmp_dir(), test_dir);
2✔
2989
    CHECK_EQUAL(DBOptions().temp_dir, test_dir);
2✔
2990

1✔
2991
    DBOptions::set_sys_tmp_dir(initial_system_dir);
2✔
2992
}
2✔
2993

2994

2995
namespace {
2996

2997
void wait_for(size_t expected, std::mutex& mutex, size_t& test_value)
2998
{
20✔
2999
    while (true) {
1,981✔
3000
        millisleep(1);
1,981✔
3001
        std::lock_guard<std::mutex> guard(mutex);
1,981✔
3002
        if (test_value == expected) {
1,981✔
3003
            return;
20✔
3004
        }
20✔
3005
    }
1,981✔
3006
}
20✔
3007

3008
} // end anonymous namespace
3009

3010
TEST(Shared_LockFileInitSpinsOnZeroSize)
3011
{
2✔
3012
    SHARED_GROUP_TEST_PATH(path);
2✔
3013

1✔
3014
    bool no_create = false;
2✔
3015
    DBOptions options;
2✔
3016
    options.encryption_key = crypt_key();
2✔
3017
    DBRef sg = DB::create(path, no_create, options);
2✔
3018
    sg->close();
2✔
3019

1✔
3020
    CHECK(File::exists(path));
2✔
3021
    CHECK(File::exists(path.get_lock_path()));
2✔
3022

1✔
3023
    std::mutex mutex;
2✔
3024
    size_t test_stage = 0;
2✔
3025

1✔
3026
    Thread t;
2✔
3027
    auto do_async = [&]() {
2✔
3028
        File f(path.get_lock_path(), File::mode_Write);
2✔
3029
        f.rw_lock_shared();
2✔
3030
        File::UnlockGuard ug(f);
2✔
3031

1✔
3032
        CHECK(f.is_attached());
2✔
3033

1✔
3034
        f.resize(0);
2✔
3035
        f.sync();
2✔
3036

1✔
3037
        mutex.lock();
2✔
3038
        test_stage = 1;
2✔
3039
        mutex.unlock();
2✔
3040

1✔
3041
        millisleep(100);
2✔
3042
        // the lock is then released and the other thread will be able to initialise properly
1✔
3043
    };
2✔
3044
    t.start(do_async);
2✔
3045

1✔
3046
    wait_for(1, mutex, test_stage);
2✔
3047

1✔
3048
    // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves
1✔
3049
    sg = DB::create(path, no_create, options);
2✔
3050
    CHECK(sg->is_attached());
2✔
3051
    sg->close();
2✔
3052

1✔
3053
    t.join();
2✔
3054
}
2✔
3055

3056

3057
TEST(Shared_LockFileSpinsOnInitComplete)
3058
{
2✔
3059
    SHARED_GROUP_TEST_PATH(path);
2✔
3060

1✔
3061
    bool no_create = false;
2✔
3062
    DBOptions options;
2✔
3063
    options.encryption_key = crypt_key();
2✔
3064
    DBRef sg = DB::create(path, no_create, options);
2✔
3065
    sg->close();
2✔
3066

1✔
3067
    CHECK(File::exists(path));
2✔
3068
    CHECK(File::exists(path.get_lock_path()));
2✔
3069

1✔
3070
    std::mutex mutex;
2✔
3071
    size_t test_stage = 0;
2✔
3072

1✔
3073
    Thread t;
2✔
3074
    auto do_async = [&]() {
2✔
3075
        File f(path.get_lock_path(), File::mode_Write);
2✔
3076
        f.rw_lock_shared();
2✔
3077
        File::UnlockGuard ug(f);
2✔
3078

1✔
3079
        CHECK(f.is_attached());
2✔
3080

1✔
3081
        f.resize(1); // ftruncate will write 0 to init_complete
2✔
3082
        f.sync();
2✔
3083

1✔
3084
        mutex.lock();
2✔
3085
        test_stage = 1;
2✔
3086
        mutex.unlock();
2✔
3087

1✔
3088
        millisleep(100);
2✔
3089
        // the lock is then released and the other thread will be able to initialise properly
1✔
3090
    };
2✔
3091
    t.start(do_async);
2✔
3092

1✔
3093
    wait_for(1, mutex, test_stage);
2✔
3094

1✔
3095
    // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves
1✔
3096
    sg = DB::create(path, no_create, options);
2✔
3097
    CHECK(sg->is_attached());
2✔
3098
    sg->close();
2✔
3099

1✔
3100
    t.join();
2✔
3101
}
2✔
3102

3103

3104
TEST(Shared_LockFileOfWrongSizeThrows)
3105
{
2✔
3106
    // NOTE: This unit test attempts to mimic the initialization of the .lock file as it takes place inside
1✔
3107
    // the SharedGroup::do_open() method. NOTE: If the layout of SharedGroup::SharedInfo should change,
1✔
3108
    // this unit test might stop working.
1✔
3109

1✔
3110
    SHARED_GROUP_TEST_PATH(path);
2✔
3111

1✔
3112
    bool no_create = false;
2✔
3113
    DBOptions options;
2✔
3114
    options.encryption_key = crypt_key();
2✔
3115
    DBRef sg = DB::create(path, no_create, options);
2✔
3116
    sg->close();
2✔
3117

1✔
3118
    CHECK(File::exists(path));
2✔
3119
    CHECK(File::exists(path.get_lock_path()));
2✔
3120

1✔
3121
    std::mutex mutex;
2✔
3122
    size_t test_stage = 0;
2✔
3123

1✔
3124
    Thread t;
2✔
3125
    auto do_async = [&]() {
2✔
3126
        File f(path.get_lock_path(), File::mode_Write);
2✔
3127
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3128
        f.rw_lock_shared();
2✔
3129
        File::UnlockGuard ug(f);
2✔
3130

1✔
3131
        CHECK(f.is_attached());
2✔
3132

1✔
3133
        size_t wrong_size = 100; // < sizeof(SharedInfo)
2✔
3134
        f.resize(wrong_size);    // ftruncate will fill with 0, which will set the init_complete flag to 0.
2✔
3135
        f.seek(0);
2✔
3136

1✔
3137
        // On Windows, we implement a shared lock on a file by locking the first byte of the file. Since
1✔
3138
        // you cannot write to a locked region using WriteFile(), we use memory mapping which works fine, and
1✔
3139
        // which is also the same method used by the .lock file initialization in SharedGroup::do_open()
1✔
3140
        char* mem = static_cast<char*>(f.map(realm::util::File::access_ReadWrite, 1));
2✔
3141

1✔
3142
        // set init_complete flag to 1 and sync
1✔
3143
        mem[0] = 1;
2✔
3144
        f.sync();
2✔
3145

1✔
3146
        CHECK_EQUAL(f.get_size(), wrong_size);
2✔
3147

1✔
3148
        mutex.lock();
2✔
3149
        test_stage = 1;
2✔
3150
        mutex.unlock();
2✔
3151

1✔
3152
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3153
    };
2✔
3154
    t.start(do_async);
2✔
3155

1✔
3156
    wait_for(1, mutex, test_stage);
2✔
3157

1✔
3158
    // we expect to throw if init_complete = 1 but the file is not the expected size (< sizeof(SharedInfo))
1✔
3159
    // we go through 10 retry attempts before throwing
1✔
3160
    CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile);
2✔
3161
    CHECK(!sg->is_attached());
2✔
3162

1✔
3163
    mutex.lock();
2✔
3164
    test_stage = 2;
2✔
3165
    mutex.unlock();
2✔
3166

1✔
3167
    t.join();
2✔
3168
}
2✔
3169

3170

3171
TEST(Shared_LockFileOfWrongVersionThrows)
3172
{
2✔
3173
    SHARED_GROUP_TEST_PATH(path);
2✔
3174

1✔
3175
    bool no_create = false;
2✔
3176
    DBOptions options;
2✔
3177
    options.encryption_key = crypt_key();
2✔
3178
    DBRef sg = DB::create(path, no_create, options);
2✔
3179

1✔
3180
    CHECK(File::exists(path));
2✔
3181
    CHECK(File::exists(path.get_lock_path()));
2✔
3182

1✔
3183
    std::mutex mutex;
2✔
3184
    size_t test_stage = 0;
2✔
3185

1✔
3186
    Thread t;
2✔
3187
    auto do_async = [&]() {
2✔
3188
        CHECK(File::exists(path.get_lock_path()));
2✔
3189

1✔
3190
        File f;
2✔
3191
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3192
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3193

1✔
3194
        f.rw_lock_shared();
2✔
3195
        File::UnlockGuard ug(f);
2✔
3196

1✔
3197
        CHECK(f.is_attached());
2✔
3198
        f.seek(6);
2✔
3199
        char bad_version = 0;
2✔
3200
        f.write(&bad_version, 1);
2✔
3201
        f.sync();
2✔
3202

1✔
3203
        mutex.lock();
2✔
3204
        test_stage = 1;
2✔
3205
        mutex.unlock();
2✔
3206

1✔
3207
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3208
    };
2✔
3209
    t.start(do_async);
2✔
3210

1✔
3211
    wait_for(1, mutex, test_stage);
2✔
3212
    sg->close();
2✔
3213

1✔
3214
    // we expect to throw if info->shared_info_version != g_shared_info_version
1✔
3215
    CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile);
2✔
3216
    CHECK(!sg->is_attached());
2✔
3217

1✔
3218
    mutex.lock();
2✔
3219
    test_stage = 2;
2✔
3220
    mutex.unlock();
2✔
3221

1✔
3222
    t.join();
2✔
3223
}
2✔
3224

3225

3226
TEST(Shared_LockFileOfWrongMutexSizeThrows)
3227
{
2✔
3228
    SHARED_GROUP_TEST_PATH(path);
2✔
3229

1✔
3230
    bool no_create = false;
2✔
3231
    DBOptions options;
2✔
3232
    options.encryption_key = crypt_key();
2✔
3233
    DBRef sg = DB::create(path, no_create, options);
2✔
3234

1✔
3235
    CHECK(File::exists(path));
2✔
3236
    CHECK(File::exists(path.get_lock_path()));
2✔
3237

1✔
3238
    std::mutex mutex;
2✔
3239
    size_t test_stage = 0;
2✔
3240

1✔
3241
    Thread t;
2✔
3242
    auto do_async = [&]() {
2✔
3243
        File f;
2✔
3244
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3245
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3246
        f.rw_lock_shared();
2✔
3247
        File::UnlockGuard ug(f);
2✔
3248

1✔
3249
        CHECK(f.is_attached());
2✔
3250

1✔
3251
        char bad_mutex_size = sizeof(InterprocessMutex::SharedPart) + 1;
2✔
3252
        f.seek(1);
2✔
3253
        f.write(&bad_mutex_size, 1);
2✔
3254
        f.sync();
2✔
3255

1✔
3256
        mutex.lock();
2✔
3257
        test_stage = 1;
2✔
3258
        mutex.unlock();
2✔
3259

1✔
3260
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3261
    };
2✔
3262
    t.start(do_async);
2✔
3263

1✔
3264
    wait_for(1, mutex, test_stage);
2✔
3265

1✔
3266
    sg->close();
2✔
3267

1✔
3268
    // we expect to throw if the mutex size is incorrect
1✔
3269
    CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile);
2✔
3270
    CHECK(!sg->is_attached());
2✔
3271

1✔
3272
    mutex.lock();
2✔
3273
    test_stage = 2;
2✔
3274
    mutex.unlock();
2✔
3275

1✔
3276
    t.join();
2✔
3277
}
2✔
3278

3279

3280
TEST(Shared_LockFileOfWrongCondvarSizeThrows)
3281
{
2✔
3282
    SHARED_GROUP_TEST_PATH(path);
2✔
3283

1✔
3284
    bool no_create = false;
2✔
3285
    DBOptions options;
2✔
3286
    options.encryption_key = crypt_key();
2✔
3287
    DBRef sg = DB::create(path, no_create, options);
2✔
3288

1✔
3289
    CHECK(File::exists(path));
2✔
3290
    CHECK(File::exists(path.get_lock_path()));
2✔
3291

1✔
3292
    std::mutex mutex;
2✔
3293
    size_t test_stage = 0;
2✔
3294

1✔
3295
    Thread t;
2✔
3296
    auto do_async = [&]() {
2✔
3297
        File f;
2✔
3298
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3299
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3300
        f.rw_lock_shared();
2✔
3301
        File::UnlockGuard ug(f);
2✔
3302

1✔
3303
        CHECK(f.is_attached());
2✔
3304

1✔
3305
        char bad_condvar_size = sizeof(InterprocessCondVar::SharedPart) + 1;
2✔
3306
        f.seek(2);
2✔
3307
        f.write(&bad_condvar_size, 1);
2✔
3308
        f.sync();
2✔
3309

1✔
3310
        mutex.lock();
2✔
3311
        test_stage = 1;
2✔
3312
        mutex.unlock();
2✔
3313

1✔
3314
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3315
    };
2✔
3316
    t.start(do_async);
2✔
3317

1✔
3318
    wait_for(1, mutex, test_stage);
2✔
3319
    sg->close();
2✔
3320

1✔
3321
    // we expect to throw if the condvar size is incorrect
1✔
3322
    CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile);
2✔
3323
    CHECK(!sg->is_attached());
2✔
3324

1✔
3325
    mutex.lock();
2✔
3326
    test_stage = 2;
2✔
3327
    mutex.unlock();
2✔
3328

1✔
3329
    t.join();
2✔
3330
}
2✔
3331

3332
TEST(Shared_ConstObject)
3333
{
2✔
3334
    SHARED_GROUP_TEST_PATH(path);
2✔
3335
    DBRef sg_w = DB::create(path);
2✔
3336
    TransactionRef writer = sg_w->start_write();
2✔
3337
    TableRef t = writer->add_table("Foo");
2✔
3338
    auto c = t->add_column(type_Int, "integers");
2✔
3339
    t->create_object(ObjKey(47)).set(c, 5);
2✔
3340
    writer->commit();
2✔
3341

1✔
3342
    TransactionRef reader = sg_w->start_read();
2✔
3343
    ConstTableRef t2 = reader->get_table("Foo");
2✔
3344
    const Obj obj = t2->get_object(ObjKey(47));
2✔
3345
    CHECK_EQUAL(obj.get<int64_t>(c), 5);
2✔
3346
}
2✔
3347

3348
TEST(Shared_ConstObjectIterator)
3349
{
2✔
3350
    SHARED_GROUP_TEST_PATH(path);
2✔
3351
    DBRef sg = get_test_db(path);
2✔
3352
    ColKey col;
2✔
3353
    {
2✔
3354
        TransactionRef writer = sg->start_write();
2✔
3355
        TableRef t = writer->add_table("Foo");
2✔
3356
        col = t->add_column(type_Int, "integers");
2✔
3357
        t->create_object(ObjKey(47)).set(col, 5);
2✔
3358
        t->create_object(ObjKey(99)).set(col, 8);
2✔
3359
        writer->commit();
2✔
3360
    }
2✔
3361
    {
2✔
3362
        TransactionRef writer = sg->start_write();
2✔
3363
        TableRef t2 = writer->get_or_add_table("Foo");
2✔
3364
        auto i1 = t2->begin();
2✔
3365
        auto i2 = t2->begin();
2✔
3366
        CHECK_EQUAL(i1->get<int64_t>(col), 5);
2✔
3367
        CHECK_EQUAL(i2->get<int64_t>(col), 5);
2✔
3368
        i1->set(col, 7);
2✔
3369
        CHECK_EQUAL(i2->get<int64_t>(col), 7);
2✔
3370
        ++i1;
2✔
3371
        CHECK_EQUAL(i1->get<int64_t>(col), 8);
2✔
3372
        writer->commit();
2✔
3373
    }
2✔
3374

1✔
3375
    // Now ensure that we can create a ConstIterator
1✔
3376
    TransactionRef reader = sg->start_read();
2✔
3377
    ConstTableRef t3 = reader->get_table("Foo");
2✔
3378
    auto i3 = t3->begin();
2✔
3379
    CHECK_EQUAL(i3->get<int64_t>(col), 7);
2✔
3380
    ++i3;
2✔
3381
    CHECK_EQUAL(i3->get<int64_t>(col), 8);
2✔
3382
    reader.reset();
2✔
3383
    // Verify that we can copy a ConstIterator - even an invalid one
1✔
3384
    TransactionRef writer = sg->start_write();
2✔
3385
    TableRef t4 = writer->get_table("Foo");
2✔
3386
    auto i4 = t4->begin();
2✔
3387
    t4->clear();
2✔
3388
    auto i5(i4);
2✔
3389
    // dereferencing an invalid iterator will throw
1✔
3390
    CHECK_THROW(*i5, realm::Exception);
2✔
3391
    // but moving it will not, it just stays invalid
1✔
3392
    ++i5;
2✔
3393
    i5 += 3;
2✔
3394
    // so, should still throw
1✔
3395
    CHECK_THROW(*i5, realm::Exception);
2✔
3396
    CHECK(i5 == t4->end());
2✔
3397
}
2✔
3398

3399
TEST(Shared_ConstList)
3400
{
2✔
3401
    SHARED_GROUP_TEST_PATH(path);
2✔
3402
    DBRef sg = get_test_db(path);
2✔
3403
    TransactionRef writer = sg->start_write();
2✔
3404

1✔
3405
    TableRef t = writer->add_table("Foo");
2✔
3406
    auto list_col = t->add_column_list(type_Int, "int_list");
2✔
3407
    t->create_object(ObjKey(47)).get_list<int64_t>(list_col).add(47);
2✔
3408
    writer->commit();
2✔
3409

1✔
3410
    TransactionRef reader = sg->start_read();
2✔
3411
    ConstTableRef t2 = reader->get_table("Foo");
2✔
3412
    const Obj obj = t2->get_object(ObjKey(47));
2✔
3413
    auto list1 = obj.get_list<int64_t>(list_col);
2✔
3414

1✔
3415
    CHECK_EQUAL(list1.get(0), 47);
2✔
3416
    CHECK_EQUAL(obj.get_listbase_ptr(list_col)->size(), 1);
2✔
3417
}
2✔
3418

3419
#ifdef LEGACY_TESTS
3420

3421
// Test if we can successfully open an existing encrypted file (generated by Core 4.0.3)
3422
#if !REALM_ANDROID // FIXME
3423
TEST_IF(Shared_DecryptExisting, REALM_ENABLE_ENCRYPTION)
3424
{
3425
    // Page size of system that reads the .realm file must be the same as on the system
3426
    // that created it, because we are running with encryption
3427
    std::string path = test_util::get_test_resource_path() + "test_shared_decrypt_" +
3428
                       realm::util::to_string(page_size() / 1024) + "k_page.realm";
3429

3430
#if 0 // set to 1 to generate the .realm file
3431
    {
3432
        File::try_remove(path);
3433
        //DB db(path, false, DBOptions(crypt_key(true)));
3434
        auto db = DB::create(path, false, DBOptions(crypt_key(true)));
3435
        auto rt = db->start_write();
3436
        //Group& group = db.begin_write();
3437
        TableRef table = rt->add_table("table");
3438
        auto c0 = table->add_column(type_String, "string");
3439
        auto o1 = table->create_object();
3440
        std::string s = std::string(size_t(1.5 * page_size()), 'a');
3441
        o1.set(c0, s);
3442
        rt->commit();
3443
    }
3444
#else
3445
    {
3446
        SHARED_GROUP_TEST_PATH(temp_copy);
3447
        File::copy(path, temp_copy);
3448
        // Use history as we will now have to upgrade file
3449
        std::unique_ptr<Replication> hist_w(make_in_realm_history());
3450
        auto db = DB::create(*hist_w, temp_copy, DBOptions(crypt_key(true)));
3451
        auto rt = db->start_read();
3452
        ConstTableRef table = rt->get_table("table");
3453
        auto o1 = *table->begin();
3454
        std::string s1 = o1.get<StringData>(table->get_column_key("string"));
3455
        std::string s2 = std::string(size_t(1.5 * page_size()), 'a');
3456
        CHECK_EQUAL(s1, s2);
3457
        rt->verify();
3458
    }
3459
#endif
3460
}
3461
#endif
3462
#endif // LEGACY_TESTS
3463

3464
TEST(Shared_RollbackFirstTransaction)
3465
{
2✔
3466
    SHARED_GROUP_TEST_PATH(path);
2✔
3467
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3468
    DBRef db = DB::create(*hist_w, path);
2✔
3469
    auto wt = db->start_write();
2✔
3470

1✔
3471
    wt->add_table("table");
2✔
3472
    wt->rollback_and_continue_as_read();
2✔
3473
}
2✔
3474

3475
TEST(Shared_SimpleTransaction)
3476
{
2✔
3477
    SHARED_GROUP_TEST_PATH(path);
2✔
3478
    std::unique_ptr<Replication> hist_r(make_in_realm_history());
2✔
3479
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3480

1✔
3481
    {
2✔
3482
        DBRef db_w = DB::create(*hist_w, path);
2✔
3483
        auto wt = db_w->start_write();
2✔
3484
        wt->verify();
2✔
3485
        wt->commit();
2✔
3486
        wt = nullptr;
2✔
3487
        wt = db_w->start_write();
2✔
3488
        wt->verify();
2✔
3489
        wt->commit();
2✔
3490
    }
2✔
3491
    DBRef db_r = DB::create(*hist_r, path);
2✔
3492
    {
2✔
3493
        auto rt = db_r->start_read();
2✔
3494
        rt->verify();
2✔
3495
    }
2✔
3496
}
2✔
3497

3498
TEST(Shared_OpenAfterClose)
3499
{
2✔
3500
    // Test case generated in [realm-core-6.0.0-rc1] on Wed Apr 11 16:08:05 2018.
1✔
3501
    // REALM_MAX_BPNODE_SIZE is 4
1✔
3502
    // ----------------------------------------------------------------------
1✔
3503
    SHARED_GROUP_TEST_PATH(path);
2✔
3504
    const char* key = nullptr;
2✔
3505
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3506
    DBRef db_w = DB::create(*hist_w, path, DBOptions(key));
2✔
3507
    auto wt = db_w->start_write();
2✔
3508

1✔
3509
    wt = nullptr;
2✔
3510
    db_w->close();
2✔
3511
    db_w = DB::create(path, true, DBOptions(key));
2✔
3512
    wt = db_w->start_write();
2✔
3513
    wt = nullptr;
2✔
3514
    db_w->close();
2✔
3515
}
2✔
3516

3517
TEST(Shared_RemoveTableWithEnumAndLinkColumns)
3518
{
2✔
3519
    // Test case generated with fuzzer
1✔
3520
    SHARED_GROUP_TEST_PATH(path);
2✔
3521
    DBRef db_w = DB::create(path);
2✔
3522
    TableKey tk;
2✔
3523
    {
2✔
3524
        auto wt = db_w->start_write();
2✔
3525
        wt->add_table("Table_2");
2✔
3526
        wt->commit();
2✔
3527
    }
2✔
3528
    {
2✔
3529
        auto wt = db_w->start_write();
2✔
3530
        auto table = wt->get_table("Table_2");
2✔
3531
        tk = table->get_key();
2✔
3532
        auto col_key = table->add_column(DataType(2), "string_3", false);
2✔
3533
        table->enumerate_string_column(col_key);
2✔
3534
        table->add_column(*table, "link_5");
2✔
3535
        table->add_search_index(col_key);
2✔
3536
        wt->commit();
2✔
3537
    }
2✔
3538
    {
2✔
3539
        auto wt = db_w->start_write();
2✔
3540
        wt->remove_table(tk);
2✔
3541
        wt->commit();
2✔
3542
    }
2✔
3543
}
2✔
3544

3545
TEST(Shared_GenerateObjectIdAfterRollback)
3546
{
2✔
3547
    // Test case generated in [realm-core-6.0.0-alpha.0] on Mon Aug 13 14:43:06 2018.
1✔
3548
    // REALM_MAX_BPNODE_SIZE is 1000
1✔
3549
    // ----------------------------------------------------------------------
1✔
3550
    SHARED_GROUP_TEST_PATH(path);
2✔
3551
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3552
    DBRef db_w = DB::create(*hist_w, path);
2✔
3553

1✔
3554
    auto wt = db_w->start_write();
2✔
3555

1✔
3556
    wt->add_table("Table_0");
2✔
3557
    {
2✔
3558
        std::vector<ObjKey> keys;
2✔
3559
        wt->get_table(TableKey(0))->create_objects(254, keys);
2✔
3560
    }
2✔
3561
    wt->commit_and_continue_as_read();
2✔
3562

1✔
3563
    wt->promote_to_write();
2✔
3564
    try {
2✔
3565
        wt->remove_table(TableKey(0));
2✔
3566
    }
2✔
3567
    catch (const CrossTableLinkTarget&) {
1✔
3568
    }
×
3569
    // Table accessor recycled
1✔
3570
    wt->rollback_and_continue_as_read();
2✔
3571

1✔
3572
    wt->promote_to_write();
2✔
3573
    // New table accessor created with m_next_key_value == -1
1✔
3574
    wt->get_table(TableKey(0))->clear();
2✔
3575
    {
2✔
3576
        std::vector<ObjKey> keys;
2✔
3577
        wt->get_table(TableKey(0))->create_objects(11, keys);
2✔
3578
    }
2✔
3579
    // table->m_next_key_value is now 11
1✔
3580
    wt->get_table(TableKey(0))->add_column(DataType(9), "float_1", false);
2✔
3581
    wt->rollback_and_continue_as_read();
2✔
3582

1✔
3583
    wt->promote_to_write();
2✔
3584
    // Should not try to create object with key == 11
1✔
3585
    {
2✔
3586
        std::vector<ObjKey> keys;
2✔
3587
        wt->get_table(TableKey(0))->create_objects(22, keys);
2✔
3588
    }
2✔
3589
}
2✔
3590

3591
TEST(Shared_UpgradeBinArray)
3592
{
2✔
3593
    // Checks that parent is updated appropriately when upgrading binary array
1✔
3594
    SHARED_GROUP_TEST_PATH(path);
2✔
3595
    DBRef db_w = DB::create(path);
2✔
3596
    ColKey col;
2✔
3597
    {
2✔
3598
        auto wt = db_w->start_write();
2✔
3599
        auto table = wt->add_table("Table_0");
2✔
3600
        std::vector<ObjKey> keys;
2✔
3601
        table->create_objects(65, keys);
2✔
3602
        col = table->add_column(type_Binary, "binary_0", true);
2✔
3603
        Obj obj = table->get_object(keys[0]);
2✔
3604
        // This will upgrade from small to big blobs. Parent should be updated.
1✔
3605
        obj.set(col, BinaryData{"dgrpnpgmjbchktdgagmqlihjckcdhpjccsjhnqlcjnbtersepknglaqnckqbffehqfgjnr"});
2✔
3606
        wt->commit();
2✔
3607
    }
2✔
3608

1✔
3609
    auto rt = db_w->start_read();
2✔
3610
    CHECK_NOT(rt->get_table(TableKey(0))->get_object(ObjKey(0)).is_null(col));
2✔
3611
    CHECK(rt->get_table(TableKey(0))->get_object(ObjKey(54)).is_null(col));
2✔
3612
}
2✔
3613

3614
#if !REALM_ANDROID // FIXME
3615
TEST_IF(Shared_MoreVersionsInUse, REALM_ENABLE_ENCRYPTION)
3616
{
2✔
3617
    SHARED_GROUP_TEST_PATH(path);
2✔
3618
    const char* key = "1234567890123456789012345678901123456789012345678901234567890123";
2✔
3619
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3620
    ColKey col;
2✔
3621
    {
2✔
3622
        DBRef db = DB::create(*hist, path, DBOptions(key));
2✔
3623
        {
2✔
3624
            auto wt = db->start_write();
2✔
3625
            auto t = wt->add_table("Table_0");
2✔
3626
            col = t->add_column(type_Int, "Integers");
2✔
3627
            t->create_object();
2✔
3628
            wt->add_table("Table_1");
2✔
3629
            wt->commit();
2✔
3630
        }
2✔
3631
        // Create a number of versions
1✔
3632
        for (int i = 0; i < 10; i++) {
22✔
3633
            auto wt = db->start_write();
20✔
3634
            auto t = wt->get_table("Table_1");
20✔
3635
            for (int j = 0; j < 200; j++) {
4,020✔
3636
                t->create_object();
4,000✔
3637
            }
4,000✔
3638
            wt->commit();
20✔
3639
        }
20✔
3640
    }
2✔
3641
    {
2✔
3642
        DBRef db = DB::create(*hist, path, DBOptions(key));
2✔
3643

1✔
3644
        // rt will now hold onto version 12
1✔
3645
        auto rt = db->start_read();
2✔
3646
        // Create table accessor to Table_0 - will use initial mapping
1✔
3647
        auto table_r = rt->get_table("Table_0");
2✔
3648
        {
2✔
3649
            auto wt = db->start_write();
2✔
3650
            auto t = wt->get_table("Table_1");
2✔
3651
            // This will require extention of the mappings
1✔
3652
            t->add_column(type_String, "Strings");
2✔
3653
            // Here the mappings no longer required will be purged
1✔
3654
            // Before the fix, this would delete the mapping used by
1✔
3655
            // table_r accessor
1✔
3656
            wt->commit();
2✔
3657
        }
2✔
3658

1✔
3659
        auto obj = table_r->get_object(0);
2✔
3660
        // Here we will need to translate a ref
1✔
3661
        auto i = obj.get<Int>(col);
2✔
3662
        CHECK_EQUAL(i, 0);
2✔
3663
    }
2✔
3664
}
2✔
3665

3666
TEST_IF(Shared_LinksToSameCluster, REALM_ENABLE_ENCRYPTION)
3667
{
2✔
3668
    // There was a problem when a link referred an object living in the same
1✔
3669
    // Cluster as the origin object.
1✔
3670
    SHARED_GROUP_TEST_PATH(path);
2✔
3671
    const char* key = "1234567890123456789012345678901123456789012345678901234567890123";
2✔
3672
    std::unique_ptr<Replication> hist(make_in_realm_history());
2✔
3673
    DBRef db = DB::create(*hist, path, DBOptions(key));
2✔
3674
    std::vector<ObjKey> keys;
2✔
3675
    {
2✔
3676
        auto wt = db->start_write();
2✔
3677
        auto rt = db->start_read();
2✔
3678
        std::vector<TableView> table_views;
2✔
3679

1✔
3680
        auto t = wt->add_table("Table_0");
2✔
3681
        // Create more object that can be held in a single cluster
1✔
3682
        t->create_objects(267, keys);
2✔
3683
        auto col = t->add_column(*wt->get_table("Table_0"), "link_0");
2✔
3684

1✔
3685
        // Create two links
1✔
3686
        Obj obj = t->get_object(keys[48]);
2✔
3687
        obj.set<ObjKey>(col, keys[0]);
2✔
3688
        obj = t->get_object(keys[49]);
2✔
3689
        obj.set<ObjKey>(col, keys[1]);
2✔
3690
        wt->commit();
2✔
3691
    }
2✔
3692
    {
2✔
3693
        auto wt = db->start_write();
2✔
3694
        // Delete origin obj
1✔
3695
        wt->get_table("Table_0")->remove_object(keys[48]);
2✔
3696
        wt->verify();
2✔
3697
        wt->commit();
2✔
3698
    }
2✔
3699
    {
2✔
3700
        auto wt = db->start_write();
2✔
3701
        // Delete target obj
1✔
3702
        wt->get_table("Table_0")->remove_object(keys[1]);
2✔
3703
        wt->verify();
2✔
3704
        wt->commit();
2✔
3705
    }
2✔
3706
}
2✔
3707
#endif
3708

3709
// Not much of a test. Mostly to exercise the code paths.
3710
TEST(Shared_GetCommitSize)
3711
{
2✔
3712
    SHARED_GROUP_TEST_PATH(path);
2✔
3713
    DBRef db = DB::create(path, false, DBOptions(crypt_key()));
2✔
3714
    size_t size_before;
2✔
3715
    size_t commit_size;
2✔
3716
    {
2✔
3717
        auto wt = db->start_write();
2✔
3718
        size_before = wt->get_used_space();
2✔
3719
        auto t = wt->add_table("foo");
2✔
3720
        auto col_int = t->add_column(type_Int, "Integers");
2✔
3721
        auto col_string = t->add_column(type_String, "Strings");
2✔
3722
        for (int i = 0; i < 10000; i++) {
20,002✔
3723
            std::string str = "Shared_CompactEncrypt" + util::to_string(i);
20,000✔
3724
            t->create_object().set(col_int, i + 0x10000).set(col_string, StringData(str));
20,000✔
3725
        }
20,000✔
3726
        commit_size = wt->get_commit_size();
2✔
3727
        auto allocated_size = db->get_allocated_size();
2✔
3728
        CHECK_LESS(commit_size, allocated_size);
2✔
3729
        wt->commit();
2✔
3730
    }
2✔
3731
    {
2✔
3732
        ReadTransaction rt(db);
2✔
3733
        auto size_after = rt.get_group().get_used_space();
2✔
3734
        // Commit size will always be bigger than actual size
1✔
3735
        CHECK_LESS(size_after - size_before, commit_size);
2✔
3736
    }
2✔
3737
}
2✔
3738

3739
/*
3740
#include <valgrind/callgrind.h>
3741
TEST(Shared_TimestampQuery)
3742
{
3743
    Table table;
3744
    auto col_date = table.add_column(type_Timestamp, "date", true);
3745

3746
    Random random(random_int<unsigned long>()); // Seed from slow global generator
3747

3748
    for (int i = 0; i < 10000; i++) {
3749
        auto ndx = table.add_empty_row();
3750
        int seconds = random.draw_int_max(3600 * 24 * 10);
3751
        table.set_timestamp(col_date, ndx, Timestamp(seconds, 0));
3752
    }
3753

3754
    Query q = table.column<Timestamp>(col_date) > Timestamp(3600 * 24 * 5, 3);
3755
    auto start = std::chrono::steady_clock::now();
3756
    CALLGRIND_START_INSTRUMENTATION;
3757
    auto cnt = q.count();
3758
    CALLGRIND_STOP_INSTRUMENTATION;
3759
    auto end = std::chrono::steady_clock::now();
3760

3761
    std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << " us"
3762
              << std::endl;
3763
    CHECK_GREATER(cnt, 50000);
3764
}
3765
*/
3766

3767
TEST_IF(Shared_LargeFile, TEST_DURATION > 0 && !REALM_ANDROID)
3768
{
×
3769
    SHARED_GROUP_TEST_PATH(path);
×
3770
    DBOptions options;
×
3771
    options.durability = DBOptions::Durability::MemOnly;
×
3772
    DBRef db = DB::create(path, false, options);
×
3773

3774
    auto tr = db->start_write();
×
3775

3776
    auto foo = tr->add_table("foo");
×
3777
    // Create more than 16 columns. The cluster array will always have a minimum
3778
    // size of 128, so if number of columns is lower than 17, then the array will
3779
    // always be able to hold 64 bit values.
3780
    for (size_t i = 0; i < 20; i++) {
×
3781
        std::string name = "Prop" + util::to_string(i);
×
3782
        foo->add_column(type_Int, name);
×
3783
    }
×
3784
    auto bar = tr->add_table("bar");
×
3785
    auto col_str = bar->add_column(type_String, "str");
×
3786

3787
    // Create enough objects to have a multi level cluster
3788
    for (size_t i = 0; i < 400; i++) {
×
3789
        foo->create_object();
×
3790
    }
×
3791

3792
    // Add a lot of data (nearly 2 Gb)
3793
    std::string string_1M(1024 * 1024, 'A');
×
3794
    for (size_t i = 0; i < 1900; i++) {
×
3795
        bar->create_object().set(col_str, string_1M);
×
3796
    }
×
3797
    tr->commit();
×
3798
    tr = db->start_write();
×
3799
    foo = tr->get_table("foo");
×
3800
    bar = tr->get_table("bar");
×
3801

3802
    std::vector<ObjKey> keys;
×
3803
    foo->create_objects(10000, keys);
×
3804

3805
    // By assigning 500 to the properties, we provoke a resize of the
3806
    // arrays. At some point an array will have a ref larger than
3807
    // 0x80000000 which will require a resize of the cluster array.
3808
    // If the cluster array does not have the capacity to accommodate
3809
    // this resize, then it must be reallocated, which will require
3810
    // the parent to be updated with the new ref. But as the array
3811
    // used as accessor to the cluster array in Obj::set does not
3812
    // have a parent this will cause a subsequent crash. To fix this
3813
    // we have ensured that the cluster array will always have the
3814
    // required capacity.
3815
    for (size_t i = 0; i < 10000; i++) {
×
3816
        auto obj = foo->get_object(keys[i]);
×
3817
        for (auto col : foo->get_column_keys()) {
×
3818
            obj.set(col, 500);
×
3819
        }
×
3820
    }
×
3821
    auto obj = foo->begin();
×
3822
    for (auto col : foo->get_column_keys()) {
×
3823
        obj->set(col, 500);
×
3824
    }
×
3825
}
×
3826

3827
TEST(Shared_EncryptionBug)
3828
{
2✔
3829
    SHARED_GROUP_TEST_PATH(path);
2✔
3830
    DBOptions options;
2✔
3831
    options.encryption_key = crypt_key(true);
2✔
3832
    {
2✔
3833
        DBRef db = DB::create(path, false, options);
2✔
3834
        {
2✔
3835
            WriteTransaction wt(db);
2✔
3836
            auto foo = wt.add_table("foo");
2✔
3837
            auto col_str = foo->add_column(type_String, "str");
2✔
3838
            std::string string_1M(1024 * 1024, 'A');
2✔
3839
            for (int i = 0; i < 64; i++) {
130✔
3840
                foo->create_object().set(col_str, string_1M);
128✔
3841
            }
128✔
3842
            wt.commit();
2✔
3843
        }
2✔
3844
        for (int i = 0; i < 2; i++) {
6✔
3845
            WriteTransaction wt(db);
4✔
3846
            auto foo = wt.get_table("foo");
4✔
3847
            auto col_str = foo->get_column_key("str");
4✔
3848
            foo->create_object().set(col_str, "boobar");
4✔
3849
            wt.commit();
4✔
3850
        }
4✔
3851
    }
2✔
3852

1✔
3853
    {
2✔
3854
        DBRef db = DB::create(path, false, options);
2✔
3855
        db->start_read()->verify();
2✔
3856
    }
2✔
3857
}
2✔
3858

3859
TEST(Shared_ManyColumns)
3860
{
2✔
3861
    // We had a bug where cluster array has to expand, but the new ref
1✔
3862
    // was not updated in the parent.
1✔
3863

1✔
3864
    SHARED_GROUP_TEST_PATH(path);
2✔
3865
    auto hist = make_in_realm_history();
2✔
3866
    DBRef db = DB::create(*hist, path);
2✔
3867

1✔
3868
    auto tr = db->start_write();
2✔
3869

1✔
3870
    auto foo = tr->add_table("foo");
2✔
3871
    // Create many columns. The cluster array will not be able to
1✔
3872
    // expand from 16 to 32 bits within the minimum allocation of 128 bytes.
1✔
3873
    for (size_t i = 0; i < 50; i++) {
102✔
3874
        std::string name = "Prop" + util::to_string(i);
100✔
3875
        foo->add_column(type_Int, name);
100✔
3876
    }
100✔
3877
    auto bar = tr->add_table("bar");
2✔
3878

1✔
3879
    tr->commit();
2✔
3880
    tr = db->start_write();
2✔
3881
    foo = tr->get_table("foo");
2✔
3882
    bar = tr->get_table("bar");
2✔
3883

1✔
3884
    std::vector<ObjKey> keys;
2✔
3885
    foo->create_objects(10000, keys);
2✔
3886

1✔
3887
    tr->commit_and_continue_as_read();
2✔
3888
    tr->promote_to_write();
2✔
3889

1✔
3890
    auto first = foo->begin();
2✔
3891
    for (auto col : foo->get_column_keys()) {
100✔
3892
        // 32 bit values will be inserted in the cluster array, so it needs expansion
50✔
3893
        first->set(col, 500);
100✔
3894
    }
100✔
3895

1✔
3896
    tr->commit_and_continue_as_read();
2✔
3897
    tr->verify();
2✔
3898

1✔
3899
    tr->promote_to_write();
2✔
3900
    foo->clear();
2✔
3901
    foo->create_object().set("Prop0", 500);
2✔
3902
}
2✔
3903

3904
TEST(Shared_MultipleDBInstances)
3905
{
2✔
3906
    SHARED_GROUP_TEST_PATH(path);
2✔
3907
    {
2✔
3908
        auto hist = make_in_realm_history();
2✔
3909
        DBRef db = DB::create(*hist, path);
2✔
3910
        auto tr = db->start_write();
2✔
3911
        auto t = tr->add_table("foo");
2✔
3912
        t->create_object();
2✔
3913
        t->add_column(type_Int, "value");
2✔
3914
        tr->commit();
2✔
3915
    }
2✔
3916

1✔
3917
    auto hist1 = make_in_realm_history();
2✔
3918
    DBRef db1 = DB::create(*hist1, path);
2✔
3919
    auto hist2 = make_in_realm_history();
2✔
3920
    DBRef db2 = DB::create(*hist2, path);
2✔
3921

1✔
3922
    auto tr = db1->start_write();
2✔
3923
    tr->commit();
2✔
3924
    // db1 now has m_youngest_live_version=3, db2 has m_youngest_live_version=2
1✔
3925

1✔
3926
    auto frozen = db2->start_frozen(); // version=3
2✔
3927
    auto table = frozen->get_table("foo");
2✔
3928

1✔
3929
    tr = db2->start_write();
2✔
3930
    // creates a new mapping and incorrectly marks the old one as being for
1✔
3931
    // version 2 rather than 3
1✔
3932
    tr->get_table("foo")->create_object();
2✔
3933
    // deletes the old mapping even though version 3 still needs it
1✔
3934
    tr->commit();
2✔
3935

1✔
3936
    // tries to use deleted mapping
1✔
3937
    CHECK_EQUAL(table->get_object(0).get<int64_t>("value"), 0);
2✔
3938
}
2✔
3939

3940
TEST(Shared_WriteCopy)
3941
{
2✔
3942
    SHARED_GROUP_TEST_PATH(path1);
2✔
3943
    SHARED_GROUP_TEST_PATH(path2);
2✔
3944
    SHARED_GROUP_TEST_PATH(path3);
2✔
3945

1✔
3946
    {
2✔
3947
        auto hist = make_in_realm_history();
2✔
3948
        DBRef db = DB::create(*hist, path1);
2✔
3949
        auto tr = db->start_write();
2✔
3950
        auto t = tr->add_table("foo");
2✔
3951
        t->create_object();
2✔
3952
        t->add_column(type_Int, "value");
2✔
3953
        tr->commit();
2✔
3954

1✔
3955
        db->write_copy(path2.c_str(), nullptr);
2✔
3956
        CHECK_THROW_ANY(db->write_copy(path2.c_str(), nullptr)); // Not allowed to overwrite
2✔
3957
    }
2✔
3958
    {
2✔
3959
        auto hist = make_in_realm_history();
2✔
3960
        DBRef db = DB::create(*hist, path2);
2✔
3961
        db->write_copy(path3.c_str(), nullptr);
2✔
3962
    }
2✔
3963
    auto hist = make_in_realm_history();
2✔
3964
    DBRef db = DB::create(*hist, path3);
2✔
3965
    CHECK_EQUAL(db->start_read()->get_table("foo")->size(), 1);
2✔
3966
}
2✔
3967

3968
TEST(Shared_CompareGroups)
3969
{
2✔
3970
    SHARED_GROUP_TEST_PATH(path1);
2✔
3971
    SHARED_GROUP_TEST_PATH(path2);
2✔
3972

1✔
3973
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
3974
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
3975

1✔
3976
    {
2✔
3977
        // Create schema in DB1
1✔
3978
        auto wt = db1->start_write();
2✔
3979
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
3980
        embedded->add_column(type_Float, "float");
2✔
3981
        // Embedded in embedded
1✔
3982
        embedded->add_column_dictionary(*embedded, "additional");
2✔
3983
        wt->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
3984

1✔
3985
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
3986
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
3987

1✔
3988
        baas->add_column(type_Bool, "bool");
2✔
3989
        baas->add_column_list(type_Int, "list");
2✔
3990
        baas->add_column_set(type_Int, "set");
2✔
3991
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
3992
        baas->add_column(*embedded, "embedded");
2✔
3993
        baas->add_column(type_Mixed, "any", true);
2✔
3994
        baas->add_column(*foos, "link");
2✔
3995

1✔
3996
        foos->add_column(type_String, "str");
2✔
3997
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
3998
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
3999
        foos->add_column_list(*baas, "link_list");
2✔
4000
        wt->commit();
2✔
4001
    }
2✔
4002
    {
2✔
4003
        // Create schema in DB2 - slightly different
1✔
4004
        auto wt = db2->start_write();
2✔
4005
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4006

1✔
4007
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
4008
        embedded->add_column(type_Float, "float");
2✔
4009
        // Embedded in embedded
1✔
4010
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4011

1✔
4012
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4013

1✔
4014
        baas->add_column_set(type_Int, "set");
2✔
4015
        baas->add_column(type_Mixed, "any", true);
2✔
4016
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4017
        baas->add_column(*embedded, "embedded");
2✔
4018
        baas->add_column(type_Bool, "bool");
2✔
4019
        baas->add_column(*foos, "link");
2✔
4020
        baas->add_column_list(type_Int, "list");
2✔
4021

1✔
4022
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4023
        foos->add_column(type_String, "str");
2✔
4024
        foos->add_column_list(*baas, "link_list");
2✔
4025
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4026

1✔
4027
        wt->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4028
        wt->commit();
2✔
4029
    }
2✔
4030
    auto create_objects = [&](DBRef db) {
4✔
4031
        auto wt = db->start_write();
4✔
4032

2✔
4033
        auto foos = wt->get_table("class_Foo");
4✔
4034
        auto baas = wt->get_table("class_Baa");
4✔
4035

2✔
4036
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
4✔
4037
        auto children = foo.get_linklist("list_of_embedded");
4✔
4038
        children.create_and_insert_linked_object(0).set("float", 10.f);
4✔
4039
        children.create_and_insert_linked_object(1).set("float", 20.f);
4✔
4040

2✔
4041
        auto baa = baas->create_object_with_primary_key(999);
4✔
4042
        baa.set("link", foo.get_key());
4✔
4043
        baa.set("bool", true);
4✔
4044
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
4✔
4045
        obj.set("float", 42.f);
4✔
4046
        auto additional = obj.get_dictionary("additional");
4✔
4047
        additional.create_and_insert_linked_object("One").set("float", 1.f);
4✔
4048
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
4✔
4049
        additional.create_and_insert_linked_object("Three").set("float", 3.f);
4✔
4050

2✔
4051
        foo.get_linklist("link_list").add(baa.get_key());
4✔
4052

2✔
4053
        // Basic collections
2✔
4054
        auto list = baa.get_list<Int>("list");
4✔
4055
        list.add(1);
4✔
4056
        list.add(2);
4✔
4057
        list.add(3);
4✔
4058
        auto set = baa.get_set<Int>("set");
4✔
4059
        set.insert(4);
4✔
4060
        set.insert(5);
4✔
4061
        set.insert(6);
4✔
4062
        auto dict = baa.get_dictionary("dictionary");
4✔
4063
        dict.insert("key7", 7);
4✔
4064
        dict.insert("key8", 8);
4✔
4065
        dict.insert("key9", 9);
4✔
4066

2✔
4067
        wt->commit();
4✔
4068
    };
4✔
4069
    create_objects(db1);
2✔
4070
    create_objects(db2);
2✔
4071
    CHECK(*db1->start_read() == *db2->start_read());
2✔
4072
    {
2✔
4073
        auto wt = db2->start_write();
2✔
4074
        auto baas = wt->get_table("class_Baa");
2✔
4075
        auto obj = baas->get_object_with_primary_key(999);
2✔
4076
        auto embedded = obj.get_linked_object("embedded");
2✔
4077
        embedded.get_dictionary("additional").get_object("One").set("float", 555.f);
2✔
4078
        wt->commit();
2✔
4079
    }
2✔
4080
    CHECK_NOT(*db1->start_read() == *db2->start_read());
2✔
4081
}
2✔
4082

4083
TEST(Shared_CopyReplication)
4084
{
2✔
4085
    SHARED_GROUP_TEST_PATH(path1);
2✔
4086
    SHARED_GROUP_TEST_PATH(path2);
2✔
4087

1✔
4088
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4089
    auto wt = db2->start_write();
2✔
4090

1✔
4091
    _impl::CopyReplication repl(wt);
2✔
4092
    DBRef db1 = DB::create(repl, path1);
2✔
4093
    auto tr = db1->start_write();
2✔
4094

1✔
4095
    // First create the local realm
1✔
4096
    {
2✔
4097
        // Create schema
1✔
4098
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
4099
        embedded->add_column(type_Float, "float");
2✔
4100
        // Embedded in embedded
1✔
4101
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4102
        tr->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4103

1✔
4104
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4105
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4106

1✔
4107
        baas->add_column(type_Bool, "bool");
2✔
4108
        baas->add_column_list(type_Int, "list");
2✔
4109
        baas->add_column_set(type_Int, "set");
2✔
4110
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4111
        baas->add_column(*embedded, "embedded");
2✔
4112
        baas->add_column(type_Mixed, "any", true);
2✔
4113
        baas->add_column(*foos, "link");
2✔
4114

1✔
4115
        foos->add_column(type_String, "str");
2✔
4116
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4117
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4118
        foos->add_column_list(*baas, "link_list");
2✔
4119

1✔
4120

1✔
4121
        /* Create local objects */
1✔
4122
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
2✔
4123
        auto children = foo.get_linklist("list_of_embedded");
2✔
4124
        children.create_and_insert_linked_object(0).set("float", 10.f);
2✔
4125
        children.create_and_insert_linked_object(1).set("float", 20.f);
2✔
4126

1✔
4127
        auto baa = baas->create_object_with_primary_key(999);
2✔
4128
        baa.set("link", foo.get_key());
2✔
4129
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4130
        obj.set("float", 42.f);
2✔
4131
        auto additional = obj.get_dictionary("additional");
2✔
4132

1✔
4133
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4134

1✔
4135
        // Basic collections
1✔
4136
        auto list = baa.get_list<Int>("list");
2✔
4137
        list.add(1);
2✔
4138
        list.add(2);
2✔
4139
        list.add(3);
2✔
4140
        auto set = baa.get_set<Int>("set");
2✔
4141
        set.insert(4);
2✔
4142
        set.insert(5);
2✔
4143
        set.insert(6);
2✔
4144
        auto dict = baa.get_dictionary("dictionary");
2✔
4145
        dict.insert("key7", 7);
2✔
4146
        dict.insert("key8", 8);
2✔
4147
        dict.insert("key9", 9);
2✔
4148

1✔
4149
        additional.create_and_insert_linked_object("One").set("float", 1.f);
2✔
4150
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
2✔
4151
        auto embedded_obj = additional.create_and_insert_linked_object("Three");
2✔
4152

1✔
4153
        auto baa1 = baas->create_object_with_primary_key(666).set("link", foo.get_key());
2✔
4154
        obj = baa1.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4155
        additional = obj.get_dictionary("additional");
2✔
4156
        additional.create_and_insert_linked_object("Item").set("float", 100.f);
2✔
4157
        obj.set("float", 35.f);
2✔
4158
        foo = foos->create_object_with_primary_key("456");
2✔
4159
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4160
        any_list.add(Mixed(baa1.get_link()));
2✔
4161
        any_list.add(Mixed(foo.get_link()));
2✔
4162
        foos->create_object_with_primary_key("789");
2✔
4163
        baa1.set("any", Mixed(foo.get_link()));
2✔
4164
        baa.set("bool", true);
2✔
4165
        embedded_obj.set("float", 3.f);
2✔
4166
    }
2✔
4167
    tr->commit();
2✔
4168

1✔
4169
    wt->commit_and_continue_as_read();
2✔
4170
    auto rt = db1->start_read();
2✔
4171
    CHECK(*rt == *wt);
2✔
4172
}
2✔
4173

4174
TEST(Shared_WriteTo)
4175
{
2✔
4176
    SHARED_GROUP_TEST_PATH(path1);
2✔
4177
    SHARED_GROUP_TEST_PATH(path2);
2✔
4178

1✔
4179
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
4180
    auto tr = db1->start_write();
2✔
4181

1✔
4182
    // First create the local realm
1✔
4183
    {
2✔
4184
        // Create schema
1✔
4185
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
4186
        embedded->add_column(type_Float, "float");
2✔
4187
        // Embedded in embedded
1✔
4188
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4189
        tr->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4190

1✔
4191
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4192
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4193

1✔
4194
        baas->add_column(type_Bool, "bool");
2✔
4195
        baas->add_column_list(type_Int, "list");
2✔
4196
        baas->add_column_set(type_Int, "set");
2✔
4197
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4198
        baas->add_column(*embedded, "embedded");
2✔
4199
        baas->add_column(type_Mixed, "any", true);
2✔
4200
        baas->add_column(*foos, "link");
2✔
4201

1✔
4202
        auto col_str = foos->add_column(type_String, "str");
2✔
4203
        foos->add_search_index(col_str);
2✔
4204
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4205
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4206
        foos->add_column_list(*baas, "link_list");
2✔
4207

1✔
4208

1✔
4209
        /* Create local objects */
1✔
4210
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
2✔
4211
        auto children = foo.get_linklist("list_of_embedded");
2✔
4212
        children.create_and_insert_linked_object(0).set("float", 10.f);
2✔
4213
        children.create_and_insert_linked_object(1).set("float", 20.f);
2✔
4214

1✔
4215
        auto baa = baas->create_object_with_primary_key(999);
2✔
4216
        baa.set("link", foo.get_key());
2✔
4217
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4218
        obj.set("float", 42.f);
2✔
4219
        auto additional = obj.get_dictionary("additional");
2✔
4220
        additional.create_and_insert_linked_object("One").set("float", 1.f);
2✔
4221
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
2✔
4222
        additional.create_and_insert_linked_object("Three").set("float", 3.f);
2✔
4223

1✔
4224
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4225

1✔
4226
        // Basic collections
1✔
4227
        auto list = baa.get_list<Int>("list");
2✔
4228
        list.add(1);
2✔
4229
        list.add(2);
2✔
4230
        list.add(3);
2✔
4231
        auto set = baa.get_set<Int>("set");
2✔
4232
        set.insert(4);
2✔
4233
        set.insert(5);
2✔
4234
        set.insert(6);
2✔
4235
        auto dict = baa.get_dictionary("dictionary");
2✔
4236
        dict.insert("key7", 7);
2✔
4237
        dict.insert("key8", 8);
2✔
4238
        dict.insert("key9", 9);
2✔
4239

1✔
4240
        auto baa1 = baas->create_object_with_primary_key(666).set("link", foo.get_key());
2✔
4241
        obj = baa1.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4242
        additional = obj.get_dictionary("additional");
2✔
4243
        additional.create_and_insert_linked_object("Item").set("float", 100.f);
2✔
4244
        obj.set("float", 35.f);
2✔
4245
        foo = foos->create_object_with_primary_key("456");
2✔
4246
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4247
        any_list.add(Mixed(baa1.get_link()));
2✔
4248
        any_list.add(Mixed(foo.get_link()));
2✔
4249
        foos->create_object_with_primary_key("789");
2✔
4250
        baa1.set("any", Mixed(foo.get_link()));
2✔
4251
        baa.set("bool", true);
2✔
4252
    }
2✔
4253
    tr->commit_and_continue_as_read();
2✔
4254
    // tr->to_json(std::cout);
1✔
4255

1✔
4256

1✔
4257
    // Create remote db
1✔
4258
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4259
    {
2✔
4260
        auto wt = db2->start_write();
2✔
4261

1✔
4262
        // Create schema - where some columns are missing. This will cause the
1✔
4263
        // ColKeys to be different
1✔
4264
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
4265
        embedded->add_column(type_Float, "float");
2✔
4266
        // Embedded in embedded
1✔
4267
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4268

1✔
4269
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4270
        baas->add_column(*embedded, "embedded");
2✔
4271

1✔
4272
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4273
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4274

1✔
4275
        baas->create_object_with_primary_key(333);
2✔
4276
        auto baa = baas->create_object_with_primary_key(666);
2✔
4277
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4278
        obj.set("float", 99.f);
2✔
4279
        auto additional = obj.get_dictionary("additional");
2✔
4280
        additional.create_and_insert_linked_object("Item").set("float", 200.f);
2✔
4281
        foos->create_object_with_primary_key("789").get_list<Mixed>("list_of_any").add(Mixed(baa.get_link()));
2✔
4282
        wt->commit();
2✔
4283
    }
2✔
4284

1✔
4285
    // Copy local object over
1✔
4286
    auto dest = db2->start_write();
2✔
4287
    tr->copy_to(dest);
2✔
4288
    dest->commit_and_continue_as_read();
2✔
4289

1✔
4290
    // The difference between the two realms should now be that the remote db has an
1✔
4291
    // extra baa object with pk 333.
1✔
4292
    CHECK_NOT(*tr == *dest);
2✔
4293
    tr->promote_to_write();
2✔
4294
    // So if we add this to the local realm, they should match
1✔
4295
    tr->get_table("class_Baa")->create_object_with_primary_key(333);
2✔
4296
    tr->commit_and_continue_as_read();
2✔
4297
    CHECK(*tr == *dest);
2✔
4298
}
2✔
4299

4300
TEST(Shared_WriteToFail)
4301
{
2✔
4302
    SHARED_GROUP_TEST_PATH(path1);
2✔
4303
    SHARED_GROUP_TEST_PATH(path2);
2✔
4304

1✔
4305
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
4306
    auto tr = db1->start_write();
2✔
4307

1✔
4308
    // First create the local realm
1✔
4309
    {
2✔
4310
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4311

1✔
4312
        foos->add_column(type_String, "classification");
2✔
4313
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4314

1✔
4315
        /* Create local objects */
1✔
4316
        auto foo = foos->create_object_with_primary_key("anders.andk@andeby.io").set("classification", "Duck");
2✔
4317
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4318
        any_list.add(Mixed(17));
2✔
4319
        any_list.add(Mixed("Hello"));
2✔
4320
    }
2✔
4321
    tr->commit_and_continue_as_read();
2✔
4322
    // tr->to_json(std::cout);
1✔
4323

1✔
4324
    ColKey col_fail;
2✔
4325
    // Create remote db
1✔
4326
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4327
    {
2✔
4328
        auto wt = db2->start_write();
2✔
4329
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4330
        col_fail = foos->add_column(type_Int, "classification");
2✔
4331
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4332
        wt->commit();
2✔
4333
    }
2✔
4334

1✔
4335
    auto dest = db2->start_write();
2✔
4336
    std::string message;
2✔
4337

1✔
4338
    CHECK_THROW_ANY_GET_MESSAGE(tr->copy_to(dest), message);
2✔
4339
    CHECK_EQUAL(message, "Incompatible property: class_Foo::classification");
2✔
4340

1✔
4341
    auto foos = dest->get_table("class_Foo");
2✔
4342
    foos->remove_column(col_fail);
2✔
4343
    col_fail = foos->add_column(type_String, "classification", true); // Now nullable
2✔
4344
    dest->commit();
2✔
4345
    dest = db2->start_write();
2✔
4346

1✔
4347
    CHECK_THROW_ANY_GET_MESSAGE(tr->copy_to(dest), message);
2✔
4348
    CHECK_EQUAL(message, "Incompatible property: class_Foo::classification");
2✔
4349

1✔
4350
    foos = dest->get_table("class_Foo");
2✔
4351
    foos->remove_column(col_fail);
2✔
4352
    col_fail = foos->add_column(type_String, "classification");
2✔
4353
    dest->commit();
2✔
4354
    dest = db2->start_write();
2✔
4355
    tr->copy_to(dest);
2✔
4356
    dest->commit_and_continue_as_read();
2✔
4357

1✔
4358
    CHECK(*tr == *dest);
2✔
4359
}
2✔
4360

4361
NONCONCURRENT_TEST_IF(Shared_LockFileConcurrentInit, testing_supports_spawn_process)
4362
{
2✔
4363
    auto path = realm::test_util::get_test_path(test_context.get_test_name(), ".test-dir");
2✔
4364
    test_util::TestDirGuard test_dir(path, false);
2✔
4365
    test_dir.do_remove = SpawnedProcess::is_parent();
2✔
4366
    auto lock_prefix = std::string(path) + "/lock";
2✔
4367

1✔
4368
    struct Lock {
2✔
4369
        std::unique_ptr<InterprocessMutex> mutex;
2✔
4370
        std::unique_ptr<InterprocessMutex::SharedPart> sp;
2✔
4371

1✔
4372
        Lock(const std::string& name, const std::string& lock_prefix_path)
2✔
4373
            : mutex(std::make_unique<InterprocessMutex>())
2✔
4374
            , sp(std::make_unique<InterprocessMutex::SharedPart>())
2✔
4375
        {
1✔
4376
            mutex->set_shared_part(*sp, lock_prefix_path, name);
×
4377
        }
×
4378

1✔
4379
        Lock(Lock&&) = default;
1✔
4380
        Lock& operator=(Lock&&) = default;
2✔
4381
    };
2✔
4382

1✔
4383
    for (size_t i = 0; i < 10; ++i) {
22✔
4384
        std::vector<std::unique_ptr<SpawnedProcess>> spawned;
20✔
4385

10✔
4386
        // create multiple processes initializing multiple same purpose locks
10✔
4387
        for (size_t j = 0; j < 10; ++j) {
220✔
4388
            spawned.emplace_back(
200✔
4389
                test_util::spawn_process(test_context.test_details.test_name, util::format("child [%1]", i)));
200✔
4390

100✔
4391
            if (spawned.back()->is_child()) {
200✔
4392
                std::vector<Lock> locks;
×
4393

4394
                // mimic the same impl detail as in DB and hope it'd trigger some assertions
4395
                for (auto tag : {"write", "control", "versions"}) {
×
4396
                    locks.emplace_back(tag, lock_prefix);
×
4397
                    CHECK(locks.back().mutex->is_valid());
×
4398
                }
×
4399

4400
                // if somehow initialization is scrambled or there is an issues with
4401
                // underlying files then it should hang here
4402
                for (int k = 0; k < 3; ++k) {
×
4403
                    for (auto&& lock : locks)
×
4404
                        lock.mutex->lock();
×
4405
                    for (auto&& lock : locks)
×
4406
                        lock.mutex->unlock();
×
4407
                }
×
4408

4409
                exit(0);
×
4410
            }
×
4411
        }
200✔
4412

10✔
4413
        if (SpawnedProcess::is_parent()) {
20✔
4414
            for (auto&& process : spawned)
20✔
4415
                process->wait_for_child_to_finish();
200✔
4416

10✔
4417
            // start everytime with no lock files for mutexes
10✔
4418
            test_dir.clean_dir();
20✔
4419
        }
20✔
4420
    }
20✔
4421
}
2✔
4422

4423
#endif // TEST_SHARED
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc