diff options
Diffstat (limited to 'src/testing/testing_api_loop.c')
-rw-r--r-- | src/testing/testing_api_loop.c | 604 |
1 files changed, 0 insertions, 604 deletions
diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c deleted file mode 100644 index 95d6b88e6..000000000 --- a/src/testing/testing_api_loop.c +++ /dev/null | |||
@@ -1,604 +0,0 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet | ||
3 | Copyright (C) 2021 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file testing/testing_api_loop.c | ||
23 | * @brief main interpreter loop for testcases | ||
24 | * @author Christian Grothoff (GNU Taler testing) | ||
25 | * @author Marcello Stanisci (GNU Taler testing) | ||
26 | * @author t3sserakt | ||
27 | */ | ||
28 | #include "platform.h" | ||
29 | #include "gnunet_util_lib.h" | ||
30 | #include "gnunet_testing_ng_lib.h" | ||
31 | #include "testing.h" | ||
32 | |||
33 | /** | ||
34 | * Global state of the interpreter, used by a command | ||
35 | * to access information about other commands. | ||
36 | */ | ||
37 | struct GNUNET_TESTING_Interpreter | ||
38 | { | ||
39 | |||
40 | /** | ||
41 | * Function to call with the test result. | ||
42 | */ | ||
43 | GNUNET_TESTING_ResultCallback rc; | ||
44 | |||
45 | /** | ||
46 | * Closure for @e rc. | ||
47 | */ | ||
48 | void *rc_cls; | ||
49 | |||
50 | /** | ||
51 | * Commands the interpreter will run. | ||
52 | */ | ||
53 | struct GNUNET_TESTING_Command *commands; | ||
54 | |||
55 | /** | ||
56 | * Number of GNUNET_TESTING_Command in commands. | ||
57 | */ | ||
58 | unsigned int cmds_n; | ||
59 | |||
60 | /** | ||
61 | * Interpreter task (if one is scheduled). | ||
62 | */ | ||
63 | struct GNUNET_SCHEDULER_Task *task; | ||
64 | |||
65 | /** | ||
66 | * Final task that returns the result. | ||
67 | */ | ||
68 | struct GNUNET_SCHEDULER_Task *final_task; | ||
69 | |||
70 | /** | ||
71 | * Task run on timeout. | ||
72 | */ | ||
73 | struct GNUNET_SCHEDULER_Task *timeout_task; | ||
74 | |||
75 | /** | ||
76 | * Instruction pointer. Tells #interpreter_run() which instruction to run | ||
77 | * next. Need (signed) int because it gets -1 when rewinding the | ||
78 | * interpreter to the first CMD. | ||
79 | */ | ||
80 | int ip; | ||
81 | |||
82 | /** | ||
83 | * Result of the testcases, #GNUNET_OK on success | ||
84 | */ | ||
85 | enum GNUNET_GenericReturnValue result; | ||
86 | |||
87 | }; | ||
88 | |||
89 | |||
90 | const struct GNUNET_TESTING_Command * | ||
91 | get_command (struct GNUNET_TESTING_Interpreter *is, | ||
92 | const char *label, | ||
93 | unsigned int future) | ||
94 | { | ||
95 | int start_i = GNUNET_NO == future ? is->ip : is->cmds_n - 1; | ||
96 | int end_i = GNUNET_NO == future ? 0 : is->ip + 1; | ||
97 | |||
98 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
99 | "start_i: %u end_i: %u\n", | ||
100 | start_i, | ||
101 | end_i); | ||
102 | if (NULL == label) | ||
103 | { | ||
104 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
105 | "Attempt to lookup command for empty label\n"); | ||
106 | return NULL; | ||
107 | } | ||
108 | |||
109 | for (int i = start_i; i >= end_i; i--) | ||
110 | { | ||
111 | const struct GNUNET_TESTING_Command *cmd = &is->commands[i]; | ||
112 | |||
113 | if (NULL != cmd->label) | ||
114 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
115 | "label to compare %s\n", | ||
116 | cmd->label); | ||
117 | /* Give precedence to top-level commands. */ | ||
118 | if ( (NULL != cmd->label) && | ||
119 | (0 == strcmp (cmd->label, | ||
120 | label)) ) | ||
121 | return cmd; | ||
122 | |||
123 | if (GNUNET_TESTING_cmd_is_batch_ (cmd)) | ||
124 | { | ||
125 | struct GNUNET_TESTING_Command **batch; | ||
126 | struct GNUNET_TESTING_Command *current; | ||
127 | const struct GNUNET_TESTING_Command *icmd; | ||
128 | const struct GNUNET_TESTING_Command *match; | ||
129 | |||
130 | current = GNUNET_TESTING_cmd_batch_get_current_ (cmd); | ||
131 | GNUNET_assert (GNUNET_OK == | ||
132 | GNUNET_TESTING_get_trait_batch_cmds (cmd, | ||
133 | &batch)); | ||
134 | /* We must do the loop forward, but we can find the last match */ | ||
135 | match = NULL; | ||
136 | for (unsigned int j = 0; | ||
137 | NULL != (icmd = &(*batch)[j])->label; | ||
138 | j++) | ||
139 | { | ||
140 | if (current == icmd) | ||
141 | break; /* do not go past current command */ | ||
142 | if ( (NULL != icmd->label) && | ||
143 | (0 == strcmp (icmd->label, | ||
144 | label)) ) | ||
145 | match = icmd; | ||
146 | } | ||
147 | if (NULL != match) | ||
148 | return match; | ||
149 | } | ||
150 | } | ||
151 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
152 | "Command `%s' not found\n", | ||
153 | label); | ||
154 | return NULL; | ||
155 | } | ||
156 | |||
157 | |||
158 | /** | ||
159 | * Lookup command by label. | ||
160 | * Only future commands are looked up. | ||
161 | * | ||
162 | * @param is interpreter to lookup command in | ||
163 | * @param label label of the command to lookup. | ||
164 | * @return the command, if it is found, or NULL. | ||
165 | */ | ||
166 | const struct GNUNET_TESTING_Command * | ||
167 | GNUNET_TESTING_interpreter_lookup_future_command ( | ||
168 | struct GNUNET_TESTING_Interpreter *is, | ||
169 | const char *label) | ||
170 | { | ||
171 | return get_command (is, label, GNUNET_YES); | ||
172 | } | ||
173 | |||
174 | |||
175 | /** | ||
176 | * Lookup command by label. | ||
177 | * Only commands from current command to commands in the past are looked up. | ||
178 | * | ||
179 | * @param is interpreter to lookup command in | ||
180 | * @param label label of the command to lookup. | ||
181 | * @return the command, if it is found, or NULL. | ||
182 | */ | ||
183 | const struct GNUNET_TESTING_Command * | ||
184 | GNUNET_TESTING_interpreter_lookup_command ( | ||
185 | struct GNUNET_TESTING_Interpreter *is, | ||
186 | const char *label) | ||
187 | { | ||
188 | return get_command (is, label, GNUNET_NO); | ||
189 | } | ||
190 | |||
191 | |||
192 | /** | ||
193 | * Lookup command by label. | ||
194 | * All commands, first into the past, then into the furture are looked up. | ||
195 | * | ||
196 | * @param is interpreter to lookup command in | ||
197 | * @param label label of the command to lookup. | ||
198 | * @return the command, if it is found, or NULL. | ||
199 | */ | ||
200 | const struct GNUNET_TESTING_Command * | ||
201 | GNUNET_TESTING_interpreter_lookup_command_all ( | ||
202 | struct GNUNET_TESTING_Interpreter *is, | ||
203 | const char *label) | ||
204 | { | ||
205 | const struct GNUNET_TESTING_Command *cmd; | ||
206 | |||
207 | cmd = get_command (is, label, GNUNET_NO); | ||
208 | if (NULL == cmd) | ||
209 | cmd = get_command (is, label, GNUNET_YES); | ||
210 | return cmd; | ||
211 | } | ||
212 | |||
213 | |||
214 | /** | ||
215 | * Finish the test run, return the final result. | ||
216 | * | ||
217 | * @param cls the `struct GNUNET_TESTING_Interpreter` | ||
218 | */ | ||
219 | static void | ||
220 | finish_test (void *cls) | ||
221 | { | ||
222 | struct GNUNET_TESTING_Interpreter *is = cls; | ||
223 | struct GNUNET_TESTING_Command *cmd; | ||
224 | const char *label; | ||
225 | |||
226 | is->final_task = NULL; | ||
227 | label = is->commands[is->ip].label; | ||
228 | if (NULL == label) | ||
229 | label = "END"; | ||
230 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
231 | "Interpreter finishes at `%s' with status %d\n", | ||
232 | label, | ||
233 | is->result); | ||
234 | for (unsigned int j = 0; | ||
235 | NULL != (cmd = &is->commands[j])->label; | ||
236 | j++) | ||
237 | { | ||
238 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
239 | "Cleaning up cmd %s\n", | ||
240 | cmd->label); | ||
241 | cmd->cleanup (cmd->cls); | ||
242 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
243 | "Cleaned up cmd %s\n", | ||
244 | cmd->label); | ||
245 | } | ||
246 | if (NULL != is->task) | ||
247 | { | ||
248 | GNUNET_SCHEDULER_cancel (is->task); | ||
249 | is->task = NULL; | ||
250 | } | ||
251 | if (NULL != is->timeout_task) | ||
252 | { | ||
253 | GNUNET_SCHEDULER_cancel (is->timeout_task); | ||
254 | is->timeout_task = NULL; | ||
255 | } | ||
256 | GNUNET_free (is->commands); | ||
257 | is->rc (is->rc_cls, | ||
258 | is->result); | ||
259 | GNUNET_free (is); | ||
260 | } | ||
261 | |||
262 | |||
263 | /** | ||
264 | * Run the main interpreter loop that performs exchange operations. | ||
265 | * | ||
266 | * @param cls contains the `struct InterpreterState` | ||
267 | */ | ||
268 | static void | ||
269 | interpreter_run (void *cls); | ||
270 | |||
271 | |||
272 | /** | ||
273 | * Current command is done, run the next one. | ||
274 | */ | ||
275 | static void | ||
276 | interpreter_next (void *cls) | ||
277 | { | ||
278 | struct GNUNET_TESTING_Interpreter *is = cls; | ||
279 | static unsigned long long ipc; | ||
280 | static struct GNUNET_TIME_Absolute last_report; | ||
281 | struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip]; | ||
282 | |||
283 | if (GNUNET_SYSERR == is->result) | ||
284 | return; /* ignore, we already failed! */ | ||
285 | cmd->finish_time = GNUNET_TIME_absolute_get (); | ||
286 | if ( (! GNUNET_TESTING_cmd_is_batch_ (cmd)) || | ||
287 | (! GNUNET_TESTING_cmd_batch_next_ (cmd->cls)) ) | ||
288 | is->ip++; | ||
289 | if (0 == (ipc % 1000)) | ||
290 | { | ||
291 | if (0 != ipc) | ||
292 | GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, | ||
293 | "Interpreter executed 1000 instructions in %s\n", | ||
294 | GNUNET_STRINGS_relative_time_to_string ( | ||
295 | GNUNET_TIME_absolute_get_duration (last_report), | ||
296 | GNUNET_YES)); | ||
297 | last_report = GNUNET_TIME_absolute_get (); | ||
298 | } | ||
299 | ipc++; | ||
300 | is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, | ||
301 | is); | ||
302 | } | ||
303 | |||
304 | |||
305 | void | ||
306 | GNUNET_TESTING_interpreter_fail (struct GNUNET_TESTING_Interpreter *is) | ||
307 | { | ||
308 | struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip]; | ||
309 | |||
310 | if (GNUNET_SYSERR == is->result) | ||
311 | { | ||
312 | GNUNET_break (0); | ||
313 | return; /* ignore, we already failed! */ | ||
314 | } | ||
315 | if (NULL != cmd) | ||
316 | { | ||
317 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
318 | "Failed at command `%s'\n", | ||
319 | cmd->label); | ||
320 | while (GNUNET_TESTING_cmd_is_batch_ (cmd)) | ||
321 | { | ||
322 | cmd = GNUNET_TESTING_cmd_batch_get_current_ (cmd); | ||
323 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
324 | "Failed in batch at command `%s'\n", | ||
325 | cmd->label); | ||
326 | } | ||
327 | } | ||
328 | else | ||
329 | { | ||
330 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
331 | "Failed with CMD being NULL!\n"); | ||
332 | } | ||
333 | is->result = GNUNET_SYSERR; | ||
334 | GNUNET_assert (NULL == is->final_task); | ||
335 | is->final_task = GNUNET_SCHEDULER_add_now (&finish_test, | ||
336 | is); | ||
337 | } | ||
338 | |||
339 | |||
340 | /** | ||
341 | * Returns the actual running command. | ||
342 | * | ||
343 | * @param is Global state of the interpreter, used by a command | ||
344 | * to access information about other commands. | ||
345 | * @return The actual running command. | ||
346 | */ | ||
347 | struct GNUNET_TESTING_Command * | ||
348 | GNUNET_TESTING_interpreter_get_current_command ( | ||
349 | struct GNUNET_TESTING_Interpreter *is) | ||
350 | { | ||
351 | return &is->commands[is->ip]; | ||
352 | } | ||
353 | |||
354 | const char * | ||
355 | GNUNET_TESTING_interpreter_get_current_label ( | ||
356 | struct GNUNET_TESTING_Interpreter *is) | ||
357 | { | ||
358 | struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip]; | ||
359 | |||
360 | return cmd->label; | ||
361 | } | ||
362 | |||
363 | |||
364 | /** | ||
365 | * Run the main interpreter loop. | ||
366 | * | ||
367 | * @param cls contains the `struct GNUNET_TESTING_Interpreter` | ||
368 | */ | ||
369 | static void | ||
370 | interpreter_run (void *cls) | ||
371 | { | ||
372 | struct GNUNET_TESTING_Interpreter *is = cls; | ||
373 | struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip]; | ||
374 | |||
375 | is->task = NULL; | ||
376 | if (NULL == cmd->label) | ||
377 | { | ||
378 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
379 | "Running command END\n"); | ||
380 | is->result = GNUNET_OK; | ||
381 | finish_test (is); | ||
382 | return; | ||
383 | } | ||
384 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
385 | "Running command `%s'\n", | ||
386 | cmd->label); | ||
387 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
388 | "start time of %p expected 0 is `%lu'\n", | ||
389 | cmd, | ||
390 | cmd->start_time.abs_value_us); | ||
391 | cmd->start_time | ||
392 | = cmd->last_req_time | ||
393 | = GNUNET_TIME_absolute_get (); | ||
394 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
395 | "start time of %p expected something is `%lu'\n", | ||
396 | cmd, | ||
397 | cmd->start_time.abs_value_us); | ||
398 | cmd->num_tries = 1; | ||
399 | if (NULL != cmd->ac) | ||
400 | { | ||
401 | cmd->ac->is = is; | ||
402 | cmd->ac->cont = &interpreter_next; | ||
403 | cmd->ac->cont_cls = is; | ||
404 | cmd->ac->finished = GNUNET_NO; | ||
405 | } | ||
406 | cmd->run (cmd->cls, | ||
407 | is); | ||
408 | if (NULL == cmd->ac) | ||
409 | { | ||
410 | interpreter_next (is); | ||
411 | } | ||
412 | else if ( (cmd->asynchronous_finish) && | ||
413 | (NULL != cmd->ac->cont) ) | ||
414 | { | ||
415 | cmd->ac->cont = NULL; | ||
416 | interpreter_next (is); | ||
417 | } | ||
418 | } | ||
419 | |||
420 | |||
421 | /** | ||
422 | * Function run when the test terminates (good or bad) with timeout. | ||
423 | * | ||
424 | * @param cls the interpreter state | ||
425 | */ | ||
426 | static void | ||
427 | do_timeout (void *cls) | ||
428 | { | ||
429 | struct GNUNET_TESTING_Interpreter *is = cls; | ||
430 | |||
431 | is->timeout_task = NULL; | ||
432 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
433 | "Terminating test due to global timeout\n"); | ||
434 | is->result = GNUNET_SYSERR; | ||
435 | finish_test (is); | ||
436 | } | ||
437 | |||
438 | |||
439 | /** | ||
440 | * Check if the command is running. | ||
441 | * | ||
442 | * @param cmd The command to check. | ||
443 | * @return GNUNET_NO if the command is not running, GNUNET_YES if it is running. | ||
444 | */ | ||
445 | enum GNUNET_GenericReturnValue | ||
446 | GNUNET_TESTING_running (const struct GNUNET_TESTING_Command *command) | ||
447 | { | ||
448 | return 0 != command->start_time.abs_value_us && 0 == | ||
449 | command->finish_time.abs_value_us; | ||
450 | } | ||
451 | |||
452 | |||
453 | /** | ||
454 | * Check if a command is finished. | ||
455 | * | ||
456 | * @param cmd The command to check. | ||
457 | * @return GNUNET_NO if the command is not finished, GNUNET_YES if it is finished. | ||
458 | */ | ||
459 | enum GNUNET_GenericReturnValue | ||
460 | GNUNET_TESTING_finished (struct GNUNET_TESTING_Command *command) | ||
461 | { | ||
462 | struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); | ||
463 | struct GNUNET_TIME_Relative diff = GNUNET_TIME_absolute_get_difference ( | ||
464 | command->finish_time, | ||
465 | now); | ||
466 | return 0 < diff.rel_value_us; | ||
467 | } | ||
468 | |||
469 | |||
470 | void | ||
471 | GNUNET_TESTING_run (struct GNUNET_TESTING_Command *commands, | ||
472 | struct GNUNET_TIME_Relative timeout, | ||
473 | GNUNET_TESTING_ResultCallback rc, | ||
474 | void *rc_cls) | ||
475 | { | ||
476 | struct GNUNET_TESTING_Interpreter *is; | ||
477 | unsigned int i; | ||
478 | |||
479 | is = GNUNET_new (struct GNUNET_TESTING_Interpreter); | ||
480 | is->rc = rc; | ||
481 | is->rc_cls = rc_cls; | ||
482 | /* get the number of commands */ | ||
483 | for (i = 0; NULL != commands[i].label; i++) | ||
484 | ; | ||
485 | is->cmds_n = i + 1; | ||
486 | is->commands = GNUNET_new_array (is->cmds_n, | ||
487 | struct GNUNET_TESTING_Command); | ||
488 | memcpy (is->commands, | ||
489 | commands, | ||
490 | sizeof (struct GNUNET_TESTING_Command) * i); | ||
491 | is->timeout_task | ||
492 | = GNUNET_SCHEDULER_add_delayed (timeout, | ||
493 | &do_timeout, | ||
494 | is); | ||
495 | is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, | ||
496 | is); | ||
497 | } | ||
498 | |||
499 | |||
500 | /** | ||
501 | * Closure for #loop_run(). | ||
502 | */ | ||
503 | struct MainParams | ||
504 | { | ||
505 | |||
506 | /** | ||
507 | * NULL-label terminated array of commands. | ||
508 | */ | ||
509 | struct GNUNET_TESTING_Command *commands; | ||
510 | |||
511 | /** | ||
512 | * Global timeout for the test. | ||
513 | */ | ||
514 | struct GNUNET_TIME_Relative timeout; | ||
515 | |||
516 | /** | ||
517 | * Set to #EXIT_FAILURE on error. | ||
518 | */ | ||
519 | int rv; | ||
520 | }; | ||
521 | |||
522 | |||
523 | /** | ||
524 | * Function called with the final result of the test. | ||
525 | * | ||
526 | * @param cls the `struct MainParams` | ||
527 | * @param rv #GNUNET_OK if the test passed | ||
528 | */ | ||
529 | static void | ||
530 | handle_result (void *cls, | ||
531 | enum GNUNET_GenericReturnValue rv) | ||
532 | { | ||
533 | struct MainParams *mp = cls; | ||
534 | |||
535 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
536 | "Test exits with status %d\n", | ||
537 | rv); | ||
538 | if (GNUNET_OK != rv) | ||
539 | mp->rv = EXIT_FAILURE; | ||
540 | GNUNET_SCHEDULER_shutdown (); | ||
541 | } | ||
542 | |||
543 | |||
544 | /** | ||
545 | * Main function to run the test cases. | ||
546 | * | ||
547 | * @param cls a `struct MainParams *` | ||
548 | */ | ||
549 | static void | ||
550 | loop_run (void *cls) | ||
551 | { | ||
552 | struct MainParams *mp = cls; | ||
553 | |||
554 | GNUNET_TESTING_run (mp->commands, | ||
555 | mp->timeout, | ||
556 | &handle_result, | ||
557 | mp); | ||
558 | } | ||
559 | |||
560 | |||
561 | int | ||
562 | GNUNET_TESTING_main (struct GNUNET_TESTING_Command *commands, | ||
563 | struct GNUNET_TIME_Relative timeout) | ||
564 | { | ||
565 | struct MainParams mp = { | ||
566 | .commands = commands, | ||
567 | .timeout = timeout, | ||
568 | .rv = EXIT_SUCCESS | ||
569 | }; | ||
570 | |||
571 | GNUNET_SCHEDULER_run (&loop_run, | ||
572 | &mp); | ||
573 | return mp.rv; | ||
574 | } | ||
575 | |||
576 | |||
577 | void | ||
578 | GNUNET_TESTING_async_fail (struct GNUNET_TESTING_AsyncContext *ac) | ||
579 | { | ||
580 | GNUNET_assert (GNUNET_NO == ac->finished); | ||
581 | ac->finished = GNUNET_SYSERR; | ||
582 | GNUNET_TESTING_interpreter_fail (ac->is); | ||
583 | if (NULL != ac->cont) | ||
584 | { | ||
585 | ac->cont (ac->cont_cls); | ||
586 | ac->cont = NULL; | ||
587 | } | ||
588 | } | ||
589 | |||
590 | |||
591 | void | ||
592 | GNUNET_TESTING_async_finish (struct GNUNET_TESTING_AsyncContext *ac) | ||
593 | { | ||
594 | GNUNET_assert (GNUNET_NO == ac->finished); | ||
595 | ac->finished = GNUNET_OK; | ||
596 | if (NULL != ac->cont) | ||
597 | { | ||
598 | ac->cont (ac->cont_cls); | ||
599 | ac->cont = NULL; | ||
600 | } | ||
601 | } | ||
602 | |||
603 | |||
604 | /* end of testing_api_loop.c */ | ||