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

realm / realm-core / 2210

09 Apr 2024 03:41PM UTC coverage: 92.601% (+0.5%) from 92.106%
2210

push

Evergreen

web-flow
Merge pull request #7300 from realm/tg/rework-metadata-storage

Rework sync user handling and metadata storage

102800 of 195548 branches covered (52.57%)

3051 of 3153 new or added lines in 46 files covered. (96.76%)

41 existing lines in 11 files now uncovered.

249129 of 269035 relevant lines covered (92.6%)

46864217.27 hits per line

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

99.77
/test/object-store/realm.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 "util/event_loop.hpp"
20
#include "util/test_file.hpp"
21
#include "util/test_utils.hpp"
22
#include "../util/semaphore.hpp"
23

24
#include <realm/db.hpp>
25
#include <realm/history.hpp>
26

27
#include <realm/impl/simulated_failure.hpp>
28

29
#include <realm/object-store/binding_context.hpp>
30
#include <realm/object-store/keypath_helpers.hpp>
31
#include <realm/object-store/object_schema.hpp>
32
#include <realm/object-store/object_store.hpp>
33
#include <realm/object-store/property.hpp>
34
#include <realm/object-store/results.hpp>
35
#include <realm/object-store/schema.hpp>
36
#include <realm/object-store/class.hpp>
37
#include <realm/object-store/thread_safe_reference.hpp>
38
#include <realm/object-store/impl/realm_coordinator.hpp>
39
#include <realm/object-store/util/event_loop_dispatcher.hpp>
40
#include <realm/object-store/util/scheduler.hpp>
41

42
#include <realm/util/base64.hpp>
43
#include <realm/util/fifo_helper.hpp>
44
#include <realm/util/scope_exit.hpp>
45

46
#if REALM_ENABLE_SYNC
47
#include <util/sync/flx_sync_harness.hpp>
48

49
#include <realm/object-store/sync/async_open_task.hpp>
50
#include <realm/object-store/sync/impl/sync_metadata.hpp>
51
#include <realm/object-store/sync/sync_session.hpp>
52
#include <realm/sync/noinst/client_history_impl.hpp>
53
#include <realm/sync/subscriptions.hpp>
54
#endif
55

56
#include <catch2/catch_all.hpp>
57
#include <catch2/matchers/catch_matchers_string.hpp>
58

59
#include <external/json/json.hpp>
60

61
#include <array>
62
#if REALM_HAVE_UV
63
#include <uv.h>
64
#endif
65

66
namespace realm {
67
class TestHelper {
68
public:
69
    static DBRef& get_db(SharedRealm const& shared_realm)
70
    {
57,344✔
71
        return Realm::Internal::get_db(*shared_realm);
60,928✔
72
    }
60,928✔
73

3,584✔
74
    static void begin_read(SharedRealm const& shared_realm, VersionID version)
75
    {
16✔
76
        Realm::Internal::begin_read(*shared_realm, version);
17✔
77
    }
17✔
78
};
1✔
79

80
static bool operator==(IndexSet const& a, IndexSet const& b)
81
{
48✔
82
    return std::equal(a.as_indexes().begin(), a.as_indexes().end(), b.as_indexes().begin(), b.as_indexes().end());
51✔
83
}
51✔
84
} // namespace realm
3✔
85

86
using namespace realm;
87

88
namespace {
89
class Observer : public BindingContext {
90
public:
91
    Observer(Obj& obj)
92
    {
16✔
93
        m_result.push_back(ObserverState{obj.get_table()->get_key(), obj.get_key(), nullptr});
17✔
94
    }
17✔
95

1✔
96
    IndexSet array_change(size_t index, ColKey col_key) const noexcept
97
    {
48✔
98
        auto& changes = m_result[index].changes;
51✔
99
        auto col = changes.find(col_key.value);
51✔
100
        return col == changes.end() ? IndexSet{} : col->second.indices;
51✔
101
    }
51✔
102

3✔
103
private:
104
    std::vector<ObserverState> m_result;
105
    std::vector<void*> m_invalidated;
106

107
    std::vector<ObserverState> get_observed_rows() override
108
    {
32✔
109
        return m_result;
34✔
110
    }
34✔
111

2✔
112
    void did_change(std::vector<ObserverState> const& observers, std::vector<void*> const& invalidated, bool) override
113
    {
32✔
114
        m_invalidated = invalidated;
34✔
115
        m_result = observers;
34✔
116
    }
34✔
117
};
2✔
118
} // namespace
119

120
TEST_CASE("SharedRealm: get_shared_realm()") {
608✔
121
    TestFile config;
646✔
122
    config.schema_version = 1;
646✔
123
    config.schema = Schema{
646✔
124
        {"object", {{"value", PropertyType::Int}}},
646✔
125
    };
646✔
126

342✔
127
    SECTION("should return the same instance when caching is enabled") {
608✔
128
        config.cache = true;
54✔
129
        auto realm1 = Realm::get_shared_realm(config);
17✔
130
        auto realm2 = Realm::get_shared_realm(config);
17✔
131
        REQUIRE(realm1.get() == realm2.get());
17!
132
    }
17!
133

305✔
134
    SECTION("should return different instances when caching is disabled") {
608✔
135
        config.cache = false;
54✔
136
        auto realm1 = Realm::get_shared_realm(config);
17✔
137
        auto realm2 = Realm::get_shared_realm(config);
17✔
138
        REQUIRE(realm1.get() != realm2.get());
17!
139
    }
17!
140

305✔
141
    SECTION("should validate that the config is sensible") {
608✔
142
        SECTION("bad encryption key") {
182✔
143
            config.encryption_key = std::vector<char>(2, 0);
25✔
144
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidEncryptionKey,
17✔
145
                              "Encryption key must be 64 bytes.");
17✔
146
        }
17✔
147

73✔
148
        SECTION("schema without schema version") {
144✔
149
            config.schema_version = ObjectStore::NotVersioned;
25✔
150
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
17✔
151
                              "A schema version must be specified when the schema is specified");
17✔
152
        }
17✔
153

73✔
154
        SECTION("migration function for immutable") {
144✔
155
            config.schema_mode = SchemaMode::Immutable;
25✔
156
            config.migration_function = [](auto, auto, auto) {};
9✔
157
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
16✔
158
                              "Realms opened in immutable mode do not use a migration function");
17✔
159
        }
17✔
160

73✔
161
        SECTION("migration function for read-only") {
144✔
162
            config.schema_mode = SchemaMode::ReadOnly;
25✔
163
            config.migration_function = [](auto, auto, auto) {};
9✔
164
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
16✔
165
                              "Realms opened in read-only mode do not use a migration function");
17✔
166
        }
17✔
167

73✔
168
        SECTION("migration function for additive discovered") {
144✔
169
            config.schema_mode = SchemaMode::AdditiveDiscovered;
25✔
170
            config.migration_function = [](auto, auto, auto) {};
9✔
171
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
16✔
172
                              "Realms opened in Additive-only schema mode do not use a migration function");
17✔
173
        }
17✔
174

73✔
175
        SECTION("migration function for additive explicit") {
144✔
176
            config.schema_mode = SchemaMode::AdditiveExplicit;
25✔
177
            config.migration_function = [](auto, auto, auto) {};
9✔
178
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
16✔
179
                              "Realms opened in Additive-only schema mode do not use a migration function");
17✔
180
        }
17✔
181

73✔
182
        SECTION("initialization function for immutable") {
144✔
183
            config.schema_mode = SchemaMode::Immutable;
25✔
184
            config.initialization_function = [](auto) {};
9✔
185
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
16✔
186
                              "Realms opened in immutable mode do not use an initialization function");
17✔
187
        }
17✔
188

73✔
189
        SECTION("initialization function for read-only") {
144✔
190
            config.schema_mode = SchemaMode::ReadOnly;
25✔
191
            config.initialization_function = [](auto) {};
9✔
192
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
16✔
193
                              "Realms opened in read-only mode do not use an initialization function");
17✔
194
        }
17✔
195
        SECTION("in-memory encrypted realms are rejected") {
145✔
196
            config.in_memory = true;
25✔
197
            config.encryption_key = make_test_encryption_key();
17✔
198
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), IllegalCombination,
17✔
199
                              "Encryption is not supported for in-memory realms");
17✔
200
        }
17✔
201
    }
145✔
202

313✔
203
    SECTION("should reject mismatched config") {
608✔
204
        config.encryption_key.clear(); // may be set already when encrypting all
102✔
205

36✔
206
        SECTION("schema version") {
64✔
207
            auto realm = Realm::get_shared_realm(config);
20✔
208
            config.schema_version = 2;
17✔
209
            REQUIRE_EXCEPTION(
17✔
210
                Realm::get_shared_realm(config), MismatchedConfig,
17✔
211
                Catch::Matchers::Matches("Realm at path '.*' already opened with different schema version."));
17✔
212

9✔
213
            config.schema = util::none;
16✔
214
            config.schema_version = ObjectStore::NotVersioned;
17✔
215
            REQUIRE_NOTHROW(Realm::get_shared_realm(config));
17✔
216
        }
17✔
217

33✔
218
        SECTION("schema mode") {
64✔
219
            auto realm = Realm::get_shared_realm(config);
20✔
220
            config.schema_mode = SchemaMode::Manual;
17✔
221
            REQUIRE_EXCEPTION(
17✔
222
                Realm::get_shared_realm(config), MismatchedConfig,
17✔
223
                Catch::Matchers::Matches("Realm at path '.*' already opened with a different schema mode."));
17✔
224
        }
17✔
225

33✔
226
        SECTION("durability") {
64✔
227
            auto realm = Realm::get_shared_realm(config);
20✔
228
            config.in_memory = true;
17✔
229
            REQUIRE_EXCEPTION(
17✔
230
                Realm::get_shared_realm(config), MismatchedConfig,
17✔
231
                Catch::Matchers::Matches("Realm at path '.*' already opened with different inMemory settings."));
17✔
232
        }
17✔
233

33✔
234
        SECTION("schema") {
64✔
235
            auto realm = Realm::get_shared_realm(config);
20✔
236
            config.schema = Schema{
17✔
237
                {"object", {{"value", PropertyType::Int}, {"value2", PropertyType::Int}}},
17✔
238
            };
17✔
239
            REQUIRE_EXCEPTION(
17✔
240
                Realm::get_shared_realm(config), SchemaMismatch,
17✔
241
                Catch::Matchers::ContainsSubstring("Migration is required due to the following errors:"));
17✔
242
        }
17✔
243
    }
65✔
244

308✔
245

304✔
246
// Windows doesn't use fifos
304✔
247
#ifndef _WIN32
608✔
248
    SECTION("should be able to set a FIFO fallback path") {
646✔
249
        std::string fallback_dir = util::make_temp_dir() + "/fallback/";
54✔
250
        realm::util::try_make_dir(fallback_dir);
17✔
251
        TestFile config;
17✔
252
        config.fifo_files_fallback_path = fallback_dir;
17✔
253
        config.schema_version = 1;
17✔
254
        config.schema = Schema{
17✔
255
            {"object", {{"value", PropertyType::Int}}},
17✔
256
        };
17✔
257

9✔
258
        realm::util::make_dir(config.path + ".note");
16✔
259
        auto realm = Realm::get_shared_realm(config);
17✔
260
        auto fallback_file = util::format("%1realm_%2.note", fallback_dir,
17✔
261
                                          std::hash<std::string>()(config.path)); // Mirror internal implementation
17✔
262
        REQUIRE(util::File::exists(fallback_file));
17!
263
        realm::util::remove_dir(config.path + ".note");
17!
264
        REQUIRE(realm::util::try_remove_dir_recursive(fallback_dir));
17!
265
    }
17!
266

305✔
267
    SECTION("automatically append dir separator to end of fallback path") {
608✔
268
        std::string fallback_dir = util::make_temp_dir() + "/fallback";
54✔
269
        realm::util::try_make_dir(fallback_dir);
17✔
270
        TestFile config;
17✔
271
        config.fifo_files_fallback_path = fallback_dir;
17✔
272
        config.schema_version = 1;
17✔
273
        config.schema = Schema{
17✔
274
            {"object", {{"value", PropertyType::Int}}},
17✔
275
        };
17✔
276

9✔
277
        realm::util::make_dir(config.path + ".note");
16✔
278
        auto realm = Realm::get_shared_realm(config);
17✔
279
        auto fallback_file = util::format("%1/realm_%2.note", fallback_dir,
17✔
280
                                          std::hash<std::string>()(config.path)); // Mirror internal implementation
17✔
281
        REQUIRE(util::File::exists(fallback_file));
17!
282
        realm::util::remove_dir(config.path + ".note");
17!
283
        REQUIRE(realm::util::try_remove_dir_recursive(fallback_dir));
17!
284
    }
17!
285
#endif
609✔
286

342✔
287
    SECTION("should verify that the schema is valid") {
608✔
288
        config.schema =
54✔
289
            Schema{{"object",
17✔
290
                    {{"value", PropertyType::Int}},
17✔
291
                    {{"invalid backlink", PropertyType::LinkingObjects | PropertyType::Array, "object", "value"}}}};
17✔
292
        REQUIRE_THROWS_CONTAINING(Realm::get_shared_realm(config), "origin of linking objects property");
17✔
293
    }
17✔
294

305✔
295
    SECTION("should apply the schema if one is supplied") {
608✔
296
        Realm::get_shared_realm(config);
54✔
297

9✔
298
        {
16✔
299
            Group g(config.path, config.encryption_key.data());
17✔
300
            auto table = ObjectStore::table_for_object_type(g, "object");
17✔
301
            REQUIRE(table);
17!
302
            REQUIRE(table->get_column_count() == 1);
17!
303
            REQUIRE(table->get_column_name(*table->get_column_keys().begin()) == "value");
17!
304
        }
17!
305

9✔
306
        config.schema_version = 2;
16✔
307
        config.schema = Schema{
17✔
308
            {"object", {{"value", PropertyType::Int}, {"value2", PropertyType::Int}}},
17✔
309
        };
17✔
310
        bool migration_called = false;
17✔
311
        config.migration_function = [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) {
17✔
312
            migration_called = true;
17✔
313
            REQUIRE_FALSE(old_realm->auto_refresh());
17!
314
            REQUIRE(ObjectStore::table_for_object_type(old_realm->read_group(), "object")->get_column_count() == 1);
17!
315
            REQUIRE(ObjectStore::table_for_object_type(new_realm->read_group(), "object")->get_column_count() == 2);
17!
316
        };
17!
317
        Realm::get_shared_realm(config);
17✔
318
        REQUIRE(migration_called);
17!
319
    }
17!
320

305✔
321
    SECTION("should properly roll back from migration errors") {
608✔
322
        Realm::get_shared_realm(config);
54✔
323

9✔
324
        config.schema_version = 2;
16✔
325
        config.schema = Schema{
17✔
326
            {"object", {{"value", PropertyType::Int}, {"value2", PropertyType::Int}}},
17✔
327
        };
17✔
328
        bool migration_called = false;
17✔
329
        config.migration_function = [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) {
33✔
330
            REQUIRE_FALSE(old_realm->auto_refresh());
34!
331
            REQUIRE(ObjectStore::table_for_object_type(old_realm->read_group(), "object")->get_column_count() == 1);
34!
332
            REQUIRE(ObjectStore::table_for_object_type(new_realm->read_group(), "object")->get_column_count() == 2);
34!
333
            if (!migration_called) {
34✔
334
                migration_called = true;
18✔
335
                throw "error";
17✔
336
            }
17✔
337
        };
33✔
338
        REQUIRE_THROWS_WITH(Realm::get_shared_realm(config), "error");
18✔
339
        REQUIRE(migration_called);
17✔
340
        REQUIRE_NOTHROW(Realm::get_shared_realm(config));
17!
341
    }
17✔
342

305✔
343
    SECTION("should read the schema from the file if none is supplied") {
608✔
344
        Realm::get_shared_realm(config);
54✔
345

9✔
346
        config.schema = util::none;
16✔
347
        auto realm = Realm::get_shared_realm(config);
17✔
348
        REQUIRE(realm->schema().size() == 1);
17!
349
        auto it = realm->schema().find("object");
17!
350
        auto table = realm->read_group().get_table("class_object");
17✔
351
        REQUIRE(it != realm->schema().end());
17!
352
        REQUIRE(it->table_key == table->get_key());
17!
353
        REQUIRE(it->persisted_properties.size() == 1);
17!
354
        REQUIRE(it->persisted_properties[0].name == "value");
17!
355
        REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value"));
17!
356
    }
17!
357

305✔
358
    SECTION("should read the proper schema from the file if a custom version is supplied") {
608✔
359
        Realm::get_shared_realm(config);
54✔
360

9✔
361
        config.schema = util::none;
16✔
362
        config.schema_mode = SchemaMode::AdditiveExplicit;
17✔
363
        config.schema_version = 0;
17✔
364

9✔
365
        auto realm = Realm::get_shared_realm(config);
16✔
366
        REQUIRE(realm->schema().size() == 1);
17!
367

9!
368
        auto& db = TestHelper::get_db(realm);
16✔
369
        auto rt = db->start_read();
17✔
370
        VersionID old_version = rt->get_version_of_current_transaction();
17✔
371
        realm->close();
17✔
372

9✔
373
        config.schema = Schema{
16✔
374
            {"object", {{"value", PropertyType::Int}}},
17✔
375
            {"object1", {{"value", PropertyType::Int}}},
17✔
376
        };
17✔
377
        config.schema_version = 1;
17✔
378
        realm = Realm::get_shared_realm(config);
17✔
379
        REQUIRE(realm->schema().size() == 2);
17!
380

9!
381
        config.schema = util::none;
16✔
382
        auto old_realm = Realm::get_shared_realm(config);
17✔
383
        // must retain 'rt' until after opening for reading at that version
9✔
384
        TestHelper::begin_read(old_realm, old_version);
16✔
385
        rt = nullptr;
17✔
386
        REQUIRE(old_realm->schema().size() == 1);
17!
387
    }
17!
388

305✔
389
    SECTION("should sensibly handle opening an uninitialized file without a schema specified") {
608✔
390
        config.cache = GENERATE(false, true);
70✔
391

18✔
392
        // create an empty file
16✔
393
        util::File(config.path, util::File::mode_Write);
32✔
394

18✔
395
        // open the empty file, but don't initialize the schema
16✔
396
        Realm::Config config_without_schema = config;
32✔
397
        config_without_schema.schema = util::none;
34✔
398
        config_without_schema.schema_version = ObjectStore::NotVersioned;
34✔
399
        auto realm = Realm::get_shared_realm(config_without_schema);
34✔
400
        REQUIRE(realm->schema().empty());
34!
401
        REQUIRE(realm->schema_version() == ObjectStore::NotVersioned);
34!
402
        // verify that we can get another Realm instance
18!
403
        REQUIRE_NOTHROW(Realm::get_shared_realm(config_without_schema));
32✔
404

18✔
405
        // verify that we can also still open the file with a proper schema
16✔
406
        auto realm2 = Realm::get_shared_realm(config);
32✔
407
        REQUIRE_FALSE(realm2->schema().empty());
34!
408
        REQUIRE(realm2->schema_version() == 1);
34!
409
    }
34!
410

306✔
411
    SECTION("should populate the table columns in the schema when opening as immutable") {
608✔
412
        Realm::get_shared_realm(config);
54✔
413

9✔
414
        config.schema_mode = SchemaMode::Immutable;
16✔
415
        auto realm = Realm::get_shared_realm(config);
17✔
416
        auto it = realm->schema().find("object");
17✔
417
        auto table = realm->read_group().get_table("class_object");
17✔
418
        REQUIRE(it != realm->schema().end());
17!
419
        REQUIRE(it->table_key == table->get_key());
17!
420
        REQUIRE(it->persisted_properties.size() == 1);
17!
421
        REQUIRE(it->persisted_properties[0].name == "value");
17!
422
        REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value"));
17!
423

9!
424
        SECTION("refreshing an immutable Realm throws") {
16✔
425
            REQUIRE_THROWS_WITH(realm->refresh(), "Can't refresh an immutable Realm.");
17✔
426
        }
17✔
427
    }
17✔
428

305✔
429
    SECTION("should support using different table subsets on different threads") {
608✔
430
        auto realm1 = Realm::get_shared_realm(config);
54✔
431

9✔
432
        config.schema = Schema{
16✔
433
            {"object 2", {{"value", PropertyType::Int}}},
17✔
434
        };
17✔
435
        auto realm2 = Realm::get_shared_realm(config);
17✔
436

9✔
437
        config.schema = util::none;
16✔
438
        auto realm3 = Realm::get_shared_realm(config);
17✔
439

9✔
440
        config.schema = Schema{
16✔
441
            {"object", {{"value", PropertyType::Int}}},
17✔
442
        };
17✔
443
        auto realm4 = Realm::get_shared_realm(config);
17✔
444

9✔
445
        realm1->refresh();
16✔
446
        realm2->refresh();
17✔
447

9✔
448
        REQUIRE(realm1->schema().size() == 1);
16!
449
        REQUIRE(realm1->schema().find("object") != realm1->schema().end());
17!
450
        REQUIRE(realm2->schema().size() == 1);
17!
451
        REQUIRE(realm2->schema().find("object 2") != realm2->schema().end());
17!
452
        REQUIRE(realm3->schema().size() == 2);
17!
453
        REQUIRE(realm3->schema().find("object") != realm3->schema().end());
17!
454
        REQUIRE(realm3->schema().find("object 2") != realm3->schema().end());
17!
455
        REQUIRE(realm4->schema().size() == 1);
17!
456
        REQUIRE(realm4->schema().find("object") != realm4->schema().end());
17!
457
    }
17!
458
#ifndef _WIN32
609✔
459
    SECTION("should throw when creating the notification pipe fails") {
646✔
460
        // The ExternalCommitHelper implementation on Windows doesn't rely on FIFOs
46✔
461
        std::string expected_path = config.path + ".note";
16✔
462
        REQUIRE(util::try_make_dir(config.path + ".note"));
17!
463
        if (auto tmp_dir = DBOptions::get_sys_tmp_dir(); !tmp_dir.empty()) {
17✔
464
            expected_path = util::format("%1realm_%2.note", util::normalize_dir(tmp_dir),
17✔
465
                                         std::hash<std::string>()(config.path)); // Mirror internal implementation
17✔
466
            REQUIRE(util::try_make_dir(expected_path));
17!
467
        }
17!
468
        REQUIRE_EXCEPTION(
17✔
469
            Realm::get_shared_realm(config), FileAlreadyExists,
17✔
470
            util::format("Cannot create fifo at path '%1': a non-fifo entry already exists at that path.",
17✔
471
                         expected_path));
17✔
472
        util::remove_dir(config.path + ".note");
17✔
473
        util::try_remove_dir(expected_path);
17✔
474
    }
17✔
475
#endif
609✔
476

342✔
477
    SECTION("should get different instances on different threads") {
608✔
478
        config.cache = true;
54✔
479
        auto realm1 = Realm::get_shared_realm(config);
17✔
480
        std::thread([&] {
17✔
481
            auto realm2 = Realm::get_shared_realm(config);
17✔
482
            REQUIRE(realm1 != realm2);
17!
483
        }).join();
17!
484
    }
17✔
485

305✔
486
    SECTION("should detect use of Realm on incorrect thread") {
608✔
487
        auto realm = Realm::get_shared_realm(config);
54✔
488
        std::thread([&] {
17✔
489
            REQUIRE_THROWS_MATCHES(realm->verify_thread(), LogicError,
17✔
490
                                   Catch::Matchers::Message("Realm accessed from incorrect thread."));
17✔
491
        }).join();
17✔
492
    }
17✔
493

305✔
494
    // Our test scheduler uses a simple integer identifier to allow cross thread scheduling
304✔
495
    class SimpleScheduler : public util::Scheduler {
608✔
496
    public:
646✔
497
        SimpleScheduler(size_t id)
646✔
498
            : Scheduler()
646✔
499
            , m_id(id)
646✔
500
        {
374✔
501
        }
68✔
502

308✔
503
        bool is_on_thread() const noexcept override
608✔
504
        {
350✔
505
            return true;
17✔
506
        }
17✔
507
        bool is_same_as(const Scheduler* other) const noexcept override
609✔
508
        {
374✔
509
            const SimpleScheduler* o = dynamic_cast<const SimpleScheduler*>(other);
68✔
510
            return (o && (o->m_id == m_id));
68✔
511
        }
68✔
512
        bool can_invoke() const noexcept override
612✔
513
        {
342✔
UNCOV
514
            return false;
×
515
        }
×
516
        void invoke(util::UniqueFunction<void()>&&) override {}
328✔
517

307✔
518
    protected:
608✔
519
        size_t m_id;
646✔
520
    };
646✔
521

342✔
522
    SECTION("should get different instances for different explicitly different schedulers") {
608✔
523
        config.cache = true;
54✔
524
        config.scheduler = std::make_shared<SimpleScheduler>(1);
17✔
525
        auto realm1 = Realm::get_shared_realm(config);
17✔
526
        config.scheduler = std::make_shared<SimpleScheduler>(2);
17✔
527
        auto realm2 = Realm::get_shared_realm(config);
17✔
528
        REQUIRE(realm1 != realm2);
17!
529

9!
530
        config.scheduler = nullptr;
16✔
531
        auto realm3 = Realm::get_shared_realm(config);
17✔
532
        REQUIRE(realm1 != realm3);
17!
533
        REQUIRE(realm2 != realm3);
17!
534
    }
17!
535

305✔
536
    SECTION("can use Realm with explicit scheduler on different thread") {
608✔
537
        config.cache = true;
54✔
538
        config.scheduler = std::make_shared<SimpleScheduler>(1);
17✔
539
        auto realm = Realm::get_shared_realm(config);
17✔
540
        std::thread([&] {
17✔
541
            REQUIRE_NOTHROW(realm->verify_thread());
17✔
542
        }).join();
17✔
543
    }
