mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-11-17 16:48:43 +00:00
fa71b268a8
This patch ensures width of output lines from testing framework (not debug output). So output piped lined into file that has default width 80 cols is now correctly wrapped.
503 lines
11 KiB
C
503 lines
11 KiB
C
/*
|
|
* BIRD -- Unit Test Framework (BIRD Test)
|
|
*
|
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include "test/birdtest.h"
|
|
#include "lib/string.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 no_fork;
|
|
static int no_timeout;
|
|
static int is_terminal; /* Whether stdout is a live terminal or pipe redirect */
|
|
|
|
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 */
|
|
|
|
long int
|
|
bt_random(void)
|
|
{
|
|
/* Seeded in bt_init() */
|
|
long int rand_low, rand_high;
|
|
|
|
rand_low = random();
|
|
rand_high = random();
|
|
return (rand_low & 0xffff) | ((rand_high & 0xffff) << 16);
|
|
}
|
|
|
|
void
|
|
bt_init(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
|
|
srandom(BT_RANDOM_SEED);
|
|
|
|
bt_verbose = 0;
|
|
bt_filename = argv[0];
|
|
bt_result = BT_SUCCESS;
|
|
bt_test_id = NULL;
|
|
is_terminal = isatty(fileno(stdout));
|
|
|
|
while ((c = getopt(argc, argv, "lcftv")) >= 0)
|
|
switch (c)
|
|
{
|
|
case 'l':
|
|
list_tests = 1;
|
|
break;
|
|
|
|
case 'c':
|
|
do_core = 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");
|
|
}
|
|
|
|
return;
|
|
|
|
usage:
|
|
printf("Usage: %s [-l] [-c] [-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(" -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);
|
|
|
|
if (fn_arg)
|
|
result = fn(fn_arg);
|
|
else
|
|
result = ((int (*)(void))fn)();
|
|
|
|
if (bt_suite_result != BT_SUCCESS)
|
|
result = BT_FAILURE;
|
|
|
|
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: BT_SUCCESS or BT_FAILURE
|
|
* @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, const char *fmt, va_list argptr)
|
|
{
|
|
static char msg_buf[BT_BUFFER_SIZE];
|
|
char *pos;
|
|
|
|
snprintf(msg_buf, sizeof(msg_buf), "%s%s%s%s",
|
|
bt_filename,
|
|
bt_test_id ? ": " : "",
|
|
bt_test_id ? bt_test_id : "",
|
|
(fmt && strlen(fmt) > 0) ? ": " : "");
|
|
pos = msg_buf + strlen(msg_buf);
|
|
|
|
vsnprintf(pos, sizeof(msg_buf) - (pos - msg_buf), fmt, argptr);
|
|
|
|
int chrs = 0;
|
|
for (int 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 != BT_SUCCESS)
|
|
result_str = is_terminal ? BT_PROMPT_FAIL : BT_PROMPT_FAIL_NO_COLOR;
|
|
|
|
printf("%s\n", result_str);
|
|
}
|
|
|
|
/**
|
|
* bt_log_overall_result - pretty print of suite case result
|
|
* @result: BT_SUCCESS or BT_FAILURE
|
|
* @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, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
|
|
/**
|
|
* bt_log_suite_result - pretty print of suite case result
|
|
* @result: BT_SUCCESS or BT_FAILURE
|
|
* @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 == BT_FAILURE)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
bt_log_result(result, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* bt_log_suite_case_result - pretty print of suite result
|
|
* @result: BT_SUCCESS or BT_FAILURE
|
|
* @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, fmt, argptr);
|
|
va_end(argptr);
|
|
}
|
|
}
|
|
|
|
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 BT_SUCCESS;
|
|
}
|
|
|
|
if (no_fork)
|
|
forked = 0;
|
|
|
|
if (no_timeout)
|
|
timeout = 0;
|
|
|
|
if (request && strcmp(id, request))
|
|
return BT_SUCCESS;
|
|
|
|
bt_suite_result = BT_SUCCESS;
|
|
bt_test_id = id;
|
|
|
|
if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL)
|
|
bt_log("Starting");
|
|
|
|
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 = BT_FAILURE;
|
|
|
|
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_FAILURE)
|
|
bt_result = BT_FAILURE;
|
|
|
|
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_SUCCESS))
|
|
bt_log_overall_result(bt_result, "");
|
|
return bt_result == BT_SUCCESS ? 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 BT_SUCCESS or BT_FAILURE.
|
|
*/
|
|
int
|
|
bt_assert_batch__(struct bt_batch *opts)
|
|
{
|
|
int i;
|
|
for (i = 0; i < opts->ndata; i++)
|
|
{
|
|
int bt_suit_case_result = opts->test_fn(opts->out_buf, opts->data[i].in, opts->data[i].out);
|
|
|
|
if (bt_suit_case_result == BT_FAILURE)
|
|
bt_suite_result = BT_FAILURE;
|
|
|
|
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 == BT_FAILURE)
|
|
{
|
|
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;
|
|
bsnprintf(buf, size, "%I", *ip);
|
|
}
|
|
|
|
int
|
|
bt_is_char(byte c)
|
|
{
|
|
return (c >= (byte) 32 && c <= (byte) 126);
|
|
}
|
|
|
|
/*
|
|
* Mock-ups of all necessary public functions in main.c
|
|
*/
|
|
|
|
char *bird_name;
|
|
void async_config(void) {}
|
|
void async_dump(void) {}
|
|
void async_shutdown(void) {}
|
|
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_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;
|