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

realm / realm-core / 2386

04 Jun 2024 11:40PM UTC coverage: 90.844% (-0.003%) from 90.847%
2386

push

Evergreen

web-flow
Merge pull request #7734 from realm/tg/create-object-repl

RCORE-2152 Don't emit transaction log instructions for mutations on newly-created objects

101840 of 180236 branches covered (56.5%)

358 of 362 new or added lines in 6 files covered. (98.9%)

98 existing lines in 19 files now uncovered.

214786 of 236434 relevant lines covered (90.84%)

5684022.63 hits per line

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

96.74
/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) {
9✔
193
            // std::cerr << "       - " << getpid() << std::endl;
194
            tr->promote_to_write();
7✔
195
            auto t1 = tr->get_table("test");
7✔
196
            ColKeys _cols = t1->get_column_keys();
7✔
197
            std::vector<ColKey> cols;
7✔
198
            for (auto e : _cols)
7✔
199
                cols.push_back(e);
35✔
200
            Obj obj = t1->get_object(ObjKey(id));
7✔
201
            done = obj.get<Bool>(cols[2]);
7✔
202
            if (i & 1) {
7✔
203
                obj.add_int(cols[0], 1);
3✔
204
            }
3✔
205
            std::this_thread::yield(); // increase chance of signal arriving in the middle of a transaction
7✔
206
            tr->commit_and_continue_as_read();
7✔
207
        }
7✔
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) {
1,708✔
449
                std::this_thread::yield();
1,706✔
450
                ReadTransaction rt(sg);
1,706✔
451
                auto t1 = rt.get_table("test");
1,706✔
452
                const Obj obj = t1->get_object(ObjKey(41));
1,706✔
453
                waiting = obj.get<Int>(cols[0]) == 0;
1,706✔
454
                // std::cerr << t1->get_int(0, 41) << std::endl;
455
            }
1,706✔
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✔
UNCOV
531
                return true;
×
UNCOV
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("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
{
103✔
1865
    for (size_t i = 0; i < len; ++i)
927✔
1866
        res[i] = char(int('a') + random.draw_int_mod(10));
824✔
1867
}
103✔
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();
97✔
1892
            TableRef table = tr->get_table("users");
97✔
1893
            if (table->size() > 0) {
97✔
1894
                size_t del = random.draw_int_mod(table->size());
81✔
1895
                // cerr << "-" << del << ": " << table->get_string(0, del) << std::endl;
1896
                table->remove_object(keys[del]);
81✔
1897
                keys.erase(keys.begin() + del);
81✔
1898
                table->verify();
81✔
1899
            }
81✔
1900
            tr->commit();
97✔
1901
        }
97✔
1902
        else {
103✔
1903
            // add new user
1904
            auto tr = db->start_write();
103✔
1905
            TableRef table = tr->get_table("users");
103✔
1906
            char txt[100];
103✔
1907
            rand_str(random, txt, 8);
103✔
1908
            txt[8] = 0;
103✔
1909
            // cerr << "+" << txt << std::endl;
1910
            auto key = table->create_object().set_all(txt).get_key();
103✔
1911
            keys.push_back(key);
103✔
1912
            table->verify();
103✔
1913
            tr->commit();
103✔
1914
        }
103✔
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
        f.seek(5000);                       // somewhere on the first data page
2✔
2271
        constexpr std::string_view data = "an external corruption in the encrypted page";
2✔
2272
        f.write(data.data(), data.size());
2✔
2273
        f.sync();
2✔
2274
        f.close();
2✔
2275
    }
2✔
2276
    {
2✔
2277
        bool did_throw = false;
2✔
2278
        try {
2✔
2279
            DBRef sg = DB::create(path, DBOptions(crypt_key(true)));
2✔
2280
            WriteTransaction wt(sg);
2✔
2281
            TableRef table = wt.get_group().get_table("foo");
2✔
2282
            CHECK_EQUAL(table->size(), num_objects);
2✔
2283
        }
2✔
2284
        catch (const InvalidDatabase& e) {
2✔
2285
            CHECK_STRING_CONTAINS(e.what(), "decryption failed");
2✔
2286
            did_throw = true;
2✔
2287
        }
2✔
2288
        CHECK(did_throw);
2✔
2289
    }
2✔
2290
}
2✔
2291

2292
#endif // REALM_ENABLE_ENCRYPTION
2293

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

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

2332

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2541

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

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

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

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

2560

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