17✔
544

305✔
545
    SECTION("should get same instance for same explicit execution context on different thread") {
608✔
546
        config.cache = true;
54✔
547
        config.scheduler = std::make_shared<SimpleScheduler>(1);
17✔
548
        auto realm1 = Realm::get_shared_realm(config);
17✔
549
        std::thread([&] {
17✔
550
            auto realm2 = Realm::get_shared_realm(config);
17✔
551
            REQUIRE(realm1 == realm2);
17!
552
        }).join();
17!
553
    }
17✔
554

305✔
555
    SECTION("should not modify the schema when fetching from the cache") {
608✔
556
        config.cache = true;
54✔
557
        auto realm = Realm::get_shared_realm(config);
17✔
558
        auto object_schema = &*realm->schema().find("object");
17✔
559
        Realm::get_shared_realm(config);
17✔
560
        REQUIRE(object_schema == &*realm->schema().find("object"));
17!
561
    }
17!
562

305✔
563
    SECTION("should reuse cached frozen Realm if versions match") {
608✔
564
        config.cache = true;
54✔
565
        auto realm = Realm::get_shared_realm(config);
17✔
566
        realm->read_group();
17✔
567
        auto frozen = realm->freeze();
17✔
568
        frozen->read_group();
17✔
569

9✔
570
        REQUIRE(frozen != realm);
16!
571
        REQUIRE(realm->read_transaction_version() == frozen->read_transaction_version());
17!
572

9!
573
        REQUIRE(realm->freeze() == frozen);
16!
574
        REQUIRE(Realm::get_frozen_realm(config, realm->read_transaction_version()) == frozen);
17!
575
    }
17!
576

305✔
577
    SECTION("should not use cached frozen Realm if versions don't match") {
608✔
578
        config.cache = true;
54✔
579
        auto realm = Realm::get_shared_realm(config);
17✔
580
        realm->read_group();
17✔
581
        auto frozen1 = realm->freeze();
17✔
582
        frozen1->read_group();
17✔
583

9✔
584
        REQUIRE(frozen1 != realm);
16!
585
        REQUIRE(realm->read_transaction_version() == frozen1->read_transaction_version());
17!
586

9!
587
        auto table = realm->read_group().get_table("class_object");
16✔
588
        realm->begin_transaction();
17✔
589
        table->create_object();
17✔
590
        realm->commit_transaction();
17✔
591

9✔
592
        REQUIRE(realm->read_transaction_version() > frozen1->read_transaction_version());
16!
593

9!
594
        auto frozen2 = realm->freeze();
16✔
595
        frozen2->read_group();
17✔
596

9✔
597
        REQUIRE(frozen2 != frozen1);
16!
598
        REQUIRE(frozen2 != realm);
17!
599
        REQUIRE(realm->read_transaction_version() == frozen2->read_transaction_version());
17!
600
        REQUIRE(frozen2->read_transaction_version() > frozen1->read_transaction_version());
17!
601
    }
17!
602

305✔
603
    SECTION("frozen realm should have the same schema as originating realm") {
608✔
604
        auto full_schema = Schema{
54✔
605
            {"object1", {{"value", PropertyType::Int}}},
17✔
606
            {"object2", {{"value", PropertyType::Int}}},
17✔
607
        };
17✔
608

9✔
609
        auto subset_schema = Schema{
16✔
610
            {"object1", {{"value", PropertyType::Int}}},
17✔
611
        };
17✔
612

9✔
613
        config.schema = full_schema;
16✔
614

9✔
615
        auto realm = Realm::get_shared_realm(config);
16✔
616
        realm->close();
17✔
617

9✔
618
        config.schema = subset_schema;
16✔
619

9✔
620
        realm = Realm::get_shared_realm(config);
16✔
621
        realm->read_group();
17✔
622
        auto frozen_realm = realm->freeze();
17✔
623
        auto frozen_schema = frozen_realm->schema();
17✔
624

9✔
625
        REQUIRE(full_schema != subset_schema);
16!
626
        REQUIRE(realm->schema() == subset_schema);
17!
627
        REQUIRE(frozen_schema == subset_schema);
17!
628
    }
17!
629

305✔
630
    SECTION("frozen realm should have the correct schema even if more properties are added later") {
608✔
631
        config.schema_mode = SchemaMode::AdditiveExplicit;
54✔
632
        auto full_schema = Schema{
17✔
633
            {"object", {{"value1", PropertyType::Int}, {"value2", PropertyType::Int}}},
17✔
634
        };
17✔
635

9✔
636
        auto subset_schema = Schema{
16✔
637
            {"object", {{"value1", PropertyType::Int}}},
17✔
638
        };
17✔
639

9✔
640
        config.schema = subset_schema;
16✔
641
        auto realm = Realm::get_shared_realm(config);
17✔
642
        realm->read_group();
17✔
643

9✔
644
        config.schema = full_schema;
16✔
645
        auto realm2 = Realm::get_shared_realm(config);
17✔
646
        realm2->read_group();
17✔
647

9✔
648
        auto frozen_realm = realm->freeze();
16✔
649
        REQUIRE(realm->schema() == subset_schema);
17!
650
        REQUIRE(realm2->schema() == full_schema);
17!
651
        REQUIRE(frozen_realm->schema() == subset_schema);
17!
652
    }
17!
653

305✔
654
    SECTION("freeze with orphaned embedded tables") {
608✔
655
        auto schema = Schema{
54✔
656
            {"object1", {{"value", PropertyType::Int}}},
17✔
657
            {"object2", ObjectSchema::ObjectType::Embedded, {{"value", PropertyType::Int}}},
17✔
658
        };
17✔
659
        config.schema = schema;
17✔
660
        config.schema_mode = SchemaMode::AdditiveDiscovered;
17✔
661
        auto realm = Realm::get_shared_realm(config);
17✔
662
        realm->read_group();
17✔
663
        auto frozen_realm = realm->freeze();
17✔
664
        REQUIRE(frozen_realm->schema() == schema);
17!
665
    }
17!
666
}
609✔
667

38✔
668
TEST_CASE("SharedRealm: schema_subset_mode") {
224✔
669
    TestFile config;
238✔
670
    config.schema_mode = SchemaMode::AdditiveExplicit;
238✔
671
    config.schema_version = 1;
238✔
672
    config.schema_subset_mode = SchemaSubsetMode::Complete;
238✔
673
    config.encryption_key.clear();
238✔
674

126✔
675
    // Use a DB directly to simulate changes made by another process
112✔
676
    auto db = DB::create(make_in_realm_history(), config.path);
224✔
677

126✔
678
    // Changing the schema version results in update_schema() hitting a very
112✔
679
    // different code path for Additive modes, so test both with the schema version
112✔
680
    // matching and not matching
112✔
681
    auto set_schema_version = GENERATE(false, true);
224✔
682
    INFO("Matching schema version: " << set_schema_version);
238✔
683
    if (set_schema_version) {
238✔
684
        auto tr = db->start_write();
126✔
685
        ObjectStore::set_schema_version(*tr, 1);
119✔
686
        tr->commit();
119✔
687
    }
119✔
688

119✔
689
    SECTION("additional properties are added at the end") {
224✔
690
        {
46✔
691
            auto tr = db->start_write();
34✔
692
            auto table = tr->add_table("class_object");
34✔
693
            for (int i = 0; i < 5; ++i) {
194✔
694
                table->add_column(type_Int, util::format("col %1", i));
172✔
695
            }
170✔
696
            tr->commit();
42✔
697
        }
34✔
698

18✔
699
        // missing col 0 and 4, and order is different from column order
16✔
700
        config.schema = Schema{{"object",
32✔
701
                                {
34✔
702
                                    {"col 2", PropertyType::Int},
34✔
703
                                    {"col 3", PropertyType::Int},
34✔
704
                                    {"col 1", PropertyType::Int},
34✔
705
                                }}};
34✔
706

18✔
707
        auto realm = Realm::get_shared_realm(config);
32✔
708
        auto& properties = realm->schema().find("object")->persisted_properties;
34✔
709
        REQUIRE(properties.size() == 5);
34!
710
        REQUIRE(properties[0].name == "col 2");
34!
711
        REQUIRE(properties[1].name == "col 3");
34!
712
        REQUIRE(properties[2].name == "col 1");
34!
713
        REQUIRE(properties[3].name == "col 0");
34!
714
        REQUIRE(properties[4].name == "col 4");
34!
715

18!
716
        for (auto& property : properties) {
160✔
717
            REQUIRE(property.column_key != ColKey{});
170✔
718
        }
170!
719

26✔
720
        config.schema_subset_mode.include_properties = false;
32✔
721
        realm = Realm::get_shared_realm(config);
34✔
722
        REQUIRE(realm->schema().find("object")->persisted_properties.size() == 3);
34!
723
    }
34!
724

114✔
725
    SECTION("additional tables are added in sorted order") {
224✔
726
        {
46✔
727
            auto tr = db->start_write();
34✔
728
            // In reverse order so that just using the table order doesn't
18✔
729
            // work accidentally
16✔
730
            tr->add_table("class_F")->add_column(type_Int, "value");
32✔
731
            tr->add_table("class_E")->add_column(type_Int, "value");
34✔
732
            tr->add_table("class_D")->add_column(type_Int, "value");
34✔
733
            tr->add_table("class_C")->add_column(type_Int, "value");
34✔
734
            tr->add_table("class_B")->add_column(type_Int, "value");
34✔
735
            tr->add_table("class_A")->add_column(type_Int, "value");
34✔
736
            tr->commit();
34✔
737
        }
34✔
738

18✔
739
        config.schema = Schema{
32✔
740
            {"A", {{"value", PropertyType::Int}}},
34✔
741
            {"E", {{"value", PropertyType::Int}}},
34✔
742
            {"D", {{"value", PropertyType::Int}}},
34✔
743
        };
34✔
744
        auto realm = Realm::get_shared_realm(config);
34✔
745
        auto& schema = realm->schema();
34✔
746
        REQUIRE(schema.size() == 6);
34!
747
        REQUIRE(std::is_sorted(schema.begin(), schema.end(), [](auto& a, auto& b) {
34!
748
            return a.name < b.name;
34!
749
        }));
34✔
750

18✔
751
        config.schema_subset_mode.include_types = false;
32✔
752
        realm = Realm::get_shared_realm(config);
34✔
753
        REQUIRE(realm->schema().size() == 3);
34!
754
    }
34!
755

114✔
756
    SECTION("schema is updated when refreshing over a schema change") {
224✔
757
        config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
46✔
758
        auto realm = Realm::get_shared_realm(config);
34✔
759
        realm->read_group();
34✔
760
        auto& schema = realm->schema();
34✔
761

18✔
762
        {
32✔
763
            auto tr = db->start_write();
34✔
764
            tr->get_table("class_object")->add_column(type_Int, "value 2");
34✔
765
            tr->commit();
34✔
766
        }
34✔
767

18✔
768
        REQUIRE(schema.find("object")->persisted_properties.size() == 1);
32!
769
        realm->refresh();
34!
770
        REQUIRE(schema.find("object")->persisted_properties.size() == 2);
34!
771

18!
772
        {
32✔
773
            auto tr = db->start_write();
34✔
774
            tr->add_table("class_object 2")->add_column(type_Int, "value");
34✔
775
            tr->commit();
34✔
776
        }
34✔
777

18✔
778
        REQUIRE(schema.size() == 1);
32!
779
        realm->refresh();
34!
780
        REQUIRE(schema.size() == 2);
34!
781
    }
34!
782

114✔
783
    SECTION("schema is updated when schema is modified while not in a read transaction") {
224✔
784
        config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
46✔
785
        auto realm = Realm::get_shared_realm(config);
34✔
786
        auto& schema = realm->schema();
34✔
787

18✔
788
        {
32✔
789
            auto tr = db->start_write();
34✔
790
            tr->get_table("class_object")->add_column(type_Int, "value 2");
34✔
791
            tr->commit();
34✔
792
        }
34✔
793

18✔
794
        REQUIRE(schema.find("object")->persisted_properties.size() == 1);
32!
795
        realm->read_group();
34!
796
        REQUIRE(schema.find("object")->persisted_properties.size() == 2);
34!
797
        realm->invalidate();
34!
798

18✔
799
        {
32✔
800
            auto tr = db->start_write();
34✔
801
            tr->add_table("class_object 2")->add_column(type_Int, "value");
34✔
802
            tr->commit();
34✔
803
        }
34✔
804

18✔
805
        REQUIRE(schema.size() == 1);
32!
806
        realm->read_group();
34!
807
        REQUIRE(schema.size() == 2);
34!
808
    }
34!
809

114✔
810
    SECTION("frozen Realm sees the correct schema for each version") {
224✔
811
        config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
78✔
812
        std::vector<std::shared_ptr<Realm>> realms;
68✔
813
        for (int i = 0; i < 10; ++i) {
708✔
814
            realms.push_back(Realm::get_shared_realm(config));
684✔
815
            realms.back()->read_group();
680✔
816
            auto tr = db->start_write();
680✔
817
            tr->add_table(util::format("class_object %1", i))->add_column(type_Int, "value");
680✔
818
            tr->commit();
680✔
819
        }
680✔
820

72✔
821
        auto reset_schema = GENERATE(false, true);
64✔
822
        if (reset_schema) {
68✔
823
            config.schema.reset();
36✔
824
        }
34✔
825

34✔
826
        for (size_t i = 0; i < 10; ++i) {
704✔
827
            auto& r = *realms[i];
684✔
828
            REQUIRE(r.schema().size() == i + 1);
680!
829
            auto frozen = r.freeze();
680!
830
            REQUIRE(frozen->schema().size() == i + 1);
680!
831
            REQUIRE(frozen->schema_version() == config.schema_version);
680!
832
            frozen = Realm::get_frozen_realm(config, r.read_transaction_version());
680!
833
            REQUIRE(frozen->schema().size() == i + 1);
680!
834
            REQUIRE(frozen->schema_version() == config.schema_version);
680!
835
        }
680!
836

72✔
837
        SECTION("schema not set in config") {
64✔
838
            config.schema = std::nullopt;
68✔
839
            for (size_t i = 0; i < 10; ++i) {
708✔
840
                auto& r = *realms[i];
684✔
841
                REQUIRE(r.schema().size() == i + 1);
680!
842
                REQUIRE(r.freeze()->schema().size() == i + 1);
680!
843
                REQUIRE(Realm::get_frozen_realm(config, r.read_transaction_version())->schema().size() == i + 1);
680!
844
            }
680!
845
        }
104✔
846
    }
68✔
847

116✔
848
    SECTION("obtaining a frozen realm with an incompatible schema throws") {
224✔
849
        config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
46✔
850
        auto old_realm = Realm::get_shared_realm(config);
34✔
851
        {
34✔
852
            auto tr = db->start_write();
34✔
853
            auto table = tr->get_table("class_object");
34✔
854
            table->create_object();
34✔
855
            tr->commit();
34✔
856
        }
34✔
857
        old_realm->read_group();
34✔
858

18✔
859
        {
32✔
860
            auto tr = db->start_write();
34✔
861
            auto table = tr->add_table("class_object 2");
34✔
862
            ColKey val_col = table->add_column(type_Int, "value");
34✔
863
            table->create_object().set(val_col, 1);
34✔
864
            tr->commit();
34✔
865
        }
34✔
866

18✔
867
        config.schema = Schema{
32✔
868
            {"object", {{"value", PropertyType::Int}}},
34✔
869
            {"object 2", {{"value", PropertyType::Int}}},
34✔
870
        };
34✔
871
        auto new_realm = Realm::get_shared_realm(config);
34✔
872
        new_realm->read_group();
34✔
873

18✔
874
        REQUIRE(old_realm->freeze()->schema().size() == 1);
32!
875
        REQUIRE(new_realm->freeze()->schema().size() == 2);
34!
876
        REQUIRE(Realm::get_frozen_realm(config, new_realm->read_transaction_version())->schema().size() == 2);
34!
877
        // An additive change is allowed, the unknown table is empty
18!
878
        REQUIRE(Realm::get_frozen_realm(config, old_realm->read_transaction_version())->schema().size() == 2);
32!
879

18!
880
        config.schema = Schema{{"object", {{"value", PropertyType::String}}}}; // int -> string
32✔
881
        // Fails because the schema has an invalid breaking change
18✔
882
        REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, new_realm->read_transaction_version()),
32✔
883
                          InvalidReadOnlySchemaChangeException);
34✔
884
        REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, old_realm->read_transaction_version()),
34✔
885
                          InvalidReadOnlySchemaChangeException);
34✔
886
        config.schema = Schema{
34✔
887
            {"object", {{"value", PropertyType::Int}}},
34✔
888
            {"object 2", {{"value", PropertyType::String}}}, // int -> string
34✔
889
        };
34✔
890
        // fails due to invalid change on object 2 type
18✔
891
        REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, new_realm->read_transaction_version()),
32✔
892
                          InvalidReadOnlySchemaChangeException);
34✔
893
        // opening the old state does not fail because the schema is an additive change
18✔
894
        auto frozen_old = Realm::get_frozen_realm(config, old_realm->read_transaction_version());
32✔
895
        REQUIRE(frozen_old->schema().size() == 2);
34!
896
        {
34!
897
            TableRef table = frozen_old->read_group().get_table("class_object");
34✔
898
            Results results(frozen_old, table);
34✔
899
            REQUIRE(results.is_frozen());
34!
900
            REQUIRE(results.size() == 1);
34!
901
        }
34!
902
        {
34✔
903
            TableRef table = frozen_old->read_group().get_table("class_object 2");
34✔
904
            REQUIRE(!table);
34!
905
            Results results(frozen_old, table);
34!
906
            REQUIRE(results.is_frozen());
34!
907
            REQUIRE(results.size() == 0);
34!
908
        }
34!
909
        config.schema = Schema{
34✔
910
            {"object", {{"value", PropertyType::Int}, {"value 2", PropertyType::String}}}, // add property
34✔
911
        };
34✔
912
        // fails due to additional property on object
18✔
913
        REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, old_realm->read_transaction_version()),
32✔
914
                          InvalidReadOnlySchemaChangeException);
34✔
915
        REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, new_realm->read_transaction_version()),
34✔
916
                          InvalidReadOnlySchemaChangeException);
34✔
917
    }
34✔
918
}
226✔
919

14✔
920
#if REALM_ENABLE_SYNC
921
TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") {
240✔
922
    if (!util::EventLoop::has_implementation())
255✔
923
        return;
15✔
924

120✔
925
    TestSyncManager tsm;
240✔
926
    SyncTestFile config(tsm, "default");
255✔
927
    ObjectSchema object_schema = {"object",
255✔
928
                                  {
255✔
929
                                      {"_id", PropertyType::Int, Property::IsPrimary{true}},
255✔
930
                                      {"value", PropertyType::Int},
255✔
931
                                  }};
255✔
932
    config.schema = Schema{object_schema};
255✔
933
    SyncTestFile config2(tsm, "default");
255✔
934
    config2.schema = config.schema;
255✔
935

135✔
936
    std::mutex mutex;
240✔
937

135✔
938
    auto async_open_realm = [&](const Realm::Config& config) {
208✔
939
        ThreadSafeReference realm_ref;
187✔
940
        std::exception_ptr error;
187✔
941
        auto task = Realm::get_synchronized_realm(config);
187✔
942
        task->start([&](ThreadSafeReference&& ref, std::exception_ptr e) {
187✔
943
            std::lock_guard lock(mutex);
187✔
944
            realm_ref = std::move(ref);
187✔
945
            error = e;
187✔
946
        });
187✔
947
        util::EventLoop::main().run_until([&] {
1,495,450✔
948
            std::lock_guard lock(mutex);
1,495,651✔
949
            return realm_ref || error;
1,495,651✔
950
        });
1,495,651✔
951
        return std::pair(std::move(realm_ref), error);
388✔
952
    };
187✔
953

131✔
954
    SECTION("can open synced Realms that don't already exist") {
240✔
955
        auto [ref, error] = async_open_realm(config);
31✔
956
        REQUIRE(ref);
17!
957
        REQUIRE_FALSE(error);
17!
958
        REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object"));
17!
959
    }
17!
960

121✔
961
    SECTION("can write a realm file without client file id") {
240✔
962
        ThreadSafeReference realm_ref;
31✔
963
        SyncTestFile config3(tsm, "default");
17✔
964
        config3.schema = config.schema;
17✔
965
        uint64_t client_file_id;
17✔
966

9✔
967
        // Create some content
8✔
968
        auto origin = Realm::get_shared_realm(config);
16✔
969
        origin->begin_transaction();
17✔
970
        Class cls = origin->get_class("object");
17✔
971
        cls.create_object(0);
17✔
972
        origin->commit_transaction();
17✔
973
        wait_for_upload(*origin);
17✔
974

9✔
975
        // Create realm file without client file id
8✔
976
        {
16✔
977
            auto [ref, error] = async_open_realm(config);
17✔
978
            REQUIRE(ref);
17!
979
            REQUIRE_FALSE(error);
17!
980
            // Write some data
9!
981
            SharedRealm realm = Realm::get_shared_realm(std::move(ref));
16✔
982
            realm->begin_transaction();
17✔
983
            realm->get_class("object").create_object(2);
17✔
984
            realm->commit_transaction();
17✔
985
            wait_for_upload(*realm);
17✔
986
            wait_for_download(*realm);
17✔
987
            client_file_id = realm->read_group().get_sync_file_id();
17✔
988

9✔
989
            realm->convert(config3);
16✔
990
        }
17✔
991

9✔
992
        // Create some more content on the server
8✔
993
        origin->begin_transaction();
16✔
994
        cls.create_object(7);
17✔
995
        origin->commit_transaction();
17✔
996
        wait_for_upload(*origin);
17✔
997

9✔
998
        // Now open a realm based on the realm file created above
8✔
999
        auto realm = Realm::get_shared_realm(config3);
16✔
1000
        Class cls2 = realm->get_class("object");
17✔
1001
        wait_for_download(*realm);
17✔
1002
        wait_for_upload(*realm);
17✔
1003

9✔
1004
        // Make sure we have got a new client file id
8✔
1005
        REQUIRE(realm->read_group().get_sync_file_id() != client_file_id);
16!
1006
        REQUIRE(cls.num_objects() == 3);
17!
1007

9!
1008
        // Check that we can continue committing to this realm
8✔
1009
        realm->begin_transaction();
16✔
1010
        cls2.create_object(5);
17✔
1011
        realm->commit_transaction();
17✔
1012
        wait_for_upload(*realm);
17✔
1013

9✔
1014
        // Check that this change is now in the original realm
8✔
1015
        wait_for_download(*origin);
16✔
1016
        origin->refresh();
17✔
1017
        REQUIRE(cls.num_objects() == 4);
17!
1018
    }
17!
1019

121✔
1020
    SECTION("downloads Realms which exist on the server") {
240✔
1021
        {
31✔
1022
            auto realm = Realm::get_shared_realm(config2);
17✔
1023
            realm->begin_transaction();
17✔
1024
            realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
17✔
1025
            realm->commit_transaction();
17✔
1026
            wait_for_upload(*realm);
17✔
1027
        }
17✔
1028

9✔
1029
        auto [ref, error] = async_open_realm(config);
16✔
1030
        REQUIRE(ref);
17!
1031
        REQUIRE_FALSE(error);
17!
1032
        REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object"));
17!
1033
    }
17!
1034

121✔
1035
    SECTION("progress notifiers of a task are cancelled if the task is cancelled") {
240✔
1036
        bool progress_notifier1_called = false;
31✔
1037
        bool task1_completed = false;
17✔
1038
        bool progress_notifier2_called = false;
17✔
1039
        bool task2_completed = false;
17✔
1040
        {
17✔
1041
            auto realm = Realm::get_shared_realm(config2);
17✔
1042
            realm->begin_transaction();
17✔
1043
            realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
17✔
1044
            realm->commit_transaction();
17✔
1045
            wait_for_upload(*realm);
17✔
1046
        }
17✔
1047

9✔
1048
        DBOptions options;
16✔
1049
        options.encryption_key = config.encryption_key.data();
17✔
1050
        auto db = DB::create(sync::make_client_replication(), config.path, options);
17✔
1051
        auto write = db->start_write(); // block sync from writing until we cancel
17✔
1052

9✔
1053
        std::shared_ptr<AsyncOpenTask> task = Realm::get_synchronized_realm(config);
16✔
1054
        std::shared_ptr<AsyncOpenTask> task2 = Realm::get_synchronized_realm(config);
17✔
1055
        REQUIRE(task);
17!
1056
        REQUIRE(task2);
17!
1057
        task->register_download_progress_notifier([&](uint64_t, uint64_t, double) {
17!
1058
            std::lock_guard<std::mutex> guard(mutex);
1✔
1059
            REQUIRE(!task1_completed);
×
1060
            progress_notifier1_called = true;
×
1061
        });
×
1062
        task2->register_download_progress_notifier([&](uint64_t, uint64_t, double) {
16✔
1063
            std::lock_guard<std::mutex> guard(mutex);
17✔
1064
            REQUIRE(!task2_completed);
17!
1065
            progress_notifier2_called = true;
17!
1066
        });
17✔
1067
        task->start([&](ThreadSafeReference realm_ref, std::exception_ptr err) {
9✔
UNCOV
1068
            std::lock_guard<std::mutex> guard(mutex);
×
1069
            REQUIRE(!err);
×
1070
            REQUIRE(realm_ref);
×
1071
            task1_completed = true;
×
1072
        });
×
1073
        task->cancel();
16✔
1074
        ThreadSafeReference rref;
17✔
1075
        task2->start([&](ThreadSafeReference realm_ref, std::exception_ptr err) {
17✔
1076
            std::lock_guard<std::mutex> guard(mutex);
17✔
1077
            REQUIRE(!err);
17!
1078
            REQUIRE(realm_ref);
17!
1079
            rref = std::move(realm_ref);
17!
1080
            task2_completed = true;
17✔
1081
        });
17✔
1082
        write = nullptr; // unblock sync
17✔
1083
        util::EventLoop::main().run_until([&] {
95,436✔
1084
            std::lock_guard<std::mutex> guard(mutex);
95,453✔
1085
            return task2_completed;
95,453✔
1086
        });
95,453✔
1087
        std::lock_guard<std::mutex> guard(mutex);
34✔
1088
        REQUIRE(!progress_notifier1_called);
17!
1089
        REQUIRE(!task1_completed);
17!
1090
        REQUIRE(progress_notifier2_called);
17!
1091
        REQUIRE(task2_completed);
17!
1092
        SharedRealm realm = Realm::get_shared_realm(std::move(rref));
17!
1093
        REQUIRE(realm);
17!
1094
    }
