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

realm / realm-core / 2492

15 Jul 2024 06:38PM UTC coverage: 90.993% (+0.01%) from 90.98%
2492

push

Evergreen

web-flow
RCORE-2192 RCORE-2193 Fix FLX download progress reporting (#7870)

* Fix FLX download progress reporting

We need to store the download progress for each batch of a bootstrap and not
just at the end for it to be useful in any way.

The server will sometimes send us DOWNLOAD messages with a non-one estimate
followed by a one estimate where the byte-level information is the same (as the
final message is empty). When this happens we need to report the download
completion to the user, so add the estimate to the fields checked for changes.

A subscription change which doesn't actually change what set of objects is in
view can result in an empty DOWNLOAD message with no changes other than the
query version, and we should report that too.

* Fix a comment

* Pass the DownloadMessage to process_flx_bootstrap_message()

* Report steady-state download progress

102388 of 180586 branches covered (56.7%)

247 of 257 new or added lines in 10 files covered. (96.11%)

44 existing lines in 13 files now uncovered.

215408 of 236730 relevant lines covered (90.99%)

5309938.86 hits per line

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

97.31
/test/object-store/sync/session/progress_notifications.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2017 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 <realm/object-store/sync/sync_session.hpp>
20

21
#include <realm/util/scope_exit.hpp>
22

23
#if REALM_ENABLE_AUTH_TESTS
24
#include "util/test_file.hpp"
25
#include "util/sync/flx_sync_harness.hpp"
26
#include "util/sync/sync_test_utils.hpp"
27

28
#include <realm/object-store/impl/object_accessor_impl.hpp>
29
#include <realm/object-store/sync/async_open_task.hpp>
30
#include <realm/object-store/util/scheduler.hpp>
31

32
using namespace realm::app;
33
#endif
34

35
#include <catch2/catch_all.hpp>
36
#include <catch2/matchers/catch_matchers_floating_point.hpp>
37
using namespace Catch::Matchers;
38

39
#include <iomanip>
40

41
using namespace realm;
42
using NotifierType = SyncSession::ProgressDirection;
43

44
struct ProgressEntry {
45
    uint64_t transferred = 0;
46
    uint64_t transferrable = 0;
47
    double estimate = 0.0;
48

49
    inline bool operator==(const ProgressEntry& other) const noexcept
50
    {
60✔
51
        return transferred == other.transferred && transferrable == other.transferrable && estimate == other.estimate;
60✔
52
    }
60✔
53
};
54

55
static std::string estimate_to_string(double est)
56
{
237✔
57
    std::ostringstream ss;
237✔
58
    ss << std::setprecision(4) << est;
237✔
59
    return ss.str();
237✔
60
}
237✔
61

62
static std::ostream& operator<<(std::ostream& os, const ProgressEntry& value)
63
{
×
64
    return os << util::format("{ transferred: %1, transferrable: %2, estimate: %3 }", value.transferred,
×
65
                              value.transferrable, estimate_to_string(value.estimate));
×
66
}
×
67

68

69
struct WaitableProgress : public util::AtomicRefCountBase {
70
    WaitableProgress(const std::shared_ptr<util::Logger>& base_logger, std::string context)
71
        : logger(std::move(context), base_logger)
25✔
72
    {
50✔
73
    }
50✔
74

75
    std::function<SyncSession::ProgressNotifierCallback> make_cb()
76
    {
50✔
77
        auto self = util::bind_ptr(this);
50✔
78
        return [self](uint64_t transferred, uint64_t transferrable, double estimate) {
237✔
79
            self->logger.debug("Progress callback called xferred: %1, xferrable: %2, estimate: %3", transferred,
237✔
80
                               transferrable, estimate_to_string(estimate));
237✔
81
            std::lock_guard lk(self->mutex);
237✔
82
            self->entries.push_back(ProgressEntry{transferred, transferrable, estimate});
237✔
83
            self->cv.notify_one();
237✔
84
        };
237✔
85
    }
50✔
86

87
    bool empty()
88
    {
4✔
89
        std::lock_guard lk(mutex);
4✔
90
        return entries.empty();
4✔
91
    }
4✔
92

93
    std::vector<ProgressEntry> wait_for_full_sync()
94
    {
54✔
95
        std::unique_lock lk(mutex);
54✔
96
        if (!cv.wait_for(lk, std::chrono::seconds(30), [&] {
97✔
97
                return !entries.empty() && entries.back().transferred >= entries.back().transferrable &&
97✔
98
                       entries.back().estimate >= 1.0;
97✔
99
            })) {
97✔
100
            CAPTURE(entries);
×
101
            FAIL("Failed while waiting for progress to complete");
×
102
            return {};
×
103
        }
×
104

105
        std::vector<ProgressEntry> ret;
54✔
106
        std::swap(ret, entries);
54✔
107
        return ret;
54✔
108
    }
54✔
109

110
    util::PrefixLogger logger;
111
    std::mutex mutex;
112
    std::condition_variable cv;
113
    std::vector<ProgressEntry> entries;
114
};
115

116
struct TestInputValue {
117
    struct IsRegistration {};
118
    explicit TestInputValue(IsRegistration)
119
        : is_registration(true)
9✔
120
    {
18✔
121
    }
18✔
122

123
    TestInputValue(int64_t query_version, double cur_estimate, uint64_t transferred, uint64_t transferrable)
124
        : query_version(query_version)
42✔
125
        , cur_estimate(cur_estimate)
42✔
126
        , transferred(transferred)
42✔
127
        , transferrable(transferrable)
42✔
128
    {
84✔
129
    }
84✔
130

131
    int64_t query_version = 0;
132
    double cur_estimate = 0;
133
    uint64_t transferred = 0;
134
    uint64_t transferrable = 0;
135
    bool is_registration = false;
136
};
137

138
struct TestValues {
139
    std::vector<TestInputValue> input_values;
140
    std::vector<ProgressEntry> expected_values;
141
    int64_t registered_at_query_version;
142
};
143

144
TEST_CASE("progress notification", "[sync][session][progress]") {
48✔
145
    using NotifierType = SyncSession::ProgressDirection;
48✔
146
    _impl::SyncProgressNotifier progress;
48✔
147

148
    SECTION("callback is not called prior to first update") {
48✔
149
        bool callback_was_called = false;
2✔
150
        progress.register_callback(
2✔
151
            [&](auto, auto, double) {
2✔
152
                callback_was_called = true;
×
153
            },
×
154
            NotifierType::upload, false, 0);
2✔
155
        progress.register_callback(
2✔
156
            [&](auto, auto, double) {
2✔
157
                callback_was_called = true;
×
158
            },
×
159
            NotifierType::download, false, 0);
2✔
160
        REQUIRE_FALSE(callback_was_called);
2!
161
    }
2✔
162

163
    SECTION("callback is invoked immediately when a progress update has already occurred") {
48✔
164
        progress.set_local_version(1);
6✔
165
        progress.update(0, 0, 0, 0, 1, 0.0, 0.0, 0);
6✔
166

167
        bool callback_was_called = false;
6✔
168
        SECTION("for upload notifications, with no data transfer ongoing") {
6✔
169
            double estimate = 0.0;
2✔
170
            progress.register_callback(
2✔
171
                [&](auto, auto, double ep) {
2✔
172
                    callback_was_called = true;
2✔
173
                    estimate = ep;
2✔
174
                },
2✔
175
                NotifierType::upload, false, 0);
2✔
176
            REQUIRE(callback_was_called);
2!
177
            REQUIRE(estimate == 0.0);
2!
178
        }
2✔
179

180
        SECTION("for download notifications, with no data transfer ongoing") {
6✔
181
            double estimate = 0.0;
2✔
182
            progress.register_callback(
2✔
183
                [&](auto, auto, double ep) {
2✔
184
                    callback_was_called = true;
2✔
185
                    estimate = ep;
2✔
186
                },
2✔
187
                NotifierType::download, false, 0);
2✔
188
            REQUIRE(estimate == 0.0);
2!
189
            REQUIRE(callback_was_called);
2!
190
        }
2✔
191

192
        SECTION("can register another notifier while in the initial notification without deadlock") {
6✔
193
            int counter = 0;
2✔
194
            progress.register_callback(
2✔
195
                [&](auto, auto, double) {
2✔
196
                    counter++;
2✔
197
                    progress.register_callback(
2✔
198
                        [&](auto, auto, double) {
2✔
199
                            counter++;
2✔
200
                        },
2✔
201
                        NotifierType::upload, false, 0);
2✔
202
                },
2✔
203
                NotifierType::download, false, 0);
2✔
204
            REQUIRE(counter == 2);
2!
205
        }
2✔
206
    }
6✔
207

208
    SECTION("callback is invoked after each update for streaming notifiers") {
48✔
209
        progress.update(0, 0, 0, 0, 1, 0.0, 0.0, 0);
8✔
210

211
        bool callback_was_called = false;
8✔
212
        uint64_t transferred = 0;
8✔
213
        uint64_t transferrable = 0;
8✔
214
        uint64_t current_transferred = 0;
8✔
215
        uint64_t current_transferrable = 0;
8✔
216
        double estimate = 0.0;
8✔
217

218
        SECTION("for upload notifications") {
8✔
219
            progress.register_callback(
2✔
220
                [&](auto xferred, auto xferable, double ep) {
8✔
221
                    transferred = xferred;
8✔
222
                    transferrable = xferable;
8✔
223
                    callback_was_called = true;
8✔
224
                    estimate = ep;
8✔
225
                },
8✔
226
                NotifierType::upload, true, 0);
2✔
227
            REQUIRE(callback_was_called);
2!
228

229
            // Now manually call the notifier handler a few times.
230
            callback_was_called = false;
2✔
231
            current_transferred = 60;
2✔
232
            current_transferrable = 912;
2✔
233
            double current_estimate = current_transferred / double(current_transferrable);
2✔
234
            progress.update(25, 26, current_transferred, current_transferrable, 1, 25 / double(26), current_estimate,
2✔
235
                            0);
2✔
236
            CHECK(callback_was_called);
2!
237
            CHECK(transferred == current_transferred);
2!
238
            CHECK(transferrable == current_transferrable);
2!
239
            CHECK(estimate == current_estimate);
2!
240

241
            // Second callback
242
            callback_was_called = false;
2✔
243
            current_transferred = 79;
2✔
244
            current_transferrable = 1021;
2✔
245
            current_estimate = current_transferred / double(current_transferrable);
2✔
246
            progress.update(68, 191, current_transferred, current_transferrable, 1, 68 / double(191),
2✔
247
                            current_estimate, 0);
2✔
248
            CHECK(callback_was_called);
2!
249
            CHECK(transferred == current_transferred);
2!
250
            CHECK(transferrable == current_transferrable);
2!
251
            CHECK(estimate == current_estimate);
2!
252

253
            // Third callback
254
            callback_was_called = false;
2✔
255
            current_transferred = 150;
2✔
256
            current_transferrable = 1228;
2✔
257
            current_estimate = current_transferred / double(current_transferrable);
2✔
258
            progress.update(199, 591, current_transferred, current_transferrable, 1, 199 / double(591),
2✔
259
                            current_estimate, 0);
2✔
260
            CHECK(callback_was_called);
2!
261
            CHECK(transferred == current_transferred);
2!
262
            CHECK(transferrable == current_transferrable);
2!
263
            CHECK(estimate == current_estimate);
2!
264
        }
2✔
265

266
        SECTION("for download notifications") {
8✔
267
            progress.register_callback(
2✔
268
                [&](auto xferred, auto xferable, double pe) {
8✔
269
                    transferred = xferred;
8✔
270
                    transferrable = xferable;
8✔
271
                    estimate = pe;
8✔
272
                    callback_was_called = true;
8✔
273
                },
8✔
274
                NotifierType::download, true, 0);
2✔
275
            REQUIRE(callback_was_called);
2!
276

277
            // Now manually call the notifier handler a few times.
278
            callback_was_called = false;
2✔
279
            current_transferred = 60;
2✔
280
            current_transferrable = 912;
2✔
281
            progress.update(current_transferred, current_transferrable, 25, 26, 1,
2✔
282
                            current_transferred / double(current_transferrable), 1.0, 0);
2✔
283
            CHECK(callback_was_called);
2!
284
            CHECK(transferred == current_transferred);
2!
285
            CHECK(transferrable == current_transferrable);
2!
286
            CHECK(estimate == current_transferred / double(current_transferrable));
2!
287

288
            // Second callback
289
            callback_was_called = false;
2✔
290
            current_transferred = 79;
2✔
291
            current_transferrable = 1021;
2✔
292
            progress.update(current_transferred, current_transferrable, 68, 191, 1,
2✔
293
                            current_transferred / double(current_transferrable), 1.0, 0);
2✔
294
            CHECK(callback_was_called);
2!
295
            CHECK(transferred == current_transferred);
2!
296
            CHECK(transferrable == current_transferrable);
2!
297
            CHECK(estimate == current_transferred / double(current_transferrable));
2!
298

299
            // Third callback
300
            callback_was_called = false;
2✔
301
            current_transferred = 150;
2✔
302
            current_transferrable = 1228;
2✔
303
            progress.update(current_transferred, current_transferrable, 199, 591, 1,
2✔
304
                            current_transferred / double(current_transferrable), 1.0, 0);
2✔
305
            CHECK(callback_was_called);
2!
306
            CHECK(transferred == current_transferred);
2!
307
            CHECK(transferrable == current_transferrable);
2!
308
        }
2✔
309

310
        SECTION("token unregistration works") {
8✔
311
            uint64_t token = progress.register_callback(
2✔
312
                [&](auto xferred, auto xferable, double) {
4✔
313
                    transferred = xferred;
4✔
314
                    transferrable = xferable;
4✔
315
                    callback_was_called = true;
4✔
316
                },
4✔
317
                NotifierType::download, true, 0);
2✔
318
            REQUIRE(callback_was_called);
2!
319

320
            // Now manually call the notifier handler a few times.
321
            callback_was_called = false;
2✔
322
            current_transferred = 60;
2✔
323
            current_transferrable = 912;
2✔
324
            double current_estimate = current_transferred / double(current_transferrable);
2✔
325
            progress.update(current_transferred, current_transferrable, 25, 26, 1, current_estimate, 25 / double(26),
2✔
326
                            0);
2✔
327
            CHECK(callback_was_called);
2!
328
            CHECK(transferred == current_transferred);
2!
329
            CHECK(transferrable == current_transferrable);
2!
330

331
            // Unregister
332
            progress.unregister_callback(token);
2✔
333

334
            // Second callback: should not actually do anything.
335
            callback_was_called = false;
2✔
336
            current_transferred = 150;
2✔
337
            current_transferrable = 1228;
2✔
338
            current_estimate = current_transferred / double(current_transferrable);
2✔
339
            progress.update(current_transferred, current_transferrable, 199, 591, 1, current_estimate,
2✔
340
                            199 / double(591), 0);
2✔
341
            CHECK(!callback_was_called);
2!
342
        }
2✔
343

344
        SECTION("for multiple notifiers") {
8✔
345
            progress.register_callback(
2✔
346
                [&](auto xferred, auto xferable, double ep) {
6✔
347
                    transferred = xferred;
6✔
348
                    transferrable = xferable;
6✔
349
                    estimate = ep;
6✔
350
                    callback_was_called = true;
6✔
351
                },
6✔
352
                NotifierType::download, true, 0);
2✔
353
            REQUIRE(callback_was_called);
2!
354

355
            // Register a second notifier.
356
            bool callback_was_called_2 = false;
2✔
357
            uint64_t transferred_2 = 0;
2✔
358
            uint64_t transferrable_2 = 0;
2✔
359
            double upload_estimate = 0.0;
2✔
360
            progress.register_callback(
2✔
361
                [&](auto xferred, auto xferable, double ep) {
6✔
362
                    transferred_2 = xferred;
6✔
363
                    transferrable_2 = xferable;
6✔
364
                    callback_was_called_2 = true;
6✔
365
                    upload_estimate = ep;
6✔
366
                },
6✔
367
                NotifierType::upload, true, 0);
2✔
368
            REQUIRE(callback_was_called_2);
2!
369

370
            // Now manually call the notifier handler a few times.
371
            callback_was_called = false;
2✔
372
            callback_was_called_2 = false;
2✔
373
            uint64_t current_uploaded = 16;
2✔
374
            uint64_t current_uploadable = 201;
2✔
375
            uint64_t current_downloaded = 68;
2✔
376
            uint64_t current_downloadable = 182;
2✔
377
            auto current_down_estimate = current_downloaded / double(current_downloadable);
2✔
378
            auto current_up_estimate = current_uploaded / double(current_uploadable);
2✔
379
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
380
                            current_down_estimate, current_up_estimate, 0);
2✔
381
            CHECK(callback_was_called);
2!
382
            CHECK(transferred == current_downloaded);
2!
383
            CHECK(transferrable == current_downloadable);
2!
384
            CHECK(estimate == current_down_estimate);
2!
385
            CHECK(callback_was_called_2);
2!
386
            CHECK(transferred_2 == current_uploaded);
2!
387
            CHECK(transferrable_2 == current_uploadable);
2!
388
            CHECK(upload_estimate == current_up_estimate);
2!
389

390
            // Second callback
391
            callback_was_called = false;
2✔
392
            callback_was_called_2 = false;
2✔
393
            current_uploaded = 31;
2✔
394
            current_uploadable = 329;
2✔
395
            current_downloaded = 76;
2✔
396
            current_downloadable = 191;
2✔
397
            current_down_estimate = current_downloaded / double(current_downloadable);
2✔
398
            current_up_estimate = current_uploaded / double(current_uploadable);
2✔
399
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
400
                            current_down_estimate, current_up_estimate, 0);
2✔
401
            CHECK(callback_was_called);
2!
402
            CHECK(transferred == current_downloaded);
2!
403
            CHECK(transferrable == current_downloadable);
2!
404
            CHECK(estimate == current_down_estimate);
2!
405
            CHECK(callback_was_called_2);
2!
406
            CHECK(transferred_2 == current_uploaded);
2!
407
            CHECK(transferrable_2 == current_uploadable);
2!
408
            CHECK(current_up_estimate == upload_estimate);
2!
409
        }
2✔
410
    }
