Coverage Report

Created: 2026-10-24 08:16

/work/auto_tests/scenarios/scenario_group_sync_test.c
Line
Count
Source (jump to first uncovered line)
1
#include "framework/framework.h"
2
#include <stdio.h>
3
#include <string.h>
4
#include <stdlib.h>
5
6
127
#define NUM_PEERS 5
7
2
#define GROUP_NAME "Sync Test Group"
8
1
#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1)
9
10
typedef struct {
11
    uint32_t group_number;
12
    uint32_t peer_count;
13
    bool connected;
14
    uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
15
    uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH];
16
    size_t topic_len;
17
    uint32_t peers[NUM_PEERS]; // Track peer IDs we've seen
18
    Tox_Group_Role self_role;
19
} SyncState;
20
21
static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data)
22
13
{
23
13
    ToxNode *self = (ToxNode *)user_data;
24
13
    SyncState *state = (SyncState *)tox_node_get_script_ctx(self);
25
13
    state->connected = true;
26
13
    tox_node_log(self, "Joined group");
27
13
}
28
29
static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data)
30
10
{
31
10
    ToxNode *self = (ToxNode *)user_data;
32
10
    SyncState *state = (SyncState *)tox_node_get_script_ctx(self);
33
10
    uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event);
34
35
    // Check if it's ourselves
36
10
    Tox_Err_Group_Self_Query err_peer;
37
10
    uint32_t self_id = tox_group_self_get_peer_id(tox_node_get_tox(self), state->group_number, &err_peer);
38
10
    if (err_peer == TOX_ERR_GROUP_SELF_QUERY_OK && peer_id == self_id) {
39
0
        return;
40
0
    }
41
42
18
    for (uint32_t i = 0; i < state->peer_count; ++i) {
43
8
        if (state->peers[i] == peer_id) {
44
0
            return;
45
0
        }
46
8
    }
47
48
10
    if (state->peer_count < NUM_PEERS) {
49
10
        tox_node_log(self, "Peer joined: %u", peer_id);
50
10
        state->peers[state->peer_count++] = peer_id;
51
10
    }
52
10
}
53
54
static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data)
55
31
{
56
31
    ToxNode *self = (ToxNode *)user_data;
57
31
    SyncState *state = (SyncState *)tox_node_get_script_ctx(self);
58
31
    state->topic_len = tox_event_group_topic_get_topic_length(event);
59
31
    memcpy(state->topic, tox_event_group_topic_get_topic(event), state->topic_len);
60
31
    state->topic[state->topic_len] = '\0';
61
31
    tox_node_log(self, "Topic updated to: %s", state->topic);
62
31
}
63
64
static void common_init(ToxNode *self, SyncState *state)
65
5
{
66
5
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
67
5
    tox_events_callback_group_self_join(dispatch, on_group_self_join);
68
5
    tox_events_callback_group_peer_join(dispatch, on_group_peer_join);
69
5
    tox_events_callback_group_topic(dispatch, on_group_topic);
70
71
5
    tox_node_log(self, "Waiting for self connection...");
72
5
    tox_node_wait_for_self_connected(self);
73
5
    tox_node_log(self, "Connected!");
74
5
}
75
76
static void founder_script(ToxNode *self, void *ctx)
77
1
{
78
1
    SyncState *state = (SyncState *)ctx;
79
1
    Tox *tox = tox_node_get_tox(self);
80
1
    common_init(self, state);
81
82
1
    Tox_Err_Group_New err_new;
83
1
    state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new);
84
1
    ck_assert(err_new == TOX_ERR_GROUP_NEW_OK);
85
1
    state->self_role = TOX_GROUP_ROLE_FOUNDER;
86
87
1
    tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr);
88
89
    // Phase 1: Wait for everyone to join
90
1
    tox_scenario_barrier_wait(self); // Barrier 1: Created
91
92
1
    tox_node_log(self, "Waiting for peers to join (current count: %u)...", state->peer_count);