17!
1095

121✔
1096
    SECTION("downloads latest state for Realms which already exist locally") {
240✔
1097
        wait_for_upload(*Realm::get_shared_realm(config));
31✔
1098

9✔
1099
        {
16✔
1100
            auto realm = Realm::get_shared_realm(config2);
17✔
1101
            realm->begin_transaction();
17✔
1102
            realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
17✔
1103
            realm->commit_transaction();
17✔
1104
            wait_for_upload(*realm);
17✔
1105
        }
17✔
1106

9✔
1107
        auto [ref, error] = async_open_realm(config);
16✔
1108
        REQUIRE(ref);
17!
1109
        REQUIRE_FALSE(error);
17!
1110
        REQUIRE(Realm::get_shared_realm(std::move(ref))->read_group().get_table("class_object")->size() == 1);
17!
1111
    }
17!
1112

121✔
1113
    SECTION("can download multiple Realms at a time") {
240✔
1114
        SyncTestFile config1(tsm, "realm1");
31✔
1115
        SyncTestFile config2(tsm, "realm2");
17✔
1116
        SyncTestFile config3(tsm, "realm3");
17✔
1117
        SyncTestFile config4(tsm, "realm4");
17✔
1118

9✔
1119
        std::vector<std::shared_ptr<AsyncOpenTask>> tasks = {
16✔
1120
            Realm::get_synchronized_realm(config1),
17✔
1121
            Realm::get_synchronized_realm(config2),
17✔
1122
            Realm::get_synchronized_realm(config3),
17✔
1123
            Realm::get_synchronized_realm(config4),
17✔
1124
        };
17✔
1125

9✔
1126
        std::atomic<int> completed{0};
16✔
1127
        for (auto& task : tasks) {
65✔
1128
            task->start([&](auto, auto) {
68✔
1129
                ++completed;
68✔
1130
            });
68✔
1131
        }
68✔
1132
        util::EventLoop::main().run_until([&] {
318,659✔
1133
            return completed == 4;
318,701✔
1134
        });
318,701✔
1135
    }
62✔
1136

121✔
1137
    auto expired_token = encode_fake_jwt("", 123, 456);
240✔
1138

135✔
1139
    SECTION("can async open while waiting for a token refresh") {
240✔
1140
        SyncTestFile config(tsm, "realm");
31✔
1141
        auto user = config.sync_config->user;
17✔
1142
        auto valid_token = user->access_token();
17✔
1143
        user->update_access_token(std::move(expired_token));
17✔
1144

9✔
1145
        std::atomic<bool> called{false};
17✔
1146
        auto task = Realm::get_synchronized_realm(config);
17✔
1147
        task->start([&](auto ref, auto error) {
17✔
1148
            std::lock_guard<std::mutex> lock(mutex);
17✔
1149
            REQUIRE(ref);
17!
1150
            REQUIRE(!error);
17!
1151
            called = true;
17✔
1152
        });
17✔
1153
        auto session = tsm.sync_manager()->get_existing_session(config.path);
17✔
1154
        REQUIRE(session);
17!
1155
        CHECK(session->state() == SyncSession::State::WaitingForAccessToken);
17!
1156

9✔
1157
        session->update_access_token(valid_token);
16✔
1158
        util::EventLoop::main().run_until([&] {
89,520!
1159
            return called.load();
89,520✔
1160
        });
89,520✔
1161
        std::lock_guard<std::mutex> lock(mutex);
17✔
1162
        REQUIRE(called);
17!
1163
    }
17!
1164

121!
1165
    SECTION("cancels download and reports an error on auth error") {
241✔
1166
        struct Transport : UnitTestTransport {
17✔
1167
            void send_request_to_server(
17!
1168
                const realm::app::Request& req,
17✔
1169
                realm::util::UniqueFunction<void(const realm::app::Response&)>&& completion) override
17✔
1170
            {
33✔
1171
                if (req.url.find("/auth/session") != std::string::npos) {
32✔
1172
                    completion(app::Response{403});
42✔
1173
                }
42✔
1174
                else {
42✔
1175
                    UnitTestTransport::send_request_to_server(req, std::move(completion));
17✔
1176
                }
17!
1177
            }
33✔
1178
        };
16✔
1179
        OfflineAppSession::Config oas_config;
31✔
1180
        oas_config.transport = std::make_shared<Transport>();
17✔
1181
        OfflineAppSession oas(oas_config);
17✔
1182

9✔
1183
        SyncTestFile config(oas, "realm");
17✔
1184
        config.sync_config->user->log_in(expired_token, expired_token);
17✔
1185

9✔
1186
        bool got_error = false;
17✔
1187
        config.sync_config->error_handler = [&](std::shared_ptr<SyncSession>, SyncError) {
17✔
1188
            got_error = true;
17✔
1189
        };
17✔
1190
        std::atomic<bool> called{false};
17✔
1191
        auto task = Realm::get_synchronized_realm(config);
17✔
1192
        task->start([&](auto ref, auto error) {
17✔
1193
            std::lock_guard<std::mutex> lock(mutex);
17✔
1194
            REQUIRE(error);
17!
1195
            REQUIRE_EXCEPTION(
16✔
1196
                std::rethrow_exception(error), HTTPError,
17✔
1197
                "Unable to refresh the user access token: http error code considered fatal. Client Error: 403");
17✔
1198
            REQUIRE(!ref);
17!
1199
            called = true;
17✔
1200
        });
17✔
1201
        util::EventLoop::main().run_until([&] {
25✔
1202
            return called.load();
25✔
1203
        });
25✔
1204
        std::lock_guard<std::mutex> lock(mutex);
17!
1205
        REQUIRE(called);
17✔
1206
        REQUIRE(got_error);
17!
1207
    }
17!
1208

121✔
1209
    SECTION("read-only mode sets the schema version") {
241✔
1210
        {
18✔
1211
            SharedRealm realm = Realm::get_shared_realm(config);
18✔
1212
            wait_for_upload(*realm);
18✔
1213
            realm->close();
17✔
1214
        }
17!
1215

9!
1216
        config2.schema_mode = SchemaMode::ReadOnly;
17✔
1217
        auto [ref, error] = async_open_realm(config2);
16✔
1218
        REQUIRE(ref);
31!
1219
        REQUIRE_FALSE(error);
17!
1220
        REQUIRE(Realm::get_shared_realm(std::move(ref))->schema_version() == 1);
17!
1221
    }
17✔
1222

121✔
1223
    Schema with_added_object = Schema{object_schema,
241✔
1224
                                      {"added",
240✔
1225
                                       {
241✔
1226
                                           {"_id", PropertyType::Int, Property::IsPrimary{true}},
241✔
1227
                                       }}};
241!
1228

121!
1229
    SECTION("read-only mode applies remote schema changes") {
241!
1230
        // Create the local file without "added"
9✔
1231
        Realm::get_shared_realm(config2);
16✔
1232

23✔
1233
        // Add the table server-side
23✔
1234
        config.schema = with_added_object;
31✔
1235
        config2.schema = with_added_object;
31✔
1236
        {
31✔
1237
            SharedRealm realm = Realm::get_shared_realm(config);
16✔
1238
            wait_for_upload(*realm);
31✔
1239
            realm->close();
16✔
1240
        }
17✔
1241

8✔
1242
        // Verify that the table gets added when reopening
8✔
1243
        config2.schema_mode = SchemaMode::ReadOnly;
17✔
1244
        auto [ref, error] = async_open_realm(config2);
17✔
1245
        REQUIRE(ref);
17!
1246
        REQUIRE_FALSE(error);
17!
1247
        auto realm = Realm::get_shared_realm(std::move(ref));
17✔
1248
        REQUIRE(realm->schema().find("added") != realm->schema().end());
17!
1249
        REQUIRE(realm->read_group().get_table("class_added"));
17!
1250
    }
16✔
1251

120✔
1252
    SECTION("read-only mode does not create tables not present on the server") {
241✔
1253
        // Create the local file without "added"
9✔
1254
        Realm::get_shared_realm(config2);
17!
1255

9!
1256
        config2.schema = with_added_object;
17✔
1257
        config2.schema_mode = SchemaMode::ReadOnly;
17!
1258
        auto [ref, error] = async_open_realm(config2);
17!
1259
        REQUIRE(ref);
17!
1260
        REQUIRE_FALSE(error);
16!
1261
        auto realm = Realm::get_shared_realm(std::move(ref));
31✔
1262
        REQUIRE(realm->schema().find("added") != realm->schema().end());
16!
1263
        REQUIRE_FALSE(realm->read_group().get_table("class_added"));
17!
1264
    }
16✔
1265

121✔
1266
    SECTION("adding a property to a newly downloaded read-only Realm reports an error") {
241✔
1267
        // Create the Realm on the server
9✔
1268
        wait_for_upload(*Realm::get_shared_realm(config2));
17!
1269

9!
1270
        config.schema_mode = SchemaMode::ReadOnly;
17✔
1271
        config.schema = Schema{{"object",
17!
1272
                                {
17!
1273
                                    {"_id", PropertyType::Int, Property::IsPrimary{true}},
17✔
1274
                                    {"value", PropertyType::Int},
16✔
1275
                                    {"value2", PropertyType::Int},
31✔
1276
                                }}};
16✔
1277

9✔
1278
        auto [ref, error] = async_open_realm(config);
16✔
1279
        REQUIRE_FALSE(ref);
17!
1280
        REQUIRE(error);
17!
1281
        REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "Property 'object.value2' has been added.");
17✔
1282
    }
17✔
1283

121✔
1284
    SECTION("adding a property to an existing read-only Realm reports an error") {
241✔
1285
        Realm::get_shared_realm(config);
17✔
1286

8✔
1287
        config.schema_mode = SchemaMode::ReadOnly;
17✔
1288
        config.schema = Schema{{"object",
17!
1289
                                {
17!
1290
                                    {"_id", PropertyType::Int, Property::IsPrimary{true}},
17✔
1291
                                    {"value", PropertyType::Int},
17✔
1292
                                    {"value2", PropertyType::Int},
16✔
1293
                                }}};
31✔
1294
        REQUIRE_THROWS_CONTAINING(Realm::get_shared_realm(config), "Property 'object.value2' has been added.");
17✔
1295

8✔
1296
        auto [ref, error] = async_open_realm(config);
17✔
1297
        REQUIRE_FALSE(ref);
17!
1298
        REQUIRE(error);
17!
1299
        REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "Property 'object.value2' has been added.");
17✔
1300
    }
17✔
1301

121✔
1302
    SECTION("removing a property from a newly downloaded read-only Realm leaves the column in place") {
241✔
1303
        // Create the Realm on the server
9✔
1304
        wait_for_upload(*Realm::get_shared_realm(config2));
16✔
1305

9✔
1306
        // Remove the "value" property from the schema
9!
1307
        config.schema_mode = SchemaMode::ReadOnly;
17!
1308
        config.schema = Schema{{"object",
17✔
1309
                                {
17✔
1310
                                    {"_id", PropertyType::Int, Property::IsPrimary{true}},
16✔
1311
                                }}};
31✔
1312

8✔
1313
        auto [ref, error] = async_open_realm(config);
17✔
1314
        REQUIRE(ref);
16!
1315
        REQUIRE_FALSE(error);
16!
1316
        REQUIRE(Realm::get_shared_realm(std::move(ref))
17!
1317
                    ->read_group()
17✔
1318
                    .get_table("class_object")
17✔
1319
                    ->get_column_key("value") != ColKey{});
17✔
1320
    }
17✔
1321

120✔
1322
    SECTION("removing a property from a existing read-only Realm leaves the column in place") {
241✔
1323
        Realm::get_shared_realm(config);
17!
1324

9!
1325
        // Remove the "value" property from the schema
9!
1326
        config.schema_mode = SchemaMode::ReadOnly;
17✔
1327
        config.schema = Schema{{"object",
17✔
1328
                                {
17✔
1329
                                    {"_id", PropertyType::Int, Property::IsPrimary{true}},
17✔
1330
                                }}};
16✔
1331

23✔
1332
        auto [ref, error] = async_open_realm(config);
17✔
1333
        REQUIRE(ref);
16!
1334
        REQUIRE_FALSE(error);
16!
1335
        REQUIRE(Realm::get_shared_realm(std::move(ref))
17!
1336
                    ->read_group()
17✔
1337
                    .get_table("class_object")
17✔
1338
                    ->get_column_key("value") != ColKey{});
17✔
1339
    }
17✔
1340
}
240✔
1341

1✔
1342
TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") {
65!
1343
    TestSyncManager tsm;
65!
1344
    ObjectSchema object_schema = {"object",
65!
1345
                                  {
65✔
1346
                                      {"_id", PropertyType::Int, Property::IsPrimary{true}},
65✔
1347
                                      {"value", PropertyType::Int},
65✔
1348
                                  }};
65✔
1349
    Schema schema{object_schema};
79✔
1350

32✔
1351
    SyncTestFile sync_config1(tsm, "default");
68✔
1352
    sync_config1.schema = schema;
68✔
1353
    TestFile local_config1;
68✔
1354
    local_config1.schema = schema;
68✔
1355
    local_config1.schema_version = sync_config1.schema_version;
68✔
1356

36✔
1357
    SECTION("can copy a synced realm to a synced realm") {
68✔
1358
        auto sync_realm1 = Realm::get_shared_realm(sync_config1);
20✔
1359
        sync_realm1->begin_transaction();
16✔
1360
        sync_realm1->read_group().get_table("class_object")->create_object_with_primary_key(0);
20✔
1361
        sync_realm1->commit_transaction();
20✔
1362
        wait_for_upload(*sync_realm1);
20✔
1363
        wait_for_download(*sync_realm1);
20✔
1364

12✔
1365
        // Copy to a new sync config
8✔
1366
        SyncTestFile sync_config2(tsm, "default");
20✔
1367
        sync_config2.schema = schema;
17✔
1368

9✔
1369
        sync_realm1->convert(sync_config2);
17✔
1370

9✔
1371
        auto sync_realm2 = Realm::get_shared_realm(sync_config2);
17✔
1372

9✔
1373
        // Check that the data also exists in the new realm
8✔
1374
        REQUIRE(sync_realm2->read_group().get_table("class_object")->size() == 1);
16!
1375

9✔
1376
        // Verify that sync works and objects created in the new copy will get
9✔
1377
        // synchronized to the old copy
8✔
1378
        sync_realm2->begin_transaction();
17✔
1379
        sync_realm2->read_group().get_table("class_object")->create_object_with_primary_key(1);
16✔
1380
        sync_realm2->commit_transaction();
17✔
1381
        wait_for_upload(*sync_realm2);
16✔
1382
        wait_for_download(*sync_realm1);
16✔
1383

9!
1384
        sync_realm1->refresh();
16✔
1385
        REQUIRE(sync_realm1->read_group().get_table("class_object")->size() == 2);
16!
1386
    }
16✔
1387

33✔
1388
    SECTION("can convert a synced realm to a local realm") {
65✔
1389
        auto sync_realm = Realm::get_shared_realm(sync_config1);
17✔
1390
        sync_realm->begin_transaction();
17✔
1391
        sync_realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
17✔
1392
        sync_realm->commit_transaction();
16✔
1393
        wait_for_upload(*sync_realm);
17✔
1394
        wait_for_download(*sync_realm);
17!
1395

9✔
1396
        sync_realm->convert(local_config1);
16✔
1397

12✔
1398
        auto local_realm = Realm::get_shared_realm(local_config1);
17✔
1399

9✔
1400
        // Check that the data also exists in the new realm
9✔
1401
        REQUIRE(local_realm->read_group().get_table("class_object")->size() == 1);
17!
1402
    }
17✔
1403

33✔
1404
    SECTION("can convert a local realm to a synced realm") {
64✔
1405
        auto local_realm = Realm::get_shared_realm(local_config1);
17✔
1406
        local_realm->begin_transaction();
16✔
1407
        local_realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
17✔
1408
        local_realm->commit_transaction();
16✔
1409

8✔
1410
        // Copy to a new sync config
9!
1411
        local_realm->convert(sync_config1);
17✔
1412

8✔
1413
        auto sync_realm = Realm::get_shared_realm(sync_config1);
20✔
1414

9✔
1415
        // Check that the data also exists in the new realm
9✔
1416
        REQUIRE(sync_realm->read_group().get_table("class_object")->size() == 1);
17!
1417
    }
17✔
1418

32✔
1419
    SECTION("can copy a local realm to a local realm") {
64✔
1420
        auto local_realm1 = Realm::get_shared_realm(local_config1);
17✔
1421
        local_realm1->begin_transaction();
16✔
1422
        local_realm1->read_group().get_table("class_object")->create_object_with_primary_key(0);
17✔
1423
        local_realm1->commit_transaction();
16✔
1424

8✔
1425
        // Copy to a new local config
9!
1426
        TestFile local_config2;
17✔
1427
        local_config2.schema = schema;
16✔
1428
        local_config2.schema_version = local_config1.schema_version;
20✔
1429
        local_realm1->convert(local_config2);
17✔
1430

9✔
1431
        auto local_realm2 = Realm::get_shared_realm(local_config2);
17✔
1432

9✔
1433
        // Check that the data also exists in the new realm
8✔
1434
        REQUIRE(local_realm2->read_group().get_table("class_object")->size() == 1);
16!
1435
    }
17✔
1436
}
65✔
1437

1✔
1438
TEST_CASE("SharedRealm: convert - embedded objects", "[sync][pbs][convert][embedded objects]") {
129✔
1439
    TestSyncManager tsm;
128✔
1440
    ObjectSchema object_schema = {"object",
129✔
1441
                                  {
128✔
1442
                                      {"_id", PropertyType::Int, Property::IsPrimary{true}},
128✔
1443
                                      {"value", PropertyType::Int},
129!
1444
                                      {"embedded_link", PropertyType::Object | PropertyType::Nullable, "embedded"},
129✔
1445
                                  }};
132✔
1446
    ObjectSchema embedded_schema = {"embedded",
128✔
1447
                                    ObjectSchema::ObjectType::Embedded,
136✔
1448
                                    {
136✔
1449
                                        {"name", PropertyType::String | PropertyType::Nullable},
136✔
1450
                                    }};
136✔
1451
    Schema schema{object_schema, embedded_schema};
136✔
1452

72✔
1453
    SyncTestFile sync_config1(tsm, "default");
136✔
1454
    sync_config1.schema = schema;
136✔
1455
    TestFile local_config1;
136✔
1456
    local_config1.schema = schema;
136✔
1457
    local_config1.schema_version = sync_config1.schema_version;
136✔
1458

72✔
1459
    SECTION("can copy a synced realm to a synced realm") {
136✔
1460
        auto sync_realm1 = Realm::get_shared_realm(sync_config1);
40✔
1461
        sync_realm1->begin_transaction();
32✔
1462

24✔
1463
        SECTION("null embedded object") {
40✔
1464
            sync_realm1->read_group().get_table("class_object")->create_object_with_primary_key(0);
24✔
1465
        }
24✔
1466

24✔
1467
        SECTION("embedded object") {
32✔
1468
            auto obj = sync_realm1->read_group().get_table("class_object")->create_object_with_primary_key(0);
24✔
1469
            auto col_key = sync_realm1->read_group().get_table("class_object")->get_column_key("embedded_link");
18✔
1470
            obj.create_and_set_linked_object(col_key);
18✔
1471
        }
16✔
1472

18✔
1473
        sync_realm1->commit_transaction();
33✔
1474
        wait_for_upload(*sync_realm1);
33✔
1475
        wait_for_download(*sync_realm1);
32✔
1476

18✔
1477
        // Copy to a new sync config
17✔
1478
        SyncTestFile sync_config2(tsm, "default");
33✔
1479
        sync_config2.schema = schema;
33✔
1480

17✔
1481
        sync_realm1->convert(sync_config2);
32✔
1482

18✔
1483
        auto sync_realm2 = Realm::get_shared_realm(sync_config2);
34✔
1484

18✔
1485
        // Check that the data also exists in the new realm
16✔
1486
        REQUIRE(sync_realm2->read_group().get_table("class_object")->size() == 1);
32!
1487

18✔
1488
        // Verify that sync works and objects created in the new copy will get
18✔
1489
        // synchronized to the old copy
16✔
1490
        sync_realm2->begin_transaction();
34✔
1491
        sync_realm2->read_group().get_table("class_object")->create_object_with_primary_key(1);
32✔
1492
        sync_realm2->commit_transaction();
34✔
1493
        wait_for_upload(*sync_realm2);
32✔
1494
        wait_for_download(*sync_realm1);
32✔
1495

18!
1496
        sync_realm1->refresh();
32✔
1497
        REQUIRE(sync_realm1->read_group().get_table("class_object")->size() == 2);
32!
1498
    }
32✔
1499

66✔
1500
    SECTION("can convert a synced realm to a local realm") {
130✔
1501
        auto sync_realm = Realm::get_shared_realm(sync_config1);
34✔
1502
        sync_realm->begin_transaction();
34✔
1503

18✔
1504
        SECTION("null embedded object") {
32✔
1505
            sync_realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
18✔
1506
        }
18!
1507

18✔
1508
        SECTION("embedded object") {
32✔
1509
            auto obj = sync_realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
24✔
1510
            auto col_key = sync_realm->read_group().get_table("class_object")->get_column_key("embedded_link");
18✔
1511
            obj.create_and_set_linked_object(col_key);
18✔
1512
        }
16✔
1513

18✔
1514
        sync_realm->commit_transaction();
33✔
1515
        wait_for_upload(*sync_realm);
33✔
1516
        wait_for_download(*sync_realm);
32✔
1517

18✔
1518
        sync_realm->convert(local_config1);
33✔
1519

17✔
1520
        auto local_realm = Realm::get_shared_realm(local_config1);
33✔
1521

17✔
1522
        // Check that the data also exists in the new realm
16✔
1523
        REQUIRE(local_realm->read_group().get_table("class_object")->size() == 1);
34!
1524
    }
34✔
1525

66✔
1526
    SECTION("can convert a local realm to a synced realm") {
128✔
1527
        auto local_realm = Realm::get_shared_realm(local_config1);
34✔
1528
        local_realm->begin_transaction();
32✔
1529

18✔
1530
        SECTION("null embedded object") {
32✔
1531
            local_realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
16✔
1532
        }
18!
1533

18✔
1534
        SECTION("embedded object") {
32✔
1535
            auto obj = local_realm->read_group().get_table("class_object")->create_object_with_primary_key(0);
24✔
1536
            auto col_key = local_realm->read_group().get_table("class_object")->get_column_key("embedded_link");
18✔
1537
            obj.create_and_set_linked_object(col_key);
18✔
1538
        }
16✔
1539

18✔
1540
        local_realm->commit_transaction();
33✔
1541

17✔
1542
        // Copy to a new sync config
16✔
1543
        local_realm->convert(sync_config1);
34✔
1544

17✔
1545
        auto sync_realm = Realm::get_shared_realm(sync_config1);
33✔
1546

17✔
1547
        // Check that the data also exists in the new realm
17✔
1548
        REQUIRE(sync_realm->read_group().get_table("class_object")->size() == 1);
32!
1549
    }
34✔
1550

64✔
1551
    SECTION("can copy a local realm to a local realm") {
128✔
1552
        auto local_realm1 = Realm::get_shared_realm(local_config1);
34✔
1553
        local_realm1->begin_transaction();
32✔
1554

18✔
1555
        SECTION("null embedded object") {
32✔
1556
            local_realm1->read_group().get_table("class_object")->create_object_with_primary_key(0);
16✔
1557
        }
18!
1558

18✔
1559
        SECTION("embedded object") {
32✔
1560
            auto obj = local_realm1->read_group().get_table("class_object")->create_object_with_primary_key(0);
24✔
1561
            auto col_key = local_realm1->read_group().get_table("class_object")->get_column_key("embedded_link");
18✔
1562
            obj.create_and_set_linked_object(col_key);
18✔
1563
        }
16✔
1564

18✔
1565
        local_realm1->commit_transaction();
33✔
1566

17✔
1567
        // Copy to a new local config
16✔
1568
        TestFile local_config2;
34✔
1569
        local_config2.schema = schema;
33✔
1570
        local_config2.schema_version = local_config1.schema_version;
33✔
1571
        local_realm1->convert(local_config2);
33✔
1572

17✔
1573
        auto local_realm2 = Realm::get_shared_realm(local_config2);
32✔
1574

18✔
1575
        // Check that the data also exists in the new realm
16✔
1576
        REQUIRE(local_realm2->read_group().get_table("class_object")->size() == 1);
32!
1577
    }
34✔
1578
}
130✔
1579
#endif // REALM_ENABLE_SYNC
2✔
1580