2572

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

2582

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

2621

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

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

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

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

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

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

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

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

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

2723
            std::string instr;
6✔
2724

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

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

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

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

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

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

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

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

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

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

2817

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

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

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

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

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

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

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

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

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

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

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

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

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

3010

3011
namespace {
3012

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

3024
} // end anonymous namespace
3025

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

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

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

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

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

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

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

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

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

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

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

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

3069

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

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

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

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

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

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

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

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

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

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

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

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

3113

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

3120
    SHARED_GROUP_TEST_PATH(path);
2✔
3121

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3177

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

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

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

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

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

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

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

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

3208
        mutex.lock();
2✔
3209
        test_stage = 1;
2✔
3210
        mutex.unlock();
2✔
3211

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

3215
    wait_for(1, mutex, test_stage);
2✔
3216
    sg->close();
2✔
3217

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

3222
    mutex.lock();
2✔
3223
    test_stage = 2;
2✔
3224
    mutex.unlock();
2✔
3225

3226
    t.join();
2✔
3227
}
2✔
3228

3229

3230
TEST(Shared_LockFileOfWrongMutexSizeThrows)
3231
{
2✔
3232
    SHARED_GROUP_TEST_PATH(path);
2✔
3233

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

3238
    CHECK(File::exists(path));
2✔
3239
    CHECK(File::exists(path.get_lock_path()));
2✔
3240

3241
    std::mutex mutex;
2✔
3242
    size_t test_stage = 0;
2✔
3243

3244
    std::thread t([&]() {
2✔
3245
        File f;
2✔
3246
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3247
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3248
        f.rw_lock_shared();
2✔
3249
        File::UnlockGuard ug(f);
2✔
3250

3251
        CHECK(f.is_attached());
2✔
3252

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

3258
        mutex.lock();
2✔
3259
        test_stage = 1;
2✔
3260
        mutex.unlock();
2✔
3261

3262
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3263
    });
2✔
3264

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

3267
    sg->close();
2✔
3268

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

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

3277
    t.join();
2✔
3278
}
2✔
3279

3280

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

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

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

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

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

3302
        CHECK(f.is_attached());
2✔
3303

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

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

3313
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3314
    });
2✔
3315

3316
    wait_for(1, mutex, test_stage);
2✔
3317
    sg->close();
2✔
3318

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

3323
    mutex.lock();
2✔
3324
    test_stage = 2;
2✔
3325
    mutex.unlock();
2✔
3326

3327
    t.join();
2✔
3328
}
2✔
3329

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

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

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

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

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

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

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

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

3417
#ifdef LEGACY_TESTS
3418

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

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

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

3469
    wt->add_table("table");
2✔
3470
    wt->rollback_and_continue_as_read();
2✔
3471
}
2✔
3472

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4120

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

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

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

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

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

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

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

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

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

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

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

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

4202
        // collections in mixed
4203
        baas->add_column(type_Mixed, "mixed_nested_list", true);
2✔
4204
        baas->add_column(type_Mixed, "mixed_nested_dictionary", true);
2✔
4205

4206
        auto col_str = foos->add_column(type_String, "str");
2✔
4207
        foos->add_search_index(col_str);
2✔
4208
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4209
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4210
        foos->add_column_list(*baas, "link_list");
2✔
4211

4212

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

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

4228
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4229

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

4244
        // nested collections
4245
        // nested list
4246
        auto col_key_mixed_list = baas->get_column_key("mixed_nested_list");
2✔
4247
        baa.set_collection(col_key_mixed_list, CollectionType::List);
2✔
4248
        auto any_nested_list = baa.get_collection_ptr(col_key_mixed_list);
2✔
4249
        any_nested_list->insert_collection(0, CollectionType::List);
2✔
4250
        any_nested_list->insert_collection(1, CollectionType::Dictionary);
2✔
4251
        auto nested_list1 = any_nested_list->get_list(0);
2✔
4252
        nested_list1->add(1);
2✔
4253
        nested_list1->add(2);
2✔
4254
        nested_list1->add(3);
2✔
4255
        auto nested_dict1 = any_nested_list->get_dictionary(1);
2✔
4256
        nested_dict1->insert("test", 10);
2✔
4257
        nested_dict1->insert("test", "test");
2✔
4258

4259
        // nested dictionary
4260
        auto col_key_mixed_dict = baas->get_column_key("mixed_nested_dictionary");
