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

realm / realm-core / 1771

20 Oct 2023 08:58AM UTC coverage: 91.567% (-0.009%) from 91.576%
1771

push

Evergreen

web-flow
Fix blocked DB::open on multiprocess access on exFAT filesystem (#6959)

Fix double file lock and DB::open being blocked with multiple concurrent realm access on fat32/exfat file systems.

When file is truncated on fat32/exfat its uid is available for other files. With multiple processes opening and truncating the same set of files could lead to the situation when within one process get_unique_id will return the same value for different files in timing is right. This breaks proper initialization of static data for interprocess mutexes, so that subsequent locks will hang by trying to lock essentially the same file twice.

94304 of 173552 branches covered (0.0%)

59 of 82 new or added lines in 5 files covered. (71.95%)

53 existing lines in 13 files now uncovered.

230544 of 251776 relevant lines covered (91.57%)

6594884.0 hits per line

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

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

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

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

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

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

55
#include "fuzz_group.hpp"
56

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

61
extern unsigned int unit_test_random_seed;
62

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

68

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

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

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

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

115
    Query q = ttt.where();
116

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

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

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

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

144
    GetModuleFileName(nullptr, filename, MAX_PATH);
145

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

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

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

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

168

169
namespace {
170

171
namespace {
172

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

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

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

267
#endif
268
} // anonymous namespace
269

270

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

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

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

329
#endif
330

331
#if 0
332

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

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

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

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

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

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

367
        int action = fastrand(100);
368

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

372
        t1->verify();
373

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

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

380
        size_t rows = fastrand(3000);
381

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

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

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

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

413
    }
414
}
415

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

418

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

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

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

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

490

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

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

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

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

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

572

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

599

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

1✔
611
        // Create table entries
1✔
612

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

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

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

633

634
TEST(Shared_Initial)
635
{
2✔
636
    SHARED_GROUP_TEST_PATH(path);
2✔
637
    {
2✔
638
        // Create a new shared db
1✔
639
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
640

1✔
641
        // Verify that new group is empty
1✔
642
        {
2✔
643
            ReadTransaction rt(sg);
2✔
644
            CHECK(rt.get_group().is_empty());
2✔
645
        }
2✔
646
    }
2✔
647
}
2✔
648

649

650
TEST(Shared_InitialMem)
651
{
2✔
652
    SHARED_GROUP_TEST_PATH(path);
2✔
653
    {
2✔
654
        // Create a new shared db
1✔
655
        bool no_create = false;
2✔
656
        DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
657

1✔
658
        // Verify that new group is empty
1✔
659
        {
2✔
660
            ReadTransaction rt(sg);
2✔
661
            CHECK(rt.get_group().is_empty());
2✔
662
        }
2✔
663
    }
2✔
664

1✔
665
    // In MemOnly mode, the database file must be automatically
1✔
666
    // removed.
1✔
667
    CHECK(!File::exists(path));
2✔
668
}
2✔
669

670

671
TEST(Shared_InitialMem_StaleFile)
672
{
2✔
673
    SHARED_GROUP_TEST_PATH(path);
2✔
674

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

1✔
680
    // Create a MemOnly realm at the path so that a lock file gets initialized
1✔
681
    {
2✔
682
        bool no_create = false;
2✔
683
        DBRef r = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
684
    }
2✔
685
    CHECK(!File::exists(path));
2✔
686
    CHECK(File::exists(path.get_lock_path()));
2✔
687

1✔
688
    // Create a file at the DB path to fake a process crashing and failing to
1✔
689
    // delete it
1✔
690
    {
2✔
691
        File f(path, File::mode_Write);
2✔
692
        f.write("text");
2✔
693
    }
2✔
694
    CHECK(File::exists(path));
2✔
695
    CHECK(File::exists(path.get_lock_path()));
2✔
696

1✔
697
    // Verify that we can still open the path as a MemOnly SharedGroup and that
1✔
698
    // it's cleaned up afterwards
1✔
699
    {
2✔
700
        bool no_create = false;
2✔
701
        DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
702
        CHECK(File::exists(path));
2✔
703
    }
2✔
704
    CHECK(!File::exists(path));
2✔
705
    CHECK(File::exists(path.get_lock_path()));
2✔
706
}
2✔
707

708

709
TEST(Shared_Initial2)
710
{
2✔
711
    SHARED_GROUP_TEST_PATH(path);
2✔
712
    {
2✔
713
        // Create a new shared db
1✔
714
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
715

1✔
716
        {
2✔
717
            // Open the same db again (in empty state)
1✔
718
            DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
719

1✔
720
            // Verify that new group is empty
1✔
721
            {
2✔
722
                ReadTransaction rt(sg2);
2✔
723
                CHECK(rt.get_group().is_empty());
2✔
724
            }
2✔
725

1✔
726
            // Add a new table
1✔
727
            {
2✔
728
                WriteTransaction wt(sg2);
2✔
729
                wt.get_group().verify();
2✔
730
                auto t1 = wt.add_table("test");
2✔
731
                test_table_add_columns(t1);
2✔
732
                t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
733
                wt.commit();
2✔
734
            }
2✔
735
        }
2✔
736

1✔
737
        // Verify that the new table has been added
1✔
738
        {
2✔
739
            ReadTransaction rt(sg);
2✔
740
            rt.get_group().verify();
2✔
741
            auto t1 = rt.get_table("test");
2✔
742
            auto cols = t1->get_column_keys();
2✔
743
            CHECK_EQUAL(1, t1->size());
2✔
744
            const Obj obj = t1->get_object(ObjKey(7));
2✔
745
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
746
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
747
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
748
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
749
        }
2✔
750
    }
2✔
751
}
2✔
752

753

754
TEST(Shared_Initial2_Mem)
755
{
2✔
756
    SHARED_GROUP_TEST_PATH(path);
2✔
757
    {
2✔
758
        // Create a new shared db
1✔
759
        bool no_create = false;
2✔
760
        DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
761

1✔
762
        {
2✔
763
            // Open the same db again (in empty state)
1✔
764
            DBRef sg2 = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
2✔
765

1✔
766
            // Verify that new group is empty
1✔
767
            {
2✔
768
                ReadTransaction rt(sg2);
2✔
769
                CHECK(rt.get_group().is_empty());
2✔
770
            }
2✔
771

1✔
772
            // Add a new table
1✔
773
            {
2✔
774
                WriteTransaction wt(sg2);
2✔
775
                wt.get_group().verify();
2✔
776
                auto t1 = wt.add_table("test");
2✔
777
                test_table_add_columns(t1);
2✔
778
                t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
779
                wt.commit();
2✔
780
            }
2✔
781
        }
2✔
782

1✔
783
        // Verify that the new table has been added
1✔
784
        {
2✔
785
            ReadTransaction rt(sg);
2✔
786
            rt.get_group().verify();
2✔
787
            auto t1 = rt.get_table("test");
2✔
788
            auto cols = t1->get_column_keys();
2✔
789
            CHECK_EQUAL(1, t1->size());
2✔
790
            const Obj obj = t1->get_object(ObjKey(7));
2✔
791
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
792
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
793
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
794
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
795
        }
2✔
796
    }
2✔
797
}
2✔
798

799
TEST(Shared_1)
800
{
2✔
801
    SHARED_GROUP_TEST_PATH(path);
2✔
802
    {
2✔
803
        // Create a new shared db
1✔
804
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
805
        Timestamp first_timestamp_value{1, 1};
2✔
806
        std::vector<ColKey> cols;
2✔
807

1✔
808
        // Create first table in group
1✔
809
        {
2✔
810
            WriteTransaction wt(sg);
2✔
811
            wt.get_group().verify();
2✔
812
            auto t1 = wt.add_table("test");
2✔
813
            cols = test_table_add_columns(t1);
2✔
814
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test", Timestamp{1, 1});
2✔
815
            wt.commit();
2✔
816
        }
2✔
817
        {
2✔
818
            ReadTransaction rt(sg);
2✔
819
            rt.get_group().verify();
2✔
820

1✔
821
            // Verify that last set of changes are commited
1✔
822
            auto t2 = rt.get_table("test");
2✔
823
            CHECK(t2->size() == 1);
2✔
824
            const Obj obj = t2->get_object(ObjKey(7));
2✔
825
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
826
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
827
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
828
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
829
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
830

1✔
831
            // Do a new change while still having current read transaction open
1✔
832
            {
2✔
833
                WriteTransaction wt(sg);
2✔
834
                wt.get_group().verify();
2✔
835
                auto t1 = wt.get_table("test");
2✔
836
                t1->create_object(ObjKey(8)).set_all(2, 3, true, "more test", Timestamp{2, 2});
2✔
837
                wt.commit();
2✔
838
            }
2✔
839

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

1✔
858
            // Verify that that the read transaction does still not see
1✔
859
            // the change yet (is isolated)
1✔
860
            CHECK(t2->size() == 1);
2✔
861
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
862
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
863
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
864
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
865
            CHECK_EQUAL(first_timestamp_value, obj.get<Timestamp>(cols[4]));
2✔
866
        }
2✔
867

1✔
868
        // Start a new read transaction and verify that it can now see the changes
1✔
869
        {
2✔
870
            ReadTransaction rt(sg);
2✔
871
            rt.get_group().verify();
2✔
872
            auto t3 = rt.get_table("test");
2✔
873

1✔
874
            CHECK(t3->size() == 3);
2✔
875
            const Obj obj7 = t3->get_object(ObjKey(7));
2✔
876
            CHECK_EQUAL(1, obj7.get<Int>(cols[0]));
2✔
877
            CHECK_EQUAL(2, obj7.get<Int>(cols[1]));
2✔
878
            CHECK_EQUAL(false, obj7.get<Bool>(cols[2]));
2✔
879
            CHECK_EQUAL("test", obj7.get<String>(cols[3]));
2✔
880
            CHECK_EQUAL(first_timestamp_value, obj7.get<Timestamp>(cols[4]));
2✔
881

1✔
882
            const Obj obj8 = t3->get_object(ObjKey(8));
2✔
883
            CHECK_EQUAL(2, obj8.get<Int>(cols[0]));
2✔
884
            CHECK_EQUAL(3, obj8.get<Int>(cols[1]));
2✔
885
            CHECK_EQUAL(true, obj8.get<Bool>(cols[2]));
2✔
886
            CHECK_EQUAL("more test", obj8.get<String>(cols[3]));
2✔
887
            Timestamp second_timestamp_value{2, 2};
2✔
888
            CHECK_EQUAL(second_timestamp_value, obj8.get<Timestamp>(cols[4]));
2✔
889

1✔
890
            const Obj obj9 = t3->get_object(ObjKey(9));
2✔
891
            CHECK_EQUAL(0, obj9.get<Int>(cols[0]));
2✔
892
            CHECK_EQUAL(1, obj9.get<Int>(cols[1]));
2✔
893
            CHECK_EQUAL(false, obj9.get<Bool>(cols[2]));
2✔
894
            CHECK_EQUAL("even more test", obj9.get<String>(cols[3]));
2✔
895
            Timestamp third_timestamp_value{3, 3};
2✔
896
            CHECK_EQUAL(third_timestamp_value, obj9.get<Timestamp>(cols[4]));
2✔
897
        }
2✔
898
    }
2✔
899
}
2✔
900

901
TEST(Shared_try_begin_write)
902
{
2✔
903
    SHARED_GROUP_TEST_PATH(path);
2✔
904
    // Create a new shared db
1✔
905
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
906
    std::mutex thread_obtains_write_lock;
2✔
907
    std::condition_variable cv;
2✔
908
    std::mutex cv_lock;
2✔
909
    bool init_complete = false;
2✔
910

1✔
911
    auto do_async = [&]() {
2✔
912
        auto tr = sg->start_write(true);
2✔
913
        bool success = bool(tr);
2✔
914
        CHECK(success);
2✔
915
        {
2✔
916
            std::lock_guard<std::mutex> lock(cv_lock);
2✔
917
            init_complete = true;
2✔
918
        }
2✔
919
        cv.notify_one();
2✔
920
        TableRef t = tr->add_table(StringData("table"));
2✔
921
        t->add_column(type_String, StringData("string_col"));
2✔
922
        std::vector<ObjKey> keys;
2✔
923
        t->create_objects(1000, keys);
2✔
924
        thread_obtains_write_lock.lock();
2✔
925
        tr->commit();
2✔
926
        thread_obtains_write_lock.unlock();
2✔
927
    };
2✔
928

1✔
929
    thread_obtains_write_lock.lock();
2✔
930
    Thread async_writer;
2✔
931
    async_writer.start(do_async);
2✔
932

1✔
933
    // wait for the thread to start a write transaction
1✔
934
    std::unique_lock<std::mutex> lock(cv_lock);
2✔
935
    cv.wait(lock, [&] {
4✔
936
        return init_complete;
4✔
937
    });
4✔
938

1✔
939
    // Try to also obtain a write lock. This should fail but not block.
1✔
940
    auto tr = sg->start_write(true);
2✔
941
    bool success = bool(tr);
2✔
942
    CHECK(!success);
2✔
943

1✔
944
    // Let the async thread finish its write transaction.
1✔
945
    thread_obtains_write_lock.unlock();
2✔
946
    async_writer.join();
2✔
947

1✔
948
    {
2✔
949
        // Verify that the thread transaction commit succeeded.
1✔
950
        auto rt = sg->start_read();
2✔
951
        ConstTableRef t = rt->get_table(rt->get_table_keys()[0]);
2✔
952
        CHECK(t->get_name() == StringData("table"));
2✔
953
        CHECK(t->get_column_name(t->get_column_keys()[0]) == StringData("string_col"));
2✔
954
        CHECK(t->size() == 1000);
2✔
955
        CHECK(rt->size() == 1);
2✔
956
    }
2✔
957

1✔
958
    // Now try to start a transaction without any contenders.
1✔
959
    tr = sg->start_write(true);
2✔
960
    success = bool(tr);
2✔
961
    CHECK(success);
2✔
962
    CHECK(tr->size() == 1);
2✔
963
    tr->verify();
2✔
964

1✔
965
    // Add some data and finish the transaction.
1✔
966
    auto t2k = tr->add_table(StringData("table 2"))->get_key();
2✔
967
    CHECK(tr->size() == 2);
2✔
968
    tr->commit();
2✔
969

1✔
970
    {
2✔
971
        // Verify that the main thread transaction now succeeded.
1✔
972
        ReadTransaction rt(sg);
2✔
973
        const Group& gr = rt.get_group();
2✔
974
        CHECK(gr.size() == 2);
2✔
975
        CHECK(gr.get_table(t2k)->get_name() == StringData("table 2"));
2✔
976
    }
2✔
977
}
2✔
978

