Coverage Report

Created: 2026-10-24 08:16

/work/auto_tests/scenarios/scenario_conference_invite_merge_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
typedef struct {
7
    uint32_t conference;
8
    bool connected;
9
    uint32_t peer_count;
10
    bool n1_should_be_offline;
11
    bool coordinator_should_be_offline;
12
} State;
13
14
static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data)
15
19
{
16
19
    ToxNode *self = (ToxNode *)user_data;
17
19
    State *state = (State *)tox_node_get_script_ctx(self);
18
19
19
    state->conference = tox_conference_join(tox_node_get_tox(self),
20
19
                                            tox_event_conference_invite_get_friend_number(event),
21
19
                                            tox_event_conference_invite_get_cookie(event),
22
19
                                            tox_event_conference_invite_get_cookie_length(event),
23
19
                                            nullptr);
24
19
    tox_node_log(self, "Joined conference %u", state->conference);
25
19
}
26
27
static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data)
28
20
{
29
20
    ToxNode *self = (ToxNode *)user_data;
30
20
    State *state = (State *)tox_node_get_script_ctx(self);
31
20
    state->connected = true;
32
20
    tox_node_log(self, "Connected to conference");
33
20
}
34
35
static void node_script(ToxNode *self, void *ctx)
36
2
{
37
2
    const State *state = (const State *)ctx;
38
2
    const Tox *tox = tox_node_get_tox(self);
39
2
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
40
41
2
    tox_events_callback_conference_invite(dispatch, on_conference_invite);
42
2
    tox_events_callback_conference_connected(dispatch, on_conference_connected);
43
44
2
    tox_node_log(self, "Waiting for self connection...");
45
2
    tox_node_wait_for_self_connected(self);
46
2
    tox_node_log(self, "Connected to DHT!");
47
48
    // Wait for all nodes to be DHT-connected
49
12
    for (int i = 0; i < 5; ++i) {
50
10
        ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
51
10
        WAIT_UNTIL(tox_node_is_self_connected(node));
52
10
    }
53
2
    tox_node_log(self, "All nodes connected to DHT!");
54
55
    // Nodes 0, 1, 3, 4 just wait to be invited and connect.
56
    // Coordination is handled by Node 2 (Founder/Coordinator).
57
58
2
    tox_node_log(self, "Waiting for conference connection...");
59
2
    WAIT_UNTIL(state->connected);
60
2
    tox_node_log(self, "Connected to conference!");
61
62
    // In this test, nodes participate in multiple phases.
63
    // They just stay alive until the group is fully merged.
64
2
    WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
65
2
    tox_node_log(self, "Finished!");
66
2
}
67
68
static void coordinator_script(ToxNode *self, void *ctx)
69
1
{
70
1
    State *state = (State *)ctx;
71
1
    Tox *tox = tox_node_get_tox(self);
72
1
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
73
74
1
    tox_events_callback_conference_invite(dispatch, on_conference_invite);
75
1
    tox_events_callback_conference_connected(dispatch, on_conference_connected);
76
77
1
    tox_node_log(self, "Waiting for self connection...");
78
1
    tox_node_wait_for_self_connected(self);
79
1
    tox_node_log(self, "Connected to DHT!");
80
81
    // Wait for all nodes to be DHT-connected
82
6
    for (int i = 0; i < 5; ++i) {
83
5
        ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
84
5
        WAIT_UNTIL(tox_node_is_self_connected(node));
85
5
    }
86
1
    tox_node_log(self, "All nodes connected to DHT!");
87
88
    // 1. Create conference
89
1
    state->conference = tox_conference_new(tox, nullptr);
90
1
    state->connected = true;
91
1
    tox_node_log(self, "Created conference %u", state->conference);
92
93
    // 2. Invite Node 1
94
1
    tox_node_log(self, "Waiting for friend 0 (Node1) to connect...");
95
1
    tox_node_wait_for_friend_connected(self, 0); // Node 1 is friend 0
96
1
    tox_node_log(self, "Friend 0 (Node1) connected! Inviting...");
97
1
    tox_conference_invite(tox, 0, state->conference, nullptr);
98
99
1
    const ToxNode *n1 = tox_scenario_get_node(tox_node_get_scenario(self), 1);
100
1
    const State *s1_view = (const State *)tox_node_get_peer_ctx(n1);
101
1
    WAIT_UNTIL(s1_view->connected);
102
103
    // 3. Node 1 invites Node 0
104
    // Wait for Node 0 to join conference
105
1
    const ToxNode *n0 = tox_scenario_get_node(tox_node_get_scenario(self), 0);
106
1
    const State *s0_view = (const State *)tox_node_get_peer_ctx(n0);
107
1
    tox_node_log(self, "Waiting for Node 0 to join conference...");
108
1
    WAIT_UNTIL(s0_view->connected);
109
1
    tox_node_log(self, "Node 0 joined!");
110
111
    // 4. Split: Node 1 goes offline
112
1
    tox_node_log(self, "Splitting group. Node 1 goes offline.");
113
1
    state->n1_should_be_offline = true;
114
115
    // Wait for Coordinator to see Node 1 is gone (Safe read via mirror)
116
1
    tox_node_log(self, "Waiting for Node 1 to be seen as gone from conference...");
117
1
    WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 2); // Only Coordinator and Node 0 left
