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

realm / realm-core / thomas.goyne_491

09 Aug 2024 04:34PM UTC coverage: 89.577% (-1.5%) from 91.087%
thomas.goyne_491

Pull #7967

Evergreen

tgoyne
Actually check for unuplaoded changes in no_pending_local_changes()

We can have local changesets stored which have already been uploaded and
acknoledged by the server, so checking all of the changesets is incorrect. We
need to instead only check changesets for versions after the current position
of the upload cursor.
Pull Request #7967: RCORE-2232 Actually check for unuploaded changes in no_pending_local_changes()

90956 of 164876 branches covered (55.17%)

37 of 38 new or added lines in 2 files covered. (97.37%)

42 existing lines in 8 files now uncovered.

145956 of 162940 relevant lines covered (89.58%)

8094301.68 hits per line

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

86.47
/test/object-store/util/test_file.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/test_file.hpp"
20

21
#include "util/test_utils.hpp"
22
#include "util/sync/baas_admin_api.hpp"
23
#include "util/sync/sync_test_utils.hpp"
24
#include "../util/crypt_key.hpp"
25
#include "../util/test_path.hpp"
26
#include "util/sync/sync_test_utils.hpp"
27

28
#include <realm/db.hpp>
29
#include <realm/disable_sync_to_disk.hpp>
30
#include <realm/history.hpp>
31
#include <realm/string_data.hpp>
32
#include <realm/object-store/impl/realm_coordinator.hpp>
33
#include <realm/util/base64.hpp>
34
#include <realm/util/file.hpp>
35

36
#if REALM_ENABLE_SYNC
37
#include <realm/object-store/sync/mongo_client.hpp>
38
#include <realm/object-store/sync/mongo_database.hpp>
39
#include <realm/object-store/sync/mongo_collection.hpp>
40
#include <realm/object-store/sync/sync_manager.hpp>
41
#include <realm/object-store/sync/sync_session.hpp>
42
#include <realm/object-store/sync/sync_user.hpp>
43
#include <realm/object-store/schema.hpp>
44
#endif
45

46
#include <cstdlib>
47
#include <iostream>
48

49
#ifdef _WIN32
50
#include <io.h>
51
#include <fcntl.h>
52

53
inline static int mkstemp(char* _template)
54
{
55
    return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE);
56
}
57
#else
58
#include <unistd.h>
59
#endif
60

61
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
62
#include <condition_variable>
63
#include <functional>
64
#include <thread>
65
#include <map>
66
#endif
67

68
using namespace realm;
69

70
TestFile::TestFile()
71
{
10,071✔
72
    disable_sync_to_disk();
10,071✔
73
    m_temp_dir = util::make_temp_dir();
10,071✔
74
    path = (fs::path(m_temp_dir) / "realm.XXXXXX").string();
10,071✔
75
    if (const char* crypt_key = test_util::crypt_key()) {
10,071✔
76
        encryption_key = std::vector<char>(crypt_key, crypt_key + 64);
×
77
    }
×
78
    int fd = mkstemp(path.data());
10,071✔
79
    if (fd < 0) {
10,071✔
80
        int err = errno;
×
81
        throw std::system_error(err, std::system_category());
×
82
    }
×
83
#ifdef _WIN32
84
    _close(fd);
85
    _unlink(path.c_str());
86
#else // POSIX
87
    close(fd);
10,071✔
88
    unlink(path.c_str());
10,071✔
89
#endif
10,071✔
90

91
    schema_version = 0;
10,071✔
92
}
10,071✔
93

94
TestFile::~TestFile()
95
{
10,071✔
96
    if (!m_persist) {
10,071✔
97
        try {
9,983✔
98
            util::Logger::get_default_logger()->debug("~TestFile() removing '%1' and '%2'", path, m_temp_dir);
9,983✔
99
            util::File::try_remove(path);
9,983✔
100
            util::try_remove_dir_recursive(m_temp_dir);
9,983✔
101
        }
9,983✔
102
        catch (const std::exception& e) {
9,983✔
UNCOV
103
            util::Logger::get_default_logger()->warn("~TestFile() cleanup failed for '%1': %2", path, e.what());
×
104
            // clean up is best effort, ignored.
UNCOV
105
        }
×
106
    }
9,983✔
107
}
10,071✔
108