979
TEST(Shared_Rollback)
980
{
2✔
981
    SHARED_GROUP_TEST_PATH(path);
2✔
982
    {
2✔
983
        // Create a new shared db
1✔
984
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
985
        std::vector<ColKey> cols;
2✔
986

1✔
987
        // Create first table in group (but rollback)
1✔
988
        {
2✔
989
            WriteTransaction wt(sg);
2✔
990
            wt.get_group().verify();
2✔
991
            auto t1 = wt.add_table("test");
2✔
992
            cols = test_table_add_columns(t1);
2✔
993
            t1->create_object().set_all(1, 2, false, "test");
2✔
994
            // Note: Implicit rollback
1✔
995
        }
2✔
996

1✔
997
        // Verify that no changes were made
1✔
998
        {
2✔
999
            ReadTransaction rt(sg);
2✔
1000
            rt.get_group().verify();
2✔
1001
            CHECK(!rt.get_group().has_table("test"));
2✔
1002
        }
2✔
1003

1✔
1004
        // Really create first table in group
1✔
1005
        {
2✔
1006
            WriteTransaction wt(sg);
2✔
1007
            wt.get_group().verify();
2✔
1008
            auto t1 = wt.add_table("test");
2✔
1009
            cols = test_table_add_columns(t1);
2✔
1010
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1011
            wt.commit();
2✔
1012
        }
2✔
1013

1✔
1014
        // Verify that the changes were made
1✔
1015
        {
2✔
1016
            ReadTransaction rt(sg);
2✔
1017
            rt.get_group().verify();
2✔
1018
            auto t = rt.get_table("test");
2✔
1019
            CHECK(t->size() == 1);
2✔
1020
            const Obj obj = t->get_object(ObjKey(7));
2✔
1021
            CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1022
            CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1023
            CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1024
            CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1025
        }
2✔
1026

1✔
1027
        // Greate more changes (but rollback)
1✔
1028
        {
2✔
1029
            WriteTransaction wt(sg);
2✔
1030
            wt.get_group().verify();
2✔
1031
            auto t1 = wt.get_table("test");
2✔
1032
            t1->create_object(ObjKey(8)).set_all(0, 0, true, "more test");
2✔
1033
            // Note: Implicit rollback
1✔
1034
        }
2✔
1035

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

1051
TEST(Shared_Writes)
1052
{
2✔
1053
    SHARED_GROUP_TEST_PATH(path);
2✔
1054
    {
2✔
1055
        // Create a new shared db
1✔
1056
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1057
        std::vector<ColKey> cols;
2✔
1058

1✔
1059
        // Create first table in group
1✔
1060
        {
2✔
1061
            WriteTransaction wt(sg);
2✔
1062
            wt.get_group().verify();
2✔
1063
            auto t1 = wt.add_table("test");
2✔
1064
            cols = test_table_add_columns(t1);
2✔
1065
            t1->create_object(ObjKey(7)).set_all(0, 2, false, "test");
2✔
1066
            wt.commit();
2✔
1067
        }
2✔
1068

1✔
1069
        // Do a lot of repeated write transactions
1✔
1070
        for (size_t i = 0; i < 100; ++i) {
202✔
1071
            WriteTransaction wt(sg);
200✔
1072
            wt.get_group().verify();
200✔
1073
            auto t1 = wt.get_table("test");
200✔
1074
            t1->get_object(ObjKey(7)).add_int(cols[0], 1);
200✔
1075
            wt.commit();
200✔
1076
        }
200✔
1077

1✔
1078
        // Verify that the changes were made
1✔
1079
        {
2✔
1080
            ReadTransaction rt(sg);
2✔
1081
            rt.get_group().verify();
2✔
1082
            auto t = rt.get_table("test");
2✔
1083
            const int64_t v = t->get_object(ObjKey(7)).get<Int>(cols[0]);
2✔
1084
            CHECK_EQUAL(100, v);
2✔
1085
        }
2✔
1086
    }
2✔
1087
}
2✔
1088

1089
#if !REALM_ANDROID // FIXME
1090
TEST_IF(Shared_ManyReaders, TEST_DURATION > 0)
1091
{
×
1092
    // This test was written primarily to expose a former bug in
1093
    // SharedGroup::end_read(), where the lock-file was not remapped
1094
    // after ring-buffer expansion.
1095

1096
    const int chunk_1_size = 251;
×
1097
    char chunk_1[chunk_1_size];
×
1098
    for (int i = 0; i < chunk_1_size; ++i)
×
1099
        chunk_1[i] = (i + 3) % 251;
×
1100
    const int chunk_2_size = 123;
×
1101
    char chunk_2[chunk_2_size];
×
1102
    for (int i = 0; i < chunk_2_size; ++i)
×
1103
        chunk_2[i] = (i + 11) % 241;
×
1104

1105
#if TEST_DURATION < 1
×
1106
    // Mac OS X 10.8 cannot handle more than 15 due to its default ulimit settings.
1107
    int rounds[] = {3, 5, 7, 9, 11, 13};
×
1108
#else
1109
    int rounds[] = {3, 5, 11, 15, 17, 23, 27, 31, 47, 59};
1110
#endif
1111
    const int num_rounds = sizeof rounds / sizeof *rounds;
×
1112

1113
    const int max_N = 64;
×
1114
    CHECK(max_N >= rounds[num_rounds - 1]);
×
1115
    DBRef shared_groups[8 * max_N];
×
1116
    TransactionRef read_transactions[8 * max_N];
×
1117
    ColKey col_int;
×
1118
    ColKey col_bin;
×
1119

1120
    auto add_int = [](Table& table, ColKey col, int64_t diff) {
×
1121
        for (auto& o : table) {
×
1122
            o.add_int(col, diff);
×
1123
        }
×
1124
    };
×
1125

1126
    for (int round = 0; round < num_rounds; ++round) {
×
1127
        int N = rounds[round];
×
1128

1129
        SHARED_GROUP_TEST_PATH(path);
×
1130

1131
        bool no_create = false;
×
1132
        auto root_sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
×
1133

1134
        // Add two tables
1135
        {
×
1136
            WriteTransaction wt(root_sg);
×
1137
            wt.get_group().verify();
×
1138
            bool was_added = false;
×
1139
            TableRef test_1 = wt.get_or_add_table("test_1", Table::Type::TopLevel, &was_added);
×
1140
            if (was_added) {
×
1141
                col_int = test_1->add_column(type_Int, "i");
×
1142
            }
×
1143
            test_1->create_object().set(col_int, 0);
×
1144
            TableRef test_2 = wt.get_or_add_table("test_2", Table::Type::TopLevel, &was_added);
×
1145
            if (was_added) {
×
1146
                col_bin = test_2->add_column(type_Binary, "b");
×
1147
            }
×
1148
            wt.commit();
×
1149
        }
×
1150

1151

1152
        // Create 8*N shared group accessors
1153
        for (int i = 0; i < 8 * N; ++i)
×
1154
            shared_groups[i] = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
×
1155

1156
        // Initiate 2*N read transactions with progressive changes
1157
        for (int i = 0; i < 2 * N; ++i) {
×
1158
            read_transactions[i] = shared_groups[i]->start_read();
×
1159
            read_transactions[i]->verify();
×
1160
            {
×
1161
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
×
1162
                CHECK_EQUAL(1u, test_1->size());
×
1163
                CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
×
1164
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
×
1165
                int n_1 = i * 1;
×
1166
                int n_2 = i * 18;
×
1167
                CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1168
                for (int j = 0; j < n_1 + n_2; ++j) {
×
1169
                    if (j % 19 == 0) {
×
1170
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1171
                    }
×
1172
                    else {
×
1173
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1174
                    }
×
1175
                }
×
1176
            }
×
1177
            {
×
1178
                WriteTransaction wt(root_sg);
×
1179
                wt.get_group().verify();
×
1180
                TableRef test_1 = wt.get_table("test_1");
×
1181
                add_int(*test_1, col_int, 1);
×
1182
                TableRef test_2 = wt.get_table("test_2");
×
1183
                test_2->create_object().set(col_bin, BinaryData(chunk_1));
×
1184
                wt.commit();
×
1185
            }
×
1186
            {
×
1187
                WriteTransaction wt(root_sg);
×
1188
                wt.get_group().verify();
×
1189
                TableRef test_2 = wt.get_table("test_2");
×
1190
                for (int j = 0; j < 18; ++j) {
×
1191
                    test_2->create_object().set(col_bin, BinaryData(chunk_2));
×
1192
                }
×
1193
                wt.commit();
×
1194
            }
×
1195
        }
×
1196

1197
        // Check isolation between read transactions
1198
        for (int i = 0; i < 2 * N; ++i) {
×
1199
            ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
×
1200
            CHECK_EQUAL(1, test_1->size());
×
1201
            CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
×
1202
            ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
×
1203
            int n_1 = i * 1;
×
1204
            int n_2 = i * 18;
×
1205
            CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1206
            for (int j = 0; j < n_1 + n_2; ++j) {
×
1207
                if (j % 19 == 0) {
×
1208
                    CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1209
                }
×
1210
                else {
×
1211
                    CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1212
                }
×
1213
            }
×
1214
        }
×
1215

1216
        // End the first half of the read transactions during further
1217
        // changes
1218
        for (int i = N - 1; i >= 0; --i) {
×
1219
            {
×
1220
                WriteTransaction wt(root_sg);
×
1221
#if !defined(_WIN32) || TEST_DURATION > 0 // These .verify() calls are horribly slow on Windows
×
1222
                wt.get_group().verify();
×
1223
#endif
×
1224
                TableRef test_1 = wt.get_table("test_1");
×
1225
                add_int(*test_1, col_int, 2);
×
1226
                wt.commit();
×
1227
            }
×
1228
            {
×
1229
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
×
1230
                CHECK_EQUAL(1, test_1->size());
×
1231
                CHECK_EQUAL(i, test_1->begin()->get<Int>(col_int));
×
1232
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
×
1233
                int n_1 = i * 1;
×
1234
                int n_2 = i * 18;
×
1235
                CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1236
                for (int j = 0; j < n_1 + n_2; ++j) {
×
1237
                    if (j % 19 == 0) {
×
1238
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1239
                    }
×
1240
                    else {
×
1241
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1242
                    }
×
1243
                }
×
1244
            }
×
1245
            read_transactions[i] = nullptr;
×
1246
        }
×
1247

1248
        // Initiate 6*N extra read transactionss with further progressive changes
1249
        for (int i = 2 * N; i < 8 * N; ++i) {
×
1250
            read_transactions[i] = shared_groups[i]->start_read();
×
1251
#if !defined(_WIN32) || TEST_DURATION > 0
×
1252
            read_transactions[i]->verify();
×
1253
#endif
×
1254
            {
×
1255
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
×
1256
                CHECK_EQUAL(1u, test_1->size());
×
1257
                int i_2 = 2 * N + i;
×
1258
                CHECK_EQUAL(i_2, test_1->begin()->get<Int>(col_int));
×
1259
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
×
1260
                int n_1 = i * 1;
×
1261
                int n_2 = i * 18;
×
1262
                CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1263
                for (int j = 0; j < n_1 + n_2; ++j) {
×
1264
                    if (j % 19 == 0) {
×
1265
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1266
                    }
×
1267
                    else {
×
1268
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1269
                    }
×
1270
                }
×
1271
            }
×
1272
            {
×
1273
                WriteTransaction wt(root_sg);
×
1274
#if !defined(_WIN32) || TEST_DURATION > 0
×
1275
                wt.get_group().verify();
×
1276
#endif
×
1277
                TableRef test_1 = wt.get_table("test_1");
×
1278
                add_int(*test_1, col_int, 1);
×
1279
                TableRef test_2 = wt.get_table("test_2");
×
1280
                test_2->create_object().set(col_bin, BinaryData(chunk_1));
×
1281
                wt.commit();
×
1282
            }
×
1283
            {
×
1284
                WriteTransaction wt(root_sg);
×
1285
#if !defined(_WIN32) || TEST_DURATION > 0
×
1286
                wt.get_group().verify();
×
1287
#endif
×
1288
                TableRef test_2 = wt.get_table("test_2");
×
1289
                for (int j = 0; j < 18; ++j) {
×
1290
                    test_2->create_object().set(col_bin, BinaryData(chunk_2));
×
1291
                }
×
1292
                wt.commit();
×
1293
            }
×
1294
        }
×
1295

1296
        // End all remaining read transactions during further changes
1297
        for (int i = 1 * N; i < 8 * N; ++i) {
×
1298
            {
×
1299
                WriteTransaction wt(root_sg);
×
1300
#if !defined(_WIN32) || TEST_DURATION > 0
×
1301
                wt.get_group().verify();
×
1302
#endif
×
1303
                TableRef test_1 = wt.get_table("test_1");
×
1304
                add_int(*test_1, col_int, 2);
×
1305
                wt.commit();
×
1306
            }
×
1307
            {
×
1308
                ConstTableRef test_1 = read_transactions[i]->get_table("test_1");
×
1309
                CHECK_EQUAL(1, test_1->size());
×
1310
                int i_2 = i < 2 * N ? i : 2 * N + i;
×
1311
                CHECK_EQUAL(i_2, test_1->begin()->get<Int>(col_int));
×
1312
                ConstTableRef test_2 = read_transactions[i]->get_table("test_2");
×
1313
                int n_1 = i * 1;
×
1314
                int n_2 = i * 18;
×
1315
                CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1316
                for (int j = 0; j < n_1 + n_2; ++j) {
×
1317
                    if (j % 19 == 0) {
×
1318
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1319
                    }
×
1320
                    else {
×
1321
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1322
                    }
×
1323
                }
×
1324
            }
×
1325
            read_transactions[i] = nullptr;
×
1326
        }
×
1327

1328
        // Check final state via each shared group, then destroy it
1329
        for (int i = 0; i < 8 * N; ++i) {
×
1330
            {
×
1331
                ReadTransaction rt(shared_groups[i]);
×
1332
#if !defined(_WIN32) || TEST_DURATION > 0
×
1333
                rt.get_group().verify();
×
1334
#endif
×
1335
                ConstTableRef test_1 = rt.get_table("test_1");
×
1336
                CHECK_EQUAL(1, test_1->size());
×
1337
                CHECK_EQUAL(3 * 8 * N, test_1->begin()->get<Int>(col_int));
×
1338
                ConstTableRef test_2 = rt.get_table("test_2");
×
1339
                int n_1 = 8 * N * 1;
×
1340
                int n_2 = 8 * N * 18;
×
1341
                CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1342
                for (int j = 0; j < n_1 + n_2; ++j) {
×
1343
                    if (j % 19 == 0) {
×
1344
                        CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1345
                    }
×
1346
                    else {
×
1347
                        CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1348
                    }
×
1349
                }
×
1350
            }
×
1351
            shared_groups[i] = nullptr;
×
1352
        }
×
1353

1354
        // Check final state via new shared group
1355
        {
×
1356
            DBRef sg = DB::create(path, no_create, DBOptions(DBOptions::Durability::MemOnly));
×
1357
            ReadTransaction rt(sg);
×
1358
#if !defined(_WIN32) || TEST_DURATION > 0
×
1359
            rt.get_group().verify();
×
1360
#endif
×
1361
            ConstTableRef test_1 = rt.get_table("test_1");
×
1362
            CHECK_EQUAL(1, test_1->size());
×
1363
            CHECK_EQUAL(3 * 8 * N, test_1->begin()->get<Int>(col_int));
×
1364
            ConstTableRef test_2 = rt.get_table("test_2");
×
1365
            int n_1 = 8 * N * 1;
×
1366
            int n_2 = 8 * N * 18;
×
1367
            CHECK_EQUAL(n_1 + n_2, test_2->size());
×
1368
            for (int j = 0; j < n_1 + n_2; ++j) {
×
1369
                if (j % 19 == 0) {
×
1370
                    CHECK_EQUAL(BinaryData(chunk_1), test_2->get_object(j).get<Binary>(col_bin));
×
1371
                }
×
1372
                else {
×
1373
                    CHECK_EQUAL(BinaryData(chunk_2), test_2->get_object(j).get<Binary>(col_bin));
×
1374
                }
×
1375
            }
×
1376
        }
×
1377
    }
×
1378
}
×
1379
#endif
1380

1381
// This test is a minimal repro. of core issue #842.
1382
TEST(Many_ConcurrentReaders)
1383
{
2✔
1384
    SHARED_GROUP_TEST_PATH(path);
2✔
1385
    const std::string path_str = path;
2✔
1386

1✔
1387
    // setup
1✔
1388
    DBRef sg_w = DB::create(path_str);
2✔
1389
    WriteTransaction wt(sg_w);
2✔
1390
    TableRef t = wt.add_table("table");
2✔
1391
    auto col_ndx = t->add_column(type_String, "column");
2✔
1392
    t->create_object().set(col_ndx, StringData("string"));
2✔
1393
    wt.commit();
2✔
1394
    sg_w->close();
2✔
1395

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

1✔
1422
    constexpr int num_threads = 4;
2✔
1423
    Thread threads[num_threads];
2✔
1424
    for (int i = 0; i < num_threads; ++i) {
10✔
1425
        threads[i].start(reader);
8✔
1426
    }
8✔
1427
    for (int i = 0; i < num_threads; ++i) {
10✔
1428
        threads[i].join();
8✔
1429
    }
8✔
1430
}
2✔
1431

1432

1433
TEST(Shared_WritesSpecialOrder)
1434
{
2✔
1435
    SHARED_GROUP_TEST_PATH(path);
2✔
1436
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1437

1✔
1438
    const int num_rows =
2✔
1439
        5; // FIXME: Should be strictly greater than REALM_MAX_BPNODE_SIZE, but that takes too long time.
2✔
1440
    const int num_reps = 25;
2✔
1441

1✔
1442
    {
2✔
1443
        WriteTransaction wt(sg);
2✔
1444
        wt.get_group().verify();
2✔
1445
        auto table = wt.add_table("test");
2✔
1446
        auto col = table->add_column(type_Int, "first");
2✔
1447
        for (int i = 0; i < num_rows; ++i) {
12✔
1448
            table->create_object(ObjKey(i)).set(col, 0);
10✔
1449
        }
10✔
1450
        wt.commit();
2✔
1451
    }
2✔
1452

1✔
1453
    for (int i = 0; i < num_rows; ++i) {
12✔
1454
        for (int j = 0; j < num_reps; ++j) {
260✔
1455
            {
250✔
1456
                WriteTransaction wt(sg);
250✔
1457
                wt.get_group().verify();
250✔
1458
                auto table = wt.get_table("test");
250✔
1459
                auto col = table->get_column_key("first");
250✔
1460
                Obj obj = table->get_object(ObjKey(i));
250✔
1461
                CHECK_EQUAL(j, obj.get<Int>(col));
250✔
1462
                obj.add_int(col, 1);
250✔
1463
                wt.commit();
250✔
1464
            }
250✔
1465
        }
250✔
1466
    }
10✔
1467

1✔
1468
    {
2✔
1469
        ReadTransaction rt(sg);
2✔
1470
        rt.get_group().verify();
2✔
1471
        auto table = rt.get_table("test");
2✔
1472
        auto col = table->get_column_key("first");
2✔
1473
        for (int i = 0; i < num_rows; ++i) {
12✔
1474
            CHECK_EQUAL(num_reps, table->get_object(ObjKey(i)).get<Int>(col));
10✔
1475
        }
10✔
1476
    }
2✔
1477
}
2✔
1478

1479
namespace {
1480

1481
void writer_threads_thread(TestContext& test_context, const DBRef& sg, ObjKey key)
1482
{
20✔
1483

10✔
1484
    for (size_t i = 0; i < 100; ++i) {
2,020✔
1485
        // Increment cell
1,000✔
1486
        {
2,000✔
1487
            WriteTransaction wt(sg);
2,000✔
1488
            wt.get_group().verify();
2,000✔
1489
            auto t1 = wt.get_table("test");
2,000✔
1490
            auto cols = t1->get_column_keys();
2,000✔
1491
            t1->get_object(key).add_int(cols[0], 1);
2,000✔
1492
            // FIXME: For some reason this takes ages when running
1,000✔
1493
            // inside valgrind, it is probably due to the "extreme
1,000✔
1494
            // overallocation" bug. The 1000 transactions performed
1,000✔
1495
            // here can produce a final database file size of more
1,000✔
1496
            // than 1 GiB. Really! And that is a table with only 10
1,000✔
1497
            // rows. It is about 1 MiB per transaction.
1,000✔
1498
            wt.commit();
2,000✔
1499
        }
2,000✔
1500

1,000✔
1501
        // Verify in new transaction so that we interleave
1,000✔
1502
        // read and write transactions
1,000✔
1503
        {
2,000✔
1504
            ReadTransaction rt(sg);
2,000✔
1505
            // rt.get_group().verify(); // verify() is not supported on a read transaction
1,000✔
1506
            // concurrently with writes.
1,000✔
1507
            auto t = rt.get_table("test");
2,000✔
1508
            auto cols = t->get_column_keys();
2,000✔
1509
            int64_t v = t->get_object(key).get<Int>(cols[0]);
2,000✔
1510
            int64_t expected = i + 1;
2,000✔
1511
            CHECK_EQUAL(expected, v);
2,000✔
1512
        }
2,000✔
1513
    }
2,000✔
1514
}
20✔
1515

1516
} // anonymous namespace
1517

1518
TEST(Shared_WriterThreads)
1519
{
2✔
1520
    SHARED_GROUP_TEST_PATH(path);
2✔
1521
    {
2✔
1522
        // Create a new shared db
1✔
1523
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1524

1✔
1525
        const int thread_count = 10;
2✔
1526
        // Create first table in group
1✔
1527
        {
2✔
1528
            WriteTransaction wt(sg);
2✔
1529
            wt.get_group().verify();
2✔
1530
            auto t1 = wt.add_table("test");
2✔
1531
            test_table_add_columns(t1);
2✔
1532
            for (int i = 0; i < thread_count; ++i)
22✔
1533
                t1->create_object(ObjKey(i)).set_all(0, 2, false, "test");
20✔
1534
            wt.commit();
2✔
1535
        }
2✔
1536

1✔
1537
        Thread threads[thread_count];
2✔
1538

1✔
1539
        // Create all threads
1✔
1540
        for (int i = 0; i < thread_count; ++i)
22✔
1541
            threads[i].start([this, &sg, i] {
20✔
1542
                writer_threads_thread(test_context, sg, ObjKey(i));
20✔
1543
            });
20✔
1544

1✔
1545
        // Wait for all threads to complete
1✔
1546
        for (int i = 0; i < thread_count; ++i)
22✔
1547
            threads[i].join();
20✔
1548

1✔
1549
        // Verify that the changes were made
1✔
1550
        {
2✔
1551
            ReadTransaction rt(sg);
2✔
1552
            rt.get_group().verify();
2✔
1553
            auto t = rt.get_table("test");
2✔
1554
            auto col = t->get_column_keys()[0];
2✔
1555

1✔
1556
            for (int i = 0; i < thread_count; ++i) {
22✔
1557
                int64_t v = t->get_object(ObjKey(i)).get<Int>(col);
20✔
1558
                CHECK_EQUAL(100, v);
20✔
1559
            }
20✔
1560
        }
2✔
1561
    }
2✔
1562
}
2✔
1563

1564

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

1571
// Not supported on Windows in particular? Keywords: winbug
1572
TEST(Shared_RobustAgainstDeathDuringWrite)
1573
{
1574
    // Abort if robust mutexes are not supported on the current
1575
    // platform. Otherwise we would probably get into a dead-lock.
1576
    if (!RobustMutex::is_robust_on_this_platform)
1577
        return;
1578

1579
    // This test can only be conducted by spawning independent
1580
    // processes which can then be terminated individually.
1581
    const int process_count = 100;
1582
    SHARED_GROUP_TEST_PATH(path);
1583
    ColKey col_int;
1584

1585
    auto add_int = [](Table& table, ColKey col, int64_t diff) {
1586
        for (auto& o : table) {
1587
            o.add_int(col, diff);
1588
        }
1589
    };
1590

1591
    for (int i = 0; i < process_count; ++i) {
1592
        pid_t pid = fork();
1593
        if (pid == pid_t(-1))
1594
            REALM_TERMINATE("fork() failed");
1595
        if (pid == 0) {
1596
            // Child
1597
            DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
1598
            WriteTransaction wt(sg);
1599
            wt.get_group().verify();
1600
            wt.get_or_add_table("alpha");
1601
            _Exit(42); // Die hard with an active write transaction
1602
        }
1603
        else {
1604
            // Parent
1605
            int stat_loc = 0;
1606
            int options = 0;
1607
            pid = waitpid(pid, &stat_loc, options);
1608
            if (pid == pid_t(-1))
1609
                REALM_TERMINATE("waitpid() failed");
1610
            bool child_exited_normaly = WIFEXITED(stat_loc);
1611
            CHECK(child_exited_normaly);
1612
            int child_exit_status = WEXITSTATUS(stat_loc);
1613
            CHECK_EQUAL(42, child_exit_status);
1614
        }
1615

1616
        // Check that we can continue without dead-locking
1617
        {
1618
            DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
1619
            WriteTransaction wt(sg);
1620
            wt.get_group().verify();
1621
            TableRef table = wt.get_or_add_table("beta");
1622
            if (table->is_empty()) {
1623
                col_int = table->add_column(type_Int, "i");
1624
                table->create_object().set(col_int, 0);
1625
            }
1626
            add_int(*table, col_int, 1);
1627
            wt.commit();
1628
        }
1629
    }
1630

1631
    {
1632
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
1633
        ReadTransaction rt(sg);
1634
        rt.get_group().verify();
1635
        CHECK(!rt.has_table("alpha"));
1636
        CHECK(rt.has_table("beta"));
1637
        ConstTableRef table = rt.get_table("beta");
1638
        CHECK_EQUAL(process_count, table->begin()->get<Int>(col_int));
1639
    }
1640
}
1641

1642
#endif // on apple
1643
#endif // encryption enabled
1644

1645
// not ios or android
1646
// #endif // defined TEST_ROBUSTNESS && defined ENABLE_ROBUST_AGAINST_DEATH_DURING_WRITE && !REALM_ENABLE_ENCRYPTION
1647

1648

1649
TEST(Shared_SpaceOveruse)
1650
{
2✔
1651
#if TEST_DURATION < 1
2✔
1652
    int n_outer = 300;
2✔
1653
    int n_inner = 21;
2✔
1654
#else
1655
    int n_outer = 3000;
1656
    int n_inner = 42;
1657
#endif
1658

1✔
1659
    // Many transactions
1✔
1660
    SHARED_GROUP_TEST_PATH(path);
2✔
1661
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1662

1✔
1663
    // Do a lot of sequential transactions
1✔
1664
    for (int i = 0; i != n_outer; ++i) {
602✔
1665
        WriteTransaction wt(sg);
600✔
1666
        wt.get_group().verify();
600✔
1667
        auto table = wt.get_or_add_table("my_table");
600✔
1668

300✔
1669
        if (table->is_empty()) {
600✔
1670
            REALM_ASSERT(table);
2✔
1671
            table->add_column(type_String, "text");
2✔
1672
        }
2✔
1673
        auto cols = table->get_column_keys();
600✔
1674

300✔
1675
        for (int j = 0; j != n_inner; ++j) {
13,200✔
1676
            REALM_ASSERT(table);
12,600✔
1677
            table->create_object().set(cols[0], "x");
12,600✔
1678
        }
12,600✔
1679
        wt.commit();
600✔
1680
    }
600✔
1681

1✔
1682
    // Verify that all was added correctly
1✔
1683
    {
2✔
1684
        ReadTransaction rt(sg);
2✔
1685
        rt.get_group().verify();
2✔
1686
        auto table = rt.get_table("my_table");
2✔
1687
        auto col = table->get_column_keys()[0];
2✔
1688
        size_t n = table->size();
2✔
1689
        CHECK_EQUAL(n_outer * n_inner, n);
2✔
1690

1✔
1691
        for (auto it : *table) {
12,600✔
1692
            CHECK_EQUAL("x", it.get<String>(col));
12,600✔
1693
        }
12,600✔
1694

1✔
1695
        table->verify();
2✔
1696
    }
2✔
1697
}
2✔
1698

1699

1700
TEST(Shared_Notifications)
1701
{
2✔
1702
    // Create a new shared db
1✔
1703
    SHARED_GROUP_TEST_PATH(path);
2✔
1704
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1705
    TransactionRef tr1 = sg->start_read();
2✔
1706

1✔
1707
    // No other instance have changed db since last transaction
1✔
1708
    CHECK(!sg->has_changed(tr1));
2✔
1709

1✔
1710
    {
2✔
1711
        // Open the same db again (in empty state)
1✔
1712
        DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
1713

1✔
1714
        // Verify that new group is empty
1✔
1715
        {
2✔
1716
            TransactionRef reader = sg2->start_read();
2✔
1717
            CHECK(reader->is_empty());
2✔
1718
            CHECK(!sg2->has_changed(reader));
2✔
1719
        }
2✔
1720

1✔
1721
        // No other instance have changed db since last transaction
1✔
1722

1✔
1723
        // Add a new table
1✔
1724
        {
2✔
1725
            WriteTransaction wt(sg2);
2✔
1726
            wt.get_group().verify();
2✔
1727
            auto t1 = wt.add_table("test");
2✔
1728
            test_table_add_columns(t1);
2✔
1729
            t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1730
            wt.commit();
2✔
1731
        }
2✔
1732
    }
2✔
1733

1✔
1734
    // Db has been changed by other instance
1✔
1735
    CHECK(sg->has_changed(tr1));
2✔
1736
    tr1 = sg->start_read();
2✔
1737
    // Verify that the new table has been added
1✔
1738
    {
2✔
1739
        ReadTransaction rt(sg);
2✔
1740
        rt.get_group().verify();
2✔
1741
        auto t1 = rt.get_table("test");
2✔
1742
        CHECK_EQUAL(1, t1->size());
2✔
1743
        const Obj obj = t1->get_object(ObjKey(7));
2✔
1744
        auto cols = t1->get_column_keys();
2✔
1745
        CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1746
        CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1747
        CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1748
        CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1749
    }
2✔
1750

1✔
1751
    // No other instance have changed db since last transaction
1✔
1752
    CHECK(!sg->has_changed(tr1));
2✔
1753
}
2✔
1754

1755

1756
TEST(Shared_FromSerialized)
1757
{
2✔
1758
    SHARED_GROUP_TEST_PATH(path);
2✔
1759

1✔
1760
    // Create new group and serialize to disk
1✔
1761
    {
2✔
1762
        Group g1;
2✔
1763
        auto t1 = g1.add_table("test");
2✔
1764
        test_table_add_columns(t1);
2✔
1765
        t1->create_object(ObjKey(7)).set_all(1, 2, false, "test");
2✔
1766
        g1.write(path, crypt_key());
2✔
1767
    }
2✔
1768

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

1✔
1772
    // Verify that contents is there when shared
1✔
1773
    {
2✔
1774
        ReadTransaction rt(sg);
2✔
1775
        rt.get_group().verify();
2✔
1776
        auto t1 = rt.get_table("test");
2✔
1777
        CHECK_EQUAL(1, t1->size());
2✔
1778
        const Obj obj = t1->get_object(ObjKey(7));
2✔
1779
        auto cols = t1->get_column_keys();
2✔
1780
        CHECK_EQUAL(1, obj.get<Int>(cols[0]));
2✔
1781
        CHECK_EQUAL(2, obj.get<Int>(cols[1]));
2✔
1782
        CHECK_EQUAL(false, obj.get<Bool>(cols[2]));
2✔
1783
        CHECK_EQUAL("test", obj.get<String>(cols[3]));
2✔
1784
    }
2✔
1785
}
2✔
1786

1787
TEST_IF(Shared_StringIndexBug1, TEST_DURATION >= 1)
1788
{
×
1789
    SHARED_GROUP_TEST_PATH(path);
×
1790
    DBRef db = DB::create(path, false, DBOptions(crypt_key()));
×
1791

1792
    {
×
1793
        auto tr = db->start_write();
×
1794
        TableRef table = tr->add_table("users");
×
1795
        auto col = table->add_column(type_String, "username");
×
1796
        table->add_search_index(col);
×
1797
        for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i)
×
1798
            table->create_object();
×
1799
        for (int i = 0; i < REALM_MAX_BPNODE_SIZE + 1; ++i)
×
1800
            table->remove_object(table->begin());
×
1801
        tr->commit();
×
1802
    }
×
1803

1804
    {
×
1805
        auto tr = db->start_write();
×
1806
        TableRef table = tr->get_table("users");
×
1807
        table->create_object();
×
1808
        tr->commit();
×
1809
    }
×
1810
}
×
1811

1812

1813
TEST(Shared_StringIndexBug2)
1814
{
2✔
1815
    SHARED_GROUP_TEST_PATH(path);
2✔
1816
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1817

1✔
1818
    {
2✔
1819
        WriteTransaction wt(sg);
2✔
1820
        wt.get_group().verify();
2✔
1821
        TableRef table = wt.add_table("a");
2✔
1822
        auto col = table->add_column(type_String, "b");
2✔
1823
        table->add_search_index(col); // Not adding index makes it work
2✔
1824
        table->create_object();
2✔
1825
        wt.commit();
2✔
1826
    }
2✔
1827

1✔
1828
    {
2✔
1829
        ReadTransaction rt(sg);
2✔
1830
        rt.get_group().verify();
2✔
1831
    }
2✔
1832
}
2✔
1833

1834

1835
namespace {
1836

1837
void rand_str(Random& random, char* res, size_t len)
1838
{
114✔
1839
    for (size_t i = 0; i < len; ++i)
1,026✔
1840
        res[i] = char(int('a') + random.draw_int_mod(10));
912✔
1841
}
114✔
1842

1843
} // anonymous namespace
1844

1845
TEST(Shared_StringIndexBug3)
1846
{
2✔
1847
    SHARED_GROUP_TEST_PATH(path);
2✔
1848
    DBRef db = DB::create(path, false, DBOptions(crypt_key()));
2✔
1849
    ColKey col;
2✔
1850
    {
2✔
1851
        auto tr = db->start_write();
2✔
1852
        TableRef table = tr->add_table("users");
2✔
1853
        col = table->add_column(type_String, "username");
2✔
1854
        table->add_search_index(col); // Disabling index makes it work
2✔
1855
        tr->commit();
2✔
1856
    }
2✔
1857

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

100✔
1863
        if (action <= 500) {
200✔
1864
            // delete random user
36✔
1865
            auto tr = db->start_write();
86✔
1866
            TableRef table = tr->get_table("users");
86✔
1867
            if (table->size() > 0) {
86✔
1868
                size_t del = random.draw_int_mod(table->size());
72✔
1869
                // cerr << "-" << del << ": " << table->get_string(0, del) << std::endl;
35✔
1870
                table->remove_object(keys[del]);
72✔
1871
                keys.erase(keys.begin() + del);
72✔
1872
                table->verify();
72✔
1873
            }
72✔
1874
            tr->commit();
86✔
1875
        }
86✔
1876
        else {
114✔
1877
            // add new user
64✔
1878
            auto tr = db->start_write();
114✔
1879
            TableRef table = tr->get_table("users");
114✔
1880
            char txt[100];
114✔
1881
            rand_str(random, txt, 8);
114✔
1882
            txt[8] = 0;
114✔
1883
            // cerr << "+" << txt << std::endl;
64✔
1884
            auto key = table->create_object().set_all(txt).get_key();
114✔
1885
            keys.push_back(key);
114✔
1886
            table->verify();
114✔
1887
            tr->commit();
114✔
1888
        }
114✔
1889
    }
200✔
1890
}
2✔
1891

