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

realm / realm-core / 2409

11 Jun 2024 12:22PM UTC coverage: 90.974% (+0.02%) from 90.951%
2409

push

Evergreen

web-flow
Optimize Query::between for integers and timestamps (#7785)

* Add benchmark test for QueryRange<Timestamp>

102158 of 180430 branches covered (56.62%)

122 of 123 new or added lines in 9 files covered. (99.19%)

37 existing lines in 10 files now uncovered.

214729 of 236033 relevant lines covered (90.97%)

5697886.12 hits per line

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

96.73
/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;
188
    try {
2✔
189
        auto tr = sg->start_read();
2✔
190
        bool done = false;
2✔
191
        // std::cerr << "Opened sg " << std::endl;
192
        for (int i = 0; !done; ++i) {
10✔
193
            // std::cerr << "       - " << getpid() << std::endl;
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;
209
        tr->end_read();
2✔
210
    }
2✔
211
    catch (...) {
2✔
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
        DBOptions options(crypt_key());
222
        options.no_create = true;
223
        DBRef sg = DB::create(path, options);
224
        bool done = false;
225
        do {
226
            sched_yield();
227
            // pseudo randomized wait (to prevent unwanted synchronization effects of yield):
228
            int n = random() % 10000;
229
            volatile int thing = 0;
230
            while (n--)
231
                thing += random();
232
            ReadTransaction rt(sg);
233
            rt.get_group().verify();
234
            auto t1 = rt.get_table("test");
235
            auto cols = t1->get_column_keys();
236
            auto obj = t1->get_object(ObjKey(id));
237
            done = 10 < obj.get<Int>(cols[0]);
238
        } while (!done);
239
    }
240
    kill(pid, 9);
241
    int stat_loc = 0;
242
    int options = 0;
243
    int ret_pid = waitpid(pid, &stat_loc, options);
244
    if (ret_pid == pid_t(-1)) {
245
        if (errno == EINTR)
246
            std::cerr << "waitpid was interrupted" << std::endl;
247
        if (errno == EINVAL)
248
            std::cerr << "waitpid got bad arguments" << std::endl;
249
        if (errno == ECHILD)
250
            std::cerr << "waitpid tried to wait for the wrong child: " << pid << std::endl;
251
        REALM_TERMINATE("waitpid failed");
252
    }
253
    bool child_exited_from_signal = WIFSIGNALED(stat_loc);
254
    CHECK(child_exited_from_signal);
255
    int child_exit_status = WEXITSTATUS(stat_loc);
256
    CHECK_EQUAL(0, child_exit_status);
257
    {
258
        // Verify that we surely did kill the process before it could do all it's commits.
259
        DBOptions options;
260
        options.no_create = true;
261
        DBRef sg = DB::create(path, options);
262
        ReadTransaction rt(sg);
263
        rt.get_group().verify();
264
        auto t1 = rt.get_table("test");
265
        auto cols = t1->get_column_keys();
266
        auto obj = t1->get_object(ObjKey(id));
267
        CHECK(10 < obj.get<Int>(cols[0]));
268
    }
269
}
270

271
#endif
272
} // anonymous namespace
273

274

275
#if !defined(_WIN32) && !REALM_ENABLE_ENCRYPTION && !REALM_ANDROID
276

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

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

333
#endif
334

335
#if 0
336

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

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

356
        std::string path = "test.realm";
357

358
        SharedGroup sg(path, DBOptions("1234567890123456789012345678901123456789012345678901234567890123"));
359
        //    SharedGroup sg(path, false, SharedGroupOptions(nullptr));
360

361
        int seed = time(0);
362
        fastrand(seed, true);
363

364
        int foo = fastrand(100);
365
        if (foo > 50) {
366
            const Group& g = sg.begin_read();
367
            g.verify();
368
            continue;
369
        }
370

371
        int action = fastrand(100);
372

373
        WriteTransaction wt(sg);
374
        auto t1 = wt.get_or_add_table("test");
375

376
        t1->verify();
377

378
        if (t1->size() == 0) {
379
            t1->add_column(type_String, "name");
380
        }
381

382
        std::string str(fastrand(3000), 'a');
383

384
        size_t rows = fastrand(3000);
385

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

398
        if (fastrand(100) < 5) {
399
            File::try_remove("y");
400
            t1->clear();
401
            File::copy("x", "y");
402
        }
403

404
        if (fastrand(100) < 90) {
405
            wt.commit();
406
        }
407

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

417
    }
418
}
419

420
#endif // Only disables above special unit test
421

422

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

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

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

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

494

495
TEST(Shared_ReadAfterCompact)
496
{
2✔
497
    SHARED_GROUP_TEST_PATH(path);
2✔
498
    {
2✔
499
        DBRef sg = DB::create(make_in_realm_history(), path);
2✔
500
        WriteTransaction wt(sg);
2✔
501
        auto table = wt.add_table("table");
2✔
502
        table->add_column(type_Int, "col");
2✔
503
        table->create_object().set_all(1);
2✔
504
        wt.commit();
2✔
505
        sg->compact();
2✔
506
    }
2✔
507
    {
2✔
508
        DBOptions options;
2✔
509
        options.allow_file_format_upgrade = false;
2✔
510
        DBRef sg = DB::create(make_in_realm_history(), path, options);
2✔
511
        auto rt = sg->start_read();
2✔
512
        auto table = rt->get_table("table");
2✔
513
        for (int i = 2; i < 4; ++i) {
6✔
514
            WriteTransaction wt(sg);
4✔
515
            wt.get_table("table")->create_object().set_all(i);
4✔
516
            wt.commit();
4✔
517
        }
4✔
518

519
        CHECK_EQUAL(table->size(), 1);
2✔
520
        auto obj = table->get_object(0);
2✔
521
        CHECK_EQUAL(obj.get<int64_t>("col"), 1);
2✔
522

523
        struct Parser : _impl::NoOpTransactionLogParser {
2✔
524
            bool create_object(ObjKey)
2✔
525
            {
4✔
526
                nb_objects++;
4✔
527
                return true;
4✔
528
            }
4✔
529
            bool modify_object(ColKey, ObjKey)
2✔
530
            {
2✔
531
                return true;
×
532
            }
×
533
            void parse_complete() {}
2✔
534
            int nb_objects = 0;
2✔
535
        } parser;
2✔
536

537
        rt->advance_read(&parser);
2✔
538
        CHECK_EQUAL(parser.nb_objects, 2);
2✔
539
    }
2✔
540
}
2✔
541

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

565
TEST(Shared_ReadOverReadAfterCompact)
566
{
2✔
567
    SHARED_GROUP_TEST_PATH(path);
2✔
568
    DBRef sg = get_test_db(path);
2✔
569
    {
2✔
570
        WriteTransaction wt(sg);
2✔
571
        auto table = wt.add_table("table");
2✔
572
        table->add_column(type_Int, "col");
2✔
573
        table->create_object().set_all(1);
2✔
574
        wt.commit();
2✔
575
    }
2✔
576
    sg->compact();
2✔
577
    DBRef sg2 = get_test_db(path);
2✔
578
    auto rt = sg->start_read();
2✔
579
    auto rt2 = sg2->start_read();
2✔
580
    auto table = rt->get_table("table");
2✔
581
    for (int i = 2; i < 4; ++i) {
6✔
582
        WriteTransaction wt(sg);
4✔
583
        wt.get_table("table")->create_object().set_all(i);
4✔
584
        wt.commit();
4✔
585
    }
4✔
586
    CHECK_EQUAL(table->size(), 1);
2✔
587
    CHECK_EQUAL(table->get_object(0).get<int64_t>("col"), 1);
2✔
588

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_ReadOverRead2)
601
{
2✔
602
    SHARED_GROUP_TEST_PATH(path);
2✔
603
    {
2✔
604
        DBRef sg = get_test_db(path);
2✔
605
        {
2✔
606
            WriteTransaction wt(sg);
2✔
607
            auto table = wt.add_table("table");
2✔
608
            table->add_column(type_Int, "col");
2✔
609
            table->create_object().set_all(1);
2✔
610
            wt.commit();
2✔
611
        }
2✔
612
    }
2✔
613
    DBRef sg2 = get_test_db(path);
2✔
614
    DBRef sg3 = get_test_db(path);
2✔
615
    auto rt2 = sg2->start_read();
2✔
616
    auto table2 = rt2->get_table("table");
2✔
617
    for (int i = 2; i < 4; ++i) {
6✔
618
        WriteTransaction wt(sg2);
4✔
619
        wt.get_table("table")->create_object().set_all(i);
4✔
620
        wt.commit();
4✔
621
    }
4✔
622
    CHECK_EQUAL(table2->size(), 1);
2✔
623
    CHECK_EQUAL(table2->get_object(0).get<int64_t>("col"), 1);
2✔
624
}
2✔
625

626
TEST(Shared_EncryptedRemap)
627
{
2✔
628
    // Attempts to trigger code coverage in util::mremap() for the case where the file is encrypted.
629
    // This requires a "non-encrypted database size" (not physical file size) which is non-divisible
630
    // by page_size() *and* is bigger than current allocated section. Following row count and payload
631
    // seems to work on both Windows+Linux
632
    const int64_t rows = 12;
2✔
633
    SHARED_GROUP_TEST_PATH(path);
2✔
634
    {
2✔
635
        DBRef sg = get_test_db(path, crypt_key());
2✔
636

637
        // Create table entries
638

639
        WriteTransaction wt(sg);
2✔
640
        auto t1 = wt.add_table("test");
2✔
641
        test_table_add_columns(t1);
2✔
642
        std::string str(100000, 'a');
2✔
643
        for (int64_t i = 0; i < rows; ++i) {
26✔
644
            t1->create_object().set_all(0, i, false, str.c_str());
24✔
645
        }
24✔
646
        wt.commit();
2✔
647
    }
2✔
648

649
    DBRef sg2 = get_test_db(path, crypt_key());
2✔
650

651
    CHECK_EQUAL(true, sg2->compact());
2✔
652
    ReadTransaction rt2(sg2);
2✔
653
    auto table = rt2.get_table("test");
2✔
654
    CHECK(table);
2✔
655
    CHECK_EQUAL(table->size(), rows);
2✔
656
    rt2.get_group().verify();
2✔
657
}
2✔
658

659

660
TEST(Shared_Initial)
661
{
2✔
662
    SHARED_GROUP_TEST_PATH(path);
2✔
663
    std::vector<char> key;
2✔
664

665
    CHECK_NOT(DB::needs_file_format_upgrade(path, key)); // File not created yet
2✔
666

667
    auto key_str = crypt_key();
2✔
668
    {
2✔
669
        // Create a new shared db
670
        DBRef sg = DB::create(path, DBOptions(key_str));
2✔
671

672
        // Verify that new group is empty
673
        {
2✔
674
            ReadTransaction rt(sg);
2✔
675
            CHECK(rt.get_group().is_empty());
2✔
676
        }
2✔
677
    }
2✔
678
    if (key_str) {
2✔
679
        key.insert(key.end(), key_str, key_str + strlen(key_str));
×
680
    }
×
681
    CHECK_NOT(DB::needs_file_format_upgrade(path, key));
2✔
682
}
2✔
683

684

685
TEST(Shared_InitialMem)
686
{
2✔
687
    SHARED_GROUP_TEST_PATH(path);
2✔
688
    {
2✔
689
        // Create a new shared db
690
        DBRef sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
2✔
691

692
        // Verify that new group is empty
693
        {
2✔
694
            ReadTransaction rt(sg);
2✔
695
            CHECK(rt.get_group().is_empty());
2✔
696
        }
2✔
697
    }
2✔
698

699
    // In MemOnly mode, the database file must be automatically
700
    // removed.
701
    CHECK(!File::exists(path));
2✔
702
}
2✔
703

704

705
TEST(Shared_InitialMem_StaleFile)
706
{
2✔
707
    SHARED_GROUP_TEST_PATH(path);
2✔
708

709
    // On platforms which do not support automatically deleting a file when it's
710
    // closed, MemOnly files won't be deleted if the process crashes, and so any
711
    // existing file at the given path should be overwritten if no one has the
712
    // file open
713

714
    // Create a MemOnly realm at the path so that a lock file gets initialized
715
    DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
2✔
716
    CHECK(!File::exists(path));
2✔
717
    CHECK(File::exists(path.get_lock_path()));
2✔
718

719
    // Create a file at the DB path to fake a process crashing and failing to
720
    // delete it
721
    {
2✔
722
        File f(path, File::mode_Write);
2✔
723
        f.write(0, "text");
2✔
724
    }
2✔
725
    CHECK(File::exists(path));
2✔
726
    CHECK(File::exists(path.get_lock_path()));
2✔
727

728
    // Verify that we can still open the path as a MemOnly SharedGroup and that
729
    // it's cleaned up afterwards
730
    {
2✔
731
        DBRef db = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
2✔
732
        CHECK(File::exists(path));
2✔
733
    }
2✔
734
    CHECK(!File::exists(path));
2✔
735
    CHECK(File::exists(path.get_lock_path()));
2✔
736
}
2✔
737

738

739
TEST(Shared_Initial2)
740
{
2✔
741
    SHARED_GROUP_TEST_PATH(path);
2✔
742
    {
2✔
743
        // Create a new shared db
744
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
745

746
        {
2✔
747
            // Open the same db again (in empty state)
748
            DBRef sg2 = DB::create(path, DBOptions(crypt_key()));
2✔
749

750
            // Verify that new group is empty
751
            {
2✔
752
                ReadTransaction rt(sg2);
2✔
753
                CHECK(rt.get_group().is_empty());
2✔
754
            }
2✔
755

756
            // Add a new table
757
            {
2✔
758
                WriteTransaction wt(sg2);
2✔
759
                wt.get_group().verify();
2✔
760
                auto t1 = wt.add_table("test");
2✔
761
                test_table_add_columns(t1);
2✔
762
                t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
763
                wt.commit();
2✔
764
            }
2✔
765
        }
2✔
766

767
        // Verify that the new table has been added
768
        {
2✔
769
            ReadTransaction rt(sg);
2✔
770
            rt.get_group().verify();
2✔
771
            auto t1 = rt.get_table("test");
2✔
772
            auto cols = t1->get_column_keys();
2✔
773
            CHECK_EQUAL(1, t1->size());
2✔
774
            const Obj obj = t1->get_object(ObjKey(7));
2✔
775
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
776
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
777
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
778
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
779
        }
2✔
780
    }
2✔
781
}
2✔
782

783

784
TEST(Shared_Initial2_Mem)
785
{
2✔
786
    SHARED_GROUP_TEST_PATH(path);
2✔
787
    {
2✔
788
        // Create a new shared db
789
        DBRef sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
2✔
790

791
        {
2✔
792
            // Open the same db again (in empty state)
793
            DBRef sg2 = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
2✔
794

795
            // Verify that new group is empty
796
            {
2✔
797
                ReadTransaction rt(sg2);
2✔
798
                CHECK(rt.get_group().is_empty());
2✔
799
            }
2✔
800

801
            // Add a new table
802
            {
2✔
803
                WriteTransaction wt(sg2);
2✔
804
                wt.get_group().verify();
2✔
805
                auto t1 = wt.add_table("test");
2✔
806
                test_table_add_columns(t1);
2✔
807
                t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
808
                wt.commit();
2✔
809
            }
2✔
810
        }
2✔
811

812
        // Verify that the new table has been added
813
        {
2✔
814
            ReadTransaction rt(sg);
2✔
815
            rt.get_group().verify();
2✔
816
            auto t1 = rt.get_table("test");
2✔
817
            auto cols = t1->get_column_keys();
2✔
818
            CHECK_EQUAL(1, t1->size());
2✔
819
            const Obj obj = t1->get_object(ObjKey(7));
2✔
820
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
821
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
822
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
823
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
824
        }
2✔
825
    }
2✔
826
}
2✔
827

828
TEST(Shared_1)
829
{
2✔
830
    SHARED_GROUP_TEST_PATH(path);
2✔
831
    {
2✔
832
        // Create a new shared db
833
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
834
        Timestamp first_timestamp_value{1, 1};
2✔
835
        std::vector<ColKey> cols;
2✔
836

837
        // Create first table in group
838
        {
2✔
839
            WriteTransaction wt(sg);
2✔
840
            wt.get_group().verify();
2✔
841
            auto t1 = wt.add_table("test");
2✔
842
            cols = test_table_add_columns(t1);
2✔
843
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test", Timestamp{1, 1});
2✔
844
            wt.commit();
2✔
845
        }
2✔
846
        {
2✔
847
            ReadTransaction rt(sg);
2✔
848
            rt.get_group().verify();
2✔
849

850
            // Verify that last set of changes are commited
851
            auto t2 = rt.get_table("test");
2✔
852
            CHECK(t2->size() == 1);
2✔
853
            const Obj obj = t2->get_object(ObjKey(7));
2✔
854
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
855
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
856
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
857
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
858
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
859

860
            // Do a new change while still having current read transaction open
861
            {
2✔
862
                WriteTransaction wt(sg);
2✔
863
                wt.get_group().verify();
2✔
864
                auto t1 = wt.get_table("test");
2✔
865
                t1->create_object(ObjKey(8)).set_all(2, 3, true, "more test", Timestamp{2, 2});
2✔
866
                wt.commit();
2✔
867
            }
2✔
868

869
            // Verify that that the read transaction does not see
870
            // the change yet (is isolated)
871
            CHECK(t2->size() == 1);
2✔
872
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
873
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
874
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
875
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
876
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
877
            // Do one more new change while still having current read transaction open
878
            // so we know that it does not overwrite data held by
879
            {
2✔
880
                WriteTransaction wt(sg);
2✔
881
                wt.get_group().verify();
2✔
882
                auto t1 = wt.get_table("test");
2✔
883
                t1->create_object(ObjKey(9)).set_all(0, 1, false, "even more test", Timestamp{3, 3});
2✔
884
                wt.commit();
2✔
885
            }
2✔
886

887
            // Verify that that the read transaction does still not see
888
            // the change yet (is isolated)
889
            CHECK(t2->size() == 1);
2✔
890
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
891
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
892
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
893
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
894
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
895
        }
2✔
896

897
        // Start a new read transaction and verify that it can now see the changes
898
        {
2✔
899
            ReadTransaction rt(sg);
2✔
900
            rt.get_group().verify();
2✔
901
            auto t3 = rt.get_table("test");
2✔
902

903
            CHECK(t3->size() == 3);
2✔
904
            const Obj obj7 = t3->get_object(ObjKey(7));
2✔
905
            CHECK_EQUAL(1, obj7.get<Int>(cols[0]));
2✔
906
            CHECK_EQUAL(2, obj7.get<Int>(cols[1]));
2✔
907
            CHECK_EQUAL(false, obj7.get<Bool>(cols[2]));
2✔
908
            CHECK_EQUAL("test", obj7.get<String>(cols[3]));
2✔
909
            CHECK_EQUAL(first_timestamp_value, obj7.get<Timestamp>(cols[4]));
2✔
910

911
            const Obj obj8 = t3->get_object(ObjKey(8));
2✔
912
            CHECK_EQUAL(2, obj8.get<Int>(cols[0]));
2✔
913
            CHECK_EQUAL(3, obj8.get<Int>(cols[1]));
2✔
914
            CHECK_EQUAL(true, obj8.get<Bool>(cols[2]));
2✔
915
            CHECK_EQUAL("more test", obj8.get<String>(cols[3]));
2✔
916
            Timestamp second_timestamp_value{2, 2};
2✔
917
            CHECK_EQUAL(second_timestamp_value, obj8.get<Timestamp>(cols[4]));
2✔
918

919
            const Obj obj9 = t3->get_object(ObjKey(9));
2✔
920
            CHECK_EQUAL(0, obj9.get<Int>(cols[0]));
2✔
921
            CHECK_EQUAL(1, obj9.get<Int>(cols[1]));
2✔
922
            CHECK_EQUAL(false, obj9.get<Bool>(cols[2]));
2✔
923
            CHECK_EQUAL("even more test", obj9.get<String>(cols[3]));
2✔
924
            Timestamp third_timestamp_value{3, 3};
2✔
925
            CHECK_EQUAL(third_timestamp_value, obj9.get<Timestamp>(cols[4]));
2✔
926
        }
2✔
927
    }
2✔
928
}
2✔
929