109
DBOptions TestFile::options() const
110
{
26✔
111
    DBOptions options;
26✔
112
    options.durability = in_memory ? DBOptions::Durability::MemOnly : DBOptions::Durability::Full;
26✔
113
    return options;
26✔
114
}
26✔
115

116
InMemoryTestFile::InMemoryTestFile()
117
{
8,618✔
118
    in_memory = true;
8,618✔
119
    schema_version = 0;
8,618✔
120
    encryption_key = std::vector<char>();
8,618✔
121
}
8,618✔
122

123
DBOptions InMemoryTestFile::options() const
124
{
×
125
    DBOptions options;
×
126
    options.durability = DBOptions::Durability::MemOnly;
×
127
    return options;
×
128
}
×
129

130
#if REALM_ENABLE_SYNC
131

132
static const std::string fake_refresh_token = ENCODE_FAKE_JWT("not_a_real_token");
133
static const std::string fake_access_token = ENCODE_FAKE_JWT("also_not_real");
134
static const std::string fake_device_id = "123400000000000000000000";
135

136
SyncTestFile::SyncTestFile(TestSyncManager& tsm, std::string name, std::string user_name)
137
    : SyncTestFile(tsm.fake_user(user_name), bson::Bson(name))
91✔
138
{
240✔
139
}
240✔
140

141
#if REALM_APP_SERVICES
142
SyncTestFile::SyncTestFile(OfflineAppSession& oas, std::string name)
143
    : SyncTestFile(oas.make_user(), bson::Bson(name))
1,830✔
144
{
3,660✔
145
}
3,660✔
146
#endif // REALM_APP_SERVICES
147

148
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition, util::Optional<Schema> schema)
149
{
8,066✔
150
    REALM_ASSERT(user);
8,066✔
151
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
8,066✔
152
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
8,066✔
153
    sync_config->error_handler = [](std::shared_ptr<SyncSession>, SyncError error) {
8,066✔
154
        util::format(std::cerr, "An unexpected sync error was caught by the default SyncTestFile handler: '%1'\n",
×
155
                     error.status);
×
156
        abort();
×
157
    };
×
158
    schema_version = 1;
8,066✔
159
    this->schema = std::move(schema);
8,066✔
160
    schema_mode = SchemaMode::AdditiveExplicit;
8,066✔
161
}
8,066✔
162

163
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition,
164
                           realm::util::Optional<realm::Schema> schema,
165
                           std::function<SyncSessionErrorHandler>&& error_handler)
166
{
2✔
167
    REALM_ASSERT(user);
2✔
168
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
2✔
169
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
2✔
170
    sync_config->error_handler = std::move(error_handler);
2✔
171
    schema_version = 1;
2✔
172
    this->schema = std::move(schema);
2✔
173
    schema_mode = SchemaMode::AdditiveExplicit;
2✔
174
}
2✔
175

176
SyncTestFile::SyncTestFile(std::shared_ptr<realm::SyncUser> user, realm::Schema _schema, SyncConfig::FLXSyncEnabled)
177
{
493✔
178
    REALM_ASSERT(user);
493✔
179
    sync_config = std::make_shared<realm::SyncConfig>(user, SyncConfig::FLXSyncEnabled{});
493✔
180
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
493✔
181
    sync_config->error_handler = [](std::shared_ptr<SyncSession> session, SyncError error) {
493✔
182
        util::format(std::cerr,
×
183
                     "An unexpected sync error was caught by the default SyncTestFile handler: '%1' for '%2'",
×
184
                     error.status, session->path());
×
185
        abort();
×
186
    };
×
187
    schema_version = 0;
493✔
188
    schema = _schema;
493✔
189
    schema_mode = SchemaMode::AdditiveExplicit;
493✔
190
}
493✔
191

192
SyncTestFile::SyncTestFile(TestSyncManager& tsm, bson::Bson partition, Schema schema)
193
    : SyncTestFile(tsm.fake_user("test"), std::move(partition), std::move(schema))
