]> git.ipfire.org Git - thirdparty/bird.git/blob - test/birdtest.c
Merge branch 'master' into mq-filter-stack
[thirdparty/bird.git] / test / birdtest.c
1 /*
2 * BIRD -- Unit Test Framework (BIRD Test)
3 *
4 * Can be freely distributed and used under the terms of the GNU GPL.
5 */
6
7 #include <stdarg.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <signal.h>
13 #include <unistd.h>
14
15 #include <sys/ioctl.h>
16 #include <sys/resource.h>
17 #include <sys/wait.h>
18
19 #include "test/birdtest.h"
20 #include "lib/string.h"
21
22 #ifdef HAVE_EXECINFO_H
23 #include <execinfo.h>
24 #endif
25
26 #define BACKTRACE_MAX_LINES 100
27
28 #define sprintf_concat(s, format, ...) \
29 snprintf(s + strlen(s), sizeof(s) - strlen(s), format, ##__VA_ARGS__)
30
31 static const char *request;
32 static int list_tests;
33 static int do_core;
34 static int do_die;
35 static int no_fork;
36 static int no_timeout;
37 static int is_terminal; /* Whether stdout is a live terminal or pipe redirect */
38
39 uint bt_verbose;
40 const char *bt_filename;
41 const char *bt_test_id;
42
43 int bt_result; /* Overall program run result */
44 int bt_suite_result; /* One suit result */
45 char bt_out_fmt_buf[1024]; /* Temporary memory buffer for output of testing function */
46
47 long int
48 bt_random(void)
49 {
50 /* Seeded in bt_init() */
51 long int rand_low, rand_high;
52
53 rand_low = random();
54 rand_high = random();
55 return (rand_low & 0xffff) | ((rand_high & 0xffff) << 16);
56 }
57
58 void
59 bt_init(int argc, char *argv[])
60 {
61 int c;
62
63 srandom(BT_RANDOM_SEED);
64
65 bt_verbose = 0;
66 bt_filename = argv[0];
67 bt_result = 1;
68 bt_test_id = NULL;
69 is_terminal = isatty(fileno(stdout));
70
71 while ((c = getopt(argc, argv, "lcdftv")) >= 0)
72 switch (c)
73 {
74 case 'l':
75 list_tests = 1;
76 break;
77
78 case 'c':
79 do_core = 1;
80 break;
81
82 case 'd':
83 do_die = 1;
84 break;
85
86 case 'f':
87 no_fork = 1;
88 break;
89
90 case 't':
91 no_timeout = 1;
92 break;
93
94 case 'v':
95 bt_verbose++;
96 break;
97
98 default:
99 goto usage;
100 }
101
102 /* Optional requested test_id */
103 if ((optind + 1) == argc)
104 request = argv[optind++];
105
106 if (optind != argc)
107 goto usage;
108
109 if (do_core)
110 {
111 struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY};
112 int rv = setrlimit(RLIMIT_CORE, &rl);
113 bt_syscall(rv < 0, "setrlimit RLIMIT_CORE");
114 }
115
116 return;
117
118 usage:
119 printf("Usage: %s [-l] [-c] [-d] [-f] [-t] [-vvv] [<test_suit_name>]\n", argv[0]);
120 printf("Options: \n");
121 printf(" -l List all test suite names and descriptions \n");
122 printf(" -c Force unlimit core dumps (needs root privileges) \n");
123 printf(" -d Die on first failed test case \n");
124 printf(" -f No forking \n");
125 printf(" -t No timeout limit \n");
126 printf(" -v More verbosity, maximum is 3 -vvv \n");
127 exit(3);
128 }
129
130 static void
131 bt_dump_backtrace(void)
132 {
133 #ifdef HAVE_EXECINFO_H
134 void *buf[BACKTRACE_MAX_LINES];
135 char **pp_backtrace;
136 int lines, j;
137
138 if (!bt_verbose)
139 return;
140
141 lines = backtrace(buf, BACKTRACE_MAX_LINES);
142 bt_log("backtrace() returned %d addresses", lines);
143
144 pp_backtrace = backtrace_symbols(buf, lines);
145 if (pp_backtrace == NULL)
146 {
147 perror("backtrace_symbols");
148 exit(EXIT_FAILURE);
149 }
150
151 for (j = 0; j < lines; j++)
152 bt_log("%s", pp_backtrace[j]);
153
154 free(pp_backtrace);
155 #endif /* HAVE_EXECINFO_H */
156 }
157
158 static
159 int bt_run_test_fn(int (*fn)(const void *), const void *fn_arg, int timeout)
160 {
161 int result;
162 alarm(timeout);
163
164 result = fn(fn_arg);
165
166 if (!bt_suite_result)
167 result = 0;
168
169 return result;
170 }
171
172 static uint
173 get_num_terminal_cols(void)
174 {
175 struct winsize w = {};
176 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
177 uint cols = w.ws_col;
178 return (cols > 0 ? cols : 80);
179 }
180
181 /**
182 * bt_log_result - pretty print of test result
183 * @result: 1 or 0
184 * @fmt: a description message (could be long, over more lines)
185 * @argptr: variable argument list
186 *
187 * This function is used for pretty printing of test results on all verbose
188 * levels.
189 */
190 static void
191 bt_log_result(int result, const char *fmt, va_list argptr)
192 {
193 static char msg_buf[BT_BUFFER_SIZE];
194 char *pos;
195
196 snprintf(msg_buf, sizeof(msg_buf), "%s%s%s%s",
197 bt_filename,
198 bt_test_id ? ": " : "",
199 bt_test_id ? bt_test_id : "",
200 (fmt && strlen(fmt) > 0) ? ": " : "");
201 pos = msg_buf + strlen(msg_buf);
202
203 vsnprintf(pos, sizeof(msg_buf) - (pos - msg_buf), fmt, argptr);
204
205 int chrs = 0;
206 for (uint i = 0; i < strlen(msg_buf); i += get_num_terminal_cols())
207 {
208 if (i)
209 printf("\n");
210 char *stop = msg_buf + i + get_num_terminal_cols();
211 char backup = *stop;
212 *stop = 0;
213 chrs = printf("%s", msg_buf + i);
214 *stop = backup;
215 }
216
217 int offset = get_num_terminal_cols() - chrs - BT_PROMPT_OK_FAIL_STRLEN;
218 if (offset < 0)
219 {
220 printf("\n");
221 offset = get_num_terminal_cols() - BT_PROMPT_OK_FAIL_STRLEN;
222 }
223
224 for (int i = 0; i < offset; i++)
225 putchar(' ');
226
227 const char *result_str = is_terminal ? BT_PROMPT_OK : BT_PROMPT_OK_NO_COLOR;
228 if (!result)
229 result_str = is_terminal ? BT_PROMPT_FAIL : BT_PROMPT_FAIL_NO_COLOR;
230
231 printf("%s\n", result_str);
232
233 if (do_die && !result)
234 abort();
235 }
236
237 /**
238 * bt_log_overall_result - pretty print of suite case result
239 * @result: 1 or 0
240 * @fmt: a description message (could be long, over more lines)
241 * ...: variable argument list
242 *
243 * This function is used for pretty printing of test suite case result.
244 */
245 static void
246 bt_log_overall_result(int result, const char *fmt, ...)
247 {
248 va_list argptr;
249 va_start(argptr, fmt);
250 bt_log_result(result, fmt, argptr);
251 va_end(argptr);
252 }
253
254 /**
255 * bt_log_suite_result - pretty print of suite case result
256 * @result: 1 or 0
257 * @fmt: a description message (could be long, over more lines)
258 * ...: variable argument list
259 *
260 * This function is used for pretty printing of test suite case result.
261 */
262 void
263 bt_log_suite_result(int result, const char *fmt, ...)
264 {
265 if(bt_verbose >= BT_VERBOSE_SUITE || !result)
266 {
267 va_list argptr;
268 va_start(argptr, fmt);
269 bt_log_result(result, fmt, argptr);
270 va_end(argptr);
271 }
272 }
273
274 /**
275 * bt_log_suite_case_result - pretty print of suite result
276 * @result: 1 or 0
277 * @fmt: a description message (could be long, over more lines)
278 * ...: variable argument list
279 *
280 * This function is used for pretty printing of test suite result.
281 */
282 void
283 bt_log_suite_case_result(int result, const char *fmt, ...)
284 {
285 if(bt_verbose >= BT_VERBOSE_SUITE_CASE)
286 {
287 va_list argptr;
288 va_start(argptr, fmt);
289 bt_log_result(result, fmt, argptr);
290 va_end(argptr);
291 }
292 }
293
294 int
295 bt_test_suite_base(int (*fn)(const void *), const char *id, const void *fn_arg, int forked, int timeout, const char *dsc, ...)
296 {
297 if (list_tests)
298 {
299 printf("%28s - ", id);
300 va_list args;
301 va_start(args, dsc);
302 vprintf(dsc, args);
303 va_end(args);
304 printf("\n");
305 return 1;
306 }
307
308 if (no_fork)
309 forked = 0;
310
311 if (no_timeout)
312 timeout = 0;
313
314 if (request && strcmp(id, request))
315 return 1;
316
317 bt_suite_result = 1;
318 bt_test_id = id;
319
320 if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL)
321 bt_log("Starting");
322
323 if (!forked)
324 {
325 bt_suite_result = bt_run_test_fn(fn, fn_arg, timeout);
326 }
327 else
328 {
329 pid_t pid = fork();
330 bt_syscall(pid < 0, "fork");
331
332 if (pid == 0)
333 {
334 /* child of fork */
335 _exit(bt_run_test_fn(fn, fn_arg, timeout));
336 }
337
338 int s;
339 int rv = waitpid(pid, &s, 0);
340 bt_syscall(rv < 0, "waitpid");
341
342 if (WIFEXITED(s))
343 {
344 /* Normal exit */
345 bt_suite_result = WEXITSTATUS(s);
346 }
347 else if (WIFSIGNALED(s))
348 {
349 /* Stopped by signal */
350 bt_suite_result = 0;
351
352 int sn = WTERMSIG(s);
353 if (sn == SIGALRM)
354 {
355 bt_log("Timeout expired");
356 }
357 else if (sn == SIGSEGV)
358 {
359 bt_log("Segmentation fault");
360 bt_dump_backtrace();
361 }
362 else if (sn != SIGABRT)
363 bt_log("Signal %d received", sn);
364 }
365
366 if (WCOREDUMP(s) && bt_verbose)
367 bt_log("Core dumped");
368 }
369
370 if (!bt_suite_result)
371 bt_result = 0;
372
373 bt_log_suite_result(bt_suite_result, NULL);
374 bt_test_id = NULL;
375
376 return bt_suite_result;
377 }
378
379 int
380 bt_exit_value(void)
381 {
382 if (!list_tests || (list_tests && !bt_result))
383 bt_log_overall_result(bt_result, "");
384 return bt_result ? EXIT_SUCCESS : EXIT_FAILURE;
385 }
386
387 /**
388 * bt_assert_batch__ - test a batch of inputs/outputs tests
389 * @opts: includes all necessary data
390 *
391 * Should be called using macro bt_assert_batch().
392 * Returns 1 or 0.
393 */
394 int
395 bt_assert_batch__(struct bt_batch *opts)
396 {
397 int i;
398 for (i = 0; i < opts->ndata; i++)
399 {
400 int bt_suit_case_result = opts->test_fn(opts->out_buf, opts->data[i].in, opts->data[i].out);
401
402 if (bt_suit_case_result == 0)
403 bt_suite_result = 0;
404
405 char b[BT_BUFFER_SIZE];
406 snprintf(b, sizeof(b), "%s(", opts->test_fn_name);
407
408 opts->in_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].in);
409 sprintf_concat(b, ") gives ");
410 opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->out_buf);
411
412 if (bt_suit_case_result == 0)
413 {
414 sprintf_concat(b, ", but expecting is ");
415 opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].out);
416 }
417
418 bt_log_suite_case_result(bt_suit_case_result, "%s", b);
419 }
420
421 return bt_suite_result;
422 }
423
424 /**
425 * bt_fmt_str - formating string into output buffer
426 * @buf: buffer for write
427 * @size: empty size in @buf
428 * @data: null-byte terminated string
429 *
430 * This function can be used with bt_assert_batch() function.
431 * Input @data should be const char * string.
432 */
433 void
434 bt_fmt_str(char *buf, size_t size, const void *data)
435 {
436 const byte *s = data;
437
438 snprintf(buf, size, "\"");
439 while (*s)
440 {
441 snprintf(buf+strlen(buf), size-strlen(buf), bt_is_char(*s) ? "%c" : "\\%03u", *s);
442 s++;
443 }
444 snprintf(buf+strlen(buf), size-strlen(buf), "\"");
445 }
446
447 /**
448 * bt_fmt_unsigned - formating unsigned int into output buffer
449 * @buf: buffer for write
450 * @size: empty size in @buf
451 * @data: unsigned number
452 *
453 * This function can be used with bt_assert_batch() function.
454 */
455 void
456 bt_fmt_unsigned(char *buf, size_t size, const void *data)
457 {
458 const uint *n = data;
459 snprintf(buf, size, "0x%x (%u)", *n, *n);
460 }
461
462 /**
463 * bt_fmt_ipa - formating ip_addr into output buffer
464 * @buf: buffer for write
465 * @size: empty size in @buf
466 * @data: should be struct ip_addr *
467 *
468 * This function can be used with bt_assert_batch() function.
469 */
470 void
471 bt_fmt_ipa(char *buf, size_t size, const void *data)
472 {
473 const ip_addr *ip = data;
474 bsnprintf(buf, size, "%I", *ip);
475 }
476
477 int
478 bt_is_char(byte c)
479 {
480 return (c >= (byte) 32 && c <= (byte) 126);
481 }
482
483 /*
484 * Mock-ups of all necessary public functions in main.c
485 */
486
487 char *bird_name;
488 void async_config(void) {}
489 void async_dump(void) {}
490 void async_shutdown(void) {}
491 void cmd_check_config(char *name UNUSED) {}
492 void cmd_reconfig(char *name UNUSED, int type UNUSED, int timeout UNUSED) {}
493 void cmd_reconfig_confirm(void) {}
494 void cmd_reconfig_undo(void) {}
495 void cmd_reconfig_status(void) {}
496 void cmd_graceful_restart(void) {}
497 void cmd_shutdown(void) {}
498 void cmd_reconfig_undo_notify(void) {}
499
500 #include "nest/bird.h"
501 #include "lib/net.h"
502 #include "conf/conf.h"
503 void sysdep_preconfig(struct config *c UNUSED) {}
504 int sysdep_commit(struct config *new UNUSED, struct config *old UNUSED) { return 0; }
505 void sysdep_shutdown_done(void) {}
506
507 #include "nest/cli.h"
508 int cli_get_command(cli *c UNUSED) { return 0; }
509 void cli_write_trigger(cli *c UNUSED) {}
510 cli *cmd_reconfig_stored_cli;