0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-11-17 16:48:43 +00:00
bird/test/birdtest.c
Pavel Tvrdik fa71b268a8 Birdtest: Put hard new lines for strict line width
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.
2016-11-11 17:03:43 +01:00

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;