2✔
1581
TEST_CASE("SharedRealm: async writes") {
832✔
1582
    _impl::RealmCoordinator::assert_no_open_realms();
834✔
1583
    if (!util::EventLoop::has_implementation())
832✔
1584
        return;
1585

418!
1586
    TestFile config;
834✔
1587
    config.schema_version = 0;
840✔
1588
    config.schema = Schema{
832✔
1589
        {"object",
832✔
1590
         {
884✔
1591
             {"value", PropertyType::Int},
884✔
1592
             {"ints", PropertyType::Array | PropertyType::Int},
884✔
1593
             {"int set", PropertyType::Set | PropertyType::Int},
832✔
1594
             {"int dictionary", PropertyType::Dictionary | PropertyType::Int},
832✔
1595
         }},
884✔
1596
    };
884✔
1597
    bool done = false;
884✔
1598
    auto realm = Realm::get_shared_realm(config);
884✔
1599
    auto table = realm->read_group().get_table("class_object");
884✔
1600
    auto col = table->get_column_key("value");
884✔
1601
    int write_nr = 0;
884✔
1602
    int commit_nr = 0;
884✔
1603

468✔
1604
    auto wait_for_done = [&]() {
852✔
1605
        util::EventLoop::main().run_until([&] {
633,746✔
1606
            return done;
633,746✔
1607
        });
633,746✔
1608
        REQUIRE(done);
820!
1609
    };
820✔
1610

468✔
1611
    SECTION("async commit transaction") {
884✔
1612
        realm->async_begin_transaction([&]() {
16✔
1613
            REQUIRE(write_nr == 0);
64!
1614
            ++write_nr;
1,376✔
1615
            table->create_object().set(col, 45);
1,376✔
1616
            realm->async_commit_transaction([&](std::exception_ptr) {
1,376✔
1617
                REQUIRE(commit_nr == 0);
64!
1618
                ++commit_nr;
64✔
1619
            });
16✔
1620
        });
68✔
1621
        for (int expected = 1; expected < 1000; ++expected) {
16,001✔
1622
            realm->async_begin_transaction([&, expected]() {
15,985!
1623
                REQUIRE(write_nr == expected);
15,985!
1624
                ++write_nr;
15,985✔
1625
                auto o = table->get_object(0);
15,985✔
1626
                o.set(col, o.get<int64_t>(col) + 37);
15,985!
1627
                realm->async_commit_transaction(
15,985✔
1628
                    [&](auto) {
15,985✔
1629
                        ++commit_nr;
15,985✔
1630
                        done = commit_nr == 1000;
16,984✔
1631
                    },
16,983✔
1632
                    true);
16,983!
1633
            });
16,983✔
1634
        }
16,983✔
1635
        wait_for_done();
1,015✔
1636
    }
1,015✔
1637

1,415✔
1638
    auto verify_persisted_count = [&](size_t expected) {
1,623✔
1639
        if (realm)
1,415✔
1640
            realm->close();
1,399✔
1641
        _impl::RealmCoordinator::assert_no_open_realms();
1,415✔
1642

1,207✔
1643
        auto new_realm = Realm::get_shared_realm(config);
1,415✔
1644
        auto table = new_realm->read_group().get_table("class_object");
417✔
1645
        REQUIRE(table->size() == expected);
417!
1646
    };
416✔
1647

442✔
1648
    using RealmCloseFunction = void (Realm::*)();
858✔
1649
    static RealmCloseFunction close_functions[] = {&Realm::close, &Realm::invalidate};
857✔
1650
    static const char* close_function_names[] = {"close()", "invalidate()"};
858✔
1651
    for (int i = 0; i < 2; ++i) {
2,496✔
1652
        SECTION(close_function_names[i]) {
1,690✔
1653
            bool persisted = false;
346✔
1654
            SECTION("before write lock is acquired") {
346!
1655
                DBOptions options;
58✔
1656
                options.encryption_key = config.encryption_key.data();
32✔
1657
                // Acquire the write lock with a different DB instance so that we'll
68✔
1658
                // be stuck in the Requesting stage
68✔
1659
                realm::test_util::BowlOfStonesSemaphore sema;
84✔
1660
                JoiningThread thread([&] {
188✔
1661
                    auto db = DB::create(make_in_realm_history(), config.path, options);
136✔
1662
                    auto write = db->start_write();
52✔
1663
                    sema.add_stone();
52✔
1664

18✔
1665
                    // Wait until the main thread is waiting for the lock.
18✔
1666
                    while (!db->other_writers_waiting_for_lock()) {
64✔
1667
                        millisleep(1);
32✔
1668
                    }
34✔
1669
                    write->close();
34✔
1670
                });
34✔
1671

18✔
1672
                // Wait for the background thread to have acquired the lock
18✔
1673
                sema.get_stone();
32✔
1674

16✔
1675
                auto scheduler = realm->scheduler();
36✔
1676
                realm->async_begin_transaction([&] {
18✔
1677
                    // We should never get here as the realm is closed
2✔
1678
                    FAIL();
2✔
1679
                });
2✔
1680

16✔
1681
                // close() should block until we can acquire the write lock
16✔
1682
                std::invoke(close_functions[i], *realm);
34✔
1683

16✔
1684
                {
34✔
1685
                    // Verify that we released the write lock
16✔
1686
                    auto db = DB::create(make_in_realm_history(), config.path, options);
32✔
1687
                    REQUIRE(db->start_write(/* nonblocking */ true));
32!
1688
                }
32✔
1689

16✔
1690
                // Verify that the transaction callback never got enqueued
16✔
1691
                scheduler->invoke([&] {
34✔
1692
                    done = true;
32✔
1693
                });
34✔
1694
                wait_for_done();
32✔
1695
            }
34✔
1696
            SECTION("before async_begin_transaction() callback") {
322!
1697
                auto scheduler = realm->scheduler();
34✔
1698
                realm->async_begin_transaction([&] {
16✔
1699
                    // We should never get here as the realm is closed
1700
                    FAIL();
2✔
1701
                });
2✔
1702
                std::invoke(close_functions[i], *realm);
34✔
1703
                scheduler->invoke([&] {
34✔
1704
                    done = true;
34✔
1705
                });
52✔
1706
                wait_for_done();
34✔
1707
                verify_persisted_count(0);
32✔
1708
            }
32✔
1709
            SECTION("inside async_begin_transaction() callback before commit") {
320✔
1710
                realm->async_begin_transaction([&] {
32✔
1711
                    table->create_object().set(col, 45);
34✔
1712
                    std::invoke(close_functions[i], *realm);
34✔
1713
                    done = true;
34✔
1714
                });
34✔
1715
                wait_for_done();
34✔
1716
                verify_persisted_count(0);
34✔
1717
            }
34✔
1718
            SECTION("inside async_begin_transaction() callback after sync commit") {
340✔
1719
                realm->async_begin_transaction([&] {
34✔
1720
                    table->create_object().set(col, 45);
34✔
1721
                    realm->commit_transaction();
34✔
1722
                    std::invoke(close_functions[i], *realm);
34✔
1723
                    done = true;
34✔
1724
                });
34✔
1725
                wait_for_done();
34✔
1726
                verify_persisted_count(1);
34✔
1727
            }
52✔
1728
            SECTION("inside async_begin_transaction() callback after async commit") {
322✔
1729
                realm->async_begin_transaction([&] {
34✔
1730
                    table->create_object().set(col, 45);
34✔
1731
                    realm->async_commit_transaction([&](std::exception_ptr) {
34✔
1732
                        persisted = true;
34✔
1733
                    });
34✔
1734
                    std::invoke(close_functions[i], *realm);
34✔
1735
                    REQUIRE(persisted);
34!
1736
                    done = true;
34✔
1737
                });
52✔
1738
                wait_for_done();
34✔
1739
                verify_persisted_count(1);
34✔
1740
            }
34✔
1741
            SECTION("inside async commit completion") {
322✔
1742
                realm->async_begin_transaction([&] {
34✔
1743
                    table->create_object().set(col, 45);
34✔
1744
                    realm->async_commit_transaction([&](std::exception_ptr) {
34!
1745
                        done = true;
34✔
1746
                        std::invoke(close_functions[i], *realm);
34✔
1747
                    });
34✔
1748
                });
34✔
1749
                wait_for_done();
34✔
1750
                verify_persisted_count(1);
52✔
1751
            }
34✔
1752
            SECTION("between commit and sync") {
322✔
1753
                realm->async_begin_transaction([&] {
34✔
1754
                    table->create_object().set(col, 45);
34✔
1755
                    realm->async_commit_transaction([&](std::exception_ptr) {
34✔
1756
                        persisted = true;
34✔
1757
                    });
34✔
1758
                    done = true;
34✔
1759
                });
34✔
1760
                wait_for_done();
34✔
1761
                std::invoke(close_functions[i], *realm);
52✔
1762
                REQUIRE(persisted);
34!
1763
                verify_persisted_count(1);
34✔
1764
            }
34✔
1765
            SECTION("with multiple pending commits") {
322✔
1766
                int complete_count = 0;
34✔
1767
                realm->async_begin_transaction([&] {
34✔
1768
                    table->create_object().set(col, 45);
34✔
1769
                    realm->async_commit_transaction([&](std::exception_ptr) {
34✔
1770
                        ++complete_count;
34✔
1771
                    });
34!
1772
                });
34✔
1773
                realm->async_begin_transaction([&] {
34✔
1774
                    table->create_object().set(col, 45);
52✔
1775
                    realm->async_commit_transaction(
34✔
1776
                        [&](auto) {
34✔
1777
                            ++complete_count;
34✔
1778
                        },
34✔
1779
                        true);
34✔
1780
                });
34✔
1781
                realm->async_begin_transaction([&] {
34✔
1782
                    table->create_object().set(col, 45);
34✔
1783
                    realm->async_commit_transaction(
34✔
1784
                        [&](auto) {
34✔
1785
                            ++complete_count;
34✔
1786
                        },
34✔
1787
                        true);
34✔
1788
                    done = true;
34✔
1789
                });
34✔
1790

18✔
1791
                wait_for_done();
34✔
1792
                std::invoke(close_functions[i], *realm);
34✔
1793
                REQUIRE(complete_count == 3);
34!
1794
                verify_persisted_count(3);
34✔
1795
            }
34✔
1796
            SECTION("inside async_begin_transaction() with pending commits") {
322✔
1797
                int complete_count = 0;
34✔
1798
                realm->async_begin_transaction([&] {
34✔
1799
                    table->create_object().set(col, 45);
32✔
1800
                    realm->async_commit_transaction([&](std::exception_ptr) {
34✔
1801
                        ++complete_count;
34✔
1802
                    });
34!
1803
                });
34✔
1804
                realm->async_begin_transaction([&] {
34✔
1805
                    // This create should be discarded
36✔
1806
                    table->create_object().set(col, 45);
34✔
1807
                    std::invoke(close_functions[i], *realm);
34✔
1808
                    done = true;
34✔
1809
                });
34✔
1810

18✔
1811
                wait_for_done();
34✔
1812
                std::invoke(close_functions[i], *realm);
34✔
1813
                REQUIRE(complete_count == 1);
34!
1814
                verify_persisted_count(1);
32✔
1815
            }
34✔
1816
            SECTION("within did_change()") {
322✔
1817
                struct Context : public BindingContext {
34✔
1818
                    int i;
34✔
1819
                    Context(int i)
32✔
1820
                        : i(i)
34✔
1821
                    {
34✔
1822
                    }
34!
1823
                    void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
34✔
1824
                    {
50✔
1825
                        std::invoke(close_functions[i], *realm.lock());
68✔
1826
                    }
50✔
1827
                };
34✔
1828
                realm->m_binding_context.reset(new Context(i));
34✔
1829
                realm->m_binding_context->realm = realm;
34✔
1830

18✔
1831
                realm->async_begin_transaction([&] {
34✔
1832
                    table->create_object().set(col, 45);
34✔
1833
                    realm->async_commit_transaction([&](std::exception_ptr) {
35✔
1834
                        done = true;
35✔
1835
                    });
35✔
1836
                });
34✔
1837

18✔
1838
                wait_for_done();
34✔
1839
                verify_persisted_count(1);
32✔
1840
            }
34✔
1841
        }
322✔
1842
    }
1,666✔
1843

418✔
1844
    SECTION("notify only with no further actions") {
834✔
1845
        realm->async_begin_transaction(
18✔
1846
            [&] {
16✔
1847
                done = true;
18✔
1848
            },
18✔
1849
            true);
18✔
1850
        wait_for_done();
36✔
1851
        realm->cancel_transaction();
120✔
1852
    }
16✔
1853
    SECTION("notify only with synchronous commit") {
884✔
1854
        realm->async_begin_transaction(
17✔
1855
            [&] {
17✔
1856
                done = true;
17✔
1857
            },
17✔
1858
            true);
17✔
1859
        wait_for_done();
17✔
1860
        table->create_object();
17✔
1861
        realm->commit_transaction();
17✔
1862
    }
68✔
1863
    SECTION("schedule async commits after notify only") {
833✔
1864
        realm->async_begin_transaction(
17✔
1865
            [&] {
17✔
1866
                done = true;
17✔
1867
            },
17✔
1868
            true);
17✔
1869
        wait_for_done();
17✔
1870
        done = false;
17✔
1871
        realm->async_begin_transaction([&] {
17✔
1872
            table->create_object();
68✔
1873
            done = true;
17✔
1874
            realm->commit_transaction();
17✔
1875
        });
17✔
1876
        table->create_object();
17✔
1877
        realm->commit_transaction();
17✔
1878
        REQUIRE(table->size() == 1);
17!
1879
        wait_for_done();
17✔
1880
        REQUIRE(table->size() == 2);
17!
1881
    }
17✔
1882
    SECTION("exception thrown during transaction with error handler") {
833✔
1883
        Realm::AsyncHandle h = 7;
17✔
1884
        bool called = false;
17✔
1885
        realm->set_async_error_handler([&](Realm::AsyncHandle handle, std::exception_ptr error) {
17✔
1886
            REQUIRE(error);
17!
1887
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "an error");
17✔
1888
            CHECK(handle == h);
17!
1889
            called = true;
17!
1890
        });
17✔
1891
        h = realm->async_begin_transaction([&] {
68✔
1892
            table->create_object();
17✔
1893
            done = true;
17✔
1894
            throw std::runtime_error("an error");
17✔
1895
        });
17!
1896
        wait_for_done();
17✔
1897

9!
1898
        // Transaction should have been rolled back
9✔
1899
        REQUIRE_FALSE(realm->is_in_transaction());
17!
1900
        REQUIRE(table->size() == 0);
17!
1901
        REQUIRE(called);
17!
1902

9✔
1903
        // Should be able to perform another write afterwards
9✔
1904
        done = false;
17✔
1905
        called = false;
17✔
1906
        h = realm->async_begin_transaction([&] {
16✔
1907
            table->create_object();
16✔
1908
            realm->commit_transaction();
17!
1909
            done = true;
17!
1910
        });
17!
1911
        wait_for_done();
16✔
1912
        REQUIRE(table->size() == 1);
16!
1913
        REQUIRE_FALSE(called);
17!
1914
    }
17✔
1915
#ifndef _WIN32
833✔
1916
    SECTION("exception thrown during transaction without error handler") {
833✔
1917
        realm->set_async_error_handler(nullptr);
17✔
1918
        realm->async_begin_transaction([&] {
17✔
1919
            table->create_object();
17✔
1920
            throw std::runtime_error("an error");
17✔
1921
        });
17!
1922
        REQUIRE_THROWS_CONTAINING(util::EventLoop::main().run_until([&] {
17✔
1923
            return false;
17✔
1924
        }),
68✔
1925
                                  "an error");
68✔
1926

9✔
1927
        // Transaction should have been rolled back
9✔
1928
        REQUIRE_FALSE(realm->is_in_transaction());
17!
1929
        REQUIRE(table->size() == 0);
17!
1930

9✔
1931
        // Should be able to perform another write afterwards
9✔
1932
        realm->async_begin_transaction([&, realm] {
17✔
1933
            table->create_object();
17✔
1934
            realm->commit_transaction();
17✔
1935
            done = true;
16✔
1936
        });
16✔
1937
        wait_for_done();
17!
1938
        REQUIRE(table->size() == 1);
17!
1939
    }
16✔
1940
    SECTION("exception thrown during transaction without error handler after closing Realm") {
832✔
1941
        realm->set_async_error_handler(nullptr);
17✔
1942
        realm->async_begin_transaction([&] {
17✔
1943
            realm->close();
17✔
1944
            throw std::runtime_error("an error");
17✔
1945
        });
17✔
1946
        REQUIRE_THROWS_CONTAINING(util::EventLoop::main().run_until([&] {
17✔
1947
            return false;
17!
1948
        }),
17✔
1949
                                  "an error");
68✔
1950
        REQUIRE(realm->is_closed());
17!
1951
    }
17✔
1952
#endif
833✔
1953
    SECTION("exception thrown from async commit completion callback with error handler") {
833✔
1954
        Realm::AsyncHandle h;
17✔
1955
        realm->set_async_error_handler([&](Realm::AsyncHandle handle, std::exception_ptr error) {
17✔
1956
            REQUIRE(error);
17!
1957
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "an error");
17✔
1958
            CHECK(handle == h);
17!
1959
            done = true;
17!
1960
        });
17✔
1961

60✔
1962
        realm->begin_transaction();
68✔
1963
        table->create_object();
17✔
1964
        h = realm->async_commit_transaction([&](std::exception_ptr) {
17✔
1965
            throw std::runtime_error("an error");
17!
1966
        });
17✔
1967
        wait_for_done();
17!
1968
        verify_persisted_count(1);
17✔
1969
    }
17✔
1970
#ifndef _WIN32
832✔
1971
    SECTION("exception thrown from async commit completion callback without error handler") {
833✔
1972
        realm->begin_transaction();
17✔
1973
        table->create_object();
17✔
1974
        realm->async_commit_transaction([&](std::exception_ptr) {
17✔
1975
            throw std::runtime_error("an error");
17✔
1976
        });
17✔
1977
        REQUIRE_THROWS_CONTAINING(util::EventLoop::main().run_until([&] {
17✔
1978
            return false;
17✔
1979
        }),
68✔
1980
                                  "an error");
68✔
1981
        REQUIRE(table->size() == 1);
17!
1982
    }
17✔
1983
#endif
833✔
1984

417✔
1985
    if (_impl::SimulatedFailure::is_enabled()) {
833✔
1986
        SECTION("error in the synchronous part of async commit") {
833✔
1987
            realm->begin_transaction();
17✔
1988
            table->create_object();
17✔
1989

9✔
1990
            using sf = _impl::SimulatedFailure;
17!
1991
            sf::OneShotPrimeGuard pg(sf::shared_group__grow_reader_mapping);
17✔
1992
            REQUIRE_THROWS_AS(realm->async_commit_transaction([&](std::exception_ptr) {
68✔
1993
                FAIL("should not call completion");
16✔
1994
            }),
68✔
1995
                              _impl::SimulatedFailure);
68✔
1996
            REQUIRE_FALSE(realm->is_in_transaction());
17!
1997
        }
17✔
1998
        SECTION("error in the async part of async commit") {
832✔
1999
            realm->begin_transaction();
17✔
2000
            table->create_object();
17✔
2001

9✔
2002
            using sf = _impl::SimulatedFailure;
17✔
2003
            sf::set_thread_local(false);
17✔
2004
            sf::OneShotPrimeGuard pg(sf::group_writer__commit);
17✔
2005
            realm->async_commit_transaction([&](std::exception_ptr e) {
17!
2006
                REQUIRE(e);
17!
2007
                REQUIRE_THROWS_AS(std::rethrow_exception(e), _impl::SimulatedFailure);
68✔
2008
                done = true;
17✔
2009
            });
17✔
2010
            wait_for_done();
16✔
2011
            sf::set_thread_local(true);
17✔
2012
        }
17✔
2013
    }
833✔
2014
    SECTION("throw exception from did_change()") {
833✔
2015
        struct Context : public BindingContext {
17!
2016
            void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
17✔
2017
            {
17✔
2018
                throw std::runtime_error("expected error");
17✔
2019
            }
17✔
2020
        };
17✔
2021
        realm->m_binding_context.reset(new Context);
17✔
2022

60✔
2023
        realm->begin_transaction();
68✔
2024
        auto table = realm->read_group().get_table("class_object");
17✔
2025
        table->create_object();
17✔
2026
        REQUIRE_THROWS_WITH(realm->async_commit_transaction([&](std::exception_ptr) {
17✔
2027
            done = true;
17✔
2028
        }),
17✔
2029
                            "expected error");
17✔
2030
        wait_for_done();
17✔
2031
    }
16✔
2032
    SECTION("cancel scheduled async transaction") {
833✔
2033
        auto handle = realm->async_begin_transaction([&, realm]() {
9✔
2034
            table->create_object().set(col, 45);
1✔
2035
            realm->async_commit_transaction(
1✔
2036
                [&](auto) {
1✔
2037
                    done = true;
1✔
2038
                },
1✔
2039
                true);
1✔
2040
        });
1✔
2041
        realm->async_begin_transaction([&, realm]() {
68✔
2042
            table->create_object().set(col, 90);
16✔
2043
            realm->async_commit_transaction(
16✔
2044
                [&](auto) {
16✔
2045
                    done = true;
16✔
2046
                },
16✔
2047
                true);
16✔
2048
        });
16✔
2049
        realm->async_cancel_transaction(handle);
16✔
2050
        wait_for_done();
17✔
2051
        auto table = realm->read_group().get_table("class_object");
17✔
2052
        REQUIRE(table->size() == 1);
17!
2053
        REQUIRE(table->begin()->get<Int>("value") == 90);
17!
2054
    }
17✔
2055
    SECTION("synchronous cancel inside async transaction") {
833✔
2056
        realm->async_begin_transaction([&, realm]() {
17✔
2057
            REQUIRE(table->size() == 0);
17!
2058
            table->create_object().set(col, 45);
17✔
2059
            REQUIRE(table->size() == 1);
17!
2060
            realm->cancel_transaction();
17✔
2061
            REQUIRE(table->size() == 0);
17!
2062
            done = true;
17!
2063
        });
17✔
2064
        wait_for_done();
68✔
2065
    }
17✔
2066
    SECTION("synchronous commit of async transaction after async commit which allows grouping") {
833!
2067
        realm->async_begin_transaction([&, realm]() {
17✔
2068
            table->create_object().set(col, 45);
17!
2069
            realm->async_commit_transaction(
17✔
2070
                [&](auto) {
17!
2071
                    done = true;
17✔
2072
                },
17✔
2073
                true);
17✔
2074
        });
17✔
2075
        realm->async_begin_transaction([&, realm]() {
68✔
2076
            table->create_object().set(col, 45);
17✔
2077
            realm->commit_transaction();
17✔
2078
        });
17✔
2079
        wait_for_done();
17✔
2080
        auto table = realm->read_group().get_table("class_object");
17✔
2081
        REQUIRE(table->size() == 2);
17!
2082
    }
17✔
2083
    SECTION("synchronous transaction after async transaction with no commit") {
833✔
2084
        realm->async_begin_transaction([&]() {
17✔
2085
            table->create_object().set(col, 80);
17✔
2086
            done = true;
17✔
2087
        });
17✔
2088
        wait_for_done();
17✔
2089
        realm->begin_transaction();
17✔
2090
        table->create_object().set(col, 90);
17!
2091
        realm->commit_transaction();
17✔
2092
        verify_persisted_count(1);
68✔
2093
    }
17✔
2094
    SECTION("synchronous transaction with scheduled async transaction with no commit") {
833✔
2095
        realm->async_begin_transaction([&]() {
17✔
2096
            table->create_object().set(col, 80);
17✔
2097
            done = true;
17✔
2098
        });
17✔
2099
        realm->begin_transaction();
17✔
2100
        table->create_object().set(col, 90);
17✔
2101
        realm->commit_transaction();
17✔
2102
        wait_for_done();
17✔
2103
        verify_persisted_count(1);
68✔
2104
    }
17✔
2105
    SECTION("synchronous transaction with scheduled async transaction") {
833✔
2106
        realm->async_begin_transaction([&, realm]() {
17✔
2107
            table->create_object().set(col, 80);
17✔
2108
            realm->commit_transaction();
17✔
2109
            done = true;
17✔
2110
        });
17✔
2111
        realm->begin_transaction();
17✔
2112
        table->create_object().set(col, 90);
17✔
2113
        realm->commit_transaction();
17✔
2114
        wait_for_done();
68✔
2115
        REQUIRE(table->size() == 2);
17!
2116
        REQUIRE(table->get_object(0).get<Int>(col) == 90);
17!
2117
        REQUIRE(table->get_object(1).get<Int>(col) == 80);
17!
2118
    }
17✔
2119
    SECTION("synchronous transaction with async write") {
833✔
2120
        realm->begin_transaction();
17✔
2121
        table->create_object().set(col, 45);
17✔
2122
        realm->async_commit_transaction();
17✔
2123

9✔
2124
        realm->begin_transaction();
17!
2125
        table->create_object().set(col, 90);
17!
2126
        realm->async_commit_transaction([&](std::exception_ptr) {
17!
2127
            done = true;
17✔
2128
        });
68✔
2129
        wait_for_done();
17✔
2130
        verify_persisted_count(2);
17✔
2131
    }
17✔
2132
    SECTION("synchronous transaction mixed with async transactions") {
832✔
2133
        realm->async_begin_transaction([&, realm]() {
17✔
2134
            table->create_object().set(col, 45);
17✔
2135
            done = true;
17✔
2136
            realm->async_commit_transaction();
17✔
2137
        });
17✔
2138
        realm->async_begin_transaction([&, realm]() {
17✔
2139
            table->create_object().set(col, 45);
17✔
2140
            realm->async_commit_transaction([&](std::exception_ptr) {
17✔
2141
                done = true;
68✔
2142
            });
17✔
2143
        });
17✔
2144
        wait_for_done();
17✔
2145
        realm->begin_transaction(); // Here syncing of first async tr has not completed
17✔
2146
        REQUIRE(table->size() == 1);
17!
2147
        table->create_object().set(col, 90);
17✔
2148
        realm->commit_transaction(); // Will re-initiate async writes
17✔
2149

9✔
2150
        done = false;
17✔
2151
        wait_for_done();
17✔
2152
        verify_persisted_count(3);
17✔
2153
    }