930
TEST(Shared_try_begin_write)
931
{
2✔
932
    SHARED_GROUP_TEST_PATH(path);
2✔
933
    // Create a new shared db
934
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
935
    std::mutex thread_obtains_write_lock;
2✔
936
    std::condition_variable cv;
2✔
937
    std::mutex cv_lock;
2✔
938
    bool init_complete = false;
2✔
939

940
    auto do_async = [&]() {
2✔
941
        auto tr = sg->start_write(true);
2✔
942
        bool success = bool(tr);
2✔
943
        CHECK(success);
2✔
944
        {
2✔
945
            std::lock_guard<std::mutex> lock(cv_lock);
2✔
946
            init_complete = true;
2✔
947
        }
2✔
948
        cv.notify_one();
2✔
949
        TableRef t = tr->add_table(StringData("table"));
2✔
950
        t->add_column(type_String, StringData("string_col"));
2✔
951
        std::vector<ObjKey> keys;
2✔
952
        t->create_objects(1000, keys);
2✔
953
        thread_obtains_write_lock.lock();
2✔
954
        tr->commit();
2✔
955
        thread_obtains_write_lock.unlock();
2✔
956
    };
2✔
957

958
    thread_obtains_write_lock.lock();
2✔
959
    std::thread async_writer(do_async);
2✔
960

961
    // wait for the thread to start a write transaction
962
    std::unique_lock<std::mutex> lock(cv_lock);
2✔
963
    cv.wait(lock, [&] {
4✔
964
        return init_complete;
4✔
965
    });
4✔
966

967
    // Try to also obtain a write lock. This should fail but not block.
968
    auto tr = sg->start_write(true);
2✔
969
    bool success = bool(tr);
2✔
970
    CHECK(!success);
2✔
971

972
    // Let the async thread finish its write transaction.
973
    thread_obtains_write_lock.unlock();
2✔
974
    async_writer.join();
2✔
975

976
    {
2✔
977
        // Verify that the thread transaction commit succeeded.
978
        auto rt = sg->start_read();
2✔
979
        ConstTableRef t = rt->get_table(rt->get_table_keys()[0]);
2✔
980
        CHECK(t->get_name() == StringData("table"));
2✔
981
        CHECK(t->get_column_name(t->get_column_keys()[0]) == StringData("string_col"));
2✔
982
        CHECK(t->size() == 1000);
2✔
983
        CHECK(rt->size() == 1);
2✔
984
    }
2✔
985

986
    // Now try to start a transaction without any contenders.
987
    tr = sg->start_write(true);
2✔
988
    success = bool(tr);
2✔
989
    CHECK(success);
2✔
990
    CHECK(tr->size() == 1);
2✔
991
    tr->verify();
2✔
992

993
    // Add some data and finish the transaction.
994
    auto t2k = tr->add_table(StringData("table 2"))->get_key();
2✔
995
    CHECK(tr->size() == 2);
2✔
996
    tr->commit();
2✔
997

998
    {
2✔
999
        // Verify that the main thread transaction now succeeded.
1000
        ReadTransaction rt(sg);
2✔
1001
        const Group& gr = rt.get_group();
2✔
1002
        CHECK(gr.size() == 2);
2✔
1003
        CHECK(gr.get_table(t2k)->get_name() == StringData("table 2"));
2✔
1004
    }
2✔
1005
}
2✔
1006

1007
TEST(Shared_Rollback)
1008
{
2✔
1009
    SHARED_GROUP_TEST_PATH(path);
2✔
1010
    {
2✔
1011
        // Create a new shared db
1012
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1013
        std::vector<ColKey> cols;
2✔
1014

1015
        // Create first table in group (but rollback)
1016
        {
2✔
1017
            WriteTransaction wt(sg);
2✔
1018
            wt.get_group().verify();
2✔
1019
            auto t1 = wt.add_table("test");
2✔
1020
            cols = test_table_add_columns(t1);
2✔
1021
            t1->create_object().set_all(1, 2, false, "test");
2✔
1022
            // Note: Implicit rollback
1023
        }
2✔
1024

1025
        // Verify that no changes were made
1026
        {
2✔
1027
            ReadTransaction rt(sg);
2✔
1028
            rt.get_group().verify();
2✔
1029
            CHECK(!rt.get_group().has_table("test"));
2✔
1030
        }
2✔
1031

1032
        // Really create first table in group
1033
        {
2✔
1034
            WriteTransaction wt(sg);
2✔
1035
            wt.get_group().verify();
2✔
1036
            auto t1 = wt.add_table("test");
2✔
1037
            cols = test_table_add_columns(t1);
2✔
1038
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1039
            wt.commit();
2✔
1040
        }
2✔
1041

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

1055
        // Greate more changes (but rollback)
1056
        {
2✔
1057
            WriteTransaction wt(sg);
2✔
1058
            wt.get_group().verify();
2✔
1059
            auto t1 = wt.get_table("test");
2✔
1060
            t1->create_object(ObjKey(8)).set_all(0, 0, true, "more test");
2✔
1061
            // Note: Implicit rollback
1062
        }
2✔
1063

1064
        // Verify that no changes were made
1065
        {
2✔
1066
            ReadTransaction rt(sg);
2✔
1067
            rt.get_group().verify();
2✔
1068
            auto t = rt.get_table("test");
2✔
1069
            CHECK(t->size() == 1);
2✔
1070
            const Obj obj = t->get_object(ObjKey(7));
2✔
1071
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1072
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1073
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1074
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1075
        }
2✔
1076
    }
2✔
1077
}
2✔
1078

1079
TEST(Shared_Writes)
1080
{
2✔
1081
    SHARED_GROUP_TEST_PATH(path);
2✔
1082
    {
2✔
1083
        // Create a new shared db
1084
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1085
        std::vector<ColKey> cols;
2✔
1086

1087
        // Create first table in group
1088
        {
2✔
1089
            WriteTransaction wt(sg);
2✔
1090
            wt.get_group().verify();
2✔
1091
            auto t1 = wt.add_table("test");
2✔
1092
            cols = test_table_add_columns(t1);
2✔
1093
            t1->create_object(ObjKey(7)).set_all(0, 2, false, "test");
2✔
1094
            wt.commit();
2✔
1095
        }
2✔
1096

1097
        // Do a lot of repeated write transactions
1098
        for (size_t i = 0; i < 100; ++i) {
202✔
1099
            WriteTransaction wt(sg);
200✔
1100
            wt.get_group().verify();
200✔
1101
            auto t1 = wt.get_table("test");
200✔
1102
            t1->get_object(ObjKey(7)).add_int(cols[0], 1);
200✔
1103
            wt.commit();
200✔
1104
        }
200✔
1105

1106
        // Verify that the changes were made
1107
        {
2✔
1108
            ReadTransaction rt(sg);
2✔
1109
            rt.get_group().verify();
2✔
1110
            auto t = rt.get_table("test");
2✔
1111
            const int64_t v = t->get_object(ObjKey(7)).get<Int>(cols[0]);
2✔
1112
            CHECK_EQUAL(100, v);
2✔
1113
        }
2✔
1114
    }
2✔
1115
}
2✔
1116

1117
#if !REALM_ANDROID // FIXME
1118
TEST(Shared_ManyReaders)
1119
{
2✔
1120
    // This test was written primarily to expose a former bug in
1121
    // SharedGroup::end_read(), where the lock-file was not remapped
1122
    // after ring-buffer expansion.
1123

1124
    const int chunk_1_size = 251;
2✔
1125
    char chunk_1[chunk_1_size];
2✔
1126
    for (int i = 0; i < chunk_1_size; ++i)
504✔
1127
        chunk_1[i] = (i + 3) % 251;
502✔
1128
    const int chunk_2_size = 123;
2✔
1129
    char chunk_2[chunk_2_size];
2✔
1130
    for (int i = 0; i < chunk_2_size; ++i)
248✔
1131
        chunk_2[i] = (i + 11) % 241;
246✔
1132

1133
#if TEST_DURATION < 1
2✔
1134
    // Mac OS X 10.8 cannot handle more than 15 due to its default ulimit settings.
1135
    int rounds[] = {3, 5, 7, 9, 11, 13};
2✔
1136
#elif REALM_DEBUG // this test is disproportionately slower in debug
1137
    int rounds[] = {3, 5, 7, 9, 11, 13, 15, 17, 23};
1138
#else
1139
    int rounds[] = {3, 5, 11, 15, 17, 23, 27, 31, 47, 59};
1140
#endif
1141
    const int num_rounds = sizeof rounds / sizeof *rounds;
2✔
1142

1143
    const int max_N = 64;
2✔
1144
    CHECK(max_N >= rounds[num_rounds - 1]);
2✔
1145
    DBRef shared_groups[8 * max_N];
2✔
1146
    TransactionRef read_transactions[8 * max_N];
2✔
1147
    ColKey col_int;
2✔
1148
    ColKey col_bin;
2✔
1149

1150
    auto add_int = [](Table& table, ColKey col, int64_t diff) {
1,536✔
1151
        for (auto& o : table) {
1,536✔
1152
            o.add_int(col, diff);
1,536✔
1153
        }
1,536✔
1154
    };
1,536✔
1155

1156
    for (int round = 0; round < num_rounds; ++round) {
14✔
1157
        int N = rounds[round];
12✔
1158

1159
        SHARED_GROUP_TEST_PATH(path);
12✔
1160

1161
        auto root_sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
12✔
1162

1163
        // Add two tables
1164
        {
12✔
1165
            WriteTransaction wt(root_sg);
12✔
1166
            wt.get_group().verify();
12✔
1167
            bool was_added = false;
12✔
1168
            TableRef test_1 = wt.get_or_add_table("test_1", Table::Type::TopLevel, &was_added);
12✔
1169
            if (was_added) {
12✔
1170
                col_int = test_1->add_column(type_Int, "i");
12✔
1171
            }
12✔
1172
            test_1->create_object().set(col_int, 0);
12✔
1173
            TableRef test_2 = wt.get_or_add_table("test_2", Table::Type::TopLevel, &was_added);
12✔
1174
            if (was_added) {
12✔
1175
                col_bin = test_2->add_column(type_Binary, "b");
12✔
1176
            }
12✔
1177
            wt.commit();
12✔
1178
        }
12✔
1179

1180

1181
        // Create 8*N shared group accessors
1182
        for (int i = 0; i < 8 * N; ++i)
780✔
1183
            shared_groups[i] = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
768✔
1184

1185
        // Initiate 2*N read transactions with progressive changes
1186
        for (int i = 0; i < 2 * N; ++i) {
204✔
1187
            read_transactions[i] = shared_groups[i]->start_read();
192✔
1188
            read_transactions[i]->verify();
192✔
1189
            {
192✔
1190
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
192✔
1191
                CHECK_EQUAL(1u, test_1->size());
192✔
1192
                CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
192✔
1193
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
192✔
1194
                int n_1 = i * 1;
192✔
1195
                int n_2 = i * 18;
192✔
1196
                CHECK_EQUAL(n_1 + n_2, test_2->size());
192✔
1197
                for (int j = 0; j < n_1 + n_2; ++j) {
32,872✔
1198
                    if (j % 19 == 0) {
32,680✔
1199
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
1,720✔
1200
                    }
1,720✔
1201
                    else {
30,960✔
1202
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
30,960✔
1203
                    }
30,960✔
1204
                }
32,680✔
1205
            }
192✔
1206
            {
192✔
1207
                WriteTransaction wt(root_sg);
192✔
1208
                wt.get_group().verify();
192✔
1209
                TableRef test_1 = wt.get_table("test_1");
192✔
1210
                add_int(*test_1, col_int, 1);
192✔
1211
                TableRef test_2 = wt.get_table("test_2");
192✔
1212
                test_2->create_object().set(col_bin, BinaryData(chunk_1));
192✔
1213
                wt.commit();
192✔
1214
            }
192✔
1215
            {
192✔
1216
                WriteTransaction wt(root_sg);
192✔
1217
                wt.get_group().verify();
192✔
1218
                TableRef test_2 = wt.get_table("test_2");
192✔
1219
                for (int j = 0; j < 18; ++j) {
3,648✔
1220
                    test_2->create_object().set(col_bin, BinaryData(chunk_2));
3,456✔
1221
                }
3,456✔
1222
                wt.commit();
192✔
1223
            }
192✔
1224
        }
192✔
1225

1226
        // Check isolation between read transactions
1227
        for (int i = 0; i < 2 * N; ++i) {
204✔
1228
            ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
192✔
1229
            CHECK_EQUAL(1, test_1->size());
192✔
1230
            CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
192✔
1231
            ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
192✔
1232
            int n_1 = i * 1;
192✔
1233
            int n_2 = i * 18;
192✔
1234
            CHECK_EQUAL(n_1 + n_2, test_2->size());
192✔
1235
            for (int j = 0; j < n_1 + n_2; ++j) {
32,872✔
1236
                if (j % 19 == 0) {
32,680✔
1237
                    CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
1,720✔
1238
                }
1,720✔
1239
                else {
30,960✔
1240
                    CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
30,960✔
1241
                }
30,960✔
1242
            }
32,680✔
1243
        }
192✔
1244

1245
        // End the first half of the read transactions during further
1246
        // changes
1247
        for (int i = N - 1; i >= 0; --i) {
108✔
1248
            {
96✔
1249
                WriteTransaction wt(root_sg);
96✔
1250
#if !defined(_WIN32) || TEST_DURATION > 0 // These .verify() calls are horribly slow on Windows
96✔
1251
                wt.get_group().verify();
96✔
1252
#endif
96✔
1253
                TableRef test_1 = wt.get_table("test_1");
96✔
1254
                add_int(*test_1, col_int, 2);
96✔
1255
                wt.commit();
96✔
1256
            }
96✔
1257
            {
96✔
1258
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
96✔
1259
                CHECK_EQUAL(1, test_1->size());
96✔
1260
                CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
96✔
1261
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
96✔
1262
                int n_1 = i * 1;
96✔
1263
                int n_2 = i * 18;
96✔
1264
                CHECK_EQUAL(n_1 + n_2, test_2->size());
96✔
1265
                for (int j = 0; j < n_1 + n_2; ++j) {
7,810✔
1266
                    if (j % 19 == 0) {
7,714✔
1267
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
406✔
1268
                    }
406✔
1269
                    else {
7,308✔
1270
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
7,308✔
1271
                    }
7,308✔
1272
                }
7,714✔
1273
            }
96✔
1274
            read_transactions[i] = nullptr;
96✔
1275
        }
96✔
1276

1277
        // Initiate 6*N extra read transactionss with further progressive changes
1278
        for (int i = 2 * N; i < 8 * N; ++i) {
588✔
1279
            read_transactions[i] = shared_groups[i]->start_read();
576✔
1280
#if !defined(_WIN32) || TEST_DURATION > 0
576✔
1281
            read_transactions[i]->verify();
576✔
1282
#endif
576✔
1283
            {
576✔
1284
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
576✔
1285
                CHECK_EQUAL(1u, test_1->size());
576✔
1286
                int i_2 = 2 * N + i;
576✔
1287
                CHECK_EQUAL(i_2, test_1->begin()->get<Int>(col_int));
576✔
1288
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
576✔
1289
                int n_1 = i * 1;
576✔
1290
                int n_2 = i * 18;
576✔
1291
                CHECK_EQUAL(n_1 + n_2, test_2->size());
576✔
1292
                for (int j = 0; j < n_1 + n_2; ++j) {
512,664✔
1293
                    if (j % 19 == 0) {
512,088✔
1294
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
26,952✔
1295
                    }
26,952✔
1296
                    else {
485,136✔
1297
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
485,136✔
1298
                    }
485,136✔
1299
                }
512,088✔
1300
            }
576✔
1301
            {
576✔
1302
                WriteTransaction wt(root_sg);
576✔
1303
#if !defined(_WIN32) || TEST_DURATION > 0
576✔
1304
                wt.get_group().verify();
576✔
1305
#endif
576✔
1306
                TableRef test_1 = wt.get_table("test_1");
576✔
1307
                add_int(*test_1, col_int, 1);
576✔
1308
                TableRef test_2 = wt.get_table("test_2");
576✔
1309
                test_2->create_object().set(col_bin, BinaryData(chunk_1));
576✔
1310
                wt.commit();
576✔
1311
            }
576✔
1312
            {
576✔
1313
                WriteTransaction wt(root_sg);
576✔
1314
#if !defined(_WIN32) || TEST_DURATION > 0
576✔
1315
                wt.get_group().verify();
576✔
1316
#endif
576✔
1317
                TableRef test_2 = wt.get_table("test_2");
576✔
1318
                for (int j = 0; j < 18; ++j) {
10,944✔
1319
                    test_2->create_object().set(col_bin, BinaryData(chunk_2));
10,368✔
1320
                }
10,368✔
1321
                wt.commit();
576✔
1322
            }
576✔
1323
        }
576✔
1324

1325
        // End all remaining read transactions during further changes
1326
        for (int i = 1 * N; i < 8 * N; ++i) {
684✔
1327
            {
672✔
1328
                WriteTransaction wt(root_sg);
672✔
1329
#if !defined(_WIN32) || TEST_DURATION > 0
672✔
1330
                wt.get_group().verify();
672✔
1331
#endif
672✔
1332
                TableRef test_1 = wt.get_table("test_1");
672✔
1333
                add_int(*test_1, col_int, 2);
672✔
1334
                wt.commit();
672✔
1335
            }
672✔
1336
            {
672✔
1337
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
672✔
1338
                CHECK_EQUAL(1, test_1->size());
672✔
1339
                int i_2 = i < 2 * N ? i : 2 * N + i;
672✔
1340
                CHECK_EQUAL(i_2, test_1->begin()->get<Int>(col_int));
672✔
1341
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
672✔
1342
                int n_1 = i * 1;
672✔
1343
                int n_2 = i * 18;
672✔
1344
                CHECK_EQUAL(n_1 + n_2, test_2->size());
672✔
1345
                for (int j = 0; j < n_1 + n_2; ++j) {
537,726✔
1346
                    if (j % 19 == 0) {
537,054✔
1347
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
28,266✔
1348
                    }
28,266✔
1349
                    else {
508,788✔
1350
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
508,788✔
1351
                    }
508,788✔
1352
                }
537,054✔
1353
            }
672✔
1354
            read_transactions[i] = nullptr;
672✔
1355
        }
672✔
1356

1357
        // Check final state via each shared group, then destroy it
1358
        for (int i = 0; i < 8 * N; ++i) {
780✔
1359
            {
768✔
1360
                ReadTransaction rt(shared_groups[i]);
768✔
1361
#if !defined(_WIN32) || TEST_DURATION > 0
768✔
1362
                rt.get_group().verify();
768✔
1363
#endif
768✔
1364
                ConstTableRef test_1 = rt.get_table("test_1");
768✔
1365
                CHECK_EQUAL(1, test_1->size());
768✔
1366
                CHECK_EQUAL(3 * 8 * N, test_1->begin()->get<Int>(col_int));
768✔
1367
                ConstTableRef test_2 = rt.get_table("test_2");
768✔
1368
                int n_1 = 8 * N * 1;
768✔
1369
                int n_2 = 8 * N * 18;
768✔
1370
                CHECK_EQUAL(n_1 + n_2, test_2->size());
768✔
1371
                for (int j = 0; j < n_1 + n_2; ++j) {
1,104,896✔
1372
                    if (j % 19 == 0) {
1,104,128✔
1373
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
58,112✔
1374
                    }
58,112✔
1375
                    else {
1,046,016✔
1376
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
1,046,016✔
1377
                    }
1,046,016✔
1378
                }
1,104,128✔
1379
            }
768✔
1380
            shared_groups[i] = nullptr;
768✔
1381
        }
768✔
1382

1383
        // Check final state via new shared group
1384
        {
12✔
1385
            DBRef sg = DB::create(path, DBOptions(DBOptions::Durability::MemOnly));
12✔
1386
            ReadTransaction rt(sg);
12✔
1387
#if !defined(_WIN32) || TEST_DURATION > 0
12✔
1388
            rt.get_group().verify();
12✔
1389
#endif
12✔
1390
            ConstTableRef test_1 = rt.get_table("test_1");
12✔
1391
            CHECK_EQUAL(1, test_1->size());
12✔
1392
            CHECK_EQUAL(3 * 8 * N, test_1->begin()->get<Int>(col_int));
12✔
1393
            ConstTableRef test_2 = rt.get_table("test_2");
12✔
1394
            int n_1 = 8 * N * 1;
12✔
1395
            int n_2 = 8 * N * 18;
12✔
1396
            CHECK_EQUAL(n_1 + n_2, test_2->size());
12✔
1397
            for (int j = 0; j < n_1 + n_2; ++j) {
14,604✔
1398
                if (j % 19 == 0) {
14,592✔
1399
                    CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
768✔
1400
                }
768✔
1401
                else {
13,824✔
1402
                    CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
13,824✔
1403
                }
13,824✔
1404
            }
14,592✔
1405
        }
12✔
1406
    }
12✔
1407
}
2✔
1408
#endif
1409