1892
TEST(Shared_ClearColumnWithBasicArrayRootLeaf)
1893
{
2✔
1894
    SHARED_GROUP_TEST_PATH(path);
2✔
1895
    {
2✔
1896
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1897
        WriteTransaction wt(sg);
2✔
1898
        TableRef test = wt.add_table("Test");
2✔
1899
        auto col = test->add_column(type_Double, "foo");
2✔
1900
        test->clear();
2✔
1901
        test->create_object(ObjKey(7)).set(col, 727.2);
2✔
1902
        wt.commit();
2✔
1903
    }
2✔
1904
    {
2✔
1905
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
1906
        ReadTransaction rt(sg);
2✔
1907
        ConstTableRef test = rt.get_table("Test");
2✔
1908
        auto col = test->get_column_key("foo");
2✔
1909
        CHECK_EQUAL(727.2, test->get_object(ObjKey(7)).get<Double>(col));
2✔
1910
    }
2✔
1911
}
2✔
1912

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

1938
#if 0 // FIXME: Reenable when it can pass reliably
1939
#ifdef _WIN32
1940

1941
TEST(Shared_WaitForChangeAfterOwnCommit)
1942
{
1943
    SHARED_GROUP_TEST_PATH(path);
1944

1945
    DB* sg = new DB(path);
1946
    sg->begin_write();
1947
    sg->commit();
1948
    bool b = sg->wait_for_change();
1949
}
1950