194
{
×
195
}
×
196

197
// MARK: - SyncServer
198
SyncServer::SyncServer(const SyncServer::Config& config)
199
    : m_local_root_dir(config.local_dir.empty() ? util::make_temp_dir() : config.local_dir)
99✔
200
    , m_server(m_local_root_dir, util::none, ([&] {
252✔
201
                   using namespace std::literals::chrono_literals;
252✔
202

203
                   m_logger = util::Logger::get_default_logger();
252✔
204

205
                   sync::Server::Config c;
252✔
206
                   c.logger = m_logger;
252✔
207
                   c.token_expiration_clock = this;
252✔
208
                   c.listen_address = "127.0.0.1";
252✔
209
                   c.disable_sync_to_disk = true;
252✔
210
                   c.ssl = config.ssl;
252✔
211
                   if (c.ssl) {
252✔
212
                       c.ssl_certificate_path = test_util::get_test_resource_path() + "test_util_network_ssl_ca.pem";
2✔
213
                       c.ssl_certificate_key_path =
2✔
214
                           test_util::get_test_resource_path() + "test_util_network_ssl_key.pem";
2✔
215
                   }
2✔
216

217
                   return c;
252✔
218
               })())
252✔
219
{
252✔
220
    m_server.start();
252✔
221
    m_url = util::format("%1://127.0.0.1:%2", config.ssl ? "wss" : "ws", m_server.listen_endpoint().port());
252✔
222
    if (config.start_immediately)
252✔
223
        start();
191✔
224
}
252✔
225

226
SyncServer::~SyncServer()
227
{
252✔
228
    stop();
252✔
229
}
252✔
230

231
void SyncServer::start()
232
{
224✔
233
    REALM_ASSERT(!m_thread.joinable());
224✔
234
    m_thread = std::thread([this] {
224✔
235
        m_server.run();
224✔
236
    });
224✔
237
}
224✔
238

239
void SyncServer::stop()
240
{
302✔
241
    m_server.stop();
302✔
242
    if (m_thread.joinable())
302✔
243
        m_thread.join();
224✔
244
}
302✔
245

246
std::string SyncServer::url_for_realm(StringData realm_name) const
247
{
×
248
    return util::format("%1/%2", m_url, realm_name);
×
249
}
×
250

251
int SyncServer::port() const
252
{
24✔
253
    return static_cast<int>(m_server.listen_endpoint().port());
24✔
254
}
24✔
255

256
struct WaitForSessionState {
257
    std::condition_variable cv;
258
    std::mutex mutex;
259
    bool complete = false;
260
    Status status = Status::OK();
261
};
262

263
static Status wait_for_session(Realm& realm, void (SyncSession::*fn)(util::UniqueFunction<void(Status)>&&),
264
                               std::chrono::seconds timeout)
265
{
1,980✔
266
    auto shared_state = std::make_shared<WaitForSessionState>();
1,980✔
267
    auto& session = *realm.sync_session();
1,980✔
268
    auto delay = TEST_TIMEOUT_EXTRA > 0 ? timeout + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : timeout;
1,980✔
269
    (session.*fn)([weak_state = std::weak_ptr<WaitForSessionState>(shared_state)](Status s) {
1,980✔
270
        auto shared_state = weak_state.lock();
1,980✔
271
        if (!shared_state) {
1,980✔
272
            return;
×
273
        }
×
274
        std::lock_guard<std::mutex> lock(shared_state->mutex);
1,980✔
275
        shared_state->complete = true;
1,980✔
276
        shared_state->status = s;
1,980✔
277
        shared_state->cv.notify_one();
1,980✔
278
    });
1,980✔
279
    std::unique_lock<std::mutex> lock(shared_state->mutex);
1,980✔
280
    bool completed = shared_state->cv.wait_for(lock, delay, [&]() {
3,960✔
281
        return shared_state->complete == true;
3,960✔
282
    });
3,960✔
283
    if (!completed) {
1,980✔
284
        throw std::runtime_error(util::format("wait_for_session() exceeded %1 s", delay.count()));
×
285
    }
×
286
    return shared_state->status;
1,980✔
287
}
1,980✔
288