118
119
    // 5. Coordinator invites Node 3
120
1
    tox_node_log(self, "Waiting for friend 1 (Node3) to connect...");
121
1
    tox_node_wait_for_friend_connected(self, 1); // Node 3 is friend 1
122
1
    tox_node_log(self, "Friend 1 (Node3) connected! Inviting...");
123
1
    tox_conference_invite(tox, 1, state->conference, nullptr);
124
125
1
    const ToxNode *n3 = tox_scenario_get_node(tox_node_get_scenario(self), 3);
126
1
    const State *s3_view = (const State *)tox_node_get_peer_ctx(n3);
127
1
    WAIT_UNTIL(s3_view->connected);
128
129
    // 6. Node 3 invites Node 4
130
1
    const ToxNode *n4 = tox_scenario_get_node(tox_node_get_scenario(self), 4);
131
1
    const State *s4_view = (const State *)tox_node_get_peer_ctx(n4);
132
1
    tox_node_log(self, "Waiting for Node 4 to join conference...");
133
1
    WAIT_UNTIL(s4_view->connected);
134
1
    tox_node_log(self, "Node 4 joined!");
135
136
    // 7. Coordinator goes offline, Node 1 comes back online
137
1
    tox_node_log(self, "Coordinator goes offline, Node 1 comes back online.");
138
1
    state->n1_should_be_offline = false;
139
1
    tox_node_set_offline(self, true);
140
141
    // Wait in offline mode
142
201
    for (int i = 0; i < 200; ++i) {
143
200
        tox_scenario_yield(self);
144
200
    }
145
146
    // 8. Node 1 merges groups by inviting Node 3
147
    // This is handled in Node 1's script after it observes n1_should_be_offline == false.
148
149
    // 9. Coordinator comes back online
150
1
    tox_node_log(self, "Coming back online.");
151
1
    tox_node_set_offline(self, false);
152
153
    // Coordinator rejoins
154
    // In original test: reload and invite again.
155
    // Here we just re-sync.
156
157
    // Wait for all nodes to be in one group
158
1
    WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
159
1
    tox_node_log(self, "Group merged successfully!");
160
1
}
161
162
static void n1_script(ToxNode *self, void *ctx)
163
1
{
164
1
    const State *state = (const State *)ctx;
165
1
    Tox *tox = tox_node_get_tox(self);
166
1
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
167
168
1
    tox_events_callback_conference_invite(dispatch, on_conference_invite);
169
1
    tox_events_callback_conference_connected(dispatch, on_conference_connected);
170
171
1
    tox_node_log(self, "Waiting for self connection...");
172
1
    tox_node_wait_for_self_connected(self);
173
1
    tox_node_log(self, "Connected to DHT!");
174
175
    // Wait for all nodes to be DHT-connected
176
6
    for (int i = 0; i < 5; ++i) {
177
5
        ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
178
5
        WAIT_UNTIL(tox_node_is_self_connected(node));
179
5
    }
180
1
    tox_node_log(self, "All nodes connected to DHT!");
181
182
    // Phase 1: Wait to join
183
1
    tox_node_log(self, "Waiting for conference connection...");
184
1
    WAIT_UNTIL(state->connected);
185
1
    tox_node_log(self, "Connected to conference!");
186
187
    // Phase 2: Invite Node 0
188
1
    tox_node_log(self, "Waiting for friend 0 (Node0) to connect...");
189
1
    tox_node_wait_for_friend_connected(self, 0); // Node 0 is friend 0
190
1
    tox_node_log(self, "Friend 0 (Node0) connected! Inviting...");
191
1
    tox_conference_invite(tox, 0, state->conference, nullptr);
192
193
    // Phase 3: Split (we will be set offline by Coordinator)
194
    // Observe coordinator's request for our offline status
195
1
    const ToxNode *coordinator = tox_scenario_get_node(tox_node_get_scenario(self), 2);
196
1
    const State *coord_view = (const State *)tox_node_get_peer_ctx(coordinator);
197
198
1
    WAIT_UNTIL(coord_view->n1_should_be_offline);
199
1
    tox_node_log(self, "Going offline as requested by coordinator.");
200
1
    tox_node_set_offline(self, true);
201
202
1
    WAIT_UNTIL(!coord_view->n1_should_be_offline);
203
1
    tox_node_log(self, "Coming back online as requested by coordinator.");
204
1
    tox_node_set_offline(self, false);
205
206
    // Phase 4: Merge groups
207
    // Node 3 is friend 2 for Node 1
208
1
    tox_node_log(self, "Waiting for friend 2 (Node3) to connect...");
209
1
    tox_node_wait_for_friend_connected(self, 2);
210
1
    tox_node_log(self, "Friend 2 (Node3) connected! Inviting to merge...");
211
1
    tox_node_log(self, "Inviting Node 3 to merge groups.");
212
1
    tox_conference_invite(tox, 2, state->conference, nullptr);
213
214
1
    WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
215
1
    tox_node_log(self, "Finished!");
216
1
}
217
218
static void n3_script(ToxNode *self, void *ctx)
219
1
{
220
1
    const State *state = (const State *)ctx;
221
1
    Tox *tox = tox_node_get_tox(self);
222
1
    Tox_Dispatch *dispatch = tox_node_get_dispatch(self);
223
224
1
    tox_events_callback_conference_invite(dispatch, on_conference_invite);
225
1
    tox_events_callback_conference_connected(dispatch, on_conference_connected);
226
227
1
    tox_node_log(self, "Waiting for self connection...");
228
1
    tox_node_wait_for_self_connected(self);
229
1
    tox_node_log(self, "Connected to DHT!");
230
231
    // Wait for all nodes to be DHT-connected
232
6
    for (int i = 0; i < 5; ++i) {
233
5
        ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i);
234
5
        WAIT_UNTIL(tox_node_is_self_connected(node));
235
5
    }
