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

realm / realm-core / 2646

22 Jan 2025 04:07PM UTC coverage: 91.131% (+0.007%) from 91.124%
2646

push

Evergreen

web-flow
Sync access token refreshes shouldn't extend SyncSession lifetime (#8064)

102702 of 181514 branches covered (56.58%)

73 of 73 new or added lines in 3 files covered. (100.0%)

65 existing lines in 15 files now uncovered.

217391 of 238549 relevant lines covered (91.13%)

5759073.66 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,021✔
82
        return 7.0 / 8;
2,021✔
83
    }
2,021✔
84

85
    static constexpr double table_to_array_level_transition_chance()
86
    {
427✔
87
        return 7.0 / 8;
427✔
88
    }
427✔
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,120✔
121
        return m_source.template draw_int<T>(min, max);
5,120✔
122
    }
5,120✔
123

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

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

136
    template <class T>
137
    T draw_float()
138
    {
2,448✔
139
        return m_source.template draw_float<T>();
2,448✔
140
    }
2,448✔
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
    {
253✔
197
        char table_name[] = {'c', 'l', 'a', 's', 's', '_', 0, 0};
253✔
198
        table_name[6] = 'A' + draw_int_mod(6); // pick a random letter A-F
253✔
199

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

203
        bool is_string_pk = (table_name[6] == 'B');
75✔
204
        if (m_trace) {
75✔
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");
75✔
216
    }
75✔
217

218
    void erase_table(Peer& client)
219
    {
14✔
220
        size_t num_tables = count_classes(client);
14✔
221
        size_t table_ndx = draw_int_mod(num_tables);
14✔
222
        TableRef table = get_class(client, table_ndx);
14✔
223
        if (m_trace) {
14✔
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());
14✔
230
    }
14✔
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
    {
62✔
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"};
62✔
248
        const DataType column_types[] = {type_Int, type_Int, type_String, type_String};
62✔
249
        const bool column_nullable[] = {false, true, false, true};
62✔
250

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

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

260
        if (m_trace) {
56✔
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);
56✔
277
        m_unstructured_columns.push_back(col_key);
56✔
278
    }
56✔
279

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

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

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

290
        TableRef table = client.selected_table;
41✔
291
        if (table->get_column_key(name))
41✔
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");
41✔
296
        if (!link_target_table_key)
41✔
297
            return;
41✔
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
    {
35✔
325
        REALM_ASSERT(count_classes(client) >= 1);
35✔
326

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

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

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

339
        if (m_trace) {
35✔
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);
35✔
355
        m_array_columns.push_back(col_key);
35✔
356
    }
35✔
357

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

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

371
        if (type == type_Int) {
134✔
372
            int_fast64_t value = next_value();
51✔
373
            if (nullable && value % 7 == 0) {
51✔
374
                bool is_default = (value % 21 == 0);
3✔
375
                if (m_trace) {
3✔
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);
3✔
380
                return;
3✔
381
            }
3✔
382
            else {
48✔
383
                if (value % 3 == 0 && (!nullable || !obj.is_null(col_key))) {
48✔
384
                    if (m_trace) {
10✔
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);
10✔
389
                }
10✔
390
                else {
38✔
391
                    bool is_default = (value % 13 == 0);
38✔
392
                    if (m_trace) {
38✔
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);
38✔
397
                }
38✔
398
                return;
48✔
399
            }
48✔
400
        }
51✔
401

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

405
            if (nullable && ival % 7 == 0) {
83✔
406
                bool is_default = (ival % 21 == 0);
5✔
407
                if (m_trace) {
5✔
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);
5✔
412
                return;
5✔
413
            }
5✔
414
            else {
78✔
415
                std::stringstream ss;
78✔
416
                ss << ival;
78✔
417
                std::string value = ss.str();
78✔
418

419
                bool is_default = (ival % 13 == 0);
78✔
420
                if (m_trace) {
78✔
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);
78✔
425
                return;
78✔
426
            }
78✔
427
        }
