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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

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

4,478✔
87
    schema_version = 0;
9,034✔
88
}
9,034✔
89

90
TestFile::~TestFile()
91
{
9,082✔
92
    if (!m_persist) {
9,082✔
93
        try {
8,998✔
94
            util::Logger::get_default_logger()->detail("~TestFile() removing '%1' and '%2'", path, m_temp_dir);
8,998✔
95
            util::File::try_remove(path);
8,998✔
96
            util::try_remove_dir_recursive(m_temp_dir);
8,998✔
97
        }
8,998✔
98
        catch (const std::exception& e) {
4,463✔
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,998✔
103
}
9,082✔
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,564✔
114
    in_memory = true;
8,564✔
115
    schema_version = 0;
8,564✔
116
    encryption_key = std::vector<char>();
8,564✔
117
    util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
8,564✔
118
}
8,564✔
119

120
DBOptions InMemoryTestFile::options() const
121
{
×
122
    DBOptions options;
×
123
    options.durability = DBOptions::Durability::MemOnly;
×
124
    return options;
×
UNCOV
125
}
×
126

127
#if REALM_ENABLE_SYNC
128

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

133
static std::shared_ptr<SyncUser> get_fake_user(app::App& app, const std::string& user_name)
134
{
6,810✔
135
    return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id);
6,810✔
136
}
6,810✔
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,806✔
141
}
6,806✔
142

143
SyncTestFile::SyncTestFile(std::shared_ptr<SyncUser> user, bson::Bson partition, util::Optional<Schema> schema)
144
{
7,256✔
145
    REALM_ASSERT(user);
7,256✔
146
    sync_config = std::make_shared<realm::SyncConfig>(user, partition);
7,256✔
147
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
7,256✔
148
    sync_config->error_handler = [](std::shared_ptr<SyncSession>, SyncError error) {
3,592✔
149
        util::format(std::cerr, "An unexpected sync error was caught by the default SyncTestFile handler: '%1'\n",
×
150
                     error.status);
×
151
        abort();
×
UNCOV
152
    };
×
153
    schema_version = 1;
7,256✔
154
    this->schema = std::move(schema);
7,256✔
155
    schema_mode = SchemaMode::AdditiveExplicit;
7,256✔
156
}
7,256✔
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
{
291✔
173
    REALM_ASSERT(user);
291✔
174
    sync_config = std::make_shared<realm::SyncConfig>(user, SyncConfig::FLXSyncEnabled{});
291✔
175
    sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
291✔
176
    sync_config->error_handler = [](std::shared_ptr<SyncSession> session, SyncError error) {
145✔
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();
×
UNCOV
181
    };
×
182
    schema_version = 1;
291✔
183
    schema = _schema;
291✔
184
    schema_mode = SchemaMode::AdditiveExplicit;
291✔
185
}
291✔
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
{
126✔
190
}
126✔
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,702✔
196
                   using namespace std::literals::chrono_literals;
3,702✔
197

1,824✔
198
                   m_logger = std::make_shared<util::StderrLogger>(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
3,702✔
199

1,824✔
200
                   sync::Server::Config c;
3,702✔
201
                   c.logger = m_logger;
3,702✔
202
                   c.token_expiration_clock = this;
3,702✔
203
                   c.listen_address = "127.0.0.1";
3,702✔
204
                   c.disable_sync_to_disk = true;
3,702✔
205
                   c.ssl = config.ssl;
3,702✔
206
                   if (c.ssl) {
3,702✔
207
                       c.ssl_certificate_path = test_util::get_test_resource_path() + "test_util_network_ssl_ca.pem";
2✔
208
                       c.ssl_certificate_key_path =
2✔
209
                           test_util::get_test_resource_path() + "test_util_network_ssl_key.pem";
2✔
210
                   }
2✔
211

1,824✔
212
                   return c;
3,702✔
213
               })())
3,702✔
214
{
3,702✔
215
    m_server.start();
3,702✔
216
    m_url = util::format("%1://127.0.0.1:%2", config.ssl ? "wss" : "ws", m_server.listen_endpoint().port());
3,701✔
217
    if (config.start_immediately)
3,702✔
218
        start();
3,645✔
219
}
3,702✔
220

221
SyncServer::~SyncServer()
222
{
3,702✔
223
    stop();
3,702✔
224
}
3,702✔
225

