/work/auto_tests/conference_test.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Auto Tests: Conferences. |
2 | | */ |
3 | | |
4 | | #include <stdlib.h> |
5 | | #include <string.h> |
6 | | #include <time.h> |
7 | | #include <stdint.h> |
8 | | |
9 | | #include "../toxcore/util.h" |
10 | | |
11 | | #include "check_compat.h" |
12 | | |
13 | 2.20k | #define NUM_GROUP_TOX 16 |
14 | 18 | #define NUM_DISCONNECT 8 |
15 | 48 | #define GROUP_MESSAGE "Install Gentoo" |
16 | | |
17 | 288 | #define NAMELEN 9 |
18 | 16 | #define NAME_FORMAT_STR "Tox #%4u" |
19 | 0 | #define NEW_NAME_FORMAT_STR "New #%4u" |
20 | | |
21 | | typedef struct State { |
22 | | bool invited_next; |
23 | | } State; |
24 | | |
25 | | #include "auto_test_support.h" |
26 | | |
27 | | static void handle_self_connection_status( |
28 | | Tox *tox, Tox_Connection connection_status, void *user_data) |
29 | 42 | { |
30 | 42 | const AutoTox *autotox = (AutoTox *)user_data; |
31 | | |
32 | 42 | if (connection_status != TOX_CONNECTION_NONE) { |
33 | 21 | printf("tox #%u: is now connected\n", autotox->index); |
34 | 21 | } else { |
35 | 21 | printf("tox #%u: is now disconnected\n", autotox->index); |
36 | 21 | } |
37 | 42 | } |
38 | | |
39 | | static void handle_friend_connection_status( |
40 | | Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) |
41 | 57 | { |
42 | 57 | const AutoTox *autotox = (AutoTox *)user_data; |
43 | | |
44 | 57 | if (connection_status != TOX_CONNECTION_NONE) { |
45 | 28 | printf("tox #%u: is now connected to friend %u\n", autotox->index, friendnumber); |
46 | 29 | } else { |
47 | 29 | printf("tox #%u: is now disconnected from friend %u\n", autotox->index, friendnumber); |
48 | 29 | } |
49 | 57 | } |
50 | | |
51 | | static void handle_conference_invite( |
52 | | Tox *tox, uint32_t friendnumber, Tox_Conference_Type type, |
53 | | const uint8_t *data, size_t length, void *user_data) |
54 | 15 | { |
55 | 15 | const AutoTox *autotox = (AutoTox *)user_data; |
56 | 15 | ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", autotox->index, type); |
57 | | |
58 | 15 | Tox_Err_Conference_Join err; |
59 | 15 | uint32_t g_num = tox_conference_join(tox, friendnumber, data, length, &err); |
60 | | |
61 | 15 | ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", autotox->index, err); |
62 | 15 | ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", autotox->index); |
63 | | |
64 | | // Try joining again. We should only be allowed to join once. |
65 | 15 | tox_conference_join(tox, friendnumber, data, length, &err); |
66 | 15 | ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK, |
67 | 15 | "tox #%u: joining groupchat twice should be impossible.", autotox->index); |
68 | 15 | } |
69 | | |
70 | | static void handle_conference_connected( |
71 | | Tox *tox, uint32_t conference_number, void *user_data) |
72 | 30 | { |
73 | 30 | const AutoTox *autotox = (AutoTox *)user_data; |
74 | 30 | State *state = (State *)autotox->state; |
75 | | |
76 | 30 | if (state->invited_next || tox_self_get_friend_list_size(tox) <= 1) { |
77 | 2 | return; |
78 | 2 | } |
79 | | |
80 | 28 | Tox_Err_Conference_Invite err; |
81 | 28 | tox_conference_invite(tox, 1, 0, &err); |
82 | 28 | ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", autotox->index, |
83 | 28 | err); |
84 | 28 | printf("tox #%u: invited next friend\n", autotox->index); |
85 | 28 | state->invited_next = true; |
86 | 28 | } |
87 | | |
88 | | static uint32_t num_recv; |
89 | | |
90 | | static void handle_conference_message( |
91 | | Tox *tox, uint32_t groupnumber, uint32_t peernumber, Tox_Message_Type type, |
92 | | const uint8_t *message, size_t length, void *user_data) |
93 | 16 | { |
94 | 16 | if (length == (sizeof(GROUP_MESSAGE) - 1) && memcmp(message, GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1) == 0) { |
95 | 16 | ++num_recv; |
96 | 16 | } |
97 | 16 | } |
98 | | |
99 | | static bool toxes_are_disconnected_from_group(uint32_t tox_count, AutoTox *autotoxes, |
100 | | const bool *disconnected) |
101 | 234 | { |
102 | 234 | uint32_t num_disconnected = 0; |
103 | | |
104 | 3.97k | for (uint32_t i = 0; i < tox_count; ++i) { |
105 | 3.74k | num_disconnected += disconnected[i]; |
106 | 3.74k | } |
107 | | |
108 | 489 | for (uint32_t i = 0; i < tox_count; i++) { |
109 | 485 | if (disconnected[i]) { |
110 | 218 | continue; |
111 | 218 | } |
112 | | |
113 | 267 | if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) > tox_count - num_disconnected) { |
114 | 230 | return false; |
115 | 230 | } |
116 | 267 | } |
117 | | |
118 | 4 | return true; |
119 | 234 | } |
120 | | |
121 | | static void disconnect_toxes(uint32_t tox_count, AutoTox *autotoxes, |
122 | | const bool *disconnect, const bool *exclude) |
123 | 2 | { |
124 | | /* Fake a network outage for a set of peers D by iterating only the other |
125 | | * peers D' until the connections time out according to D', then iterating |
126 | | * only D until the connections time out according to D. */ |
127 | | |
128 | 2 | VLA(bool, disconnect_now, tox_count); |
129 | 2 | bool invert = false; |
130 | | |
131 | 4 | do { |
132 | 68 | for (uint32_t i = 0; i < tox_count; ++i) { |
133 | 64 | disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); |
134 | 64 | } |
135 | | |
136 | 234 | do { |
137 | 3.97k | for (uint32_t i = 0; i < tox_count; ++i) { |
138 | 3.74k | if (!disconnect_now[i]) { |
139 | 1.39k | tox_iterate(autotoxes[i].tox, &autotoxes[i]); |
140 | 1.39k | autotoxes[i].clock += 1000; |
141 | 1.39k | } |
142 | 3.74k | } |
143 | | |
144 | 234 | c_sleep(20); |
145 | 234 | } while (!toxes_are_disconnected_from_group(tox_count, autotoxes, disconnect_now)); |
146 | | |
147 | 4 | invert = !invert; |
148 | 4 | } while (invert); |
149 | 2 | } |
150 | | |
151 | | static bool all_connected_to_group(uint32_t tox_count, AutoTox *autotoxes) |
152 | 214 | { |
153 | 994 | for (uint32_t i = 0; i < tox_count; i++) { |
154 | 992 | if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) < tox_count) { |
155 | 212 | return false; |
156 | 212 | } |
157 | 992 | } |
158 | | |
159 | 2 | return true; |
160 | 214 | } |
161 | | |
162 | | static bool names_propagated(uint32_t tox_count, AutoTox *autotoxes) |
163 | 1 | { |
164 | 17 | for (uint32_t i = 0; i < tox_count; ++i) { |
165 | 272 | for (uint32_t j = 0; j < tox_count; ++j) { |
166 | 256 | const size_t len = tox_conference_peer_get_name_size(autotoxes[i].tox, 0, j, nullptr); |
167 | | |
168 | 256 | if (len != NAMELEN) { |
169 | 0 | return false; |
170 | 0 | } |
171 | 256 | } |
172 | 16 | } |
173 | | |
174 | 1 | return true; |
175 | 1 | } |
176 | | |
177 | | |
178 | | /** |
179 | | * returns a random index at which a list of booleans is false |
180 | | * (some such index is required to exist) |
181 | | */ |
182 | | static uint32_t random_false_index(const Random *rng, const bool *list, const uint32_t length) |
183 | 24 | { |
184 | 24 | uint32_t index; |
185 | | |
186 | 30 | do { |
187 | 30 | index = random_u32(rng) % length; |
188 | 30 | } while (list[index]); |
189 | | |
190 | 24 | return index; |
191 | 24 | } |
192 | | |
193 | | static void run_conference_tests(AutoTox *autotoxes) |
194 | 1 | { |
195 | 1 | const Random *rng = os_random(); |
196 | 1 | ck_assert(rng != nullptr); |
197 | | /* disabling name change propagation check for now, as it occasionally |
198 | | * fails due to disconnections too short to trigger freezing */ |
199 | 1 | const bool check_name_change_propagation = false; |
200 | | |
201 | | /* each peer should freeze at least its two friends, but freezing more |
202 | | * should not be necessary */ |
203 | 1 | const uint32_t max_frozen = max_u32(2, NUM_DISCONNECT / 2); |
204 | 1 | printf("restricting number of frozen peers to %u\n", max_frozen); |
205 | | |
206 | 17 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
207 | 16 | Tox_Err_Conference_Set_Max_Offline err; |
208 | 16 | tox_conference_set_max_offline(autotoxes[i].tox, 0, max_frozen, &err); |
209 | 16 | ck_assert_msg(err == TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK, |
210 | 16 | "tox #%u failed to set max offline: err = %d", autotoxes[i].index, err); |
211 | 16 | } |
212 | | |
213 | 1 | printf("letting random toxes timeout\n"); |
214 | 1 | bool disconnected[NUM_GROUP_TOX] = {0}; |
215 | 1 | bool restarting[NUM_GROUP_TOX] = {0}; |
216 | | |
217 | 1 | ck_assert(NUM_DISCONNECT < NUM_GROUP_TOX); |
218 | | |
219 | 9 | for (uint32_t i = 0; i < NUM_DISCONNECT; ++i) { |
220 | 8 | uint32_t disconnect = random_false_index(rng, disconnected, NUM_GROUP_TOX); |
221 | 8 | disconnected[disconnect] = true; |
222 | | |
223 | 8 | if (i < NUM_DISCONNECT / 2) { |
224 | 4 | restarting[disconnect] = true; |
225 | 4 | printf("Restarting #%u\n", autotoxes[disconnect].index); |
226 | 4 | } else { |
227 | 4 | printf("Disconnecting #%u\n", autotoxes[disconnect].index); |
228 | 4 | } |
229 | 8 | } |
230 | | |
231 | 1 | uint8_t *save[NUM_GROUP_TOX]; |
232 | 1 | size_t save_size[NUM_GROUP_TOX]; |
233 | | |
234 | 17 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
235 | 16 | if (restarting[i]) { |
236 | 4 | save_size[i] = tox_get_savedata_size(autotoxes[i].tox); |
237 | 4 | ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]); |
238 | 4 | save[i] = (uint8_t *)malloc(save_size[i]); |
239 | 4 | ck_assert_msg(save[i] != nullptr, "malloc failed"); |
240 | 4 | tox_get_savedata(autotoxes[i].tox, save[i]); |
241 | 4 | tox_kill(autotoxes[i].tox); |
242 | 4 | } |
243 | 16 | } |
244 | | |
245 | 1 | disconnect_toxes(NUM_GROUP_TOX, autotoxes, disconnected, restarting); |
246 | | |
247 | 17 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
248 | 16 | if (restarting[i]) { |
249 | 4 | struct Tox_Options *const options = tox_options_new(nullptr); |
250 | 4 | ck_assert(options != nullptr); |
251 | 4 | tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); |
252 | 4 | tox_options_set_savedata_data(options, save[i], save_size[i]); |
253 | 4 | autotoxes[i].tox = tox_new_log(options, nullptr, &autotoxes[i].index); |
254 | 4 | ck_assert(autotoxes[i].tox != nullptr); |
255 | 4 | tox_options_free(options); |
256 | 4 | free(save[i]); |
257 | | |
258 | 4 | set_mono_time_callback(&autotoxes[i]); |
259 | 4 | tox_conference_set_max_offline(autotoxes[i].tox, 0, max_frozen, nullptr); |
260 | 4 | } |
261 | 16 | } |
262 | | |
263 | 1 | if (check_name_change_propagation) { |
264 | 0 | printf("changing names\n"); |
265 | |
|
266 | 0 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
267 | 0 | char name[NAMELEN + 1]; |
268 | 0 | snprintf(name, NAMELEN + 1, NEW_NAME_FORMAT_STR, autotoxes[i].index); |
269 | 0 | tox_self_set_name(autotoxes[i].tox, (const uint8_t *)name, NAMELEN, nullptr); |
270 | 0 | } |
271 | 0 | } |
272 | | |
273 | 17 | for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { |
274 | 16 | const uint32_t num_frozen = tox_conference_offline_peer_count(autotoxes[i].tox, 0, nullptr); |
275 | 16 | ck_assert_msg(num_frozen <= max_frozen, |
276 | 16 | "tox #%u has too many offline peers: %u\n", |
277 | 16 | autotoxes[i].index, num_frozen); |
278 | 16 | } |
279 | | |
280 | 1 | printf("reconnecting toxes\n"); |
281 | | |
282 | 152 | do { |
283 | 152 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
284 | 152 | } while (!all_connected_to_group(NUM_GROUP_TOX, autotoxes)); |
285 | | |
286 | 1 | printf("running conference tests\n"); |
287 | | |
288 | 17 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
289 | 16 | tox_callback_conference_message(autotoxes[i].tox, &handle_conference_message); |
290 | | |
291 | 16 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
292 | 16 | } |
293 | | |
294 | 1 | Tox_Err_Conference_Send_Message err; |
295 | 1 | ck_assert_msg( |
296 | 1 | tox_conference_send_message( |
297 | 1 | autotoxes[random_u32(rng) % NUM_GROUP_TOX].tox, 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)GROUP_MESSAGE, |
298 | 1 | sizeof(GROUP_MESSAGE) - 1, &err) != 0, "failed to send group message"); |
299 | 1 | ck_assert_msg( |
300 | 1 | err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, "failed to send group message"); |
301 | 1 | num_recv = 0; |
302 | | |
303 | 33 | for (uint8_t j = 0; j < NUM_GROUP_TOX * 2; ++j) { |
304 | 32 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
305 | 32 | } |
306 | | |
307 | 1 | ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); |
308 | | |
309 | 1 | if (check_name_change_propagation) { |
310 | 0 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
311 | 0 | for (uint32_t j = 0; j < NUM_GROUP_TOX; ++j) { |
312 | 0 | uint8_t name[NAMELEN]; |
313 | 0 | tox_conference_peer_get_name(autotoxes[i].tox, 0, j, name, nullptr); |
314 | | /* Note the toxes will have been reordered */ |
315 | 0 | ck_assert_msg(memcmp(name, "New", 3) == 0, |
316 | 0 | "name of #%u according to #%u not updated", autotoxes[j].index, autotoxes[i].index); |
317 | 0 | } |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | 17 | for (uint32_t k = NUM_GROUP_TOX; k != 0 ; --k) { |
322 | 16 | tox_conference_delete(autotoxes[k - 1].tox, 0, nullptr); |
323 | | |
324 | 272 | for (uint8_t j = 0; j < 10 || j < NUM_GROUP_TOX; ++j) { |
325 | 256 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
326 | 256 | } |
327 | | |
328 | 136 | for (uint32_t i = 0; i < k - 1; ++i) { |
329 | 120 | uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, nullptr); |
330 | 120 | ck_assert_msg(peer_count == (k - 1), "\n\tBad number of group peers (post check)." |
331 | 120 | "\n\t\t\tExpected: %u but tox_instance(%u) only has: %u\n\n", |
332 | 120 | k - 1, i, (unsigned)peer_count); |
333 | 120 | } |
334 | 16 | } |
335 | 1 | } |
336 | | |
337 | | static void test_many_group(AutoTox *autotoxes) |
338 | 1 | { |
339 | 1 | const time_t test_start_time = time(nullptr); |
340 | | |
341 | 17 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
342 | 16 | tox_callback_self_connection_status(autotoxes[i].tox, &handle_self_connection_status); |
343 | 16 | tox_callback_friend_connection_status(autotoxes[i].tox, &handle_friend_connection_status); |
344 | 16 | tox_callback_conference_invite(autotoxes[i].tox, &handle_conference_invite); |
345 | 16 | tox_callback_conference_connected(autotoxes[i].tox, &handle_conference_connected); |
346 | | |
347 | 16 | char name[NAMELEN + 1]; |
348 | 16 | snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, autotoxes[i].index); |
349 | 16 | tox_self_set_name(autotoxes[i].tox, (const uint8_t *)name, NAMELEN, nullptr); |
350 | 16 | } |
351 | | |
352 | 1 | ck_assert_msg(tox_conference_new(autotoxes[0].tox, nullptr) != UINT32_MAX, "failed to create group"); |
353 | 1 | printf("tox #%u: inviting its first friend\n", autotoxes[0].index); |
354 | 1 | ck_assert_msg(tox_conference_invite(autotoxes[0].tox, 0, 0, nullptr) != 0, "failed to invite friend"); |
355 | 1 | ((State *)autotoxes[0].state)->invited_next = true; |
356 | 1 | ck_assert_msg(tox_conference_set_title(autotoxes[0].tox, 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, |
357 | 1 | nullptr) != 0, |
358 | 1 | "failed to set group title"); |
359 | | |
360 | | |
361 | 1 | printf("waiting for invitations to be made\n"); |
362 | 1 | uint32_t invited_count = 0; |
363 | | |
364 | 15 | do { |
365 | 15 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
366 | | |
367 | 15 | invited_count = 0; |
368 | | |
369 | 255 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
370 | 240 | invited_count += ((State *)autotoxes[i].state)->invited_next; |
371 | 240 | } |
372 | 15 | } while (invited_count != NUM_GROUP_TOX - 1); |
373 | | |
374 | 1 | uint64_t pregroup_clock = autotoxes[0].clock; |
375 | 1 | printf("waiting for all toxes to be in the group\n"); |
376 | 1 | uint32_t fully_connected_count = 0; |
377 | | |
378 | 9 | do { |
379 | 9 | fully_connected_count = 0; |
380 | 9 | printf("current peer counts: ["); |
381 | | |
382 | 9 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
383 | | |
384 | 153 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
385 | 144 | Tox_Err_Conference_Peer_Query err; |
386 | 144 | uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, &err); |
387 | | |
388 | 144 | if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { |
389 | 0 | peer_count = 0; |
390 | 0 | } |
391 | | |
392 | 144 | fully_connected_count += peer_count == NUM_GROUP_TOX; |
393 | | |
394 | 144 | if (i != 0) { |
395 | 135 | printf(", "); |
396 | 135 | } |
397 | | |
398 | 144 | printf("%u", peer_count); |
399 | 144 | } |
400 | | |
401 | 9 | printf("]\n"); |
402 | 9 | fflush(stdout); |
403 | 9 | } while (fully_connected_count != NUM_GROUP_TOX); |
404 | | |
405 | 17 | for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { |
406 | 16 | uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, nullptr); |
407 | | |
408 | 16 | ck_assert_msg(peer_count == NUM_GROUP_TOX, "\n\tBad number of group peers (pre check)." |
409 | 16 | "\n\t\t\tExpected: %d but tox_instance(%u) only has: %u\n\n", |
410 | 16 | NUM_GROUP_TOX, i, (unsigned)peer_count); |
411 | | |
412 | 16 | uint8_t title[2048]; |
413 | 16 | size_t ret = tox_conference_get_title_size(autotoxes[i].tox, 0, nullptr); |
414 | 16 | ck_assert_msg(ret == sizeof("Gentoo") - 1, "Wrong title length"); |
415 | 16 | tox_conference_get_title(autotoxes[i].tox, 0, title, nullptr); |
416 | 16 | ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title"); |
417 | 16 | } |
418 | | |
419 | 1 | printf("waiting for names to propagate\n"); |
420 | | |
421 | 1 | do { |
422 | 1 | iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); |
423 | 1 | } while (!names_propagated(NUM_GROUP_TOX, autotoxes)); |
424 | | |
425 | 1 | printf("group connected, took %d seconds\n", (int)((autotoxes[0].clock - pregroup_clock) / 1000)); |
426 | | |
427 | 1 | run_conference_tests(autotoxes); |
428 | | |
429 | 1 | printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); |
430 | 1 | } |
431 | | |
432 | | int main(void) |
433 | 721 | { |
434 | 721 | setvbuf(stdout, nullptr, _IONBF, 0); |
435 | | |
436 | 721 | Run_Auto_Options options = default_run_auto_options(); |
437 | 721 | options.graph = GRAPH_LINEAR; |
438 | 721 | options.events = false; |
439 | | |
440 | 721 | run_auto_test(nullptr, NUM_GROUP_TOX, test_many_group, sizeof(State), &options); |
441 | 721 | return 0; |
442 | 721 | } |