83✔
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
    {
736✔
454
        ColKey pk_col_key = client.selected_table->get_column_key("pk");
736✔
455

456
        char string_buffer[2] = {0};
736✔
457
        bool is_string_pk = (client.selected_table->get_column_type(pk_col_key) == type_String);
736✔
458
        int_fast64_t pk_int = 0;
736✔
459
        StringData pk_string;
736✔
460
        if (is_string_pk) {
736✔
461
            string_buffer[0] = 'a' + draw_int_max(25); // "a" to "z"
92✔
462
            pk_string = StringData{string_buffer, 1};
92✔
463
        }
92✔
464
        else {
644✔
465
            pk_int = draw_int_max(10); // Low number to ensure some collisions
644✔
466
        }
644✔
467
        if (m_trace) {
736✔
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)
736✔
476
            client.selected_table->create_object_with_primary_key(pk_string);
92✔
477
        else
644✔
478
            client.selected_table->create_object_with_primary_key(pk_int);
644✔
479
    }
736✔
480

481
    void move_last_row_over(Peer& client)
482
    {
392✔
483
        size_t num_rows = client.selected_table->size();
392✔
484
        size_t row_ndx = draw_int_mod(num_rows);
392✔
485
        ObjKey row_key = (client.selected_table->begin() + row_ndx)->get_key();
392✔
486
        if (m_trace) {
392✔
487
            std::cerr << trace_selected_table(client) << "->remove_object(" << row_key << ");\n";
×
488
        }
×
489
        client.selected_table->remove_object(row_key);
392✔
490
    }
392✔
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
    {
52✔
561
        size_t num_elements = client.selected_array->size();
52✔
562
        DataType type = client.selected_array->get_table()->get_column_type(client.selected_array->get_col_key());
52✔
563
        size_t ndx = draw_int_max(num_elements - 1);
52✔
564
        if (type == type_Int) {
52✔
565
            int_fast64_t value = draw_int_max(1000);
25✔
566
            if (m_trace) {
25✔
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);
25✔
570
        }
25✔
571
        else {
27✔
572
            StringData value = "abc";
27✔
573
            if (m_trace) {
27✔
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);
27✔
577
        }
27✔
578
    }
52✔
579

580
    void array_insert(Peer& client)
581
    {
275✔
582
        size_t num_elements = client.selected_array->size();
275✔
583
        DataType type = client.selected_array->get_table()->get_column_type(client.selected_array->get_col_key());
275✔
584
        size_t ndx = draw_int_max(num_elements);
275✔
585
        if (type == type_Int) {
275✔
586
            if (m_trace) {
148✔
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);
148✔
590
        }
148✔
591
        else if (type == type_String) {
127✔
592
            if (m_trace) {
127✔
593
                std::cerr << trace_selected_string_array(client) << "->insert(" << ndx << ", \"\");\n";
×
594
            }
×
595
            static_cast<Lst<StringData>*>(client.selected_array.get())->insert(ndx, "");
127✔
596
        }
127✔
597
    }
275✔
598

599
    void array_remove(Peer& client)
600
    {
51✔
601
        size_t num_elements = client.selected_array->size();
51✔
602
        size_t ndx = draw_int_max(num_elements - 1);
51✔
603
        if (m_trace) {
51✔
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);
51✔
610
    }
51✔
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
    {
134✔
649
        return ++m_current_value;
134✔
650
    }
134✔
651

652
    void get_group_level_modify_actions(size_t num_classes, std::vector<action_type>& actions)
653
    {
267✔
654
        if (num_classes >= 1)
267✔
655
            actions.push_back(std::make_pair(rename_table_weight + 0, &FuzzTester<Source>::rename_table));
240✔
656
        if (true)
267✔
657
            actions.push_back(std::make_pair(add_table_weight + 0, &FuzzTester<Source>::add_table));
267✔
658
        if (num_classes >= 1)
267✔
659
            actions.push_back(std::make_pair(erase_table_weight + 0, &FuzzTester<Source>::erase_table));
240✔
660
    }
267✔
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,400✔
665
        if (true)
1,400✔
666
            actions.push_back(std::make_pair(insert_column_weight + 0, &FuzzTester<Source>::insert_column));