8✔
411

412
    SECTION("properly runs for non-streaming notifiers") {
48✔
413
        bool callback_was_called = false;
14✔
414
        uint64_t transferred = 0;
14✔
415
        uint64_t transferrable = 0;
14✔
416
        uint64_t current_transferred = 0;
14✔
417
        uint64_t current_transferrable = 0;
14✔
418
        double upload_estimate = 0;
14✔
419
        double download_estimate = 0;
14✔
420

421
        SECTION("for upload notifications") {
14✔
422
            // Prime the progress updater
423
            current_transferred = 60;
2✔
424
            current_transferrable = 501;
2✔
425
            const uint64_t original_transferrable = current_transferrable;
2✔
426
            double current_estimate = current_transferred / double(current_transferrable);
2✔
427
            progress.update(21, 26, current_transferred, current_transferrable, 1, 21 / double(26), current_estimate,
2✔
428
                            0);
2✔
429

430
            progress.register_callback(
2✔
431
                [&](auto xferred, auto xferable, double ep) {
6✔
432
                    transferred = xferred;
6✔
433
                    transferrable = xferable;
6✔
434
                    upload_estimate = ep;
6✔
435
                    callback_was_called = true;
6✔
436
                },
6✔
437
                NotifierType::upload, false, 0);
2✔
438
            REQUIRE(callback_was_called);
2!
439

440
            // Now manually call the notifier handler a few times.
441
            callback_was_called = false;
2✔
442
            current_transferred = 66;
2✔
443
            current_transferrable = 582;
2✔
444
            current_estimate = current_transferred / double(current_transferrable);
2✔
445
            progress.update(21, 26, current_transferred, current_transferrable, 1, 21 / double(26), current_estimate,
2✔
446
                            0);
2✔
447
            CHECK(callback_was_called);
2!
448
            CHECK(transferred == current_transferred);
2!
449
            CHECK(transferrable == original_transferrable);
2!
450
            CHECK(upload_estimate == current_transferred / double(original_transferrable));
2!
451

452
            // Second callback
453
            callback_was_called = false;
2✔
454
            current_transferred = original_transferrable + 100;
2✔
455
            current_transferrable = 1021;
2✔
456
            current_estimate = current_transferred / double(current_transferrable);
2✔
457
            progress.update(68, 191, current_transferred, current_transferrable, 1, 68 / double(191),
2✔
458
                            current_estimate, 0);
2✔
459
            CHECK(callback_was_called);
2!
460
            CHECK(transferred == current_transferred);
2!
461
            CHECK(transferrable == original_transferrable);
2!
462
            CHECK(upload_estimate == 1.0);
2!
463

464
            // The notifier should be unregistered at this point, and not fire.
465
            callback_was_called = false;
2✔
466
            current_transferred = original_transferrable + 250;
2✔
467
            current_transferrable = 1228;
2✔
468
            current_estimate = current_transferred / double(current_transferrable);
2✔
469
            progress.update(199, 591, current_transferred, current_transferrable, 1, 199 / double(591),
2✔
470
                            current_estimate, 0);
2✔
471
            CHECK(!callback_was_called);
2!
472
        }
2✔
473

474
        SECTION("upload notifications are not sent until all local changesets have been processed") {
14✔
475
            progress.set_local_version(4);
2✔
476

477
            progress.register_callback(
2✔
478
                [&](auto xferred, auto xferable, double) {
2✔
479
                    transferred = xferred;
2✔
480
                    transferrable = xferable;
2✔
481
                    callback_was_called = true;
2✔
482
                },
2✔
483
                NotifierType::upload, false, 0);
2✔
484
            REQUIRE_FALSE(callback_was_called);
2!
485

486
            current_transferred = 66;
2✔
487
            current_transferrable = 582;
2✔
488
            double current_estimate = current_transferred / double(current_transferrable);
2✔
489
            progress.update(0, 0, current_transferred, current_transferrable, 3, 1.0, current_estimate, 0);
2✔
490
            REQUIRE_FALSE(callback_was_called);
2!
491

492
            current_transferred = 77;
2✔
493
            current_transferrable = 1021;
2✔
494
            current_estimate = current_transferred / double(current_transferrable);
2✔
495
            progress.update(0, 0, current_transferred, current_transferrable, 4, 1.0, current_estimate, 0);
2✔
496
            REQUIRE(callback_was_called);
2!
497
            CHECK(transferred == current_transferred);
2!
498
            // should not have captured transferrable from the first update
499
            CHECK(transferrable == current_transferrable);
2!
500
            CHECK(current_estimate == current_estimate);
2!
501
        }
2✔
502

503
        SECTION("for download notifications") {
14✔
504
            // Prime the progress updater
505
            current_transferred = 60;
2✔
506
            current_transferrable = 501;
2✔
507
            double current_estimate = current_transferred / double(current_transferrable);
2✔
508
            const uint64_t original_transferrable = current_transferrable;
2✔
509
            progress.update(current_transferred, current_transferrable, 21, 26, 1, current_estimate, 21 / double(26),
2✔
510
                            0);
2✔
511

512
            progress.register_callback(
2✔
513
                [&](auto xferred, auto xferable, double ep) {
6✔
514
                    transferred = xferred;
6✔
515
                    transferrable = xferable;
6✔
516
                    download_estimate = ep;
6✔
517
                    callback_was_called = true;
6✔
518
                },
6✔
519
                NotifierType::download, false, 0);
2✔
520
            REQUIRE(callback_was_called);
2!
521

522
            // Now manually call the notifier handler a few times.
523
            callback_was_called = false;
2✔
524
            current_transferred = 66;
2✔
525
            current_transferrable = 582;
2✔
526
            current_estimate = current_transferred / double(current_transferrable);
2✔
527
            progress.update(current_transferred, current_transferrable, 25, 26, 1, current_estimate, 25 / double(26),
2✔
528
                            0);
2✔
529
            CHECK(callback_was_called);
2!
530
            CHECK(transferred == current_transferred);
2!
531
            CHECK(transferrable == original_transferrable);
2!
532
            CHECK(download_estimate == current_estimate);
2!
533

534
            // Second callback
535
            callback_was_called = false;
2✔
536
            current_transferred = original_transferrable + 100;
2✔
537
            current_transferrable = 1021;
2✔
538
            current_estimate = current_transferred / double(current_transferrable);
2✔
539
            progress.update(current_transferred, current_transferrable, 68, 191, 1, current_estimate,
2✔
540
                            68 / double(191), 0);
2✔
541
            CHECK(callback_was_called);
2!
542
            CHECK(transferred == current_transferred);
2!
543
            CHECK(transferrable == original_transferrable);
2!
544
            CHECK(download_estimate == current_estimate);
2!
545

546
            // The notifier should be unregistered at this point, and not fire.
547
            callback_was_called = false;
2✔
548
            current_transferred = original_transferrable + 250;
2✔
549
            current_transferrable = 1228;
2✔
550
            current_estimate = current_transferred / double(current_transferrable);
2✔
551
            progress.update(current_transferred, current_transferrable, 199, 591, 1, current_estimate,
2✔
552
                            199 / double(591), 0);
2✔
553
            CHECK(!callback_was_called);
2!
554
        }
2✔
555

556
        SECTION("token unregistration works") {
14✔
557
            // Prime the progress updater
558
            current_transferred = 60;
2✔
559
            current_transferrable = 501;
2✔
560
            double current_estimate = current_transferred / double(current_transferrable);
2✔
561
            const uint64_t original_transferrable = current_transferrable;
2✔
562
            progress.update(21, 26, current_transferred, current_transferrable, 1, 21 / double(26), current_estimate,
2✔
563
                            0);
2✔
564

565
            uint64_t token = progress.register_callback(
2✔
566
                [&](auto xferred, auto xferable, double ep) {
4✔
567
                    transferred = xferred;
4✔
568
                    transferrable = xferable;
4✔
569
                    upload_estimate = ep;
4✔
570
                    callback_was_called = true;
4✔
571
                },
4✔
572
                NotifierType::upload, false, 0);
2✔
573
            REQUIRE(callback_was_called);
2!
574

575
            // Now manually call the notifier handler a few times.
576
            callback_was_called = false;
2✔
577
            current_transferred = 66;
2✔
578
            current_transferrable = 912;
2✔
579
            current_estimate = current_transferred / double(current_transferrable);
2✔
580
            progress.update(25, 26, current_transferred, current_transferrable, 1, 25 / double(26), current_estimate,
2✔
581
                            0);
2✔
582
            CHECK(callback_was_called);
2!
583
            CHECK(transferred == current_transferred);
2!
584
            CHECK(transferrable == original_transferrable);
2!
585
            CHECK(upload_estimate == std::min(1.0, current_transferred / double(original_transferrable)));
2!
586

587
            // Unregister
588
            progress.unregister_callback(token);
2✔
589

590
            // Second callback: should not actually do anything.
591
            callback_was_called = false;
2✔
592
            current_transferred = 67;
2✔
593
            current_transferrable = 1228;
2✔
594
            current_estimate = current_transferred / double(current_transferrable);
2✔
595
            progress.update(199, 591, current_transferred, current_transferrable, 1, 199 / double(591),
2✔
596
                            current_estimate, 0);
2✔
597
            CHECK(!callback_was_called);
2!
598
        }
2✔
599

600
        SECTION("for multiple notifiers, different directions") {
14✔
601
            // Prime the progress updater
602
            uint64_t current_uploaded = 16;
2✔
603
            uint64_t current_uploadable = 201;
2✔
604
            uint64_t current_downloaded = 68;
2✔
605
            uint64_t current_downloadable = 182;
2✔
606
            const uint64_t original_uploadable = current_uploadable;
2✔
607
            const uint64_t original_downloadable = current_downloadable;
2✔
608
            double current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
609
            double current_download_estimate = current_downloaded / double(current_downloadable);
2✔
610
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
611
                            current_download_estimate, current_upload_estimate, 0);
2✔
612

613
            progress.register_callback(
2✔
614
                [&](auto xferred, auto xferable, double ep) {
6✔
615
                    transferred = xferred;
6✔
616
                    transferrable = xferable;
6✔
617
                    upload_estimate = ep;
6✔
618
                    callback_was_called = true;
6✔
619
                },
6✔
620
                NotifierType::upload, false, 0);
2✔
621
            REQUIRE(callback_was_called);
2!
622

623
            // Register a second notifier.
624
            bool callback_was_called_2 = false;
2✔
625
            uint64_t downloaded = 0;
2✔
626
            uint64_t downloadable = 0;
2✔
627
            progress.register_callback(
2✔
628
                [&](auto xferred, auto xferable, double ep) {
8✔
629
                    downloaded = xferred;
8✔
630
                    downloadable = xferable;
8✔
631
                    download_estimate = ep;
8✔
632
                    callback_was_called_2 = true;
8✔
633
                },
8✔
634
                NotifierType::download, false, 0);
2✔
635
            REQUIRE(callback_was_called_2);
2!
636

637
            // Now manually call the notifier handler a few times.
638
            callback_was_called = false;
2✔
639
            callback_was_called_2 = false;
2✔
640
            current_uploaded = 36;
2✔
641
            current_uploadable = 310;
2✔
642
            current_downloaded = 171;
2✔
643
            current_downloadable = 185;
2✔
644
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
645
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
646
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
647
                            current_download_estimate, current_upload_estimate, 0);
