aboutsummaryrefslogtreecommitdiff
path: root/src/testing/testing_api_loop.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing/testing_api_loop.c')
-rw-r--r--src/testing/testing_api_loop.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c
new file mode 100644
index 000000000..f32f4061e
--- /dev/null
+++ b/src/testing/testing_api_loop.c
@@ -0,0 +1,652 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2008, 2009, 2012 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_testing_ng_lib.h"
30
31/**
32 * Pipe used to communicate child death via signal.
33 * Must be global, as used in signal handler!
34 */
35static struct GNUNET_DISK_PipeHandle *sigpipe;
36
37/**
38 * Lookup command by label.
39 *
40 * @param is interpreter state to search
41 * @param label label to look for
42 * @return NULL if command was not found
43 */
44const struct GNUNET_TESTING_Command *
45GNUNET_TESTING_interpreter_lookup_command (struct
46 GNUNET_TESTING_Interpreter *is,
47 const char *label)
48{
49 if (NULL == label)
50 {
51 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
52 "Attempt to lookup command for empty label\n");
53 return NULL;
54 }
55 /* Search backwards as we most likely reference recent commands */
56 for (int i = is->ip; i >= 0; i--)
57 {
58 const struct GNUNET_TESTING_Command *cmd = &is->commands[i];
59
60 /* Give precedence to top-level commands. */
61 if ( (NULL != cmd->label) &&
62 (0 == strcmp (cmd->label,
63 label)) )
64 return cmd;
65
66 if (GNUNET_TESTING_cmd_is_batch (cmd))
67 {
68#define BATCH_INDEX 1
69 struct GNUNET_TESTING_Command *batch;
70 struct GNUNET_TESTING_Command *current;
71 struct GNUNET_TESTING_Command *icmd;
72 const struct GNUNET_TESTING_Command *match;
73
74 current = GNUNET_TESTING_cmd_batch_get_current (cmd);
75 GNUNET_assert (GNUNET_OK ==
76 GNUNET_TESTING_get_trait_cmd (cmd,
77 BATCH_INDEX,
78 &batch));
79 /* We must do the loop forward, but we can find the last match */
80 match = NULL;
81 for (unsigned int j = 0;
82 NULL != (icmd = &batch[j])->label;
83 j++)
84 {
85 if (current == icmd)
86 break; /* do not go past current command */
87 if ( (NULL != icmd->label) &&
88 (0 == strcmp (icmd->label,
89 label)) )
90 match = icmd;
91 }
92 if (NULL != match)
93 return match;
94 }
95 }
96 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
97 "Command not found: %s\n",
98 label);
99 return NULL;
100
101}
102
103
104/**
105 * Run the main interpreter loop that performs exchange operations.
106 *
107 * @param cls contains the `struct InterpreterState`
108 */
109static void
110interpreter_run (void *cls);
111
112
113/**
114 * Current command is done, run the next one.
115 */
116void
117GNUNET_TESTING_interpreter_next (struct GNUNET_TESTING_Interpreter *is)
118{
119 static unsigned long long ipc;
120 static struct GNUNET_TIME_Absolute last_report;
121 struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip];
122
123 if (GNUNET_SYSERR == is->result)
124 return; /* ignore, we already failed! */
125 if (GNUNET_TESTING_cmd_is_batch (cmd))
126 {
127 GNUNET_TESTING_cmd_batch_next (is);
128 }
129 else
130 {
131 cmd->finish_time = GNUNET_TIME_absolute_get ();
132 is->ip++;
133 }
134 if (0 == (ipc % 1000))
135 {
136 if (0 != ipc)
137 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
138 "Interpreter executed 1000 instructions in %s\n",
139 GNUNET_STRINGS_relative_time_to_string (
140 GNUNET_TIME_absolute_get_duration (last_report),
141 GNUNET_YES));
142 last_report = GNUNET_TIME_absolute_get ();
143 }
144 ipc++;
145 is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
146 is);
147}
148
149
150/**
151 * Current command failed, clean up and fail the test case.
152 *
153 * @param is interpreter of the test
154 */
155void
156GNUNET_TESTING_interpreter_fail (struct GNUNET_TESTING_Interpreter *is)
157{
158 struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip];
159
160 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
161 "Failed at command `%s'\n",
162 cmd->label);
163 while (GNUNET_TESTING_cmd_is_batch (cmd))
164 {
165 cmd = GNUNET_TESTING_cmd_batch_get_current (cmd);
166 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
167 "Batch is at command `%s'\n",
168 cmd->label);
169 }
170 is->result = GNUNET_SYSERR;
171 GNUNET_SCHEDULER_shutdown ();
172}
173
174
175/**
176 * Create command array terminator.
177 *
178 * @return a end-command.
179 */
180struct GNUNET_TESTING_Command
181GNUNET_TESTING_cmd_end (void)
182{
183 static struct GNUNET_TESTING_Command cmd;
184 cmd.label = NULL;
185
186 return cmd;
187}
188
189
190/**
191 * Obtain current label.
192 */
193const char *
194GNUNET_TESTING_interpreter_get_current_label (struct
195 GNUNET_TESTING_Interpreter *is)
196{
197 struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip];
198
199 return cmd->label;
200}
201
202
203/**
204 * Run the main interpreter loop that performs exchange operations.
205 *
206 * @param cls contains the `struct GNUNET_TESTING_Interpreter`
207 */
208static void
209interpreter_run (void *cls)
210{
211 struct GNUNET_TESTING_Interpreter *is = cls;
212 struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip];
213
214 is->task = NULL;
215
216 if (NULL == cmd->label)
217 {
218
219 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
220 "Running command END\n");
221 is->result = GNUNET_OK;
222 GNUNET_SCHEDULER_shutdown ();
223 return;
224 }
225
226 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
227 "Running command `%s'\n",
228 cmd->label);
229 cmd->start_time
230 = cmd->last_req_time
231 = GNUNET_TIME_absolute_get ();
232 cmd->num_tries = 1;
233 cmd->run (cmd->cls,
234 cmd,
235 is);
236}
237
238
239/**
240 * Function run when the test terminates (good or bad).
241 * Cleans up our state.
242 *
243 * @param cls the interpreter state.
244 */
245static void
246do_shutdown (void *cls)
247{
248 struct GNUNET_TESTING_Interpreter *is = cls;
249 struct GNUNET_TESTING_Command *cmd;
250 const char *label;
251
252 label = is->commands[is->ip].label;
253 if (NULL == label)
254 label = "END";
255
256 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
257 "Executing shutdown at `%s'\n",
258 label);
259
260 for (unsigned int j = 0;
261 NULL != (cmd = &is->commands[j])->label;
262 j++)
263 cmd->cleanup (cmd->cls,
264 cmd);
265
266 if (NULL != is->task)
267 {
268 GNUNET_SCHEDULER_cancel (is->task);
269 is->task = NULL;
270 }
271 if (NULL != is->timeout_task)
272 {
273 GNUNET_SCHEDULER_cancel (is->timeout_task);
274 is->timeout_task = NULL;
275 }
276 if (NULL != is->child_death_task)
277 {
278 GNUNET_SCHEDULER_cancel (is->child_death_task);
279 is->child_death_task = NULL;
280 }
281 GNUNET_free (is->commands);
282}
283
284
285/**
286 * Function run when the test terminates (good or bad) with timeout.
287 *
288 * @param cls NULL
289 */
290static void
291do_timeout (void *cls)
292{
293 struct GNUNET_TESTING_Interpreter *is = cls;
294
295 is->timeout_task = NULL;
296 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
297 "Terminating test due to timeout\n");
298 GNUNET_SCHEDULER_shutdown ();
299}
300
301
302/**
303 * Task triggered whenever we receive a SIGCHLD (child
304 * process died).
305 *
306 * @param cls closure
307 */
308static void
309maint_child_death (void *cls)
310{
311 struct GNUNET_TESTING_Interpreter *is = cls;
312 struct GNUNET_TESTING_Command *cmd = &is->commands[is->ip];
313 const struct GNUNET_DISK_FileHandle *pr;
314 struct GNUNET_OS_Process **processp;
315 char c[16];
316 enum GNUNET_OS_ProcessStatusType type;
317 unsigned long code;
318
319 if (GNUNET_TESTING_cmd_is_batch (cmd))
320 {
321 struct GNUNET_TESTING_Command *batch_cmd;
322
323 GNUNET_assert (GNUNET_OK ==
324 GNUNET_TESTING_get_trait_cmd (cmd,
325 0,
326 &batch_cmd));
327 cmd = batch_cmd;
328 }
329
330 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
331 "Got SIGCHLD for `%s'.\n",
332 cmd->label);
333 is->child_death_task = NULL;
334 pr = GNUNET_DISK_pipe_handle (sigpipe,
335 GNUNET_DISK_PIPE_END_READ);
336 GNUNET_break (0 <
337 GNUNET_DISK_file_read (pr,
338 &c,
339 sizeof (c)));
340 if (GNUNET_OK !=
341 GNUNET_TESTING_get_trait_process (cmd,
342 0,
343 &processp))
344 {
345 GNUNET_break (0);
346 GNUNET_TESTING_interpreter_fail (is);
347 return;
348 }
349
350 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
351 "Got the dead child process handle, waiting for termination ...\n");
352 GNUNET_OS_process_wait_status (*processp,
353 &type,
354 &code);
355 GNUNET_OS_process_destroy (*processp);
356 *processp = NULL;
357 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
358 "... definitively terminated\n");
359 switch (type)
360 {
361 case GNUNET_OS_PROCESS_UNKNOWN:
362 GNUNET_break (0);
363 GNUNET_TESTING_interpreter_fail (is);
364 return;
365 case GNUNET_OS_PROCESS_RUNNING:
366 GNUNET_break (0);
367 GNUNET_TESTING_interpreter_fail (is);
368 return;
369 case GNUNET_OS_PROCESS_STOPPED:
370 GNUNET_break (0);
371 GNUNET_TESTING_interpreter_fail (is);
372 return;
373 case GNUNET_OS_PROCESS_EXITED:
374 if (0 != code)
375 {
376 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
377 "Process exited with unexpected status %u\n",
378 (unsigned int) code);
379 GNUNET_TESTING_interpreter_fail (is);
380 return;
381 }
382 break;
383 case GNUNET_OS_PROCESS_SIGNALED:
384 GNUNET_break (0);
385 GNUNET_TESTING_interpreter_fail (is);
386 return;
387 }
388
389 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
390 "Dead child, go on with next command.\n");
391 GNUNET_TESTING_interpreter_next (is);
392}
393
394
395/**
396 * Wait until we receive SIGCHLD signal.
397 * Then obtain the process trait of the current
398 * command, wait on the the zombie and continue
399 * with the next command.
400 */
401void
402GNUNET_TESTING_wait_for_sigchld (struct GNUNET_TESTING_Interpreter *is)
403{
404 const struct GNUNET_DISK_FileHandle *pr;
405
406 GNUNET_assert (NULL == is->child_death_task);
407 pr = GNUNET_DISK_pipe_handle (sigpipe,
408 GNUNET_DISK_PIPE_END_READ);
409 is->child_death_task
410 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
411 pr,
412 &maint_child_death,
413 is);
414}
415
416
417/**
418 * Run the testsuite. Note, CMDs are copied into
419 * the interpreter state because they are _usually_
420 * defined into the "run" method that returns after
421 * having scheduled the test interpreter.
422 *
423 * @param is the interpreter state
424 * @param commands the list of command to execute
425 * @param timeout how long to wait
426 */
427void
428GNUNET_TESTING_run2 (struct GNUNET_TESTING_Interpreter *is,
429 struct GNUNET_TESTING_Command *commands,
430 struct GNUNET_TIME_Relative timeout)
431{
432 unsigned int i;
433
434 if (NULL != is->timeout_task)
435 {
436 GNUNET_SCHEDULER_cancel (is->timeout_task);
437 is->timeout_task = NULL;
438 }
439 /* get the number of commands */
440 for (i = 0; NULL != commands[i].label; i++)
441 ;
442 is->commands = GNUNET_new_array (i + 1,
443 struct GNUNET_TESTING_Command);
444 memcpy (is->commands,
445 commands,
446 sizeof (struct GNUNET_TESTING_Command) * i);
447 is->timeout_task = GNUNET_SCHEDULER_add_delayed
448 (timeout,
449 &do_timeout,
450 is);
451 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, is);
452 is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, is);
453}
454
455
456/**
457 * Run the testsuite. Note, CMDs are copied into
458 * the interpreter state because they are _usually_
459 * defined into the "run" method that returns after
460 * having scheduled the test interpreter.
461 *
462 * @param is the interpreter state
463 * @param commands the list of command to execute
464 */
465void
466GNUNET_TESTING_run (struct GNUNET_TESTING_Interpreter *is,
467 struct GNUNET_TESTING_Command *commands)
468{
469 GNUNET_TESTING_run2 (is,
470 commands,
471 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES,
472 5));
473}
474
475
476/**
477 * Information used by the wrapper around the main
478 * "run" method.
479 */
480struct MainContext
481{
482 /**
483 * Main "run" method.
484 */
485 GNUNET_TESTING_Main main_cb;
486
487 /**
488 * Closure for @e main_cb.
489 */
490 void *main_cb_cls;
491
492 /**
493 * Interpreter state.
494 */
495 struct GNUNET_TESTING_Interpreter *is;
496};
497
498
499/**
500 * Signal handler called for SIGCHLD. Triggers the
501 * respective handler by writing to the trigger pipe.
502 */
503static void
504sighandler_child_death (void)
505{
506 static char c;
507 int old_errno = errno; /* back-up errno */
508
509 GNUNET_break (1 == GNUNET_DISK_file_write
510 (GNUNET_DISK_pipe_handle (sigpipe,
511 GNUNET_DISK_PIPE_END_WRITE),
512 &c, sizeof (c)));
513 errno = old_errno; /* restore errno */
514}
515
516
517/**
518 * Initialize scheduler loop and curl context for the testcase,
519 * and responsible to run the "run" method.
520 *
521 * @param cls closure, typically the "run" method, the
522 * interpreter state and a closure for "run".
523 */
524static void
525main_wrapper_exchange_agnostic (void *cls)
526{
527 struct MainContext *main_ctx = cls;
528
529 main_ctx->main_cb (main_ctx->main_cb_cls,
530 main_ctx->is);
531}
532
533
534/**
535 * Function run when the test is aborted before we launch the actual
536 * interpreter. Cleans up our state.
537 *
538 * @param cls the main context
539 */
540static void
541do_abort (void *cls)
542{
543 struct MainContext *main_ctx = cls;
544 struct GNUNET_TESTING_Interpreter *is = main_ctx->is;
545
546 is->timeout_task = NULL;
547 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
548 "Executing abort prior to interpreter launch\n");
549}
550
551
552/**
553 * Initialize scheduler loop and curl context for the testcase,
554 * and responsible to run the "run" method.
555 *
556 * @param cls a `struct MainContext *`
557 */
558static void
559main_wrapper_exchange_connect (void *cls)
560{
561 struct MainContext *main_ctx = cls;
562 struct GNUNET_TESTING_Interpreter *is = main_ctx->is;
563 char *exchange_url;
564
565 if (GNUNET_OK !=
566 GNUNET_CONFIGURATION_get_value_string (is->cfg,
567 "exchange",
568 "BASE_URL",
569 &exchange_url))
570 {
571 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
572 "exchange",
573 "BASE_URL");
574 return;
575 }
576 is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort,
577 main_ctx);
578 is->working = GNUNET_YES;
579
580 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
581 "Starting main test loop\n");
582 main_ctx->main_cb (main_ctx->main_cb_cls,
583 is);
584}
585
586
587/**
588 * Install signal handlers plus schedules the main wrapper
589 * around the "run" method.
590 *
591 * @param main_cb the "run" method which contains all the
592 * commands.
593 * @param main_cb_cls a closure for "run", typically NULL.
594 * @param cfg configuration to use
595 * @param exchanged exchange process handle: will be put in the
596 * state as some commands - e.g. revoke - need to send
597 * signal to it, for example to let it know to reload the
598 * key state.. if NULL, the interpreter will run without
599 * trying to connect to the exchange first.
600 * @param exchange_connect #GNUNET_YES if the test should connect
601 * to the exchange, #GNUNET_NO otherwise
602 * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise.
603 * non-GNUNET_OK codes are #GNUNET_SYSERR most of the
604 * times.
605 */
606int
607GNUNET_TESTING_setup (GNUNET_TESTING_Main main_cb,
608 void *main_cb_cls,
609 const struct GNUNET_CONFIGURATION_Handle *cfg,
610 struct GNUNET_OS_Process *exchanged,
611 int exchange_connect)
612{
613 struct GNUNET_TESTING_Interpreter is;
614 struct MainContext main_ctx = {
615 .main_cb = main_cb,
616 .main_cb_cls = main_cb_cls,
617 /* needed to init the curl ctx */
618 .is = &is,
619 };
620 struct GNUNET_SIGNAL_Context *shc_chld;
621
622 memset (&is,
623 0,
624 sizeof (is));
625 is.exchanged = exchanged;
626 is.cfg = cfg;
627 sigpipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
628 GNUNET_assert (NULL != sigpipe);
629 shc_chld = GNUNET_SIGNAL_handler_install
630 (GNUNET_SIGCHLD,
631 &sighandler_child_death);
632
633
634 /* Blocking */
635 if (GNUNET_YES == exchange_connect)
636 GNUNET_SCHEDULER_run (&main_wrapper_exchange_connect,
637 &main_ctx);
638 else
639 GNUNET_SCHEDULER_run (&main_wrapper_exchange_agnostic,
640 &main_ctx);
641 if (NULL != is.final_cleanup_cb)
642 is.final_cleanup_cb (is.final_cleanup_cb_cls);
643 GNUNET_SIGNAL_handler_uninstall (shc_chld);
644 GNUNET_DISK_pipe_close (sigpipe);
645 sigpipe = NULL;
646 GNUNET_free (is.auditor_url);
647 GNUNET_free (is.exchange_url);
648 return is.result;
649}
650
651
652/* end of testing_api_loop.c */