1951
NONCONCURRENT_TEST(Shared_InterprocessWaitForChange)
1952
{
1953
    // We can't use SHARED_GROUP_TEST_PATH() because it will attempt to clean up the .realm file at the end,
1954
    // and hence throw if the other processstill has the .realm file open
1955
    std::string path = get_test_path("Shared_InterprocessWaitForChange", ".realm");
1956

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

1961
    if (pid == -1) {
1962
        CHECK(false);
1963
        return;
1964
    }
1965

1966
    auto sg = DB::create(path);
1967

1968
    // An old .realm file with random contents can exist (such as a leftover from earlier crash) with random
1969
    // data, so we always initialize the database
1970
    {
1971
        auto tr = sg->start_write();
1972
        Group& g(*tr);
1973
        if (g.size() == 1) {
1974
            g.remove_table("data");
1975
            TableRef table = g.add_table("data");
1976
            auto col = table->add_column(type_Int, "ints");
1977
            table->create_object().set(col, 0);
1978
        }
1979
        tr->commit();
1980
        sg->wait_for_change(tr);
1981
    }
1982

1983
    bool first = false;
1984
    fastrand(time(0), true);
1985

1986
    // By turn, incremenet the counter and wait for the other to increment it too
1987
    for (int i = 0; i < 10; i++)
1988
    {
1989
        auto tr = sg->start_write();
1990
        Group& g(*tr);
1991
        if (g.size() == 1) {
1992
            TableRef table = g.get_table("data");
1993
            auto col = table->get_column_key("ints");
1994
            auto first_obj = table->begin();
1995
            int64_t v = first_obj->get<int64_t>(col);
1996

1997
            if (i == 0 && v == 0)
1998
                first = true;
1999

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

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

2011
        tr->commit();
2012

2013
        if (fastrand(1))
2014
            millisleep((time(0) % 10) * 10);
2015

2016
        sg->wait_for_change(tr);
2017

2018
        if (fastrand(1))
2019
            millisleep((time(0) % 10) * 10);
2020
    }
2021

2022
    // Wake up other process so it will exit too
2023
    auto tr = sg->start_write();
2024
    tr->commit();
2025
}
2026
#endif
2027

2028
// This test will hang infinitely instead of failing!!!
2029
TEST(Shared_WaitForChange)
2030
{
2031
    const int num_threads = 3;
2032
    Mutex mutex;
2033
    int shared_state[num_threads];
2034
    for (int j = 0; j < num_threads; j++)
2035
        shared_state[j] = 0;
2036
    SHARED_GROUP_TEST_PATH(path);
2037
    DBRef sg = DB::create(path, false);
2038

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

2080
    Thread threads[num_threads];
2081
    for (int j = 0; j < num_threads; j++)
2082
        threads[j].start([waiter, sg, j] { waiter(sg, j); });
2083
    bool try_again = true;
2084
    while (try_again) {
2085
        try_again = false;
2086
        for (int j = 0; j < num_threads; j++) {
2087
            LockGuard l(mutex);
2088
            if (shared_state[j] < 1) try_again = true;
2089
            CHECK(shared_state[j] < 2);
2090
        }
2091
    }
2092
    // At this point all transactions have progress to state 1,
2093
    // and none of them has progressed further.
2094
    // This write transaction should allow all readers to run again
2095
    {
2096
        WriteTransaction wt(sg);
2097
        wt.commit();
2098
    }
2099

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

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

2190
#if REALM_ENABLE_ENCRYPTION
2191
// verify that even though different threads share the same encrypted pages,
2192
// a thread will not get access without the key.
2193
TEST(Shared_EncryptionKeyCheck)
2194
{
2✔
2195
    SHARED_GROUP_TEST_PATH(path);
2✔
2196
    DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2✔
2197
    CHECK_THROW(DB::create(path, false, DBOptions()), InvalidDatabase);
2✔
2198
    DBRef sg3 = DB::create(path, false, DBOptions(crypt_key(true)));
2✔
2199
}
2✔
2200

2201
// opposite - if opened unencrypted, attempt to share it encrypted
2202
// will throw an error.
2203
TEST(Shared_EncryptionKeyCheck_2)
2204
{
2✔
2205
    SHARED_GROUP_TEST_PATH(path);
2✔
2206
    DBRef sg = DB::create(path, false, DBOptions());
2✔
2207
    CHECK_THROW(DB::create(path, false, DBOptions(crypt_key(true))), InvalidDatabase);
2✔
2208
    DBRef sg3 = DB::create(path, false, DBOptions());
2✔
2209
    CHECK(sg3);
2✔
2210
}
2✔
2211

2212
// if opened by one key, it cannot be opened by a different key
2213
TEST(Shared_EncryptionKeyCheck_3)
2214
{
2✔
2215
    SHARED_GROUP_TEST_PATH(path);
2✔
2216
    const char* first_key = crypt_key(true);
2✔
2217
    char second_key[64];
2✔
2218
    memcpy(second_key, first_key, 64);
2✔
2219
    second_key[3] = ~second_key[3];
2✔
2220
    DBRef sg = DB::create(path, false, DBOptions(first_key));
2✔
2221
    CHECK_THROW(DB::create(path, false, DBOptions(second_key)), InvalidDatabase);
2✔
2222
    DBRef sg3 = DB::create(path, false, DBOptions(first_key));
2✔
2223
}
2✔
2224

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

2266
#endif // REALM_ENABLE_ENCRYPTION
2267

2268
TEST(Shared_VersionCount)
2269
{
2✔
2270
    SHARED_GROUP_TEST_PATH(path);
2✔
2271
    DBRef sg = get_test_db(path);
2✔
2272
    CHECK_EQUAL(1, sg->get_number_of_versions());
2✔
2273
    TransactionRef reader = sg->start_read();
2✔
2274
    {
2✔
2275
        WriteTransaction wt(sg);
2✔
2276
        CHECK_EQUAL(1, sg->get_number_of_versions());
2✔
2277
        wt.commit();
2✔
2278
    }
2✔
2279
    CHECK_EQUAL(2, sg->get_number_of_versions());
2✔
2280
    {
2✔
2281
        WriteTransaction wt(sg);
2✔
2282
        wt.commit();
2✔
2283
    }
2✔
2284
    CHECK_EQUAL(3, sg->get_number_of_versions());
2✔
2285
    reader->close();
2✔
2286
    CHECK_EQUAL(3, sg->get_number_of_versions());
2✔
2287
    {
2✔
2288
        WriteTransaction wt(sg);
2✔
2289
        wt.commit();
2✔
2290
    }
2✔
2291
    // both the last and the second-last commit is kept, so once
1✔
2292
    // you've committed anything, you will never get back to having
1✔
2293
    // just a single version.
1✔
2294
    CHECK_EQUAL(2, sg->get_number_of_versions());
2✔
2295
}
2✔
2296

2297
TEST(Shared_MultipleRollbacks)
2298
{
2✔
2299
    SHARED_GROUP_TEST_PATH(path);
2✔
2300
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2301
    TransactionRef wt = sg->start_write();
2✔
2302
    wt->rollback();
2✔
2303
    wt->rollback();
2✔
2304
}
2✔
2305

2306

2307
TEST(Shared_MultipleEndReads)
2308
{
2✔
2309
    SHARED_GROUP_TEST_PATH(path);
2✔
2310
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2311
    TransactionRef reader = sg->start_read();
2✔
2312
    reader->end_read();
2✔
2313
    reader->end_read();
2✔
2314
}
2✔
2315

2316
#ifdef REALM_DEBUG
2317
// SharedGroup::reserve() is a debug method only available in debug mode
2318
TEST(Shared_ReserveDiskSpace)
2319
{
2✔
2320
    SHARED_GROUP_TEST_PATH(path);
2✔
2321
    {
2✔
2322
        DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2323
        size_t orig_file_size = size_t(File(path).get_size());
2✔
2324

1✔
2325
        // Check that reserve() does not change the file size if the
1✔
2326
        // specified size is less than the actual file size.
1✔
2327
        size_t reserve_size_1 = orig_file_size / 2;
2✔
2328
        sg->reserve(reserve_size_1);
2✔
2329
        size_t new_file_size_1 = size_t(File(path).get_size());
2✔
2330
        CHECK_EQUAL(orig_file_size, new_file_size_1);
2✔
2331

1✔
2332
        // Check that reserve() does not change the file size if the
1✔
2333
        // specified size is equal to the actual file size.
1✔
2334
        size_t reserve_size_2 = orig_file_size;
2✔
2335
        sg->reserve(reserve_size_2);
2✔
2336
        size_t new_file_size_2 = size_t(File(path).get_size());
2✔
2337
        if (crypt_key()) {
2✔
2338
            // For encrypted files, reserve() may actually grow the file
2339
            // with a page sized header.
2340
            CHECK(orig_file_size <= new_file_size_2 && (orig_file_size + page_size()) >= new_file_size_2);
×
2341
        }
×
2342
        else {
2✔
2343
            CHECK_EQUAL(orig_file_size, new_file_size_2);
2✔
2344
        }
2✔
2345

1✔
2346
        // Check that reserve() does change the file size if the
1✔
2347
        // specified size is greater than the actual file size, and
1✔
2348
        // that the new size is at least as big as the requested size.
1✔
2349
        size_t reserve_size_3 = orig_file_size + 1;
2✔
2350
        sg->reserve(reserve_size_3);
2✔
2351
        size_t new_file_size_3 = size_t(File(path).get_size());
2✔
2352
        CHECK(new_file_size_3 >= reserve_size_3);
2✔
2353
        ObjKeys keys;
2✔
2354

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

2399
TEST(Shared_MovingSearchIndex)
2400
{
2✔
2401
    // Test that the 'index in parent' property of search indexes is properly
1✔
2402
    // adjusted when columns are inserted or removed at a lower column_index.
1✔
2403

1✔
2404
    SHARED_GROUP_TEST_PATH(path);
2✔
2405
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2406

1✔
2407
    // Create an int column, regular string column, and an enumeration strings
1✔
2408
    // column, and equip them with search indexes.
1✔
2409
    ColKey int_col, str_col, enum_col, padding_col;
2✔
2410
    std::vector<ObjKey> obj_keys;
2✔
2411
    {
2✔
2412
        WriteTransaction wt(sg);
2✔
2413
        TableRef table = wt.add_table("foo");
2✔
2414
        padding_col = table->add_column(type_Int, "padding");
2✔
2415
        int_col = table->add_column(type_Int, "int");
2✔
2416
        str_col = table->add_column(type_String, "regular");
2✔
2417
        enum_col = table->add_column(type_String, "enum");
2✔
2418

1✔
2419
        table->create_objects(64, obj_keys);
2✔
2420
        for (int i = 0; i < 64; ++i) {
130✔
2421
            auto obj = table->get_object(obj_keys[i]);
128✔
2422
            std::string out(std::string("foo") + util::to_string(i));
128✔
2423
            obj.set<Int>(int_col, i);
128✔
2424
            obj.set<String>(str_col, out);
128✔
2425
            obj.set<String>(enum_col, "bar");
128✔
2426
        }
128✔
2427
        table->get_object(obj_keys.back()).set<String>(enum_col, "bar63");
2✔
2428
        table->enumerate_string_column(enum_col);
2✔
2429
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2430
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2431
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2432

1✔
2433
        table->add_search_index(int_col);
2✔
2434
        table->add_search_index(str_col);
2✔
2435
        table->add_search_index(enum_col);
2✔
2436

1✔
2437
        wt.get_group().verify();
2✔
2438

1✔
2439
        CHECK_EQUAL(obj_keys[61], table->find_first_int(int_col, 61));
2✔
2440
        CHECK_EQUAL(obj_keys[62], table->find_first_string(str_col, "foo62"));
2✔
2441
        CHECK_EQUAL(obj_keys[63], table->find_first_string(enum_col, "bar63"));
2✔
2442
        wt.commit();
2✔
2443
    }
2✔
2444

1✔
2445
    // Remove the padding column to shift the indexed columns
1✔
2446
    {
2✔
2447
        WriteTransaction wt(sg);
2✔
2448
        TableRef table = wt.get_table("foo");
2✔
2449

1✔
2450
        CHECK(table->has_search_index(int_col));
2✔
2451
        CHECK(table->has_search_index(str_col));
2✔
2452
        CHECK(table->has_search_index(enum_col));
2✔
2453
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2454
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2455
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2456
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2457
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2458
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2459
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2460
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2461
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2462

1✔
2463
        table->remove_column(padding_col);
2✔
2464
        wt.get_group().verify();
2✔
2465

1✔
2466
        CHECK(table->has_search_index(int_col));
2✔
2467
        CHECK(table->has_search_index(str_col));
2✔
2468
        CHECK(table->has_search_index(enum_col));
2✔
2469
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2470
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2471
        CHECK_EQUAL(2, table->get_num_unique_values(enum_col));
2✔
2472
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2473
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2474
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2475
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2476
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2477
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2478

1✔
2479
        auto obj = table->get_object(obj_keys[1]);
2✔
2480
        obj.set<Int>(int_col, 101);
2✔
2481
        obj.set<String>(str_col, "foo_Y");
2✔
2482
        obj.set<String>(enum_col, "bar_Y");
2✔
2483
        wt.get_group().verify();
2✔
2484

1✔
2485
        CHECK(table->has_search_index(int_col));
2✔
2486
        CHECK(table->has_search_index(str_col));
2✔
2487
        CHECK(table->has_search_index(enum_col));
2✔
2488
        CHECK_EQUAL(0, table->get_num_unique_values(int_col));
2✔
2489
        CHECK_EQUAL(0, table->get_num_unique_values(str_col));
2✔
2490
        CHECK_EQUAL(3, table->get_num_unique_values(enum_col));
2✔
2491
        CHECK_EQUAL(ObjKey(), table->find_first_int(int_col, 100));
2✔
2492
        CHECK_EQUAL(ObjKey(), table->find_first_string(str_col, "bad"));
2✔
2493
        CHECK_EQUAL(ObjKey(), table->find_first_string(enum_col, "bad"));
2✔
2494
        CHECK_EQUAL(obj_keys[41], table->find_first_int(int_col, 41));
2✔
2495
        CHECK_EQUAL(obj_keys[42], table->find_first_string(str_col, "foo42"));
2✔
2496
        CHECK_EQUAL(obj_keys[0], table->find_first_string(enum_col, "bar"));
2✔
2497
        CHECK_EQUAL(obj_keys[1], table->find_first_int(int_col, 101));
2✔
2498
        CHECK_EQUAL(obj_keys[1], table->find_first_string(str_col, "foo_Y"));
2✔
2499
        CHECK_EQUAL(obj_keys[1], table->find_first_string(enum_col, "bar_Y"));
2✔
2500
        CHECK_EQUAL(obj_keys[63], table->find_first_string(enum_col, "bar63"));
2✔
2501

1✔
2502
        wt.commit();
2✔
2503
    }
2✔
2504
}
2✔
2505

2506
TEST_IF(Shared_BeginReadFailure, _impl::SimulatedFailure::is_enabled())
2507
{
2✔
2508
    SHARED_GROUP_TEST_PATH(path);
2✔
2509
    DBRef sg = get_test_db(path);
2✔
2510
    using sf = _impl::SimulatedFailure;
2✔
2511
    sf::OneShotPrimeGuard pg(sf::shared_group__grow_reader_mapping);
2✔
2512
    CHECK_THROW(sg->start_read(), sf);
2✔
2513
}
2✔
2514

2515

2516
TEST(Shared_SessionDurabilityConsistency)
2517
{
2✔
2518
    // Check that we can reliably detect inconsist durability choices across
1✔
2519
    // concurrent session participants.
1✔
2520

1✔
2521
    // Errors of this kind are considered as incorrect API usage, and will lead
1✔
2522
    // to throwing of LogicError exceptions.
1✔
2523

1✔
2524
    SHARED_GROUP_TEST_PATH(path);
2✔
2525
    {
2✔
2526
        bool no_create = false;
2✔
2527
        DBOptions::Durability durability_1 = DBOptions::Durability::Full;
2✔
2528
        DBRef sg = DB::create(path, no_create, DBOptions(durability_1));
2✔
2529

1✔
2530
        DBOptions::Durability durability_2 = DBOptions::Durability::MemOnly;
2✔
2531
        CHECK_RUNTIME_ERROR(DB::create(path, no_create, DBOptions(durability_2)), ErrorCodes::IncompatibleSession);
2✔
2532
    }
2✔
2533
}
2✔
2534

2535

2536
TEST(Shared_WriteEmpty)
2537
{
2✔
2538
    SHARED_GROUP_TEST_PATH(path_1);
2✔
2539
    GROUP_TEST_PATH(path_2);
2✔
2540
    {
2✔
2541
        DBRef sg = DB::create(path_1);
2✔
2542
        ReadTransaction rt(sg);
2✔
2543
        rt.get_group().write(path_2);
2✔
2544
    }
2✔
2545
}
2✔
2546

2547

2548
TEST(Shared_CompactEmpty)
2549
{
2✔
2550
    SHARED_GROUP_TEST_PATH(path);
2✔
2551
    {
2✔
2552
        DBRef sg = get_test_db(path);
2✔
2553
        CHECK(sg->compact());
2✔
2554
    }
2✔
2555
}
2✔
2556

2557

2558
TEST(Shared_VersionOfBoundSnapshot)
2559
{
2✔
2560
    SHARED_GROUP_TEST_PATH(path);
2✔
2561
    DB::version_type version;
2✔
2562
    DBRef sg = get_test_db(path);
2✔
2563
    {
2✔
2564
        ReadTransaction rt(sg);
2✔
2565
        version = rt.get_version();
2✔
2566
    }
2✔
2567
    {
2✔
2568
        ReadTransaction rt(sg);
2✔
2569
        CHECK_EQUAL(version, rt.get_version());
2✔
2570
    }
2✔
2571
    {
2✔
2572
        WriteTransaction wt(sg);
2✔
2573
        CHECK_EQUAL(version, wt.get_version());
2✔
2574
    }
2✔
2575
    {
2✔
2576
        WriteTransaction wt(sg);
2✔
2577
        CHECK_EQUAL(version, wt.get_version());
2✔
2578
        wt.commit(); // Increment version
2✔
2579
    }
2✔
2580
    {
2✔
2581
        ReadTransaction rt(sg);
2✔
2582
        CHECK_LESS(version, rt.get_version());
2✔
2583
        version = rt.get_version();
2✔
2584
    }
2✔
2585
    {
2✔
2586
        WriteTransaction wt(sg);
2✔
2587
        CHECK_EQUAL(version, wt.get_version());
2✔
2588
        wt.commit(); // Increment version
2✔
2589
    }
2✔
2590
    {
2✔
2591
        ReadTransaction rt(sg);
2✔
2592
        CHECK_LESS(version, rt.get_version());
2✔
2593
    }
2✔
2594
}
2✔
2595

2596

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

2621
    std::vector<std::pair<void*, size_t>> memory_list;
2622
    // Reserve enough for 5*100000 Gb, but in practice the vector is only ever around size 10.
2623
    // Do this here to avoid the (small) chance that adding to the vector will request new virtual memory
2624
    memory_list.reserve(500);
2625
    size_t chunk_size = size_t(1024) * 1024 * 1024 * 100000;
2626
    while (chunk_size > string_length) {
2627
        void* addr = ::mmap(nullptr, chunk_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
2628
        if (addr == MAP_FAILED) {
2629
            chunk_size /= 2;
2630
        }
2631
        else {
2632
            memory_list.push_back(std::pair<void*, size_t>(addr, chunk_size));
2633
        }
2634
    }
2635

2636
    bool expected_exception_caught = false;
2637
    // Attempt to open Realm, should fail because we hold too much already.
2638
    try {
2639
        SharedGroup sg2(path, false, SharedGroupOptions(crypt_key()));
2640
    }
2641
    catch (AddressSpaceExhausted& e) {
2642
        expected_exception_caught = true;
2643
    }
2644
    CHECK(expected_exception_caught);
2645

2646
    // Release memory manually.
2647
    for (auto it = memory_list.begin(); it != memory_list.end(); ++it) {
2648
        ::munmap(it->first, it->second);
2649
    }
2650

2651
    // Realm should succeed to open now.
2652
    expected_exception_caught = false;
2653
    try {
2654
        SharedGroup sg2(path, false, SharedGroupOptions(crypt_key()));
2655
    }
2656
    catch (AddressSpaceExhausted& e) {
2657
        expected_exception_caught = true;
2658
    }
2659
    CHECK(!expected_exception_caught);
2660
}
2661
#endif // !win32
2662
*/
2663

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

1✔
2677
    if (filename != "") {
2✔
2678
        const char* tmp[] = {"", filename.c_str(), "--log"};
×
2679
        run_fuzzy(sizeof(tmp) / sizeof(tmp[0]), tmp);
×
2680
    }
×
2681
    else {
2✔
2682
        // Number of fuzzy tests
1✔
2683
#if TEST_DURATION == 0
2✔
2684
        const size_t iterations = 3;
2✔
2685
#else
2686
        const size_t iterations = 1000;
2687
#endif
2688

1✔
2689
        // Number of instructions in each test
1✔
2690
        // Changing this strongly affects the test suite run time
1✔
2691
        const size_t instructions = 200;
2✔
2692

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

3✔
2698
            std::string instr;
6✔
2699

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

3✔
2704
            for (size_t t = 0; t < instructions; t++) {
1,206✔
2705
                char c = static_cast<char>(generator());
1,200✔
2706
                instr += c;
1,200✔
2707
                std::string tmp;
1,200✔
2708
                unit_test::to_string(static_cast<int>(c), tmp);
1,200✔
2709
                fastlog += tmp;
1,200✔
2710
                if (t + 1 < instructions) {
1,200✔
2711
                    fastlog += ", ";
1,194✔
2712
                }
1,194✔
2713
                else {
6✔
2714
                    fastlog += "}; instr = string(instr2);";
6✔
2715
                }
6✔
2716
            }
1,200✔
2717

3✔
2718
            // Scope guard of "path" is inside the loop to clean up files per iteration
3✔
2719
            SHARED_GROUP_TEST_PATH(path);
6✔
2720
            // If using std::cerr, you can copy/paste the console output into a unit test
3✔
2721
            // to get a reproduction test case
3✔
2722
            // parse_and_apply_instructions(instr, path, std::cerr);
3✔
2723
            parse_and_apply_instructions(instr, path, nullptr);
6✔
2724
        }
6✔
2725
    }
2✔
2726
}
2✔
2727

2728
#if 0 // not suitable for automatic testing
2729
// This test checks what happens when a version is pinned and there are many
2730
// large write transactions that grow the file quickly. It takes a long time
2731
// and can make very very large files so it is not suited to automatic testing.
2732
TEST_IF(Shared_encrypted_pin_and_write, false)
2733
{
2734
    const size_t num_objects = 1000;
2735
    const size_t num_transactions = 1000000;
2736
    const size_t num_writer_threads = 8;
2737
    SHARED_GROUP_TEST_PATH(path);
2738

2739
    { // initial table structure setup on main thread
2740
        DBRef sg = DB::create(path, false, DBOptions(crypt_key(true)));
2741
        WriteTransaction wt(sg);
2742
        Group& group = wt.get_group();
2743
        TableRef t = group.add_table("table");
2744
        t->add_column(type_String, "string_col", true);
2745
        for (size_t i = 0; i < num_objects; ++i) {
2746
            t->create_object();
2747
        }
2748
        wt.commit();
2749
    }
2750

2751
    DBRef sg_reader = DB::create(path, false, DBOptions(crypt_key(true)));
2752

2753
    ReadTransaction rt(sg_reader); // hold first version
2754

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

2782
    Thread threads[num_writer_threads];
2783
    for (size_t i = 0; i < num_writer_threads; ++i)
2784
        threads[i].start(do_many_writes);
2785

2786
    for (size_t i = 0; i < num_writer_threads; ++i) {
2787
        threads[i].join();
2788
    }
2789
}
2790
#endif
2791

2792

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

2830
TEST_IF(Shared_CompactEncrypt, REALM_ENABLE_ENCRYPTION)
2831
{
2✔
2832
    SHARED_GROUP_TEST_PATH(path);
2✔
2833
    const char* key1 = "KdrL2ieWyspILXIPetpkLD6rQYKhYnS6lvGsgk4qsJAMr1adQnKsYo3oTEYJDIfa";
2✔
2834
    const char* key2 = "ti6rOKviXrwxSGMPVk35Dp9Q4eku8Cu8YTtnnZKAejOTNIEv7TvXrYdjOPSNexMR";
2✔
2835
    {
2✔
2836
        auto db = DB::create(path, false, DBOptions(key1));
2✔
2837
        auto tr = db->start_write();
2✔
2838
        TableRef t = tr->add_table("table");
2✔
2839
        auto col = t->add_column(type_String, "Strings");
2✔
2840
        for (size_t i = 0; i < 10000; i++) {
20,002✔
2841
            std::string str = "Shared_CompactEncrypt" + util::to_string(i);
20,000✔
2842
            t->create_object().set(col, StringData(str));
20,000✔
2843
        }
20,000✔
2844
        tr->commit();
2✔
2845

1✔
2846
        CHECK(db->compact());
2✔
2847
        {
2✔
2848
            auto rt = db->start_read();
2✔
2849
            CHECK(rt->has_table("table"));
2✔
2850
        }
2✔
2851

1✔
2852
        bool bump_version_number = true;
2✔
2853
        CHECK(db->compact(bump_version_number, key2));
2✔
2854
        {
2✔
2855
            auto rt = db->start_read();
2✔
2856
            CHECK(rt->has_table("table"));
2✔
2857
        }
2✔
2858

1✔
2859
        CHECK(db->compact(bump_version_number, nullptr));
2✔
2860
        {
2✔
2861
            auto rt = db->start_read();
2✔
2862
            CHECK(rt->has_table("table"));
2✔
2863
        }
2✔
2864
    }
2✔
2865
    {
2✔
2866
        auto db = DB::create(path, true, DBOptions());
2✔
2867
        {
2✔
2868
            auto rt = db->start_read();
2✔
2869
            CHECK(rt->has_table("table"));
2✔
2870
        }
2✔
2871
    }
2✔
2872
}
2✔
2873

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

2915
// Found by AFL (on a heavy hint from Finn that we should add a compact() instruction
2916
NONCONCURRENT_TEST(Shared_TopSizeNotEqualNine)
2917
{
2✔
2918
    SHARED_GROUP_TEST_PATH(path);
2✔
2919
    DBRef sg = DB::create(path, false, DBOptions(crypt_key()));
2✔
2920
    {
2✔
2921
        TransactionRef writer = sg->start_write();
2✔
2922

1✔
2923
        TableRef t = writer->add_table("foo");
2✔
2924
        t->add_column(type_Double, "doubles");
2✔
2925
        std::vector<ObjKey> keys;
2✔
2926
        t->create_objects(241, keys);
2✔
2927
        writer->commit();
2✔
2928
    }
2✔
2929
    REALM_ASSERT_RELEASE(sg->compact());
2✔
2930
    DBRef sg2 = DB::create(path, false, DBOptions(crypt_key()));
2✔
2931
    {
2✔
2932
        TransactionRef writer = sg2->start_write();
2✔
2933
        writer->commit();
2✔
2934
    }
2✔
2935
    TransactionRef reader2 = sg2->start_read();
2✔
2936
    DBRef sg3 = DB::create(path, false, DBOptions(crypt_key()));
2✔
2937
    TransactionRef reader3 = sg3->start_read();
2✔
2938
    TransactionRef reader = sg->start_read();
2✔
2939
}
2✔
2940

2941
// Found by AFL after adding the compact instruction
2942
// after further manual simplification, this test no longer triggers
2943
// the double free, but crashes in a different way
2944
TEST(Shared_Bptree_insert_failure)
2945
{
2✔
2946
    SHARED_GROUP_TEST_PATH(path);
2✔
2947
    DBRef sg_w = DB::create(path, false, DBOptions(crypt_key()));
2✔
2948
    TransactionRef writer = sg_w->start_write();
2✔
2949

1✔
2950
    auto tk = writer->add_table("")->get_key();
2✔
2951
    writer->get_table(tk)->add_column(type_Double, "dgrpn", true);
2✔
2952
    std::vector<ObjKey> keys;
2✔
2953
    writer->get_table(tk)->create_objects(246, keys);
2✔
2954
    writer->commit();
2✔
2955
    REALM_ASSERT_RELEASE(sg_w->compact());
2✔
2956
#if 0
2957
    {
2958
        // This intervening sg can do the same operation as the one doing compact,
2959
        // but without failing:
2960
        DB sg2(path, false, DBOptions(crypt_key()));
2961
        Group& g2 = const_cast<Group&>(sg2.begin_write());
2962
        g2.get_table(tk)->add_empty_row(396);
2963
    }
2964
#endif
2965
    {
2✔
2966
        TransactionRef writer2 = sg_w->start_write();
2✔
2967
        writer2->get_table(tk)->create_objects(396, keys);
2✔
2968
    }
2✔
2969
}
2✔
2970

2971
NONCONCURRENT_TEST(SharedGroupOptions_tmp_dir)
2972
{
2✔
2973
    const std::string initial_system_dir = DBOptions::get_sys_tmp_dir();
2✔
2974

1✔
2975
    const std::string test_dir = "/test-temp";
2✔
2976
    DBOptions::set_sys_tmp_dir(test_dir);
2✔
2977
    CHECK_EQUAL(DBOptions::get_sys_tmp_dir(), test_dir);
2✔
2978
    CHECK_EQUAL(DBOptions().temp_dir, test_dir);
2✔
2979

1✔
2980
    DBOptions::set_sys_tmp_dir(initial_system_dir);
2✔
2981
}
2✔
2982

2983

2984
namespace {
2985

2986
void wait_for(size_t expected, std::mutex& mutex, size_t& test_value)
2987
{
20✔
2988
    while (true) {
2,021✔
2989
        millisleep(1);
2,021✔
2990
        std::lock_guard<std::mutex> guard(mutex);
2,021✔
2991
        if (test_value == expected) {
2,021✔
2992
            return;
20✔
2993
        }
20✔
2994
    }
2,021✔
2995
}
20✔
2996

2997
} // end anonymous namespace
2998

2999
TEST(Shared_LockFileInitSpinsOnZeroSize)
3000
{
2✔
3001
    SHARED_GROUP_TEST_PATH(path);
2✔
3002

1✔
3003
    bool no_create = false;
2✔
3004
    DBOptions options;
2✔
3005
    options.encryption_key = crypt_key();
2✔
3006
    DBRef sg = DB::create(path, no_create, options);
2✔
3007
    sg->close();
2✔
3008

1✔
3009
    CHECK(File::exists(path));
2✔
3010
    CHECK(File::exists(path.get_lock_path()));
2✔
3011

1✔
3012
    std::mutex mutex;
2✔
3013
    size_t test_stage = 0;
2✔
3014

1✔
3015
    Thread t;
2✔
3016
    auto do_async = [&]() {
2✔
3017
        File f(path.get_lock_path(), File::mode_Write);
2✔
3018
        f.rw_lock_shared();
2✔
3019
        File::UnlockGuard ug(f);
2✔
3020

1✔
3021
        CHECK(f.is_attached());
2✔
3022

1✔
3023
        f.resize(0);
2✔
3024
        f.sync();
2✔
3025

1✔
3026
        mutex.lock();
2✔
3027
        test_stage = 1;
2✔
3028
        mutex.unlock();
2✔
3029

1✔
3030
        millisleep(100);
2✔
3031
        // the lock is then released and the other thread will be able to initialise properly
1✔
3032
    };
2✔
3033
    t.start(do_async);
2✔
3034

1✔
3035
    wait_for(1, mutex, test_stage);
2✔
3036

1✔
3037
    // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves
1✔
3038
    sg = DB::create(path, no_create, options);
2✔
3039
    CHECK(sg->is_attached());
2✔
3040
    sg->close();
2✔
3041

1✔
3042
    t.join();
2✔
3043
}
2✔
3044

3045

3046
TEST(Shared_LockFileSpinsOnInitComplete)
3047
{
2✔
3048
    SHARED_GROUP_TEST_PATH(path);
2✔
3049

1✔
3050
    bool no_create = false;
2✔
3051
    DBOptions options;
2✔
3052
    options.encryption_key = crypt_key();
2✔
3053
    DBRef sg = DB::create(path, no_create, options);
2✔
3054
    sg->close();
2✔
3055

1✔
3056
    CHECK(File::exists(path));
2✔
3057
    CHECK(File::exists(path.get_lock_path()));
2✔
3058

1✔
3059
    std::mutex mutex;
2✔
3060
    size_t test_stage = 0;
2✔
3061

1✔
3062
    Thread t;
2✔
3063
    auto do_async = [&]() {
2✔
3064
        File f(path.get_lock_path(), File::mode_Write);
2✔
3065
        f.rw_lock_shared();
2✔
3066
        File::UnlockGuard ug(f);
2✔
3067

1✔
3068
        CHECK(f.is_attached());
2✔
3069

1✔
3070
        f.resize(1); // ftruncate will write 0 to init_complete
2✔
3071
        f.sync();
2✔
3072

1✔
3073
        mutex.lock();
2✔
3074
        test_stage = 1;
2✔
3075
        mutex.unlock();
2✔
3076

1✔
3077
        millisleep(100);
2✔
3078
        // the lock is then released and the other thread will be able to initialise properly
1✔
3079
    };
2✔
3080
    t.start(do_async);
2✔
3081

1✔
3082
    wait_for(1, mutex, test_stage);
2✔
3083

1✔
3084
    // we'll spin here without error until we can obtain the exclusive lock and initialise it ourselves
1✔
3085
    sg = DB::create(path, no_create, options);
2✔
3086
    CHECK(sg->is_attached());
2✔
3087
    sg->close();
2✔
3088

1✔
3089
    t.join();
2✔
3090
}
2✔
3091

3092

3093
TEST(Shared_LockFileOfWrongSizeThrows)
3094
{
2✔
3095
    // NOTE: This unit test attempts to mimic the initialization of the .lock file as it takes place inside
1✔
3096
    // the SharedGroup::do_open() method. NOTE: If the layout of SharedGroup::SharedInfo should change,
1✔
3097
    // this unit test might stop working.
1✔
3098

1✔
3099
    SHARED_GROUP_TEST_PATH(path);
2✔
3100

1✔
3101
    bool no_create = false;
2✔
3102
    DBOptions options;
2✔
3103
    options.encryption_key = crypt_key();
2✔
3104
    DBRef sg = DB::create(path, no_create, options);
2✔
3105
    sg->close();
2✔
3106

1✔
3107
    CHECK(File::exists(path));
2✔
3108
    CHECK(File::exists(path.get_lock_path()));
2✔
3109

1✔
3110
    std::mutex mutex;
2✔
3111
    size_t test_stage = 0;
2✔
3112

1✔
3113
    Thread t;
2✔
3114
    auto do_async = [&]() {
2✔
3115
        File f(path.get_lock_path(), File::mode_Write);
2✔
3116
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3117
        f.rw_lock_shared();
2✔
3118
        File::UnlockGuard ug(f);
2✔
3119

1✔
3120
        CHECK(f.is_attached());
2✔
3121

1✔
3122
        size_t wrong_size = 100; // < sizeof(SharedInfo)
2✔
3123
        f.resize(wrong_size);    // ftruncate will fill with 0, which will set the init_complete flag to 0.
2✔
3124
        f.seek(0);
2✔
3125

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

1✔
3131
        // set init_complete flag to 1 and sync
1✔
3132
        mem[0] = 1;
2✔
3133
        f.sync();
2✔
3134

1✔
3135
        CHECK_EQUAL(f.get_size(), wrong_size);
2✔
3136

1✔
3137
        mutex.lock();
2✔
3138
        test_stage = 1;
2✔
3139
        mutex.unlock();
2✔
3140

1✔
3141
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3142
    };
2✔
3143
    t.start(do_async);
2✔
3144

1✔
3145
    wait_for(1, mutex, test_stage);
2✔
3146

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

1✔
3152
    mutex.lock();
2✔
3153
    test_stage = 2;
2✔
3154
    mutex.unlock();
2✔
3155

1✔
3156
    t.join();
2✔
3157
}
2✔
3158

3159

3160
TEST(Shared_LockFileOfWrongVersionThrows)
3161
{
2✔
3162
    SHARED_GROUP_TEST_PATH(path);
2✔
3163

1✔
3164
    bool no_create = false;
2✔
3165
    DBOptions options;
2✔
3166
    options.encryption_key = crypt_key();
2✔
3167
    DBRef sg = DB::create(path, no_create, options);
2✔
3168

1✔
3169
    CHECK(File::exists(path));
2✔
3170
    CHECK(File::exists(path.get_lock_path()));
2✔
3171

1✔
3172
    std::mutex mutex;
2✔
3173
    size_t test_stage = 0;
2✔
3174

1✔
3175
    Thread t;
2✔
3176
    auto do_async = [&]() {
2✔
3177
        CHECK(File::exists(path.get_lock_path()));
2✔
3178

1✔
3179
        File f;
2✔
3180
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3181
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3182

1✔
3183
        f.rw_lock_shared();
2✔
3184
        File::UnlockGuard ug(f);
2✔
3185

1✔
3186
        CHECK(f.is_attached());
2✔
3187
        f.seek(6);
2✔
3188
        char bad_version = 0;
2✔
3189
        f.write(&bad_version, 1);
2✔
3190
        f.sync();
2✔
3191

1✔
3192
        mutex.lock();
2✔
3193
        test_stage = 1;
2✔
3194
        mutex.unlock();
2✔
3195

1✔
3196
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3197
    };
2✔
3198
    t.start(do_async);
2✔
3199

1✔
3200
    wait_for(1, mutex, test_stage);
2✔
3201
    sg->close();
2✔
3202

1✔
3203
    // we expect to throw if info->shared_info_version != g_shared_info_version
1✔
3204
    CHECK_THROW(DB::create(path, no_create, options), IncompatibleLockFile);
2✔
3205
    CHECK(!sg->is_attached());
2✔
3206

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

1✔
3211
    t.join();
2✔
3212
}
2✔
3213

3214

3215
TEST(Shared_LockFileOfWrongMutexSizeThrows)
3216
{
2✔
3217
    SHARED_GROUP_TEST_PATH(path);
2✔
3218

1✔
3219
    bool no_create = false;
2✔
3220
    DBOptions options;
2✔
3221
    options.encryption_key = crypt_key();
2✔
3222
    DBRef sg = DB::create(path, no_create, options);
2✔
3223

1✔
3224
    CHECK(File::exists(path));
2✔
3225
    CHECK(File::exists(path.get_lock_path()));
2✔
3226

1✔
3227
    std::mutex mutex;
2✔
3228
    size_t test_stage = 0;
2✔
3229

1✔
3230
    Thread t;
2✔
3231
    auto do_async = [&]() {
2✔
3232
        File f;
2✔
3233
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3234
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3235
        f.rw_lock_shared();
2✔
3236
        File::UnlockGuard ug(f);
2✔
3237

1✔
3238
        CHECK(f.is_attached());
2✔
3239

1✔
3240
        char bad_mutex_size = sizeof(InterprocessMutex::SharedPart) + 1;
2✔
3241
        f.seek(1);
2✔
3242
        f.write(&bad_mutex_size, 1);
2✔
3243
        f.sync();
2✔
3244

1✔
3245
        mutex.lock();
2✔
3246
        test_stage = 1;
2✔
3247
        mutex.unlock();
2✔
3248

1✔
3249
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3250
    };
2✔
3251
    t.start(do_async);
2✔
3252

1✔
3253
    wait_for(1, mutex, test_stage);
2✔
3254

1✔
3255
    sg->close();
2✔
3256

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

1✔
3261
    mutex.lock();
2✔
3262
    test_stage = 2;
2✔
3263
    mutex.unlock();
2✔
3264

1✔
3265
    t.join();
2✔
3266
}
2✔
3267

3268

3269
TEST(Shared_LockFileOfWrongCondvarSizeThrows)
3270
{
2✔
3271
    SHARED_GROUP_TEST_PATH(path);
2✔
3272

1✔
3273
    bool no_create = false;
2✔
3274
    DBOptions options;
2✔
3275
    options.encryption_key = crypt_key();
2✔
3276
    DBRef sg = DB::create(path, no_create, options);
2✔
3277

1✔
3278
    CHECK(File::exists(path));
2✔
3279
    CHECK(File::exists(path.get_lock_path()));
2✔
3280

1✔
3281
    std::mutex mutex;
2✔
3282
    size_t test_stage = 0;
2✔
3283

1✔
3284
    Thread t;
2✔
3285
    auto do_async = [&]() {
2✔
3286
        File f;
2✔
3287
        f.open(path.get_lock_path(), File::access_ReadWrite, File::create_Auto, 0); // Throws
2✔
3288
        f.set_fifo_path(std::string(path) + ".management", "lock.fifo");
2✔
3289
        f.rw_lock_shared();
2✔
3290
        File::UnlockGuard ug(f);
2✔
3291

1✔
3292
        CHECK(f.is_attached());
2✔
3293

1✔
3294
        char bad_condvar_size = sizeof(InterprocessCondVar::SharedPart) + 1;
2✔
3295
        f.seek(2);
2✔
3296
        f.write(&bad_condvar_size, 1);
2✔
3297
        f.sync();
2✔
3298

1✔
3299
        mutex.lock();
2✔
3300
        test_stage = 1;
2✔
3301
        mutex.unlock();
2✔
3302

1✔
3303
        wait_for(2, mutex, test_stage); // hold the lock until other thread finished an open attempt
2✔
3304
    };
2✔
3305
    t.start(do_async);
2✔
3306

1✔
3307
    wait_for(1, mutex, test_stage);
2✔
3308
    sg->close();
2✔
3309

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

1✔
3314
    mutex.lock();
2✔
3315
    test_stage = 2;
2✔
3316
    mutex.unlock();
2✔
3317

1✔
3318
    t.join();
2✔
3319
}
2✔
3320

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

1✔
3331
    TransactionRef reader = sg_w->start_read();
2✔
3332
    ConstTableRef t2 = reader->get_table("Foo");
2✔
3333
    const Obj obj = t2->get_object(ObjKey(47));
2✔
3334
    CHECK_EQUAL(obj.get<int64_t>(c), 5);
2✔
3335
}
2✔
3336

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

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

3388
TEST(Shared_ConstList)
3389
{
2✔
3390
    SHARED_GROUP_TEST_PATH(path);
2✔
3391
    DBRef sg = get_test_db(path);
2✔
3392
    TransactionRef writer = sg->start_write();
2✔
3393

1✔
3394
    TableRef t = writer->add_table("Foo");
2✔
3395
    auto list_col = t->add_column_list(type_Int, "int_list");
2✔
3396
    t->create_object(ObjKey(47)).get_list<int64_t>(list_col).add(47);
2✔
3397
    writer->commit();
2✔
3398

1✔
3399
    TransactionRef reader = sg->start_read();
2✔
3400
    ConstTableRef t2 = reader->get_table("Foo");
2✔
3401
    const Obj obj = t2->get_object(ObjKey(47));
2✔
3402
    auto list1 = obj.get_list<int64_t>(list_col);
2✔
3403

1✔
3404
    CHECK_EQUAL(list1.get(0), 47);
2✔
3405
    CHECK_EQUAL(obj.get_listbase_ptr(list_col)->size(), 1);
2✔
3406
}
2✔
3407

3408
#ifdef LEGACY_TESTS
3409

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

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

3453
TEST(Shared_RollbackFirstTransaction)
3454
{
2✔
3455
    SHARED_GROUP_TEST_PATH(path);
2✔
3456
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3457
    DBRef db = DB::create(*hist_w, path);
2✔
3458
    auto wt = db->start_write();
2✔
3459

1✔
3460
    wt->add_table("table");
2✔
3461
    wt->rollback_and_continue_as_read();
2✔
3462
}
2✔
3463

3464
TEST(Shared_SimpleTransaction)
3465
{
2✔
3466
    SHARED_GROUP_TEST_PATH(path);
2✔
3467
    std::unique_ptr<Replication> hist_r(make_in_realm_history());
2✔
3468
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3469

1✔
3470
    {
2✔
3471
        DBRef db_w = DB::create(*hist_w, path);
2✔
3472
        auto wt = db_w->start_write();
2✔
3473
        wt->verify();
2✔
3474
        wt->commit();
2✔
3475
        wt = nullptr;
2✔
3476
        wt = db_w->start_write();
2✔
3477
        wt->verify();
2✔
3478
        wt->commit();
2✔
3479
    }
2✔
3480
    DBRef db_r = DB::create(*hist_r, path);
2✔
3481
    {
2✔
3482
        auto rt = db_r->start_read();
2✔
3483
        rt->verify();
2✔
3484
    }
2✔
3485
}
2✔
3486

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

1✔
3498
    wt = nullptr;
2✔
3499
    db_w->close();
2✔
3500
    db_w = DB::create(path, true, DBOptions(key));
2✔
3501
    wt = db_w->start_write();
2✔
3502
    wt = nullptr;
2✔
3503
    db_w->close();
2✔
3504
}
2✔
3505

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