289
bool wait_for_upload(Realm& realm, std::chrono::seconds timeout)
290
{
932✔
291
    return !wait_for_session(realm, &SyncSession::wait_for_upload_completion, timeout).is_ok();
932✔
292
}
932✔
293

294
bool wait_for_download(Realm& realm, std::chrono::seconds timeout)
295
{
1,048✔
296
    return !wait_for_session(realm, &SyncSession::wait_for_download_completion, timeout).is_ok();
1,048✔
297
}
1,048✔
298

299
#if REALM_APP_SERVICES
300
void set_app_config_defaults(app::AppConfig& app_config,
301
                             const std::shared_ptr<app::GenericNetworkTransport>& transport)
302
{
4,379✔
303
    if (!app_config.transport)
4,379✔
304
        app_config.transport = transport;
3,832✔
305
    if (app_config.device_info.platform_version.empty())
4,379✔
306
        app_config.device_info.platform_version = "Object Store Test Platform Version";
3,832✔
307
    if (app_config.device_info.sdk_version.empty())
4,379✔
308
        app_config.device_info.sdk_version = "SDK Version";
3,832✔
309
    if (app_config.device_info.sdk.empty())
4,379✔
310
        app_config.device_info.sdk = "SDK Name";
3,832✔
311
    if (app_config.device_info.device_name.empty())
4,379✔
312
        app_config.device_info.device_name = "Device Name";
3,832✔
313
    if (app_config.device_info.device_version.empty())
4,379✔
314
        app_config.device_info.device_version = "Device Version";
3,832✔
315
    if (app_config.device_info.framework_name.empty())
4,379✔
316
        app_config.device_info.framework_name = "Framework Name";
3,832✔
317
    if (app_config.device_info.framework_version.empty())
4,379✔
318
        app_config.device_info.framework_version = "Framework Version";
3,832✔
319
    if (app_config.device_info.bundle_id.empty())
4,379✔
320
        app_config.device_info.bundle_id = "Bundle Id";
3,832✔
321
    if (app_config.app_id.empty())
4,379✔
322
        app_config.app_id = "app_id";
3,832✔
323
    app_config.metadata_mode = app::AppConfig::MetadataMode::InMemory;
4,379✔
324
}
4,379✔
325
#endif // REALM_APP_SERVICES
326

327
// MARK: - TestAppSession
328

329
#if REALM_ENABLE_AUTH_TESTS
330

331
TestAppSession::TestAppSession()
332
    : TestAppSession(get_runtime_app_session(), nullptr, DeleteApp{false})
65✔
333
{
130✔
334
}
130✔
335

336
TestAppSession::TestAppSession(AppSession session,
337
                               std::shared_ptr<realm::app::GenericNetworkTransport> custom_transport,
338
                               DeleteApp delete_app, ReconnectMode reconnect_mode,
339
                               std::shared_ptr<realm::sync::SyncSocketProvider> custom_socket_provider,
340
                               std::shared_ptr<realm::util::Logger> logger)
341
    : m_app_session(std::make_unique<AppSession>(session))
265✔
342
    , m_base_file_path(util::make_temp_dir() + random_string(10))
265✔
343
    , m_delete_app(delete_app)
265✔
344
    , m_transport(custom_transport)
265✔
345
{
547✔
346
    if (!m_transport)
547✔
347
        m_transport = instance_of<SynchronousTestTransport>;
318✔
348
    app_config = get_config(m_transport, *m_app_session);
547✔
349
    set_app_config_defaults(app_config, m_transport);
547✔
350
    app_config.base_file_path = m_base_file_path;
547✔
351
    app_config.metadata_mode = realm::app::AppConfig::MetadataMode::NoEncryption;
547✔
352

353
    util::try_make_dir(m_base_file_path);
547✔
354
    app_config.sync_client_config.reconnect_mode = reconnect_mode;
547✔
355
    app_config.sync_client_config.socket_provider = custom_socket_provider;
547✔
356
    // With multiplexing enabled, the linger time controls how long a
357
    // connection is kept open for reuse. In tests, we want to shut
358
    // down sync clients immediately.
359
    app_config.sync_client_config.timeouts.connection_linger_time = 0;
547✔
360
    if (logger) {
547✔
361
        app_config.sync_client_config.logger_factory = [logger](util::Logger::Level) {
2✔
362
            return logger;
2✔
363
        };
2✔
364
    }
2✔
365

366
    m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config);
