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

realm / realm-core / github_pull_request_301264

30 Jul 2024 07:11PM UTC coverage: 91.111% (+0.009%) from 91.102%
github_pull_request_301264

Pull #7936

Evergreen

web-flow
Add support for multi-process subscription state change notifications (#7862)

As with the other multi-process notifications, the core idea here is to
eliminate the in-memory state and produce notifications based entirely on the
current state of the Realm file.

SubscriptionStore::update_state() has been replaced with separate functions for
the specific legal state transitions, which also take a write transaction as a
parameter. These functions are called by PendingBootstrapStore inside the same
write transaction as the bootstrap updates which changed the subscription
state. This is both a minor performance optimization (due to fewer writes) and
eliminates a brief window between the two writes where the Realm file was in an
inconsistent state.

There's a minor functional change here: previously old subscription sets were
superseded when the new one reached the Completed state, and now they are
superseded on AwaitingMark. This aligns it with when the new subscription set
becomes the one which is returned by get_active().
Pull Request #7936: Fix connection callback crashes when reloading with React Native

102800 of 181570 branches covered (56.62%)

216840 of 237996 relevant lines covered (91.11%)

5918493.47 hits per line

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

57.73
/test/fuzz_tester.hpp
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
#ifndef REALM_TEST_FUZZ_TESTER_HPP
20
#define REALM_TEST_FUZZ_TESTER_HPP
21

22
#include <realm/util/features.h>
23
#include <realm/list.hpp>
24
#include <realm/sync/transform.hpp>
25

26
#include "util/unit_test.hpp"
27
#include "util/quote.hpp"
28
#include "util/compare_groups.hpp"
29
#include "util/dump_changesets.hpp"
30
#include "peer.hpp"
31

32
#include <iostream>
33

34
namespace realm {
35
namespace test_util {
36

37
template <class L>
38
struct StreamableLambda {
39
    StreamableLambda(L&& l)
40
        : m_lambda(std::move(l))
41
    {
×
42
    }
×
43
    L m_lambda;
44
};
45

46
template <class L>
47
StreamableLambda<L> make_streamable_lambda(L&& lambda)
48
{
×
49
    return StreamableLambda<L>(std::move(lambda));
×
50
}
×
51

52
template <class OS, class L>
53
OS& operator<<(OS& os, StreamableLambda<L>&& sl)
54
{
×
55
    sl.m_lambda(os);
×
56
    return os;
×
57
}
×
58

59
template <class Source>
60
class FuzzTester {
61
public:
62
    FuzzTester(Source& source, bool trace)
63
        : m_source(source)
1✔
64
        , m_trace(trace)
1✔
65
    {
2✔
66
    }
2✔
67

68
    // FIXME: Eliminate the dependency on the unit_test namespace.
69
    void round(unit_test::TestContext&, std::string path_add_on = "");
70

71
private:
72
    static const int num_modifications_per_round = 256;
73
    static const int num_clients = 4;
74

75
    static const int modify_weight = 100;
76
    static const int upload_weight = 100;
77
    static const int download_weight = 100;
78

79

80
    static constexpr double group_to_table_level_transition_chance()
81
    {
2,019✔
82
        return 7.0 / 8;
2,019✔
83
    }
2,019✔
84

85
    static constexpr double table_to_array_level_transition_chance()
86
    {
434✔
87
        return 7.0 / 8;
434✔
88
    }
434✔
89

90
    static const int rename_table_weight = 0; // Rename table is destructive; not supported.
91
    static const int add_table_weight = 100;
92
    static const int erase_table_weight = 10;
93

94
    static const int insert_column_weight = 10;
95
    static const int insert_link_column_weight = 5;
96
    static const int insert_array_column_weight = 5;
97
    static const int erase_column_weight = 1;
98

99
    static const int update_row_weight = 80;
100
    static const int insert_row_weight = 100;
101
    static const int erase_row_weight = 80;
102

103
    static const int set_link_weight = 80;
104
    static const int insert_link_weight = 100;
105
    static const int remove_link_weight = 70;
106
    static const int move_link_weight = 50;
107
    static const int swap_links_weight = 50;
108
    static const int clear_link_list_weight = 1;
109

110
    static const int array_set_weight = 80;
111
    static const int array_insert_weight = 100;
112
    static const int array_remove_weight = 70;
113
    static const int array_move_weight = 50;
114
    static const int array_swap_weight = 0;
115
    static const int array_clear_weight = 1;
116

117

118
    template <class T>
119
    T draw_int(T min, T max)
120
    {
5,072✔
121
        return m_source.template draw_int<T>(min, max);
5,072✔
122
    }
5,072✔
123

124
    template <class T>
125
    T draw_int_mod(T mod)
126
    {
26,082✔
127
        return m_source.template draw_int_mod<T>(mod);
26,082✔
128
    }
26,082✔
129

130
    template <class T>
131
    T draw_int_max(T max)
132
    {
1,181✔
133
        return m_source.template draw_int_max<T>(max);
1,181✔
134
    }
1,181✔
135

136
    template <class T>
137
    T draw_float()
138
    {
2,453✔
139
        return m_source.template draw_float<T>();
2,453✔
140
    }
2,453✔
141

142
    bool draw_bool()
143
    {
144
        return m_source.draw_bool();
145
    }
146

147
    void rename_table(Peer& client)
148
    {
×
149
        static_cast<void>(client);
×
150
        REALM_ASSERT(false);
×
151
    }
×
152

153
    auto trace_client(Peer& client)
154
    {
×
155
        return make_streamable_lambda([&](std::ostream& os) {
×
156
            os << "client_" << client.local_file_ident;
×
157
        });
×
158
    }
×
159

160
    auto trace_selected_table(Peer& client)
161
    {
×
162
        return make_streamable_lambda([&](std::ostream& os) {
×
163
            os << trace_client(client) << "->selected_table";
×
164
        });
×
165
    }
×
166

167
    auto trace_selected_link_list(Peer& client)
168
    {
×
169
        return make_streamable_lambda([&](std::ostream& os) {
×
170
            os << trace_client(client) << "->selected_link_list";
×
171
        });
×
172
    }
×
173

174
    auto trace_selected_array(Peer& client)
175
    {
×
176
        return make_streamable_lambda([&](std::ostream& os) {
×
177
            os << trace_client(client) << "->selected_array";
×
178
        });
×
179
    }
×
180

181
    auto trace_selected_int_array(Peer& client)
182
    {
×
183
        return make_streamable_lambda([&](std::ostream& os) {
×
184
            os << "static_cast<Lst<int64_t>*>(" << trace_client(client) << "->selected_array.get())";
×
185
        });
×
186
    }
×
187

188
    auto trace_selected_string_array(Peer& client)
189
    {
×
190
        return make_streamable_lambda([&](std::ostream& os) {
×
191
            os << "static_cast<Lst<StringData>*>(" << trace_client(client) << "->selected_array.get())";
×
192
        });
×
193
    }
×
194

195
    void add_table(Peer& client)
196
    {
262✔
197
        char table_name[] = {'c', 'l', 'a', 's', 's', '_', 0, 0};
262✔
198
        table_name[6] = 'A' + draw_int_mod(6); // pick a random letter A-F
262✔
199

200
        if (client.group->get_table(table_name))
262✔
201
            return;
160✔
202

203
        bool is_string_pk = (table_name[6] == 'B');
102✔
204
        if (m_trace) {
102✔
205
            std::cerr << "sync::create_table_with_primary_key(*" << trace_client(client)
×
206
                      << "->"
×
207
                         "group, \""
×
208
                      << table_name << "\",";
×
209
            if (is_string_pk)
×
210
                std::cerr << "type_String";
×
211
            else
×
212
                std::cerr << "type_Int";
×
213
            std::cerr << ", \"pk\");\n";
×
214
        }
×
215
        client.group->add_table_with_primary_key(table_name, is_string_pk ? type_String : type_Int, "pk");
102✔
216
    }
102✔
217

218
    void erase_table(Peer& client)
219
    {
27✔
220
        size_t num_tables = count_classes(client);
27✔
221
        size_t table_ndx = draw_int_mod(num_tables);
27✔
222
        TableRef table = get_class(client, table_ndx);
27✔
223
        if (m_trace) {
27✔
224
            std::cerr << "sync::erase_table(*" << trace_client(client)
×
225
                      << "->"
×
226
                         "group, \""
×
227
                      << table->get_name() << "\");\n";
×
228
        }
×
229
        client.group->remove_table(table->get_name());
27✔
230
    }
27✔
231

232
    void clear_group(Peer& client)
233
    {
234
        if (m_trace) {
235
            std::cerr << trace_client(client)
236
                      << "->"
237
                         "group->clear();\n";
238
        }
239
        //        client.group->clear();
240
    }
241

242
    void insert_column(Peer& client)
243
    {
87✔
244
        // It is currently an error to request multiple columns with the same name
245
        // but with different types / nullability (there is no non-destructive way
246
        // to merge them).
247
        const char* column_names[] = {"a", "b", "c", "d"};
87✔
248
        const DataType column_types[] = {type_Int, type_Int, type_String, type_String};
87✔
249
        const bool column_nullable[] = {false, true, false, true};
87✔
250

251
        size_t which = draw_int_mod(4);
87✔
252
        const char* name = column_names[which];
87✔
253
        DataType type = column_types[which];
87✔
254
        bool nullable = column_nullable[which];
87✔
255

256
        TableRef table = client.selected_table;
87✔
257
        if (table->get_column_key(name))
87✔
258
            return;
13✔
259

260
        if (m_trace) {
74✔
261
            const char* type_name;
×
262
            if (type == type_Int) {
×
263
                type_name = "type_Int";
×
264
            }
×
265
            else if (type == type_String) {
×
266
                type_name = "type_String";
×
267
            }
×
268
            else {
×
269
                REALM_TERMINATE("Missing trace support for column type.");
270
            }
×
271

272
            std::cerr << trace_selected_table(client) << "->add_column(" << type_name << ", \"" << name << "\", "
×
273
                      << nullable << ");\n";
×
274
        }
×
275

276
        ColKey col_key = table->add_column(type, name, nullable);
74✔
277
        m_unstructured_columns.push_back(col_key);
74✔
278
    }
74✔
279

280
    void insert_link_column(Peer& client)
281
    {
31✔
282
        REALM_ASSERT(count_classes(client) > 1);
31✔
283

284
        const char* column_names[] = {"e", "f"};
31✔
285

286
        size_t which = draw_int_max(1);
31✔
287
        const char* name = column_names[which];
31✔
288
        bool is_list = bool(which);
31✔
289

290
        TableRef table = client.selected_table;
31✔
291
        if (table->get_column_key(name))
31✔
292
            return;
×
293

294
        // Avoid divergent schemas by always creating links to table "A"
295
        TableKey link_target_table_key = client.group->find_table("A");
31✔
296
        if (!link_target_table_key)
31✔
297
            return;
31✔
298

299
        TableRef link_target_table = client.group->get_table(link_target_table_key);
×
300

301
        if (m_trace) {
×
302
            const char* type_name;
×
303
            if (is_list) {
×
304
                type_name = "type_LinkList";
×
305
            }
×
306
            else {
×
307
                type_name = "type_Link";
×
308
            }
×
309
            std::cerr << trace_selected_table(client) << "->add_column_link(" << type_name << ", \"" << name
×
310
                      << "\", *client_" << client.local_file_ident << "->group->get_table(\"A\"));\n";
×
311
        }
×
312

313
        if (is_list) {
×
314
            ColKey col_key = table->add_column_list(*link_target_table, name);
×
315
            m_link_list_columns.push_back(col_key);
×
316
        }
×
317
        else {
×
318
            ColKey col_key = table->add_column(*link_target_table, name);
×
319
            m_unstructured_columns.push_back(col_key);
×
320
        }
×
321
    }
×
322

323
    void insert_array_column(Peer& client)
324
    {
49✔
325
        REALM_ASSERT(count_classes(client) >= 1);
49✔
326

327
        const char* column_names[] = {"g", "h"};
49✔
328
        const DataType column_types[] = {type_Int, type_String};
49✔
329

330
        size_t which = draw_int_max(1);
49✔
331
        const char* name = column_names[which];
49✔
332
        DataType type = column_types[which];
49✔
333
        bool nullable = false;
49✔
334

335
        TableRef table = client.selected_table;
49✔
336
        if (table->get_column_key(name))
49✔
337
            return;
×
338

339
        if (m_trace) {
49✔
340
            const char* type_name;
×
341
            if (type == type_Int) {
×
342
                type_name = "type_Int";
×
343
            }
×
344
            else if (type == type_String) {
×
345
                type_name = "type_String";
×
346
            }
×
347
            else {
×
348
                REALM_TERMINATE("Missing trace support for column type.");
349
            }
×
350
            std::cerr << trace_selected_table(client) << "->add_column_list(" << type_name << ", \"" << name << "\", "
×
351
                      << nullable << ");\n";
×
352
        }
×
353

354
        ColKey col_key = table->add_column_list(type, name, nullable);
49✔
355
        m_array_columns.push_back(col_key);
49✔
356
    }
49✔
357

358
    void update_row(Peer& client)
359
    {
130✔
360
        REALM_ASSERT(!m_unstructured_columns.empty());
130✔
361
        size_t i = draw_int_mod(m_unstructured_columns.size());
130✔
362
        ColKey col_key = m_unstructured_columns[i];
130✔
363
        size_t num_rows = client.selected_table->size();
130✔
364
        size_t row_ndx = draw_int_mod(num_rows);
130✔
365
        ObjKey row_key = (client.selected_table->begin() + row_ndx)->get_key();
130✔
366
        DataType type = client.selected_table->get_column_type(col_key);
130✔
367
        bool nullable = client.selected_table->is_nullable(col_key);
130✔
368

369
        Obj obj = client.selected_table->get_object(row_key);
130✔
370

371
        if (type == type_Int) {
130✔
372
            int_fast64_t value = next_value();
67✔
373
            if (nullable && value % 7 == 0) {
67✔
374
                bool is_default = (value % 21 == 0);
1✔
375
                if (m_trace) {
1✔
376
                    std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").set_null("
×
377
                              << col_key << ", " << is_default << ");\n";
×
378
                }
×
379
                client.selected_table->get_object(row_key).set_null(col_key, is_default);
1✔
380
                return;
1✔
381
            }
1✔
382
            else {
66✔
383
                if (value % 3 == 0 && (!nullable || !obj.is_null(col_key))) {
66✔
384
                    if (m_trace) {
18✔
385
                        std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").add_int("
×
386
                                  << col_key << ", " << value << ");\n";
×
387
                    }
×
388
                    obj.add_int(col_key, value);
18✔
389
                }
18✔
390
                else {
48✔
391
                    bool is_default = (value % 13 == 0);
48✔
392
                    if (m_trace) {
48✔
393
                        std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").set(" << col_key
×
394
                                  << ", " << value << ", " << is_default << ");\n";
×
395
                    }
×
396
                    obj.set(col_key, value, is_default);
48✔
397
                }
48✔
398
                return;
66✔
399
            }
66✔
400
        }
67✔
401

402
        if (type == type_String) {
63✔
403
            int_fast64_t ival = next_value();
63✔
404

405
            if (nullable && ival % 7 == 0) {
63✔
406
                bool is_default = (ival % 21 == 0);
3✔
407
                if (m_trace) {
3✔
408
                    std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").set_null("
×
409
                              << col_key << ", " << is_default << ");\n";
×
410
                }
×
411
                client.selected_table->get_object(row_key).set_null(col_key, is_default);
3✔
412
                return;
3✔
413
            }
3✔
414
            else {
60✔
415
                std::stringstream ss;
60✔
416
                ss << ival;
60✔
417
                std::string value = ss.str();
60✔
418

419
                bool is_default = (ival % 13 == 0);
60✔
420
                if (m_trace) {
60✔
421
                    std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").set(" << col_key
×
422
                              << ", \"" << value << "\", " << is_default << ");\n";
×
423
                }
×
424
                obj.set(col_key, value, is_default);
60✔
425
                return;
60✔
426
            }
60✔
427
        }
63✔
428

429
        if (type == type_Link) {
×
430
            TableRef target_table = client.selected_table->get_link_target(col_key);
×
431
            size_t value = draw_int_mod(target_table->size() + 1);
×
432
            if (value == target_table->size()) {
×
433
                if (m_trace) {
×
434
                    std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").set_null("
×
435
                              << col_key << ";\n";
×
436
                }
×
437
                obj.set_null(col_key);
×
438
            }
×
439
            else {
×
440
                ObjKey target_key = (target_table->begin() + value)->get_key();
×
441
                if (m_trace) {
×
442
                    std::cerr << trace_selected_table(client) << "->get_object(" << row_key << ").set(" << col_key
×
443
                              << ", " << target_key << ");\n";
×
444
                }
×
445
                obj.set(col_key, target_key);
×
446
            }
×
447
            return;
×
448
        }
×
449
        REALM_ASSERT(false);
×
450
    }
×
451

452
    void insert_row(Peer& client)
453
    {
694✔
454
        ColKey pk_col_key = client.selected_table->get_column_key("pk");
694✔
455

456
        char string_buffer[2] = {0};
694✔
457
        bool is_string_pk = (client.selected_table->get_column_type(pk_col_key) == type_String);
694✔
458
        int_fast64_t pk_int = 0;
694✔
459
        StringData pk_string;
694✔
460
        if (is_string_pk) {
694✔
461
            string_buffer[0] = 'a' + draw_int_max(25); // "a" to "z"
111✔
462
            pk_string = StringData{string_buffer, 1};
111✔
463
        }
111✔
464
        else {
583✔
465
            pk_int = draw_int_max(10); // Low number to ensure some collisions
583✔
466
        }
583✔
467
        if (m_trace) {
694✔
468
            std::cerr << trace_selected_table(client) << "->create_object_with_primary_key(";
×
469
            if (is_string_pk)
×
470
                std::cerr << "\"" << pk_string << "\"";
×
471
            else
×
472
                std::cerr << pk_int;
×
473
            std::cerr << ");\n";
×
474
        }
×
475
        if (is_string_pk)
694✔
476
            client.selected_table->create_object_with_primary_key(pk_string);
111✔
477
        else
583✔
478
            client.selected_table->create_object_with_primary_key(pk_int);
583✔
479
    }
694✔
480

481
    void move_last_row_over(Peer& client)
482
    {
377✔
483
        size_t num_rows = client.selected_table->size();
377✔
484
        size_t row_ndx = draw_int_mod(num_rows);
377✔
485
        ObjKey row_key = (client.selected_table->begin() + row_ndx)->get_key();
377✔
486
        if (m_trace) {
377✔
487
            std::cerr << trace_selected_table(client) << "->remove_object(" << row_key << ");\n";
×
488
        }
×
489
        client.selected_table->remove_object(row_key);
377✔
490
    }
377✔
491

492
    void set_link(Peer& client)
493
    {
×
494
        size_t num_links = client.selected_link_list->size();
×
495
        size_t link_ndx = draw_int_max(num_links - 1);
×
496
        auto target_table = client.selected_link_list->get_target_table();
×
497
        size_t num_target_rows = target_table->size();
×
498
        REALM_ASSERT(num_target_rows > 0);
×
499
        size_t target_row_ndx = draw_int_mod(num_target_rows);
×
500
        ObjKey target_row_key = (target_table->begin() + target_row_ndx)->get_key();
×
501
        if (m_trace) {
×
502
            std::cerr << trace_selected_link_list(client) << "->set(" << link_ndx << ", " << target_row_key << ");\n";
×
503
        }
×
504
        client.selected_link_list->set(link_ndx, target_row_key);
×
505
    }
×
506

507
    void insert_link(Peer& client)
508
    {
×
509
        size_t num_links = client.selected_link_list->size();
×
510
        size_t link_ndx = draw_int_max(num_links);
×
511
        auto target_table = client.selected_link_list->get_target_table();
×
512
        size_t num_target_rows = target_table->size();
×
513
        REALM_ASSERT(num_target_rows > 0);
×
514
        size_t target_row_ndx = draw_int_mod(num_target_rows);
×
515
        ObjKey target_row_key = (target_table->begin() + target_row_ndx)->get_key();
×
516
        if (m_trace) {
×
517
            std::cerr << trace_selected_link_list(client) << "->insert(" << link_ndx << ", " << target_row_key
×
518
                      << ");\n";
×
519
        }
×
520
        client.selected_link_list->insert(link_ndx, target_row_key);
×
521
    }
×
522

523
    void remove_link(Peer& client)
524
    {
×
525
        size_t num_links = client.selected_link_list->size();
×
526
        size_t link_ndx = draw_int_mod(num_links);
×
527
        if (m_trace) {
×
528
            std::cerr << trace_selected_link_list(client) << "->remove(" << link_ndx << ");\n";
×
529
        }
×
530
        client.selected_link_list->remove(link_ndx);
×
531
    }
×
532

533
    void move_link(Peer& client)
534
    {
×
535
        size_t num_links = client.selected_link_list->size();
×
536
        size_t from_link_ndx, to_link_ndx;
×
537
        for (;;) {
×
538
            from_link_ndx = draw_int_mod(num_links);
×
539
            to_link_ndx = draw_int_mod(num_links);
×
540
            if (from_link_ndx != to_link_ndx)
×
541
                break;
×
542
        }
×
543

544
        if (m_trace) {
×
545
            std::cerr << trace_selected_link_list(client) << "->move(" << from_link_ndx << ", " << to_link_ndx
×
546
                      << ");\n";
×
547
        }
×
548
        client.selected_link_list->move(from_link_ndx, to_link_ndx);
×
549
    }
×
550

551
    void clear_link_list(Peer& client)
552
    {
×
553
        if (m_trace) {
×
554
            std::cerr << trace_selected_link_list(client) << "->clear();\n";
×
555
        }
×
556
        client.selected_link_list->clear();
×
557
    }
×
558

559
    void array_set(Peer& client)
560
    {
63✔
561
        size_t num_elements = client.selected_array->size();
63✔
562
        DataType type = client.selected_array->get_table()->get_column_type(client.selected_array->get_col_key());
63✔
563
        size_t ndx = draw_int_max(num_elements - 1);
63✔
564
        if (type == type_Int) {
63✔
565
            int_fast64_t value = draw_int_max(1000);
19✔
566
            if (m_trace) {
19✔
567
                std::cerr << trace_selected_int_array(client) << "->set(" << ndx << ", " << value << ");\n";
×
568
            }
×
569
            static_cast<Lst<int64_t>*>(client.selected_array.get())->set(ndx, value);
19✔
570
        }
19✔
571
        else {
44✔
572
            StringData value = "abc";
44✔
573
            if (m_trace) {
44✔
574
                std::cerr << trace_selected_string_array(client) << "->set(" << ndx << ", \"" << value << "\");\n";
×
575
            }
×
576
            static_cast<Lst<StringData>*>(client.selected_array.get())->set(ndx, value);
44✔
577
        }
44✔
578
    }
63✔
579

580
    void array_insert(Peer& client)
581
    {
266✔
582
        size_t num_elements = client.selected_array->size();
266✔
583
        DataType type = client.selected_array->get_table()->get_column_type(client.selected_array->get_col_key());
266✔
584
        size_t ndx = draw_int_max(num_elements);
266✔
585
        if (type == type_Int) {
266✔
586
            if (m_trace) {
95✔
587
                std::cerr << trace_selected_int_array(client) << "->insert(" << ndx << ", 0);\n";
×
588
            }
×
589
            static_cast<Lst<int64_t>*>(client.selected_array.get())->insert(ndx, 0);
95✔
590
        }
95✔
591
        else if (type == type_String) {
171✔
592
            if (m_trace) {
171✔
593
                std::cerr << trace_selected_string_array(client) << "->insert(" << ndx << ", \"\");\n";
×
594
            }
×
595
            static_cast<Lst<StringData>*>(client.selected_array.get())->insert(ndx, "");
171✔
596
        }
171✔
597
    }
266✔
598

599
    void array_remove(Peer& client)
600
    {
59✔
601
        size_t num_elements = client.selected_array->size();
59✔
602
        size_t ndx = draw_int_max(num_elements - 1);
59✔
603
        if (m_trace) {
59✔
604
            std::cerr << "client_" << client.local_file_ident
×
605
                      << "->"
×
606
                         "selected_array->remove("
×
607
                      << ndx << ", " << ndx + 1 << ");\n";
×
608
        }
×
609
        client.selected_array->remove(ndx, ndx + 1);
59✔
610
    }
59✔
611

612
    void array_move(Peer& client)
613
    {
614
        size_t num_elements = client.selected_array->size();
615
        size_t from_ndx, to_ndx;
616
        for (;;) {
617
            from_ndx = draw_int_mod(num_elements);
618
            to_ndx = draw_int_mod(num_elements);
619
            if (from_ndx != to_ndx)
620
                break;
621
        }
622

623
        if (m_trace) {
624
            std::cerr << trace_selected_array(client) << "->move_row(" << from_ndx << ", " << to_ndx << ");\n";
625
        }
626
        client.selected_array->move(from_ndx, to_ndx);
627
    }
628

629
    void array_clear(Peer& client)
630
    {
3✔
631
        if (m_trace) {
3✔
632
            std::cerr << trace_selected_array(client) << "->clear();\n";
×
633
        }
×
634
        client.selected_array->clear();
3✔
635
    }
3✔
636

637
    using action_func_type = void (FuzzTester<Source>::*)(Peer&);
638
    using action_type = std::pair<int, action_func_type>; // First componenet is 'weight'
639

640
    Source& m_source;
641
    const bool m_trace;
642
    int_fast64_t m_current_value;
643
    std::vector<ColKey> m_unstructured_columns;
644
    std::vector<ColKey> m_link_list_columns;
645
    std::vector<ColKey> m_array_columns;
646

647
    int_fast64_t next_value()
648
    {
130✔
649
        return ++m_current_value;
130✔
650
    }
130✔
651

652
    void get_group_level_modify_actions(size_t num_classes, std::vector<action_type>& actions)
653
    {
289✔
654
        if (num_classes >= 1)
289✔
655
            actions.push_back(std::make_pair(rename_table_weight + 0, &FuzzTester<Source>::rename_table));
260✔
656
        if (true)
289✔
657
            actions.push_back(std::make_pair(add_table_weight + 0, &FuzzTester<Source>::add_table));
289✔
658
        if (num_classes >= 1)
289✔
659
            actions.push_back(std::make_pair(erase_table_weight + 0, &FuzzTester<Source>::erase_table));
260✔
660
    }
289✔
661

662
    void get_table_level_modify_actions(size_t num_classes, size_t num_cols, size_t num_rows,
663
                                        std::vector<action_type>& actions)
664
    {
1,368✔
665
        if (true)
1,368✔
666
            actions.push_back(std::make_pair(insert_column_weight + 0, &FuzzTester<Source>::insert_column));
1,368✔
667
        if (num_classes > 1)
1,368✔
668
            actions.push_back(std::make_pair(insert_link_column_weight + 0, &FuzzTester<Source>::insert_link_column));
1,292✔
669
        if (num_classes >= 1)
1,368✔
670
            actions.push_back(
1,368✔
671
                std::make_pair(insert_array_column_weight + 0, &FuzzTester<Source>::insert_array_column));
1,368✔
672
        if (num_rows >= 1 && !m_unstructured_columns.empty())
1,368✔
673
            actions.push_back(std::make_pair(update_row_weight + 0, &FuzzTester<Source>::update_row));
410✔
674
        if (num_cols >= 1)
1,368✔
675
            actions.push_back(std::make_pair(insert_row_weight + 0, &FuzzTester<Source>::insert_row));
1,368✔
676
        if (num_rows >= 1)
1,368✔
677
            actions.push_back(std::make_pair(erase_row_weight + 0, &FuzzTester<Source>::move_last_row_over));
1,058✔
678
    }
1,368✔
679

680
    void get_link_list_level_modify_actions(size_t num_links, std::vector<action_type>& actions)
681
    {
×
682
        if (num_links >= 1)
×
683
            actions.push_back(std::make_pair(set_link_weight + 0, &FuzzTester<Source>::set_link));
×
684
        if (true)
×
685
            actions.push_back(std::make_pair(insert_link_weight + 0, &FuzzTester<Source>::insert_link));
×
686
        if (num_links >= 1)
×
687
            actions.push_back(std::make_pair(remove_link_weight + 0, &FuzzTester<Source>::remove_link));
×
688
        if (num_links >= 2)
×
689
            actions.push_back(std::make_pair(move_link_weight + 0, &FuzzTester<Source>::move_link));
×
690
        if (true)
×
691
            actions.push_back(std::make_pair(clear_link_list_weight + 0, &FuzzTester<Source>::clear_link_list));
×
692
    }
×
693

694
    void get_array_level_modify_actions(size_t num_elements, std::vector<action_type>& actions)
695
    {
391✔
696
        if (num_elements >= 1)
391✔
697
            actions.push_back(std::make_pair(array_set_weight + 0, &FuzzTester<Source>::array_set));
201✔
698
        if (true)
391✔
699
            actions.push_back(std::make_pair(array_insert_weight + 0, &FuzzTester<Source>::array_insert));
391✔
700
        if (num_elements >= 1)
391✔
701
            actions.push_back(std::make_pair(array_remove_weight + 0, &FuzzTester<Source>::array_remove));
201✔
702
        // if (num_elements >= 2)
703
        //     actions.push_back(std::make_pair(array_move_weight+0, &FuzzTester<Source>::array_move));
704
        if (true)
391✔
705
            actions.push_back(std::make_pair(array_clear_weight + 0, &FuzzTester<Source>::array_clear));
391✔
706
    }
391✔
707

708
    size_t count_classes(Peer& client)
709
    {
2,155✔
710
        size_t count = 0;
2,155✔
711
        for (TableKey key : client.group->get_table_keys()) {
9,023✔
712
            if (client.group->get_table_name(key).begins_with("class_"))
9,023✔
713
                ++count;
9,023✔
714
        }
9,023✔
715
        return count;
2,155✔
716
    }
2,155✔
717

718
    TableRef get_class(Peer& client, size_t ndx)
719
    {
1,786✔
720
        size_t x = 0;
1,786✔
721
        for (TableKey key : client.group->get_table_keys()) {
4,761✔
722
            if (client.group->get_table_name(key).begins_with("class_")) {
4,761✔
723
                if (x == ndx)
4,761✔
724
                    return client.group->get_table(key);
1,786✔
725
                else
2,975✔
726
                    ++x;
2,975✔
727
            }
4,761✔
728
        }
4,761✔
729
        return TableRef{};
×
730
    }
1,786✔
731
};
732

733
template <class S>
734
void FuzzTester<S>::round(unit_test::TestContext& test_context, std::string path_add_on)
735
{
8✔
736
    m_current_value = 0;
8✔
737

738
    if (m_trace) {
8✔
739
        std::cerr << "auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);\n"
×
740
                  << "auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get());\n";
×
741
    }
×
742
    auto changeset_dump_dir_gen = get_changeset_dump_dir_generator(test_context);
8✔
743
    auto server = Peer::create_server(test_context, changeset_dump_dir_gen.get(), path_add_on);
8✔
744
    std::vector<std::unique_ptr<Peer>> clients(num_clients);
8✔
745
    for (int i = 0; i < num_clients; ++i) {
40✔
746
        using file_ident_type = Peer::file_ident_type;
32✔
747
        file_ident_type client_file_ident = 2 + i;
32✔
748
        if (m_trace) {
32✔
749
            std::cerr << "auto client_" << client_file_ident << " = Peer::create_client(test_context, "
×
750
                      << client_file_ident << ", changeset_dump_dir_gen.get());\n";
×
751
        }
×
752
        clients[i] = Peer::create_client(test_context, client_file_ident, changeset_dump_dir_gen.get(), path_add_on);
32✔
753
    }
32✔
754
    int pending_modifications = num_modifications_per_round;
8✔
755
    std::vector<int> pending_uploads(num_clients);   // One entry per client
8✔
756
    std::vector<int> pending_downloads(num_clients); // One entry per client
8✔
757
    std::vector<int> client_indexes;
8✔
758
    std::vector<action_type> actions;
8✔
759
    for (;;) {
10,248✔
760
        int client_index;
10,248✔
761
        bool can_modify = pending_modifications > 0;
10,248✔
762
        if (can_modify) {
10,248✔
763
            client_index = draw_int_mod(num_clients);
5,769✔
764
        }
5,769✔
765
        else {
4,479✔
766
            client_indexes.clear();
4,479✔
767
            for (int i = 0; i < num_clients; ++i) {
22,395✔
768
                if (pending_uploads[i] > 0 || pending_downloads[i] > 0)
17,916✔
769
                    client_indexes.push_back(i);
17,011✔
770
            }
17,916✔
771
            if (client_indexes.empty())
4,479✔
772
                break;
8✔
773
            client_index = client_indexes[draw_int_mod(client_indexes.size())];
4,471✔
774
        }
4,471✔
775
        Peer& client = *clients[client_index];
10,240✔
776
        if (m_source.chance(1, 2)) {
10,240✔
777
            int time;
5,072✔
778
            if (m_source.chance(1, 16)) {
5,072✔
779
                time = draw_int(-16, -1);
330✔
780
            }
330✔
781
            else {
4,742✔
782
                time = draw_int(1, 5);
4,742✔
783
            }
4,742✔
784
            if (m_trace) {
5,072✔
785
                std::cerr << "client_" << client.local_file_ident
×
786
                          << "->"
×
787
                             "history.advance_time("
×
788
                          << time << ");\n";
×
789
            }
×
790
            client.history.advance_time(time);
5,072✔
791
        }
5,072✔
792
        bool can_upload = pending_uploads[client_index] > 0;
10,240✔
793
        bool can_download = pending_downloads[client_index] > 0;
10,240✔
794
        long long accum_weights = 0;
10,240✔
795
        if (can_modify)
10,240✔
796
            accum_weights += modify_weight;
5,769✔
797
        if (can_upload)
10,240✔
798
            accum_weights += upload_weight;
5,816✔
799
        if (can_download)
10,240✔
800
            accum_weights += download_weight;
10,156✔
801
        REALM_ASSERT(accum_weights > 0);
10,240✔
802
        long long rest_weight = draw_int_mod(accum_weights);
10,240✔
803
        if (can_modify) {
10,240✔
804
            if (rest_weight < modify_weight) {
5,769✔
805
                actions.clear();
2,048✔
806
                if (m_trace) {
2,048✔
807
                    std::cerr << "client_" << client.local_file_ident
×
808
                              << "->"
×
809
                                 "start_transaction();\n";
×
810
                }
×
811
                client.start_transaction();
2,048✔
812
                size_t num_classes = count_classes(client);
2,048✔
813
                bool group_level =
2,048✔
814
                    num_classes == 0 || draw_float<double>() >= group_to_table_level_transition_chance();
2,048✔
815
                if (group_level) {
2,048✔
816
                    get_group_level_modify_actions(num_classes, actions);
289✔
817
                }
289✔
818
                else {
1,759✔
819
                    // Draw a table, but not the special "pk" table.
820
                    TableRef table = get_class(client, draw_int_mod<size_t>(num_classes));
1,759✔
821

822
                    if (m_trace && table != client.selected_table) {
1,759!
823
                        std::cerr << trace_selected_table(client) << " = " << trace_client(client)
×
824
                                  << "->"
×
825
                                     "group->get_table(\""
×
826
                                  << table->get_name() << "\");\n";
×
827
                    }
×
828
                    client.selected_table = table;
1,759✔
829
                    m_unstructured_columns.clear();
1,759✔
830
                    m_link_list_columns.clear();
1,759✔
831
                    m_array_columns.clear();
1,759✔
832
                    size_t n = table->get_column_count();
1,759✔
833
                    for (ColKey key : table->get_column_keys()) {
2,989✔
834
                        if (table->get_primary_key_column() == key)
2,989✔
835
                            continue; // don't make normal modifications to primary keys
1,759✔
836
                        DataType type = table->get_column_type(key);
1,230✔
837
                        if (key.is_list() && type == type_Link) {
1,230✔
838
                            // Only consider LinkList columns that target tables
839
                            // with rows in them.
840
                            if (table->get_link_target(key)->size() != 0) {
×
841
                                m_link_list_columns.push_back(key);
×
842
                            }
×
843
                        }
×
844
                        else if (table->is_list(key)) {
1,230✔
845
                            m_array_columns.push_back(key);
463✔
846
                        }
463✔
847
                        else {
767✔
848
                            m_unstructured_columns.push_back(key);
767✔
849
                        }
767✔
850
                    }
1,230✔
851
                    size_t num_cols = table->get_column_count();
1,759✔
852
                    size_t num_rows = table->size();
1,759✔
853
                    bool table_level = num_rows == 0 || (m_link_list_columns.empty() && m_array_columns.empty()) ||
1,759✔
854
                                       draw_float<double>() >= table_to_array_level_transition_chance();
1,759✔
855
                    if (table_level) {
1,759✔
856
                        get_table_level_modify_actions(num_classes, num_cols, num_rows, actions);
1,368✔
857
                    }
1,368✔
858
                    else {
391✔
859
                        REALM_ASSERT(n > 0); // No columns implies no rows
391✔
860
                        size_t i = draw_int_mod<size_t>(m_link_list_columns.size() + m_array_columns.size());
391✔
861
                        ColKey col_key;
391✔
862
                        bool is_array;
391✔
863
                        if (i >= m_link_list_columns.size()) {
391✔
864
                            col_key = m_array_columns[i - m_link_list_columns.size()];
391✔
865
                            is_array = true;
391✔
866
                        }
391✔
867
                        else {
×
868
                            col_key = m_link_list_columns[i];
×
869
                            is_array = false;
×
870
                        }
×
871

872
                        size_t row_ndx = draw_int_mod<size_t>(num_rows);
391✔
873
                        ObjKey row_key = (table->begin() + row_ndx)->get_key();
391✔
874

875
                        if (is_array) {
391✔
876
                            DataType type = table->get_column_type(col_key);
391✔
877
                            if (type == type_Int) {
391✔
878
                                LstPtr<int64_t> array = table->get_object(row_key).get_list_ptr<int64_t>(col_key);
144✔
879
                                if (m_trace) {
144✔
880
                                    std::cerr << trace_selected_array(client) << " = " << trace_selected_table(client)
×
881
                                              << "->get_object(" << row_key << ").get_list_ptr<int64_t>(" << col_key
×
882
                                              << ");\n";
×
883
                                }
×
884
                                client.selected_array = std::move(array);
144✔
885
                            }
144✔
886
                            else if (type == type_String) {
247✔
887
                                LstPtr<StringData> array =
247✔
888
                                    table->get_object(row_key).get_list_ptr<StringData>(col_key);
247✔
889
                                if (m_trace) {
247✔
890
                                    std::cerr << trace_selected_array(client) << " = " << trace_selected_table(client)
×
891
                                              << "->get_object(" << row_key << ").get_list_ptr<StringData>("
×
892
                                              << col_key << ");\n";
×
893
                                }
×
894
                                client.selected_array = std::move(array);
247✔
895
                            }
247✔
896
                            else {
×
897
                                REALM_TERMINATE("Unsupported list type.");
898
                            }
×
899
                            size_t num_elements = client.selected_array->size();
391✔
900
                            get_array_level_modify_actions(num_elements, actions);
391✔
901
                        }
391✔
902
                        else {
×
903
                            LnkLstPtr link_list = table->get_object(row_key).get_linklist_ptr(col_key);
×
904
                            if (m_trace) {
×
905
                                std::cerr << trace_selected_link_list(client) << " = " << trace_selected_table(client)
×
906
                                          << "->get_object(" << row_key << ").get_linklist_ptr(" << col_key << ");\n";
×
907
                            }
×
908
                            size_t num_links = link_list->size();
×
909
                            client.selected_link_list = std::move(link_list);
×
910
                            get_link_list_level_modify_actions(num_links, actions);
×
911
                        }
×
912
                    }
391✔
913
                }
1,759✔
914
                long long accum_weights_2 = 0;
2,048✔
915
                for (int i = 0; i < int(actions.size()); ++i)
10,905✔
916
                    accum_weights_2 += actions[i].first;
8,857✔
917
                long long rest_weight_2 = draw_int_mod(accum_weights_2);
2,048✔
918
                action_func_type action_func = 0;
2,048✔
919
                for (int i = 0; i < int(actions.size()); ++i) {
6,814✔
920
                    int action_weight = actions[i].first;
6,814✔
921
                    if (rest_weight_2 < action_weight) {
6,814✔
922
                        action_func = actions[i].second;
2,048✔
923
                        break;
2,048✔
924
                    }
2,048✔
925
                    rest_weight_2 -= action_weight;
4,766✔
926
                }
4,766✔
927
                REALM_ASSERT(action_func);
2,048✔
928
                (this->*action_func)(client);
2,048✔
929
                if (m_trace) {
2,048✔
930
                    std::cerr << "client_" << client.local_file_ident
×
931
                              << "->"
×
932
                                 "commit();";
×
933
                }
×
934
                auto produced_version = client.commit();
2,048✔
935
                if (m_trace) {
2,048✔
936
                    std::cerr << " // changeset " << produced_version << '\n';
×
937
                }
×
938
                ++pending_uploads[client_index];
2,048✔
939
                --pending_modifications;
2,048✔
940
                continue;
2,048✔
941
            }
2,048✔
942
            rest_weight -= modify_weight;
3,721✔
943
        }
3,721✔
944
        if (can_upload) {
8,192✔
945
            if (rest_weight < upload_weight) {
4,029✔
946
                if (m_trace) {
2,048✔
947
                    std::cerr << "server->integrate_next_changeset_from"
×
948
                                 "(*client_"
×
949
                              << client.local_file_ident << ");\n";
×
950
                }
×
951
                bool identical_initial_schema_creating_transaction = server->integrate_next_changeset_from(client);
2,048✔
952
                --pending_uploads[client_index];
2,048✔
953
                for (int i = 0; i < num_clients; ++i) {
10,240✔
954
                    if (i != client_index)
8,192✔
955
                        ++pending_downloads[i];
6,144✔
956
                }
8,192✔
957
                if (m_trace && identical_initial_schema_creating_transaction) {
2,048!
958
                    std::cerr << "// Special handling of identical initial "
×
959
                                 "schema-creating transaction occurred\n";
×
960
                }
×
961
                continue;
2,048✔
962
            }
2,048✔
963
            rest_weight -= upload_weight;
1,981✔
964
        }
1,981✔
965
        if (can_download) {
6,144✔
966
            if (rest_weight < download_weight) {
6,144✔
967
                if (m_trace) {
6,144✔
968
                    std::cerr << "client_" << client.local_file_ident
×
969
                              << "->"
×
970
                                 "integrate_next_changeset_from(*server);\n";
×
971
                }
×
972
                client.integrate_next_changeset_from(*server);
6,144✔
973
                --pending_downloads[client_index];
6,144✔
974
                continue;
6,144✔
975
            }
6,144✔
976
            rest_weight -= download_weight;
×
977
        }
×
978
        REALM_ASSERT(false);
×
979
    }
×
980

981
    ReadTransaction rt_0(server->shared_group);
8✔
982
    for (int i = 0; i < num_clients; ++i) {
40✔
983
        ReadTransaction rt_1(clients[i]->shared_group);
32✔
984
        bool same = CHECK(compare_groups(rt_0, rt_1));
32✔
985
        if (!same) {
32✔
986
            std::cout << "Server" << std::endl;
×
987
            rt_0.get_group().to_json(std::cout);
×
988
            std::cout << "Client_" << clients[i]->local_file_ident << std::endl;
×
989
            rt_1.get_group().to_json(std::cout);
×
990
        }
×
991
        CHECK(same);
32✔
992
    }
32✔
993
}
8✔
994

995

996
} // namespace test_util
997
} // namespace realm
998

999
#endif // REALM_TEST_FUZZ_TESTER_HPP
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