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

realm / realm-core / github_pull_request_275914

25 Sep 2023 03:10PM UTC coverage: 92.915% (+1.7%) from 91.215%
github_pull_request_275914

Pull #6073

Evergreen

jedelbo
Merge tag 'v13.21.0' into next-major

"Feature/Bugfix release"
Pull Request #6073: Merge next-major

96928 of 177706 branches covered (0.0%)

8324 of 8714 new or added lines in 122 files covered. (95.52%)

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

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

94.12
/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/crypt_key.hpp"
24
#include "../util/test_path.hpp"
25

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

34
#if REALM_ENABLE_SYNC
35
#include <realm/object-store/sync/sync_manager.hpp>
36
#include <realm/object-store/sync/sync_session.hpp>
37
#include <realm/object-store/sync/sync_user.hpp>
38
#include <realm/object-store/schema.hpp>
39
#endif
40

41
#include <cstdlib>
42
#include <iostream>
43

44
#ifdef _WIN32
45
#include <io.h>
46
#include <fcntl.h>
47

48
inline static int mkstemp(char* _template)
49
{
50
    return _open(_mktemp(_template), _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE);
51
}
52
#else
53
#include <unistd.h>
54
#endif
55

56
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
57
#include <condition_variable>
58
#include <functional>
59
#include <thread>
60
#include <map>
61
#endif
62

63
using namespace realm;
64

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

4,398✔
87
    schema_version = 0;
8,882✔
88
}
8,882✔
89

90
TestFile::~TestFile()
91
{
8,930✔
92
    if (!m_persist) {
8,930✔
93
        try {
8,846✔
94
            util::Logger::get_default_logger()->detail("~TestFile() removing '%1' and '%2'", path, m_temp_dir);
8,846✔
95
            util::File::try_remove(path);
8,846✔
96
            util::try_remove_dir_recursive(m_temp_dir);
8,846✔
97
        }
8,846✔
98
        catch (const std::exception& e) {
4,384✔
99
            util::Logger::get_default_logger()->warn("~TestFile() cleanup failed for '%1': %2", path, e.what());
6✔
100
            // clean up is best effort, ignored.
2✔
101
        }
6✔
102
    }
8,846✔
103
}
8,930✔
104

105
DBOptions TestFile::options() const
106
{
26✔
107
    DBOptions options;
26✔
108
    options.durability = in_memory ? DBOptions::Durability::MemOnly : DBOptions::Durability::Full;
26✔
109
    return options;
26✔
110
}
26✔
111

112
InMemoryTestFile::InMemoryTestFile()
113
{
8,550✔
114
    in_memory = true;
8,550✔
115
    schema_version = 0;
8,550✔
116
    encryption_key = std::vector<char>();
8,550✔
117
}
8,550✔
118

4,282✔
119
DBOptions InMemoryTestFile::options() const
120
{
121
    DBOptions options;
×
122
    options.durability = DBOptions::Durability::MemOnly;
×
123
    return options;
×
124
}
×
125

126
#if REALM_ENABLE_SYNC
127

128
static const std::string fake_refresh_token = ENCODE_FAKE_JWT("not_a_real_token");
129
static const std::string fake_access_token = ENCODE_FAKE_JWT("also_not_real");
130
static const std::string fake_device_id = "123400000000000000000000";
131

132
static std::shared_ptr<SyncUser> get_fake_user(app::App& app, const std::string& user_name)
133
{
3,304✔
134
    return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id);
6,666✔
135
}
6,666✔
136

3,362✔
137
SyncTestFile::SyncTestFile(std::shared_ptr<app::App> app, std::string name, std::string user_name)
138
    : SyncTestFile(get_fake_user(*app, user_name), bson::Bson(name))
139
{
3,302✔
140
}
6,662✔
141

3,360✔
142
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition, util::Optional<Schema> schema)
143
{
3,520✔
144
    REALM_ASSERT(user);
7,112✔
145
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
7,112✔
146
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
7,112✔
147
    sync_config->error_handler = [](std::shared_ptr<SyncSession>, SyncError error) {
7,112✔
UNCOV
148
        util::format(std::cerr, "An unexpected sync error was caught by the default SyncTestFile handler: '%1'\n",
×
149
                     error.status);
×
150
        abort();
×
151
    };
×
152
    schema_version = 1;
3,520✔
153
    this->schema = std::move(schema);
7,112✔
154
    schema_mode = SchemaMode::AdditiveExplicit;
7,112✔
155
}
7,112✔
156

3,592✔
157
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition,
158
                           realm::util::Optional<realm::Schema> schema,
159
                           std::function<SyncSessionErrorHandler>&& error_handler)