236
1
    tox_node_log(self, "All nodes connected to DHT!");
237
238
    // Phase 2: Wait to join
239
1
    tox_node_log(self, "Waiting for conference connection...");
240
1
    WAIT_UNTIL(state->connected);
241
1
    tox_node_log(self, "Connected to conference!");
242
243
    // Phase 3: Invite Node 4
244
    // Node 3 invites Node 4 when Coordinator is ready.
245
    // We can just wait for Node 1 to go offline, which signals the split.
246
1
    ToxNode *n1 = tox_scenario_get_node(tox_node_get_scenario(self), 1);
247
1
    WAIT_UNTIL(tox_node_is_offline(n1));
248
249
1
    tox_node_log(self, "Waiting for friend 1 (Node4) to connect...");
250
1
    tox_node_wait_for_friend_connected(self, 1); // Node 4 is friend 1
251
1
    tox_node_log(self, "Friend 1 (Node4) connected! Inviting...");
252
1
    tox_conference_invite(tox, 1, state->conference, nullptr);
253
254
1
    WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5);
255
1
    tox_node_log(self, "Finished!");
256
1
}
257
258
int main(int argc, char *argv[])
259
1
{
260
1
    ToxScenario *s = tox_scenario_new(argc, argv, 300000);
261
1
    State states[5] = {0};
262
1
    ToxNode *nodes[5];
263
264
1
    nodes[0] = tox_scenario_add_node(s, "Node0", node_script, &states[0], sizeof(State));
265
1
    nodes[1] = tox_scenario_add_node(s, "Node1", n1_script, &states[1], sizeof(State));
266
1
    nodes[2] = tox_scenario_add_node(s, "Coordinator", coordinator_script, &states[2], sizeof(State));
267
1
    nodes[3] = tox_scenario_add_node(s, "Node3", n3_script, &states[3], sizeof(State));
268
1
    nodes[4] = tox_scenario_add_node(s, "Node4", node_script, &states[4], sizeof(State));
269
270
    // Topology: 0-1-2-3-4
271
1
    tox_node_friend_add(nodes[0], nodes[1]);
272
1
    tox_node_friend_add(nodes[1], nodes[0]);
273
1
    tox_node_friend_add(nodes[1], nodes[2]);
274
1
    tox_node_friend_add(nodes[2], nodes[1]);
275
1
    tox_node_friend_add(nodes[2], nodes[3]);
276
1
    tox_node_friend_add(nodes[3], nodes[2]);
277
1
    tox_node_friend_add(nodes[3], nodes[4]);
278
1
    tox_node_friend_add(nodes[4], nodes[3]);
279
280
    // Merge connection: 1-3
281
1
    tox_node_friend_add(nodes[1], nodes[3]);
282
1
    tox_node_friend_add(nodes[3], nodes[1]);
283
284
6
    for (int i = 0; i < 5; ++i) {
285
30
        for (int j = 0; j < 5; ++j) {
286
25
            if (i != j) {
287
20
                tox_node_bootstrap(nodes[i], nodes[j]);
288
20
            }
289
25
        }
290
5
    }
291
292
1
    ToxScenarioStatus res = tox_scenario_run(s);
293
1
    if (res != TOX_SCENARIO_DONE) {
294
0
        tox_scenario_log(s, "Test failed with status %u", res);
295
0
        return 1;
296
0
    }
297
298
1
    tox_scenario_free(s);
299
1
    return 0;
300
1
}