/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 | } |