93
1
    WAIT_UNTIL(state->peer_count >= NUM_PEERS - 1);
94
1
    tox_node_log(self, "All peers joined.");
95
96
    // Phase 2: Topic Sync
97
1
    tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr);
98
1
    tox_scenario_barrier_wait(self); // Barrier 2: Lock disabled
99
100
    // Everyone spams topic
101
1
    tox_node_log(self, "Spamming topic...");
102
1
    for (uint32_t i = 0; i < tox_node_get_index(self); ++i) {
103
0
        tox_scenario_yield(self);
104
0
    }
105
1
    char topic[64];
106
1
    snprintf(topic, sizeof(topic), "Founder Topic %d", rand());
107
1
    bool ok = tox_group_set_topic(tox, state->group_number, (const uint8_t *)topic, strlen(topic), nullptr);
108
1
    ck_assert(ok);
109
110
    // Manually update state because Tox might not call on_group_topic for self
111
1
    state->topic_len = strlen(topic);
112
1
    memcpy(state->topic, topic, state->topic_len);
113
1
    state->topic[state->topic_len] = '\0';
114
115
1
    tox_scenario_barrier_wait(self); // Barrier 3: Topic spam done
116
117
    // Wait for topic convergence
118
1
    tox_node_log(self, "Waiting for topic convergence...");
119
1
    uint64_t last_log = 0;
120
3
    while (1) {
121
3
        bool converged = true;
122
10
        for (int i = 0; i < NUM_PEERS; ++i) {
123
9
            const ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
124
9
            const SyncState *s_view = (const SyncState *)tox_node_get_peer_ctx(node);
125
9
            if (s_view->topic_len != state->topic_len || memcmp(s_view->topic, state->topic, state->topic_len) != 0) {
126
2
                converged = false;
127
2
                if (tox_scenario_get_time(tox_node_get_scenario(self)) > last_log + 5000) {
128
1
                    tox_node_log(self, "Still waiting for %s to converge topic. Expected: %s, Got: %s",
129
1
                                 tox_node_get_alias(node), state->topic, s_view->topic);
130
1
                }
131
2
                break;
132
2
            }
133
9
        }
134
3
        if (converged) {
135
1
            break;
136
1
        }
137
2
        if (tox_scenario_get_time(tox_node_get_scenario(self)) > last_log + 5000) {
138
1
            last_log = tox_scenario_get_time(tox_node_get_scenario(self));
139
1
        }
140
2
        tox_scenario_yield(self);
141
2
    }
142
1
    tox_node_log(self, "Topics converged!");
143
144
    // Phase 3: Role Sync
145
    // Promote everyone to Moderator
146
1
    tox_node_log(self, "Promoting everyone to Moderator...");
147
5
    for (uint32_t i = 0; i < state->peer_count; ++i) {
148
4
        tox_group_set_role(tox, state->group_number, state->peers[i], TOX_GROUP_ROLE_MODERATOR, nullptr);
149
4
    }
150
151
1
    tox_scenario_barrier_wait(self); // Barrier 4: Roles set
152
153
    // Wait for role convergence
154
1
    tox_node_log(self, "Waiting for role convergence...");
155
1
    while (1) {
156
1
        bool converged = true;
157
6
        for (int i = 0; i < NUM_PEERS; ++i) {
158
5
            ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
159
5
            const SyncState *s_view = (const SyncState *)tox_node_get_peer_ctx(node);
160
161
5
            Tox_Group_Role expected = (i == 0 ? TOX_GROUP_ROLE_FOUNDER : TOX_GROUP_ROLE_MODERATOR);
162
5
            if (s_view->self_role != expected) {
163
0
                converged = false;
164
0
                break;
165
0
            }
166
5
        }
167
1
        if (converged) {
168
1
            break;
169
1
        }
170
0
        tox_scenario_yield(self);
171
0
    }
172
1
    tox_node_log(self, "Roles converged!");
173
174
1
    tox_scenario_barrier_wait(self); // Barrier 5: Done