2✔
4261
        baa.set_collection(col_key_mixed_dict, CollectionType::Dictionary);
2✔
4262
        auto any_nested_dict = baa.get_collection_ptr(col_key_mixed_dict);
2✔
4263
        any_nested_dict->insert_collection("List", CollectionType::List);
2✔
4264
        any_nested_dict->insert_collection("Dict", CollectionType::Dictionary);
2✔
4265
        auto nested_list2 = any_nested_dict->get_list("List");
2✔
4266
        nested_list2->add(1);
2✔
4267
        nested_list2->add(2);
2✔
4268
        nested_list2->add(3);
2✔
4269
        auto nested_dict2 = any_nested_dict->get_dictionary("Dict");
2✔
4270
        nested_dict2->insert("test", 10);
2✔
4271
        nested_dict2->insert("test", "test");
2✔
4272

4273
        auto baa1 = baas->create_object_with_primary_key(666).set("link", foo.get_key());
2✔
4274
        obj = baa1.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4275
        additional = obj.get_dictionary("additional");
2✔
4276
        additional.create_and_insert_linked_object("Item").set("float", 100.f);
2✔
4277
        obj.set("float", 35.f);
2✔
4278
        foo = foos->create_object_with_primary_key("456");
2✔
4279
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4280
        any_list.add(Mixed(baa1.get_link()));
2✔
4281
        any_list.add(Mixed(foo.get_link()));
2✔
4282
        foos->create_object_with_primary_key("789");
2✔
4283
        baa1.set("any", Mixed(foo.get_link()));
2✔
4284
        baa.set("bool", true);
2✔
4285
    }
2✔
4286
    tr->commit_and_continue_as_read();
2✔
4287
    // tr->to_json(std::cout);
4288

4289
    // Create remote db
4290
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4291
    {
2✔
4292
        auto wt = db2->start_write();
2✔
4293

4294
        // Create schema - where some columns are missing. This will cause the
4295
        // ColKeys to be different
4296
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
4297
        embedded->add_column(type_Float, "float");
2✔
4298
        // Embedded in embedded
4299
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4300

4301
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4302
        baas->add_column(*embedded, "embedded");
2✔
4303

4304
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4305
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4306

4307
        baas->create_object_with_primary_key(333);
2✔
4308
        auto baa = baas->create_object_with_primary_key(666);
2✔
4309
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4310
        obj.set("float", 99.f);
2✔
4311
        auto additional = obj.get_dictionary("additional");
2✔
4312
        additional.create_and_insert_linked_object("Item").set("float", 200.f);
2✔
4313
        foos->create_object_with_primary_key("789").get_list<Mixed>("list_of_any").add(Mixed(baa.get_link()));
2✔
4314
        wt->commit();
2✔
4315
    }
2✔
4316

4317
    // Copy local object over
4318
    auto dest = db2->start_write();
2✔
4319
    tr->copy_to(dest);
2✔
4320
    dest->verify();
2✔
4321
    dest->commit_and_continue_as_read();
2✔
4322
    // dest->to_json(std::cout);
4323

4324
    // The difference between the two realms should now be that the remote db has an
4325
    // extra baa object with pk 333.
4326
    CHECK_NOT(*tr == *dest);
2✔
4327
    tr->promote_to_write();
2✔
4328
    // So if we add this to the local realm, they should match
4329
    tr->get_table("class_Baa")->create_object_with_primary_key(333);
2✔
4330
    tr->commit_and_continue_as_read();
2✔
4331
    CHECK(*tr == *dest);
2✔
4332
}
2✔
4333

