0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 09:41:54 +00:00
bird/test/birdtest.c
Ondrej Zajicek fc2b4b26a6 Fixed crash if logging happened in unit tests
The patch initializes logging in unit tests. Previously, unit tests did
not initialize logging and other subsystems, just resources. But
resource_init() could under certain circumstances trigger logging and
cause crash.

The bug was Found by Jakub Ruzicka, dissected by David Petera and Maria
Matejka, disguised as failing build for Debian arm64 in pbuilder
emulation which did not like disabling THP.

Fixes #42.
2024-11-27 04:15:16 +01:00

568 lines
12 KiB
C

/*
* BIRD -- Unit Test Framework (BIRD Test)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include <inttypes.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include "test/birdtest.h"
#include "lib/string.h"
#include "lib/event.h"
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif
#define BACKTRACE_MAX_LINES 100
#define sprintf_concat(s, format, ...) \
snprintf(s + strlen(s), sizeof(s) - strlen(s), format, ##__VA_ARGS__)
static const char *request;
static int list_tests;
static int do_core;
static int do_die;
static int no_fork;
static int no_timeout;
static int is_terminal; /* Whether stdout is a live terminal or pipe redirect */
volatile sig_atomic_t async_config_flag; /* Asynchronous reconfiguration/dump scheduled */
volatile sig_atomic_t async_dump_flag;
volatile sig_atomic_t async_shutdown_flag;
uint bt_verbose;
const char *bt_filename;
const char *bt_test_id;
int bt_result; /* Overall program run result */
int bt_suite_result; /* One suit result */
char bt_out_fmt_buf[1024]; /* Temporary memory buffer for output of testing function */
struct timespec bt_begin, bt_suite_begin, bt_suite_case_begin;
u64 bt_random_state[] = {
0x80241f302bd4d95d, 0xd10ba2e910f772b, 0xea188c9046f507c5, 0x4c4c581f04e6da05,
0x53d9772877c1b647, 0xab8ce3eb466de6c5, 0xad02844c8a8e865f, 0xe8cc78080295065d
};
void log_init_debug(char *);
void log_switch(int initial, list *l, const char *);
void
bt_init(int argc, char *argv[])
{
int c;
initstate(BT_RANDOM_SEED, (char *) bt_random_state, sizeof(bt_random_state));
bt_verbose = 0;
bt_filename = argv[0];
bt_result = 1;
bt_test_id = NULL;
is_terminal = isatty(fileno(stdout));
while ((c = getopt(argc, argv, "lcdftv")) >= 0)
switch (c)
{
case 'l':
list_tests = 1;
break;
case 'c':
do_core = 1;
break;
case 'd':
do_die = 1;
break;
case 'f':
no_fork = 1;
break;
case 't':
no_timeout = 1;
break;
case 'v':
bt_verbose++;
break;
default:
goto usage;
}
/* Optional requested test_id */
if ((optind + 1) == argc)
request = argv[optind++];
if (optind != argc)
goto usage;
if (do_core)
{
struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY};
int rv = setrlimit(RLIMIT_CORE, &rl);
bt_syscall(rv < 0, "setrlimit RLIMIT_CORE");
}
clock_gettime(CLOCK_MONOTONIC, &bt_begin);
bt_suite_case_begin = bt_suite_begin = bt_begin;
/* Initialize logging to stderr */
log_init_debug("");
log_switch(1, NULL, NULL);
resource_init();
ev_init_list(&global_event_list);
return;
usage:
printf("Usage: %s [-l] [-c] [-d] [-f] [-t] [-vvv] [<test_suit_name>]\n", argv[0]);
printf("Options: \n");
printf(" -l List all test suite names and descriptions \n");
printf(" -c Force unlimit core dumps (needs root privileges) \n");
printf(" -d Die on first failed test case \n");
printf(" -f No forking \n");
printf(" -t No timeout limit \n");
printf(" -v More verbosity, maximum is 3 -vvv \n");
exit(3);
}
static void
bt_dump_backtrace(void)
{
#ifdef HAVE_EXECINFO_H
void *buf[BACKTRACE_MAX_LINES];
char **pp_backtrace;
int lines, j;
if (!bt_verbose)
return;
lines = backtrace(buf, BACKTRACE_MAX_LINES);
bt_log("backtrace() returned %d addresses", lines);
pp_backtrace = backtrace_symbols(buf, lines);
if (pp_backtrace == NULL)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < lines; j++)
bt_log("%s", pp_backtrace[j]);
free(pp_backtrace);
#endif /* HAVE_EXECINFO_H */
}
static
int bt_run_test_fn(int (*fn)(const void *), const void *fn_arg, int timeout)
{
int result;
alarm(timeout);
result = fn(fn_arg);
if (!bt_suite_result)
result = 0;
tmp_flush();
return result;
}
static uint
get_num_terminal_cols(void)
{
struct winsize w = {};
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
uint cols = w.ws_col;
return (cols > 0 ? cols : 80);
}
/**
* bt_log_result - pretty print of test result
* @result: 1 or 0
* @fmt: a description message (could be long, over more lines)
* @argptr: variable argument list
*
* This function is used for pretty printing of test results on all verbose
* levels.
*/
static void
bt_log_result(int result, u64 time, const char *fmt, va_list argptr)
{
static char msg_buf[BT_BUFFER_SIZE];
char *pos;
snprintf(msg_buf, sizeof(msg_buf), "%s%s%s %" PRIu64 ".%09" PRIu64 "s%s",
bt_filename,
bt_test_id ? ": " : "",
bt_test_id ? bt_test_id : "",
time / 1000000000,
time % 1000000000,
(fmt && strlen(fmt) > 0) ? ": " : "");
pos = msg_buf + strlen(msg_buf);
if (fmt)
vsnprintf(pos, sizeof(msg_buf) - (pos - msg_buf), fmt, argptr);
int chrs = 0;
for (uint i = 0; i < strlen(msg_buf); i += get_num_terminal_cols())
{
if (i)
printf("\n");
char *stop = msg_buf + i + get_num_terminal_cols();
char backup = *stop;
*stop = 0;
chrs = printf("%s", msg_buf + i);
*stop = backup;
}
int offset = get_num_terminal_cols() - chrs - BT_PROMPT_OK_FAIL_STRLEN;
if (offset < 0)
{
printf("\n");
offset = get_num_terminal_cols() - BT_PROMPT_OK_FAIL_STRLEN;
}
for (int i = 0; i < offset; i++)
putchar(' ');
const char *result_str = is_terminal ? BT_PROMPT_OK : BT_PROMPT_OK_NO_COLOR;
if (!result)
result_str = is_terminal ? BT_PROMPT_FAIL : BT_PROMPT_FAIL_NO_COLOR;
printf("%s\n", result_str);
if (do_die && !result)
abort();
}
static u64
get_time_diff(struct timespec *begin)
{
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - begin->tv_sec) * 1000000000ULL
+ end.tv_nsec - begin->tv_nsec;
}
/**
* bt_log_overall_result - pretty print of suite case result
* @result: 1 or 0
* @fmt: a description message (could be long, over more lines)
* ...: variable argument list
*
* This function is used for pretty printing of test suite case result.
*/
static void
bt_log_overall_result(int result, const char *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
bt_log_result(result, get_time_diff(&bt_begin), fmt, argptr);
va_end(argptr);
}
/**
* bt_log_suite_result - pretty print of suite case result
* @result: 1 or 0
* @fmt: a description message (could be long, over more lines)
* ...: variable argument list
*
* This function is used for pretty printing of test suite case result.
*/
void
bt_log_suite_result(int result, const char *fmt, ...)
{
if (bt_verbose >= BT_VERBOSE_SUITE || !result)
{
va_list argptr;
va_start(argptr, fmt);
bt_log_result(result, get_time_diff(&bt_suite_begin), fmt, argptr);
va_end(argptr);
}
}
/**
* bt_log_suite_case_result - pretty print of suite result
* @result: 1 or 0
* @fmt: a description message (could be long, over more lines)
* ...: variable argument list
*
* This function is used for pretty printing of test suite result.
*/
void
bt_log_suite_case_result(int result, const char *fmt, ...)
{
if(bt_verbose >= BT_VERBOSE_SUITE_CASE)
{
va_list argptr;
va_start(argptr, fmt);
bt_log_result(result, get_time_diff(&bt_suite_case_begin), fmt, argptr);
va_end(argptr);
}
}
void
bt_reset_suite_case_timer(void)
{
clock_gettime(CLOCK_MONOTONIC, &bt_suite_case_begin);
}
int
bt_test_suite_base(int (*fn)(const void *), const char *id, const void *fn_arg, int forked, int timeout, const char *dsc, ...)
{
if (list_tests)
{
printf("%28s - ", id);
va_list args;
va_start(args, dsc);
vprintf(dsc, args);
va_end(args);
printf("\n");
return 1;
}
if (no_fork)
forked = 0;
if (no_timeout)
timeout = 0;
if (request && strcmp(id, request))
return 1;
bt_suite_result = 1;
bt_test_id = id;
if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL)
bt_log("Starting");
clock_gettime(CLOCK_MONOTONIC, &bt_suite_begin);
bt_suite_case_begin = bt_suite_begin;
if (!forked)
{
bt_suite_result = bt_run_test_fn(fn, fn_arg, timeout);
}
else
{
pid_t pid = fork();
bt_syscall(pid < 0, "fork");
if (pid == 0)
{
/* child of fork */
_exit(bt_run_test_fn(fn, fn_arg, timeout));
}
int s;
int rv = waitpid(pid, &s, 0);
bt_syscall(rv < 0, "waitpid");
if (WIFEXITED(s))
{
/* Normal exit */
bt_suite_result = WEXITSTATUS(s);
}
else if (WIFSIGNALED(s))
{
/* Stopped by signal */
bt_suite_result = 0;
int sn = WTERMSIG(s);
if (sn == SIGALRM)
{
bt_log("Timeout expired");
}
else if (sn == SIGSEGV)
{
bt_log("Segmentation fault");
bt_dump_backtrace();
}
else if (sn != SIGABRT)
bt_log("Signal %d received", sn);
}
if (WCOREDUMP(s) && bt_verbose)
bt_log("Core dumped");
}
if (!bt_suite_result)
bt_result = 0;
bt_log_suite_result(bt_suite_result, NULL);
bt_test_id = NULL;
return bt_suite_result;
}
int
bt_exit_value(void)
{
if (!list_tests || (list_tests && !bt_result))
bt_log_overall_result(bt_result, "");
return bt_result ? EXIT_SUCCESS : EXIT_FAILURE;
}
/**
* bt_assert_batch__ - test a batch of inputs/outputs tests
* @opts: includes all necessary data
*
* Should be called using macro bt_assert_batch().
* Returns 1 or 0.
*/
int
bt_assert_batch__(struct bt_batch *opts)
{
int i;
for (i = 0; i < opts->ndata; i++)
{
if (bt_verbose >= BT_VERBOSE_SUITE)
clock_gettime(CLOCK_MONOTONIC, &bt_suite_case_begin);
int bt_suit_case_result = opts->test_fn(opts->out_buf, opts->data[i].in, opts->data[i].out);
if (bt_suit_case_result == 0)
bt_suite_result = 0;
char b[BT_BUFFER_SIZE];
snprintf(b, sizeof(b), "%s(", opts->test_fn_name);
opts->in_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].in);
sprintf_concat(b, ") gives ");
opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->out_buf);
if (bt_suit_case_result == 0)
{
sprintf_concat(b, ", but expecting is ");
opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].out);
}
bt_log_suite_case_result(bt_suit_case_result, "%s", b);
}
return bt_suite_result;
}
/**
* bt_fmt_str - formating string into output buffer
* @buf: buffer for write
* @size: empty size in @buf
* @data: null-byte terminated string
*
* This function can be used with bt_assert_batch() function.
* Input @data should be const char * string.
*/
void
bt_fmt_str(char *buf, size_t size, const void *data)
{
const byte *s = data;
snprintf(buf, size, "\"");
while (*s)
{
snprintf(buf+strlen(buf), size-strlen(buf), bt_is_char(*s) ? "%c" : "\\%03u", *s);
s++;
}
snprintf(buf+strlen(buf), size-strlen(buf), "\"");
}
/**
* bt_fmt_unsigned - formating unsigned int into output buffer
* @buf: buffer for write
* @size: empty size in @buf
* @data: unsigned number
*
* This function can be used with bt_assert_batch() function.
*/
void
bt_fmt_unsigned(char *buf, size_t size, const void *data)
{
const uint *n = data;
snprintf(buf, size, "0x%x (%u)", *n, *n);
}
/**
* bt_fmt_ipa - formating ip_addr into output buffer
* @buf: buffer for write
* @size: empty size in @buf
* @data: should be struct ip_addr *
*
* This function can be used with bt_assert_batch() function.
*/
void
bt_fmt_ipa(char *buf, size_t size, const void *data)
{
const ip_addr *ip = data;
if (data)
bsnprintf(buf, size, "%I", *ip);
else
bsnprintf(buf, size, "(null)");
}
void
bt_format_net(char *buf, size_t size, const void *data)
{
if (data)
bsnprintf(buf, size, "%N", (const net_addr *) data);
else
bsnprintf(buf, size, "(null)");
}
int
bt_is_char(byte c)
{
return (c >= (byte) 32 && c <= (byte) 126);
}
/*
* Mock-ups of all necessary public functions in main.c
*/
int parse_and_exit;
char *bird_name;
void async_config(void) {}
void async_dump(void) {}
void async_shutdown(void) {}
char *get_hostname(linpool *lp UNUSED) { return NULL; }
void cmd_check_config(char *name UNUSED) {}
void cmd_reconfig(char *name UNUSED, int type UNUSED, int timeout UNUSED) {}
void cmd_reconfig_confirm(void) {}
void cmd_reconfig_undo(void) {}
void cmd_reconfig_status(void) {}
void cmd_graceful_restart(void) {}
void cmd_shutdown(void) {}
void cmd_reconfig_undo_notify(void) {}
#include "nest/bird.h"
#include "lib/net.h"
#include "conf/conf.h"
void sysdep_preconfig(struct config *c UNUSED) {}
int sysdep_commit(struct config *new UNUSED, struct config *old UNUSED) { return 0; }
void sysdep_shutdown_done(void) {}
#include "nest/cli.h"
int cli_get_command(cli *c UNUSED) { return 0; }
void cli_write_trigger(cli *c UNUSED) {}
cli *cmd_reconfig_stored_cli;