2✔
648
            CHECK(callback_was_called);
2!
649
            CHECK(transferred == current_uploaded);
2!
650
            CHECK(transferrable == original_uploadable);
2!
651
            CHECK(callback_was_called_2);
2!
652
            CHECK(downloaded == current_downloaded);
2!
653
            CHECK(downloadable == original_downloadable);
2!
654
            CHECK(upload_estimate == std::min(1.0, current_uploaded / double(original_uploadable)));
2!
655
            CHECK(download_estimate == current_download_estimate);
2!
656

657
            // Second callback, last one for the upload notifier
658
            callback_was_called = false;
2✔
659
            callback_was_called_2 = false;
2✔
660
            current_uploaded = 218;
2✔
661
            current_uploadable = 310;
2✔
662
            current_downloaded = 174;
2✔
663
            current_downloadable = 190;
2✔
664
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
665
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
666
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
667
                            current_download_estimate, current_upload_estimate, 0);
2✔
668
            CHECK(callback_was_called);
2!
669
            CHECK(transferred == current_uploaded);
2!
670
            CHECK(transferrable == original_uploadable);
2!
671
            CHECK(callback_was_called_2);
2!
672
            CHECK(downloaded == current_downloaded);
2!
673
            CHECK(downloadable == original_downloadable);