4334
TEST(Shared_WriteToFail)
4335
{
2✔
4336
    SHARED_GROUP_TEST_PATH(path1);
2✔
4337
    SHARED_GROUP_TEST_PATH(path2);
2✔
4338

4339
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
4340
    auto tr = db1->start_write();
2✔
4341

4342
    // First create the local realm
4343
    {
2✔
4344
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4345

4346
        foos->add_column(type_String, "classification");
2✔
4347
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4348

4349
        /* Create local objects */
4350
        auto foo = foos->create_object_with_primary_key("anders.andk@andeby.io").set("classification", "Duck");
2✔
4351
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4352
        any_list.add(Mixed(17));
2✔
4353
        any_list.add(Mixed("Hello"));
2✔
4354
    }
2✔
4355
    tr->commit_and_continue_as_read();
2✔
4356
    // tr->to_json(std::cout);
4357

4358
    ColKey col_fail;
2✔
4359
    // Create remote db
4360
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4361
    {
2✔
4362
        auto wt = db2->start_write();
2✔
4363
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4364
        col_fail = foos->add_column(type_Int, "classification");
2✔
4365
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4366
        wt->commit();
2✔
4367
    }
2✔
4368

4369
    auto dest = db2->start_write();
2✔
4370
    std::string message;
2✔
4371

4372
    CHECK_THROW_ANY_GET_MESSAGE(tr->copy_to(dest), message);
2✔
4373
    CHECK_EQUAL(message, "Incompatible property: class_Foo::classification");
2✔
4374

4375
    auto foos = dest->get_table("class_Foo");
2✔
4376
    foos->remove_column(col_fail);
2✔
4377
    col_fail = foos->add_column(type_String, "classification", true); // Now nullable
2✔
4378
    dest->commit();
2✔
4379
    dest = db2->start_write();
2✔
4380

4381
    CHECK_THROW_ANY_GET_MESSAGE(tr->copy_to(dest), message);
2✔
4382
    CHECK_EQUAL(message, "Incompatible property: class_Foo::classification");
2✔
4383

4384
    foos = dest->get_table("class_Foo");
2✔
4385
    foos->remove_column(col_fail);
2✔
4386
    col_fail = foos->add_column(type_String, "classification");
2✔
4387
    dest->commit();
2✔
4388
    dest = db2->start_write();
2✔
4389
    tr->copy_to(dest);
2✔
4390
    dest->commit_and_continue_as_read();
2✔
4391

4392
    CHECK(*tr == *dest);
2✔
4393
}
2✔
4394

4395
NONCONCURRENT_TEST_IF(Shared_LockFileConcurrentInit, testing_supports_spawn_process)
4396
{
2✔
4397
    auto path = realm::test_util::get_test_path(test_context.get_test_name(), ".test-dir");
2✔
4398
    test_util::TestDirGuard test_dir(path, false);
2✔
4399
    test_dir.do_remove = SpawnedProcess::is_parent();
2✔
4400
    auto lock_prefix = std::string(path) + "/lock";
2✔
4401

4402
    struct Lock {
2✔
4403
        std::unique_ptr<InterprocessMutex> mutex;
2✔
4404
        std::unique_ptr<InterprocessMutex::SharedPart> sp;
2✔
4405

4406
        Lock(const std::string& name, const std::string& lock_prefix_path)
2✔
4407
            : mutex(std::make_unique<InterprocessMutex>())
2✔
4408
            , sp(std::make_unique<InterprocessMutex::SharedPart>())
2✔
4409
        {
2✔
4410
            mutex->set_shared_part(*sp, lock_prefix_path, name);
×
4411
        }
×
4412

4413
        Lock(Lock&&) = default;
2✔
4414
        Lock& operator=(Lock&&) = default;
2✔
4415
    };
2✔
4416

4417
    for (size_t i = 0; i < 10; ++i) {
22✔
4418
        std::vector<std::unique_ptr<SpawnedProcess>> spawned;
20✔
4419

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

4425
            if (spawned.back()->is_child()) {
200✔
4426
                std::vector<Lock> locks;
×
4427

4428
                // mimic the same impl detail as in DB and hope it'd trigger some assertions
4429
                for (auto tag : {"write", "control", "versions"}) {
×
4430
                    locks.emplace_back(tag, lock_prefix);
×
4431
                    CHECK(locks.back().mutex->is_valid());
×
4432
                }
×
4433

4434
                // if somehow initialization is scrambled or there is an issues with
4435
                // underlying files then it should hang here
4436
                for (int k = 0; k < 3; ++k) {
×
4437
                    for (auto&& lock : locks)
×
4438
                        lock.mutex->lock();
×
4439
                    for (auto&& lock : locks)
×
4440
                        lock.mutex->unlock();
×
4441
                }
×
4442

4443
                exit(0);
×
4444
            }
×
4445
        }
200✔
4446

4447
        if (SpawnedProcess::is_parent()) {
20✔
4448
            for (auto&& process : spawned)
20✔
4449
                process->wait_for_child_to_finish();
200✔
4450

4451
            // start everytime with no lock files for mutexes
4452
            test_dir.clean_dir();
20✔
4453
        }
20✔
4454
    }
20✔
4455
}
2✔
4456