1,400✔
667
        if (num_classes > 1)
1,400✔
668
            actions.push_back(std::make_pair(insert_link_column_weight + 0, &FuzzTester<Source>::insert_link_column));
1,351✔
669
        if (num_classes >= 1)
1,400✔
670
            actions.push_back(
1,400✔
671
                std::make_pair(insert_array_column_weight + 0, &FuzzTester<Source>::insert_array_column));
1,400✔
672
        if (num_rows >= 1 && !m_unstructured_columns.empty())
1,400✔
673
            actions.push_back(std::make_pair(update_row_weight + 0, &FuzzTester<Source>::update_row));
446✔
674
        if (num_cols >= 1)
1,400✔
675
            actions.push_back(std::make_pair(insert_row_weight + 0, &FuzzTester<Source>::insert_row));
1,400✔
676
        if (num_rows >= 1)
1,400✔
677
            actions.push_back(std::make_pair(erase_row_weight + 0, &FuzzTester<Source>::move_last_row_over));
1,141✔
678
    }
1,400✔
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
    {
381✔
696
        if (num_elements >= 1)
381✔
697
            actions.push_back(std::make_pair(array_set_weight + 0, &FuzzTester<Source>::array_set));
191✔
698
        if (true)
381✔
699
            actions.push_back(std::make_pair(array_insert_weight + 0, &FuzzTester<Source>::array_insert));
381✔
700
        if (num_elements >= 1)
381✔
701
            actions.push_back(std::make_pair(array_remove_weight + 0, &FuzzTester<Source>::array_remove));
191✔
702
        // if (num_elements >= 2)
703
        //     actions.push_back(std::make_pair(array_move_weight+0, &FuzzTester<Source>::array_move));
704
        if (true)
381✔
705
            actions.push_back(std::make_pair(array_clear_weight + 0, &FuzzTester<Source>::array_clear));
381✔
706
    }
381✔
707

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

718
    TableRef get_class(Peer& client, size_t ndx)
719
    {
1,795✔
720
        size_t x = 0;
1,795✔
721
        for (TableKey key : client.group->get_table_keys()) {
4,743✔
722
            if (client.group->get_table_name(key).begins_with("class_")) {
4,743✔
723
                if (x == ndx)
4,743✔
724
                    return client.group->get_table(key);
1,795✔
725
                else
2,948✔
726
                    ++x;
2,948✔
727
            }
4,743✔
728
        }
4,743✔
729
        return TableRef{};
×
730
    }
1,795✔
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,899✔
764
        }
5,899✔
765
        else {
4,349✔
766
            client_indexes.clear();
4,349✔
767
            for (int i = 0; i < num_clients; ++i) {
21,745✔
768
                if (pending_uploads[i] > 0 || pending_downloads[i] > 0)
17,396✔
769
                    client_indexes.push_back(i);
16,518✔
770
            }
17,396✔
771
            if (client_indexes.empty())
4,349✔
772
                break;
8✔
773
            client_index = client_indexes[draw_int_mod(client_indexes.size())];
4,341✔
774
        }
4,341✔
775
        Peer& client = *clients[client_index];
10,240✔
776
        if (m_source.chance(1, 2)) {
10,240✔
777
            int time;
5,120✔
778
            if (m_source.chance(1, 16)) {
5,120✔
779
                time = draw_int(-16, -1);
341✔
780
            }
341✔
781
            else {
4,779✔
782
                time = draw_int(1, 5);
4,779✔
783
            }
4,779✔
784
            if (m_trace) {
5,120✔
785
                std::cerr << "client_" << client.local_file_ident
×
786
                          << "->"
×
787
                             "history.advance_time("
×
788
                          << time << ");\n";
×
789
            }
×
790
            client.history.advance_time(time);
5,120✔
791
        }
5,120✔
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,899✔
797
        if (can_upload)
10,240✔
798
            accum_weights += upload_weight;
5,819✔
799
        if (can_download)
10,240✔
800
            accum_weights += download_weight;
