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