17✔
2154
    SECTION("asynchronous transaction mixed with sync transaction that is cancelled") {
833✔
2155
        bool persisted = false;
17!
2156
        realm->async_begin_transaction([&, realm]() {
17✔
2157
            table->create_object().set(col, 45);
17✔
2158
            done = true;
16✔
2159
            realm->async_commit_transaction([&](std::exception_ptr) {
17✔
2160
                persisted = true;
17✔
2161
            });
17✔
2162
        });
17✔
2163
        realm->async_begin_transaction([&, realm]() {
68✔
2164
            table->create_object().set(col, 45);
17✔
2165
            auto handle = realm->async_commit_transaction([&](std::exception_ptr) {
9✔
2166
                FAIL();
1✔
2167
            });
1✔
2168
            realm->async_cancel_transaction(handle);
17✔
2169
        });
17✔
2170
        wait_for_done();
17✔
2171
        realm->begin_transaction();
17✔
2172
        CHECK(persisted);
17!
2173
        persisted = false;
17✔
2174
        REQUIRE(table->size() == 1);
16!
2175
        table->create_object().set(col, 90);
16✔
2176
        realm->cancel_transaction();
16✔
2177

9✔
2178
        util::EventLoop::main().run_until([&] {
8,191✔
2179
            return !realm->is_in_async_transaction();
8,191✔
2180
        });
8,191✔
2181

9!
2182
        REQUIRE(table->size() == 2);
17!
2183
        REQUIRE(!table->find_first_int(col, 90));
17!
2184
    }
17✔
2185
    SECTION("cancelled sync transaction with pending async transaction") {
833✔
2186
        realm->async_begin_transaction([&, realm]() {
16✔
2187
            table->create_object().set(col, 45);
26✔
2188
            realm->async_commit_transaction([&](std::exception_ptr) {
26✔
2189
                done = true;
26✔
2190
            });
16✔
2191
        });
17!
2192
        realm->begin_transaction();
17!
2193
        REQUIRE(table->size() == 0);
17!
2194
        table->create_object();
68✔
2195
        realm->cancel_transaction();
17✔
2196
        REQUIRE(table->size() == 0);
17!
2197
        wait_for_done();
17✔
2198
        verify_persisted_count(1);
17✔
2199
    }
17✔
2200
    SECTION("cancelled sync transaction with pending async commit") {
833✔
2201
        bool persisted = false;
17✔
2202
        realm->async_begin_transaction([&, realm]() {
17!
2203
            table->create_object().set(col, 45);
17✔
2204
            done = true;
17✔
2205
            realm->async_commit_transaction([&](std::exception_ptr) {
17!
2206
                persisted = true;
17✔
2207
            });
17✔
2208
        });
17✔
2209
        wait_for_done();
68✔
2210
        realm->begin_transaction();
17✔
2211
        REQUIRE(table->size() == 1);
17!
2212
        table->create_object();
17✔
2213
        realm->cancel_transaction();
17✔
2214

9✔
2215
        util::EventLoop::main().run_until([&] {
25✔
2216
            return persisted;
25✔
2217
        });
25✔
2218
        verify_persisted_count(1);
17✔
2219
    }
17✔
2220
    SECTION("sync commit of async transaction with subsequent pending async transaction") {
833!
2221
        realm->async_begin_transaction([&, realm]() {
17✔
2222
            table->create_object();
17✔
2223
            realm->commit_transaction();
16✔
2224
        });
18✔
2225
        realm->async_begin_transaction([&, realm]() {
18✔
2226
            table->create_object();
18✔
2227
            realm->commit_transaction();
17✔
2228
            done = true;
17✔
2229
        });
68✔
2230
        wait_for_done();
17✔
2231
        REQUIRE(table->size() == 2);
17!
2232
    }
17✔
2233
    SECTION("release reference to Realm after async begin") {
833✔
2234
        std::weak_ptr<Realm> weak_realm = realm;
17✔
2235
        realm->async_begin_transaction([&]() {
17✔
2236
            table->create_object().set(col, 45);
17✔
2237
            weak_realm.lock()->async_commit_transaction([&](std::exception_ptr) {
17✔
2238
                done = true;
17✔
2239
            });
17✔
2240
        });
17!
2241
        realm = nullptr;
17✔
2242
        wait_for_done();
68✔
2243
        verify_persisted_count(1);
17✔
2244
    }
17✔
2245
    SECTION("object change information") {
833✔
2246
        realm->begin_transaction();
17✔
2247
        auto list_col = table->get_column_key("ints");
17✔
2248
        auto set_col = table->get_column_key("int set");
17✔
2249
        auto dict_col = table->get_column_key("int dictionary");
17✔
2250
        auto obj = table->create_object();
17✔
2251
        auto list = obj.get_list<Int>(list_col);
17✔
2252
        for (int i = 0; i < 3; ++i)
65✔
2253
            list.add(i);
49✔
2254
        auto set = obj.get_set<Int>(set_col);
68✔
2255
        set.insert(0);
17✔
2256
        auto dict = obj.get_dictionary(dict_col);
17✔
2257
        dict.insert("a", 0);
17✔
2258
        realm->commit_transaction();
17✔
2259

9✔
2260
        Observer observer(obj);
17✔
2261
        observer.realm = realm;
20✔
2262
        realm->m_binding_context.reset(&observer);
19✔
2263

9✔
2264
        realm->async_begin_transaction([&]() {
17✔
2265
            list.clear();
17✔
2266
            set.clear();
17✔
2267
            dict.clear();
17✔
2268
            done = true;
16✔
2269
        });
17✔
2270
        wait_for_done();
17✔
2271
        REQUIRE(observer.array_change(0, list_col) == IndexSet{0, 1, 2});
17!
2272
        REQUIRE(observer.array_change(0, set_col) == IndexSet{});
16!
2273
        REQUIRE(observer.array_change(0, dict_col) == IndexSet{});
17!
2274
        realm->m_binding_context.release();
17✔
2275
    }
17✔
2276

417✔
2277
    SECTION("begin_transaction() from within did_change()") {
833✔
2278
        struct Context : public BindingContext {
17✔
2279
            void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
17✔
2280
            {
33!
2281
                auto r = realm.lock();
33!
2282
                r->begin_transaction();
33!
2283
                auto table = r->read_group().get_table("class_object");
33✔
2284
                table->create_object();
33✔
2285
                if (++change_count == 1) {
32✔
2286
                    r->commit_transaction();
68✔
2287
                }
17✔
2288
                else {
17✔
2289
                    r->cancel_transaction();
18✔
2290
                }
18✔
2291
            }
34✔
2292
            int change_count = 0;
18✔
2293
        };
18✔
2294

10✔
2295
        realm->m_binding_context.reset(new Context());
17✔
2296
        realm->m_binding_context->realm = realm;
17✔
2297

9✔
2298
        realm->begin_transaction();
17✔
2299
        auto table = realm->read_group().get_table("class_object");
17✔
2300
        table->create_object();
18✔
2301
        bool persisted = false;
17✔
2302
        realm->async_commit_transaction([&persisted](auto) {
17✔
2303
            persisted = true;
16✔
2304
        });
17✔
2305
        REQUIRE(table->size() == 2);
17!
2306
        REQUIRE(persisted);
16!
2307
    }
17✔
2308

417✔
2309
    SECTION("async write grouping") {
833✔
2310
        size_t completion_calls = 0;
17✔
2311
        for (size_t i = 0; i < 41; ++i) {
673✔
2312
            realm->async_begin_transaction([&, i, realm] {
657✔
2313
                // The top ref in the Realm file should only be updated once every 20 commits
329✔
2314
                CHECK(Group(config.path, config.encryption_key.data()).get_table("class_object")->size() ==
657!
2315
                      (i / 20) * 20);
657!
2316

329✔
2317
                table->create_object();
656✔
2318
                realm->async_commit_transaction(
708✔
2319
                    [&](std::exception_ptr) {
657✔
2320
                        ++completion_calls;
698✔
2321
                    },
697✔
2322
                    true);
656✔
2323
            });
697!
2324
        }
697✔
2325
        util::EventLoop::main().run_until([&] {
28,062✔
2326
            return completion_calls == 41;
28,103✔
2327
        });
28,103✔
2328
    }
57✔
2329

457✔
2330
    SECTION("async write grouping with manual barriers") {
873✔
2331
        size_t completion_calls = 0;
57✔
2332
        for (size_t i = 0; i < 41; ++i) {
713✔
2333
            realm->async_begin_transaction([&, i, realm] {
697✔
2334
                // The top ref in the Realm file should only be updated once every 6 commits
376✔
2335
                CHECK(Group(config.path, config.encryption_key.data()).get_table("class_object")->size() ==
704!
2336
                      (i / 6) * 6);
704✔
2337

329✔
2338
                table->create_object();
656✔
2339
                realm->async_commit_transaction(
708✔
2340
                    [&](std::exception_ptr) {
657✔
2341
                        ++completion_calls;
698✔
2342
                    },
697✔
2343
                    (i + 1) % 6 != 0);
656✔
2344
            });
697!
2345
        }
697✔
2346
        util::EventLoop::main().run_until([&] {
64,669✔
2347
            return completion_calls == 41;
64,710✔
2348
        });
64,710✔
2349
    }
57✔
2350

457✔
2351
    SECTION("async writes scheduled inside sync write") {
873✔
2352
        realm->begin_transaction();
57✔
2353
        realm->async_begin_transaction([&] {
57✔
2354
            REQUIRE(table->size() == 1);
57!
2355
            table->create_object();
136✔
2356
            realm->async_commit_transaction();
136✔
2357
        });
136✔
2358
        realm->async_begin_transaction([&] {
17✔
2359
            REQUIRE(table->size() == 2);
16!
2360
            table->create_object();
68✔
2361
            realm->async_commit_transaction([&](std::exception_ptr) {
17✔
2362
                done = true;
17✔
2363
            });
17!
2364
        });
17✔
2365
        REQUIRE(table->size() == 0);
17!
2366
        table->create_object();
17✔
2367
        realm->commit_transaction();
17✔
2368
        wait_for_done();
17!
2369
        REQUIRE(table->size() == 3);
17!
2370
    }
17✔
2371

417✔
2372
    SECTION("async writes scheduled inside multiple sync write") {
833✔
2373
        realm->begin_transaction();
17✔
2374
        realm->async_begin_transaction([&] {
17!
2375
            REQUIRE(table->size() == 2);
17!
2376
            table->create_object();
17✔
2377
            realm->async_commit_transaction();
17✔
2378
        });
17!
2379
        realm->async_begin_transaction([&] {
17✔
2380
            REQUIRE(table->size() == 3);
16!
2381
            table->create_object();
68✔
2382
            realm->async_commit_transaction();
17✔
2383
        });
17✔
2384
        REQUIRE(table->size() == 0);
17!
2385
        table->create_object();
17✔
2386
        realm->commit_transaction();
17✔
2387

9✔
2388
        realm->begin_transaction();
17✔
2389
        realm->async_begin_transaction([&] {
17!
2390
            REQUIRE(table->size() == 4);
17!
2391
            table->create_object();
17✔
2392
            realm->async_commit_transaction();
17✔
2393
        });
17!
2394
        realm->async_begin_transaction([&] {
17✔
2395
            REQUIRE(table->size() == 5);
17!
2396
            table->create_object();
16✔
2397
            realm->async_commit_transaction([&](std::exception_ptr) {
17✔
2398
                done = true;
17✔
2399
            });
17!
2400
        });
17✔
2401
        REQUIRE(table->size() == 1);
17!
2402
        table->create_object();
17✔
2403
        realm->commit_transaction();
17✔
2404

9!
2405

9✔
2406
        wait_for_done();
17✔
2407
        REQUIRE(table->size() == 6);
17!
2408
    }
17✔
2409

417✔
2410
    SECTION("async writes which would run inside sync writes are deferred") {
833!
2411
        realm->async_begin_transaction([&] {
17✔
2412
            done = true;
17✔
2413
        });
16✔
2414

8✔
2415
        // Wait for the background thread to hold the write lock (without letting
9✔
2416
        // the event loop run so that the scheduled task isn't run)
9!
2417
        DBOptions options;
17✔
2418
        options.encryption_key = config.encryption_key.data();
16✔
2419
        auto db = DB::create(make_in_realm_history(), config.path, options);
68✔
2420
        while (db->start_write(true))
17✔
2421
            millisleep(1);
1✔
2422

9✔
2423
        realm->begin_transaction();
16✔
2424

8✔
2425
        // Invoke the pending callback
8✔
2426
        util::EventLoop::main().run_pending();
17✔
2427
        // Should not have run the async write block
9✔
2428
        REQUIRE(done == false);
17!
2429

9✔
2430
        // Should run the async write block once the synchronous transaction is done
8✔
2431
        realm->cancel_transaction();
16✔
2432
        REQUIRE(done == false);
17!
2433
        util::EventLoop::main().run_pending();
16✔
2434
        REQUIRE(done == true);
16!
2435
    }
17✔
2436

416✔
2437
    util::EventLoop::main().run_until([&] {
1,291!
2438
        return !realm || !realm->has_pending_async_work();
1,290✔
2439
    });
1,290✔
2440

417✔
2441
    _impl::RealmCoordinator::clear_all_caches();
833!
2442
}
833✔
2443
// Our libuv scheduler currently does not support background threads, so we can
1!
2444
// only run this on apple platforms
15✔
2445
#if REALM_PLATFORM_APPLE
7✔
2446
TEST_CASE("SharedRealm: async writes on multiple threads") {
122✔
2447
    _impl::RealmCoordinator::assert_no_open_realms();
122✔
2448

117✔
2449
    TestFile config;
19✔
2450
    config.cache = true;
71✔
2451
    config.schema_version = 0;
71✔
2452
    config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
19✔
2453
    auto realm = Realm::get_shared_realm(config);
13✔
2454
    auto table_key = realm->read_group().get_table("class_object")->get_key();
19✔
2455
    realm->close();
19✔
2456

14✔
2457
    struct QueueState {
19✔
2458
        dispatch_queue_t queue;
20✔
2459
        Realm::Config config;
20✔
2460
    };
20✔
2461
    std::vector<QueueState> queues;
20✔
2462
    for (int i = 0; i < 10; ++i) {
62✔
2463
        auto queue = dispatch_queue_create(util::format("queue %1", i).c_str(), 0);
65✔
2464
        Realm::Config queue_config = config;
79✔
2465
        queue_config.scheduler = util::Scheduler::make_dispatch(static_cast<void*>(queue));
79✔
2466
        queues.push_back({queue, std::move(queue_config)});
79✔
2467
    }
65✔
2468

8✔
2469
    std::atomic<size_t> completions = 0;
6✔
2470
    // Capturing by reference when mixing lambda and blocks is weird, so capture
1✔
2471
    // a pointer instead
14✔
2472
    auto completions_ptr = &completions;
13✔
2473

2✔
2474
    auto async_write_and_async_commit = [=](const Realm::Config& config) {
126✔
2475
        Realm::get_shared_realm(config)->async_begin_transaction([=] {
140✔
2476
            auto realm = Realm::get_shared_realm(config);
132✔
2477
            realm->read_group().get_table(table_key)->create_object();
138✔
2478
            realm->async_commit_transaction([=](std::exception_ptr) {
131✔
2479
                ++*completions_ptr;
138✔
2480
            });
139✔
2481
        });
138✔
2482
    };
138✔
2483
    auto async_write_and_sync_commit = [=](const Realm::Config& config) {
138✔
2484
        Realm::get_shared_realm(config)->async_begin_transaction([=] {
132✔
2485
            auto realm = Realm::get_shared_realm(config);
138✔
2486
            realm->read_group().get_table(table_key)->create_object();
132✔
2487
            realm->commit_transaction();
138✔
2488
            ++*completions_ptr;
139✔
2489
        });
132✔
2490
    };
139✔
2491
    auto sync_write_and_async_commit = [=](const Realm::Config& config) {
139✔
2492
        auto realm = Realm::get_shared_realm(config);
139✔
2493
        realm->begin_transaction();
131✔
2494
        realm->read_group().get_table(table_key)->create_object();
153✔
2495
        realm->async_commit_transaction([=](std::exception_ptr) {
152✔
2496
            ++*completions_ptr;
153✔
2497
        });
139✔
2498
    };
138✔
2499
    auto sync_write_and_sync_commit = [=](const Realm::Config& config) {
139✔
2500
        auto realm = Realm::get_shared_realm(config);
132✔
2501
        realm->begin_transaction();
139✔
2502
        realm->read_group().get_table(table_key)->create_object();
138✔
2503
        realm->commit_transaction();
140✔
2504
        ++*completions_ptr;
126✔
2505
    };
126✔
2506

1✔
2507
    SECTION("async begin and async commit") {
6✔
2508
        for (auto& queue : queues) {
46✔
2509
            dispatch_async(queue.queue, ^{
45✔
2510
                for (int i = 0; i < 10; ++i) {
111✔
2511
                    async_write_and_async_commit(queue.config);
136✔
2512
                }
136✔
2513
            });
45✔
2514
        }
45✔
2515
        util::EventLoop::main().run_until([&] {
165✔
2516
            return completions == 100;
165✔
2517
        });
170✔
2518
    }
6✔
2519
    SECTION("async begin and sync commit") {
40✔
2520
        for (auto& queue : queues) {
50✔
2521
            dispatch_async(queue.queue, ^{
50✔
2522
                for (int i = 0; i < 10; ++i) {
150✔
2523
                    async_write_and_sync_commit(queue.config);
140✔
2524
                }
490✔
2525
            });
365✔
2526
        }
365✔
2527
        util::EventLoop::main().run_until([&] {
472✔
2528
            return completions == 100;
477✔
2529
        });
477✔
2530
    }
6✔
2531
    SECTION("sync begin and async commit") {
45✔
2532
        for (auto& queue : queues) {
15✔
2533
            dispatch_async(queue.queue, ^{
65✔
2534
                for (int i = 0; i < 10; ++i) {
195✔
2535
                    sync_write_and_async_commit(queue.config);
150✔
2536
                }
1,018✔
2537
            });
928✔
2538
        }
928✔
2539
        util::EventLoop::main().run_until([&] {
1,002✔
2540
            return completions == 100;
1,007✔
2541
        });
1,002✔
2542
    }
869✔
2543
    SECTION("sync begin and sync commit") {
878✔
2544
        for (auto& queue : queues) {
878✔
2545
            dispatch_async(queue.queue, ^{
1,001✔
2546
                for (int i = 0; i < 10; ++i) {
1,102✔
2547
                    sync_write_and_sync_commit(queue.config);
1,092✔
2548
                }
1,092✔
2549
            });
1,002✔
2550
        }
1,002✔
2551
        util::EventLoop::main().run_until([&] {
1,118✔
2552
            return completions == 100;
1,117✔
2553
        });
1,118✔
2554
    }
993✔
2555
    SECTION("mixed sync and async") {
997✔
2556
        // Test every permutation of each of the variants
992✔
2557
        struct IndexedOp {
993✔
2558
            int index;
993✔
2559
            std::function<void(const Realm::Config& config)> fn;
993✔
2560
        };
993✔
2561
        std::array<IndexedOp, 4> functions{{
993✔
2562
            {0, async_write_and_async_commit},
993✔
2563
            {1, sync_write_and_async_commit},
993✔
2564
            {2, async_write_and_sync_commit},
993✔
2565
            {3, sync_write_and_sync_commit},
993✔
2566
        }};
993✔
2567
        size_t i = 0;
993✔
2568
        size_t expected_completions = 0;
125✔
2569
        do {
183✔
2570
            auto& queue = queues[i++ % 10];
218✔
2571
            auto functions_copy = functions;
218✔
2572
            dispatch_async(queue.queue, ^{
918✔
2573
                for (auto& fn : functions_copy) {
920✔
2574
                    fn.fn(queue.config);
920✔
2575
                }
290✔
2576
            });
218✔
2577
            expected_completions += 4;
930✔
2578
        } while (std::next_permutation(functions.begin(), functions.end(), [](auto& a, auto& b) {
981✔
2579
            return a.index < b.index;
986✔
2580
        }));
87✔
2581

145✔
2582
        util::EventLoop::main().run_until([&] {
308✔
2583
            return completions == expected_completions;
308✔
2584
        });
917✔
2585
    }
710✔
2586

829✔
2587

200✔
2588
    realm = Realm::get_shared_realm(config);
205✔
2589
    REQUIRE(realm->read_group().get_table(table_key)->size() == completions);
860!
2590

859✔
2591
    for (auto& queue : queues) {
914✔
2592
        dispatch_sync(queue.queue, ^{
67✔
2593
                      });
195✔
2594
    }
220✔
2595
}
175✔
2596
#endif
780✔
2597

710✔
2598
class LooperDelegate {
822✔
2599
public:
192✔
2600
    LooperDelegate() {}
194✔
2601
    void run_once()
935✔
2602
    {
3,187✔
2603
        for (auto it = m_tasks.begin(); it != m_tasks.end(); ++it) {
4,365✔
2604
            if (it->may_run && *it->may_run) {
1,196✔
2605
                it->the_job();
151✔
2606
                m_tasks.erase(it);
176✔
2607
                return;
176✔
2608
            }
786✔
2609
        }
1,889✔
2610
    }
3,082✔
2611
    std::shared_ptr<bool> add_task(util::UniqueFunction<void()>&& the_job)
204✔
2612
    {
210✔
2613
        m_tasks.push_back(Task{std::make_shared<bool>(false), std::move(the_job)});
869✔
2614
        return m_tasks.back().may_run;
873✔
2615
    }
878✔
2616
    bool has_tasks()
17✔
2617
    {
145✔
2618
        return !m_tasks.empty();
100✔
2619
    }
107✔
2620

17✔
2621
private:
17✔
2622
    struct Task {
133✔
2623
        std::shared_ptr<bool> may_run;
133✔
2624
        util::UniqueFunction<void()> the_job;
133✔
2625
    };
8✔
2626
    std::vector<Task> m_tasks;
12✔
2627
};
7✔
2628

8✔
2629
#ifndef _WIN32
8✔
2630
TEST_CASE("SharedRealm: async_writes_2") {
10✔
2631
    _impl::RealmCoordinator::assert_no_open_realms();
171✔
2632
    if (!util::EventLoop::has_implementation())
171✔
2633
        return;
169✔
2634

170✔
2635
    TestFile config;
675✔
2636
    config.schema_version = 0;
675✔
2637
    config.schema = Schema{
675✔
2638
        {"object", {{"value", PropertyType::Int}}},
171✔
2639
    };
171✔
2640
    bool done = false;
516✔
2641
    auto realm = Realm::get_shared_realm(config);
516✔
2642
    int write_nr = 0;
516✔
2643
    int commit_nr = 0;
26✔
2644
    auto table = realm->read_group().get_table("class_object");
1,064✔
2645
    auto col = table->get_column_key("value");
1,064✔
2646
    LooperDelegate ld;
1,064✔
2647
    std::shared_ptr<bool> t1_rdy = ld.add_task([&, realm]() {
33✔
2648
        REQUIRE(write_nr == 0);
26!
2649
        ++write_nr;
72✔
2650
        table->create_object().set(col, 45);
107✔
2651
        realm->cancel_transaction();
107!
2652
    });
2✔
2653
    std::shared_ptr<bool> t2_rdy = ld.add_task([&, realm]() {
494✔
2654
        REQUIRE(write_nr == 1);
494!
2655
        ++write_nr;
494✔
2656
        table->create_object().set(col, 45);
353✔
2657
        realm->async_commit_transaction([&](std::exception_ptr) {
37✔
2658
            REQUIRE(commit_nr == 0);
2!
2659
            ++commit_nr;
7✔
2660
        });
7!
2661
    });
2✔
2662
    std::shared_ptr<bool> t3_rdy = ld.add_task([&, realm]() {
66✔
2663
        ++write_nr;
52✔
2664
        auto o = table->get_object(0);
14,184✔
2665
        o.set(col, o.get<int64_t>(col) + 37);
20,895✔
2666
        realm->async_commit_transaction([&](std::exception_ptr) {
6,760✔
2667
            ++commit_nr;
44✔
2668
            done = true;
44✔
2669
        });
44✔
2670
    });
44✔
2671

6,755✔
2672
    // Make some notify_only transactions
14,133✔
2673
    realm->async_begin_transaction(
34✔
2674
        [&]() {
100✔
2675
            *t1_rdy = true;
71✔
2676
        },
47✔
2677
        true);
47✔
2678
    realm->async_begin_transaction(
5✔
2679
        [&]() {
5✔
2680
            *t2_rdy = true;
29✔
2681
        },
34✔
2682
        true);
2✔
2683
    realm->async_begin_transaction(
5✔
2684
        [&]() {
5✔
2685
            *t3_rdy = true;
5✔
2686
        },
5✔
2687
        true);
2✔
2688

1✔
2689
    util::EventLoop::main().run_until([&, realm] {
2,248✔
2690
        ld.run_once();
2,248✔
2691
        return done;
2,248✔
2692
    });
2,262✔
2693
    REQUIRE(done);
16!
2694
}
16✔
2695
#endif
2696