547✔
367

368
    // initialize sync client
369
    m_app->sync_manager()->get_sync_client();
547✔
370
    user_creds = create_user_and_log_in(m_app);
547✔
371
}
547✔
372

373
TestAppSession::~TestAppSession()
374
{
547✔
375
    if (util::File::exists(m_base_file_path)) {
547✔
376
        try {
547✔
377
            m_app->sync_manager()->tear_down_for_testing();
547✔
378
            util::try_remove_dir_recursive(m_base_file_path);
547✔
379
        }
547✔
380
        catch (const std::exception& ex) {
547✔
381
            std::cerr << ex.what() << "\n";
×
382
        }
×
383
        app::App::clear_cached_apps();
547✔
384
    }
547✔
385
    if (m_delete_app) {
547✔
386
        m_app_session->admin_api.delete_app(m_app_session->server_app_id);
389✔
387
    }
389✔
388
}
547✔
389

390
void TestAppSession::close(bool tear_down)
391
{
2✔
392
    try {
2✔
393
        if (tear_down) {
2✔
394
            // If tearing down, make sure there's an app to work with
395
            if (!m_app) {
×
396
                reopen(false);
×
397
            }
×
398
            REALM_ASSERT(m_app);
×
399
            // Clean up the app data
400
            m_app->sync_manager()->tear_down_for_testing();
×
401
        }
×
402
        else if (m_app) {
2✔
403
            // Otherwise, make sure all the session are closed
404
            m_app->sync_manager()->close_all_sessions();
2✔
405
        }
2✔
406
        m_app.reset();
2✔
407

408
        // If tearing down, clean up the test file directory
409
        if (tear_down && !m_base_file_path.empty() && util::File::exists(m_base_file_path)) {
2!
410
            util::try_remove_dir_recursive(m_base_file_path);
×
411
            m_base_file_path.clear();
×
412
        }
×
413
    }
2✔
414
    catch (const std::exception& ex) {
2✔
415
        std::cerr << "Error tearing down TestAppSession: " << ex.what() << "\n";
×
416
    }
×
417
    // Ensure all cached apps are cleared
418
    app::App::clear_cached_apps();
2✔
419
}
2✔
420

421
void TestAppSession::reopen(bool log_in)
422
{
2✔
423
    REALM_ASSERT(!m_base_file_path.empty());
2✔
424
    if (m_app) {
2✔
425
        close(false);
2✔
426
    }
2✔
427
    m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config);
2✔
428

429
    // initialize sync client
430
    m_app->sync_manager()->get_sync_client();
2✔
431
    if (log_in) {
2✔
432
        log_in_user(m_app, user_creds);
×
433
    }
×
434
}
2✔
435

436
std::vector<bson::BsonDocument> TestAppSession::get_documents(app::User& user, const std::string& object_type,
437
                                                              size_t expected_count) const
438
{
25✔
439
    app::MongoClient remote_client = user.mongo_client("BackingDB");
25✔
440
    app::MongoDatabase db = remote_client.db(m_app_session->config.mongo_dbname);
25✔
441
    app::MongoCollection collection = db[object_type];
25✔
442
    int sleep_time = 10;
25✔
443
    timed_wait_for(
25✔
444
        [&] {
101✔
445
            uint64_t count = 0;
101✔
446
            collection.count({}, [&](uint64_t c, util::Optional<app::AppError> error) {
101✔
447
                REQUIRE(!error);
101!
448
                count = c;
101✔
449
            });
101✔
450
            if (count < expected_count) {
101✔
451
                // querying the server too frequently makes it take longer to process the sync changesets we're
452
                // waiting for
453
                millisleep(sleep_time);
56✔
454
                if (sleep_time < 500) {
56✔
455
                    sleep_time *= 2;
52✔
456
                }
52✔
457
                return false;
56✔
458
            }
56✔
459
            return true;
45✔
460
        },
101✔
461
        std::chrono::minutes(5));
25✔
462

463
    std::vector<bson::BsonDocument> documents;
25✔
464
    collection.find({}, {}, [&](util::Optional<bson::BsonArray>&& result, util::Optional<app::AppError> error) {
25✔
465
        REQUIRE(result);
25!
466
        REQUIRE(!error);
25!
467
        REQUIRE(result->size() == expected_count);
25!
468
        documents.reserve(result->size());
25✔
469
        for (auto&& bson : *result) {
2,294✔
470
            REQUIRE(bson.type() == bson::Bson::Type::Document);
2,294!
471
            documents.push_back(std::move(static_cast<const bson::BsonDocument&>(bson)));
2,294✔
472
        }
2,294✔
473
    });
25✔
474
    return documents;
25✔
475
}
25✔
476
#endif // REALM_ENABLE_AUTH_TESTS
477