160
{
1✔
161
    REALM_ASSERT(user);
2✔
162
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
2✔
163
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
2✔
164
    sync_config->error_handler = std::move(error_handler);
2✔
165
    schema_version = 1;
2✔
166
    this->schema = std::move(schema);
2✔
167
    schema_mode = SchemaMode::AdditiveExplicit;
2✔
168
}
2✔
169

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

146✔
186
SyncTestFile::SyncTestFile(std::shared_ptr<app::App> app, bson::Bson partition, Schema schema)
187
    : SyncTestFile(app->current_user(), std::move(partition), std::move(schema))
188
{
63✔
189
}
126✔
190

63✔
191
// MARK: - SyncServer
192
SyncServer::SyncServer(const SyncServer::Config& config)
193
    : m_local_root_dir(config.local_dir.empty() ? util::make_temp_dir() : config.local_dir)
194
    , m_server(m_local_root_dir, util::none, ([&] {
1,788✔
195
                   using namespace std::literals::chrono_literals;
3,630✔
196

3,630✔
197
#if TEST_ENABLE_LOGGING
1,788✔
198
                   auto logger = new util::StderrLogger(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
3,630✔
199
                   m_logger.reset(logger);
1,788✔
200
#else
1,842✔
201
                   // Logging is disabled, use a NullLogger to prevent printing anything
1,842✔
202
                   m_logger.reset(new util::NullLogger());
1,842✔
203
#endif
1,842✔
204

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

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

226
SyncServer::~SyncServer()
227
{
3,618✔
228
    stop();
3,618✔
229
}
3,618✔
230

1,830✔
231
void SyncServer::start()
1,830✔
232
{
3,606✔
233
    REALM_ASSERT(!m_thread.joinable());
1,776✔
234
    m_thread = std::thread([this] {
1,776✔
235
        m_server.run();
3,668✔
236
    });
3,668✔
237
}
3,668✔
238

1,830✔
239
void SyncServer::stop()
1,892✔
240
{
1,788✔
241
    m_server.stop();
1,788✔
242
    if (m_thread.joinable())
1,788✔
243
        m_thread.join();
1,776✔
244
}
1,788✔
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
struct WaitForSessionState {
252
    std::condition_variable cv;
253
    std::mutex mutex;
254
    bool complete = false;
255
    Status status = Status::OK();
764✔
256
};
764✔
257

764✔
258
static Status wait_for_session(Realm& realm, void (SyncSession::*fn)(util::UniqueFunction<void(Status)>&&),
764✔
259
                               std::chrono::seconds timeout)
764✔
260
{
1,528✔
261
    auto shared_state = std::make_shared<WaitForSessionState>();
764✔
262
    auto& session = *realm.config().sync_config->user->session_for_on_disk_path(realm.config().path);
764✔
263
    (session.*fn)([weak_state = std::weak_ptr<WaitForSessionState>(shared_state)](Status s) {
1,528✔
264
        auto shared_state = weak_state.lock();
1,528✔
265
        if (!shared_state) {
1,528✔
266
            return;
764✔
267
        }
764✔
268
        std::lock_guard<std::mutex> lock(shared_state->mutex);
1,528✔
269
        shared_state->complete = true;
2,292✔
270
        shared_state->status = s;
2,292✔
271
        shared_state->cv.notify_one();
2,292✔
272
    });
1,528✔
273
    std::unique_lock<std::mutex> lock(shared_state->mutex);
764✔
274
    bool completed = shared_state->cv.wait_for(lock, timeout, [&]() {
1,528✔
275
        return shared_state->complete == true;
2,292✔
276
    });
2,292✔
277
    if (!completed) {
764✔
278
        throw std::runtime_error("wait_for_session() timed out");
279
    }
372✔
280
    return shared_state->status;
1,136✔
281
}
1,136✔
282

283
bool wait_for_upload(Realm& realm, std::chrono::seconds timeout)
284
{
764✔
285
    return !wait_for_session(realm, &SyncSession::wait_for_upload_completion, timeout).is_ok();
764✔
286
}
764✔
287

288
bool wait_for_download(Realm& realm, std::chrono::seconds timeout)
289
{
392✔
290
    return !wait_for_session(realm, &SyncSession::wait_for_download_completion, timeout).is_ok();
2,503✔
291
}
2,503✔
292

1,843✔
293
void set_app_config_defaults(app::App::Config& app_config,
2,111✔
294
                             const std::shared_ptr<app::GenericNetworkTransport>& transport)
1,843✔
295
{
4,151✔
296
    if (!app_config.transport)
3,883✔
297
        app_config.transport = transport;
3,900✔
298
    if (app_config.device_info.platform_version.empty())
3,883✔
299
        app_config.device_info.platform_version = "Object Store Test Platform Version";
3,900✔
300
    if (app_config.device_info.sdk_version.empty())
3,883✔
301
        app_config.device_info.sdk_version = "SDK Version";
3,900✔
302
    if (app_config.device_info.sdk.empty())
3,883✔
303
        app_config.device_info.sdk = "SDK Name";
3,900✔
304
    if (app_config.device_info.device_name.empty())
3,883✔
305
        app_config.device_info.device_name = "Device Name";
3,900✔
306
    if (app_config.device_info.device_version.empty())
3,883✔
307
        app_config.device_info.device_version = "Device Version";
3,900✔
308
    if (app_config.device_info.framework_name.empty())
3,883✔
309
        app_config.device_info.framework_name = "Framework Name";
3,900✔
310
    if (app_config.device_info.framework_version.empty())
3,865✔
311
        app_config.device_info.framework_version = "Framework Version";
3,900✔
312
    if (app_config.device_info.bundle_id.empty())
2,040✔
313
        app_config.device_info.bundle_id = "Bundle Id";
1,789✔
314
    if (app_config.app_id.empty())
2,040✔
315
        app_config.app_id = "app_id";
1,771✔
316
}
2,040✔
317

318
// MARK: - TestAppSession
319

69✔
320
#if REALM_ENABLE_AUTH_TESTS
69✔
321

322
TestAppSession::TestAppSession()
323
    : TestAppSession(get_runtime_app_session(get_base_url()), nullptr, DeleteApp{false})
324
{
69✔
325
}
69✔
326

327
TestAppSession::TestAppSession(AppSession session,
328
                               std::shared_ptr<realm::app::GenericNetworkTransport> custom_transport,
329
                               DeleteApp delete_app, ReconnectMode reconnect_mode,
330
                               std::shared_ptr<realm::sync::SyncSocketProvider> custom_socket_provider)
240✔
331
    : m_app_session(std::make_unique<AppSession>(session))
240✔
332
    , m_base_file_path(util::make_temp_dir() + random_string(10))
163✔
333
    , m_delete_app(delete_app)
240✔
334
    , m_transport(custom_transport)
240✔
335
{
463✔
336
    if (!m_transport)
223✔
337
        m_transport = instance_of<SynchronousTestTransport>;
389✔
338
    auto app_config = get_config(m_transport, *m_app_session);
463✔
339
    util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
463✔
340
    set_app_config_defaults(app_config, m_transport);
463✔
341

463✔
342
    util::try_make_dir(m_base_file_path);
463✔
343
    SyncClientConfig sc_config;
223✔
344
    sc_config.base_file_path = m_base_file_path;
223✔
345
    sc_config.metadata_mode = realm::SyncManager::MetadataMode::NoEncryption;
223✔
346
    sc_config.reconnect_mode = reconnect_mode;
463✔
347
    sc_config.socket_provider = custom_socket_provider;
223✔
348
    // With multiplexing enabled, the linger time controls how long a
463✔
349
    // connection is kept open for reuse. In tests, we want to shut
223✔
350
    // down sync clients immediately.
223✔
351
    sc_config.timeouts.connection_linger_time = 0;
463✔
352

463✔
353
    m_app = app::App::get_uncached_app(app_config, sc_config);
463✔
354

223✔
355
    // initialize sync client
223✔
356
    m_app->sync_manager()->get_sync_client();
463✔
357
    create_user_and_log_in(m_app);
463✔
358
}
463✔
359

240✔
360
TestAppSession::~TestAppSession()
240✔
361
{
463✔
362
    if (util::File::exists(m_base_file_path)) {
223✔
363
        try {
223✔
364
            m_app->sync_manager()->reset_for_testing();
223✔
365
            util::try_remove_dir_recursive(m_base_file_path);
463✔
366
        }
463✔
367
        catch (const std::exception& ex) {
463✔
368
            std::cerr << ex.what() << "\n";
158✔
369
        }
158✔
370
        app::App::clear_cached_apps();
463✔
371
    }
223✔
372
    if (m_delete_app) {
223✔
373
        m_app_session->admin_api.delete_app(m_app_session->server_app_id);
141✔
374
    }
141✔
375
}
223✔
376

377
#endif // REALM_ENABLE_AUTH_TESTS
378

379
// MARK: - TestSyncManager
380

1,842✔
381
TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config)
1,842✔
382
    : transport(config.transport ? config.transport : std::make_shared<Transport>(network_callback))
1,842✔
383
    , m_sync_server(sync_server_config)
1,842✔
384
    , m_should_teardown_test_directory(config.should_teardown_test_directory)
385
{
3,630✔
386
    app::App::Config app_config = config.app_config;
3,609✔
387
    set_app_config_defaults(app_config, transport);
3,630✔
388
    util::Logger::set_default_level_threshold(config.log_level);
3,630✔
389

3,630✔
390
    SyncClientConfig sc_config;
1,788✔
391
    m_base_file_path = config.base_path.empty() ? util::make_temp_dir() + random_string(10) : config.base_path;
3,630✔
392
    util::try_make_dir(m_base_file_path);
3,630✔
393
    sc_config.base_file_path = m_base_file_path;
3,629✔
394
    sc_config.metadata_mode = config.metadata_mode;
3,629✔
395

1,788✔
396
    m_app = app::App::get_uncached_app(app_config, sc_config);
3,630✔
397
    if (config.override_sync_route) {
1,788✔
398
        m_app->sync_manager()->set_sync_route(m_sync_server.base_url() + "/realm-sync");
3,627✔
399
    }
3,627✔
400

3,630✔
401
    if (config.start_sync_client) {
1,788✔
402
        // initialize sync client
1,786✔
403
        m_app->sync_manager()->get_sync_client();
3,628✔
404
    }
3,628✔
405
}
3,611✔
406

1,822✔
407
TestSyncManager::~TestSyncManager()
1,822✔
408
{
3,610✔
409
    if (m_should_teardown_test_directory) {
3,610✔
410
        if (!m_base_file_path.empty() && util::File::exists(m_base_file_path)) {
1,769✔
411
            try {
1,768✔
412
                m_app->sync_manager()->reset_for_testing();
1,768✔
413
                util::try_remove_dir_recursive(m_base_file_path);
3,590✔
414
            }
3,590✔
415
            catch (const std::exception& ex) {
3,591✔
416
                std::cerr << ex.what() << "\n";
1,842✔
417
            }
418
            app::App::clear_cached_apps();
1,768✔
419
        }
1,770✔
420
    }
1,771✔
421
}
1,790✔
422

423
std::shared_ptr<realm::SyncUser> TestSyncManager::fake_user(const std::string& name)
424
{
2✔
425
    return get_fake_user(*m_app, name);
2✔
426
}
2✔
427

428
#endif // REALM_ENABLE_SYNC
429

430
#if REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
431
// MARK: - TsanNotifyWorker
432
// A helper which synchronously runs on_change() on a fixed background thread
433
// so that ThreadSanitizer can potentially detect issues
434
// This deliberately uses an unsafe spinlock for synchronization to ensure that
435
// the code being tested has to supply all required safety
436
static class TsanNotifyWorker {
437
public:
438
    TsanNotifyWorker()
439
    {
440
        m_thread = std::thread([&] {
441
            work();
442
        });
443
    }
444

445
    void work()
446
    {
447
        while (true) {
448
            auto value = m_signal.load(std::memory_order_relaxed);
449
            if (value == 0 || value == 1)
450
                continue;
451
            if (value == 2)
452
                return;
453

454
            if (value & 1) {
455
                // Synchronize on the first handover of a given coordinator.
456
                value &= ~1;
457
                m_signal.load();
458
            }
459

460
            auto c = reinterpret_cast<_impl::RealmCoordinator*>(value);
461
            c->on_change();
462
            m_signal.store(1, std::memory_order_relaxed);
463
        }
464
    }
465

466
    ~TsanNotifyWorker()
467
    {
468
        m_signal = 2;
469
        m_thread.join();
470
    }
471

472
    void on_change(const std::shared_ptr<_impl::RealmCoordinator>& c)
473
    {
474
        auto& it = m_published_coordinators[c.get()];
475
        if (it.lock()) {
476
            m_signal.store(reinterpret_cast<uintptr_t>(c.get()), std::memory_order_relaxed);
477
        }
478
        else {
479
            // Synchronize on the first handover of a given coordinator.
480
            it = c;
481
            m_signal = reinterpret_cast<uintptr_t>(c.get()) | 1;
482
        }
483

484
        while (m_signal.load(std::memory_order_relaxed) != 1)
485
            ;
486
    }
487

488
private:
489
    std::atomic<uintptr_t> m_signal{0};
490
    std::thread m_thread;
491
    std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators;
492
} s_worker;
493

494
void on_change_but_no_notify(Realm& realm)
495
{
496
    s_worker.on_change(_impl::RealmCoordinator::get_existing_coordinator(realm.config().path));
497
}
498

499
void advance_and_notify(Realm& realm)
500
{
501
    on_change_but_no_notify(realm);
502
    realm.notify();
503
}
8,786✔
504

8,786✔
505
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
8,786✔
506

507
void on_change_but_no_notify(Realm& realm)
508
{
19,360✔
509
    _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change();
19,360✔
510
}
19,360✔
511

8,125✔
512
void advance_and_notify(Realm& realm)
513
{
8,077✔
514
    on_change_but_no_notify(realm);
8,077✔
515
    realm.notify();
8,077✔
516
}
8,077✔
517
#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