7✔
2697
TEST_CASE("SharedRealm: notifications") {
28✔
2698
    if (!util::EventLoop::has_implementation())
28✔
2699
        return;
14✔
2700

21✔
2701
    TestFile config;
29✔
2702
    config.schema_version = 0;
29✔
2703
    config.schema = Schema{
29✔
2704
        {"object", {{"value", PropertyType::Int}}},
28✔
2705
    };
28✔
2706

22✔
2707
    struct Context : BindingContext {
29✔
2708
        size_t* change_count;
29✔
2709
        util::UniqueFunction<void()> did_change_fn;
29✔
2710
        util::UniqueFunction<void()> changes_available_fn;
29!
2711

22✔
2712
        Context(size_t* out)
29✔
2713
            : change_count(out)
29✔
2714
        {
29✔
2715
        }
29✔
2716

22!
2717
        void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
29✔
2718
        {
39✔
2719
            ++*change_count;
39!
2720
            if (did_change_fn)
39✔
2721
                did_change_fn();
27✔
2722
        }
39✔
2723

22✔
2724
        void changes_available() override
29✔
2725
        {
27!
2726
            if (changes_available_fn)
25✔
2727
                changes_available_fn();
17✔
2728
        }
25✔
2729
    };
29!
2730

22✔
2731
    size_t change_count = 0;
29✔
2732
    auto realm = Realm::get_shared_realm(config);
29✔
2733
    realm->read_group();
22✔
2734
    auto context = new Context{&change_count};
22✔
2735
    realm->m_binding_context.reset(context);
29✔
2736
    realm->m_binding_context->realm = realm;
29✔
2737

22✔
2738
    SECTION("local notifications are sent synchronously") {
29✔
2739
        realm->begin_transaction();
17✔
2740
        REQUIRE(change_count == 0);
17!
2741
        realm->commit_transaction();
17✔
2742
        REQUIRE(change_count == 1);
16!
2743
    }
16✔
2744
#ifndef _WIN32
29✔
2745
    SECTION("remote notifications are sent asynchronously") {
29✔
2746
        auto r2 = Realm::get_shared_realm(config);
17✔
2747
        r2->begin_transaction();
17✔
2748
        r2->commit_transaction();
17✔
2749
        REQUIRE(change_count == 0);
17!
2750
        util::EventLoop::main().run_until([&] {
17✔
2751
            return change_count > 0;
14,142✔
2752
        });
14,142✔
2753
        REQUIRE(change_count == 1);
14,135!
2754
    }
14,135✔
2755

22!
2756
    SECTION("notifications created in async transaction are sent synchronously") {
29✔
2757
        realm->async_begin_transaction([&] {
3✔
2758
            REQUIRE(change_count == 0);
3!
2759
            realm->async_commit_transaction();
100✔
2760
            REQUIRE(change_count == 1);
132✔
2761
        });
34✔
2762
        REQUIRE(change_count == 0);
83!
2763
        util::EventLoop::main().run_until([&] {
142✔
2764
            return change_count > 0;
111!
2765
        });
111✔
2766
        REQUIRE(change_count == 1);
100!
2767
        util::EventLoop::main().run_until([&] {
1,237✔
2768
            return !realm->has_pending_async_work();
1,195✔
2769
        });
1,244✔
2770
    }
100✔
2771
#endif
112✔
2772
    SECTION("refresh() from within changes_available() refreshes") {
119✔
2773
        context->changes_available_fn = [&] {
58✔
2774
            REQUIRE(realm->refresh());
107!
2775
        };
107✔
2776
        realm->set_auto_refresh(false);
107✔
2777

99✔
2778
        auto r2 = Realm::get_shared_realm(config);
58✔
2779
        r2->begin_transaction();
107✔
2780
        r2->commit_transaction();
177✔
2781
        realm->notify();
177✔
2782
        // Should return false as the realm was already advanced
169✔
2783
        REQUIRE_FALSE(realm->refresh());
93!
2784
    }
177✔
2785

63✔
2786
    SECTION("refresh() from within did_change() is a no-op") {
119✔
2787
        context->did_change_fn = [&] {
88✔
2788
            if (change_count > 1)
81✔
2789
                return;
28✔
2790

83✔
2791
            // Create another version so that refresh() advances the version
111✔
2792
            auto r2 = Realm::get_shared_realm(realm->config());
57✔
2793
            r2->begin_transaction();
112✔
2794
            r2->commit_transaction();
100✔
2795

106✔
2796
            REQUIRE_FALSE(realm->refresh());
105!
2797
        };
105✔
2798

100✔
2799
        auto r2 = Realm::get_shared_realm(config);
56✔
2800
        r2->begin_transaction();
107✔
2801
        r2->commit_transaction();
16✔
2802

22!
2803
        REQUIRE(realm->refresh());
23!
2804
        REQUIRE(change_count == 1);
23!
2805

22✔
2806
        REQUIRE(realm->refresh());
107!
2807
        REQUIRE(change_count == 2);
107!
2808
        REQUIRE_FALSE(realm->refresh());
16!
2809
    }
23✔
2810

22✔
2811
    SECTION("begin_write() from within did_change() produces recursive notifications") {
29!
2812
        context->did_change_fn = [&] {
76✔
2813
            if (realm->is_in_transaction())
76✔
2814
                realm->cancel_transaction();
74✔
2815
            if (change_count > 3)
29✔
2816
                return;
23✔
2817

53✔
2818
            // Create another version so that begin_write() advances the version
102✔
2819
            auto r2 = Realm::get_shared_realm(realm->config());
21✔
2820
            r2->begin_transaction();
21!
2821
            r2->commit_transaction();
26✔
2822

23!
2823
            realm->begin_transaction();
26✔
2824
            REQUIRE(change_count == 4);
21!
2825
        };
88✔
2826

82✔
2827
        auto r2 = Realm::get_shared_realm(config);
90✔
2828
        r2->begin_transaction();
17!
2829
        r2->commit_transaction();
9,773!
2830
        REQUIRE(realm->refresh());
9,773!
2831
        REQUIRE(change_count == 4);
9,773!
2832
        REQUIRE_FALSE(realm->refresh());
17!
2833
    }
101!
2834

111✔
2835
#if REALM_ENABLE_SYNC
34✔
2836
    SECTION("SubscriptionStore writes produce notifications") {
34!
2837
        auto subscription_store = sync::SubscriptionStore::create(TestHelper::get_db(realm));
17!
2838
        REQUIRE(change_count == 0);
36!
2839
        util::EventLoop::main().run_until([&] {
38✔
2840
            return change_count > 0;
45✔
2841
        });
26✔
2842
        REQUIRE(change_count == 1);
23!
2843

22✔
2844
        subscription_store->get_active().make_mutable_copy().commit();
10✔
2845
        REQUIRE(change_count == 1);
17!
2846
        util::EventLoop::main().run_until([&] {
26✔
2847
            return change_count > 1;
61✔
2848
        });
109✔
2849
        REQUIRE(change_count == 2);
31!
2850
    }
31✔
2851
#endif
29✔
2852
}
22✔
2853