3534
TEST(Shared_GenerateObjectIdAfterRollback)
3535
{
2✔
3536
    // Test case generated in [realm-core-6.0.0-alpha.0] on Mon Aug 13 14:43:06 2018.
1✔
3537
    // REALM_MAX_BPNODE_SIZE is 1000
1✔
3538
    // ----------------------------------------------------------------------
1✔
3539
    SHARED_GROUP_TEST_PATH(path);
2✔
3540
    std::unique_ptr<Replication> hist_w(make_in_realm_history());
2✔
3541
    DBRef db_w = DB::create(*hist_w, path);
2✔
3542

1✔
3543
    auto wt = db_w->start_write();
2✔
3544

1✔
3545
    wt->add_table("Table_0");
2✔
3546
    {
2✔
3547
        std::vector<ObjKey> keys;
2✔
3548
        wt->get_table(TableKey(0))->create_objects(254, keys);
2✔
3549
    }
2✔
3550
    wt->commit_and_continue_as_read();
2✔
3551

1✔
3552
    wt->promote_to_write();
2✔
3553
    try {
2✔
3554
        wt->remove_table(TableKey(0));
2✔
3555
    }
2✔
3556
    catch (const CrossTableLinkTarget&) {
1✔
3557
    }
×
3558
    // Table accessor recycled
1✔
3559
    wt->rollback_and_continue_as_read();
2✔
3560

1✔
3561
    wt->promote_to_write();
2✔
3562
    // New table accessor created with m_next_key_value == -1
1✔
3563
    wt->get_table(TableKey(0))->clear();
2✔
3564
    {
2✔
3565
        std::vector<ObjKey> keys;
2✔
3566
        wt->get_table(TableKey(0))->create_objects(11, keys);
2✔
3567
    }
2✔
3568
    // table->m_next_key_value is now 11
1✔
3569
    wt->get_table(TableKey(0))->add_column(DataType(9), "float_1", false);
2✔
3570
    wt->rollback_and_continue_as_read();
2✔
3571

1✔
3572
    wt->promote_to_write();
2✔
3573
    // Should not try to create object with key == 11
1✔
3574
    {
2✔
3575
        std::vector<ObjKey> keys;
2✔
3576
        wt->get_table(TableKey(0))->create_objects(22, keys);
2✔
3577
    }
2✔
3578
}
2✔
3579

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