2!
674
            CHECK(upload_estimate == std::min(1.0, current_uploaded / double(original_uploadable)));
2!
675
            CHECK(download_estimate == current_download_estimate);
2!
676

677
            // Third callback, last one for the download notifier
678
            callback_was_called = false;
2✔
679
            callback_was_called_2 = false;
2✔
680
            current_uploaded = 218;
2✔
681
            current_uploadable = 310;
2✔
682
            current_downloaded = 182;
2✔
683
            current_downloadable = 196;
2✔
684
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
685
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
686
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
687
                            current_download_estimate, current_upload_estimate, 0);
2✔
688
            CHECK(!callback_was_called);
2!
689
            CHECK(callback_was_called_2);
2!
690
            CHECK(downloaded == current_downloaded);
2!
691
            CHECK(downloadable == original_downloadable);
2!
692
            CHECK(upload_estimate == std::min(1.0, current_uploaded / double(original_uploadable)));
2!
693
            CHECK(download_estimate == current_download_estimate);
2!
694

695
            // Fourth callback, last one for the download notifier
696
            callback_was_called_2 = false;
2✔
697
            current_uploaded = 220;
2✔
698
            current_uploadable = 410;
2✔
699
            current_downloaded = 192;
2✔
700
            current_downloadable = 591;
2✔
701
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
702
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
703
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
704
                            current_download_estimate, current_upload_estimate, 0);
2✔
705
            CHECK(!callback_was_called);
2!
706
            CHECK(!callback_was_called_2);
2!
707
        }
2✔
708

709
        SECTION("for multiple notifiers, same direction") {
14✔
710
            // Prime the progress updater
711
            uint64_t current_uploaded = 16;
2✔
712
            uint64_t current_uploadable = 201;
2✔
713
            uint64_t current_downloaded = 68;
2✔
714
            uint64_t current_downloadable = 182;
2✔
715
            double current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
716
            double current_download_estimate = current_downloaded / double(current_downloadable);
2✔
717

718
            const uint64_t original_downloadable = current_downloadable;
2✔
719
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
720
                            current_download_estimate, current_upload_estimate, 0);
2✔
721

722
            progress.register_callback(
2✔
723
                [&](auto xferred, auto xferable, double ep) {
6✔
724
                    transferred = xferred;
6✔
725
                    transferrable = xferable;
6✔
726
                    download_estimate = ep;
6✔
727
                    callback_was_called = true;
6✔
728
                },
6✔
729
                NotifierType::download, false, 0);
2✔
730
            REQUIRE(callback_was_called);
2!
731

732
            // Now manually call the notifier handler a few times.
733
            callback_was_called = false;
2✔
734
            current_uploaded = 36;
2✔
735
            current_uploadable = 310;
2✔
736
            current_downloaded = 171;
2✔
737
            current_downloadable = 185;
2✔
738
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
739
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
740

741
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
742
                            current_download_estimate, current_upload_estimate, 0);
2✔
743
            CHECK(callback_was_called);
2!
744
            CHECK(transferred == current_downloaded);
2!
745
            CHECK(transferrable == original_downloadable);
2!
746

747
            // Register a second notifier.
748
            bool callback_was_called_2 = false;
2✔
749
            uint64_t downloaded = 0;
2✔
750
            uint64_t downloadable = 0;
2✔
751
            const uint64_t original_downloadable_2 = current_downloadable;
2✔
752
            progress.register_callback(
2✔
753
                [&](auto xferred, auto xferable, double ep) {
6✔
754
                    downloaded = xferred;
6✔
755
                    downloadable = xferable;
6✔
756
                    download_estimate = ep;
6✔
757
                    callback_was_called_2 = true;
6✔
758
                },
6✔
759
                NotifierType::download, false, 0);
2✔
760
            REQUIRE(callback_was_called_2);
2!
761

762
            // Second callback, last one for first notifier
763
            callback_was_called = false;
2✔
764
            callback_was_called_2 = false;
2✔
765
            current_uploaded = 36;
2✔
766
            current_uploadable = 310;
2✔
767
            current_downloaded = 182;
2✔
768
            current_downloadable = 190;
2✔
769
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
770
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
771
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
772
                            current_download_estimate, current_upload_estimate, 0);
2✔
773
            CHECK(callback_was_called);
2!
774
            CHECK(transferred == current_downloaded);
2!
775
            CHECK(transferrable == original_downloadable);
2!
776
            CHECK(callback_was_called_2);
2!
777
            CHECK(downloaded == current_downloaded);
2!
778
            CHECK(downloadable == original_downloadable_2);
2!
779
            CHECK(download_estimate == current_download_estimate);
2!
780

781
            // Third callback, last one for second notifier
782
            callback_was_called = false;
2✔
783
            callback_was_called_2 = false;
2✔
784
            current_uploaded = 36;
2✔
785
            current_uploadable = 310;
2✔
786
            current_downloaded = 189;
2✔
787
            current_downloadable = 250;
2✔
788
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
789
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
790
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
791
                            current_download_estimate, current_upload_estimate, 0);
2✔
792
            CHECK(!callback_was_called);
2!
793
            CHECK(callback_was_called_2);
2!
794
            CHECK(downloaded == current_downloaded);
2!
795
            CHECK(downloadable == original_downloadable_2);
2!
796
            CHECK(download_estimate == current_download_estimate);
2!
797

798
            // Fourth callback
799
            callback_was_called_2 = false;
2✔
800
            current_uploaded = 36;
2✔
801
            current_uploadable = 310;
2✔
802
            current_downloaded = 201;
2✔
803
            current_downloadable = 289;
2✔
804
            current_upload_estimate = current_uploaded / double(current_uploadable);
2✔
805
            current_download_estimate = current_downloaded / double(current_downloadable);
2✔
806
            progress.update(current_downloaded, current_downloadable, current_uploaded, current_uploadable, 1,
2✔
807
                            current_download_estimate, current_upload_estimate, 0);
2✔
808
            CHECK(!callback_was_called_2);
2!
809
        }
2✔
810