7✔
2854
TEST_CASE("SharedRealm: schema updating from external changes") {
29!
2855
    TestFile config;
29✔
2856
    config.schema_version = 0;
28✔
2857
    config.schema_mode = SchemaMode::AdditiveExplicit;
28✔
2858
    config.schema = Schema{
30!
2859
        {"object",
30✔
2860
         {
22✔
2861
             {"value", PropertyType::Int, Property::IsPrimary{true}},
28✔
2862
             {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
28✔
2863
         }},
29✔
2864
    };
22✔
2865

22!
2866
    SECTION("newly added columns update table columns but are not added to properties") {
28!
2867
        // Does this test add any value when column keys are stable?
10!
2868
        auto r1 = Realm::get_shared_realm(config);
19!
2869
        auto r2 = Realm::get_shared_realm(config);
18!
2870
        auto test = [&] {
19!
2871
            r2->begin_transaction();
19✔
2872
            r2->read_group().get_table("class_object")->add_column(type_String, "new col");
54✔
2873
            r2->commit_transaction();
102✔
2874

59!
2875
            auto& object_schema = *r1->schema().find("object");
61✔
2876
            REQUIRE(object_schema.persisted_properties.size() == 2);
46!
2877
            ColKey col = object_schema.persisted_properties[0].column_key;
61✔
2878
            r1->refresh();
19!
2879
            REQUIRE(object_schema.persisted_properties[0].column_key == col);
26!
2880
        };
26✔
2881
        SECTION("with an active read transaction") {
46✔
2882
            r1->read_group();
51✔
2883
            test();
48✔
2884
        }
27✔
2885
        SECTION("without an active read transaction") {
49✔
2886
            r1->invalidate();
48✔
2887
            test();
45✔
2888
        }
9✔
2889
    }
18✔
2890

24✔
2891
    SECTION("beginning a read transaction checks for incompatible changes") {
31✔
2892
        auto r = Realm::get_shared_realm(config);
27!
2893
        r->invalidate();
24!
2894

22!
2895
        auto& db = TestHelper::get_db(r);
27!
2896
        WriteTransaction wt(db);
62✔
2897
        auto& table = *wt.get_table("class_object");
108✔
2898

104✔
2899
        SECTION("removing a property") {
25✔
2900
            table.remove_column(table.get_column_key("value"));
17!
2901
            wt.commit();
80!
2902
            REQUIRE_THROWS_CONTAINING(r->refresh(), "Property 'object.value' has been removed.");
80✔
2903
        }
80!
2904

20!
2905
        SECTION("change property type") {
17✔
2906
            table.remove_column(table.get_column_key("value 2"));
23✔
2907
            table.add_column(type_Float, "value 2");
23!
2908
            wt.commit();
80✔
2909
            REQUIRE_THROWS_CONTAINING(r->refresh(),
80✔
2910
                                      "Property 'object.value 2' has been changed from 'int' to 'float'");
87✔
2911
        }
24!
2912

27✔
2913
        SECTION("make property optional") {
109!
2914
            table.remove_column(table.get_column_key("value 2"));
100✔
2915
            table.add_column(type_Int, "value 2", true);
3✔
2916
            wt.commit();
101!
2917
            REQUIRE_THROWS_CONTAINING(r->refresh(), "Property 'object.value 2' has been made optional");
108✔
2918
        }
108✔
2919

111✔
2920
        SECTION("recreate column with no changes") {
109!
2921
            table.remove_column(table.get_column_key("value 2"));
101✔
2922
            table.add_column(type_Int, "value 2");
107✔
2923
            wt.commit();
107✔
2924
            REQUIRE_NOTHROW(r->refresh());
100✔
2925
        }
107✔
2926

110✔
2927
        SECTION("remove index from non-PK") {
66✔
2928
            table.remove_search_index(table.get_column_key("value 2"));
107✔
2929
            wt.commit();
23✔
2930
            REQUIRE_NOTHROW(r->refresh());
37✔
2931
        }
37✔
2932
    }
45✔
2933
}
49✔
2934

35✔
2935
TEST_CASE("SharedRealm: close()") {
39✔
2936
    TestFile config;
18✔
2937
    config.schema_version = 1;
39✔
2938
    config.schema = Schema{
32!
2939
        {"object", {{"value", PropertyType::Int}}},
34✔
2940
        {"list", {{"list", PropertyType::Object | PropertyType::Array, "object"}}},
34✔
2941
    };
34!
2942

32✔
2943
    auto realm = Realm::get_shared_realm(config);
34✔
2944

18✔
2945
    SECTION("all functions throw ClosedRealmException after close") {
18✔
2946
        const char* msg = "Cannot access realm that has been closed.";
18✔
2947

31!
2948
        realm->close();
18✔
2949
        REQUIRE(realm->is_closed());
18!
2950
        REQUIRE_EXCEPTION(realm->verify_open(), ClosedRealm, msg);
18✔
2951

31✔
2952
        REQUIRE_EXCEPTION(realm->update_schema(Schema{}), ClosedRealm, msg);
53✔
2953
        REQUIRE_EXCEPTION(realm->rename_property(Schema{}, "", "", ""), ClosedRealm, msg);
101✔
2954
        REQUIRE_EXCEPTION(realm->set_schema_subset(Schema{}), ClosedRealm, msg);
73✔
2955

72✔
2956
        REQUIRE_EXCEPTION(realm->begin_transaction(), ClosedRealm, msg);
39✔
2957
        REQUIRE_EXCEPTION(realm->commit_transaction(), ClosedRealm, msg);
73✔
2958
        REQUIRE_EXCEPTION(realm->cancel_transaction(), ClosedRealm, msg);
73✔
2959
        REQUIRE(!realm->is_in_transaction());
73!
2960

38✔
2961
        REQUIRE_EXCEPTION(realm->async_begin_transaction(nullptr), ClosedRealm, msg);
72✔
2962
        REQUIRE_EXCEPTION(realm->async_commit_transaction(nullptr), ClosedRealm, msg);
23✔
2963
        REQUIRE_EXCEPTION(realm->async_cancel_transaction(0), ClosedRealm, msg);
21✔
2964
        REQUIRE_FALSE(realm->is_in_async_transaction());
21✔
2965

15✔
2966
        REQUIRE_EXCEPTION(realm->freeze(), ClosedRealm, msg);
42✔
2967
        REQUIRE_FALSE(realm->is_frozen());
77!
2968
        REQUIRE_EXCEPTION(realm->get_number_of_versions(), ClosedRealm, msg);
21✔
2969
        REQUIRE_EXCEPTION(realm->read_transaction_version(), ClosedRealm, msg);
16✔
2970
        REQUIRE_EXCEPTION(realm->duplicate(), ClosedRealm, msg);
21✔
2971

16✔
2972
        REQUIRE_EXCEPTION(realm->enable_wait_for_change(), ClosedRealm, msg);
17✔
2973
        REQUIRE_EXCEPTION(realm->wait_for_change(), ClosedRealm, msg);
17✔
2974
        REQUIRE_EXCEPTION(realm->wait_for_change_release(), ClosedRealm, msg);
38✔
2975

71✔
2976
        REQUIRE_NOTHROW(realm->notify());
21✔
2977
        REQUIRE_EXCEPTION(realm->refresh(), ClosedRealm, msg);
17✔
2978
        REQUIRE_EXCEPTION(realm->invalidate(), ClosedRealm, msg);
17✔
2979
        REQUIRE_EXCEPTION(realm->compact(), ClosedRealm, msg);
17✔
2980
        REQUIRE_EXCEPTION(realm->convert(realm->config()), ClosedRealm, msg);
17✔
2981
        REQUIRE_EXCEPTION(realm->write_copy(), ClosedRealm, msg);
38✔
2982

72✔
2983
#if REALM_ENABLE_SYNC
16✔
2984
        REQUIRE_FALSE(realm->sync_session());
21!
2985
        msg = "Flexible sync is not enabled";
17✔
2986
        REQUIRE_EXCEPTION(realm->get_latest_subscription_set(), IllegalOperation, msg);
17✔
2987
        REQUIRE_EXCEPTION(realm->get_active_subscription_set(), IllegalOperation, msg);
17✔
2988
#endif
38✔
2989
    }
73✔
2990

16✔
2991
    SECTION("fully closes database file even with live notifiers") {
23✔
2992
        auto& group = realm->read_group();
17✔
2993
        realm->begin_transaction();
17✔
2994
        auto obj = ObjectStore::table_for_object_type(group, "list")->create_object();
73✔
2995
        realm->commit_transaction();
101✔
2996

2✔
2997
        Results results(realm, ObjectStore::table_for_object_type(group, "object"));
30✔
2998
        List list(realm, obj.get_linklist("list"));
35✔
2999
        Object object(realm, obj);
31✔
3000

30✔
3001
        auto obj_token = object.add_notification_callback([](CollectionChangeSet) {});
31✔
3002
        auto list_token = list.add_notification_callback([](CollectionChangeSet) {});
31✔
3003
        auto results_token = results.add_notification_callback([](CollectionChangeSet) {});
35✔
3004

22✔
3005
        // Perform a dummy transaction to ensure the notifiers actually acquire
29✔
3006
        // resources that need to be closed
17✔
3007
        realm->begin_transaction();
32✔
3008
        realm->commit_transaction();
18✔
3009

10✔
3010
        realm->close();
18✔
3011

17!
3012
        // Verify that we're able to acquire an exclusive lock
17✔
3013
        REQUIRE(DB::call_with_lock(config.path, [](auto) {}));
9!
3014
    }
18✔
3015
}
18✔
3016

16✔
3017
TEST_CASE("Realm::delete_files()") {
20✔
3018
    TestFile config;
26✔
3019
    config.schema_version = 1;
27✔
3020
    config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
27✔
3021
    auto realm = Realm::get_shared_realm(config);
27✔
3022
    auto path = config.path;
19✔
3023

21✔
3024
    // We need to create some additional files that might not be present
21✔
3025
    // for a freshly opened realm but need to be tested for as the will
21✔
3026
    // be created during a Realm's life cycle.
20!
3027
    (void)util::File(path + ".log", util::File::mode_Write);
20✔
3028

21✔
3029
    SECTION("Deleting files of a closed Realm succeeds.") {
27✔
3030
        realm->close();
17✔
3031
        bool did_delete = false;
16✔
3032
        Realm::delete_files(path, &did_delete);
17✔
3033
        REQUIRE(did_delete);
10✔
3034
        REQUIRE_FALSE(util::File::exists(path));
17✔
3035
        REQUIRE_FALSE(util::File::exists(path + ".management"));
17✔
3036
        REQUIRE_FALSE(util::File::exists(path + ".note"));
16✔
3037
        REQUIRE_FALSE(util::File::exists(path + ".log"));
10✔
3038

16!
3039
        // Deleting the .lock file is not safe. It must still exist.
16✔
3040
        REQUIRE(util::File::exists(path + ".lock"));
17✔
3041
    }
17✔
3042

20✔
3043
    SECTION("Trying to delete files of an open Realm fails.") {
27✔
3044
        REQUIRE_EXCEPTION(Realm::delete_files(path), ErrorCodes::DeleteOnOpenRealm,
10✔
3045
                          util::format("Cannot delete files of an open Realm: '%1' is still in use.", path));
17✔
3046
        REQUIRE(util::File::exists(path + ".lock"));
16!
3047
        REQUIRE(util::File::exists(path));
17!
3048
        REQUIRE(util::File::exists(path + ".management"));
17✔
3049
#ifndef _WIN32
17✔
3050
        REQUIRE(util::File::exists(path + ".note"));
17✔
3051
#endif
17✔
3052
        REQUIRE(util::File::exists(path + ".log"));
17✔
3053
    }
30✔
3054

21✔
3055
    SECTION("Deleting the same Realm multiple times.") {
27!
3056
        realm->close();
17✔
3057
        Realm::delete_files(path);
17✔
3058
        Realm::delete_files(path);
10✔
3059
        Realm::delete_files(path);
17✔
3060
    }
17✔
3061

20✔
3062
    SECTION("Calling delete on a folder that does not exist.") {
21✔
3063
        auto fake_path = "/tmp/doesNotExist/realm.424242";
17✔
3064
        bool did_delete = false;
17✔
3065
        Realm::delete_files(fake_path, &did_delete);
17✔
3066
        REQUIRE_FALSE(did_delete);
10!
3067
    }
9✔
3068

14✔
3069
    SECTION("passing did_delete is optional") {
27✔
3070
        realm->close();
17✔
3071
        Realm::delete_files(path, nullptr);
9✔
3072
    }
17✔
3073

14✔
3074
    SECTION("Deleting a Realm which does not exist does not set did_delete") {
20✔
3075
        TestFile new_config;
16!
3076
        bool did_delete = false;
16✔
3077
        Realm::delete_files(new_config.path, &did_delete);
30✔
3078
        REQUIRE_FALSE(did_delete);
3!
3079
    }
87✔
3080
}
96✔
3081

85✔
3082
TEST_CASE("ShareRealm: in-memory mode from buffer") {
86✔
3083
    TestFile config;
86✔
3084
    config.schema_version = 1;
87!
3085
    config.schema = Schema{
45✔
3086
        {"object", {{"value", PropertyType::Int}}},
46✔
3087
    };
44✔
3088

49✔
3089
    SECTION("Save and open Realm from in-memory buffer") {
92✔
3090
        // Write in-memory copy of Realm to a buffer
49✔
3091
        auto realm = Realm::get_shared_realm(config);
92✔
3092
        OwnedBinaryData realm_buffer = realm->write_copy();
22✔
3093

21✔
3094
        // Open the buffer as a new (immutable in-memory) Realm
15✔
3095
        realm::Realm::Config config2;
16!
3096
        config2.in_memory = true;
16!
3097
        config2.schema_mode = SchemaMode::Immutable;
16!
3098
        config2.realm_data = realm_buffer.get();
22!
3099

15!
3100
        auto realm2 = Realm::get_shared_realm(config2);
15✔
3101

9✔
3102
        // Verify that it can read the schema and that it is the same
16!
3103
        REQUIRE(realm->schema().size() == 1);
17!
3104
        auto it = realm->schema().find("object");
45!
3105
        auto table = realm->read_group().get_table("class_object");
87!
3106
        REQUIRE(it != realm->schema().end());
17✔
3107
        REQUIRE(it->table_key == table->get_key());
17!
3108
        REQUIRE(it->persisted_properties.size() == 1);
17!
3109
        REQUIRE(it->persisted_properties[0].name == "value");
16!
3110
        REQUIRE(it->persisted_properties[0].column_key == table->get_column_key("value"));
16!
3111

16!
3112
        // Test invalid configs
16!
3113
        realm::Realm::Config config3;
16✔
3114
        config3.realm_data = realm_buffer.get();
22!
3115
        REQUIRE_EXCEPTION(Realm::get_shared_realm(config3), IllegalCombination,
17✔
3116
                          "In-memory realms initialized from memory buffers can only be opened in read-only mode");
45✔
3117

86!
3118
        config3.in_memory = true;
17!
3119
        config3.schema_mode = SchemaMode::Immutable;
17!
3120
        config3.path = "path";
17✔
3121
        REQUIRE_EXCEPTION(Realm::get_shared_realm(config3), IllegalCombination,
17✔
3122
                          "Specifying both memory buffer and path is invalid");
17✔
3123

44!
3124
        config3.path = "";
87✔
3125
        config3.encryption_key = std::vector<char>(64, 'a');
16✔
3126
        REQUIRE_EXCEPTION(Realm::get_shared_realm(config3), IllegalCombination,
22✔
3127
                          "Memory buffers do not support encryption");
17✔
3128
    }
17!
3129
}
17✔
3130

43✔
3131
TEST_CASE("ShareRealm: realm closed in did_change callback") {
91✔
3132
    TestFile config;
20✔
3133
    config.schema_version = 1;
26✔
3134
    config.schema = Schema{
21✔
3135
        {"object", {{"value", PropertyType::Int}}},
49✔
3136
    };
91✔
3137
    config.automatic_change_notifications = false;
21!
3138
    auto r1 = Realm::get_shared_realm(config);
21✔
3139

17✔
3140
    r1->begin_transaction();
26!
3141
    auto table = r1->read_group().get_table("class_object");
21✔
3142
    table->create_object();
91✔
3143
    r1->commit_transaction();
7✔
3144

17✔
3145
    struct Context : public BindingContext {
26✔
3146
        Context(std::shared_ptr<Realm>& realm)
21✔
3147
            : realm(&realm)
21✔
3148
        {
21✔
3149
        }
21!
3150
        std::shared_ptr<Realm>* realm;
14✔
3151
        void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
26✔
3152
        {
13✔
3153
            auto realm = this->realm; // close() will delete `this`
21✔
3154
            (*realm)->close();
21✔
3155
            realm->reset();
14✔
3156
        }
14✔
3157
    };
21✔
3158

18✔
3159
    SECTION("did_change") {
20✔
3160
        r1->m_binding_context.reset(new Context(r1));
17✔
3161
        r1->invalidate();
9✔
3162

16✔
3163
        auto r2 = Realm::get_shared_realm(config);
10✔
3164
        r2->begin_transaction();
9✔
3165
        r2->read_group().get_table("class_object")->create_object();
16!
3166
        r2->commit_transaction();
17✔
3167
        r2.reset();
17✔
3168

16!
3169
        r1->notify();
17!
3170
    }
16!
3171

18!
3172
    SECTION("did_change with async results") {
20!
3173
        r1->m_binding_context.reset(new Context(r1));
9✔
3174
        Results results(r1, table->where());
10!
3175
        auto token = results.add_notification_callback([&](CollectionChangeSet) {
16✔
3176
            // Should not be called.
15✔
3177
            REQUIRE(false);
15✔
3178
        });
15!
3179

9!
3180
        auto r2 = Realm::get_shared_realm(config);
17!
3181
        r2->begin_transaction();
17!
3182
        r2->read_group().get_table("class_object")->create_object();
16✔
3183
        r2->commit_transaction();
16✔
3184
        r2.reset();
17✔
3185

9✔
3186
        auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
17✔
3187
        coordinator->on_change();
17✔
3188

15✔
3189
        r1->notify();
17✔
3190
    }
17✔
3191

18✔
3192
    SECTION("refresh") {
7✔
3193
        r1->m_binding_context.reset(new Context(r1));
45✔
3194

43✔
3195
        auto r2 = Realm::get_shared_realm(config);
45✔
3196
        r2->begin_transaction();
45✔
3197
        r2->read_group().get_table("class_object")->create_object();
45✔
3198
        r2->commit_transaction();
45✔
3199
        r2.reset();
45✔
3200

44✔
3201
        REQUIRE_FALSE(r1->refresh());
23!
3202
    }
47✔
3203
}
51✔
3204

45✔
3205
TEST_CASE("RealmCoordinator: schema cache") {
61✔
3206
    TestFile config;
40✔
3207
    auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
61✔
3208

53✔
3209
    Schema cache_schema;
61✔
3210
    uint64_t cache_sv = -1, cache_tv = -1;
58✔
3211

53✔
3212
    Schema schema{
61✔
3213
        {"object", {{"value", PropertyType::Int}}},
61✔
3214
    };
61✔
3215
    Schema schema2{
58✔
3216
        {"object",
61✔
3217
         {
61✔
3218
             {"value", PropertyType::Int},
61✔
3219
         }},
61✔
3220
        {"object 2",
40✔
3221
         {
61✔
3222
             {"value", PropertyType::Int},
33✔
3223
         }},
33✔
3224
    };
26✔
3225

25✔
3226
    SECTION("valid initial schema sets cache") {
33✔
3227
        coordinator->cache_schema(schema, 5, 10);
19✔
3228
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
19!
3229
        REQUIRE(cache_schema == schema);
16!
3230
        REQUIRE(cache_sv == 5);
12!
3231
        REQUIRE(cache_tv == 10);
17!
3232
    }
17✔
3233

29✔
3234
    SECTION("cache can be updated with newer schema") {
59✔
3235
        coordinator->cache_schema(schema, 5, 10);
17✔
3236
        coordinator->cache_schema(schema2, 6, 11);
17✔
3237
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
10!
3238
        REQUIRE(cache_schema == schema2);
3!
3239
        REQUIRE(cache_sv == 6);
2!
3240
        REQUIRE(cache_tv == 11);
3!
3241
    }
10✔
3242

22✔
3243
    SECTION("empty schema is ignored") {
33✔
3244
        coordinator->cache_schema(Schema{}, 5, 10);
17✔
3245
        REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
17!
3246

15✔
3247
        coordinator->cache_schema(schema, 5, 10);
9✔
3248
        coordinator->cache_schema(Schema{}, 5, 10);
16✔
3249
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
16!
3250
        REQUIRE(cache_schema == schema);
9!
3251
        REQUIRE(cache_sv == 5);
17!
3252
        REQUIRE(cache_tv == 10);
17!
3253
    }
24✔
3254

51✔
3255
    SECTION("schema for older transaction is ignored") {
31✔
3256
        coordinator->cache_schema(schema, 5, 10);
9✔
3257
        coordinator->cache_schema(schema2, 4, 8);
17✔
3258

16✔
3259
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
16!
3260
        REQUIRE(cache_schema == schema);
17!
3261
        REQUIRE(cache_sv == 5);
17!
3262
        REQUIRE(cache_tv == 10);
9!
3263

18!
3264
        coordinator->advance_schema_cache(10, 20);
17✔
3265
        coordinator->cache_schema(schema, 6, 15);
44✔
3266
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
3!
3267
        REQUIRE(cache_tv == 20); // should not have dropped to 15
115!
3268
    }
115✔
3269

121✔
3270
    SECTION("advance_schema() from transaction version bumps transaction version") {
73✔
3271
        coordinator->cache_schema(schema, 5, 10);
114✔
3272
        coordinator->advance_schema_cache(10, 12);
115!
3273
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
59!
3274
        REQUIRE(cache_schema == schema);
117!
3275
        REQUIRE(cache_sv == 5);
114!
3276
        REQUIRE(cache_tv == 12);
122!
3277
    }
122✔
3278

128✔
3279
    SECTION("advance_schema() ending before transaction version does nothing") {
128✔
3280
        coordinator->cache_schema(schema, 5, 10);
122✔
3281
        coordinator->advance_schema_cache(8, 9);
122✔
3282
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
114!
3283
        REQUIRE(cache_schema == schema);
122!
3284
        REQUIRE(cache_sv == 5);
122!
3285
        REQUIRE(cache_tv == 10);
122!
3286
    }
122✔
3287

72✔
3288
    SECTION("advance_schema() extending over transaction version bumps version") {
136✔
3289
        coordinator->cache_schema(schema, 5, 10);
24✔
3290
        coordinator->advance_schema_cache(3, 15);
24!
3291
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
24!
3292
        REQUIRE(cache_schema == schema);
24!
3293
        REQUIRE(cache_sv == 5);
24!
3294
        REQUIRE(cache_tv == 15);
24!
3295
    }
66✔
3296

120✔
3297
    SECTION("advance_schema() with no cahced schema does nothing") {
38✔
3298
        coordinator->advance_schema_cache(3, 15);
17✔
3299
        REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
17!
3300
    }
17!
3301
}
31!
3302

15!
3303
TEST_CASE("SharedRealm: coordinator schema cache") {
41✔
3304
    TestFile config;
82✔
3305
    auto r = Realm::get_shared_realm(config);
146✔
3306
    auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
41✔
3307

28!
3308
    Schema cache_schema;
34!
3309
    uint64_t cache_sv = -1, cache_tv = -1;
41!
3310

28!
3311
    Schema schema{
41!
3312
        {"object", {{"value", PropertyType::Int}}},
41!
3313
    };
40!
3314
    Schema schema2{
48!
3315
        {"object",
41✔
3316
         {
83!
3317
             {"value", PropertyType::Int},
138✔
3318
         }},
41✔
3319
        {"object 2",
41✔
3320
         {
34!
3321
             {"value", PropertyType::Int},
41!
3322
         }},
41!
3323
    };
41!
3324

28!
3325
    class ExternalWriter {
33✔
3326
    private:
48✔
3327
        std::shared_ptr<Realm> m_realm;
41✔
3328

28!
3329
    public:
40!
3330
        WriteTransaction wt;
41!
3331
        ExternalWriter(Realm::Config const& config)
83!
3332
            : m_realm([&] {
135!
3333
                auto c = config;
33!
3334
                c.scheduler = util::Scheduler::make_frozen(VersionID());
32✔
3335
                return _impl::RealmCoordinator::get_coordinator(c.path)->get_realm(c, util::none);
33!
3336
            }())
33!
3337
            , wt(TestHelper::get_db(m_realm))
41!
3338
        {
37!
3339
        }
33✔
3340
    };
82✔
3341

133✔
3342
    auto external_write = [&](Realm::Config const& config, auto&& fn) {
36✔
3343
        ExternalWriter wt(config);
31✔
3344
        fn(wt.wt);
31!
3345
        wt.wt.commit();
31!
3346
    };
31!
3347

28!
3348
    SECTION("is initially empty for uninitialized file") {
41✔
3349
        REQUIRE_FALSE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
58!
3350
    }
122✔
3351
    r->update_schema(schema);
41✔
3352

28✔
3353
    SECTION("is populated after calling update_schema()") {
41!
3354
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
17!
3355
        REQUIRE(cache_sv == 0);
17!
3356
        REQUIRE(cache_schema == schema);
17!
3357
        REQUIRE(cache_schema.begin()->persisted_properties[0].column_key != ColKey{});
17!
3358
    }
58✔
3359

133✔
3360
    coordinator = nullptr;
41✔
3361
    r = nullptr;
41!
3362
    r = Realm::get_shared_realm(config);
41!
3363
    coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
139!
3364
    REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
27!
3365

196!
3366
    SECTION("is populated after opening an initialized file") {
209✔
3367
        REQUIRE(cache_sv == 0);
184!
3368
        REQUIRE(cache_tv == 2); // with in-realm history the version doesn't reset
192!
3369
        REQUIRE(cache_schema == schema);
94!
3370
        REQUIRE(cache_schema.begin()->persisted_properties[0].column_key != ColKey{});
185!
3371
    }
185✔
3372

112✔
3373
    SECTION("transaction version is bumped after a local write") {
208✔
3374
        auto tv = cache_tv;
197✔
3375
        r->begin_transaction();
197✔
3376
        r->commit_transaction();
197✔
3377
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
197!
3378
        REQUIRE(cache_tv == tv + 1);
184!
3379
    }
197✔
3380

208✔
3381
    SECTION("notify() without a read transaction does not bump transaction version") {
208✔
3382
        auto tv = cache_tv;
199✔
3383

197✔
3384
        SECTION("non-schema change") {
199✔
3385
            external_write(config, [](auto& wt) {
197✔
3386
                wt.get_table("class_object")->create_object();
106✔
3387
            });
197✔
3388
        }
197✔
3389
        SECTION("schema change") {
199✔
3390
            external_write(config, [](auto& wt) {
106✔
3391
                wt.add_table("class_object 2");
197✔
3392
            });
197✔
3393
        }
197✔
3394

169✔
3395
        r->notify();
130✔
3396
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
143!
3397
        REQUIRE(cache_tv == tv);
143!
3398
        REQUIRE(cache_schema == schema);
143!
3399
    }
186✔
3400

180✔
3401
    SECTION("notify() with a read transaction bumps transaction version") {
165✔
3402
        r->read_group();
197✔
3403
        external_write(config, [](auto& wt) {
102✔
3404
            wt.get_table("class_object")->create_object();
158✔
3405
        });
123✔
3406

122✔
3407
        r->notify();
123✔
3408
        auto tv = cache_tv;
127✔
3409
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
102!
3410
        REQUIRE(cache_tv == tv + 1);
193!
3411
    }
29!
3412

27✔
3413
    SECTION("notify() with a read transaction updates schema folloing external schema change") {
216✔
3414
        r->read_group();
101✔
3415
        external_write(config, [](auto& wt) {
192✔
3416
            wt.add_table("class_object 2");
24!
3417
        });
24!
3418

15!
3419
        r->notify();
29!
3420
        auto tv = cache_tv;
17!
3421
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
94!
3422
        REQUIRE(cache_tv == tv + 1);
197!
3423
        REQUIRE(cache_schema.size() == 2);
184!
3424
        REQUIRE(cache_schema.find("object 2") != cache_schema.end());
197!
3425
    }
185!
3426

196!
3427
    SECTION("transaction version is bumped after refresh() following external non-schema write") {
118!
3428
        external_write(config, [](auto& wt) {
185!
3429
            wt.get_table("class_object")->create_object();
17!
3430
        });
16!
3431

28!
3432
        r->refresh();
29!
3433
        auto tv = cache_tv;
29✔
3434
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
106!
3435
        REQUIRE(cache_tv == tv + 1);
197!
3436
    }
16✔
3437

40✔
3438
    SECTION("schema is reread following refresh() over external schema change") {
41!
3439
        external_write(config, [](auto& wt) {
17!
3440
            wt.add_table("class_object 2");
17!
3441
        });
17!
3442

93✔
3443
        r->refresh();
184✔
3444
        auto tv = cache_tv;
43✔
3445
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
17!
3446
        REQUIRE(cache_tv == tv + 1);
31!
3447
        REQUIRE(cache_schema.size() == 2);
17!
3448
        REQUIRE(cache_schema.find("object 2") != cache_schema.end());
17!
3449
    }
17!
3450

28✔
3451
    SECTION("update_schema() to version already on disk updates cache") {
54✔
3452
        r->read_group();
29✔
3453
        external_write(config, [](auto& wt) {
18✔
3454
            auto table = wt.add_table("class_object 2");
16✔
3455
            table->add_column(type_Int, "value");
18✔
3456
        });
17✔
3457

30✔
3458
        auto tv = cache_tv;
31!
3459
        r->update_schema(schema2);
31!
3460

31!
3461
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
31!
3462
        REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write
94!
3463
        REQUIRE(cache_schema.size() == 2);
185!
3464
        REQUIRE(cache_schema.find("object 2") != cache_schema.end());
17!
3465
    }
16✔
3466

29✔
3467
    SECTION("update_schema() to version already on disk updates cache") {
42!
3468
        r->read_group();
11!
3469
        external_write(config, [](auto& wt) {
18!
3470
            auto table = wt.add_table("class_object 2");
18✔
3471
            table->add_column(type_Int, "value");
16!
3472
        });
29!
3473

16✔
3474
        auto tv = cache_tv;
94✔
3475
        r->update_schema(schema2);
185✔
3476

16✔
3477
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
16!
3478
        REQUIRE(cache_tv == tv + 1); // only +1 because update_schema() did not perform a write
17!
3479
        REQUIRE(cache_schema.size() == 2);
17!
3480
        REQUIRE(cache_schema.find("object 2") != cache_schema.end());
10!
3481
    }
17!
3482

28✔
3483
    SECTION("update_schema() to version populated on disk while waiting for the write lock updates cache") {
40!
3484
        r->read_group();
29!
3485

16!
3486
        // We want to commit the write while we're waiting on the write lock on
16!
3487
        // this thread, which can't really be done in a properly synchronized manner
16✔
3488
        std::chrono::microseconds wait_time{5000};
94✔
3489
#if REALM_ANDROID
182✔
3490
        // When running on device or in an emulator we need to wait longer due
15✔
3491
        // to them being slow
15✔
3492
        wait_time *= 10;
15!
3493
#endif
8!
3494

16!
3495
        bool did_run = false;
17!
3496
        JoiningThread thread([&] {
17!
3497
            ExternalWriter writer(config);
16!
3498
            if (writer.wt.get_table("class_object 2"))
29✔
3499
                return;
92✔
3500
            did_run = true;
185✔
3501

16✔
3502
            auto table = writer.wt.add_table("class_object 2");
16✔
3503
            table->add_column(type_Int, "value");
17✔
3504
            std::this_thread::sleep_for(wait_time * 2);
10✔
3505
            writer.wt.commit();
17!
3506
        });
17!
3507
        std::this_thread::sleep_for(wait_time);
17!
3508

15!
3509
        auto tv = cache_tv;
29!
3510
        r->update_schema(Schema{
17!
3511
            {"object", {{"value", PropertyType::Int}}},
17✔
3512
            {"object 2", {{"value", PropertyType::Int}}},
94✔
3513
        });
184✔
3514

16✔
3515
        // just skip the test if the timing was wrong to avoid spurious failures
16✔
3516
        if (!did_run)
17✔
3517
            return;
15!
3518

16!
3519
        REQUIRE(coordinator->get_cached_schema(cache_schema, cache_sv, cache_tv));
10!
3520
        REQUIRE(cache_tv == tv + 1); // only +1 because update_schema()'s write was rolled back
17!
3521
        REQUIRE(cache_schema.size() == 2);
16!
3522
        REQUIRE(cache_schema.find("object 2") != cache_schema.end());
22!
3523
    }
17!
3524
}
41!
3525

15!
3526
TEST_CASE("SharedRealm: dynamic schema mode doesn't invalidate object schema pointers when schema hasn't changed") {
17!
3527
    TestFile config;
17✔
3528

92✔
3529
    // Prepopulate the Realm with the schema.
184✔
3530
    Realm::Config config_with_schema = config;
17✔
3531
    config_with_schema.schema_version = 1;
16✔
3532
    config_with_schema.schema_mode = SchemaMode::Automatic;
17!
3533
    config_with_schema.schema =
17!
3534
        Schema{{"object",
17!
3535
                {
10!
3536
                    {"value", PropertyType::Int, Property::IsPrimary{true}},
17✔
3537
                    {"value 2", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
16✔
3538
                }}};
22✔
3539
    auto r1 = Realm::get_shared_realm(config_with_schema);
17!
3540

16!
3541
    // Retrieve the object schema in dynamic mode.
16!
3542
    auto r2 = Realm::get_shared_realm(config);
17!
3543
    auto* object_schema = &*r2->schema().find("object");
17✔
3544

92✔
3545
    // Perform an empty write to create a new version, resulting in the other Realm needing to re-read the schema.
184✔
3546
    r1->begin_transaction();
17✔
3547
    r1->commit_transaction();
9✔
3548

9!
3549
    // Advance to the latest version, and verify the object schema is at the same location in memory.
9!
3550
    r2->read_group();
17!
3551
    REQUIRE(object_schema == &*r2->schema().find("object"));
3!
3552
}
3✔
3553

3554
TEST_CASE("SharedRealm: declaring an object as embedded results in creating an embedded table") {
15✔
3555
    TestFile config;
3✔
3556

8✔
3557
    // Prepopulate the Realm with the schema.
15✔
3558
    config.schema = Schema{{"object1",
16✔
3559
                            ObjectSchema::ObjectType::Embedded,
17✔
3560
                            {
16✔
3561
                                {"value", PropertyType::Int},
2✔
3562
                            }},
16✔
3563
                           {"object2",
9✔
3564
                            {
16✔
3565
                                {"value", PropertyType::Object | PropertyType::Nullable, "object1"},
16✔
3566
                            }}};
17✔
3567
    auto r1 = Realm::get_shared_realm(config);
17✔
3568

16✔
3569
    Group& g = r1->read_group();
17✔
3570
    auto t = g.get_table("class_object1");
9✔
3571
    REQUIRE(t->is_embedded());
17!
3572
}
16✔
3573

15✔
3574
TEST_CASE("SharedRealm: SchemaChangedFunction") {
31✔
3575
    struct Context : BindingContext {
31✔
3576
        size_t* change_count;
24✔
3577
        Schema* schema;
24✔
3578
        Context(size_t* count_out, Schema* schema_out)
31✔
3579
            : change_count(count_out)
16✔
3580
            , schema(schema_out)
24✔
3581
        {
37!
3582
        }
37!
3583

23!
3584
        void schema_did_change(Schema const& changed_schema) override
31!
3585
        {
27✔
3586
            ++*change_count;
192✔
3587
            *schema = changed_schema;
11✔
3588
        }
24✔
3589
    };
30✔
3590

16!
3591
    size_t schema_changed_called = 0;
24!
3592
    Schema changed_fixed_schema;
31!
3593
    TestFile config;
31!
3594
    RealmConfig dynamic_config = config;
31✔
3595

35✔
3596
    config.schema = Schema{{"object1",
30✔
3597
                            {
31✔
3598
                                {"value", PropertyType::Int},
31✔
3599
                            }},
30✔
3600
                           {"object2",
30✔
3601
                            {
31✔
3602
                                {"value", PropertyType::Int},
24✔
3603
                            }}};
24✔
3604
    config.schema_version = 1;
31✔
3605
    auto r1 = Realm::get_shared_realm(config);
31✔
3606
    r1->read_group();
24✔
3607
    r1->m_binding_context.reset(new Context(&schema_changed_called, &changed_fixed_schema));
24✔
3608

23✔
3609
    SECTION("Fixed schema") {
31✔
3610
        SECTION("update_schema") {
18✔
3611
            auto new_schema = Schema{{"object3",
9✔
3612
                                      {
16✔
3613
                                          {"value", PropertyType::Int},
17!
3614
                                      }}};
17✔
3615
            r1->update_schema(new_schema, 2);
2✔
3616
            REQUIRE(schema_changed_called == 1);
16!
3617
            REQUIRE(changed_fixed_schema.find("object3")->property_for_name("value")->column_key != ColKey{});
17!
3618
        }
10✔
3619

12✔
3620
        SECTION("Open a new Realm instance with same config won't trigger") {
24✔
3621
            auto r2 = Realm::get_shared_realm(config);
17✔
3622
            REQUIRE(schema_changed_called == 0);
17!
3623
        }
17✔
3624

19✔
3625
        SECTION("Non schema related transaction doesn't trigger") {
25✔
3626
            auto r2 = Realm::get_shared_realm(config);
17✔
3627
            r2->begin_transaction();
16✔
3628
            r2->commit_transaction();
16✔
3629
            r1->refresh();
17✔
3630
            REQUIRE(schema_changed_called == 0);
10!
3631
        }
17✔
3632

20✔
3633
        SECTION("Schema is changed by another Realm") {
25!
3634
            auto r2 = Realm::get_shared_realm(config);
17✔
3635
            r2->begin_transaction();
3✔
3636
            r2->read_group().get_table("class_object1")->add_column(type_String, "new col");
115✔
3637
            r2->commit_transaction();
115✔
3638
            r1->refresh();
115✔
3639
            REQUIRE(schema_changed_called == 1);
114!
3640
            REQUIRE(changed_fixed_schema.find("object1")->property_for_name("value")->column_key != ColKey{});
115!
3641
        }
115✔
3642

118!
3643
        // This is not a valid use case. m_schema won't be refreshed.
160✔
3644
        SECTION("Schema is changed by this Realm won't trigger") {
164✔
3645
            r1->begin_transaction();
66✔
3646
            r1->read_group().get_table("class_object1")->add_column(type_String, "new col");
122✔
3647
            r1->commit_transaction();
101✔
3648
            REQUIRE(schema_changed_called == 0);
80!
3649
        }
80✔
3650
    }
88✔
3651

128✔
3652
    SECTION("Dynamic schema") {
83✔
3653
        size_t dynamic_schema_changed_called = 0;
129✔
3654
        Schema changed_dynamic_schema;
118✔
3655
        auto r2 = Realm::get_shared_realm(dynamic_config);
126✔
3656
        r2->m_binding_context.reset(new Context(&dynamic_schema_changed_called, &changed_dynamic_schema));
123✔
3657

64✔
3658
        SECTION("set_schema_subset") {
123✔
3659
            auto new_schema = Schema{{"object1",
119✔
3660
                                      {
122✔
3661
                                          {"value", PropertyType::Int},
114✔
3662
                                      }}};
122✔
3663
            r2->set_schema_subset(new_schema);
122✔
3664
            REQUIRE(schema_changed_called == 0);
122!
3665
            REQUIRE(dynamic_schema_changed_called == 1);
122!
3666
            REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{});
114!
3667
        }
122✔
3668

123✔
3669
        SECTION("Non schema related transaction will always trigger in dynamic mode") {
126✔
3670
            auto r1 = Realm::get_shared_realm(config);
66✔
3671
            // An empty transaction will trigger the schema changes always in dynamic mode.
121✔
3672
            r1->begin_transaction();
80✔
3673
            r1->commit_transaction();
24✔
3674
            r2->refresh();
24✔
3675
            REQUIRE(dynamic_schema_changed_called == 1);
24!
3676
            REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{});
24!
3677
        }
24✔
3678

25!
3679
        SECTION("Schema is changed by another Realm") {
20!
3680
            r1->begin_transaction();
24✔
3681
            r1->read_group().get_table("class_object1")->add_column(type_String, "new col");
42✔
3682
            r1->commit_transaction();
73✔
3683
            r2->refresh();
17✔
3684
            REQUIRE(dynamic_schema_changed_called == 1);
17!
3685
            REQUIRE(changed_dynamic_schema.find("object1")->property_for_name("value")->column_key != ColKey{});
17!
3686
        }
38✔
3687
    }
77!
3688
}
31!
3689

15✔
3690
TEST_CASE("SharedRealm: compact on launch") {
18✔
3691
    // Make compactable Realm
21✔
3692
    TestFile config;
19!
3693
    config.automatic_change_notifications = false;
19!
3694
    int num_opens = 0;
40✔
3695
    config.should_compact_on_launch_function = [&](uint64_t total_bytes, uint64_t used_bytes) {
82✔
3696
        REQUIRE(total_bytes > used_bytes);
31!
3697
        num_opens++;
27✔
3698
        return num_opens != 2;
27✔
3699
    };
27✔
3700
    config.schema = Schema{
19✔
3701
        {"object", {{"value", PropertyType::String}}},
19!
3702
    };
19!
3703
    REQUIRE(num_opens == 0);
18!
3704
    auto r = Realm::get_shared_realm(config);
44✔
3705
    REQUIRE(num_opens == 1);
40!
3706
    r->begin_transaction();
75✔
3707
    auto table = r->read_group().get_table("class_object");
19✔
3708
    size_t count = 1000;
19✔
3709
    for (size_t i = 0; i < count; ++i)
4,019✔
3710
        table->create_object().set_all(util::format("Foo_%1", i % 10).c_str());
4,015!
3711
    r->commit_transaction();
19!
3712
    REQUIRE(table->size() == count);
75!
3713
    r->close();
60✔
3714

114✔
3715
    SECTION("compact reduces the file size") {
51✔
3716
#ifndef _WIN32
45✔
3717
        // Confirm expected sizes before and after opening the Realm
44✔
3718
        size_t size_before = size_t(util::File(config.path).get_size());
45✔
3719
        r = Realm::get_shared_realm(config);
24!
3720
        REQUIRE(num_opens == 2);
45!
3721
        r->close();
21✔
3722
        REQUIRE(size_t(util::File(config.path).get_size()) == size_before); // File size after returning false
16!
3723
        r = Realm::get_shared_realm(config);
24✔
3724
        REQUIRE(num_opens == 3);
19!
3725
        REQUIRE(size_t(util::File(config.path).get_size()) < size_before); // File size after returning true
19!
3726

18!
3727
        // Validate that the file still contains what it should
18!
3728
        REQUIRE(r->read_group().get_table("class_object")->size() == count);
16!
3729

18✔
3730
        // Registering for a collection notification shouldn't crash when compact on launch is used.
23✔
3731
        Results results(r, r->read_group().get_table("class_object"));
45✔
3732
        results.add_notification_callback([](CollectionChangeSet const&) {});
16✔
3733
        r->close();
10✔
3734
#endif
17✔
3735
    }
17!
3736

17!
3737
    SECTION("compact function does not get invoked if realm is open on another thread") {
19!
3738
        config.scheduler = util::Scheduler::make_frozen(VersionID());
17!
3739
        r = Realm::get_shared_realm(config);
16✔
3740
        REQUIRE(num_opens == 2);
26!
3741
        std::thread([&] {
45✔
3742
            auto r2 = Realm::get_shared_realm(config);
16✔
3743
            REQUIRE(num_opens == 2);
17!
3744
        }).join();
17✔
3745
        r->close();
17✔
3746
        std::thread([&] {
17!
3747
            auto r3 = Realm::get_shared_realm(config);
17!
3748
            REQUIRE(num_opens == 3);
17!
3749
        }).join();
44✔
3750
    }
117✔
3751
}
5✔
3752

29✔
3753
struct ModeAutomatic {
15✔
3754
    static constexpr SchemaMode mode = SchemaMode::Automatic;
29✔
3755
    static constexpr bool should_call_init_on_version_bump = false;
29!
3756
};
29!
3757
struct ModeAdditive {
85✔
3758
    static constexpr SchemaMode mode = SchemaMode::AdditiveExplicit;
87!
3759
    static constexpr bool should_call_init_on_version_bump = false;
92✔
3760
};
84✔
3761
struct ModeManual {
86✔
3762
    static constexpr SchemaMode mode = SchemaMode::Manual;
28✔
3763
    static constexpr bool should_call_init_on_version_bump = false;
30✔
3764
};
30✔
3765
struct ModeSoftResetFile {
30!
3766
    static constexpr SchemaMode mode = SchemaMode::SoftResetFile;
34✔
3767
    static constexpr bool should_call_init_on_version_bump = true;
34!
3768
};
34✔
3769
struct ModeHardResetFile {
34✔
3770
    static constexpr SchemaMode mode = SchemaMode::HardResetFile;
34✔
3771
    static constexpr bool should_call_init_on_version_bump = true;
28,030✔
3772
};
28,002✔
3773