1✔
3598
    auto rt = db_w->start_read();
2✔
3599
    CHECK_NOT(rt->get_table(TableKey(0))->get_object(ObjKey(0)).is_null(col));
2✔
3600
    CHECK(rt->get_table(TableKey(0))->get_object(ObjKey(54)).is_null(col));
2✔
3601
}
2✔
3602

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

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

1✔
3648
        auto obj = table_r->get_object(0);
2✔
3649
        // Here we will need to translate a ref
1✔
3650
        auto i = obj.get<Int>(col);
2✔
3651
        CHECK_EQUAL(i, 0);
2✔
3652
    }
2✔
3653
}
2✔
3654

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

1✔
3669
        auto t = wt->add_table("Table_0");
2✔
3670
        // Create more object that can be held in a single cluster
1✔
3671
        t->create_objects(267, keys);
2✔
3672
        auto col = t->add_column(*wt->get_table("Table_0"), "link_0");
2✔
3673

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

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

3728
/*
3729
#include <valgrind/callgrind.h>
3730
TEST(Shared_TimestampQuery)
3731
{
3732
    Table table;
3733
    auto col_date = table.add_column(type_Timestamp, "date", true);
3734

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

3737
    for (int i = 0; i < 10000; i++) {
3738
        auto ndx = table.add_empty_row();
3739
        int seconds = random.draw_int_max(3600 * 24 * 10);
3740
        table.set_timestamp(col_date, ndx, Timestamp(seconds, 0));
3741
    }
3742

3743
    Query q = table.column<Timestamp>(col_date) > Timestamp(3600 * 24 * 5, 3);
3744
    auto start = std::chrono::steady_clock::now();
3745
    CALLGRIND_START_INSTRUMENTATION;
3746
    auto cnt = q.count();
3747
    CALLGRIND_STOP_INSTRUMENTATION;
3748
    auto end = std::chrono::steady_clock::now();
3749

3750
    std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << " us"
3751
              << std::endl;
3752
    CHECK_GREATER(cnt, 50000);
3753
}
3754
*/
3755

