summaryrefslogtreecommitdiff
path: root/auto_tests/conference_av_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'auto_tests/conference_av_test.c')
-rw-r--r--auto_tests/conference_av_test.c467
1 files changed, 467 insertions, 0 deletions
diff --git a/auto_tests/conference_av_test.c b/auto_tests/conference_av_test.c
new file mode 100644
index 00000000..6d701751
--- /dev/null
+++ b/auto_tests/conference_av_test.c
@@ -0,0 +1,467 @@
1/* Auto Tests: Conferences AV.
2 */
3
4#ifdef HAVE_CONFIG_H
5#include "config.h"
6#endif
7
8#include <stdlib.h>
9#include <string.h>
10#include <time.h>
11#include <stdint.h>
12
13#include "../toxav/toxav.h"
14#include "check_compat.h"
15
16#define NUM_AV_GROUP_TOX 16
17#define NUM_AV_DISCONNECT (NUM_AV_GROUP_TOX / 2)
18#define NUM_AV_DISABLE (NUM_AV_GROUP_TOX / 2)
19
20typedef struct State {
21 uint32_t index;
22 uint64_t clock;
23
24 bool invited_next;
25
26 uint32_t received_audio_peers[NUM_AV_GROUP_TOX];
27 uint32_t received_audio_num;
28} State;
29
30#include "run_auto_test.h"
31
32static void handle_self_connection_status(
33 Tox *tox, Tox_Connection connection_status, void *user_data)
34{
35 const State *state = (State *)user_data;
36
37 if (connection_status != TOX_CONNECTION_NONE) {
38 printf("tox #%u: is now connected\n", state->index);
39 } else {
40 printf("tox #%u: is now disconnected\n", state->index);
41 }
42}
43
44static void handle_friend_connection_status(
45 Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data)
46{
47 const State *state = (State *)user_data;
48
49 if (connection_status != TOX_CONNECTION_NONE) {
50 printf("tox #%u: is now connected to friend %u\n", state->index, friendnumber);
51 } else {
52 printf("tox #%u: is now disconnected from friend %u\n", state->index, friendnumber);
53 }
54}
55
56static void audio_callback(void *tox, uint32_t groupnumber, uint32_t peernumber,
57 const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t
58 sample_rate, void *userdata)
59{
60 if (samples == 0) {
61 return;
62 }
63
64 State *state = (State *)userdata;
65
66 for (uint32_t i = 0; i < state->received_audio_num; ++i) {
67 if (state->received_audio_peers[i] == peernumber) {
68 return;
69 }
70 }
71
72 ck_assert(state->received_audio_num < NUM_AV_GROUP_TOX);
73
74 state->received_audio_peers[state->received_audio_num] = peernumber;
75 ++state->received_audio_num;
76}
77
78static void handle_conference_invite(
79 Tox *tox, uint32_t friendnumber, Tox_Conference_Type type,
80 const uint8_t *data, size_t length, void *user_data)
81{
82 const State *state = (State *)user_data;
83 ck_assert_msg(type == TOX_CONFERENCE_TYPE_AV, "tox #%u: wrong conference type: %d", state->index, type);
84
85 ck_assert_msg(toxav_join_av_groupchat(tox, friendnumber, data, length, audio_callback, user_data) == 0,
86 "tox #%u: failed to join group", state->index);
87}
88
89static void handle_conference_connected(
90 Tox *tox, uint32_t conference_number, void *user_data)
91{
92 State *state = (State *)user_data;
93
94 if (state->invited_next || tox_self_get_friend_list_size(tox) <= 1) {
95 return;
96 }
97
98 Tox_Err_Conference_Invite err;
99 tox_conference_invite(tox, 1, 0, &err);
100 ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", state->index, err);
101 printf("tox #%u: invited next friend\n", state->index);
102 state->invited_next = true;
103}
104
105static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes,
106 bool *disconnected)
107{
108 uint32_t num_disconnected = 0;
109
110 for (uint32_t i = 0; i < tox_count; ++i) {
111 num_disconnected += disconnected[i];
112 }
113
114 for (uint32_t i = 0; i < tox_count; i++) {
115 if (disconnected[i]) {
116 continue;
117 }
118
119 if (tox_conference_peer_count(toxes[i], 0, nullptr) > tox_count - num_disconnected) {
120 return false;
121 }
122 }
123
124 return true;
125}
126
127static void disconnect_toxes(uint32_t tox_count, Tox **toxes, State *state,
128 const bool *disconnect, const bool *exclude)
129{
130 /* Fake a network outage for a set of peers D by iterating only the other
131 * peers D' until the connections time out according to D', then iterating
132 * only D until the connections time out according to D. */
133
134 VLA(bool, disconnect_now, tox_count);
135 bool invert = false;
136
137 do {
138 for (uint32_t i = 0; i < tox_count; ++i) {
139 disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]);
140 }
141
142 do {
143 for (uint32_t i = 0; i < tox_count; ++i) {
144 if (!disconnect_now[i]) {
145 tox_iterate(toxes[i], &state[i]);
146 state[i].clock += 1000;
147 }
148 }
149
150 c_sleep(20);
151 } while (!toxes_are_disconnected_from_group(tox_count, toxes, disconnect_now));
152
153 invert = !invert;
154 } while (invert);
155}
156
157static bool all_connected_to_group(uint32_t tox_count, Tox **toxes)
158{
159 for (uint32_t i = 0; i < tox_count; i++) {
160 if (tox_conference_peer_count(toxes[i], 0, nullptr) < tox_count) {
161 return false;
162 }
163 }
164
165 return true;
166}
167
168/**
169 * returns a random index at which a list of booleans is false
170 * (some such index is required to exist)
171 */
172static uint32_t random_false_index(bool *list, const uint32_t length)
173{
174 uint32_t index;
175
176 do {
177 index = random_u32() % length;
178 } while (list[index]);
179
180 return index;
181}
182
183static bool all_got_audio(State *state, const bool *disabled)
184{
185 uint32_t num_disabled = 0;
186
187 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
188 num_disabled += disabled[i];
189 }
190
191 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
192 if (disabled[i] ^ (state[i].received_audio_num
193 != NUM_AV_GROUP_TOX - num_disabled - 1)) {
194 return false;
195 }
196 }
197
198 return true;
199}
200
201static void reset_received_audio(Tox **toxes, State *state)
202{
203 for (uint32_t j = 0; j < NUM_AV_GROUP_TOX; ++j) {
204 state[j].received_audio_num = 0;
205 }
206}
207
208#define GROUP_AV_TEST_SAMPLES 960
209
210/* must have
211 * GROUP_AV_AUDIO_ITERATIONS - NUM_AV_GROUP_TOX >= 2^n >= GROUP_JBUF_SIZE
212 * for some n, to give messages time to be relayed and to let the jitter
213 * buffers fill up. */
214#define GROUP_AV_AUDIO_ITERATIONS (8 + NUM_AV_GROUP_TOX)
215
216static bool test_audio(Tox **toxes, State *state, const bool *disabled, bool quiet)
217{
218 if (!quiet) {
219 printf("testing sending and receiving audio\n");
220 }
221
222 int16_t PCM[GROUP_AV_TEST_SAMPLES];
223
224 reset_received_audio(toxes, state);
225
226 for (uint32_t n = 0; n < GROUP_AV_AUDIO_ITERATIONS; n++) {
227 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
228 if (disabled[i]) {
229 continue;
230 }
231
232 if (toxav_group_send_audio(toxes[i], 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) != 0) {
233 if (!quiet) {
234 ck_abort_msg("#%u failed to send audio", state[i].index);
235 }
236
237 return false;
238 }
239 }
240
241 iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
242
243 if (all_got_audio(state, disabled)) {
244 return true;
245 }
246 }
247
248 if (!quiet) {
249 ck_abort_msg("group failed to receive audio");
250 }
251
252 return false;
253}
254
255static void test_eventual_audio(Tox **toxes, State *state, const bool *disabled, uint64_t timeout)
256{
257 uint64_t start = state[0].clock;
258
259 while (state[0].clock < start + timeout) {
260 if (test_audio(toxes, state, disabled, true)
261 && test_audio(toxes, state, disabled, true)) {
262 printf("audio test successful after %d seconds\n", (int)((state[0].clock - start) / 1000));
263 return;
264 }
265 }
266
267 printf("audio seems not to be getting through: testing again with errors.\n");
268 test_audio(toxes, state, disabled, false);
269}
270
271static void do_audio(Tox **toxes, State *state, uint32_t iterations)
272{
273 int16_t PCM[GROUP_AV_TEST_SAMPLES];
274 printf("running audio for %u iterations\n", iterations);
275
276 for (uint32_t f = 0; f < iterations; ++f) {
277 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
278 ck_assert_msg(toxav_group_send_audio(toxes[i], 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) == 0,
279 "#%u failed to send audio", state[i].index);
280 iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
281 }
282 }
283}
284
285// should agree with value in groupav.c
286#define GROUP_JBUF_DEAD_SECONDS 4
287
288#define JITTER_SETTLE_TIME (GROUP_JBUF_DEAD_SECONDS*1000 + NUM_AV_GROUP_TOX*ITERATION_INTERVAL*(GROUP_AV_AUDIO_ITERATIONS+1))
289
290static void run_conference_tests(Tox **toxes, State *state)
291{
292 bool disabled[NUM_AV_GROUP_TOX] = {0};
293
294 test_audio(toxes, state, disabled, false);
295
296 /* have everyone send audio for a bit so we can test that the audio
297 * sequnums dropping to 0 on restart isn't a problem */
298 do_audio(toxes, state, 20);
299
300 printf("letting random toxes timeout\n");
301 bool disconnected[NUM_AV_GROUP_TOX] = {0};
302 bool restarting[NUM_AV_GROUP_TOX] = {0};
303
304 ck_assert(NUM_AV_DISCONNECT < NUM_AV_GROUP_TOX);
305
306 for (uint32_t i = 0; i < NUM_AV_DISCONNECT; ++i) {
307 uint32_t disconnect = random_false_index(disconnected, NUM_AV_GROUP_TOX);
308 disconnected[disconnect] = true;
309
310 if (i < NUM_AV_DISCONNECT / 2) {
311 restarting[disconnect] = true;
312 printf("Restarting #%u\n", state[disconnect].index);
313 } else {
314 printf("Disconnecting #%u\n", state[disconnect].index);
315 }
316 }
317
318 uint8_t *save[NUM_AV_GROUP_TOX];
319 size_t save_size[NUM_AV_GROUP_TOX];
320
321 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
322 if (restarting[i]) {
323 save_size[i] = tox_get_savedata_size(toxes[i]);
324 ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]);
325 save[i] = (uint8_t *)malloc(save_size[i]);
326 ck_assert_msg(save[i] != nullptr, "malloc failed");
327 tox_get_savedata(toxes[i], save[i]);
328 tox_kill(toxes[i]);
329 }
330 }
331
332 disconnect_toxes(NUM_AV_GROUP_TOX, toxes, state, disconnected, restarting);
333
334 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
335 if (restarting[i]) {
336 struct Tox_Options *const options = tox_options_new(nullptr);
337 tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE);
338 tox_options_set_savedata_data(options, save[i], save_size[i]);
339 toxes[i] = tox_new_log(options, nullptr, &state[i].index);
340 tox_options_free(options);
341 free(save[i]);
342
343 set_mono_time_callback(toxes[i], &state[i]);
344 }
345 }
346
347 printf("reconnecting toxes\n");
348
349 do {
350 iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
351 } while (!all_connected_to_group(NUM_AV_GROUP_TOX, toxes));
352
353 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
354 if (restarting[i]) {
355 ck_assert_msg(toxav_groupchat_enable_av(toxes[i], 0, audio_callback, &state[i]) == 0,
356 "#%u failed to re-enable av", state[i].index);
357 }
358 }
359
360 printf("testing audio\n");
361
362 /* Allow time for the jitter buffers to reset and for the group to become
363 * connected enough for lossy messages to get through
364 * (all_connected_to_group() only checks lossless connectivity, which is a
365 * looser condition). */
366 test_eventual_audio(toxes, state, disabled, JITTER_SETTLE_TIME + NUM_AV_GROUP_TOX * 1000);
367
368 printf("testing disabling av\n");
369
370 ck_assert(NUM_AV_DISABLE < NUM_AV_GROUP_TOX);
371
372 for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) {
373 uint32_t disable = random_false_index(disabled, NUM_AV_GROUP_TOX);
374 disabled[disable] = true;
375 printf("Disabling #%u\n", state[disable].index);
376 ck_assert_msg(toxav_groupchat_enable_av(toxes[disable], 0, audio_callback, &state[disable]) != 0,
377 "#%u could enable already enabled av!", state[i].index);
378 ck_assert_msg(toxav_groupchat_disable_av(toxes[disable], 0) == 0,
379 "#%u failed to disable av", state[i].index);
380 }
381
382 // Run test without error to clear out messages from now-disabled peers.
383 test_audio(toxes, state, disabled, true);
384
385 printf("testing audio with some peers having disabled their av\n");
386 test_audio(toxes, state, disabled, false);
387
388 for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) {
389 if (!disabled[i]) {
390 continue;
391 }
392
393 disabled[i] = false;
394 ck_assert_msg(toxav_groupchat_disable_av(toxes[i], 0) != 0,
395 "#%u could disable already disabled av!", state[i].index);
396 ck_assert_msg(toxav_groupchat_enable_av(toxes[i], 0, audio_callback, &state[i]) == 0,
397 "#%u failed to re-enable av", state[i].index);
398 }
399
400 printf("testing audio after re-enabling all av\n");
401 test_eventual_audio(toxes, state, disabled, JITTER_SETTLE_TIME);
402}
403
404static void test_groupav(Tox **toxes, State *state)
405{
406 const time_t test_start_time = time(nullptr);
407
408 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
409 tox_callback_self_connection_status(toxes[i], &handle_self_connection_status);
410 tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status);
411 tox_callback_conference_invite(toxes[i], &handle_conference_invite);
412 tox_callback_conference_connected(toxes[i], &handle_conference_connected);
413 }
414
415 ck_assert_msg(toxav_add_av_groupchat(toxes[0], audio_callback, &state[0]) != UINT32_MAX, "failed to create group");
416 printf("tox #%u: inviting its first friend\n", state[0].index);
417 ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend");
418 state[0].invited_next = true;
419
420
421 printf("waiting for invitations to be made\n");
422 uint32_t invited_count = 0;
423
424 do {
425 iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
426
427 invited_count = 0;
428
429 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
430 invited_count += state[i].invited_next;
431 }
432 } while (invited_count != NUM_AV_GROUP_TOX - 1);
433
434 uint64_t pregroup_clock = state[0].clock;
435 printf("waiting for all toxes to be in the group\n");
436 uint32_t fully_connected_count = 0;
437
438 do {
439 fully_connected_count = 0;
440 iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL);
441
442 for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) {
443 Tox_Err_Conference_Peer_Query err;
444 uint32_t peer_count = tox_conference_peer_count(toxes[i], 0, &err);
445
446 if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
447 peer_count = 0;
448 }
449
450 fully_connected_count += peer_count == NUM_AV_GROUP_TOX;
451 }
452 } while (fully_connected_count != NUM_AV_GROUP_TOX);
453
454 printf("group connected, took %d seconds\n", (int)((state[0].clock - pregroup_clock) / 1000));
455
456 run_conference_tests(toxes, state);
457
458 printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time));
459}
460
461int main(void)
462{
463 setvbuf(stdout, nullptr, _IONBF, 0);
464
465 run_auto_test(NUM_AV_GROUP_TOX, test_groupav, true);
466 return 0;
467}