summaryrefslogtreecommitdiff
path: root/test/birdtest.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/birdtest.c')
-rw-r--r--test/birdtest.c488
1 files changed, 488 insertions, 0 deletions
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 <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)
+{
+ 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;