811
        SECTION("download notifiers handle transferrable decreasing") {
14✔
812
            // Prime the progress updater
813
            current_transferred = 60;
2✔
814
            current_transferrable = 501;
2✔
815
            const uint64_t original_transferrable = current_transferrable;
2✔
816
            double current_estimate = current_transferred / double(current_transferrable);
2✔
817
            progress.update(current_transferred, current_transferrable, 21, 26, 1, current_estimate, 21 / double(26),
2✔
818
                            0);
2✔
819

820
            progress.register_callback(
2✔
821
                [&](auto xferred, auto xferable, double ep) {
8✔
822
                    transferred = xferred;
8✔
823
                    transferrable = xferable;
8✔
824
                    callback_was_called = true;
8✔
825
                    download_estimate = ep;
8✔
826
                },
8✔
827
                NotifierType::download, false, 0);
2✔
828
            REQUIRE(callback_was_called);
2!
829

830
            // Download some data but also drop the total. transferrable should
831
            // update because it decreased.
832
            callback_was_called = false;
2✔
833
            current_transferred = 160;
2✔
834
            current_transferrable = 451;
2✔
835
            current_estimate = current_transferred / double(current_transferrable);
2✔
836
            progress.update(current_transferred, current_transferrable, 25, 26, 1, current_estimate, 26 / double(26),
2✔
837
                            0);
2✔
838
            CHECK(callback_was_called);
2!
839
            CHECK(transferred == current_transferred);
2!
840
            CHECK(transferrable == current_transferrable);
2!
841
            CHECK(current_estimate == download_estimate);
2!
842

843
            // Increasing current_transferrable should not increase transferrable
844
            const uint64_t previous_transferrable = current_transferrable;
2✔
845
            callback_was_called = false;
2✔
846
            current_transferrable = 1000;
2✔
847
            current_estimate = current_transferred / double(current_transferrable);
2✔
848
            progress.update(current_transferred, current_transferrable, 68, 191, 1, current_estimate,
2✔
849
                            68 / double(191), 0);
2✔
850
            CHECK(callback_was_called);
2!
851
            CHECK(transferred == current_transferred);
2!
852
            CHECK(transferrable == previous_transferrable);
2!
853
            CHECK(download_estimate == current_estimate);
2!
854

855
            // Transferrable dropping to be equal to transferred should notify
856
            // and then expire the notifier
857
            callback_was_called = false;
2✔
858
            current_transferred = 200;
2✔
859
            current_transferrable = current_transferred;
2✔
860
            current_estimate = current_transferred / double(current_transferrable);
2✔
861
            progress.update(current_transferred, current_transferrable, 191, 192, 1, current_estimate,
2✔
862
                            191 / double(192), 0);
2✔
863
            CHECK(callback_was_called);
2!
864
            CHECK(transferred == current_transferred);
2!
865
            CHECK(transferrable == current_transferred);
2!
866
            CHECK(current_estimate == download_estimate);
2!
867

868
            // The notifier should be unregistered at this point, and not fire.
869
            callback_was_called = false;
2✔
870
            current_transferred = original_transferrable + 250;
2✔
871
            current_transferrable = 1228;
2✔
872
            current_estimate = current_transferred / double(current_transferrable);
2✔
873

874
            progress.update(current_transferred, current_transferrable, 199, 591, 1, current_estimate,
2✔
875
                            199 / double(591), 0);
2✔
876
            CHECK(!callback_was_called);
2!
877
        }
2✔
878
    }
14✔
879

880
    SECTION("flx streaming notifiers") {
48✔
881
        // clang-format off
882
        TestValues test_values = GENERATE(
10✔
883
            // registers at the begining and should see all entries.
884
            TestValues{{
10✔
885
                TestInputValue{TestInputValue::IsRegistration{}},
10✔
886
                TestInputValue{0, 0, 0, 0},
10✔
887
                TestInputValue{0, 1, 200, 200},
10✔
888
                TestInputValue{1, 0.2, 300, 600},
10✔
889
                TestInputValue{1, 0.4, 400, 600},
10✔
890
                TestInputValue{1, 0.8, 600, 700},
10✔
891
                TestInputValue{1, 1, 700, 700},
10✔
892
                TestInputValue{2, 0.3, 800, 1000},
10✔
893
                TestInputValue{2, 0.6, 900, 1000},
10✔
894
                TestInputValue{2, 1, 1000, 1000},
10✔
895
            }, {
10✔
896
                ProgressEntry{0, 0, 0},
10✔
897
                ProgressEntry{200, 200, 1},
10✔
898
                ProgressEntry{300, 600, 0.2},
10✔
899
                ProgressEntry{400, 600, 0.4},
10✔
900
                ProgressEntry{600, 700, 0.8},
10✔
901
                ProgressEntry{700, 700, 1},
10✔
902
                ProgressEntry{800, 1000, 0.3},
10✔
903
                ProgressEntry{900, 1000, 0.6},
10✔
904
                ProgressEntry{1000, 1000, 1},
10✔
905
            }, 1},
10✔
906
            // registers in the middle of the initial download
907
            TestValues{{
10✔
908
                TestInputValue{1, 0.2, 300, 600},
10✔
909
                TestInputValue{1, 0.4, 400, 600},
10✔
910
                TestInputValue{TestInputValue::IsRegistration{}},
10✔
911
                TestInputValue{1, 0.8, 600, 700},
10✔
912
                TestInputValue{1, 1, 700, 700},
10✔
913
            }, {
10✔
914
                ProgressEntry{400, 600, 0.4},
10✔
915
                ProgressEntry{600, 700, 0.8},
10✔
916
                ProgressEntry{700, 700, 1.0},
10✔
917
            }, 1},
10✔
918
            // registers for a query version that's already up-to-date - should get an immediate update
919
            // with a progress estimate of 1 and whatever the current transferred/transferrable numbers are
920
            TestValues{{
10✔
921
                TestInputValue{2, 0.5, 800, 900},
10✔
922
                TestInputValue{2, 1, 900, 900},
10✔
923
                TestInputValue{TestInputValue::IsRegistration{}},
10✔
924
                TestInputValue{2, 1, 1000, 1000}
10✔
925
            }, {
10✔
926
                ProgressEntry{900, 900, 1},
10✔
927
                ProgressEntry{1000, 1000, 1},
10✔
928
            }, 1},
10✔
929
            // new subscription is added after registration which results in more data being downloaded
930
            TestValues{{
10✔
931
                TestInputValue{2, 1, 900, 900},
10✔
932
                TestInputValue{TestInputValue::IsRegistration{}},
10✔
933
                TestInputValue{3, 0, 900, 1000},
10✔
934
                TestInputValue{3, 1, 1000, 1000}
10✔
935
            }, {
10✔
936
                ProgressEntry{900, 900, 1},
10✔
937
                ProgressEntry{900, 1000, 0},
10✔
938
                ProgressEntry{1000, 1000, 1},
10✔
939
            }, 1},
10✔
940
            // new subscription is added after registration which doesn't result in more data being downloaded
941
            TestValues{{
10✔
942
                TestInputValue{2, 1, 900, 900},
10✔
943
                TestInputValue{TestInputValue::IsRegistration{}},
10✔
944
                TestInputValue{3, 0, 900, 900},
10✔
945
                TestInputValue{3, 1, 900, 900}
10✔
946
            }, {
10✔
947
                ProgressEntry{900, 900, 1},
10✔
948
                ProgressEntry{900, 900, 0},
10✔
949
                ProgressEntry{900, 900, 1},
10✔
950
            }, 1}
10✔
951
        );
10✔
952
        // clang-format on
953

954
        auto logger = util::Logger::get_default_logger();
10✔
955
        auto progress_output = util::make_bind<WaitableProgress>(logger, "flx non-streaming download");
10✔
956

957
        uint64_t snapshot = 1;
10✔
958
        for (const auto& input_val : test_values.input_values) {
54✔
959
            if (input_val.is_registration) {
54✔
960
                progress.register_callback(progress_output->make_cb(), NotifierType::download, true,
10✔
961
                                           test_values.registered_at_query_version);
10✔
962
                continue;
10✔
963
            }
10✔
964
            progress.update(input_val.transferred, input_val.transferrable, 0, 0, ++snapshot, input_val.cur_estimate,
44✔
965
                            0.0, input_val.query_version);
44✔
966
        }
44✔
967

968
        const auto output_values = progress_output->wait_for_full_sync();
10✔
969

970
        REQUIRE_THAT(output_values, Catch::Matchers::Equals(test_values.expected_values));
10✔
971
    }
10✔
972