1410
// This test is a minimal repro. of core issue #842.
1411
TEST(Many_ConcurrentReaders)
1412
{
2✔
1413
    SHARED_GROUP_TEST_PATH(path);
2✔
1414
    const std::string path_str = path;
2✔
1415

1416
    // setup
1417
    DBRef sg_w = DB::create(path_str);
2✔
1418
    WriteTransaction wt(sg_w);
2✔
1419
    TableRef t = wt.add_table("table");
2✔
1420
    auto col_ndx = t->add_column(type_String, "column");
2✔
1421
    t->create_object().set(col_ndx, StringData("string"));
2✔
1422
    wt.commit();
2✔
1423
    sg_w->close();
2✔
1424

1425
    auto reader = [path_str]() {
8✔
1426
        std::stringstream logs;
8✔
1427
        try {
8✔
1428
            auto logger = util::StreamLogger(logs);
8✔
1429
            DBOptions options;
8✔
1430
            options.logger = std::make_shared<util::StreamLogger>(logs);
8✔
1431
            options.logger->set_level_threshold(Logger::Level::all);
8✔
1432
            for (int i = 0; i < 1000; ++i) {
8,008✔
1433
                DBRef sg_r = DB::create(path_str, options);
8,000✔
1434
                ReadTransaction rt(sg_r);
8,000✔
1435
                ConstTableRef t = rt.get_table("table");
8,000✔
1436
                auto col_key = t->get_column_key("column");
8,000✔
1437
                REALM_ASSERT(t->get_object(0).get<StringData>(col_key) == "string");
8,000✔
1438
                rt.get_group().verify();
8,000✔
1439
            }
8,000✔
1440
        }
8✔
1441
        catch (const std::exception& e) {
8✔
1442
            std::cerr << "Exception during Many_ConcurrentReaders:" << std::endl;
×
1443
            std::cerr << "Reason: '" << e.what() << "'" << std::endl;
×
1444
            std::cerr << logs.str();
×
1445
            constexpr bool unexpected_exception = false;
×
1446
            REALM_ASSERT_EX(unexpected_exception, e.what());
×
1447
        }
×
1448
    };
8✔
1449

1450
    constexpr int num_threads = 4;
2✔
1451
    std::thread threads[num_threads];
2✔
1452
    for (int i = 0; i < num_threads; ++i) {
10✔
1453
        threads[i] = std::thread(reader);
8✔
1454
    }
8✔
1455
    for (int i = 0; i < num_threads; ++i) {
10✔
1456
        threads[i].join();
8✔
1457
    }
8✔
1458
}
2✔
1459

1460

1461
TEST(Shared_WritesSpecialOrder)
1462
{
2✔
1463
    SHARED_GROUP_TEST_PATH(path);
2✔
1464
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1465

1466
    const int num_rows =
2✔
1467
        5; // FIXME: Should be strictly greater than REALM_MAX_BPNODE_SIZE, but that takes too long time.
2✔
1468
    const int num_reps = 25;
2✔
1469

1470
    {
2✔
1471
        WriteTransaction wt(sg);
2✔
1472
        wt.get_group().verify();
2✔
1473
        auto table = wt.add_table("test");
2✔
1474
        auto col = table->add_column(type_Int, "first");
2✔
1475
        for (int i = 0; i < num_rows; ++i) {
12✔
1476
            table->create_object(ObjKey(i)).set(col, 0);
10✔
1477
        }
10✔
1478
        wt.commit();
2✔
1479
    }
2✔
1480

1481
    for (int i = 0; i < num_rows; ++i) {
12✔
1482
        for (int j = 0; j < num_reps; ++j) {
260✔
1483
            {
250✔
1484
                WriteTransaction wt(sg);
250✔
1485
                wt.get_group().verify();
250✔
1486
                auto table = wt.get_table("test");
250✔
1487
                auto col = table->get_column_key("first");
250✔
1488
                Obj obj = table->get_object(ObjKey(i));
250✔
1489
                CHECK_EQUAL(j, obj.get<Int>(col));
250✔
1490
                obj.add_int(col, 1);
250✔
1491
                wt.commit();
250✔
1492
            }
250✔
1493
        }
250✔
1494
    }
10✔
1495

1496
    {
2✔
1497
        ReadTransaction rt(sg);
2✔
1498
        rt.get_group().verify();
2✔
1499
        auto table = rt.get_table("test");
2✔
1500
        auto col = table->get_column_key("first");
2✔
1501
        for (int i = 0; i < num_rows; ++i) {
12✔
1502
            CHECK_EQUAL(num_reps, table->get_object(ObjKey(i)).get<Int>(col));
10✔
1503
        }
10✔
1504
    }
2✔
1505
}
2✔
1506

1507
namespace {
1508

1509
void writer_threads_thread(TestContext& test_context, const DBRef& sg, ObjKey key)
1510
{
20✔
1511

1512
    for (size_t i = 0; i < 100; ++i) {
2,020✔
1513
        // Increment cell
1514
        {
2,000✔
1515
            WriteTransaction wt(sg);
2,000✔
1516
            wt.get_group().verify();
2,000✔
1517
            auto t1 = wt.get_table("test");
2,000✔
1518
            auto cols = t1->get_column_keys();
2,000✔
1519
            t1->get_object(key).add_int(cols[0], 1);
2,000✔
1520
            // FIXME: For some reason this takes ages when running
1521
            // inside valgrind, it is probably due to the "extreme
1522
            // overallocation" bug. The 1000 transactions performed
1523
            // here can produce a final database file size of more
1524
            // than 1 GiB. Really! And that is a table with only 10
1525
            // rows. It is about 1 MiB per transaction.
1526
            wt.commit();
2,000✔
1527
        }
2,000✔
1528

1529
        // Verify in new transaction so that we interleave
1530
        // read and write transactions
1531
        {
2,000✔
1532
            ReadTransaction rt(sg);
2,000✔
1533
            // rt.get_group().verify(); // verify() is not supported on a read transaction
1534
            // concurrently with writes.
1535
            auto t = rt.get_table("test");
2,000✔
1536
            auto cols = t->get_column_keys();
2,000✔
1537
            int64_t v = t->get_object(key).get<Int>(cols[0]);
2,000✔
1538
            int64_t expected = i + 1;
2,000✔
1539
            CHECK_EQUAL(expected, v);
2,000✔
1540
        }
2,000✔
1541
    }
2,000✔
1542
}
20✔
1543

1544
} // anonymous namespace
1545

1546
TEST(Shared_WriterThreads)
1547
{
2✔
1548
    SHARED_GROUP_TEST_PATH(path);
2✔
1549
    {
2✔
1550
        // Create a new shared db
1551
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1552

1553
        const int thread_count = 10;
2✔
1554
        // Create first table in group
1555
        {
2✔
1556
            WriteTransaction wt(sg);
2✔
1557
            wt.get_group().verify();
2✔
1558
            auto t1 = wt.add_table("test");
2✔
1559
            test_table_add_columns(t1);
2✔
1560
            for (int i = 0; i < thread_count; ++i)
22✔
1561
                t1->create_object(ObjKey(i)).set_all(0, 2, false, "test");
20✔
1562
            wt.commit();
2✔
1563
        }
2✔
1564

1565
        std::thread threads[thread_count];
2✔
1566

1567
        // Create all threads
1568
        for (int i = 0; i < thread_count; ++i)
22✔
1569
            threads[i] = std::thread(writer_threads_thread, std::ref(test_context), sg, ObjKey(i));
20✔
1570

1571
        // Wait for all threads to complete
1572
        for (int i = 0; i < thread_count; ++i)
22✔
1573
            threads[i].join();
20✔
1574

1575
        // Verify that the changes were made
1576
        {
2✔
1577
            ReadTransaction rt(sg);
2✔
1578
            rt.get_group().verify();
2✔
1579
            auto t = rt.get_table("test");
2✔
1580
            auto col = t->get_column_keys()[0];
2✔
1581

1582
            for (int i = 0; i < thread_count; ++i) {
22✔
1583
                int64_t v = t->get_object(ObjKey(i)).get<Int>(col);
20✔
1584
                CHECK_EQUAL(100, v);
20✔
1585
            }
20✔
1586
        }
2✔
1587
    }
2✔
1588
}
2✔
1589

1590

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

1597
// Not supported on Windows in particular? Keywords: winbug
1598
TEST(Shared_RobustAgainstDeathDuringWrite)
1599
{
1600
    // Abort if robust mutexes are not supported on the current
1601
    // platform. Otherwise we would probably get into a dead-lock.
1602
    if (!RobustMutex::is_robust_on_this_platform)
1603
        return;
1604

1605
    // This test can only be conducted by spawning independent
1606
    // processes which can then be terminated individually.
1607
    const int process_count = 100;
1608
    SHARED_GROUP_TEST_PATH(path);
1609
    ColKey col_int;
1610

1611
    auto add_int = [](Table& table, ColKey col, int64_t diff) {
1612
        for (auto& o : table) {
1613
            o.add_int(col, diff);
1614
        }
1615
    };
1616

1617
    for (int i = 0; i < process_count; ++i) {
1618
        pid_t pid = fork();
1619
        if (pid == pid_t(-1))
1620
            REALM_TERMINATE("fork() failed");
1621
        if (pid == 0) {
1622
            // Child
1623
            DBRef sg = DB::create(path, DBOptions(crypt_key()));
1624
            WriteTransaction wt(sg);
1625
            wt.get_group().verify();
1626
            wt.get_or_add_table("alpha");
1627
            _Exit(42); // Die hard with an active write transaction
1628
        }
1629
        else {
1630
            // Parent
1631
            int stat_loc = 0;
1632
            int options = 0;
1633
            pid = waitpid(pid, &stat_loc, options);
1634
            if (pid == pid_t(-1))
1635
                REALM_TERMINATE("waitpid() failed");
1636
            bool child_exited_normaly = WIFEXITED(stat_loc);
1637
            CHECK(child_exited_normaly);
1638
            int child_exit_status = WEXITSTATUS(stat_loc);
1639
            CHECK_EQUAL(42, child_exit_status);
1640
        }
1641

1642
        // Check that we can continue without dead-locking
1643
        {
1644
            DBRef sg = DB::create(path, DBOptions(crypt_key()));
1645
            WriteTransaction wt(sg);
1646
            wt.get_group().verify();
1647
            TableRef table = wt.get_or_add_table("beta");
1648
            if (table->is_empty()) {
1649
                col_int = table->add_column(type_Int, "i");
1650
                table->create_object().set(col_int, 0);
1651
            }
1652
            add_int(*table, col_int, 1);
1653
            wt.commit();
1654
        }
1655
    }
1656

1657
    {
1658
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
1659
        ReadTransaction rt(sg);
1660
        rt.get_group().verify();
1661
        CHECK(!rt.has_table("alpha"));
1662
        CHECK(rt.has_table("beta"));
1663
        ConstTableRef table = rt.get_table("beta");
1664
        CHECK_EQUAL(process_count, table->begin()->get<Int>(col_int));
1665
    }
1666
}
1667

1668
#endif // on apple
1669
#endif // encryption enabled
1670

1671
// not ios or android
1672
// #endif // defined TEST_ROBUSTNESS && defined ENABLE_ROBUST_AGAINST_DEATH_DURING_WRITE && !REALM_ENABLE_ENCRYPTION
1673

1674

1675
TEST(Shared_SpaceOveruse)
1676
{
2✔
1677
#if TEST_DURATION < 1
2✔
1678
    int n_outer = 300;
2✔
1679
    int n_inner = 21;
2✔
1680
#else
1681
    int n_outer = 3000;
1682
    int n_inner = 42;
1683
#endif
1684

1685
    // Many transactions
1686
    SHARED_GROUP_TEST_PATH(path);
2✔
1687
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1688

1689
    // Do a lot of sequential transactions
1690
    for (int i = 0; i != n_outer; ++i) {
602✔
1691
        WriteTransaction wt(sg);
600✔
1692
        wt.get_group().verify();
600✔
1693
        auto table = wt.get_or_add_table("my_table");
600✔
1694

1695
        if (table->is_empty()) {
600✔
1696
            REALM_ASSERT(table);
2✔
1697
            table->add_column(type_String, "text");
2✔
1698
        }
2✔
1699
        auto cols = table->get_column_keys();
600✔
1700

1701
        for (int j = 0; j != n_inner; ++j) {
13,200✔
1702
            REALM_ASSERT(table);
12,600✔
1703
            table->create_object().set(cols[0], "x");
12,600✔
1704
        }
12,600✔
1705
        wt.commit();
600✔
1706
    }
600✔
1707

1708
    // Verify that all was added correctly
1709
    {
2✔
1710
        ReadTransaction rt(sg);
2✔
1711
        rt.get_group().verify();
2✔
1712
        auto table = rt.get_table("my_table");
2✔
1713
        auto col = table->get_column_keys()[0];
2✔
1714
        size_t n = table->size();
2✔
1715
        CHECK_EQUAL(n_outer * n_inner, n);
2✔
1716

1717
        for (auto it : *table) {
12,600✔
1718
            CHECK_EQUAL("x", it.get<String>(col));
12,600✔
1719
        }
12,600✔
1720

1721
        table->verify();
2✔
1722
    }
2✔
1723
}
2✔
1724

1725