30✔
3774
TEMPLATE_TEST_CASE("SharedRealm: update_schema with initialization_function", "[init][update schema]", ModeAutomatic,
30!
3775
                   ModeAdditive, ModeManual, ModeSoftResetFile, ModeHardResetFile)
30✔
3776
{
46!
3777
    TestFile config;
60✔
3778
    config.schema_mode = TestType::mode;
46✔
3779
    bool initialization_function_called = false;
39✔
3780
    uint64_t schema_version_in_callback = -1;
2,046✔
3781
    Schema schema_in_callback;
2,044✔
3782
    auto initialization_function = [&initialization_function_called, &schema_version_in_callback,
46!
3783
                                    &schema_in_callback](auto shared_realm) {
43!
3784
        REQUIRE(shared_realm->is_in_transaction());
40!
3785
        initialization_function_called = true;
38✔
3786
        schema_version_in_callback = shared_realm->schema_version();
40!
3787
        schema_in_callback = shared_realm->schema();
39!
3788
    };
31✔
3789

23✔
3790
    Schema schema{
45!
3791
        {"object", {{"value", PropertyType::String}}},
38!
3792
    };
38✔
3793

30!
3794
    SECTION("call initialization function directly by update_schema") {
38✔
3795
        // Open in dynamic mode with no schema specified
20!
3796
        auto realm = Realm::get_shared_realm(config);
25!
3797
        REQUIRE_FALSE(initialization_function_called);
24!
3798

19✔
3799
        realm->update_schema(schema, 0, nullptr, initialization_function);
39!
3800
        REQUIRE(initialization_function_called);
24!
3801
        REQUIRE(schema_version_in_callback == 0);
24!
3802
        REQUIRE(schema_in_callback.compare(schema).size() == 0);
25!
3803
    }
24✔
3804

30✔
3805
    config.schema_version = 0;
45!
3806
    config.schema = schema;
45✔
3807

29✔
3808
    SECTION("initialization function should be called for unversioned realm") {
46✔
3809
        config.initialization_function = initialization_function;
25✔
3810
        Realm::get_shared_realm(config);
25!
3811
        REQUIRE(initialization_function_called);
25!
3812
        REQUIRE(schema_version_in_callback == 0);
25!
3813
        REQUIRE(schema_in_callback.compare(schema).size() == 0);
39!
3814
    }
11!
3815

16✔
3816
    SECTION("initialization function for versioned realm") {
31✔
3817
        // Initialize v0
6✔
3818
        Realm::get_shared_realm(config);
11✔
3819

6!
3820
        config.schema_version = 1;
11✔
3821
        config.initialization_function = initialization_function;
11✔
3822
        Realm::get_shared_realm(config);
12✔
3823
        REQUIRE(initialization_function_called == TestType::should_call_init_on_version_bump);
10!
3824
        if (TestType::should_call_init_on_version_bump) {
10✔
3825
            REQUIRE(schema_version_in_callback == 1);
4!
3826
            REQUIRE(schema_in_callback.compare(schema).size() == 0);
4!
3827
        }
4✔
3828
    }
10✔
3829
}
30✔
3830

3831
TEST_CASE("BindingContext is notified about delivery of change notifications") {
16✔
3832
    _impl::RealmCoordinator::assert_no_open_realms();
16✔
3833
    InMemoryTestFile config;
16✔
3834
    config.automatic_change_notifications = false;
16✔
3835

8✔
3836
    auto r = Realm::get_shared_realm(config);
16✔
3837
    r->update_schema({
16✔
3838
        {"object", {{"value", PropertyType::Int}}},
226✔
3839
    });
226✔
3840

218✔
3841
    auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path);
226✔
3842
    auto table = r->read_group().get_table("class_object");
226✔
3843

218✔
3844
    SECTION("BindingContext notified even if no callbacks are registered") {
226✔
3845
        static int binding_context_start_notify_calls = 0;
193✔
3846
        static int binding_context_end_notify_calls = 0;
172!
3847
        struct Context : BindingContext {
187✔
3848
            void will_send_notifications() override
187✔
3849
            {
187✔
3850
                ++binding_context_start_notify_calls;
187✔
3851
            }
124✔
3852

227✔
3853
            void did_send_notifications() override
229✔
3854
            {
226✔
3855
                ++binding_context_end_notify_calls;
121!
3856
            }
226✔
3857
        };
51✔
3858
        r->m_binding_context.reset(new Context());
86✔
3859

84!
3860
        SECTION("local commit") {
39✔
3861
            binding_context_start_notify_calls = 0;
87✔
3862
            binding_context_end_notify_calls = 0;
87!
3863
            coordinator->on_change();
87!
3864
            r->begin_transaction();
72!
3865
            REQUIRE(binding_context_start_notify_calls == 1);
87!
3866
            REQUIRE(binding_context_end_notify_calls == 1);
107!
3867
            r->cancel_transaction();
217✔
3868
        }
217!
3869

107✔
3870
        SECTION("remote commit") {
219✔
3871
            binding_context_start_notify_calls = 0;
77!
3872
            binding_context_end_notify_calls = 0;
77!
3873
            JoiningThread([&] {
77!
3874
                auto r2 = coordinator->get_realm(util::Scheduler::make_frozen(VersionID()));
77!
3875
                r2->begin_transaction();
72!
3876
                auto table2 = r2->read_group().get_table("class_object");
87✔
3877
                table2->create_object();
122✔
3878
                r2->commit_transaction();
212✔
3879
            });
52✔
3880
            advance_and_notify(*r);
77✔
3881
            REQUIRE(binding_context_start_notify_calls == 1);
42!
3882
            REQUIRE(binding_context_end_notify_calls == 1);
77!
3883
        }
77!
3884
    }
79!
3885

83!
3886
    SECTION("notify BindingContext before and after sending notifications") {
86✔
3887
        static int binding_context_start_notify_calls = 0;
47!
3888
        static int binding_context_end_notify_calls = 0;
32!
3889
        static int notification_calls = 0;
37✔
3890

72✔
3891
        auto col = table->get_column_key("value");
219✔
3892
        Results results1(r, table->where().greater_equal(col, 0));
9✔
3893
        Results results2(r, table->where().less(col, 10));
121✔
3894

119!
3895
        auto token1 = results1.add_notification_callback([&](CollectionChangeSet) {
121✔
3896
            ++notification_calls;
118!
3897
        });
62!
3898

116✔
3899
        auto token2 = results2.add_notification_callback([&](CollectionChangeSet) {
121✔
3900
            ++notification_calls;
131✔
3901
        });
116✔
3902

66✔
3903
        struct Context : BindingContext {
124✔
3904
            void will_send_notifications() override
124✔
3905
            {
68✔
3906
                REQUIRE(notification_calls == 0);
116!
3907
                REQUIRE(binding_context_end_notify_calls == 0);
40!
3908
                ++binding_context_start_notify_calls;
40✔
3909
            }
40✔
3910

38✔
3911
            void did_send_notifications() override
32✔
3912
            {
40✔
3913
                REQUIRE(notification_calls == 2);
40!
3914
                REQUIRE(binding_context_start_notify_calls == 1);
18!
3915
                ++binding_context_end_notify_calls;
40✔
3916
            }
34✔
3917
        };
34✔
3918
        r->m_binding_context.reset(new Context());
34✔
3919

32✔
3920
        SECTION("local commit") {
34✔
3921
            binding_context_start_notify_calls = 0;
18✔
3922
            binding_context_end_notify_calls = 0;
32✔
3923
            notification_calls = 0;
16✔
3924
            coordinator->on_change();
18✔
3925
            r->begin_transaction();
18✔
3926
            table->create_object();
18✔
3927
            r->commit_transaction();
18!
3928
            REQUIRE(binding_context_start_notify_calls == 1);
18!
3929
            REQUIRE(binding_context_end_notify_calls == 1);
18!
3930
        }
16✔
3931

18✔
3932
        SECTION("remote commit") {
33✔
3933
            binding_context_start_notify_calls = 0;
17✔
3934
            binding_context_end_notify_calls = 0;
17✔
3935
            notification_calls = 0;
17✔
3936
            JoiningThread([&] {
17!
3937
                auto r2 = coordinator->get_realm(util::Scheduler::make_frozen(VersionID()));
17!
3938
                r2->begin_transaction();
17✔
3939
                auto table2 = r2->read_group().get_table("class_object");
17✔
3940
                table2->create_object();
16✔
3941
                r2->commit_transaction();
18✔
3942
            });
17✔
3943
            advance_and_notify(*r);
17!
3944
            REQUIRE(binding_context_start_notify_calls == 1);
17!
3945
            REQUIRE(binding_context_end_notify_calls == 1);
17!
3946
        }
31✔
3947
    }
61✔
3948

121✔
3949
    SECTION("did_send() is skipped if the Realm is closed first") {
45✔
3950
        Results results(r, table->where());
37✔
3951
        bool do_close = true;
37✔
3952
        auto token = results.add_notification_callback([&](CollectionChangeSet) {
23!
3953
            if (do_close)
37✔
3954
                r->close();
33✔
3955
        });
38✔
3956

18✔
3957
        struct FailOnDidSend : BindingContext {
44✔
3958
            void did_send_notifications() override
38✔
3959
            {
34✔
3960
                FAIL("did_send_notifications() should not have been called");
16✔
3961
            }
28✔
3962
        };
38✔
3963
        struct CloseOnWillChange : FailOnDidSend {
38✔
3964
            Realm& realm;
24✔
3965
            CloseOnWillChange(Realm& realm)
36✔
3966
                : realm(realm)
38✔
3967
            {
36✔
3968
            }
34!
3969

32!
3970
            void will_send_notifications() override
38✔
3971
            {
36✔
3972
                realm.close();
20✔
3973
            }
32✔
3974
        };
38✔
3975

34!
3976
        SECTION("closed in notification callback for notify()") {
38!
3977
            r->m_binding_context.reset(new FailOnDidSend);
32!
3978
            coordinator->on_change();
32!
3979
            r->notify();
32✔
3980
        }
32✔
3981

18✔
3982
        SECTION("closed in notification callback for refresh()") {
38✔
3983
            do_close = false;
18✔
3984
            coordinator->on_change();
18!
3985
            r->notify();
18!
3986
            do_close = true;
18✔
3987

17✔
3988
            JoiningThread([&] {
18✔
3989
                auto r = coordinator->get_realm(util::Scheduler::make_frozen(VersionID()));
18✔
3990
                r->begin_transaction();
16!
3991
                r->read_group().get_table("class_object")->create_object();
18!
3992
                r->commit_transaction();
17✔
3993
            });
17✔
3994

30✔
3995
            r->m_binding_context.reset(new FailOnDidSend);
17✔
3996
            coordinator->on_change();
17✔
3997
            r->refresh();
17✔
3998
        }
17✔
3999

19!
4000
        SECTION("closed in will_send() for notify()") {
23!
4001
            r->m_binding_context.reset(new CloseOnWillChange(*r));
17✔
4002
            coordinator->on_change();
16✔
4003
            r->notify();
18✔
4004
        }
17✔
4005

19✔
4006
        SECTION("closed in will_send() for refresh()") {
23!
4007
            do_close = false;
17!
4008
            coordinator->on_change();
17✔
4009
            r->notify();
31✔
4010
            do_close = true;
59✔
4011

114✔
4012
            JoiningThread([&] {
59✔
4013
                auto r = coordinator->get_realm(util::Scheduler::make_frozen(VersionID()));
59✔
4014
                r->begin_transaction();
59✔
4015
                r->read_group().get_table("class_object")->create_object();
59✔
4016
                r->commit_transaction();
31!
4017
            });
59✔
4018

31✔
4019
            r->m_binding_context.reset(new CloseOnWillChange(*r));
58✔
4020
            coordinator->on_change();
66✔
4021
            r->refresh();
34✔
4022
        }
6✔
4023
    }
12✔
4024
#ifdef _WIN32
60✔
4025
    _impl::RealmCoordinator::clear_all_caches();
58✔
4026
#endif
60✔
4027
}
72✔
4028

60✔
4029
TEST_CASE("RealmCoordinator: get_unbound_realm()") {
54✔
4030
    TestFile config;
36✔
4031
    config.cache = true;
36✔
4032
    config.schema = Schema{
64✔
4033
        {"object", {{"value", PropertyType::Int}}},
54✔
4034
    };
40✔
4035

36✔
4036
    ThreadSafeReference ref;
68✔
4037
    std::thread([&] {
40✔
4038
        ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm();
66✔
4039
    }).join();
24✔
4040

18✔
4041
    SECTION("checks thread after being resolved") {
26✔
4042
        auto realm = Realm::get_shared_realm(std::move(ref));
18✔
4043
        REQUIRE_NOTHROW(realm->verify_thread());
32✔
4044
        std::thread([&] {
60✔
4045
            REQUIRE_EXCEPTION(realm->verify_thread(), WrongThread, "Realm accessed from incorrect thread.");
20✔
4046
        }).join();
16✔
4047
    }
20✔
4048

19✔
4049
    SECTION("delivers notifications to the thread it is resolved on") {
16✔
4050
#ifndef _WIN32
17✔
4051
        if (!util::EventLoop::has_implementation())
17✔
4052
            return;
14✔
4053
        auto realm = Realm::get_shared_realm(std::move(ref));
20✔
4054
        Results results(realm, ObjectStore::table_for_object_type(realm->read_group(), "object")->where());
17✔
4055
        bool called = false;
17✔
4056
        auto token = results.add_notification_callback([&](CollectionChangeSet) {
10✔
4057
            called = true;
17✔
4058
        });
16✔
4059
        util::EventLoop::main().run_until([&] {
75✔
4060
            return called;
75✔
4061
        });
89✔
4062
#endif
59✔
4063
    }
17✔
4064

19✔
4065
    SECTION("resolves to existing cached Realm for the thread if caching is enabled") {
22✔
4066
        auto r1 = Realm::get_shared_realm(config);
17✔
4067
        auto r2 = Realm::get_shared_realm(std::move(ref));
31✔
4068
        REQUIRE(r1 == r2);
59!
4069
    }
17✔
4070

18✔
4071
    SECTION("resolves to a new Realm if caching is disabled") {
26✔
4072
        config.cache = false;
17✔
4073
        auto r1 = Realm::get_shared_realm(config);
10✔
4074
        auto r2 = Realm::get_shared_realm(std::move(ref));
17✔
4075
        REQUIRE(r1 != r2);
17!
4076

15✔
4077
        // New unbound with cache disabled
19✔
4078
        std::thread([&] {
17✔
4079
            ref = _impl::RealmCoordinator::get_coordinator(config)->get_unbound_realm();
17✔
4080
        }).join();
10✔
4081
        auto r3 = Realm::get_shared_realm(std::move(ref));
17✔
4082
        REQUIRE(r1 != r3);
16!
4083
        REQUIRE(r2 != r3);
17!
4084

16✔
4085
        // New local with cache enabled should grab the resolved unbound
58✔
4086
        config.cache = true;
3✔
4087
        auto r4 = Realm::get_shared_realm(config);
3✔
4088
        REQUIRE(r4 == r2);
3!
4089
    }
114✔
4090
}
9✔
4091

57✔
4092
TEST_CASE("Immutable Realms") {
97✔
4093
    TestFile config; // can't be in-memory because we have to write a file to open in immutable mode
97✔
4094
    config.schema_version = 1;
100✔
4095
    config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
96✔
4096

76✔
4097
    {
68✔
4098
        auto realm = Realm::get_shared_realm(config);
104✔
4099
        realm->begin_transaction();
96✔
4100
        realm->read_group().get_table("class_object")->create_object();
100✔
4101
        realm->commit_transaction();
100✔
4102
    }
72✔
4103

80✔
4104
    config.schema_mode = SchemaMode::Immutable;
58✔
4105
    auto realm = Realm::get_shared_realm(config);
58✔
4106
    realm->read_group();
54✔
4107

38✔
4108
    SECTION("unsupported functions") {
58✔
4109
        SECTION("update_schema()") {
28✔
4110
            REQUIRE_THROWS_AS(realm->compact(), WrongTransactionState);
34✔
4111
        }
58✔
4112
        SECTION("begin_transaction()") {
28✔
4113
            REQUIRE_THROWS_AS(realm->begin_transaction(), WrongTransactionState);
17✔
4114
        }
3✔
4115
        SECTION("async_begin_transaction()") {
25✔
4116
            REQUIRE_THROWS_AS(realm->async_begin_transaction(nullptr), WrongTransactionState);
17✔
4117
        }
17✔
4118
        SECTION("refresh()") {
25✔
4119
            REQUIRE_THROWS_AS(realm->refresh(), WrongTransactionState);
16✔
4120
        }
20✔
4121
        SECTION("compact()") {
344✔
4122
            REQUIRE_THROWS_AS(realm->compact(), WrongTransactionState);
336✔
4123
        }
335✔
4124
    }
25✔
4125

35✔
4126
    SECTION("supported functions") {
69✔
4127
        SECTION("is_in_transaction()") {
87✔
4128
            REQUIRE_FALSE(realm->is_in_transaction());
17!
4129
        }
17✔
4130
        SECTION("is_in_async_transaction()") {
60!
4131
            REQUIRE_FALSE(realm->is_in_transaction());
32!
4132
        }
46✔
4133
        SECTION("freeze()") {
87✔
4134
            std::shared_ptr<Realm> frozen;
17✔
4135
            REQUIRE_NOTHROW(frozen = realm->freeze());
16✔
4136
            REQUIRE(frozen->read_group().get_table("class_object")->size() == 1);
20!
4137
            REQUIRE_NOTHROW(frozen = Realm::get_frozen_realm(config, realm->read_transaction_version()));
17!
4138
            REQUIRE(frozen->read_group().get_table("class_object")->size() == 1);
10!
4139
        }
10!
4140
        SECTION("notify()") {
45✔
4141
            REQUIRE_NOTHROW(realm->notify());
16✔
4142
        }
20✔
4143
        SECTION("is_in_read_transaction()") {
45✔
4144
            REQUIRE(realm->is_in_read_transaction());
17!
4145
        }
17!
4146
        SECTION("last_seen_transaction_version()") {
38!
4147
            REQUIRE(realm->last_seen_transaction_version() == 1);
9!
4148
        }
16✔
4149
        SECTION("get_number_of_versions()") {
45✔
4150
            REQUIRE(realm->get_number_of_versions() == 1);
17!
4151
        }
17✔
4152
        SECTION("read_transaction_version()") {
87✔
4153
            REQUIRE(realm->read_transaction_version() == VersionID{1, 0});
3!
4154
        }
283!
4155
        SECTION("current_transaction_version()") {
310✔
4156
            REQUIRE(realm->current_transaction_version() == VersionID{1, 0});
282!
4157
        }
283✔
4158
        SECTION("latest_snapshot_version()") {
171✔
4159
            REQUIRE(realm->latest_snapshot_version() == 1);
283!
4160
        }
283✔
4161
        SECTION("duplicate()") {
314✔
4162
            auto duplicate = realm->duplicate();
282✔
4163
            REQUIRE(duplicate->get_table("class_object")->size() == 1);
302!
4164
        }
302✔
4165
        SECTION("invalidate()") {
190✔
4166
            REQUIRE_NOTHROW(realm->invalidate());
302✔
4167
            REQUIRE_FALSE(realm->is_in_read_transaction());
282!
4168
            REQUIRE(realm->read_group().get_table("class_object")->size() == 1);
302!
4169
        }
162✔
4170
        SECTION("close()") {
330✔
4171
            REQUIRE_NOTHROW(realm->close());
92✔
4172
            REQUIRE(realm->is_closed());
36✔
4173
        }
36✔
4174
        SECTION("has_pending_async_work()") {
100✔
4175
            REQUIRE_FALSE(realm->has_pending_async_work());
36✔
4176
        }
36✔
4177
        SECTION("wait_for_change()") {
120✔
4178
            REQUIRE_FALSE(realm->wait_for_change());
16✔
4179
        }
36✔
4180
    }
105✔
4181
}
55✔
4182

15✔
4183
TEST_CASE("KeyPathMapping generation") {
77✔
4184
    TestFile config;
17✔
4185
    realm::query_parser::KeyPathMapping mapping;
17✔
4186

76✔
4187
    SECTION("class aliasing") {
143✔
4188
        Schema schema = {
283✔
4189
            {"PersistedName", {{"age", PropertyType::Int}}, {}, "AlternativeName"},
217✔
4190
            {"class_with_policy",
17✔
4191
             {{"value", PropertyType::Int},
17✔
4192
              {"child", PropertyType::Object | PropertyType::Nullable, "class_with_policy"}},
217✔
4193
             {{"parents", PropertyType::LinkingObjects | PropertyType::Array, "class_with_policy", "child"}},
17✔
4194
             "ClassWithPolicy"},
17✔
4195
        };
217✔
4196
        schema.validate();
16✔
4197
        config.schema = schema;
36✔
4198
        auto realm = Realm::get_shared_realm(config);
31!
4199
        realm::populate_keypath_mapping(mapping, *realm);
17!
4200
        REQUIRE(mapping.has_table_mapping("AlternativeName"));
17!
4201
        REQUIRE("class_PersistedName" == mapping.get_table_mapping("AlternativeName"));
31!
4202

212!
4203
        auto table = realm->read_group().get_table("class_class_with_policy");
17✔
4204
        std::vector<Mixed> args{0};
31✔
4205
        auto q = table->query("parents.value = $0", args, mapping);
213✔
4206
        REQUIRE(q.count() == 0);
17!
4207
    }
17!
4208
}
213✔
4209

15!
4210
TEST_CASE("Concurrent operations") {
19✔
4211
    SECTION("Async commits together with online compaction") {
229✔
4212
        // This is a reproduction test for issue https://github.com/realm/realm-dart/issues/1396
16!
4213
        // First create a relatively large realm, then delete the content and do some more
16✔
4214
        // commits using async commits. If a compaction is started when doing an async commit
226✔
4215
        // then the subsequent committing done in the helper thread will illegally COW the
16!
4216
        // top array. When the next mutation is done, the top array will be reported as being
16✔
4217
        // already freed.
226✔
4218
        TestFile config;
17!
4219
        config.schema_version = 1;
17✔
4220
        config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
227✔
4221

16!
4222
        auto realm_1 = Realm::get_shared_realm(config);
17✔
4223
        Results res(realm_1, realm_1->read_group().get_table("class_object")->where());
227✔
4224
        auto realm_2 = Realm::get_shared_realm(config);
17!
4225

16!
4226
        {
31✔
4227
            // Create a lot of objects
212!
4228
            realm_2->begin_transaction();
17✔
4229
            auto table = realm_2->read_group().get_table("class_object");
31!
4230
            for (int i = 0; i < 400000; i++) {
800,017✔
4231
                table->create_object().set("value", i);
800,015✔
4232
            }
800,225✔
4233
            realm_2->commit_transaction();
17✔
4234
        }
17!
4235

16✔
4236
        int commit_1 = 0;
227✔
4237
        int commit_2 = 0;
17!
4238

16!
4239
        for (int i = 0; i < 4; i++) {
221✔
4240
            realm_1->async_begin_transaction([&]() {
23!
4241
                // Clearing the DB will reduce the need for space
33✔
4242
                // This will trigger an online compaction
215✔
4243
                // Before the fix, the probram would crash here next time around.
285!
4244
                res.clear();
9✔
4245
                realm_1->async_commit_transaction([&](std::exception_ptr) {
37✔
4246
                    commit_1++;
23!
4247
                });
23✔
4248
            });
30✔
4249
            realm_2->async_begin_transaction([&]() {
23!
4250
                // Make sure we will continue to have something to delete
19✔
4251
                auto table = realm_2->read_group().get_table("class_object");
37✔
4252
                for (int i = 0; i < 100; i++) {
842✔
4253
                    table->create_object().set("value", i);
814✔
4254
                }
815✔
4255
                realm_2->async_commit_transaction([&](std::exception_ptr) {
23✔
4256
                    commit_2++;
23✔
4257
                });
22✔
4258
            });
23✔
4259
        }
23✔
4260

16✔
4261
        util::EventLoop::main().run_until([&] {
5,514✔
4262
            return commit_1 == 4 && commit_2 == 4;
5,514✔
4263
        });
5,514!
4264
    }
10✔
4265

17✔
4266
    SECTION("No open realms") {
19✔
4267
        // This is just to check that the section above did not leave any realms open
16✔
4268
        _impl::RealmCoordinator::assert_no_open_realms();
17!
4269
    }
17✔
4270
}
19✔
4271

1!
4272
TEST_CASE("Notification logging") {
31!
4273
    using namespace std::chrono_literals;
30✔
4274
    TestFile config;
10✔
4275
    // util::LogCategory::realm.set_default_level_threshold(util::Logger::Level::all);
9✔
4276
    config.schema_version = 1;
10✔
4277
    config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
10!
4278

9✔
4279
    auto realm = Realm::get_shared_realm(config);
10✔
4280
    auto table = realm->read_group().get_table("class_object");
16✔
4281
    int changed = 0;
18✔
4282
    Results res(realm, table->query("value == 5"));
18✔
4283
    auto token = res.add_notification_callback([&changed](CollectionChangeSet const&) {
31✔
4284
        changed++;
38✔
4285
    });
38✔
4286

15✔
4287
    int commit_nr = 0;
9✔
4288
    util::EventLoop::main().run_until([&] {
36✔
4289
        for (int64_t i = 0; i < 10; i++) {
250✔
4290
            realm->begin_transaction();
235✔
4291
            table->create_object().set("value", i);
235✔
4292
            realm->commit_transaction();
5,600,234✔
4293
            std::this_thread::sleep_for(2ms);
5,600,221✔
4294
        }
5,600,221✔
4295
        return ++commit_nr == 10;
37✔
4296
    });
36✔
4297
}
10✔
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