From 9b0a0ba9e671d9134b93c33ab73ccccb352acafa Mon Sep 17 00:00:00 2001 From: "Ondrej Zajicek (work)" Date: Wed, 9 Nov 2016 16:36:34 +0100 Subject: Unit Testing for BIRD - Unit Testing Framework (BirdTest) - Integration of BirdTest into the BIRD build system - Tests for several BIRD modules Based on squashed Pavel Tvrdik's int-test branch, updated for current int-new branch. --- test/birdtest.c | 488 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 test/birdtest.c (limited to 'test/birdtest.c') diff --git a/test/birdtest.c b/test/birdtest.c new file mode 100644 index 00000000..4e8645a4 --- /dev/null +++ b/test/birdtest.c @@ -0,0 +1,488 @@ +/* + * BIRD -- Unit Test Framework (BIRD Test) + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "test/birdtest.h" +#include "lib/string.h" + +#ifdef HAVE_EXECINFO_H +#include +#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] []\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) +{ + char fmt_buf[BT_BUFFER_SIZE]; + 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); + + /* 'll' means here Last Line */ + uint cols = get_num_terminal_cols(); + uint ll_len = (strlen(msg_buf) % cols) + BT_PROMPT_OK_FAIL_STRLEN; + uint ll_offset = (ll_len / get_num_terminal_cols() + 1) * cols - BT_PROMPT_OK_FAIL_STRLEN; + uint offset = ll_offset + (strlen(msg_buf) / cols) * cols; + snprintf(fmt_buf, sizeof(fmt_buf), "%%-%us%%s\n", offset); + + 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(fmt_buf, msg_buf, 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; -- cgit v1.2.3