1726
TEST(Shared_Notifications)
1727
{
2✔
1728
    // Create a new shared db
1729
    SHARED_GROUP_TEST_PATH(path);
2✔
1730
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1731
    TransactionRef tr1 = sg->start_read();
2✔
1732

1733
    // No other instance have changed db since last transaction
1734
    CHECK(!sg->has_changed(tr1));
2✔
1735

1736
    {
2✔
1737
        // Open the same db again (in empty state)
1738
        DBRef sg2 = DB::create(path, DBOptions(crypt_key()));
2✔
1739

1740
        // Verify that new group is empty
1741
        {
2✔
1742
            TransactionRef reader = sg2->start_read();
2✔
1743
            CHECK(reader->is_empty());
2✔
1744
            CHECK(!sg2->has_changed(reader));
2✔
1745
        }
2✔
1746

1747
        // No other instance have changed db since last transaction
1748

1749
        // Add a new table
1750
        {
2✔
1751
            WriteTransaction wt(sg2);
2✔
1752
            wt.get_group().verify();
2✔
1753
            auto t1 = wt.add_table("test");
2✔
1754
            test_table_add_columns(t1);
2✔
1755
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1756
            wt.commit();
2✔
1757
        }
2✔
1758
    }
2✔
1759

1760
    // Db has been changed by other instance
1761
    CHECK(sg->has_changed(tr1));
2✔
1762
    tr1 = sg->start_read();
2✔
1763
    // Verify that the new table has been added
1764
    {
2✔
1765
        ReadTransaction rt(sg);
2✔
1766
        rt.get_group().verify();
2✔
1767
        auto t1 = rt.get_table("test");
2✔
1768
        CHECK_EQUAL(1, t1->size());
2✔
1769
        const Obj obj = t1->get_object(ObjKey(7));
2✔
1770
        auto cols = t1->get_column_keys();
2✔
1771
        CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1772
        CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1773
        CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1774
        CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1775
    }
2✔
1776

1777
    // No other instance have changed db since last transaction
1778
    CHECK(!sg->has_changed(tr1));
2✔
1779
}
2✔
1780

1781

1782
TEST(Shared_FromSerialized)
1783
{
2✔
1784
    SHARED_GROUP_TEST_PATH(path);
2✔
1785

1786
    // Create new group and serialize to disk
1787
    {
2✔
1788
        Group g1;
2✔
1789
        auto t1 = g1.add_table("test");
2✔
1790
        test_table_add_columns(t1);
2✔
1791
        t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1792
        g1.write(path, crypt_key());
2✔
1793
    }
2✔
1794

1795
    // Open same file as shared group
1796
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1797

1798
    // Verify that contents is there when shared
1799
    {
2✔
1800
        ReadTransaction rt(sg);
2✔
1801
        rt.get_group().verify();
2✔
1802
        auto t1 = rt.get_table("test");
2✔
1803
        CHECK_EQUAL(1, t1->size());
2✔
1804
        const Obj obj = t1->get_object(ObjKey(7));
2✔
1805
        auto cols = t1->get_column_keys();
2✔
1806
        CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1807
        CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1808
        CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1809
        CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1810
    }
2✔
1811
}
2✔
1812

1813
TEST_IF(Shared_StringIndexBug1, TEST_DURATION >= 1)
1814
{
×
1815
    SHARED_GROUP_TEST_PATH(path);
×
1816
    DBRef db = DB::create(path, DBOptions(crypt_key()));
×
1817

1818
    {
×
1819
        auto tr = db->start_write();
×
1820
        TableRef table = tr->add_table("users");
×
1821
        auto col = table->add_column(type_String, "username");
×
1822
        table->add_search_index(col);
×
1823
        for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i)
×
1824
            table->create_object();
×
1825
        for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i)
×
1826
            table->remove_object(table->begin());
×
1827
        tr->commit();
×
1828
    }
×
1829

1830
    {
×
1831
        auto tr = db->start_write();
×
1832
        TableRef table = tr->get_table("users");
×
1833
        table->create_object();
×
1834
        tr->commit();
×
1835
    }
×
1836
}
×
1837

1838

1839
TEST(Shared_StringIndexBug2)
1840
{
2✔
1841
    SHARED_GROUP_TEST_PATH(path);
2✔
1842
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1843

1844
    {
2✔
1845
        WriteTransaction wt(sg);
2✔
1846
        wt.get_group().verify();
2✔
1847
        TableRef table = wt.add_table("a");
2✔
1848
        auto col = table->add_column(type_String, "b");
2✔
1849
        table->add_search_index(col); // Not adding index makes it work
2✔
1850
        table->create_object();
2✔
1851
        wt.commit();
2✔
1852
    }
2✔
1853

1854
    {
2✔
1855
        ReadTransaction rt(sg);
2✔
1856
        rt.get_group().verify();
2✔
1857
    }
2✔
1858
}
2✔
1859

1860

1861
namespace {
1862

1863
void rand_str(Random& random, char* res, size_t len)
1864
{
102✔
1865
    for (size_t i = 0; i < len; ++i)
918✔
1866
        res[i] = char(int('a') + random.draw_int_mod(10));
816✔
1867
}
102✔
1868

1869
} // anonymous namespace
1870

1871
TEST(Shared_StringIndexBug3)
1872
{
2✔
1873
    SHARED_GROUP_TEST_PATH(path);
2✔
1874
    DBRef db = DB::create(path, DBOptions(crypt_key()));
2✔
1875
    ColKey col;
2✔
1876
    {
2✔
1877
        auto tr = db->start_write();
2✔
1878
        TableRef table = tr->add_table("users");
2✔
1879
        col = table->add_column(type_String, "username");
2✔
1880
        table->add_search_index(col); // Disabling index makes it work
2✔
1881
        tr->commit();
2✔
1882
    }
2✔
1883

1884
    Random random(random_int<unsigned long>()); // Seed from slow global generator
2✔
1885
    std::vector<ObjKey> keys;
2✔
1886
    for (size_t n = 0; n < 100; ++n) {
202✔
1887
        const uint64_t action = random.draw_int_mod(1000);
200✔
1888

1889
        if (action <= 500) {
200✔
1890
            // delete random user
1891
            auto tr = db->start_write();
98✔
1892
            TableRef table = tr->get_table("users");
98✔
1893
            if (table->size() > 0) {
98✔
1894
                size_t del = random.draw_int_mod(table->size());
83✔
1895
                // cerr << "-" << del << ": " << table->get_string(0, del) << std::endl;
1896
                table->remove_object(keys[del]);
83✔
1897
                keys.erase(keys.begin() + del);
83✔
1898
                table->verify();
83✔
1899
            }
83✔
1900
            tr->commit();
98✔
1901
        }
98✔
1902
        else {
102✔
1903
            // add new user
1904
            auto tr = db->start_write();
102✔
1905
            TableRef table = tr->get_table("users");
102✔
1906
            char txt[100];
102✔
1907
            rand_str(random, txt, 8);
102✔
1908
            txt[8] = 0;
102✔
1909
            // cerr << "+" << txt << std::endl;
1910
            auto key = table->create_object().set_all(txt).get_key();
102✔
1911
            keys.push_back(key);
102✔
1912
            table->verify();
102✔
1913
            tr->commit();
102✔
1914
        }
102✔
1915
    }
200✔
1916
}
2✔
1917

1918
TEST(Shared_ClearColumnWithBasicArrayRootLeaf)
1919
{
2✔
1920
    SHARED_GROUP_TEST_PATH(path);
2✔
1921
    {
2✔
1922
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1923
        WriteTransaction wt(sg);
2✔
1924
        TableRef test = wt.add_table("Test");
2✔
1925
        auto col = test->add_column(type_Double, "foo");
2✔
1926
        test->clear();
2✔
1927
        test->create_object(ObjKey(7)).set(col, 727.2);
2✔
1928
        wt.commit();
2✔
1929
    }
2✔
1930
    {
2✔
1931
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1932
        ReadTransaction rt(sg);
2✔
1933
        ConstTableRef test = rt.get_table("Test");
2✔
1934
        auto col = test->get_column_key("foo");
2✔
1935
        CHECK_EQUAL(727.2, test->get_object(ObjKey(7)).get<Double>(col));
2✔
1936
    }
2✔
1937
}
2✔
1938

1939
TEST(Shared_ClearColumnWithLinksToSelf)
1940
{
2✔
1941
    // Reproduction of issue found by fuzzer
1942
    SHARED_GROUP_TEST_PATH(path);
2✔
1943
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
1944
    {
2✔
1945
        WriteTransaction wt(sg);
2✔
1946
        TableRef test = wt.add_table("Test");
2✔
1947
        auto col = test->add_column(*test, "foo");
2✔
1948
        test->add_column(type_Int, "bar");
2✔
1949
        ObjKeys keys;
2✔
1950
        test->create_objects(400, keys);             // Ensure non root clusters
2✔
1951
        test->get_object(keys[3]).set(col, keys[8]); // Link must be even
2✔
1952
        wt.commit();
2✔
1953
    }
2✔
1954
    {
2✔
1955
        WriteTransaction wt(sg);
2✔
1956
        TableRef test = wt.get_table("Test");
2✔
1957
        // Ensure that cluster array is COW, but not expanded
1958
        test->remove_column(test->get_column_key("bar"));
2✔
1959
        test->clear();
2✔
1960
        wt.commit();
2✔
1961
    }
2✔
1962
}
2✔
1963

1964
#if 0 // FIXME: Reenable when it can pass reliably
1965
#ifdef _WIN32
1966

1967
TEST(Shared_WaitForChangeAfterOwnCommit)
1968
{
1969
    SHARED_GROUP_TEST_PATH(path);
1970

1971
    DB* sg = new DB(path);
1972
    sg->begin_write();
1973
    sg->commit();
1974
    bool b = sg->wait_for_change();
1975
}
1976