226
void SyncServer::start()
227
{
3,678✔
228
    REALM_ASSERT(!m_thread.joinable());
3,678✔
229
    m_thread = std::thread([this] {
3,678✔
230
        m_server.run();
3,678✔
231
    });
3,678✔
232
}
3,678✔
233

234
void SyncServer::stop()
235
{
3,752✔
236
    m_server.stop();
3,752✔
237
    if (m_thread.joinable())
3,752✔
238
        m_thread.join();
3,678✔
239
}
3,752✔
240

241
std::string SyncServer::url_for_realm(StringData realm_name) const
UNCOV
242
{
×
UNCOV
243
    return util::format("%1/%2", m_url, realm_name);
×
UNCOV
244
}
×
245

246
struct WaitForSessionState {
247
    std::condition_variable cv;
248
    std::mutex mutex;
249
    bool complete = false;
250
    Status status = Status::OK();
251
};
252

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

278
bool wait_for_upload(Realm& realm, std::chrono::seconds timeout)
279
{
744✔
280
    return !wait_for_session(realm, &SyncSession::wait_for_upload_completion, timeout).is_ok();
744✔
281
}
744✔
282

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

288
void set_app_config_defaults(app::App::Config& app_config,
289
                             const std::shared_ptr<app::GenericNetworkTransport>& transport)
290
{
4,223✔
291
    if (!app_config.transport)
4,223✔
292
        app_config.transport = transport;
3,704✔
293
    if (app_config.device_info.platform_version.empty())
4,223✔
294
        app_config.device_info.platform_version = "Object Store Test Platform Version";
3,704✔
295
    if (app_config.device_info.sdk_version.empty())
4,223✔
296
        app_config.device_info.sdk_version = "SDK Version";
3,704✔
297
    if (app_config.device_info.sdk.empty())
4,223✔
298
        app_config.device_info.sdk = "SDK Name";
3,704✔
299
    if (app_config.device_info.device_name.empty())
4,223✔
300
        app_config.device_info.device_name = "Device Name";
3,704✔
301
    if (app_config.device_info.device_version.empty())
4,223✔
302
        app_config.device_info.device_version = "Device Version";
3,704✔
303
    if (app_config.device_info.framework_name.empty())
4,223✔
304
        app_config.device_info.framework_name = "Framework Name";
3,704✔
305
    if (app_config.device_info.framework_version.empty())
4,223✔
306
        app_config.device_info.framework_version = "Framework Version";
3,704✔
307
    if (app_config.device_info.bundle_id.empty())
4,223✔
308
        app_config.device_info.bundle_id = "Bundle Id";
3,704✔
309
    if (app_config.app_id.empty())
4,223✔
310
        app_config.app_id = "app_id";
3,668✔
311
}
4,223✔
312

313
// MARK: - TestAppSession
314

315
#if REALM_ENABLE_AUTH_TESTS
316

317
TestAppSession::TestAppSession()
318
    : TestAppSession(get_runtime_app_session(get_base_url()), nullptr, DeleteApp{false})
319
{
138✔
320
}
138✔
321

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

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

223✔
348
    m_app = app::App::get_uncached_app(app_config, sc_config);
463✔
349

223✔
350
    // initialize sync client
223✔
351
    m_app->sync_manager()->get_sync_client();
463✔
352
    create_user_and_log_in(m_app);
463✔
353
}
463✔
354

355
TestAppSession::~TestAppSession()
356
{
463✔
357
    if (util::File::exists(m_base_file_path)) {
463✔
358
        try {
463✔
359
            m_app->sync_manager()->reset_for_testing();
463✔
360
            util::try_remove_dir_recursive(m_base_file_path);
463✔
361
        }
463✔
362
        catch (const std::exception& ex) {
223✔
UNCOV
363
            std::cerr << ex.what() << "\n";
×
UNCOV
364
        }
×
365
        app::App::clear_cached_apps();
463✔
366
    }
463✔
367
    if (m_delete_app) {
463✔
368
        m_app_session->admin_api.delete_app(m_app_session->server_app_id);
299✔
369
    }
299✔
370
}
463✔
371

372
#endif // REALM_ENABLE_AUTH_TESTS
373

374
// MARK: - TestSyncManager
375

376
TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config)
377
    : transport(config.transport ? config.transport : std::make_shared<Transport>(network_callback))