3756
TEST_IF(Shared_LargeFile, TEST_DURATION > 0 && !REALM_ANDROID)
3757
{
×
3758
    SHARED_GROUP_TEST_PATH(path);
×
3759
    DBOptions options;
×
3760
    options.durability = DBOptions::Durability::MemOnly;
×
3761
    DBRef db = DB::create(path, false, options);
×
3762

3763
    auto tr = db->start_write();
×
3764

3765
    auto foo = tr->add_table("foo");
×
3766
    // Create more than 16 columns. The cluster array will always have a minimum
3767
    // size of 128, so if number of columns is lower than 17, then the array will
3768
    // always be able to hold 64 bit values.
3769
    for (size_t i = 0; i < 20; i++) {
×
3770
        std::string name = "Prop" + util::to_string(i);
×
3771
        foo->add_column(type_Int, name);
×
3772
    }
×
3773
    auto bar = tr->add_table("bar");
×
3774
    auto col_str = bar->add_column(type_String, "str");
×
3775

3776
    // Create enough objects to have a multi level cluster
3777
    for (size_t i = 0; i < 400; i++) {
×
3778
        foo->create_object();
×
3779
    }
×
3780

3781
    // Add a lot of data (nearly 2 Gb)
3782
    std::string string_1M(1024 * 1024, 'A');
×
3783
    for (size_t i = 0; i < 1900; i++) {
×
3784
        bar->create_object().set(col_str, string_1M);
×
3785
    }
×
3786
    tr->commit();
×
3787
    tr = db->start_write();
×
3788
    foo = tr->get_table("foo");
×
3789
    bar = tr->get_table("bar");
×
3790

3791
    std::vector<ObjKey> keys;
×
3792
    foo->create_objects(10000, keys);
×
3793

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

3816
TEST(Shared_EncryptionBug)
3817
{
2✔
3818
    SHARED_GROUP_TEST_PATH(path);
2✔
3819
    DBOptions options;
2✔
3820
    options.encryption_key = crypt_key(true);
2✔
3821
    {
2✔
3822
        DBRef db = DB::create(path, false, options);
2✔
3823
        {
2✔
3824
            WriteTransaction wt(db);
2✔
3825
            auto foo = wt.add_table("foo");
2✔
3826
            auto col_str = foo->add_column(type_String, "str");
2✔
3827
            std::string string_1M(1024 * 1024, 'A');
2✔
3828
            for (int i = 0; i < 64; i++) {
130✔
3829
                foo->create_object().set(col_str, string_1M);
128✔
3830
            }
128✔
3831
            wt.commit();
2✔
3832
        }
2✔
3833
        for (int i = 0; i < 2; i++) {
6✔
3834
            WriteTransaction wt(db);
4✔
3835
            auto foo = wt.get_table("foo");
4✔
3836
            auto col_str = foo->get_column_key("str");
4✔
3837
            foo->create_object().set(col_str, "boobar");
4✔
3838
            wt.commit();
4✔
3839
        }
4✔
3840
    }
2✔
3841

1✔
3842
    {
2✔
3843
        DBRef db = DB::create(path, false, options);
2✔
3844
        db->start_read()->verify();
2✔
3845
    }
2✔
3846
}
2✔
3847

3848
TEST(Shared_ManyColumns)
3849
{
2✔
3850
    // We had a bug where cluster array has to expand, but the new ref
1✔
3851
    // was not updated in the parent.
1✔
3852

1✔
3853
    SHARED_GROUP_TEST_PATH(path);
2✔
3854
    auto hist = make_in_realm_history();
2✔
3855
    DBRef db = DB::create(*hist, path);
2✔
3856

1✔
3857
    auto tr = db->start_write();
2✔
3858

1✔
3859
    auto foo = tr->add_table("foo");
2✔
3860
    // Create many columns. The cluster array will not be able to
1✔
3861
    // expand from 16 to 32 bits within the minimum allocation of 128 bytes.
1✔
3862
    for (size_t i = 0; i < 50; i++) {
102✔
3863
        std::string name = "Prop" + util::to_string(i);
100✔
3864
        foo->add_column(type_Int, name);
100✔
3865
    }
100✔
3866
    auto bar = tr->add_table("bar");
2✔
3867

1✔
3868
    tr->commit();
2✔
3869
    tr = db->start_write();
2✔
3870
    foo = tr->get_table("foo");
2✔
3871
    bar = tr->get_table("bar");
2✔
3872

1✔
3873
    std::vector<ObjKey> keys;
2✔
3874
    foo->create_objects(10000, keys);
2✔
3875

1✔
3876
    tr->commit_and_continue_as_read();
2✔
3877
    tr->promote_to_write();
2✔
3878

1✔
3879
    auto first = foo->begin();
2✔
3880
    for (auto col : foo->get_column_keys()) {
100✔
3881
        // 32 bit values will be inserted in the cluster array, so it needs expansion
50✔
3882
        first->set(col, 500);
100✔
3883
    }
100✔
3884

1✔
3885
    tr->commit_and_continue_as_read();
2✔
3886
    tr->verify();
2✔
3887

1✔
3888
    tr->promote_to_write();
2✔
3889
    foo->clear();
2✔
3890
    foo->create_object().set("Prop0", 500);
2✔
3891
}
2✔
3892

3893
TEST(Shared_MultipleDBInstances)
3894
{
2✔
3895
    SHARED_GROUP_TEST_PATH(path);
2✔
3896
    {
2✔
3897
        auto hist = make_in_realm_history();
2✔
3898
        DBRef db = DB::create(*hist, path);
2✔
3899
        auto tr = db->start_write();
2✔
3900
        auto t = tr->add_table("foo");
2✔
3901
        t->create_object();
2✔
3902
        t->add_column(type_Int, "value");
2✔
3903
        tr->commit();
2✔
3904
    }
2✔
3905

1✔
3906
    auto hist1 = make_in_realm_history();
2✔
3907
    DBRef db1 = DB::create(*hist1, path);
2✔
3908
    auto hist2 = make_in_realm_history();
2✔
3909
    DBRef db2 = DB::create(*hist2, path);
2✔
3910

1✔
3911
    auto tr = db1->start_write();
2✔
3912
    tr->commit();
2✔
3913
    // db1 now has m_youngest_live_version=3, db2 has m_youngest_live_version=2
1✔
3914

1✔
3915
    auto frozen = db2->start_frozen(); // version=3
2✔
3916
    auto table = frozen->get_table("foo");
2✔
3917

1✔
3918
    tr = db2->start_write();
2✔
3919
    // creates a new mapping and incorrectly marks the old one as being for
1✔
3920
    // version 2 rather than 3
1✔
3921
    tr->get_table("foo")->create_object();
2✔
3922
    // deletes the old mapping even though version 3 still needs it
1✔
3923
    tr->commit();
2✔
3924

1✔
3925
    // tries to use deleted mapping
1✔
3926
    CHECK_EQUAL(table->get_object(0).get<int64_t>("value"), 0);
2✔
3927
}
2✔
3928

3929
TEST(Shared_WriteCopy)
3930
{
2✔
3931
    SHARED_GROUP_TEST_PATH(path1);
2✔
3932
    SHARED_GROUP_TEST_PATH(path2);
2✔
3933
    SHARED_GROUP_TEST_PATH(path3);
2✔
3934

1✔
3935
    {
2✔
3936
        auto hist = make_in_realm_history();
2✔
3937
        DBRef db = DB::create(*hist, path1);
2✔
3938
        auto tr = db->start_write();
2✔
3939
        auto t = tr->add_table("foo");
2✔
3940
        t->create_object();
2✔
3941
        t->add_column(type_Int, "value");
2✔
3942
        tr->commit();
2✔
3943

1✔
3944
        db->write_copy(path2.c_str(), nullptr);
2✔
3945
        CHECK_THROW_ANY(db->write_copy(path2.c_str(), nullptr)); // Not allowed to overwrite
2✔
3946
    }
2✔
3947
    {
2✔
3948
        auto hist = make_in_realm_history();
2✔
3949
        DBRef db = DB::create(*hist, path2);
2✔
3950
        db->write_copy(path3.c_str(), nullptr);
2✔
3951
    }
2✔
3952
    auto hist = make_in_realm_history();
2✔
3953
    DBRef db = DB::create(*hist, path3);
2✔
3954
    CHECK_EQUAL(db->start_read()->get_table("foo")->size(), 1);
2✔
3955
}
2✔
3956

3957
TEST(Shared_CompareGroups)
3958
{
2✔
3959
    SHARED_GROUP_TEST_PATH(path1);
2✔
3960
    SHARED_GROUP_TEST_PATH(path2);
2✔
3961

1✔
3962
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
3963
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
3964

1✔
3965
    {
2✔
3966
        // Create schema in DB1
1✔
3967
        auto wt = db1->start_write();
2✔
3968
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
3969
        embedded->add_column(type_Float, "float");
2✔
3970
        // Embedded in embedded
1✔
3971
        embedded->add_column_dictionary(*embedded, "additional");
2✔
3972
        wt->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
3973

1✔
3974
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
3975
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
3976

1✔
3977
        baas->add_column(type_Bool, "bool");
2✔
3978
        baas->add_column_list(type_Int, "list");
2✔
3979
        baas->add_column_set(type_Int, "set");
2✔
3980
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
3981
        baas->add_column(*embedded, "embedded");
2✔
3982
        baas->add_column(type_Mixed, "any", true);
2✔
3983
        baas->add_column(*foos, "link");
2✔
3984

1✔
3985
        foos->add_column(type_String, "str");
2✔
3986
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
3987
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
3988
        foos->add_column_list(*baas, "link_list");
2✔
3989
        wt->commit();
2✔
3990
    }
2✔
3991
    {
2✔
3992
        // Create schema in DB2 - slightly different
1✔
3993
        auto wt = db2->start_write();
2✔
3994
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
3995

1✔
3996
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
3997
        embedded->add_column(type_Float, "float");
2✔
3998
        // Embedded in embedded
1✔
3999
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4000

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

1✔
4003
        baas->add_column_set(type_Int, "set");
2✔
4004
        baas->add_column(type_Mixed, "any", true);
2✔
4005
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4006
        baas->add_column(*embedded, "embedded");
2✔
4007
        baas->add_column(type_Bool, "bool");
2✔
4008
        baas->add_column(*foos, "link");
2✔
4009
        baas->add_column_list(type_Int, "list");
2✔
4010

1✔
4011
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4012
        foos->add_column(type_String, "str");
2✔
4013
        foos->add_column_list(*baas, "link_list");
2✔
4014
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4015

1✔
4016
        wt->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4017
        wt->commit();
2✔
4018
    }
2✔
4019
    auto create_objects = [&](DBRef db) {
4✔
4020
        auto wt = db->start_write();
4✔
4021

2✔
4022
        auto foos = wt->get_table("class_Foo");
4✔
4023
        auto baas = wt->get_table("class_Baa");
4✔
4024

2✔
4025
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
4✔
4026
        auto children = foo.get_linklist("list_of_embedded");
4✔
4027
        children.create_and_insert_linked_object(0).set("float", 10.f);
4✔
4028
        children.create_and_insert_linked_object(1).set("float", 20.f);
4✔
4029

2✔
4030
        auto baa = baas->create_object_with_primary_key(999);
4✔
4031
        baa.set("link", foo.get_key());
4✔
4032
        baa.set("bool", true);
4✔
4033
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
4✔
4034
        obj.set("float", 42.f);
4✔
4035
        auto additional = obj.get_dictionary("additional");
4✔
4036
        additional.create_and_insert_linked_object("One").set("float", 1.f);
4✔
4037
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
4✔
4038
        additional.create_and_insert_linked_object("Three").set("float", 3.f);
4✔
4039

2✔
4040
        foo.get_linklist("link_list").add(baa.get_key());
4✔
4041

2✔
4042
        // Basic collections
2✔
4043
        auto list = baa.get_list<Int>("list");
4✔
4044
        list.add(1);
4✔
4045
        list.add(2);
4✔
4046
        list.add(3);
4✔
4047
        auto set = baa.get_set<Int>("set");
4✔
4048
        set.insert(4);
4✔
4049
        set.insert(5);
4✔
4050
        set.insert(6);
4✔
4051
        auto dict = baa.get_dictionary("dictionary");
4✔
4052
        dict.insert("key7", 7);
4✔
4053
        dict.insert("key8", 8);
4✔
4054
        dict.insert("key9", 9);
4✔
4055

2✔
4056
        wt->commit();
4✔
4057
    };
4✔
4058
    create_objects(db1);
2✔
4059
    create_objects(db2);
2✔
4060
    CHECK(*db1->start_read() == *db2->start_read());
2✔
4061
    {
2✔
4062
        auto wt = db2->start_write();
2✔
4063
        auto baas = wt->get_table("class_Baa");
2✔
4064
        auto obj = baas->get_object_with_primary_key(999);
2✔
4065
        auto embedded = obj.get_linked_object("embedded");
2✔
4066
        embedded.get_dictionary("additional").get_object("One").set("float", 555.f);
2✔
4067
        wt->commit();
2✔
4068
    }
2✔
4069
    CHECK_NOT(*db1->start_read() == *db2->start_read());
2✔
4070
}
2✔
4071

4072
TEST(Shared_CopyReplication)
4073
{
2✔
4074
    SHARED_GROUP_TEST_PATH(path1);
2✔
4075
    SHARED_GROUP_TEST_PATH(path2);
2✔
4076

1✔
4077
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4078
    auto wt = db2->start_write();
2✔
4079

1✔
4080
    _impl::CopyReplication repl(wt);
2✔
4081
    DBRef db1 = DB::create(repl, path1);
2✔
4082
    auto tr = db1->start_write();
2✔
4083

1✔
4084
    // First create the local realm
1✔
4085
    {
2✔
4086
        // Create schema
1✔
4087
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
4088
        embedded->add_column(type_Float, "float");
2✔
4089
        // Embedded in embedded
1✔
4090
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4091
        tr->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4092

1✔
4093
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4094
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4095

1✔
4096
        baas->add_column(type_Bool, "bool");
2✔
4097
        baas->add_column_list(type_Int, "list");
2✔
4098
        baas->add_column_set(type_Int, "set");
2✔
4099
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4100
        baas->add_column(*embedded, "embedded");
2✔
4101
        baas->add_column(type_Mixed, "any", true);
2✔
4102
        baas->add_column(*foos, "link");
2✔
4103

1✔
4104
        foos->add_column(type_String, "str");
2✔
4105
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4106
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4107
        foos->add_column_list(*baas, "link_list");
2✔
4108

1✔
4109

1✔
4110
        /* Create local objects */
1✔
4111
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
2✔
4112
        auto children = foo.get_linklist("list_of_embedded");
2✔
4113
        children.create_and_insert_linked_object(0).set("float", 10.f);
2✔
4114
        children.create_and_insert_linked_object(1).set("float", 20.f);
2✔
4115

1✔
4116
        auto baa = baas->create_object_with_primary_key(999);
2✔
4117
        baa.set("link", foo.get_key());
2✔
4118
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4119
        obj.set("float", 42.f);
2✔
4120
        auto additional = obj.get_dictionary("additional");
2✔
4121

1✔
4122
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4123

1✔
4124
        // Basic collections
1✔
4125
        auto list = baa.get_list<Int>("list");
2✔
4126
        list.add(1);
2✔
4127
        list.add(2);
2✔
4128
        list.add(3);
2✔
4129
        auto set = baa.get_set<Int>("set");
2✔
4130
        set.insert(4);
2✔
4131
        set.insert(5);
2✔
4132
        set.insert(6);
2✔
4133
        auto dict = baa.get_dictionary("dictionary");
2✔
4134
        dict.insert("key7", 7);
2✔
4135
        dict.insert("key8", 8);
2✔
4136
        dict.insert("key9", 9);
2✔
4137

1✔
4138
        additional.create_and_insert_linked_object("One").set("float", 1.f);
2✔
4139
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
2✔
4140
        auto embedded_obj = additional.create_and_insert_linked_object("Three");
2✔
4141

1✔
4142
        auto baa1 = baas->create_object_with_primary_key(666).set("link", foo.get_key());
2✔
4143
        obj = baa1.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4144
        additional = obj.get_dictionary("additional");
2✔
4145
        additional.create_and_insert_linked_object("Item").set("float", 100.f);
2✔
4146
        obj.set("float", 35.f);
2✔
4147
        foo = foos->create_object_with_primary_key("456");
2✔
4148
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4149
        any_list.add(Mixed(baa1.get_link()));
2✔
4150
        any_list.add(Mixed(foo.get_link()));
2✔
4151
        foos->create_object_with_primary_key("789");
2✔
4152
        baa1.set("any", Mixed(foo.get_link()));
2✔
4153
        baa.set("bool", true);
2✔
4154
        embedded_obj.set("float", 3.f);
2✔
4155
    }
2✔
4156
    tr->commit();
2✔
4157

1✔
4158
    wt->commit_and_continue_as_read();
2✔
4159
    auto rt = db1->start_read();
2✔
4160
    CHECK(*rt == *wt);
2✔
4161
}
2✔
4162

4163
TEST(Shared_WriteTo)
4164
{
2✔
4165
    SHARED_GROUP_TEST_PATH(path1);
2✔
4166
    SHARED_GROUP_TEST_PATH(path2);
2✔
4167

1✔
4168
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
4169
    auto tr = db1->start_write();
2✔
4170

1✔
4171
    // First create the local realm
1✔
4172
    {
2✔
4173
        // Create schema
1✔
4174
        auto embedded = tr->add_table("class_Embedded", Table::Type::Embedded);
2✔
4175
        embedded->add_column(type_Float, "float");
2✔
4176
        // Embedded in embedded
1✔
4177
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4178
        tr->add_table("class_AnotherEmbedded", Table::Type::Embedded);
2✔
4179

1✔
4180
        auto baas = tr->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4181
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4182

1✔
4183
        baas->add_column(type_Bool, "bool");
2✔
4184
        baas->add_column_list(type_Int, "list");
2✔
4185
        baas->add_column_set(type_Int, "set");
2✔
4186
        baas->add_column_dictionary(type_Int, "dictionary");
2✔
4187
        baas->add_column(*embedded, "embedded");
2✔
4188
        baas->add_column(type_Mixed, "any", true);
2✔
4189
        baas->add_column(*foos, "link");
2✔
4190

1✔
4191
        auto col_str = foos->add_column(type_String, "str");
2✔
4192
        foos->add_search_index(col_str);
2✔
4193
        foos->add_column_list(*embedded, "list_of_embedded");
2✔
4194
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4195
        foos->add_column_list(*baas, "link_list");
2✔
4196

1✔
4197

1✔
4198
        /* Create local objects */
1✔
4199
        auto foo = foos->create_object_with_primary_key("123").set("str", "Hello");
2✔
4200
        auto children = foo.get_linklist("list_of_embedded");
2✔
4201
        children.create_and_insert_linked_object(0).set("float", 10.f);
2✔
4202
        children.create_and_insert_linked_object(1).set("float", 20.f);
2✔
4203

1✔
4204
        auto baa = baas->create_object_with_primary_key(999);
2✔
4205
        baa.set("link", foo.get_key());
2✔
4206
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4207
        obj.set("float", 42.f);
2✔
4208
        auto additional = obj.get_dictionary("additional");
2✔
4209
        additional.create_and_insert_linked_object("One").set("float", 1.f);
2✔
4210
        additional.create_and_insert_linked_object("Two").set("float", 2.f);
2✔
4211
        additional.create_and_insert_linked_object("Three").set("float", 3.f);
2✔
4212

1✔
4213
        foo.get_linklist("link_list").add(baa.get_key());
2✔
4214

1✔
4215
        // Basic collections
1✔
4216
        auto list = baa.get_list<Int>("list");
2✔
4217
        list.add(1);
2✔
4218
        list.add(2);
2✔
4219
        list.add(3);
2✔
4220
        auto set = baa.get_set<Int>("set");
2✔
4221
        set.insert(4);
2✔
4222
        set.insert(5);
2✔
4223
        set.insert(6);
2✔
4224
        auto dict = baa.get_dictionary("dictionary");
2✔
4225
        dict.insert("key7", 7);
2✔
4226
        dict.insert("key8", 8);
2✔
4227
        dict.insert("key9", 9);
2✔
4228

1✔
4229
        auto baa1 = baas->create_object_with_primary_key(666).set("link", foo.get_key());
2✔
4230
        obj = baa1.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4231
        additional = obj.get_dictionary("additional");
2✔
4232
        additional.create_and_insert_linked_object("Item").set("float", 100.f);
2✔
4233
        obj.set("float", 35.f);
2✔
4234
        foo = foos->create_object_with_primary_key("456");
2✔
4235
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4236
        any_list.add(Mixed(baa1.get_link()));
2✔
4237
        any_list.add(Mixed(foo.get_link()));
2✔
4238
        foos->create_object_with_primary_key("789");
2✔
4239
        baa1.set("any", Mixed(foo.get_link()));
2✔
4240
        baa.set("bool", true);
2✔
4241
    }
2✔
4242
    tr->commit_and_continue_as_read();
2✔
4243
    // tr->to_json(std::cout);
1✔
4244

1✔
4245

1✔
4246
    // Create remote db
1✔
4247
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4248
    {
2✔
4249
        auto wt = db2->start_write();
2✔
4250

1✔
4251
        // Create schema - where some columns are missing. This will cause the
1✔
4252
        // ColKeys to be different
1✔
4253
        auto embedded = wt->add_table("class_Embedded", Table::Type::Embedded);
2✔
4254
        embedded->add_column(type_Float, "float");
2✔
4255
        // Embedded in embedded
1✔
4256
        embedded->add_column_dictionary(*embedded, "additional");
2✔
4257

1✔
4258
        auto baas = wt->add_table_with_primary_key("class_Baa", type_Int, "_id");
2✔
4259
        baas->add_column(*embedded, "embedded");
2✔
4260

1✔
4261
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4262
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4263

1✔
4264
        baas->create_object_with_primary_key(333);
2✔
4265
        auto baa = baas->create_object_with_primary_key(666);
2✔
4266
        auto obj = baa.create_and_set_linked_object(baas->get_column_key("embedded"));
2✔
4267
        obj.set("float", 99.f);
2✔
4268
        auto additional = obj.get_dictionary("additional");
2✔
4269
        additional.create_and_insert_linked_object("Item").set("float", 200.f);
2✔
4270
        foos->create_object_with_primary_key("789").get_list<Mixed>("list_of_any").add(Mixed(baa.get_link()));
2✔
4271
        wt->commit();
2✔
4272
    }
2✔
4273

1✔
4274
    // Copy local object over
1✔
4275
    auto dest = db2->start_write();
2✔
4276
    tr->copy_to(dest);
2✔
4277
    dest->commit_and_continue_as_read();
2✔
4278

1✔
4279
    // The difference between the two realms should now be that the remote db has an
1✔
4280
    // extra baa object with pk 333.
1✔
4281
    CHECK_NOT(*tr == *dest);
2✔
4282
    tr->promote_to_write();
2✔
4283
    // So if we add this to the local realm, they should match
1✔
4284
    tr->get_table("class_Baa")->create_object_with_primary_key(333);
2✔
4285
    tr->commit_and_continue_as_read();
2✔
4286
    CHECK(*tr == *dest);
2✔
4287
}
2✔
4288

4289
TEST(Shared_WriteToFail)
4290
{
2✔
4291
    SHARED_GROUP_TEST_PATH(path1);
2✔
4292
    SHARED_GROUP_TEST_PATH(path2);
2✔
4293

1✔
4294
    DBRef db1 = DB::create(make_in_realm_history(), path1);
2✔
4295
    auto tr = db1->start_write();
2✔
4296

1✔
4297
    // First create the local realm
1✔
4298
    {
2✔
4299
        auto foos = tr->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4300

1✔
4301
        foos->add_column(type_String, "classification");
2✔
4302
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4303

1✔
4304
        /* Create local objects */
1✔
4305
        auto foo = foos->create_object_with_primary_key("anders.andk@andeby.io").set("classification", "Duck");
2✔
4306
        auto any_list = foo.get_list<Mixed>("list_of_any");
2✔
4307
        any_list.add(Mixed(17));
2✔
4308
        any_list.add(Mixed("Hello"));
2✔
4309
    }
2✔
4310
    tr->commit_and_continue_as_read();
2✔
4311
    // tr->to_json(std::cout);
1✔
4312

1✔
4313
    ColKey col_fail;
2✔
4314
    // Create remote db
1✔
4315
    DBRef db2 = DB::create(make_in_realm_history(), path2);
2✔
4316
    {
2✔
4317
        auto wt = db2->start_write();
2✔
4318
        auto foos = wt->add_table_with_primary_key("class_Foo", type_String, "_id");
2✔
4319
        col_fail = foos->add_column(type_Int, "classification");
2✔
4320
        foos->add_column_list(type_Mixed, "list_of_any", true);
2✔
4321
        wt->commit();
2✔
4322
    }
2✔
4323

1✔
4324
    auto dest = db2->start_write();
2✔
4325
    std::string message;
2✔
4326

1✔
4327
    CHECK_THROW_ANY_GET_MESSAGE(tr->copy_to(dest), message);
2✔
4328
    CHECK_EQUAL(message, "Incompatible property: class_Foo::classification");
2✔
4329

1✔
4330
    auto foos = dest->get_table("class_Foo");
2✔
4331
    foos->remove_column(col_fail);
2✔
4332
    col_fail = foos->add_column(type_String, "classification", true); // Now nullable
2✔
4333
    dest->commit();
2✔
4334
    dest = db2->start_write();
2✔
4335

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

1✔
4339
    foos = dest->get_table("class_Foo");
2✔
4340
    foos->remove_column(col_fail);
2✔
4341
    col_fail = foos->add_column(type_String, "classification");
2✔
4342
    dest->commit();
2✔
4343
    dest = db2->start_write();
2✔
4344
    tr->copy_to(dest);
2✔
4345
    dest->commit_and_continue_as_read();
2✔
4346

1✔
4347
    CHECK(*tr == *dest);
2✔
4348
}
2✔
4349

4350
NONCONCURRENT_TEST_IF(Shared_LockFileConcurrentInit, testing_supports_spawn_process)
4351
{
2✔
4352
    auto path = realm::test_util::get_test_path(test_context.get_test_name(), ".test-dir");
2✔
4353
    test_util::TestDirGuard test_dir(path, false);
2✔
4354
    test_dir.do_remove = SpawnedProcess::is_parent();
2✔
4355
    auto lock_prefix = std::string(path) + "/lock";
2✔
4356

1✔
4357
    struct Lock {
2✔
4358
        std::unique_ptr<InterprocessMutex> mutex;
2✔
4359
        std::unique_ptr<InterprocessMutex::SharedPart> sp;
2✔
4360

1✔
4361
        Lock(const std::string& name, const std::string& lock_prefix_path)
2✔
4362
            : mutex(std::make_unique<InterprocessMutex>())
2✔
4363
            , sp(std::make_unique<InterprocessMutex::SharedPart>())
2✔
4364
        {
1✔
NEW
4365
            mutex->set_shared_part(*sp, lock_prefix_path, name);
×
NEW
4366
        }
×
4367

1✔
4368
        Lock(Lock&&) = default;
1✔
4369
        Lock& operator=(Lock&&) = default;
2✔
4370
    };
2✔
4371

1✔
4372
    for (size_t i = 0; i < 10; ++i) {
22✔
4373
        std::vector<std::unique_ptr<SpawnedProcess>> spawned;
20✔
4374

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

100✔
4380
            if (spawned.back()->is_child()) {
200✔
NEW
4381
                std::vector<Lock> locks;
×
4382

4383
                // mimic the same impl detail as in DB and hope it'd trigger some assertions
NEW
4384
                for (auto tag : {"write", "control", "versions"}) {
×
NEW
4385
                    locks.emplace_back(tag, lock_prefix);
×
NEW
4386
                    CHECK(locks.back().mutex->is_valid());
×
NEW
4387
                }
×
4388

4389
                // if somehow initialization is scrambled or there is an issues with
4390
                // underlying files then it should hang here
NEW
4391
                for (int k = 0; k < 3; ++k) {
×
NEW
4392
                    for (auto&& lock : locks)
×
NEW
4393
                        lock.mutex->lock();
×
NEW
4394
                    for (auto&& lock : locks)
×
NEW
4395
                        lock.mutex->unlock();
×
NEW
4396
                }
×
4397

NEW
4398
                exit(0);
×
NEW
4399
            }
×
4400
        }
200✔
4401

10✔
4402
        if (SpawnedProcess::is_parent()) {
20✔
4403
            for (auto&& process : spawned)
20✔
4404
                process->wait_for_child_to_finish();
200✔
4405

10✔
4406
            // start everytime with no lock files for mutexes
10✔
4407
            test_dir.clean_dir();
20✔
4408
        }
20✔
4409
    }
20✔
4410
}
2✔
4411

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