1977
NONCONCURRENT_TEST(Shared_InterprocessWaitForChange)
1978
{
1979
    // We can't use SHARED_GROUP_TEST_PATH() because it will attempt to clean up the .realm file at the end,
1980
    // and hence throw if the other processstill has the .realm file open
1981
    std::string path = get_test_path("Shared_InterprocessWaitForChange", ".realm");
1982

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

1987
    if (pid == -1) {
1988
        CHECK(false);
1989
        return;
1990
    }
1991

1992
    auto sg = DB::create(path);
1993

1994
    // An old .realm file with random contents can exist (such as a leftover from earlier crash) with random
1995
    // data, so we always initialize the database
1996
    {
1997
        auto tr = sg->start_write();
1998
        Group& g(*tr);
1999
        if (g.size() == 1) {
2000
            g.remove_table("data");
2001
            TableRef table = g.add_table("data");
2002
            auto col = table->add_column(type_Int, "ints");
2003
            table->create_object().set(col, 0);
2004
        }
2005
        tr->commit();
2006
        sg->wait_for_change(tr);
2007
    }
2008

2009
    bool first = false;
2010
    fastrand(time(0), true);
2011

2012
    // By turn, incremenet the counter and wait for the other to increment it too
2013
    for (int i = 0; i < 10; i++)
2014
    {
2015
        auto tr = sg->start_write();
2016
        Group& g(*tr);
2017
        if (g.size() == 1) {
2018
            TableRef table = g.get_table("data");
2019
            auto col = table->get_column_key("ints");
2020
            auto first_obj = table->begin();
2021
            int64_t v = first_obj->get<int64_t>(col);
2022

2023
            if (i == 0 && v == 0)
2024
                first = true;
2025

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

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

2037
        tr->commit();
2038

2039
        if (fastrand(1))
2040
            millisleep((time(0) % 10) * 10);
2041

2042
        sg->wait_for_change(tr);
2043

2044
        if (fastrand(1))
2045
            millisleep((time(0) % 10) * 10);
2046
    }
2047

2048
    // Wake up other process so it will exit too
2049
    auto tr = sg->start_write();
2050
    tr->commit();
2051
}
2052
#endif
2053

2054
// This test will hang infinitely instead of failing!!!
2055
TEST(Shared_WaitForChange)
2056
{
2057
    const int num_threads = 3;
2058
    Mutex mutex;
2059
    int shared_state[num_threads];
2060
    for (int j = 0; j < num_threads; j++)
2061
        shared_state[j] = 0;
2062
    SHARED_GROUP_TEST_PATH(path);
2063
    DBRef sg = DB::create(path);
2064

2065
    auto waiter = [&](DBRef db, int i) {
2066
        TransactionRef tr;
2067
        {
2068
            LockGuard l(mutex);
2069
            shared_state[i] = 1;
2070
            tr = db->start_read();
2071
        }
2072
        db->wait_for_change(tr);
2073
        {
2074
            LockGuard l(mutex);
2075
            shared_state[i] = 2; // this state should not be observed by the writer
2076
        }
2077
        db->wait_for_change(tr); // we'll fall right through here, because we haven't advanced our readlock
2078
        {
2079
            LockGuard l(mutex);
2080
            tr->end_read();
2081
            tr = db->start_read();
2082
            shared_state[i] = 3;
2083
        }
2084
        db->wait_for_change(tr); // this time we'll wait because state hasn't advanced since we did.
2085
        {
2086
            tr = db->start_read();
2087
            {
2088
                LockGuard l(mutex);
2089
                shared_state[i] = 4;
2090
            }
2091
            db->wait_for_change(tr); // everybody waits in state 4
2092
            {
2093
                LockGuard l(mutex);
2094
                tr->end_read();
2095
                tr = db->start_read();
2096
                shared_state[i] = 5;
2097
            }
2098
        }
2099
        db->wait_for_change(tr); // wait until wait_for_change is released
2100
        {
2101
            LockGuard l(mutex);
2102
            shared_state[i] = 6;
2103
        }
2104
    };
2105

2106
    Thread threads[num_threads];
2107
    for (int j = 0; j < num_threads; j++)
2108
        threads[j].start([waiter, sg, j] { waiter(sg, j); });
2109
    bool try_again = true;
2110
    while (try_again) {
2111
        try_again = false;
2112
        for (int j = 0; j < num_threads; j++) {
2113
            LockGuard l(mutex);
2114
            if (shared_state[j] < 1) try_again = true;
2115
            CHECK(shared_state[j] < 2);
2116
        }
2117
    }
2118
    // At this point all transactions have progress to state 1,
2119
    // and none of them has progressed further.
2120
    // This write transaction should allow all readers to run again
2121
    {
2122
        WriteTransaction wt(sg);
2123
        wt.commit();
2124
    }
2125

2126
    // All readers should pass through state 2 to state 3, so wait
2127
    // for all to reach state 3:
2128
    try_again = true;
2129
    while (try_again) {
2130
        try_again = false;
2131
        for (int j = 0; j < num_threads; j++) {
2132
            LockGuard l(mutex);
2133
            if (3 != shared_state[j]) try_again = true;
2134
            CHECK(shared_state[j] < 4);
2135
        }
2136
    }
2137
    // all readers now waiting before entering state 4
2138
    {
2139
        WriteTransaction wt(sg);
2140
        wt.commit();
2141
    }
2142
    try_again = true;
2143
    while (try_again) {
2144
        try_again = false;
2145
        for (int j = 0; j < num_threads; j++) {
2146
            LockGuard l(mutex);
2147
            if (4 != shared_state[j]) try_again = true;
2148
        }
2149
    }
2150
    // all readers now waiting in stage 4
2151
    {
2152
        WriteTransaction wt(sg);
2153
        wt.commit();
2154
    }
2155
    // readers racing into stage 5
2156
    try_again = true;
2157
    while (try_again) {
2158
        try_again = false;
2159
        for (int j = 0; j < num_threads; j++) {
2160
            LockGuard l(mutex);
2161
            if (5 != shared_state[j]) try_again = true;
2162
        }
2163
    }
2164
    // everybod reached stage 5 and waiting
2165
    try_again = true;
2166
    sg->wait_for_change_release();
2167
    while (try_again) {
2168
        try_again = false;
2169
        for (int j = 0; j < num_threads; j++) {
2170
            LockGuard l(mutex);
2171
            if (6 != shared_state[j]) {
2172
                try_again = true;
2173
            }
2174
        }
2175
    }
2176
    for (int j = 0; j < num_threads; j++)
2177
        threads[j].join();
2178
}
2179
#endif
2180

2181
TEST(Shared_MultipleSharersOfStreamingFormat)
2182
{
2✔
2183
    SHARED_GROUP_TEST_PATH(path);
2✔
2184
    {
2✔
2185
        // Create non-empty file without free-space tracking
2186
        Group g;
2✔
2187
        g.add_table("x");
2✔
2188
        g.write(path, crypt_key());
2✔
2189
    }
2✔
2190
    {
2✔
2191
        // See if we can handle overlapped accesses through multiple shared groups
2192
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2193
        DBRef sg2 = DB::create(path, DBOptions(crypt_key()));
2✔
2194
        {
2✔
2195
            ReadTransaction rt(sg);
2✔
2196
            rt.get_group().verify();
2✔
2197
            CHECK(rt.has_table("x"));
2✔
2198
            CHECK(!rt.has_table("gnyf"));
2✔
2199
            CHECK(!rt.has_table("baz"));
2✔
2200
        }
2✔
2201
        {
2✔
2202
            WriteTransaction wt(sg);
2✔
2203
            wt.get_group().verify();
2✔
2204
            wt.add_table("baz"); // Add table "baz"
2✔
2205
            wt.commit();
2✔
2206
        }
2✔
2207
        {
2✔
2208
            WriteTransaction wt2(sg2);
2✔
2209
            wt2.get_group().verify();
2✔
2210
            wt2.add_table("gnyf"); // Add table "gnyf"
2✔
2211
            wt2.commit();
2✔
2212
        }
2✔
2213
    }
2✔
2214
}
2✔
2215

2216
#if REALM_ENABLE_ENCRYPTION
2217
// verify that even though different threads share the same encrypted pages,
2218
// a thread will not get access without the key.
2219
TEST(Shared_EncryptionKeyCheck)
2220
{
2✔
2221
    SHARED_GROUP_TEST_PATH(path);
2✔
2222
    DBRef sg = DB::create(path, DBOptions(crypt_key(true)));
2✔
2223
    CHECK_THROW(DB::create(path, DBOptions()), InvalidDatabase);
2✔
2224
    DBRef sg3 = DB::create(path, DBOptions(crypt_key(true)));
2✔
2225
}
2✔
2226

2227
// opposite - if opened unencrypted, attempt to share it encrypted
2228
// will throw an error.
2229
TEST(Shared_EncryptionKeyCheck_2)
2230
{
2✔
2231
    SHARED_GROUP_TEST_PATH(path);
2✔
2232
    DBRef sg = DB::create(path, DBOptions());
2✔
2233
    CHECK_THROW(DB::create(path, DBOptions(crypt_key(true))), InvalidDatabase);
2✔
2234
    DBRef sg3 = DB::create(path, DBOptions());
2✔
2235
    CHECK(sg3);
2✔
2236
}
2✔
2237

2238
// if opened by one key, it cannot be opened by a different key
2239
TEST(Shared_EncryptionKeyCheck_3)
2240
{
2✔
2241
    SHARED_GROUP_TEST_PATH(path);
2✔
2242
    const char* first_key = crypt_key(true);
2✔
2243
    char second_key[64];
2✔
2244
    memcpy(second_key, first_key, 64);
2✔
2245
    second_key[3] = ~second_key[3];
2✔
2246
    DBRef sg = DB::create(path, DBOptions(first_key));
2✔
2247
    CHECK_THROW(DB::create(path, DBOptions(second_key)), InvalidDatabase);
2✔
2248
    DBRef sg3 = DB::create(path, DBOptions(first_key));
2✔
2249
}
2✔
2250

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

2291
#endif // REALM_ENABLE_ENCRYPTION
2292

2293
TEST(Shared_VersionCount)
2294
{
2✔
2295
    SHARED_GROUP_TEST_PATH(path);
2✔
2296
    DBRef sg = get_test_db(path);
2✔
2297
    CHECK_EQUAL(1, sg->get_number_of_versions());
2✔
2298
    TransactionRef reader = sg->start_read();
2✔
2299
    {
2✔
2300
        WriteTransaction wt(sg);
2✔
2301
        CHECK_EQUAL(1, sg->get_number_of_versions());
2✔
2302
        wt.commit();
2✔
2303
    }
2✔
2304
    CHECK_EQUAL(2, sg->get_number_of_versions());
2✔
2305
    {
2✔
2306
        WriteTransaction wt(sg);
2✔
2307
        wt.commit();
2✔
2308
    }
2✔
2309
    CHECK_EQUAL(3, sg->get_number_of_versions());
2✔
2310
    reader->close();
2✔
2311
    CHECK_EQUAL(3, sg->get_number_of_versions());
2✔
2312
    {
2✔
2313
        WriteTransaction wt(sg);
2✔
2314
        wt.commit();
2✔
2315
    }
2✔
2316
    // both the last and the second-last commit is kept, so once
2317
    // you've committed anything, you will never get back to having
2318
    // just a single version.
2319
    CHECK_EQUAL(2, sg->get_number_of_versions());
2✔
2320
}
2✔
2321

2322
TEST(Shared_MultipleRollbacks)
2323
{
2✔
2324
    SHARED_GROUP_TEST_PATH(path);
2✔
2325
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2326
    TransactionRef wt = sg->start_write();
2✔
2327
    wt->rollback();
2✔
2328
    wt->rollback();
2✔
2329
}
2✔
2330

2331

2332
TEST(Shared_MultipleEndReads)
2333
{
2✔
2334
    SHARED_GROUP_TEST_PATH(path);
2✔
2335
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2336
    TransactionRef reader = sg->start_read();
2✔
2337
    reader->end_read();
2✔
2338
    reader->end_read();
2✔
2339
}
2✔
2340

2341
#ifdef REALM_DEBUG
2342
// SharedGroup::reserve() is a debug method only available in debug mode
2343
TEST(Shared_ReserveDiskSpace)
2344
{
2✔
2345
    SHARED_GROUP_TEST_PATH(path);
2✔
2346
    {
2✔
2347
        DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2348
        size_t orig_file_size = size_t(File(path).get_size());
2✔
2349

2350
        // Check that reserve() does not change the file size if the
2351
        // specified size is less than the actual file size.
2352
        size_t reserve_size_1 = orig_file_size / 2;
2✔
2353
        sg->reserve(reserve_size_1);
2✔
2354
        size_t new_file_size_1 = size_t(File(path).get_size());
2✔
2355
        CHECK_EQUAL(orig_file_size, new_file_size_1);
2✔
2356

2357
        // Check that reserve() does not change the file size if the
2358
        // specified size is equal to the actual file size.
2359
        size_t reserve_size_2 = orig_file_size;
2✔
2360
        sg->reserve(reserve_size_2);
2✔
2361
        size_t new_file_size_2 = size_t(File(path).get_size());
2✔
2362
        if (crypt_key()) {
2✔
2363
            // For encrypted files, reserve() may actually grow the file
2364
            // with a page sized header.
2365
            CHECK(orig_file_size <= new_file_size_2 && (orig_file_size + page_size()) >= new_file_size_2);
×
2366
        }
×
2367
        else {
2✔
2368
            CHECK_EQUAL(orig_file_size, new_file_size_2);
2✔
2369
        }
2✔
2370

2371
        // Check that reserve() does change the file size if the
2372
        // specified size is greater than the actual file size, and
2373
        // that the new size is at least as big as the requested size.
2374
        size_t reserve_size_3 = orig_file_size + 1;
2✔
2375
        sg->reserve(reserve_size_3);
2✔
2376
        size_t new_file_size_3 = size_t(File(path).get_size());
2✔
2377
        CHECK(new_file_size_3 >= reserve_size_3);
2✔
2378
        ObjKeys keys;
2✔
2379

2380
        // Check that disk space reservation is independent of transactions
2381
        {
2✔
2382
            WriteTransaction wt(sg);
2✔
2383
            wt.get_group().verify();
2✔
2384
            auto t = wt.add_table("table_1");
2✔
2385
            test_table_add_columns(t);
2✔
2386
            t->create_objects(2000, keys);
2✔
2387
            wt.commit();
2✔
2388
        }
2✔
2389
        orig_file_size = size_t(File(path).get_size());
2✔
2390
        size_t reserve_size_4 = 2 * orig_file_size + 1;
2✔
2391
        sg->reserve(reserve_size_4);
2✔
2392
        size_t new_file_size_4 = size_t(File(path).get_size());
2✔
2393
        CHECK(new_file_size_4 >= reserve_size_4);
2✔
2394
        {
2✔
2395
            WriteTransaction wt(sg);
2✔
2396
            wt.get_group().verify();
2✔
2397
            auto t = wt.add_table("table_2");
2✔
2398
            test_table_add_columns(t);
2✔
2399
            t->create_objects(2000, keys);
2✔
2400
            orig_file_size = size_t(File(path).get_size());
2✔
2401
            size_t reserve_size_5 = orig_file_size + 333;
2✔
2402
            sg->reserve(reserve_size_5);
2✔
2403
            size_t new_file_size_5 = size_t(File(path).get_size());
2✔
2404
            CHECK(new_file_size_5 >= reserve_size_5);
2✔
2405
            t = wt.add_table("table_3");
2✔
2406
            test_table_add_columns(t);
2✔
2407
            t->create_objects(2000, keys);
2✔
2408
            wt.commit();
2✔
2409
        }
2✔
2410
        orig_file_size = size_t(File(path).get_size());
2✔
2411
        size_t reserve_size_6 = orig_file_size + 459;
2✔
2412
        sg->reserve(reserve_size_6);
2✔
2413
        size_t new_file_size_6 = size_t(File(path).get_size());
2✔
2414
        CHECK(new_file_size_6 >= reserve_size_6);
2✔
2415
        {
2✔
2416
            WriteTransaction wt(sg);
2✔
2417
            wt.get_group().verify();
2✔
2418
            wt.commit();
2✔
2419
        }
2✔
2420
    }
2✔
2421
}
2✔
2422
#endif
2423

2424
TEST(Shared_MovingSearchIndex)
2425
{
2✔
2426
    // Test that the 'index in parent' property of search indexes is properly
2427
    // adjusted when columns are inserted or removed at a lower column_index.
2428

2429
    SHARED_GROUP_TEST_PATH(path);
2✔
2430
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2431

2432
    // Create an int column, regular string column, and an enumeration strings
2433
    // column, and equip them with search indexes.
2434
    ColKey int_col, str_col, enum_col, padding_col;
2✔
2435
    std::vector<ObjKey> obj_keys;
2✔
2436
    {
2✔
2437
        WriteTransaction wt(sg);
2✔
2438
        TableRef table = wt.add_table("foo");
2✔
2439
        padding_col = table->add_column(type_Int, "padding");
2✔
2440
        int_col = table->add_column(type_Int, "int");
2✔
2441
        str_col = table->add_column(type_String, "regular");
2✔
2442
        enum_col = table->add_column(type_String, "enum");
2✔
2443

2444
        table->create_objects(64, obj_keys);
2✔
2445
        for (int i = 0; i < 64; ++i) {
130✔
2446
            auto obj = table->get_object(obj_keys[i]);
128✔
2447
            std::string out(std::string("foo") + util::to_string(i));
128✔
2448
            obj.set<Int>(int_col, i);
128✔
2449
            obj.set<String>(str_col, out);
128✔
2450
            obj.set<String>(enum_col, "bar");
128✔
2451
        }
128✔
2452
        table->get_object(obj_keys.back()).set<String>(enum_col, "bar63");
2✔
2453
        table->enumerate_string_column(enum_col);
2✔
2454
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2455
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2456
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2457

2458
        table->add_search_index(int_col);
2✔
2459
        table->add_search_index(str_col);
2✔
2460
        table->add_search_index(enum_col);
2✔
2461

2462
        wt.get_group().verify();
2✔
2463

2464
        CHECK_EQUAL(obj_keys[61], table->find_first_int(int_col, 61));
2✔
2465
        CHECK_EQUAL(obj_keys[62], table->find_first_string(str_col, "foo62"));
2✔
2466
        CHECK_EQUAL(obj_keys[63], table->find_first_string(enum_col, "bar63"));
2✔
2467
        wt.commit();
2✔
2468
    }
2✔
2469

2470
    // Remove the padding column to shift the indexed columns
2471
    {
2✔
2472
        WriteTransaction wt(sg);
2✔
2473
        TableRef table = wt.get_table("foo");
2✔
2474

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

2488
        table->remove_column(padding_col);
2✔
2489
        wt.get_group().verify();
2✔
2490

2491
        CHECK(table->has_search_index(int_col));
2✔
2492
        CHECK(table->has_search_index(str_col));
2✔
2493
        CHECK(table->has_search_index(enum_col));
2✔
2494
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2495
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2496
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2497
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2498
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2499
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2500
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2501
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2502
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2503

2504
        auto obj = table->get_object(obj_keys[1]);
2✔
2505
        obj.set<Int>(int_col, 101);
2✔
2506
        obj.set<String>(str_col, "foo_Y");
2✔
2507
        obj.set<String>(enum_col, "bar_Y");
2✔
2508
        wt.get_group().verify();
2✔
2509

2510
        CHECK(table->has_search_index(int_col));
2✔
2511
        CHECK(table->has_search_index(str_col));
2✔
2512
        CHECK(table->has_search_index(enum_col));
2✔
2513
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2514
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2515
        CHECK_EQUAL(3, table->get_num_unique_values(enum_col));
2✔
2516
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2517
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2518
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2519
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2520
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2521
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2522
        CHECK_EQUAL(obj_keys[1], table->find_first_int(int_col, 101));
2✔
2523
        CHECK_EQUAL(obj_keys[1], table->find_first_string(str_col, "foo_Y"));
2✔
2524
        CHECK_EQUAL(obj_keys[1], table->find_first_string(enum_col, "bar_Y"));
2✔
2525
        CHECK_EQUAL(obj_keys[63], table->find_first_string(enum_col, "bar63"));
2✔
2526

2527
        wt.commit();
2✔
2528
    }
2✔
2529
}
2✔
2530

2531
TEST_IF(Shared_BeginReadFailure, _impl::SimulatedFailure::is_enabled())
2532
{
2✔
2533
    SHARED_GROUP_TEST_PATH(path);
2✔
2534
    DBRef sg = get_test_db(path);
2✔
2535
    using sf = _impl::SimulatedFailure;
2✔
2536
    sf::OneShotPrimeGuard pg(sf::shared_group__grow_reader_mapping);
2✔
2537
    CHECK_THROW(sg->start_read(), sf);
2✔
2538
}
2✔
2539

2540

2541
TEST(Shared_SessionDurabilityConsistency)
2542
{
2✔
2543
    // Check that we can reliably detect inconsist durability choices across
2544
    // concurrent session participants.
2545

2546
    // Errors of this kind are considered as incorrect API usage, and will lead
2547
    // to throwing of LogicError exceptions.
2548

2549
    SHARED_GROUP_TEST_PATH(path);
2✔
2550
    {
2✔
2551
        DBOptions::Durability durability_1 = DBOptions::Durability::Full;
2✔
2552
        DBRef sg = DB::create(path, DBOptions(durability_1));
2✔
2553

2554
        DBOptions::Durability durability_2 = DBOptions::Durability::MemOnly;
2✔
2555
        CHECK_RUNTIME_ERROR(DB::create(path, DBOptions(durability_2)), ErrorCodes::IncompatibleSession);
2✔
2556
    }
2✔
2557
}
2✔
2558

2559

2560
TEST(Shared_WriteEmpty)
2561
{
2✔
2562
    SHARED_GROUP_TEST_PATH(path_1);
2✔
2563
    GROUP_TEST_PATH(path_2);
2✔
2564
    {
2✔
2565
        DBRef sg = DB::create(path_1);
2✔
2566
        ReadTransaction rt(sg);
2✔
2567
        rt.get_group().write(path_2);
2✔
2568
    }
2✔
2569
}
2✔
2570

2571

2572
TEST(Shared_CompactEmpty)
2573
{
2✔
2574
    SHARED_GROUP_TEST_PATH(path);
2✔
2575
    {
2✔
2576
        DBRef sg = get_test_db(path);
2✔
2577
        CHECK(sg->compact());
2✔
2578
    }
2✔
2579
}
2✔
2580

2581

2582
TEST(Shared_VersionOfBoundSnapshot)
2583
{
2✔
2584
    SHARED_GROUP_TEST_PATH(path);
2✔
2585
    DB::version_type version;
2✔
2586
    DBRef sg = get_test_db(path);
2✔
2587
    {
2✔
2588
        ReadTransaction rt(sg);
2✔
2589
        version = rt.get_version();
2✔
2590
    }
2✔
2591
    {
2✔
2592
        ReadTransaction rt(sg);
2✔
2593
        CHECK_EQUAL(version, rt.get_version());
2✔
2594
    }
2✔
2595
    {
2✔
2596
        WriteTransaction wt(sg);
2✔
2597
        CHECK_EQUAL(version, wt.get_version());
2✔
2598
    }
2✔
2599
    {
2✔
2600
        WriteTransaction wt(sg);
2✔
2601
        CHECK_EQUAL(version, wt.get_version());
2✔
2602
        wt.commit(); // Increment version
2✔
2603
    }
2✔
2604
    {
2✔
2605
        ReadTransaction rt(sg);
2✔
2606
        CHECK_LESS(version, rt.get_version());
2✔
2607
        version = rt.get_version();
2✔
2608
    }
2✔
2609
    {
2✔
2610
        WriteTransaction wt(sg);
2✔
2611
        CHECK_EQUAL(version, wt.get_version());
2✔
2612
        wt.commit(); // Increment version
2✔
2613
    }
2✔
2614
    {
2✔
2615
        ReadTransaction rt(sg);
2✔
2616
        CHECK_LESS(version, rt.get_version());
2✔
2617
    }
2✔
2618
}
2✔
2619

2620

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

2645
    std::vector<std::pair<void*, size_t>> memory_list;
2646
    // Reserve enough for 5*100000 Gb, but in practice the vector is only ever around size 10.
2647
    // Do this here to avoid the (small) chance that adding to the vector will request new virtual memory
2648
    memory_list.reserve(500);
2649
    size_t chunk_size = size_t(1024) * 1024 * 1024 * 100000;
2650
    while (chunk_size > string_length) {
2651
        void* addr = ::mmap(nullptr, chunk_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
2652
        if (addr == MAP_FAILED) {
2653
            chunk_size /= 2;
2654
        }
2655
        else {
2656
            memory_list.push_back(std::pair<void*, size_t>(addr, chunk_size));
2657
        }
2658
    }
2659

2660
    bool expected_exception_caught = false;
2661
    // Attempt to open Realm, should fail because we hold too much already.
2662
    try {
2663
        SharedGroup sg2(path, false, SharedGroupOptions(crypt_key()));
2664
    }
2665
    catch (AddressSpaceExhausted& e) {
2666
        expected_exception_caught = true;
2667
    }
2668
    CHECK(expected_exception_caught);
2669

2670
    // Release memory manually.
2671
    for (auto it = memory_list.begin(); it != memory_list.end(); ++it) {
2672
        ::munmap(it->first, it->second);
2673
    }
2674

2675
    // Realm should succeed to open now.
2676
    expected_exception_caught = false;
2677
    try {
2678
        SharedGroup sg2(path, false, SharedGroupOptions(crypt_key()));
2679
    }
2680
    catch (AddressSpaceExhausted& e) {
2681
        expected_exception_caught = true;
2682
    }
2683
    CHECK(!expected_exception_caught);
2684
}
2685
#endif // !win32
2686
*/
2687

2688
// Run some (repeatable) random checks through the fuzz tester.
2689
// For a comprehensive fuzz test, afl should be run. To do this see test/fuzzy/README.md
2690
// If this check fails for some reason, you can find the problem by changing
2691
// the parse_and_apply_instructions call to use std::cerr which will print out
2692
// the instructions used to duplicate the failure.
2693
NONCONCURRENT_TEST(Shared_StaticFuzzTestRunSanityCheck)
2694
{
2✔
2695
    // Either provide a crash file generated by AFL to reproduce a crash, or leave it blank in order to run
2696
    // a very simple fuzz test that just uses a random generator for generating Realm actions.
2697
    std::string filename = "";
2✔
2698
    // std::string filename = "/findings/hangs/id:000041,src:000000,op:havoc,rep:64";
2699
    // std::string filename = "d:/crash3";
2700

2701
    if (filename != "") {
2✔
2702
        const char* tmp[] = {"", filename.c_str(), "--log"};
×
2703
        run_fuzzy(sizeof(tmp) / sizeof(tmp[0]), tmp);
×
2704
    }
×
2705
    else {
2✔
2706
        // Number of fuzzy tests
2707
#if TEST_DURATION == 0
2✔
2708
        const size_t iterations = 3;
2✔
2709
#else
2710
        const size_t iterations = 1000;
2711
#endif
2712

2713
        // Number of instructions in each test
2714
        // Changing this strongly affects the test suite run time
2715
        const size_t instructions = 200;
2✔
2716

2717
        for (size_t counter = 0; counter < iterations; counter++) {
8✔
2718
            // You can use your own seed if you have observed a crashing unit test that
2719
            // printed out some specific seed (the "Unit test random seed:" part that appears).
2720
            FastRand generator(unit_test_random_seed + counter);
6✔
2721

2722
            std::string instr;
6✔
2723

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

2728
            for (size_t t = 0; t < instructions; t++) {
1,206✔
2729
                char c = static_cast<char>(generator());
1,200✔
2730
                instr += c;
1,200✔
2731
                std::string tmp;
1,200✔
2732
                unit_test::to_string(static_cast<int>(c), tmp);
1,200✔
2733
                fastlog += tmp;
1,200✔
2734
                if (t + 1 < instructions) {
1,200✔
2735
                    fastlog += ", ";
1,194✔
2736
                }
1,194✔
2737
                else {
6✔
2738
                    fastlog += "}; instr = string(instr2);";
6✔
2739
                }
6✔
2740
            }
1,200✔
2741

2742
            // Scope guard of "path" is inside the loop to clean up files per iteration
2743
            SHARED_GROUP_TEST_PATH(path);
6✔
2744
            // If using std::cerr, you can copy/paste the console output into a unit test
2745
            // to get a reproduction test case
2746
            // parse_and_apply_instructions(instr, path, std::cerr);
2747
            parse_and_apply_instructions(instr, path, nullptr);
6✔
2748
        }
6✔
2749
    }
2✔
2750
}
2✔
2751

2752
#if 0 // not suitable for automatic testing
2753
// This test checks what happens when a version is pinned and there are many
2754
// large write transactions that grow the file quickly. It takes a long time
2755
// and can make very very large files so it is not suited to automatic testing.
2756
TEST_IF(Shared_encrypted_pin_and_write, false)
2757
{
2758
    const size_t num_objects = 1000;
2759
    const size_t num_transactions = 1000000;
2760
    const size_t num_writer_threads = 8;
2761
    SHARED_GROUP_TEST_PATH(path);
2762

2763
    { // initial table structure setup on main thread
2764
        DBRef sg = DB::create(path, DBOptions(crypt_key(true)));
2765
        WriteTransaction wt(sg);
2766
        Group& group = wt.get_group();
2767
        TableRef t = group.add_table("table");
2768
        t->add_column(type_String, "string_col", true);
2769
        for (size_t i = 0; i < num_objects; ++i) {
2770
            t->create_object();
2771
        }
2772
        wt.commit();
2773
    }
2774

2775
    DBRef sg_reader = DB::create(path, DBOptions(crypt_key(true)));
2776

2777
    ReadTransaction rt(sg_reader); // hold first version
2778

2779
    auto do_many_writes = [&]() {
2780
        DBRef sg = DB::create(path, DBOptions(crypt_key(true)));
2781
        const size_t base_size = 100000;
2782
        std::string base(base_size, 'a');
2783
        // write many transactions to grow the file
2784
        // around 4.6 GB seems to be the breaking size
2785
        for (size_t t = 0; t < num_transactions; ++t) {
2786
            std::vector<std::string> rows(num_objects);
2787
            // change a character so there's no storage optimizations
2788
            for (size_t row = 0; row < num_objects; ++row) {
2789
                base[(t * num_objects + row)%base_size] = 'a' + (row % 52);
2790
                rows[row] = base;
2791
            }
2792
            WriteTransaction wt(sg);
2793
            Group& g = wt.get_group();
2794
            auto keys = g.get_table_keys();
2795
            TableRef table = g.get_table(keys[0]);
2796
            ColKey str_col = table->get_column_key("string_col");
2797
            size_t count = 0;
2798
            for (auto it = table->begin(); it != table->end(); ++it, ++count) {
2799
                StringData c(rows[count]);
2800
                it->set(str_col, c);
2801
            }
2802
            wt.commit();
2803
        }
2804
    };
2805

2806
    Thread threads[num_writer_threads];
2807
    for (size_t i = 0; i < num_writer_threads; ++i)
2808
        threads[i].start(do_many_writes);
2809

2810
    for (size_t i = 0; i < num_writer_threads; ++i) {
2811
        threads[i].join();
2812
    }
2813
}
2814
#endif
2815

2816

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

2854
TEST_IF(Shared_CompactEncrypt, REALM_ENABLE_ENCRYPTION)
2855
{
2✔
2856
    SHARED_GROUP_TEST_PATH(path);
2✔
2857
    const char* key1 = "KdrL2ieWyspILXIPetpkLD6rQYKhYnS6lvGsgk4qsJAMr1adQnKsYo3oTEYJDIfa";
2✔
2858
    const char* key2 = "ti6rOKviXrwxSGMPVk35Dp9Q4eku8Cu8YTtnnZKAejOTNIEv7TvXrYdjOPSNexMR";
2✔
2859
    {
2✔
2860
        auto db = DB::create(path, DBOptions(key1));
2✔
2861
        auto tr = db->start_write();
2✔
2862
        TableRef t = tr->add_table("table");
2✔
2863
        auto col = t->add_column(type_String, "Strings");
2✔
2864
        for (size_t i = 0; i < 10000; i++) {
20,002✔
2865
            std::string str = "Shared_CompactEncrypt" + util::to_string(i);
20,000✔
2866
            t->create_object().set(col, StringData(str));
20,000✔
2867
        }
20,000✔
2868
        tr->commit();
2✔
2869

2870
        CHECK(db->compact());
2✔
2871
        {
2✔
2872
            auto rt = db->start_read();
2✔
2873
            CHECK(rt->has_table("table"));
2✔
2874
        }
2✔
2875

2876
        bool bump_version_number = true;
2✔
2877
        CHECK(db->compact(bump_version_number, key2));
2✔
2878
        {
2✔
2879
            auto rt = db->start_read();
2✔
2880
            CHECK(rt->has_table("table"));
2✔
2881
        }
2✔
2882

2883
        CHECK(db->compact(bump_version_number, nullptr));
2✔
2884
        {
2✔
2885
            auto rt = db->start_read();
2✔
2886
            CHECK(rt->has_table("table"));
2✔
2887
        }
2✔
2888
    }
2✔
2889
    {
2✔
2890
        DBOptions options;
2✔
2891
        options.no_create = true;
2✔
2892
        auto db = DB::create(path, options);
2✔
2893
        {
2✔
2894
            auto rt = db->start_read();
2✔
2895
            CHECK(rt->has_table("table"));
2✔
2896
        }
2✔
2897
    }
2✔
2898
}
2✔
2899

2900
// Repro case for: Assertion failed: top_size == 3 || top_size == 5 || top_size == 7 [0, 3, 0, 5, 0, 7]
2901
NONCONCURRENT_TEST(Shared_BigAllocationsMinimized)
2902
{
2✔
2903
    // String length at 2K will not trigger the error.
2904
    // all lengths >= 4K (that were tried) trigger the error
2905
    size_t string_length = 4 * 1024;
2✔
2906
    SHARED_GROUP_TEST_PATH(path);
2✔
2907
    std::string long_string(string_length, 'a');
2✔
2908
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2909
    {
2✔
2910
        {
2✔
2911
            WriteTransaction wt(sg);
2✔
2912
            TableRef table = wt.add_table("table");
2✔
2913
            table->add_column(type_String, "string_col");
2✔
2914
            auto cols = table->get_column_keys();
2✔
2915
            table->create_object(ObjKey(0)).set(cols[0], long_string.c_str());
2✔
2916
            wt.commit();
2✔
2917
        }
2✔
2918
        sg->compact(); // <- required to provoke subsequent failures
2✔
2919
        {
2✔
2920
            WriteTransaction wt(sg);
2✔
2921
            wt.get_group().verify();
2✔
2922
            TableRef table = wt.get_table("table");
2✔
2923
            auto cols = table->get_column_keys();
2✔
2924
            table->get_object(ObjKey(0)).set(cols[0], long_string.c_str());
2✔
2925
            wt.get_group().verify();
2✔
2926
            wt.commit();
2✔
2927
        }
2✔
2928
    }
2✔
2929
    {
2✔
2930
        WriteTransaction wt(sg); // <---- fails here
2✔
2931
        wt.get_group().verify();
2✔
2932
        TableRef table = wt.get_table("table");
2✔
2933
        auto cols = table->get_column_keys();
2✔
2934
        table->get_object(ObjKey(0)).set(cols[0], long_string.c_str());
2✔
2935
        wt.get_group().verify();
2✔
2936
        wt.commit();
2✔
2937
    }
2✔
2938
    sg->close();
2✔
2939
}
2✔
2940

2941
// Found by AFL (on a heavy hint from Finn that we should add a compact() instruction
2942
NONCONCURRENT_TEST(Shared_TopSizeNotEqualNine)
2943
{
2✔
2944
    SHARED_GROUP_TEST_PATH(path);
2✔
2945
    DBRef sg = DB::create(path, DBOptions(crypt_key()));
2✔
2946
    {
2✔
2947
        TransactionRef writer = sg->start_write();
2✔
2948

2949
        TableRef t = writer->add_table("foo");
2✔
2950
        t->add_column(type_Double, "doubles");
2✔
2951
        std::vector<ObjKey> keys;
2✔
2952
        t->create_objects(241, keys);
2✔
2953
        writer->commit();
2✔
2954
    }
2✔
2955
    REALM_ASSERT_RELEASE(sg->compact());
2✔
2956
    DBRef sg2 = DB::create(path, DBOptions(crypt_key()));
2✔
2957
    {
2✔
2958
        TransactionRef writer = sg2->start_write();
2✔
2959
        writer->commit();
2✔
2960
    }
2✔
2961
    TransactionRef reader2 = sg2->start_read();
2✔
2962
    DBRef sg3 = DB::create(path, DBOptions(crypt_key()));
2✔
2963
    TransactionRef reader3 = sg3->start_read();
2✔
2964
    TransactionRef reader = sg->start_read();
2✔
2965
}
2✔
2966

2967
// Found by AFL after adding the compact instruction
2968
// after further manual simplification, this test no longer triggers
2969
// the double free, but crashes in a different way
2970
TEST(Shared_Bptree_insert_failure)
2971
{
2✔
2972
    SHARED_GROUP_TEST_PATH(path);
2✔
2973
    DBRef sg_w = DB::create(path, DBOptions(crypt_key()));
2✔
2974
    TransactionRef writer = sg_w->start_write();
2✔
2975

2976
    auto tk = writer->add_table("")->get_key();
2✔
2977
    writer->get_table(tk)->add_column(type_Double, "dgrpn", true);
2✔
2978
    std::vector<ObjKey> keys;
2✔
2979
    writer->get_table(tk)->create_objects(246, keys);
2✔
2980
    writer->commit();
2✔
2981
    REALM_ASSERT_RELEASE(sg_w->compact());
2✔
2982
#if 0
2983
    {
2984
        // This intervening sg can do the same operation as the one doing compact,
2985
        // but without failing:
2986
        DB sg2(path, DBOptions(crypt_key()));
2987
        Group& g2 = const_cast<Group&>(sg2.begin_write());
2988
        g2.get_table(tk)->add_empty_row(396);
2989
    }
2990
#endif
2991
    {
2✔
2992
        TransactionRef writer2 = sg_w->start_write();
2✔
2993
        writer2->get_table(tk)->create_objects(396, keys);
2✔
2994
    }
2✔
2995
}
2✔
2996

2997
NONCONCURRENT_TEST(SharedGroupOptions_tmp_dir)
2998
{
2✔
2999
    const std::string initial_system_dir = DBOptions::get_sys_tmp_dir();
2✔
3000

3001
    const std::string test_dir = "/test-temp";
2✔
3002
    DBOptions::set_sys_tmp_dir(test_dir);
2✔
3003
    CHECK_EQUAL(DBOptions::get_sys_tmp_dir(), test_dir);
2✔
3004
    CHECK_EQUAL(DBOptions().temp_dir, test_dir);
2✔
3005

3006
    DBOptions::set_sys_tmp_dir(initial_system_dir);
2✔
3007
}
2✔
3008

3009

3010
namespace {
3011

3012
void wait_for(size_t expected, std::mutex& mutex, size_t& test_value)
3013
{
20✔
3014
    while (true) {
1,821✔
3015
        millisleep(1);
1,821✔
3016
        std::lock_guard<std::mutex> guard(mutex);
1,821✔
3017
        if (test_value == expected) {
1,821✔
3018
            return;
20✔
3019
        }
20✔
3020
    }
1,821✔
3021
}
20✔
3022

3023
} // end anonymous namespace
3024

3025
TEST(Shared_LockFileInitSpinsOnZeroSize)
3026
{
2✔
3027
    SHARED_GROUP_TEST_PATH(path);
2✔
3028

3029
    DBOptions options;
2✔
3030
    options.encryption_key = crypt_key();
2✔
3031
    DBRef sg = DB::create(path, options);
2✔
3032
    sg->close();
2✔
3033

3034
    CHECK(File::exists(path));
2✔
3035
    CHECK(File::exists(path.get_lock_path()));
2✔
3036

3037
    std::mutex mutex;
2✔
3038
    size_t test_stage = 0;
2✔
3039

3040
    std::thread t([&]() {
2✔
3041
        File f(path.get_lock_path(), File::mode_Write);
2✔
3042
        f.rw_lock_shared();
2✔
3043
        File::UnlockGuard ug(f);
2✔
3044

3045
        CHECK(f.is_attached());
2✔
3046

3047
        f.resize(0);
2✔
3048
        f.sync();
2✔
3049

3050
        mutex.lock();
2✔
3051
        test_stage = 1;
2✔
3052
        mutex.unlock();
2✔
3053

3054
        millisleep(100);
2✔
3055
        // the lock is then released and the other thread will be able to initialise properly
3056
    });
2✔
3057

3058
    wait_for(1, mutex, test_stage);
2✔
3059

3060
    // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves
3061
    sg = DB::create(path, options);
2✔
3062
    CHECK(sg->is_attached());
2✔
3063
    sg->close();
2✔
3064

3065
    t.join();
2✔
3066
}
2✔
3067

3068

3069
TEST(Shared_LockFileSpinsOnInitComplete)
3070
{
2✔
3071
    SHARED_GROUP_TEST_PATH(path);
2✔
3072

3073
    DBOptions options;
2✔
3074
    options.encryption_key = crypt_key();
2✔
3075
    DBRef sg = DB::create(path, options);
2✔
3076
    sg->close();
2✔
3077

3078
    CHECK(File::exists(path));
2✔
3079
    CHECK(File::exists(path.get_lock_path()));
2✔
3080

3081
    std::mutex mutex;
2✔
3082
    size_t test_stage = 0;
2✔
3083

3084
    std::thread t([&]() {
2✔
3085
        File f(path.get_lock_path(), File::mode_Write);
2✔
3086
        f.rw_lock_shared();
2✔
3087
        File::UnlockGuard ug(f);
2✔
3088

3089
        CHECK(f.is_attached());
2✔
3090

3091
        f.resize(1); // ftruncate will write 0 to init_complete
2✔
3092
        f.sync();
2✔
3093

3094
        mutex.lock();
2✔
3095
        test_stage = 1;
2✔
3096
        mutex.unlock();
2✔
3097

3098
        millisleep(100);
2✔
3099
        // the lock is then released and the other thread will be able to initialise properly
3100
    });
2✔
3101

3102
    wait_for(1, mutex, test_stage);
2✔
3103

3104
    // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves
3105
    sg = DB::create(path, options);
2✔
3106
    CHECK(sg->is_attached());
2✔
3107
    sg->close();
2✔
3108

3109
    t.join();
2✔
3110
}
2✔
3111

3112

3113
TEST(Shared_LockFileOfWrongSizeThrows)
3114
{
2✔
3115
    // NOTE: This unit test attempts to mimic the initialization of the .lock file as it takes place inside
3116
    // the SharedGroup::do_open() method. NOTE: If the layout of SharedGroup::SharedInfo should change,
3117
    // this unit test might stop working.
3118

3119
    SHARED_GROUP_TEST_PATH(path);
2✔
3120

3121
    DBOptions options;
2✔
3122
    options.encryption_key = crypt_key();
2✔
3123
    DBRef sg = DB::create(path, options);
2✔
3124
    sg->close();
2✔
3125

3126
    CHECK(File::exists(path));
2✔
3127
    CHECK(File::exists(path.get_lock_path()));
2✔
3128

3129
    std::mutex mutex;
2✔
3130
    size_t test_stage = 0;
2✔
3131

3132
    std::thread t([&]() {
2✔
3133
        File f(path.get_lock_path(), File::mode_Write);
2✔
3134
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3135
        f.rw_lock_shared();
2✔
3136
        File::UnlockGuard ug(f);
2✔
3137

3138
        CHECK(f.is_attached());
2✔
3139

3140
        size_t wrong_size = 100; // < sizeof(SharedInfo)
2✔
3141
        f.resize(wrong_size);    // ftruncate will fill with 0, which will set the init_complete flag to 0.
2✔
3142
        f.seek(0);
2✔
3143

3144
        // On Windows, we implement a shared lock on a file by locking the first byte of the file. Since
3145
        // you cannot write to a locked region using WriteFile(), we use memory mapping which works fine, and
3146
        // which is also the same method used by the .lock file initialization in SharedGroup::do_open()
3147
        File::Map<char> mem(f, realm::util::File::access_ReadWrite, 1);
2✔
3148

3149
        // set init_complete flag to 1 and sync
3150
        mem.get_addr()[0] = 1;
2✔
3151
        f.sync();
2✔
3152

3153
        CHECK_EQUAL(f.get_size(), wrong_size);
2✔
3154

3155
        mutex.lock();
2✔
3156
        test_stage = 1;
2✔
3157
        mutex.unlock();
2✔
3158

3159
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3160
    });
2✔
3161

3162
    wait_for(1, mutex, test_stage);
2✔
3163

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

3169
    mutex.lock();
2✔
3170
    test_stage = 2;
2✔
3171
    mutex.unlock();
2✔
3172

3173
    t.join();
2✔
3174
}
2✔
3175

3176

3177
TEST(Shared_LockFileOfWrongVersionThrows)
3178
{
2✔
3179
    SHARED_GROUP_TEST_PATH(path);
2✔
3180

3181
    DBOptions options;
2✔
3182
    options.encryption_key = crypt_key();
2✔
3183
    DBRef sg = DB::create(path, options);
2✔
3184

3185
    CHECK(File::exists(path));
2✔
3186
    CHECK(File::exists(path.get_lock_path()));
2✔
3187

3188
    std::mutex mutex;
2✔
3189
    size_t test_stage = 0;
2✔
3190

3191
    std::thread t([&]() {
2✔
3192
        CHECK(File::exists(path.get_lock_path()));
2✔
3193

3194
        File f;
2✔
3195
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3196
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3197

3198
        f.rw_lock_shared();
2✔
3199
        File::UnlockGuard ug(f);
2✔
3200

3201
        CHECK(f.is_attached());
2✔
3202
        char bad_version = 0;
2✔
3203
        f.write(6, &bad_version, 1);
2✔
3204
        f.sync();
2✔
3205

3206
        mutex.lock();
2✔
3207
        test_stage = 1;
2✔
3208
        mutex.unlock();
2✔
3209

3210
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3211
    });
2✔
3212

3213
    wait_for(1, mutex, test_stage);
2✔
3214
    sg->close();
2✔
3215

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

3220
    mutex.lock();
2✔
3221
    test_stage = 2;
2✔
3222
    mutex.unlock();
2✔
3223

3224
    t.join();
2✔
3225
}
2✔
3226

3227

3228
TEST(Shared_LockFileOfWrongMutexSizeThrows)
3229
{
2✔
3230
    SHARED_GROUP_TEST_PATH(path);
2✔
3231

3232
    DBOptions options;
2✔
3233
    options.encryption_key = crypt_key();
2✔
3234
    DBRef sg = DB::create(path, options);
2✔
3235

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

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

3242
    std::thread t([&]() {
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

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

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

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

3259
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3260
    });
2✔
3261

3262
    wait_for(1, mutex, test_stage);
2✔
3263

3264
    sg->close();
2✔
3265

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

3270
    mutex.lock();
2✔
3271
    test_stage = 2;
2✔
3272
    mutex.unlock();
2✔
3273

3274
    t.join();
2✔
3275
}
2✔
3276

3277

3278
TEST(Shared_LockFileOfWrongCondvarSizeThrows)
3279
{
2✔
3280
    SHARED_GROUP_TEST_PATH(path);
2✔
3281

3282
    DBOptions options;
2✔
3283
    options.encryption_key = crypt_key();
2✔
3284
    DBRef sg = DB::create(path, options);
2✔
3285

3286
    CHECK(File::exists(path));
2✔
3287
    CHECK(File::exists(path.get_lock_path()));
2✔
3288

3289
    std::mutex mutex;
2✔
3290
    size_t test_stage = 0;
2✔
3291

3292
    std::thread t([&]() {
2✔
3293
        File f;
2✔
3294
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3295
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3296
        f.rw_lock_shared();
2✔
3297
        File::UnlockGuard ug(f);
2✔
3298

3299
        CHECK(f.is_attached());
2✔
3300

3301
        char bad_condvar_size = sizeof(InterprocessCondVar::SharedPart) + 1;
2✔
3302
        f.write(2, &bad_condvar_size, 1);
2✔
3303
        f.sync();
2✔
3304

3305
        mutex.lock();
2✔
3306
        test_stage = 1;
2✔
3307
        mutex.unlock();
2✔
3308

3309
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3310
    });
2✔
3311

3312
    wait_for(1, mutex, test_stage);
2✔
3313
    sg->close();
2✔
3314

3315
    // we expect to throw if the condvar size is incorrect
3316
    CHECK_THROW(DB::create(path, options), IncompatibleLockFile);
2✔
3317
    CHECK(!sg->is_attached());
2✔
3318

3319
    mutex.lock();
2✔
3320
    test_stage = 2;
2✔
3321
    mutex.unlock();
2✔
3322

3323
    t.join();
2✔
3324
}
2✔
3325

3326
TEST(Shared_ConstObject)
3327
{
2✔
3328
    SHARED_GROUP_TEST_PATH(path);
2✔
3329
    DBRef sg_w = DB::create(path);
2✔
3330
    TransactionRef writer = sg_w->start_write();
2✔
3331
    TableRef t = writer->add_table("Foo");
2✔
3332
    auto c = t->add_column(type_Int, "integers");
2✔
3333
    t->create_object(ObjKey(47)).set(c, 5);
2✔
3334
    writer->commit();
2✔
3335

3336
    TransactionRef reader = sg_w->start_read();
2✔
3337
    ConstTableRef t2 = reader->get_table("Foo");
2✔
3338
    const Obj obj = t2->get_object(ObjKey(47));
2✔
3339
    CHECK_EQUAL(obj.get<int64_t>(c), 5);
2✔
3340
}
2✔
3341

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

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

3393
TEST(Shared_ConstList)
3394
{
2✔
3395
    SHARED_GROUP_TEST_PATH(path);
2✔
3396
    DBRef sg = get_test_db(path);
2✔
3397
    TransactionRef writer = sg->start_write();
2✔
3398

3399
    TableRef t = writer->add_table("Foo");
2✔
3400
    auto list_col = t->add_column_list(type_Int, "int_list");
2✔
3401
    t->create_object(ObjKey(47)).get_list<int64_t>(list_col).add(47);
2✔
3402
    writer->commit();
2✔
3403

3404
    TransactionRef reader = sg->start_read();
2✔
3405
    ConstTableRef t2 = reader->get_table("Foo");
2✔
3406
    const Obj obj = t2->get_object(ObjKey(47));
2✔
3407
    auto list1 = obj.get_list<int64_t>(list_col);
2✔
3408

3409
    CHECK_EQUAL(list1.get(0), 47);
2✔
3410
    CHECK_EQUAL(obj.get_listbase_ptr(list_col)->size(), 1);
2✔
3411
}
2✔
3412

3413
#ifdef LEGACY_TESTS
3414

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

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

3458
TEST(Shared_RollbackFirstTransaction)
3459
{
2✔
3460
    SHARED_GROUP_TEST_PATH(path);
2✔
3461
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3462
    DBRef db = DB::create(*hist_w, path);
2✔
3463
    auto wt = db->start_write();
2✔
3464

3465
    wt->add_table("table");
2✔
3466
    wt->rollback_and_continue_as_read();
2✔
3467
}
2✔
3468

3469
TEST(Shared_SimpleTransaction)
3470
{
2✔
3471
    SHARED_GROUP_TEST_PATH(path);
2✔
3472
    std::unique_ptr<Replication> hist_r(make_in_realm_history());
2✔
3473
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3474

3475
    {
2✔
3476
        DBRef db_w = DB::create(*hist_w, path);
2✔
3477
        auto wt = db_w->start_write();
2✔
3478
        wt->verify();
2✔
3479
        wt->commit();
2✔
3480
        wt = nullptr;
2✔
3481
        wt = db_w->start_write();
2✔
3482
        wt->verify();
2✔
3483
        wt->commit();
2✔
3484
    }
2✔
3485
    DBRef db_r = DB::create(*hist_r, path);
2✔
3486
    {
2✔
3487
        auto rt = db_r->start_read();
2✔
3488
        rt->verify();
2✔
3489
    }
2✔
3490
}
2✔
3491

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

3503
    wt = nullptr;
2✔
3504
    db_w->close();
2✔
3505
    DBOptions options(key);
2✔
3506
    options.no_create = true;
2✔
3507
    db_w = DB::create(path, options);
2✔
3508
    wt = db_w->start_write();
2✔
3509
    wt = nullptr;
2✔
3510
    db_w->close();
2✔
3511
}
2✔
3512

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

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