378
    , m_sync_server(sync_server_config)
379
    , m_should_teardown_test_directory(config.should_teardown_test_directory)
380
{
3,702✔
381
    app::App::Config app_config = config.app_config;
3,702✔
382
    set_app_config_defaults(app_config, transport);
3,702✔
383
    util::Logger::set_default_level_threshold(config.log_level);
3,702✔
384

1,824✔
385
    SyncClientConfig sc_config;
3,702✔
386
    m_base_file_path = config.base_path.empty() ? util::make_temp_dir() + random_string(10) : config.base_path;
3,681✔
387
    util::try_make_dir(m_base_file_path);
3,702✔
388
    sc_config.base_file_path = m_base_file_path;
3,702✔
389
    sc_config.metadata_mode = config.metadata_mode;
3,702✔
390

1,824✔
391
    m_app = app::App::get_uncached_app(app_config, sc_config);
3,702✔
392
    if (config.override_sync_route) {
3,702✔
393
        m_app->sync_manager()->set_sync_route(m_sync_server.base_url() + "/realm-sync");
3,700✔
394
    }
3,700✔
395

1,824✔
396
    if (config.start_sync_client) {
3,702✔
397
        // initialize sync client
1,822✔
398
        m_app->sync_manager()->get_sync_client();
3,698✔
399
    }
3,698✔
400
}
3,702✔
401

402
TestSyncManager::~TestSyncManager()
403
{
3,702✔
404
    if (m_should_teardown_test_directory) {
3,702✔
405
        if (!m_base_file_path.empty() && util::File::exists(m_base_file_path)) {
3,664✔
406
            try {
3,662✔
407
                m_app->sync_manager()->reset_for_testing();
3,662✔
408
                util::try_remove_dir_recursive(m_base_file_path);
3,662✔
409
            }
3,662✔
410
            catch (const std::exception& ex) {
1,804✔
UNCOV
411
                std::cerr << ex.what() << "\n";
×
UNCOV
412
            }
×
413
            app::App::clear_cached_apps();
3,662✔
414
        }
3,662✔
415
    }
3,664✔
416
}
3,702✔
417

418
std::shared_ptr<realm::SyncUser> TestSyncManager::fake_user(const std::string& name)
419
{
4✔
420
    return get_fake_user(*m_app, name);
4✔
421
}
4✔
422

423
#endif // REALM_ENABLE_SYNC
424

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

440
    void work()
441
    {
442
        while (true) {
443
            auto value = m_signal.load(std::memory_order_relaxed);
444
            if (value == 0 || value == 1)
445
                continue;
446
            if (value == 2)
447
                return;
448

449
            if (value & 1) {
450
                // Synchronize on the first handover of a given coordinator.
451
                value &= ~1;
452
                m_signal.load();
453
            }
454

455
            auto c = reinterpret_cast<_impl::RealmCoordinator*>(value);
456
            c->on_change();
457
            m_signal.store(1, std::memory_order_relaxed);
458
        }
459
    }
460

461
    ~TsanNotifyWorker()
462
    {
463
        m_signal = 2;
464
        m_thread.join();
465
    }
466

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

479
        while (m_signal.load(std::memory_order_relaxed) != 1)
480
            ;
481
    }
482

483
private:
484
    std::atomic<uintptr_t> m_signal{0};
485
    std::thread m_thread;
486
    std::map<_impl::RealmCoordinator*, std::weak_ptr<_impl::RealmCoordinator>> m_published_coordinators;
487
} s_worker;
488

489
void on_change_but_no_notify(Realm& realm)
490
{
491
    s_worker.on_change(_impl::RealmCoordinator::get_existing_coordinator(realm.config().path));
492
}
493

494
void advance_and_notify(Realm& realm)
495
{
496
    on_change_but_no_notify(realm);
497
    realm.notify();
498
}
499

500
#else // REALM_HAVE_CLANG_FEATURE(thread_sanitizer)
501

502
void on_change_but_no_notify(Realm& realm)
503
{
18,215✔
504
    _impl::RealmCoordinator::get_coordinator(realm.config().path)->on_change();
18,215✔
505
}
18,215✔
506

507
void advance_and_notify(Realm& realm)
508
{
16,452✔
509
    on_change_but_no_notify(realm);
16,452✔
510
    realm.notify();
16,452✔
511
}
16,452✔
512
#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