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