3550
    auto wt = db_w->start_write();
2✔
3551

3552
    wt->add_table("Table_0");
2✔
3553
    {
2✔
3554
        std::vector<ObjKey> keys;
2✔
3555
        wt->get_table(TableKey(0))->create_objects(254, keys);
2✔
3556
    }
2✔
3557
    wt->commit_and_continue_as_read();
2✔
3558

3559
    wt->promote_to_write();
2✔
3560
    try {
2✔
3561
        wt->remove_table(TableKey(0));
2✔
3562
    }
2✔
3563
    catch (const CrossTableLinkTarget&) {
2✔
3564
    }
×
3565
    // Table accessor recycled
3566
    wt->rollback_and_continue_as_read();
2✔
3567

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

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

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

3605
    auto rt = db_w->start_read();
2✔
3606
    CHECK_NOT(rt->get_table(TableKey(0))->get_object(ObjKey(0)).is_null(col));
2✔
3607
    CHECK(rt->get_table(TableKey(0))->get_object(ObjKey(54)).is_null(col));
2✔
3608
}
2✔
3609

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

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

3655
        auto obj = table_r->get_object(0);
2✔
3656
        // Here we will need to translate a ref
3657
        auto i = obj.get<Int>(col);
2✔
3658
        CHECK_EQUAL(i, 0);