10,155✔
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,899✔
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);
267✔
817
                }
267✔
818
                else {
1,781✔
819
                    // Draw a table, but not the special "pk" table.
820
                    TableRef table = get_class(client, draw_int_mod<size_t>(num_classes));
1,781✔
821

822
                    if (m_trace && table != client.selected_table) {
1,781!
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,781✔
829
                    m_unstructured_columns.clear();
1,781✔
830
                    m_link_list_columns.clear();
1,781✔
831
                    m_array_columns.clear();
1,781✔
832
                    size_t n = table->get_column_count();
1,781✔
833
                    for (ColKey key : table->get_column_keys()) {
3,096✔
834
                        if (table->get_primary_key_column() == key)
3,096✔
835
                            continue; // don't make normal modifications to primary keys
1,781✔
836
                        DataType type = table->get_column_type(key);
1,315✔
837
                        if (key.is_list() && type == type_Link) {
1,315✔
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,315✔
845
                            m_array_columns.push_back(key);
501✔
846
                        }
501✔
847
                        else {
814✔
848
                            m_unstructured_columns.push_back(key);
814✔
849
                        }
814✔
850
                    }
1,315✔
851
                    size_t num_cols = table->get_column_count();
1,781✔
852
                    size_t num_rows = table->size();
1,781✔
853
                    bool table_level = num_rows == 0 || (m_link_list_columns.empty() && m_array_columns.empty()) ||
1,781✔
854
                                       draw_float<double>() >= table_to_array_level_transition_chance();
1,781✔
855
                    if (table_level) {
1,781✔
856
                        get_table_level_modify_actions(num_classes, num_cols, num_rows, actions);
1,400✔
857
                    }
1,400✔
858
                    else {
381✔
859
                        REALM_ASSERT(n > 0); // No columns implies no rows
381✔
860
                        size_t i = draw_int_mod<size_t>(m_link_list_columns.size() + m_array_columns.size());
381✔
861
                        ColKey col_key;
381✔
862
                        bool is_array;
381✔
863
                        if (i >= m_link_list_columns.size()) {
381✔
864
                            col_key = m_array_columns[i - m_link_list_columns.size()];
381✔
865
                            is_array = true;
381✔
866
                        }
381✔
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);
381✔
873
                        ObjKey row_key = (table->begin() + row_ndx)->get_key();
381✔
874

875
                        if (is_array) {
381✔
876
                            DataType type = table->get_column_type(col_key);
381✔
877
                            if (type == type_Int) {
381✔
878
                                LstPtr<int64_t> array = table->get_object(row_key).get_list_ptr<int64_t>(col_key);
199✔
879
                                if (m_trace) {
199✔
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);
199✔
885
                            }
199✔
886
                            else if (type == type_String) {
182✔
887
                                LstPtr<StringData> array =
182✔
888
                                    table->get_object(row_key).get_list_ptr<StringData>(col_key);
182✔
889
                                if (m_trace) {
182✔
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);
182✔
895
                            }
182✔
896
                            else {
×
897
                                REALM_TERMINATE("Unsupported list type.");
898
                            }
×
899
                            size_t num_elements = client.selected_array->size();
381✔
900
                            get_array_level_modify_actions(num_elements, actions);
381✔
901
                        }
381✔
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
                    }
381✔
913
                }
1,781✔
914
                long long accum_weights_2 = 0;
2,048✔
915
                for (int i = 0; i < int(actions.size()); ++i)
11,077✔
916
                    accum_weights_2 += actions[i].first;
9,029✔
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) {
7,019✔
920
                    int action_weight = actions[i].first;
7,019✔
921
                    if (rest_weight_2 < action_weight) {
7,019✔
922
                        action_func = actions[i].second;
2,048✔
923
                        break;
2,048✔
924
                    }
2,048✔
925
                    rest_weight_2 -= action_weight;
4,971✔
926
                }
4,971✔
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,851✔
943
        }
3,851✔
944
        if (can_upload) {
8,192✔
945
            if (rest_weight < upload_weight) {
4,092✔
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;
2,044✔
964
        }
2,044✔
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