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

realm / realm-core / 1658

10 Sep 2023 11:58PM UTC coverage: 91.222% (-0.04%) from 91.263%
1658

push

Evergreen

GitHub
Merge pull request #6938 from realm/tg/tls-error-reporting

95840 of 175760 branches covered (0.0%)

142 of 146 new or added lines in 7 files covered. (97.26%)

290 existing lines in 22 files now uncovered.

233460 of 255926 relevant lines covered (91.22%)

6997123.82 hits per line

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

88.93
/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,856✔
67
    disable_sync_to_disk();
8,856✔
68
    m_temp_dir = util::make_temp_dir();
8,856✔
69
    path = (fs::path(m_temp_dir) / "realm.XXXXXX").string();
8,856✔
70
    util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
8,856✔
71
    if (const char* crypt_key = test_util::crypt_key()) {
8,856✔
72
        encryption_key = std::vector<char>(crypt_key, crypt_key + 64);
×
73
    }
×
74
    int fd = mkstemp(path.data());
8,856✔
75
    if (fd < 0) {
8,856✔
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,856✔
84
    unlink(path.c_str());
8,856✔
85
#endif
8,856✔
86

4,389✔
87
    schema_version = 0;
8,856✔
88
}
8,856✔
89

90
TestFile::~TestFile()
91
{
8,904✔
92
    if (!m_persist) {
8,904✔
93
        try {
8,820✔
94
            util::Logger::get_default_logger()->detail("~TestFile() removing '%1' and '%2'", path, m_temp_dir);
8,820✔
95
            util::File::try_remove(path);
8,820✔
96
            util::try_remove_dir_recursive(m_temp_dir);
8,820✔
97
        }
8,820✔
98
        catch (const std::exception& e) {
4,374✔
99
            util::Logger::get_default_logger()->warn("~TestFile() cleanup failed for '%1': %2", path, e.what());
5✔
100
            // clean up is best effort, ignored.
2✔
101
        }
5✔
102
    }
8,820✔
103
}
8,904✔
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,536✔
114
    in_memory = true;
8,536✔
115
    schema_version = 0;
8,536✔
116
    encryption_key = std::vector<char>();
8,536✔
117
}
8,536✔
118

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
{
6,666✔
134
    return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, app.base_url(),
6,666✔
135
                                        fake_device_id);
6,666✔
136
}
6,666✔
137

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

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

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

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

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

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

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

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

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

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

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

240
void SyncServer::stop()
241
{
3,680✔
242
    m_server.stop();
3,680✔
243
    if (m_thread.joinable())
3,680✔
244
        m_thread.join();
3,606✔
245
}
3,680✔
246

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

252
struct WaitForSessionState {
253
    std::condition_variable cv;
254
    std::mutex mutex;
255
    bool complete = false;
256
    Status status = Status::OK();
257
};
258

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

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

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

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

319
// MARK: - TestAppSession
320

321
#if REALM_ENABLE_AUTH_TESTS
322

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

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

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

215✔
354
    m_app = app::App::get_uncached_app(app_config, sc_config);
447✔
355

215✔
356
    // initialize sync client
215✔
357
    m_app->sync_manager()->get_sync_client();
447✔
358
    create_user_and_log_in(m_app);
447✔
359
}
447✔
360

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

378
#endif // REALM_ENABLE_AUTH_TESTS
379

380
// MARK: - TestSyncManager
381

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

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

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

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

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

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

429
#endif // REALM_ENABLE_SYNC
430

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

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

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

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

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

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

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

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

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

500
void advance_and_notify(Realm& realm)
501
{
502
    on_change_but_no_notify(realm);
503
    realm.notify();
504
}
505

506
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
507

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

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

© 2026 Coveralls, Inc