2✔
3659
    }
2✔
3660
}
2✔
3661

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

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

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

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

3735
TEST_IF(Shared_LargeFile, TEST_DURATION > 0 && !REALM_ANDROID)
UNCOV
3736
{
×
3737
    SHARED_GROUP_TEST_PATH(path);
×
3738
    DBOptions options;
×
3739
    options.durability = DBOptions::Durability::MemOnly;
×
3740
    DBRef db = DB::create(path, options);
×
3741

3742
    auto tr = db->start_write();
×
3743

3744
    auto foo = tr->add_table("foo");
×
3745
    // Create more than 16 columns. The cluster array will always have a minimum
3746
    // size of 128, so if number of columns is lower than 17, then the array will
3747
    // always be able to hold 64 bit values.
3748
    for (size_t i = 0; i < 20; i++) {
×
3749
        std::string name = "Prop" + util::to_string(i);
×
3750
        foo->add_column(type_Int, name);
×
3751
    }
×
3752
    auto bar = tr->add_table("bar");
×
3753
    auto col_str = bar->add_column(type_String, "str");
×
3754

3755
    // Create enough objects to have a multi level cluster
3756
    for (size_t i = 0; i < 400; i++) {
×
3757
        foo->create_object();
×
3758
    }
×
3759

3760
    // Add a lot of data (nearly 2 Gb)
3761
    std::string string_1M(1024 * 1024, 'A');
×
3762
    for (size_t i = 0; i < 1900; i++) {
×
3763
        bar->create_object().set(col_str, string_1M);
×
3764
    }
×
3765
    tr->commit();
×
3766
    tr = db->start_write();
×
3767
    foo = tr->get_table("foo");
×
3768
    bar = tr->get_table("bar");
×
3769

3770
    std::vector<ObjKey> keys;
×
3771
    foo->create_objects(10000, keys);
×
3772

3773
    // By assigning 500 to the properties, we provoke a resize of the
3774
    // arrays. At some point an array will have a ref larger than
3775
    // 0x80000000 which will require a resize of the cluster array.
3776
    // If the cluster array does not have the capacity to accommodate
3777
    // this resize, then it must be reallocated, which will require
3778
    // the parent to be updated with the new ref. But as the array
3779
    // used as accessor to the cluster array in Obj::set does not
3780
    // have a parent this will cause a subsequent crash. To fix this
3781
    // we have ensured that the cluster array will always have the
3782
    // required capacity.
3783
    for (size_t i = 0; i < 10000; i++) {
×
3784
        auto obj = foo->get_object(keys[i]);
×
3785
        for (auto col : foo->get_column_keys()) {
×
3786
            obj.set(col, 500);
×
3787
        }
×
3788
    }
×
3789
    auto obj = foo->begin();
×
3790
    for (auto col : foo->get_column_keys()) {
×
3791
        obj->set(col, 500);
×
3792
    }
×
3793
}
×
3794

3795
TEST(Shared_EncryptionBug)
3796
{
2✔
3797
    SHARED_GROUP_TEST_PATH(path);
2✔
3798
    DBOptions options;
2✔
3799
    options.encryption_key = crypt_key(true);
2✔
3800
    {
2✔
3801
        DBRef db = DB::create(path, options);
2✔
3802
        {
2✔
3803
            WriteTransaction wt(db);
2✔
3804
            auto foo = wt.add_table("foo");
2✔
3805
            auto col_str = foo->add_column(type_String, "str");
2✔
3806
            std::string string_1M(1024 * 1024, 'A');
2✔
3807
            for (int i = 0; i < 64; i++) {
130✔
3808
                foo->create_object().set(col_str, string_1M);
128✔
3809
            }
128✔
3810
            wt.commit();
2✔
3811
        }
2✔
3812
        for (int i = 0; i < 2; i++) {
6✔
3813
            WriteTransaction wt(db);
4✔
3814
            auto foo = wt.get_table("foo");
4✔
3815
            auto col_str = foo->get_column_key("str");
4✔
3816
            foo->create_object().set(col_str, "boobar");
4✔
3817
            wt.commit();
4✔
3818
        }
4✔
3819
    }
2✔
3820

3821
    {
2✔
3822
        DBRef db = DB::create(path, options);
2✔
3823
        db->start_read()->verify();
2✔
3824
    }
2✔
3825
}
2✔
3826

3827
TEST(Shared_ManyColumns)
3828
{
2✔
3829
    // We had a bug where cluster array has to expand, but the new ref
3830
    // was not updated in the parent.
3831

3832
    SHARED_GROUP_TEST_PATH(path);
2✔
3833
    auto hist = make_in_realm_history();
2✔
3834
    DBRef db = DB::create(*hist, path);
2✔
3835

3836
    auto tr = db->start_write();
2✔
3837

3838
    auto foo = tr->add_table("foo");
2✔
3839
    // Create many columns. The cluster array will not be able to
3840
    // expand from 16 to 32 bits within the minimum allocation of 128 bytes.
3841
    for (size_t i = 0; i < 50; i++) {
102✔
3842
        std::string name = "Prop" + util::to_string(i);
100✔
3843
        foo->add_column(type_Int, name);
100✔
3844
    }
100✔
3845
    auto bar = tr->add_table("bar");
2✔
3846

3847
    tr->commit();
2✔
3848
    tr = db->start_write();
2✔
3849
    foo = tr->get_table("foo");
2✔
3850
    bar = tr->get_table("bar");
2✔
3851

3852
    std::vector<ObjKey> keys;
2✔
3853
    foo->create_objects(10000, keys);
2✔
3854

3855
    tr->commit_and_continue_as_read();
2✔
3856
    tr->promote_to_write();
2✔
3857

3858
    auto first = foo->begin();
2✔
3859
    for (auto col : foo->get_column_keys()) {
100✔
3860
        // 32 bit values will be inserted in the cluster array, so it needs expansion
3861
        first->set(col, 500);
100✔
3862
    }
100✔
3863

3864
    tr->commit_and_continue_as_read();
2✔
3865
    tr->verify();
2✔
3866

3867
    tr->promote_to_write();
2✔
3868
    foo->clear();
2✔
3869
    foo->create_object().set("Prop0", 500);
2✔
3870
}
2✔
3871

3872
TEST(Shared_MultipleDBInstances)
3873
{
2✔
3874
    SHARED_GROUP_TEST_PATH(path);
2✔
3875
    {
2✔
3876
        auto hist = make_in_realm_history();
2✔
3877
        DBRef db = DB::create(*hist, path);
2✔
3878
        auto tr = db->start_write();
2✔
3879
        auto t = tr->add_table("foo");
2✔
3880
        t->create_object();
2✔
3881
        t->add_column(type_Int, "value");
2✔
3882
        tr->commit();
2✔
3883
    }
2✔
3884

3885
    auto hist1 = make_in_realm_history();
2✔
3886
    DBRef db1 = DB::create(*hist1, path);
2✔
3887
    auto hist2 = make_in_realm_history();
2✔
3888
    DBRef db2 = DB::create(*hist2, path);
2✔
3889

3890
    auto tr = db1->start_write();
2✔
3891
    tr->commit();
2✔
3892
    // db1 now has m_youngest_live_version=3, db2 has m_youngest_live_version=2
3893

3894
    auto frozen = db2->start_frozen(); // version=3
2✔
3895
    auto table = frozen->get_table("foo");
2✔
3896

3897
    tr = db2->start_write();
2✔
3898
    // creates a new mapping and incorrectly marks the old one as being for
3899
    // version 2 rather than 3
3900
    tr->get_table("foo")->create_object();
2✔
3901
    // deletes the old mapping even though version 3 still needs it
3902
    tr->commit();
2✔
3903

3904
    // tries to use deleted mapping
3905
    CHECK_EQUAL(table->get_object(0).get<int64_t>("value"), 0);
2✔
3906
}
2✔
3907

3908
TEST(Shared_WriteCopy)
3909
{
2✔
3910
    SHARED_GROUP_TEST_PATH(path1);
2✔
3911
    SHARED_GROUP_TEST_PATH(path2);
2✔
3912
    SHARED_GROUP_TEST_PATH(path3);
2✔
3913

3914
    {
2✔
3915
        auto hist = make_in_realm_history();
2✔
3916
        DBRef db = DB::create(*hist, path1);
2✔
3917
        auto tr = db->start_write();
2✔
3918
        auto t = tr->add_table("foo");
2✔
3919
        t->create_object();
2✔
3920
        t->add_column(type_Int, "value");
2✔
3921
        tr->commit();
2✔
3922

3923
        db->write_copy(path2.c_str(), nullptr);
2✔
3924
        CHECK_THROW_ANY(db->write_copy(path2.c_str(), nullptr)); // Not allowed to overwrite
2✔
3925
    }
2✔
3926
    {
2✔
3927
        auto hist = make_in_realm_history();
2✔
3928
        DBRef db = DB::create(*hist, path2);
2✔
3929
        db->write_copy(path3.c_str(), nullptr);
2✔
3930
    }
2✔
3931
    auto hist = make_in_realm_history();
2✔
3932
    DBRef db = DB::create(*hist, path3);
2✔
3933
    CHECK_EQUAL(db->start_read()->get_table("foo")->size(), 1);
2✔
3934
}
2✔
3935