973
    SECTION("flx non-streaming notifiers") {
48✔
974
        // clang-format off
975
        TestValues test_values = GENERATE(
8✔
976
            // registers for query version 1 on an empty realm - should see the full progression
977
            // of query version 1 and nothing else.
978
            TestValues{{
8✔
979
                TestInputValue{TestInputValue::IsRegistration{}},
8✔
980
                TestInputValue{0, 0, 0, 0},
8✔
981
                TestInputValue{0, 1, 200, 200},
8✔
982
                TestInputValue{1, 0.2, 300, 600},
8✔
983
                TestInputValue{1, 0.4, 400, 600},
8✔
984
                TestInputValue{1, 0.8, 600, 700},
8✔
985
                TestInputValue{1, 1, 700, 700},
8✔
986
                TestInputValue{2, 0.3, 800, 1000},
8✔
987
                TestInputValue{2, 0.6, 900, 1000},
8✔
988
                TestInputValue{2, 1, 1000, 1000},
8✔
989
            }, {
8✔
990
                ProgressEntry{300, 600, 0.2},
8✔
991
                ProgressEntry{400, 600, 0.4},
8✔
992
                ProgressEntry{600, 600, 0.8},
8✔
993
                ProgressEntry{700, 600, 1.0},
8✔
994
            }, 1},
8✔
995
            // registers a notifier in the middle of syncing the target query version
996
            TestValues{{
8✔
997
                TestInputValue{1, 0.2, 300, 600},
8✔
998
                TestInputValue{1, 0.4, 400, 600},
8✔
999
                TestInputValue{TestInputValue::IsRegistration{}},
8✔
1000
                TestInputValue{1, 0.8, 600, 700},
8✔
1001
                TestInputValue{1, 1, 700, 700},
8✔
1002
                // There's also a progress notification for a regular steady state
1003
                // download message that gets ignored because we're already up-to-date
1004
                TestInputValue{1, 1, 800, 800},
8✔
1005
            }, {
8✔
1006
                ProgressEntry{400, 600, 0.4},
8✔
1007
                ProgressEntry{600, 600, 0.8},
8✔
1008
                ProgressEntry{700, 600, 1.0},
8✔
1009
            }, 1},
8✔
1010
            // registers for a notifier for a later query version - should only see notifications
1011
            // for downloads greater than the requested query version
1012
            TestValues{{
8✔
1013
                TestInputValue{TestInputValue::IsRegistration{}},
8✔
1014
                TestInputValue{1, 0.8, 700, 700},
8✔
1015
                TestInputValue{1, 1, 700, 700},
8✔
1016
                TestInputValue{3, 0.5, 800, 900},
8✔
1017
                TestInputValue{3, 1, 900, 900},
8✔
1018
            }, {
8✔
1019
                ProgressEntry{800, 900, 0.5},
8✔
1020
                ProgressEntry{900, 900, 1},
8✔
1021
            }, 2},
8✔
1022
            // registers for a query version that's already up-to-date - should get an immediate update
1023
            // with a progress estimate of 1 and whatever the current transferred/transferrable numbers are
1024
            TestValues{{
8✔
1025
                TestInputValue{2, 0.5, 800, 900},
8✔
1026
                TestInputValue{2, 1, 900, 900},
8✔
1027
                TestInputValue{TestInputValue::IsRegistration{}},
8✔
1028
            }, {
8✔
1029
                ProgressEntry{900, 900, 1},
8✔
1030
            }, 1}
8✔
1031
        );
8✔
1032
        // clang-format on
1033

1034
        auto logger = util::Logger::get_default_logger();
8✔
1035
        auto progress_output = util::make_bind<WaitableProgress>(logger, "flx non-streaming download");
8✔
1036

1037
        uint64_t snapshot = 1;
8✔
1038
        for (const auto& input_val : test_values.input_values) {
48✔
1039
            if (input_val.is_registration) {
48✔
1040
                progress.register_callback(progress_output->make_cb(), NotifierType::download, false,
8✔
1041
                                           test_values.registered_at_query_version);
8✔
1042
                continue;
8✔
1043
            }
8✔
1044
            progress.update(input_val.transferred, input_val.transferrable, 0, 0, ++snapshot, input_val.cur_estimate,
40✔
1045
                            0.0, input_val.query_version);
40✔
1046
        }
40✔
1047

1048
        const auto output_values = progress_output->wait_for_full_sync();
8✔
1049

1050
        REQUIRE_THAT(output_values, Catch::Matchers::Equals(test_values.expected_values));
8✔
1051
    }
8✔
1052
}
48✔
1053

1054
#if REALM_ENABLE_AUTH_TESTS
1055

1056
struct TestSetup {
1057
    TableRef get_table(const SharedRealm& r)
1058
    {
26✔
1059
        return r->read_group().get_table("class_" + table_name);
26✔
1060
    }
26✔
1061

1062
    size_t add_objects(SharedRealm& r, int num)
1063
    {
16✔
1064
        CppContext ctx(r);
16✔
1065
        for (int i = 0; i < num; ++i) {
116✔
1066
            // use specifically separate transactions for a bit of history
1067
            r->begin_transaction();
100✔
1068
            Object::create(ctx, r, StringData(table_name), std::any(make_one(i)));
100✔
1069
            r->commit_transaction();
100✔
1070
        }
100✔
1071
        return get_table(r)->size();
16✔
1072
    }
16✔
1073

1074
    virtual SyncTestFile make_config() = 0;
1075
    virtual AnyDict make_one(int64_t idx) = 0;
1076

1077
    std::string table_name;
1078
};
1079

1080
struct PBS : TestSetup {
1081
    PBS()
1082
    {
8✔
1083
        table_name = "Dog";
8✔
1084
    }
8✔
1085

1086
    SyncTestFile make_config() override
1087
    {
10✔
1088
        return SyncTestFile(session.app()->current_user(), partition, get_default_schema());
10✔
1089
    }
10✔
1090

1091
    AnyDict make_one(int64_t /* idx */) override
1092
    {
50✔
1093
        return AnyDict{{"_id", std::any(ObjectId::gen())},
50✔
1094
                       {"breed", std::string("bulldog")},
50✔
1095
                       {"name", random_string(1024 * 1024)}};
50✔
1096
    }
50✔
1097

1098
    TestAppSession session;
1099
    const std::string partition = random_string(100);
1100
};
1101

1102
struct FLX : TestSetup {
1103
    FLX(const std::string& app_id = "flx_sync_progress")
1104
        : harness(app_id)
4✔
1105
    {
8✔
1106
        table_name = harness.schema().begin()->name;
8✔
1107
    }
8✔
1108

1109
    SyncTestFile make_config() override
1110
    {
10✔
1111
        auto config = harness.make_test_file();
10✔
1112
        add_subscription(*config.sync_config);
10✔
1113
        return config;
10✔
1114
    }
10✔
1115

1116
    void add_subscription(SyncConfig& config)
1117
    {
10✔
1118
        config.rerun_init_subscription_on_open = true;
10✔
1119
        config.subscription_initializer = [&](SharedRealm&& realm) {
10✔
1120
            add_subscription(realm);
10✔
1121
        };
10✔
1122
    }
10✔
1123

1124
    void add_subscription(SharedRealm& realm)
1125
    {
10✔
1126
        auto sub = realm->get_latest_subscription_set().make_mutable_copy();
10✔
1127
        sub.insert_or_assign(Query(get_table(realm)));
10✔
1128
        sub.commit();
10✔
1129
    }
10✔
1130

1131
    AnyDict make_one(int64_t idx) override
1132
    {
50✔
1133
        return AnyDict{{"_id", ObjectId::gen()},
50✔
1134
                       {"queryable_int_field", idx},
50✔
1135
                       {"queryable_str_field", random_string(1024 * 1024)}};
50✔
1136
    }
50✔
1137

1138
    FLXSyncTestHarness harness;
1139
};
1140

1141
struct ProgressIncreasesMatcher : Catch::Matchers::MatcherGenericBase {
1142
    enum MatchMode { ByteCountOnly, All };
1143
    ProgressIncreasesMatcher() = default;
16✔
1144
    explicit ProgressIncreasesMatcher(MatchMode mode)
1145
        : m_mode(mode)
4✔
1146
    {
8✔
1147
    }
8✔
1148

1149
    bool match(std::vector<ProgressEntry> const& entries) const
1150
    {
24✔
1151
        if (entries.size() < 1) {
24✔
1152
            return false;
×
1153
        }
×
1154

1155
        auto last = std::ref(entries.front());
24✔
1156
        for (size_t i = 1; i < entries.size(); ++i) {
165✔
1157
            ProgressEntry const& cur = entries[i];
141✔
1158
            if (cur.transferred < last.get().transferred) {
141✔
1159
                return false;
×
1160
            }
×
1161
            if (m_mode == All && cur.estimate < last.get().estimate) {
141✔
1162
                return false;
×
1163
            }
×
1164
            last = cur;
141✔
1165
        }
141✔
1166
        return true;
24✔
1167
    }
24✔
1168

1169
    std::string describe() const override
1170
    {
×
1171
        return "progress notifications all increase";
×
1172
    }
×
1173

1174
private:
1175
    MatchMode m_mode = All;
1176
};
1177