175
1
}
176
177
static void peer_script(ToxNode *self, void *ctx)
178
4
{
179
4
    SyncState *state = (SyncState *)ctx;
180
4
    Tox *tox = tox_node_get_tox(self);
181
4
    common_init(self, state);
182
183
4
    const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0);
184
4
    tox_scenario_barrier_wait(self); // Barrier 1: Created
185
186
4
    const SyncState *founder_view = (const SyncState *)tox_node_get_peer_ctx(founder);
187
188
4
    char name[16];
189
4
    snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self));
190
4
    state->group_number = tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, nullptr);
191
4
    ck_assert(state->group_number != UINT32_MAX);
192
193
4
    WAIT_UNTIL(state->connected);
194
4
    tox_node_log(self, "Joined and connected to group.");
195
196
4
    tox_scenario_barrier_wait(self); // Barrier 2: Lock disabled
197
198
    // Spam topic
199
14
    for (uint32_t i = 0; i < tox_node_get_index(self); ++i) {
200
10
        tox_scenario_yield(self);
201
10
    }
202
4
    char topic[64];
203
4
    snprintf(topic, sizeof(topic), "Peer%u Topic %d", tox_node_get_index(self), rand());
204
4
    tox_node_log(self, "Setting topic: %s", topic);
205
4
    tox_group_set_topic(tox, state->group_number, (const uint8_t *)topic, strlen(topic), nullptr);
206
207
    // Manually update state because Tox might not call on_group_topic for self
208
4
    state->topic_len = strlen(topic);
209
4
    memcpy(state->topic, topic, state->topic_len);
210
4
    state->topic[state->topic_len] = '\0';
211
212
4
    tox_scenario_barrier_wait(self); // Barrier 3: Topic spam done
213
214
    // Observe and publish role for Phase 3
215
4
    tox_node_log(self, "Waiting for Moderator role...");
216
16
    while (tox_scenario_is_running(self)) {
217
16
        state->self_role = tox_group_self_get_role(tox, state->group_number, nullptr);
218
16
        if (state->self_role == TOX_GROUP_ROLE_MODERATOR) {
219
4
            break;
220
4
        }
221
12
        tox_scenario_yield(self);
222
12
    }
223
4
    tox_node_log(self, "Got Moderator role!");
224
225
4
    tox_scenario_barrier_wait(self); // Barrier 4: Roles set
226
227
4
    tox_scenario_barrier_wait(self); // Barrier 5: Done
228
4
}
229
230
int main(int argc, char *argv[])
231
3
{
232
3
    ToxScenario *s = tox_scenario_new(argc, argv, 300000);
233
3
    SyncState states[NUM_PEERS] = {0};
234
3
    ToxNode *nodes[NUM_PEERS];
235
236
3
    nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(SyncState));
237
3
    static char aliases[NUM_PEERS][16];
238
13
    for (int i = 1; i < NUM_PEERS; ++i) {
239
10
        snprintf(aliases[i], sizeof(aliases[i]), "Peer%d", i);
240
10
        nodes[i] = tox_scenario_add_node(s, aliases[i], peer_script, &states[i], sizeof(SyncState));
241
10
    }
242
243
16
    for (int i = 0; i < NUM_PEERS; ++i) {
244
72
        for (int j = 0; j < NUM_PEERS; ++j) {
245
59
            if (i != j) {
246
46
                tox_node_bootstrap(nodes[i], nodes[j]);
247
46
            }
248
59
        }
249
13
    }
250
251
3
    ToxScenarioStatus res = tox_scenario_run(s);
252
3
    if (res != TOX_SCENARIO_DONE) {
253
0
        tox_scenario_log(s, "Test failed with status %u", res);
254
0
        return 1;
255
0
    }
256
257
3
    tox_scenario_free(s);
258
3
    return 0;
259
3
}
260
261
#undef GROUP_NAME
262
#undef GROUP_NAME_LEN
263
#undef NUM_PEERS