478
// MARK: - TestSyncManager
479

480
TestSyncManager::Config::Config() {}
244✔
481

482
TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config)
483
    : m_sync_manager(SyncManager::create(SyncClientConfig()))
95✔
484
    , m_sync_server(sync_server_config)
95✔
485
    , m_base_file_path(config.base_path.empty() ? util::make_temp_dir() : config.base_path)
95✔
486
    , m_should_teardown_test_directory(config.should_teardown_test_directory)
95✔
487
{
244✔
488
    util::try_make_dir(m_base_file_path);
244✔
489

490
    m_sync_manager->set_sync_route(m_sync_server.base_url() + "/realm-sync", true);
244✔
491
    if (config.start_sync_client) {
244✔
492
        m_sync_manager->get_sync_client();
240✔
493
    }
240✔
494
}
244✔
495

496
TestSyncManager::~TestSyncManager()
497
{
244✔
498
    if (m_should_teardown_test_directory) {
244✔
499
        if (!m_base_file_path.empty() && util::File::exists(m_base_file_path)) {
244✔
500
            try {
244✔
501
                m_sync_manager->tear_down_for_testing();
244✔
502
                util::try_remove_dir_recursive(m_base_file_path);
244✔
503
            }
244✔
504
            catch (const std::exception& ex) {
244✔
505
                std::cerr << ex.what() << "\n";
×
506
            }
×
507
#if REALM_APP_SERVICES
244✔
508
            app::App::clear_cached_apps();
244✔
509
#endif // REALM_APP_SERVICES
244✔
510
        }
244✔
511
    }
244✔
512
}
244✔
513

514
std::shared_ptr<TestUser> TestSyncManager::fake_user(const std::string& name)
515
{
330✔
516
    auto user = std::make_shared<TestUser>(name, m_sync_manager);
330✔
517
    user->m_access_token = fake_access_token;
330✔
518
    user->m_refresh_token = fake_refresh_token;
330✔
519
    return user;
330✔
520
}
330✔
521

522
#if REALM_APP_SERVICES
523
OfflineAppSession::Config::Config(std::shared_ptr<realm::app::GenericNetworkTransport> t)
524
    : transport(t)
1,894✔
525
{
3,788✔
526
}
3,788✔
527

528
OfflineAppSession::OfflineAppSession(OfflineAppSession::Config config)
529
    : m_transport(std::move(config.transport))
1,901✔
530
    , m_delete_storage(config.delete_storage)
1,901✔
531
{
3,802✔
532
    REALM_ASSERT(m_transport);
3,802✔
533
    set_app_config_defaults(m_app_config, m_transport);
3,802✔
534

535
    if (config.storage_path) {
3,802✔
536
        m_base_file_path = *config.storage_path;
28✔
537
        util::try_make_dir(m_base_file_path);
28✔
538
    }
28✔
539
    else {
3,774✔
540
        m_base_file_path = util::make_temp_dir();
3,774✔
541
    }
3,774✔
542

543
    m_app_config.base_file_path = m_base_file_path;
3,802✔
544
    m_app_config.metadata_mode = config.metadata_mode;
3,802✔
545
    if (config.base_url) {
3,802✔
546
        m_app_config.base_url = *config.base_url;
50✔
547
    }
50✔
548
    if (config.app_id) {
3,802✔
549
        m_app_config.app_id = *config.app_id;
×
550
    }
×
551
    m_app_config.sync_client_config.socket_provider = config.socket_provider;
3,802✔
552
    m_app = app::App::get_app(app::App::CacheMode::Disabled, m_app_config);
3,802✔
553
}
3,802✔
554