1178
TEMPLATE_TEST_CASE("progress notifications fire immediately when fully caught up", "[baas][progress][sync]", PBS, FLX)
1179
{
12✔
1180
    TestType pbs_setup;
12✔
1181
    auto logger = util::Logger::get_default_logger();
12✔
1182

1183
    auto validate_noop_entry = [&](const std::vector<ProgressEntry>& entries, std::string context) {
12✔
1184
        UNSCOPED_INFO("validating noop non-streaming entry " << context);
12✔
1185
        REQUIRE(entries.size() == 1);
12!
1186
        const auto& entry = entries.front();
12✔
1187
        REQUIRE(entry.transferred >= entry.transferrable);
12!
1188
        REQUIRE(entry.estimate >= 1.0);
12!
1189
    };
12✔
1190

1191
    SECTION("empty async open results in progress notification") {
12✔
1192
        auto config = pbs_setup.make_config();
4✔
1193
        auto async_open_task = Realm::get_synchronized_realm(config);
4✔
1194
        auto async_open_progress = util::make_bind<WaitableProgress>(logger, "async open non-streaming progress ");
4✔
1195
        async_open_task->register_download_progress_notifier(async_open_progress->make_cb());
4✔
1196
        auto future = async_open_task->start();
4✔
1197
        auto realm = Realm::get_shared_realm(std::move(future).get());
4✔
1198
        auto noop_download_progress = util::make_bind<WaitableProgress>(logger, "non-streaming download ");
4✔
1199
        auto noop_token = realm->sync_session()->register_progress_notifier(
4✔
1200
            noop_download_progress->make_cb(), SyncSession::ProgressDirection::download, false);
4✔
1201
        // The registration token for a non-streaming notifier that was expired at registration time
1202
        // is zero because it's invoked immediately and never registered for further notifications.
1203
        CHECK(noop_token == 0);
4!
1204

1205
        auto async_open_entries = async_open_progress->wait_for_full_sync();
4✔
1206
        REQUIRE_THAT(async_open_entries, ProgressIncreasesMatcher{});
4✔
1207
        validate_noop_entry(noop_download_progress->wait_for_full_sync(), "noop_download_progress");
4✔
1208
    }
4✔
1209

1210
    SECTION("synchronous open then waiting for download then noop notification") {
12✔
1211
        {
4✔
1212
            auto fill_data_config = pbs_setup.make_config();
4✔
1213
            auto fill_data_realm = Realm::get_shared_realm(fill_data_config);
4✔
1214
            pbs_setup.add_objects(fill_data_realm, 5);
4✔
1215
            wait_for_upload(*fill_data_realm);
4✔
1216
        }
4✔
1217

1218
        auto config = pbs_setup.make_config();
4✔
1219
        auto realm = Realm::get_shared_realm(config);
4✔
1220
        auto initial_progress = util::make_bind<WaitableProgress>(logger, "streaming initial progress ");
4✔
1221
        realm->sync_session()->register_progress_notifier(initial_progress->make_cb(), NotifierType::download, true);
4✔
1222

1223
        auto initial_entries = initial_progress->wait_for_full_sync();
4✔
1224
        REQUIRE(!initial_entries.empty());
4!
1225
        REQUIRE_THAT(initial_entries, ProgressIncreasesMatcher{});
4✔
1226

1227
        auto noop_download_progress = util::make_bind<WaitableProgress>(logger, "non-streaming noop download ");
4✔
1228
        auto noop_token = realm->sync_session()->register_progress_notifier(
4✔
1229
            noop_download_progress->make_cb(), SyncSession::ProgressDirection::download, false);
4✔
1230
        // The registration token for a non-streaming notifier that was expired at registration time
1231
        // is zero because it's invoked immediately and never registered for further notifications.
1232
        CHECK(noop_token == 0);
4!
1233

1234
        validate_noop_entry(noop_download_progress->wait_for_full_sync(), "noop_download_progress");
4✔
1235
    }
4✔
1236

1237
    SECTION("uploads") {
12✔
1238
        auto config = pbs_setup.make_config();
4✔
1239
        auto realm = Realm::get_shared_realm(config);
4✔
1240
        auto initial_progress = util::make_bind<WaitableProgress>(logger, "non-streaming initial progress ");
4✔
1241

1242
        pbs_setup.add_objects(realm, 5);
4✔
1243

1244
        auto token = realm->sync_session()->register_progress_notifier(initial_progress->make_cb(),
4✔
1245
                                                                       NotifierType::upload, false);
4✔
1246
        auto initial_entries = initial_progress->wait_for_full_sync();
4✔
1247
        REQUIRE(!initial_entries.empty());
4!
1248
        REQUIRE_THAT(initial_entries, ProgressIncreasesMatcher{});
4✔
1249
        realm->sync_session()->unregister_progress_notifier(token);
4✔
1250

1251
        // it's possible that we've reached full synchronization in the progress notifier, but because
1252
        // of the way non-streaming notifiers work, the transferable may be higher for the next
1253
        // non-streaming notifier than for the one that just finished. So we explicitly wait for
1254
        // all uploads to complete to check that registering a noop notifier here is actually a noop.
1255
        wait_for_upload(*realm);
4✔
1256

1257
        auto noop_upload_progress = util::make_bind<WaitableProgress>(logger, "non-streaming upload ");
4✔
1258
        auto noop_token = realm->sync_session()->register_progress_notifier(
4✔
1259
            noop_upload_progress->make_cb(), SyncSession::ProgressDirection::upload, false);
4✔
1260
        // The registration token for a non-streaming notifier that was expired at registration time
1261
        // is zero because it's invoked immediately and never registered for further notifications.
1262
        CHECK(noop_token == 0);
4!
1263

1264
        validate_noop_entry(noop_upload_progress->wait_for_full_sync(), "noop_upload_progress");
4✔
1265
    }
4✔
1266
}
12✔
1267

1268

1269
TEMPLATE_TEST_CASE("sync progress: upload progress", "[sync][baas][progress]", PBS, FLX)
1270
{
4✔
1271
    TestType setup;
4✔
1272

1273
    auto realm = Realm::get_shared_realm(setup.make_config());
4✔
1274
    auto sync_session = realm->sync_session();
4✔
1275
    auto logger = util::Logger::get_default_logger();
4✔
1276
    auto non_streaming_progress = util::make_bind<WaitableProgress>(logger, "non-streaming upload ");
4✔
1277
    auto streaming_progress = util::make_bind<WaitableProgress>(logger, "streaming upload ");
4✔
1278

1279
    // There is a race between creating the objects and registering the non-streaming notifier
1280
    // since
1281
    sync_session->pause();
4✔
1282

1283
    setup.add_objects(realm, 10);
4✔
1284
    sync_session->register_progress_notifier(non_streaming_progress->make_cb(), NotifierType::upload, false);
4✔
1285
    sync_session->register_progress_notifier(streaming_progress->make_cb(), NotifierType::upload, true);
4✔
1286

1287
    sync_session->resume();
4✔
1288
    wait_for_upload(*realm);
4✔
1289

1290
    auto streaming_entries = streaming_progress->wait_for_full_sync();
4✔
1291
    auto non_streaming_entries = non_streaming_progress->wait_for_full_sync();
4✔
1292

1293
    REQUIRE(!streaming_entries.empty());
4!
1294
    REQUIRE(!non_streaming_entries.empty());
4!
1295
    REQUIRE_THAT(non_streaming_entries, ProgressIncreasesMatcher{});
4✔
1296
    REQUIRE_THAT(streaming_entries, ProgressIncreasesMatcher{ProgressIncreasesMatcher::ByteCountOnly});
4✔
1297

1298
    setup.add_objects(realm, 5);
4✔
1299
    wait_for_upload(*realm);
4✔
1300

1301
    streaming_entries = streaming_progress->wait_for_full_sync();
4✔
1302
    REQUIRE_THAT(streaming_entries, ProgressIncreasesMatcher{ProgressIncreasesMatcher::ByteCountOnly});
4✔
1303
    REQUIRE(non_streaming_progress->empty());
4!
1304
}
4✔
1305

1306
namespace {
1307
struct EstimatesAreValid : Catch::Matchers::MatcherGenericBase {
1308
    size_t initial_object_count = 0;
1309
    EstimatesAreValid(size_t initial_count = 0)
1310
        : initial_object_count(initial_count)
6✔
1311
    {
12✔
1312
    }
12✔
1313

1314
    bool match(std::vector<double> const& entries) const
1315
    {
12✔
1316
        // Download progress should always end with an estimate of 1
1317
        if (entries.empty() || entries.back() != 1)
12✔
NEW
1318
            return false;
×
1319

1320
        // All estimates should be between 0 and 1
1321
        for (double estimate : entries) {
38✔
1322
            if (estimate < 0 || estimate > 1)
38✔
NEW
1323
                return false;
×
1324
        }
38✔
1325

1326
        // The server will sometimes send us the final non-empty DOWNLOAD with
1327
        // an estimate of 0.9999 and then an empty DOWNLOAD with 1. We can use
1328
        // exact equality here because it's a specific sentinel value and not
1329
        // the result of a computation.
1330
        size_t size = entries.size();
12✔
1331
        if (size >= 2 && entries[size - 2] == 0.9999)
12✔
1332
            --size;
6✔
1333
        if (size == 1)
12✔
1334
            return true;
4✔
1335

1336
        // The actual progress for the first message should be the number of
1337
        // objects downloaded divided by the total number of objects, but in
1338
        // practice the server starts with a lower estimate so that's only an
1339
        // upper bound.
1340
        double expected_first = double(initial_object_count + 1) / (initial_object_count + size);
8✔
1341
        if (entries.front() > expected_first + .01)
8✔
NEW
1342
            return false;
×
1343

1344
        // As each of our DOWNLOAD messages have a fixed size, the progress
1345
        // estimate should go up by the same amount each time.
1346
        double expected_step = (1.0 - entries.front()) / (size - 1);
8✔
1347
        for (size_t i = 1; i < size; ++i) {
28✔
1348
            double expected = entries.front() + i * expected_step;
20✔
1349
            if (!WithinRel(entries[i], 0.1).match(expected)) {
20✔
NEW
1350
                return false;
×
NEW
1351
            }
×
1352
        }
20✔
1353
        return true;
8✔
1354
    }
8✔
1355

1356
    std::string describe() const override
NEW
1357
    {
×
NEW
1358
        return "estimated progress must progress from non-1 to 1 in fixed-size non-zero steps";
×
NEW
1359
    }
×
1360
};
1361
} // namespace
1362