4457
TEST(Shared_ClearOnError_ReopenValidFile)
4458
{
2✔
4459
    SHARED_GROUP_TEST_PATH(path);
2✔
4460
    DBOptions options(crypt_key());
2✔
4461
    options.clear_on_invalid_file = true;
2✔
4462

4463
    {
2✔
4464
        auto db = DB::create(path, options);
2✔
4465
        WriteTransaction wt(db);
2✔
4466
        wt.add_table("table");
2✔
4467
        wt.commit();
2✔
4468
    }
2✔
4469

4470
    {
2✔
4471
        // The file should not have been cleared
4472
        auto db = DB::create(path, options);
2✔
4473
        CHECK(db->start_read()->get_table("table"));
2✔
4474
    }
2✔
4475
}
2✔
4476

4477
TEST(Shared_ClearOnError_ResetInvalidFile)
4478
{
2✔
4479
    SHARED_GROUP_TEST_PATH(path);
2✔
4480
    DBOptions options;
2✔
4481
    options.clear_on_invalid_file = true;
2✔
4482

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

4490
    {
2✔
4491
        // Overwrite the first byte of the mnemonic so that this isn't a valid file
4492
        util::File file(path, File::mode_Update);
2✔
4493
        file.seek(8);
2✔
4494
        file.write("\0", 1);
2✔
4495
    }
2✔
4496

4497
    {
2✔
4498
        // The file should have been cleared
4499
        auto db = DB::create(path, options);
2✔
4500
        CHECK_NOT(db->start_read()->get_table("table"));
2✔
4501
    }
2✔
4502
}
2✔
4503

4504
#if REALM_ENABLE_ENCRYPTION
4505
TEST(Shared_ClearOnError_ChangeEncryptionKey)
4506
{
2✔
4507
    auto key_1 = "1234567890123456789012345678901123456789012345678901234567890123";
2✔
4508
    auto key_2 = "2234567890123456789012345678901123456789012345678901234567890123";
2✔
4509

4510
    SHARED_GROUP_TEST_PATH(path);
2✔
4511
    DBOptions options;
2✔
4512
    options.clear_on_invalid_file = true;
2✔
4513
    options.encryption_key = key_1;
2✔
4514

4515
    {
2✔
4516
        auto db = DB::create(path, options);
2✔
4517
        WriteTransaction wt(db);
2✔
4518
        wt.add_table("table");
2✔
4519
        wt.commit();
2✔
4520
    }
2✔
4521

4522
    { // change from first key to second
2✔
4523
        options.encryption_key = key_2;
2✔
4524
        auto db = DB::create(path, options);
2✔
4525
        WriteTransaction wt(db);
2✔
4526
        CHECK_NOT(wt.get_table("table"));
2✔
4527
        wt.add_table("table 2");
2✔
4528
        wt.commit();
2✔
4529
    }
2✔
4530

4531
    { // change from encrypted to unencrypted
2✔
4532
        options.encryption_key = nullptr;
2✔
4533
        auto db = DB::create(path, options);
2✔
4534
        WriteTransaction wt(db);
2✔
4535
        CHECK_NOT(wt.get_table("table 2"));
2✔
4536
        wt.add_table("table 3");
2✔
4537
        wt.commit();
2✔
4538
    }
2✔
4539

4540
    { // change from unencrypted to encrypted
2✔
4541
        options.encryption_key = key_1;
2✔
4542
        auto db = DB::create(path, options);
2✔
4543
        WriteTransaction wt(db);
2✔
4544
        CHECK_NOT(wt.get_table("table 3"));
2✔
4545
        wt.add_table("table 4");
2✔
4546
        wt.commit();
2✔
4547
    }
2✔
4548

4549
    { // sanity check that reopening encrypted with the same key works
2✔
4550
        auto db = DB::create(path, options);
2✔
4551
        CHECK(db->start_read()->get_table("table 4"));
2✔
4552
    }
2✔
4553
}
2✔
4554

4555
TEST(Shared_ClearOnError_CannotClearWhileFileIsOpen)
4556
{
2✔
4557
    SHARED_GROUP_TEST_PATH(path);
2✔
4558
    DBOptions options;
2✔
4559
    options.clear_on_invalid_file = true;
2✔
4560
    auto db = DB::create(make_in_realm_history(), path, options);
2✔
4561
    options.encryption_key = crypt_key(true);
2✔
4562
    CHECK_THROW(DB::create(make_in_realm_history(), path, options), InvalidDatabase);
2✔
4563
}
2✔
4564
#endif
4565

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