555
OfflineAppSession::~OfflineAppSession()
556
{
3,802✔
557
    if (util::File::exists(m_base_file_path) && m_delete_storage) {
3,802✔
558
        try {
3,788✔
559
            m_app->sync_manager()->tear_down_for_testing();
3,788✔
560
            util::try_remove_dir_recursive(m_base_file_path);
3,788✔
561
        }
3,788✔
562
        catch (const std::exception& ex) {
3,788✔
563
            std::cerr << ex.what() << "\n";
×
564
        }
×
565
        app::App::clear_cached_apps();
3,788✔
566
    }
3,788✔
567
}
3,802✔
568

569
std::shared_ptr<realm::app::User> OfflineAppSession::make_user() const
570
{
3,732✔
571
    create_user_and_log_in(app());
3,732✔
572
    return app()->current_user();
3,732✔
573
}
3,732✔
574

575
#endif // REALM_APP_SERVICES
576
#endif // REALM_ENABLE_SYNC
577

578
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
579
// MARK: - TsanNotifyWorker
580
// A helper which synchronously runs on_change() on a fixed background thread
581
// so that ThreadSanitizer can potentially detect issues
582
// This deliberately uses an unsafe spinlock for synchronization to ensure that
583
// the code being tested has to supply all required safety
584
static class TsanNotifyWorker {
585
public:
586
    TsanNotifyWorker()
587
    {
588
        m_thread = std::thread([&] {
589
            work();
590
        });
591
    }
592

593
    void work()
594
    {
595
        while (true) {
596
            auto value = m_signal.load(std::memory_order_relaxed);
597
            if (value == 0 || value == 1)
598
                continue;
599
            if (value == 2)
600
                return;
601

602
            if (value & 1) {
603
                // Synchronize on the first handover of a given coordinator.
604
                value &= ~1;
605
                m_signal.load();
606
            }
607

608
            auto c = reinterpret_cast<_impl::RealmCoordinator*>(value);
609
            c->on_change();
610
            m_signal.store(1, std::memory_order_relaxed);
611
        }
612
    }
613

614
    ~TsanNotifyWorker()
615
    {
616
        m_signal = 2;
617
        m_thread.join();
618
    }
619

620
    void on_change(const std::shared_ptr<_impl::RealmCoordinator>& c)
621
    {
622
        auto& it = m_published_coordinators[c.get()];
623
        if (it.lock()) {
624
            m_signal.store(reinterpret_cast<uintptr_t>(c.get()), std::memory_order_relaxed);
625
        }
626
        else {
627
            // Synchronize on the first handover of a given coordinator.
628
            it = c;
629
            m_signal = reinterpret_cast<uintptr_t>(c.get()) | 1;
630
        }
631

632
        while (m_signal.load(std::memory_order_relaxed) != 1)
633
            ;
634
    }
635

636
private:
637
    std::atomic<uintptr_t> m_signal{0};
638
    std::thread m_thread;
639
    std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators;
640
} s_worker;
641

642
void on_change_but_no_notify(Realm& realm)
643
{
644
    s_worker.on_change(_impl::RealmCoordinator::get_existing_coordinator(realm.config().path));
645
}
646

647
void advance_and_notify(Realm& realm)
648
{
649
    on_change_but_no_notify(realm);
650
    realm.notify();
651
}
652

653
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
654

655
void on_change_but_no_notify(Realm& realm)
656
{
26,068✔
657
    _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change();
26,068✔
658
}
26,068✔
659

660
void advance_and_notify(Realm& realm)
661
{
17,312✔
662
    on_change_but_no_notify(realm);
17,312✔
663
    realm.notify();
17,312✔
664
}
17,312✔
665
#endif
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