1363
TEST_CASE("sync progress: flx download progress", "[sync][baas][progress]") {
12✔
1364
    static std::optional<FLXSyncTestHarness> harness;
12✔
1365

1366
    std::unique_ptr<char[]> buffer;
12✔
1367
    const auto create_object = [&](const std::shared_ptr<Realm>& realm, int id) {
20✔
1368
        const size_t padding_size = 1024 * 1024;
20✔
1369
        if (!buffer)
20✔
1370
            buffer = std::make_unique<char[]>(padding_size);
4✔
1371
        auto table = realm->read_group().get_table("class_object");
20✔
1372
        auto obj = table->create_object_with_primary_key(ObjectId::gen());
20✔
1373
        obj.set("int", id);
20✔
1374
        // ensure that each object is large enough that it'll be sent in
1375
        // a separate DOWNLOAD message
1376
        obj.set("padding", BinaryData(buffer.get(), padding_size));
20✔
1377
    };
20✔
1378

1379
    if (!harness) {
12✔
1380
        Schema schema{
2✔
1381
            {"object",
2✔
1382
             {{"_id", PropertyType::ObjectId, Property::IsPrimary{true}},
2✔
1383
              {"int", PropertyType::Int | PropertyType::Nullable},
2✔
1384
              {"padding", PropertyType::Data}}},
2✔
1385
        };
2✔
1386
        realm::app::FLXSyncTestHarness::ServerSchema server_schema{std::move(schema), {"int"}};
2✔
1387
        harness.emplace("flx_download_progress", std::move(server_schema));
2✔
1388
        harness->load_initial_data([&](const std::shared_ptr<Realm>& realm) {
2✔
1389
            for (int i = 0; i < 5; ++i)
12✔
1390
                create_object(realm, i);
10✔
1391
        });
2✔
1392
    }
2✔
1393

1394
    SyncTestFile config = harness->make_test_file();
12✔
1395

1396
    SECTION("async open with no subscriptions") {
12✔
1397
        auto task = Realm::get_synchronized_realm(config);
2✔
1398
        std::vector<double> estimates;
2✔
1399
        task->register_download_progress_notifier([&](uint64_t, uint64_t, double estimate) {
2✔
1400
            // Note that no locking is needed here despite this being called on
1401
            // a background thread as the test provides the required synchronization.
1402
            // We register the notifier at a point where no notifications should
1403
            // be in process, and then wait on a Future which should be fulfilled
1404
            // after the final progress update is sent. If tsan complains about
1405
            // this, it means that progress updates are being sent at a time
1406
            // outside of the expected window and that's the bug to fix.
NEW
1407
            estimates.push_back(estimate);
×
NEW
1408
        });
×
1409
        task->start().get();
2✔
1410
        // A download happens for the schema, but we now don't report that
1411
        REQUIRE(estimates.size() == 0);
2!
1412
    }
2✔
1413

1414
    SECTION("async open with initial subscriptions") {
12✔
1415
        config.sync_config->subscription_initializer = [](const std::shared_ptr<Realm>& realm) {
2✔
1416
            subscribe_to_all(*realm);
2✔
1417
        };
2✔
1418
        auto task = Realm::get_synchronized_realm(config);
2✔
1419
        std::vector<double> estimates;
2✔
1420
        task->register_download_progress_notifier([&](uint64_t, uint64_t, double estimate) {
12✔
1421
            // See above about the lack of locking
1422
            estimates.push_back(estimate);
12✔
1423
        });
12✔
1424
        task->start().get();
2✔
1425

1426
        // Since our objects are larger than the server's soft limit for batching
1427
        // (1 MB), we expect to receive a separate DOWNLOAD message for each
1428
        // object. We also happen to get an empty DOWNLOAD at the end, but we
1429
        // don't want to require that.
1430
        REQUIRE(estimates.size() >= 5);
2!
1431
        REQUIRE_THAT(estimates, EstimatesAreValid());
2✔
1432
    }
2✔
1433

1434
    SECTION("multiple subscription updates which each trigger some downloads") {
12✔
1435
        auto realm = successfully_async_open_realm(config);
2✔
1436
        auto table = realm->read_group().get_table("class_object");
2✔
1437
        auto col = table->get_column_key("int");
2✔
1438

1439
        std::vector<double> estimates;
2✔
1440
        realm->sync_session()->register_progress_notifier(
2✔
1441
            [&](uint64_t, uint64_t, double estimate) {
14✔
1442
                // See above about the lack of locking
1443
                estimates.push_back(estimate);
14✔
1444
            },
14✔
1445
            SyncSession::ProgressDirection::download, true);
2✔
1446

1447
        for (int i = 4; i > -2; i -= 2) {
8✔
1448
            auto sub_set = realm->get_latest_subscription_set().make_mutable_copy();
6✔
1449
            sub_set.insert_or_assign(table->where().greater(col, i));
6✔
1450
            sub_set.commit().get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
6✔
1451

1452
            // We get a variable number of DOWNLOAD messages per update but it should always be at least one
1453
            REQUIRE(estimates.size() >= 1);
6!
1454
            REQUIRE_THAT(estimates, EstimatesAreValid());
6✔
1455

1456
            estimates.clear();
6✔
1457
        }
6✔
1458
    }
2✔
1459

1460
    SECTION("add subscription which doesn't add new objects") {
12✔
1461
        auto realm = successfully_async_open_realm(config);
2✔
1462
        auto table = realm->read_group().get_table("class_object");
2✔
1463
        auto col = table->get_column_key("int");
2✔
1464

1465
        std::vector<double> estimates;
2✔
1466
        realm->sync_session()->register_progress_notifier(
2✔
1467
            [&](uint64_t, uint64_t, double estimate) {
14✔
1468
                // See above about the lack of locking
1469
                estimates.push_back(estimate);
14✔
1470
            },
14✔
1471
            SyncSession::ProgressDirection::download, true);
2✔
1472

1473
        {
2✔
1474
            auto sub_set = realm->get_latest_subscription_set().make_mutable_copy();
2✔
1475
            sub_set.insert_or_assign(table->where().less(col, 5));
2✔
1476
            sub_set.commit().get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
2✔
1477
        }
2✔
1478

1479
        estimates.clear();
2✔
1480

1481
        // This subscription change should not actually result in any new objects
1482
        {
2✔
1483
            auto sub_set = realm->get_latest_subscription_set().make_mutable_copy();
2✔
1484
            sub_set.insert_or_assign(table->where().less(col, 10));
2✔
1485
            sub_set.commit().get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
2✔
1486
        }
2✔
1487

1488
        // We expect just a single update with progress_estimate=1, but the
1489
        // server could legally send us multiple empty DOWNLOADs
1490
        REQUIRE(estimates.size() >= 1);
2!
1491
        REQUIRE_THAT(estimates, EstimatesAreValid());
2✔
1492
    }
2✔
1493

1494
    SECTION("add new objects while in the steady state") {
12✔
1495
        config.sync_config->subscription_initializer = [](const std::shared_ptr<Realm>& realm) {
4✔
1496
            subscribe_to_all(*realm);
4✔
1497
        };
4✔
1498
        auto online_realm = successfully_async_open_realm(config);
2✔
1499

1500
        SyncTestFile config2 = harness->make_test_file();
2✔
1501
        config2.sync_config->subscription_initializer = config.sync_config->subscription_initializer;
2✔
1502
        auto suspended_realm = successfully_async_open_realm(config2);
2✔
1503

1504
        std::vector<double> online_estimates;
2✔
1505
        online_realm->sync_session()->register_progress_notifier(
2✔
1506
            [&](uint64_t, uint64_t, double estimate) {
12✔
1507
                // See above about the lack of locking
1508
                online_estimates.push_back(estimate);
12✔
1509
            },
12✔
1510
            SyncSession::ProgressDirection::download, true);
2✔
1511

1512
        std::vector<double> suspended_estimates;
2✔
1513
        suspended_realm->sync_session()->register_progress_notifier(
2✔
1514
            [&](uint64_t, uint64_t, double estimate) {
12✔
1515
                // See above about the lack of locking
1516
                suspended_estimates.push_back(estimate);
12✔
1517
            },
12✔
1518
            SyncSession::ProgressDirection::download, true);
2✔
1519

1520
        // We should get the initial notification that downloads are already complete
1521
        wait_for_download(*online_realm);
2✔
1522
        wait_for_download(*suspended_realm);
2✔
1523
        REQUIRE(online_estimates == std::vector{1.0});
2!
1524
        REQUIRE(suspended_estimates == std::vector{1.0});
2!
1525

1526
        online_estimates.clear();
2✔
1527
        suspended_estimates.clear();
2✔
1528
        suspended_realm->sync_session()->pause();
2✔
1529

1530
        harness->do_with_new_realm([&](const std::shared_ptr<Realm>& realm) {
2✔
1531
            subscribe_to_all(*realm);
2✔
1532
            for (int i = 5; i < 10; ++i) {
12✔
1533
                realm->begin_transaction();
10✔
1534
                create_object(realm, i);
10✔
1535
                realm->commit_transaction();
10✔
1536
                wait_for_upload(*realm);
10✔
1537

1538
                // The currently connected Realm should receive exactly one
1539
                // download message telling it that the download is complete as
1540
                // it's always staying up to date
1541
                wait_for_download(*online_realm);
10✔
1542
                REQUIRE(online_estimates == std::vector{1.0});
10!
1543
                online_estimates.clear();
10✔
1544
            }
10✔
1545
        });
2✔
1546

1547
        // Once it reconnects, the offline Realm should receive at least five
1548
        // separate DOWNLOAD messages, each of which should include actual
1549
        // progress information towards completion
1550
        suspended_realm->sync_session()->resume();
2✔
1551
        wait_for_download(*suspended_realm);
2✔
1552
        REQUIRE(suspended_estimates.size() >= 5);
2!
1553
        REQUIRE_THAT(suspended_estimates, EstimatesAreValid(5));
2✔
1554
    }
2✔
1555

1556
    SECTION("cleanup") {
12✔
1557
        harness.reset();
2✔
1558
    }
2✔
1559
}
12✔
1560

1561
#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