3936
TEST(Shared_CompareGroups)
3937
{
2✔
3938
    SHARED_GROUP_TEST_PATH(path1);
2✔
3939
    SHARED_GROUP_TEST_PATH(path2);
2✔
3940

3941
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
3942
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
3943

3944
    {
2✔
3945
        // Create schema in DB1
3946
        auto wt = db1->start_write();
2✔
3947
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
3948
        embedded->add_column(type_Float, "float");
2✔
3949
        // Embedded in embedded
3950
        embedded->add_column_dictionary(*embedded, "additional");
2✔
3951
        wt->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
3952

3953
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
3954
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
3955

3956
        baas->add_column(type_Bool, "bool");
2✔
3957
        baas->add_column_list(type_Int, "list");
2✔
3958
        baas->add_column_set(type_Int, "set");
2✔
3959
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
3960
        baas->add_column(*embedded, "embedded");
2✔
3961
        baas->add_column(type_Mixed, "any", true);
2✔
3962
        baas->add_column(*foos, "link");
2✔
3963

3964
        foos->add_column(type_String, "str");
2✔
3965
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
3966
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
3967
        foos->add_column_list(*baas, "link_list");
2✔
3968
        wt->commit();
2✔
3969
    }
2✔
3970
    {
2✔
3971
        // Create schema in DB2 - slightly different
3972
        auto wt = db2->start_write();
2✔
3973
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
3974

3975
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
3976
        embedded->add_column(type_Float, "float");
2✔
3977
        // Embedded in embedded
3978
        embedded->add_column_dictionary(*embedded, "additional");
2✔
3979

3980
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
3981

3982
        baas->add_column_set(type_Int, "set");
2✔
3983
        baas->add_column(type_Mixed, "any", true);
2✔
3984
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
3985
        baas->add_column(*embedded, "embedded");
2✔
3986
        baas->add_column(type_Bool, "bool");
2✔
3987
        baas->add_column(*foos, "link");
2✔
3988
        baas->add_column_list(type_Int, "list");
2✔
3989

3990
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
3991
        foos->add_column(type_String, "str");
2✔
3992
        foos->add_column_list(*baas, "link_list");
2✔
3993
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
3994

3995
        wt->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
3996
        wt->commit();
2✔
3997
    }
2✔
3998
    auto create_objects = [&](DBRef db) {
4✔
3999
        auto wt = db->start_write();
4✔
4000

4001
        auto foos = wt->get_table("class_Foo");
4✔
4002
        auto baas = wt->get_table("class_Baa");
4✔
4003

4004
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
4✔
4005
        auto children = foo.get_linklist("list_of_embedded");
4✔
4006
        children.create_and_insert_linked_object(0).set("float", 10.f);
4✔
4007
        children.create_and_insert_linked_object(1).set("float", 20.f);
4✔
4008

4009
        auto baa = baas->create_object_with_primary_key(999);
4✔
4010
        baa.set("link", foo.get_key());
4✔
4011
        baa.set("bool", true);
4✔
4012
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
4✔
4013
        obj.set("float", 42.f);
4✔
4014
        auto additional = obj.get_dictionary("additional");
4✔
4015
        additional.create_and_insert_linked_object("One").set("float", 1.f);
4✔
4016
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
4✔
4017
        additional.create_and_insert_linked_object("Three").set("float", 3.f);
4✔
4018

4019
        foo.get_linklist("link_list").add(baa.get_key());
4✔
4020

4021
        // Basic collections
4022
        auto list = baa.get_list<Int>("list");
4✔
4023
        list.add(1);
4✔
4024
        list.add(2);
4✔
4025
        list.add(3);
4✔
4026
        auto set = baa.get_set<Int>("set");
4✔
4027
        set.insert(4);
4✔
4028
        set.insert(5);
4✔
4029
        set.insert(6);
4✔
4030
        auto dict = baa.get_dictionary("dictionary");
4✔
4031
        dict.insert("key7", 7);
4✔
4032
        dict.insert("key8", 8);
4✔
4033
        dict.insert("key9", 9);
4✔
4034

4035
        wt->commit();
4✔
4036
    };
4✔
4037
    create_objects(db1);
2✔
4038
    create_objects(db2);
2✔
4039
    CHECK(*db1->start_read() == *db2->start_read());
2✔
4040
    {
2✔
4041
        auto wt = db2->start_write();
2✔
4042
        auto baas = wt->get_table("class_Baa");
2✔
4043
        auto obj = baas->get_object_with_primary_key(999);
2✔
4044
        auto embedded = obj.get_linked_object("embedded");
2✔
4045
        embedded.get_dictionary("additional").get_object("One").set("float", 555.f);
2✔
4046
        wt->commit();
2✔
4047
    }
2✔
4048
    CHECK_NOT(*db1->start_read() == *db2->start_read());
2✔
4049
}
2✔
4050

4051
TEST(Shared_CopyReplication)
4052
{
2✔
4053
    SHARED_GROUP_TEST_PATH(path1);
2✔
4054
    SHARED_GROUP_TEST_PATH(path2);
2✔
4055

4056
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4057
    auto wt = db2->start_write();
2✔
4058

4059
    _impl::CopyReplication repl(wt);
2✔
4060
    DBRef db1 = DB::create(repl, path1);
2✔
4061
    auto tr = db1->start_write();
2✔
4062

4063
    // First create the local realm
4064
    {
2✔
4065
        // Create schema
4066
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
4067
        embedded->add_column(type_Float, "float");
2✔
4068
        // Embedded in embedded
4069
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4070
        tr->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4071

4072
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4073
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4074

4075
        baas->add_column(type_Bool, "bool");
2✔
4076
        baas->add_column_list(type_Int, "list");
2✔
4077
        baas->add_column_set(type_Int, "set");
2✔
4078
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4079
        baas->add_column(*embedded, "embedded");
2✔
4080
        baas->add_column(type_Mixed, "any", true);
2✔
4081
        baas->add_column(*foos, "link");
2✔
4082

4083
        foos->add_column(type_String, "str");
2✔
4084
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4085
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4086
        foos->add_column_list(*baas, "link_list");
2✔
4087

4088

4089
        /* Create local objects */
4090
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
2✔
4091
        auto children = foo.get_linklist("list_of_embedded");
2✔
4092
        children.create_and_insert_linked_object(0).set("float", 10.f);
2✔
4093
        children.create_and_insert_linked_object(1).set("float", 20.f);
2✔
4094

4095
        auto baa = baas->create_object_with_primary_key(999);
2✔
4096
        baa.set("link", foo.get_key());
2✔
4097
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4098
        obj.set("float", 42.f);
2✔
4099
        auto additional = obj.get_dictionary("additional");
2✔
4100

4101
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4102

4103
        // Basic collections
4104
        auto list = baa.get_list<Int>("list");
2✔
4105
        list.add(1);
2✔
4106
        list.add(2);
2✔
4107
        list.add(3);
2✔
4108
        auto set = baa.get_set<Int>("set");
2✔
4109
        set.insert(4);
2✔
4110
        set.insert(5);
2✔
4111
        set.insert(6);
2✔
4112
        auto dict = baa.get_dictionary("dictionary");
2✔
4113
        dict.insert("key7", 7);
2✔
4114
        dict.insert("key8", 8);
2✔
4115
        dict.insert("key9", 9);
2✔
4116

4117
        additional.create_and_insert_linked_object("One").set("float", 1.f);
2✔
4118
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
2✔
4119
        auto embedded_obj = additional.create_and_insert_linked_object("Three");
2✔
4120

4121
        auto baa1 = baas->create_object_with_primary_key(666).set("link", foo.get_key());
2✔
4122
        obj = baa1.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4123
        additional = obj.get_dictionary("additional");
2✔
4124
        additional.create_and_insert_linked_object("Item").set("float", 100.f);
2✔
4125
        obj.set("float", 35.f);
2✔
4126
        foo = foos->create_object_with_primary_key("456");
2✔
4127
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4128
        any_list.add(Mixed(baa1.get_link()));
2✔
4129
        any_list.add(Mixed(foo.get_link()));
2✔
4130
        foos->create_object_with_primary_key("789");
2✔
4131
        baa1.set("any", Mixed(foo.get_link()));
2✔
4132
        baa.set("bool", true);
2✔
4133
        embedded_obj.set("float", 3.f);
2✔
4134
    }
2✔
4135
    tr->commit();
2✔
4136

4137
    wt->commit_and_continue_as_read();
2✔
4138
    auto rt = db1->start_read();
2✔
4139
    CHECK(*rt == *wt);
2✔
4140
}
2✔
4141

4142
TEST(Shared_WriteTo)
4143
{
2✔
4144
    SHARED_GROUP_TEST_PATH(path1);
2✔
4145
    SHARED_GROUP_TEST_PATH(path2);
2✔
4146

4147
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
4148
    auto tr = db1->start_write();
2✔
4149

4150
    // First create the local realm
4151
    {
2✔
4152
        // Create schema
4153
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
4154
        embedded->add_column(type_Float, "float");
2✔
4155
        // Embedded in embedded
4156
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4157
        tr->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4158

4159
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4160
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4161

4162
        baas->add_column(type_Bool, "bool");
2✔
4163
        baas->add_column_list(type_Int, "list");
2✔
4164
        baas->add_column_set(type_Int, "set");
2✔
4165
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4166
        baas->add_column(*embedded, "embedded");
2✔
4167
        baas->add_column(type_Mixed, "any", true);
2✔
4168
        baas->add_column(*foos, "link");
2✔
4169

4170
        // collections in mixed
4171
        baas->add_column(type_Mixed, "mixed_nested_list", true);
2✔
4172
        baas->add_column(type_Mixed, "mixed_nested_dictionary", true);
2✔
4173

4174
        auto col_str = foos->add_column(type_String, "str");
2✔
4175
        foos->add_search_index(col_str);
2✔
4176
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4177
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4178
        foos->add_column_list(*baas, "link_list");
2✔
4179

4180

4181
        /* Create local objects */
4182
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
2✔
4183
        auto children = foo.get_linklist("list_of_embedded");
2✔
4184
        children.create_and_insert_linked_object(0).set("float", 10.f);
2✔
4185
        children.create_and_insert_linked_object(1).set("float", 20.f);
2✔
4186

4187
        auto baa = baas->create_object_with_primary_key(999);
2✔
4188
        baa.set("link", foo.get_key());
2✔
4189
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4190
        obj.set("float", 42.f);
2✔
4191
        auto additional = obj.get_dictionary("additional");
2✔
4192
        additional.create_and_insert_linked_object("One").set("float", 1.f);
2✔
4193
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
2✔
4194
        additional.create_and_insert_linked_object("Three").set("float", 3.f);
2✔
4195

4196
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4197

4198
        // Basic collections
4199
        auto list = baa.get_list<Int>("list");
2✔
4200
        list.add(1);
2✔
4201
        list.add(2);
2✔
4202
        list.add(3);
2✔
4203
        auto set = baa.get_set<Int>("set");
2✔
4204
        set.insert(4);
2✔
4205
        set.insert(5);
2✔
4206
        set.insert(6);
2✔
4207
        auto dict = baa.get_dictionary("dictionary");
2✔
4208
        dict.insert("key7", 7);
2✔
4209
        dict.insert("key8", 8);
2✔
4210
        dict.insert("key9", 9);
2✔
4211

4212
        // nested collections
4213
        // nested list
4214
        auto col_key_mixed_list = baas->get_column_key("mixed_nested_list");
2✔
4215
        baa.set_collection(col_key_mixed_list, CollectionType::List);
2✔
4216
        auto any_nested_list = baa.get_collection_ptr(col_key_mixed_list);
2✔
4217
        any_nested_list->insert_collection(0, CollectionType::List);
2✔
4218
        any_nested_list->insert_collection(1, CollectionType::Dictionary);
2✔
4219
        auto nested_list1 = any_nested_list->get_list(0);
2✔
4220
        nested_list1->add(1);
2✔
4221
        nested_list1->add(2);
2✔
4222
        nested_list1->add(3);
2✔
4223
        auto nested_dict1 = any_nested_list->get_dictionary(1);
2✔
4224
        nested_dict1->insert("test", 10);
2✔
4225
        nested_dict1->insert("test", "test");
2✔
4226

4227
        // nested dictionary
4228
        auto col_key_mixed_dict = baas->get_column_key("mixed_nested_dictionary");
2✔
4229
        baa.set_collection(col_key_mixed_dict, CollectionType::Dictionary);
2✔
4230
        auto any_nested_dict = baa.get_collection_ptr(col_key_mixed_dict);
2✔
4231
        any_nested_dict->insert_collection("List", CollectionType::List);
2✔
4232
        any_nested_dict->insert_collection("Dict", CollectionType::Dictionary);
2✔
4233
        auto nested_list2 = any_nested_dict->get_list("List");
2✔
4234
        nested_list2->add(1);
2✔
4235
        nested_list2->add(2);
2✔
4236
        nested_list2->add(3);
2✔
4237
        auto nested_dict2 = any_nested_dict->get_dictionary("Dict");
2✔
4238
        nested_dict2->insert("test", 10);
2✔
4239
        nested_dict2->insert("test", "test");
2✔
4240

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

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

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

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

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

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

4285
    // Copy local object over
4286
    auto dest = db2->start_write();
2✔
4287
    tr->copy_to(dest);
2✔
4288
    dest->verify();
2✔
4289
    dest->commit_and_continue_as_read();
2✔
4290
    // dest->to_json(std::cout);
4291

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

4302
TEST(Shared_WriteToFail)
4303
{
2✔
4304
    SHARED_GROUP_TEST_PATH(path1);
2✔
4305
    SHARED_GROUP_TEST_PATH(path2);
2✔
4306

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

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

4314
        foos->add_column(type_String, "classification");
2✔
4315
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4316

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

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

4337
    auto dest = db2->start_write();
2✔
4338
    std::string message;
2✔
4339

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

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

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

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

4360
    CHECK(*tr == *dest);
2✔
4361
}
2✔
4362

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

4370
    struct Lock {
2✔
4371
        std::unique_ptr<InterprocessMutex> mutex;
2✔
4372
        std::unique_ptr<InterprocessMutex::SharedPart> sp;
2✔
4373

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

4381
        Lock(Lock&&) = default;
2✔
4382
        Lock& operator=(Lock&&) = default;
2✔
4383
    };
2✔
4384

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

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

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

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

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

4411
                exit(0);
×
4412
            }
×
4413
        }
200✔
4414

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

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

4425
TEST(Shared_ClearOnError_ReopenValidFile)
4426
{
2✔
4427
    SHARED_GROUP_TEST_PATH(path);
2✔
4428
    DBOptions options(crypt_key());
2✔
4429
    options.clear_on_invalid_file = true;
2✔
4430

4431
    {
2✔
4432
        auto db = DB::create(path, options);
2✔
4433
        WriteTransaction wt(db);
2✔
4434
        wt.add_table("table");
2✔
4435
        wt.commit();
2✔
4436
    }
2✔
4437

4438
    {
2✔
4439
        // The file should not have been cleared
4440
        auto db = DB::create(path, options);
2✔
4441
        CHECK(db->start_read()->get_table("table"));
2✔
4442
    }
2✔
4443
}
2✔
4444

4445
TEST(Shared_ClearOnError_ResetInvalidFile)
4446
{
2✔
4447
    SHARED_GROUP_TEST_PATH(path);
2✔
4448
    DBOptions options;
2✔
4449
    options.clear_on_invalid_file = true;
2✔
4450

4451
    {
2✔
4452
        auto db = DB::create(path, options);
2✔
4453
        WriteTransaction wt(db);
2✔
4454
        wt.add_table("table");
2✔
4455
        wt.commit();
2✔
4456
    }
2✔
4457

4458
    {
2✔
4459
        // Overwrite the first byte of the mnemonic so that this isn't a valid file
4460
        util::File file(path, File::mode_Update);
2✔
4461
        file.write(8, "\0", 1);
2✔
4462
    }
2✔
4463

4464
    {
2✔
4465
        // The file should have been cleared
4466
        auto db = DB::create(path, options);
2✔
4467
        CHECK_NOT(db->start_read()->get_table("table"));
2✔
4468
    }
2✔
4469
}
2✔
4470

4471
#if REALM_ENABLE_ENCRYPTION
4472
TEST(Shared_ClearOnError_ChangeEncryptionKey)
4473
{
2✔
4474
    auto key_1 = "1234567890123456789012345678901123456789012345678901234567890123";
2✔
4475
    auto key_2 = "2234567890123456789012345678901123456789012345678901234567890123";
2✔
4476

4477
    SHARED_GROUP_TEST_PATH(path);
2✔
4478
    DBOptions options;
2✔
4479
    options.clear_on_invalid_file = true;
2✔
4480
    options.encryption_key = key_1;
2✔
4481

4482
    {
2✔
4483
        auto db = DB::create(path, options);
2✔
4484
        WriteTransaction wt(db);
2✔
4485
        wt.add_table("table");
2✔
4486
        wt.commit();
2✔
4487
    }
2✔
4488

4489
    { // change from first key to second
2✔
4490
        options.encryption_key = key_2;
2✔
4491
        auto db = DB::create(path, options);
2✔
4492
        WriteTransaction wt(db);
2✔
4493
        CHECK_NOT(wt.get_table("table"));
2✔
4494
        wt.add_table("table 2");
2✔
4495
        wt.commit();
2✔
4496
    }
2✔
4497

4498
    { // change from encrypted to unencrypted
2✔
4499
        options.encryption_key = nullptr;
2✔
4500
        auto db = DB::create(path, options);
2✔
4501
        WriteTransaction wt(db);
2✔
4502
        CHECK_NOT(wt.get_table("table 2"));
2✔
4503
        wt.add_table("table 3");
2✔
4504
        wt.commit();
2✔
4505
    }
2✔
4506

4507
    { // change from unencrypted to encrypted
2✔
4508
        options.encryption_key = key_1;
2✔
4509
        auto db = DB::create(path, options);
2✔
4510
        WriteTransaction wt(db);
2✔
4511
        CHECK_NOT(wt.get_table("table 3"));
2✔
4512
        wt.add_table("table 4");
2✔
4513
        wt.commit();
2✔
4514
    }
2✔
4515

4516
    { // sanity check that reopening encrypted with the same key works
2✔
4517
        auto db = DB::create(path, options);
2✔
4518
        CHECK(db->start_read()->get_table("table 4"));
2✔
4519
    }
2✔
4520
}
2✔
4521

4522
TEST(Shared_ClearOnError_CannotClearWhileFileIsOpen)
4523
{
2✔
4524
    SHARED_GROUP_TEST_PATH(path);
2✔
4525
    DBOptions options;
2✔
4526
    options.clear_on_invalid_file = true;
2✔
4527
    auto db = DB::create(make_in_realm_history(), path, options);
2✔
4528
    options.encryption_key = crypt_key(true);
2✔
4529
    CHECK_THROW(DB::create(make_in_realm_history(), path, options), InvalidDatabase);
2✔
4530
}
2✔
4531
#endif
4532

4533
#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

© 2025 Coveralls, Inc