summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/Makefile3
-rw-r--r--test/birdtest.c488
-rw-r--r--test/birdtest.h183
-rw-r--r--test/bt-utils.c223
-rw-r--r--test/bt-utils.h35
5 files changed, 932 insertions, 0 deletions
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 00000000..2cee9234
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,3 @@
+src := birdtest.c bt-utils.c
+obj := $(src-o-files)
+tests_objs := $(tests_objs) $(src-o-files)
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;
diff --git a/test/birdtest.h b/test/birdtest.h
new file mode 100644
index 00000000..0090712d
--- /dev/null
+++ b/test/birdtest.h
@@ -0,0 +1,183 @@
+/*
+ * BIRD -- Unit Test Framework (BIRD Test)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRDTEST_H_
+#define _BIRDTEST_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include "nest/bird.h"
+
+
+extern int bt_result;
+extern int bt_suite_result;
+extern char bt_out_fmt_buf[1024];
+
+extern uint bt_verbose;
+#define BT_VERBOSE_NO 0
+#define BT_VERBOSE_SUITE 1
+#define BT_VERBOSE_SUITE_CASE 2
+#define BT_VERBOSE_ABSOLUTELY_ALL 3
+
+extern const char *bt_filename;
+extern const char *bt_test_id;
+
+void bt_init(int argc, char *argv[]);
+int bt_exit_value(void);
+int bt_test_suite_base(int (*test_fn)(const void *), const char *test_id, const void *test_fn_argument, int forked, int timeout, const char *dsc, ...);
+long int bt_random(void);
+
+void bt_log_suite_result(int result, const char *fmt, ...);
+void bt_log_suite_case_result(int result, const char *fmt, ...);
+
+#define BT_SUCCESS 42 /* 1 is too usual, filter quitbird returns 1 too */
+#define BT_FAILURE 0
+
+#define BT_TIMEOUT 5 /* Default timeout in seconds */
+#define BT_FORKING 1 /* Forking is enabled in default */
+
+#define BT_RANDOM_SEED 982451653
+
+#define BT_BUFFER_SIZE 10000
+
+#define BT_PROMPT_GREEN "\e[1;32m"
+#define BT_PROMPT_RED "\e[1;31m"
+#define BT_PROMPT_NORMAL "\e[0m"
+#define BT_PROMPT_OK " [" BT_PROMPT_GREEN " OK " BT_PROMPT_NORMAL "] "
+#define BT_PROMPT_OK_NO_COLOR " [" " OK " "] "
+#define BT_PROMPT_FAIL " [" BT_PROMPT_RED "FAIL" BT_PROMPT_NORMAL "] "
+#define BT_PROMPT_FAIL_NO_COLOR " [" "FAIL" "] "
+#define BT_PROMPT_OK_FAIL_STRLEN 8 /* strlen ' [FAIL] ' */
+
+#define bt_test_suite(fn, dsc, ...) \
+ bt_test_suite_extra(fn, BT_FORKING, BT_TIMEOUT, dsc, ##__VA_ARGS__)
+
+#define bt_test_suite_extra(fn, f, t, dsc, ...) \
+ bt_test_suite_base((int (*)(const void *))fn, #fn, NULL, f, t, dsc, ##__VA_ARGS__)
+
+#define bt_test_suite_arg(fn, arg, dsc, ...) \
+ bt_test_suite_arg_extra(fn, arg, BT_FORKING, BT_TIMEOUT, dsc, ##__VA_ARGS__)
+
+#define bt_test_suite_arg_extra(fn, arg, f, t, dsc, ...) \
+ bt_test_suite_base(fn, #fn, arg, f, t, dsc, ##__VA_ARGS__)
+
+#define bt_abort() \
+ bt_abort_msg("Aborted at %s:%d", __FILE__, __LINE__)
+
+#define bt_abort_msg(format, ...) \
+ do \
+ { \
+ bt_log(format, ##__VA_ARGS__); \
+ abort(); \
+ } while (0)
+
+#define bt_log(format, ...) \
+ do \
+ { \
+ if (bt_test_id) \
+ printf("%s: %s: " format "\n", bt_filename, bt_test_id, ##__VA_ARGS__); \
+ else \
+ printf("%s: " format "\n", bt_filename, ##__VA_ARGS__); \
+ } while(0)
+
+#define bt_debug(format, ...) \
+ do \
+ { \
+ if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL) \
+ printf(format, ##__VA_ARGS__); \
+ } while (0)
+
+#define bt_assert(test) \
+ bt_assert_msg(test, "Assertion (%s) at %s:%d", #test, __FILE__, __LINE__)
+
+#define bt_assert_msg(test, format, ...) \
+ do \
+ { \
+ int bt_suit_case_result = BT_SUCCESS; \
+ if ((test) == 0) \
+ { \
+ bt_result = BT_FAILURE; \
+ bt_suite_result = BT_FAILURE; \
+ bt_suit_case_result = BT_FAILURE; \
+ } \
+ bt_log_suite_case_result(bt_suit_case_result, format, ##__VA_ARGS__); \
+ } while (0)
+
+#define bt_syscall(test, format, ...) \
+ do \
+ { \
+ if (test) \
+ { \
+ bt_log(format ": %s", ##__VA_ARGS__, strerror(errno)); \
+ exit(3); \
+ } \
+ } while (0)
+
+#define bt_sprintf_concat(s, format, ...) \
+ snprintf(s + strlen(s), sizeof(s) - strlen(s), format, ##__VA_ARGS__)
+
+struct bt_pair {
+ const void *in;
+ const void *out;
+};
+
+/* Data structure used by bt_assert_batch() function */
+struct bt_batch {
+ /* in_fmt / out_fmt - formating data
+ * @buf: buffer for write stringified @data
+ * @size: empty size in @buf
+ * @data: data for stringify
+ *
+ * There are some build-in functions, see bt_fmt_* functions */
+ void (*in_fmt)(char *buf, size_t size, const void *data);
+ void (*out_fmt)(char *buf, size_t size, const void *data);
+
+ /* Temporary output buffer */
+ void *out_buf;
+
+ /* test_fn - testing function
+ * @out: output data from tested function
+ * @in: data for input
+ * @expected_out: expected data from tested function
+ *
+ * Input arguments should not be stringified using in_fmt() or out_fmt()
+ * function already. This function should return only BT_SUCCESS or
+ * BT_FAILURE */
+ int (*test_fn)(void *out, const void *in, const void *expected_out);
+
+ /* Name of testing function @test_fn */
+ const char *test_fn_name;
+
+ /* Number of items in data*/
+ int ndata;
+
+ /* Array of input and expected output pairs */
+ struct bt_pair *data;
+};
+
+void bt_fmt_str(char *buf, size_t size, const void *data);
+void bt_fmt_unsigned(char *buf, size_t size, const void *data);
+void bt_fmt_ipa(char *buf, size_t size, const void *data);
+int bt_assert_batch__(struct bt_batch *opts);
+int bt_is_char(byte c);
+
+#define bt_assert_batch(data__, fn__, in_fmt__, out_fmt__) \
+ bt_assert_batch__(& (struct bt_batch) { \
+ .data = data__, \
+ .ndata = ARRAY_SIZE(data__), \
+ .test_fn = fn__, \
+ .test_fn_name = #fn__, \
+ .in_fmt = in_fmt__, \
+ .out_fmt = out_fmt__, \
+ .out_buf = bt_out_fmt_buf, /* Global memory for this usage */ \
+ })
+
+#endif /* _BIRDTEST_H_ */
diff --git a/test/bt-utils.c b/test/bt-utils.c
new file mode 100644
index 00000000..aaeb77b8
--- /dev/null
+++ b/test/bt-utils.c
@@ -0,0 +1,223 @@
+/*
+ * BIRD Test -- Utils for testing parsing configuration file
+ *
+ * (c) 2015 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "test/birdtest.h"
+#include "test/bt-utils.h"
+
+#include "nest/bird.h"
+#include "nest/route.h"
+#include "nest/protocol.h"
+
+#include "sysdep/unix/unix.h"
+#include "sysdep/unix/krt.h"
+
+#include "nest/iface.h"
+#include "nest/locks.h"
+
+#include "filter/filter.h"
+
+#define BETWEEN(a, b, c) (((a) >= (b)) && ((a) <= (c)))
+
+static const byte *bt_config_parse_pos;
+static uint bt_config_parse_remain_len;
+
+/* This is cf_read_hook for hard-coded text configuration */
+static int
+cf_static_read(byte *dest, uint max_len, int fd UNUSED)
+{
+ if (max_len > bt_config_parse_remain_len)
+ max_len = bt_config_parse_remain_len;
+ memcpy(dest, bt_config_parse_pos, max_len);
+ bt_config_parse_pos += max_len;
+ bt_config_parse_remain_len -= max_len;
+ return max_len;
+}
+
+/* This is cf_read_hook for reading configuration files,
+ * function is copied from main.c, cf_read() */
+static int
+cf_file_read(byte *dest, uint max_len, int fd)
+{
+ int l = read(fd, dest, max_len);
+ if (l < 0)
+ cf_error("Read error");
+ return l;
+}
+
+void
+bt_bird_init(void)
+{
+ if(bt_verbose)
+ log_init_debug("");
+ log_switch(bt_verbose != 0, NULL, NULL);
+
+ resource_init();
+ olock_init();
+ io_init();
+ rt_init();
+ if_init();
+ config_init();
+
+ protos_build();
+ proto_build(&proto_unix_kernel);
+ proto_build(&proto_unix_iface);
+}
+
+void bt_bird_cleanup(void)
+{
+ for (int i = 0; i < EAP_MAX; i++)
+ attr_class_to_protocol[i] = NULL;
+
+ config = new_config = NULL;
+}
+
+static char *
+bt_load_file(const char *filename, int quiet)
+{
+ FILE *f = fopen(filename, "rb");
+ if (!quiet)
+ bt_assert_msg(f != NULL, "Open %s", filename);
+
+ if (f == NULL)
+ return NULL;
+
+ fseek(f, 0, SEEK_END);
+ long file_size_ = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (file_size_ < 0)
+ return NULL;
+
+ size_t file_size = file_size_;
+ size_t read_size = 0;
+
+ char *file_body = mb_allocz(&root_pool, file_size+1);
+
+ /* XXX: copied from cf-lex.c */
+ errno=0;
+ while ((read_size += fread(file_body+read_size, 1, file_size-read_size, f)) != file_size && ferror(f))
+ {
+ bt_debug("iteration \n");
+ if(errno != EINTR)
+ {
+ bt_abort_msg("errno: %d", errno);
+ break;
+ }
+ errno=0;
+ clearerr(f);
+ }
+ fclose(f);
+
+ if (!quiet)
+ bt_assert_msg(read_size == file_size, "Read %s", filename);
+
+ return file_body;
+}
+
+static void
+bt_show_cfg_error(const struct config *cfg)
+{
+ int lino = 0;
+ int lino_delta = 5;
+ int lino_err = cfg->err_lino;
+
+ const char *str = bt_load_file(cfg->err_file_name, 1);
+
+ while (str && *str)
+ {
+ lino++;
+ if (BETWEEN(lino, lino_err - lino_delta, lino_err + lino_delta))
+ bt_debug("%4u%s", lino, (lino_err == lino ? " >> " : " "));
+ do
+ {
+ if (BETWEEN(lino, lino_err - lino_delta, lino_err + lino_delta))
+ bt_debug("%c", *str);
+ } while (*str && *(str++) != '\n');
+ }
+ bt_debug("\n");
+}
+
+static struct config *
+bt_config_parse__(struct config *cfg)
+{
+ bt_assert_msg(config_parse(cfg) == 1, "Parse %s", cfg->file_name);
+
+ if (cfg->err_msg)
+ {
+ bt_debug("Parse error %s, line %d: %s\n", cfg->err_file_name, cfg->err_lino, cfg->err_msg);
+ bt_show_cfg_error(cfg);
+ return NULL;
+ }
+
+ config_commit(cfg, RECONFIG_HARD, 0);
+ new_config = cfg;
+
+ return cfg;
+}
+
+struct config *
+bt_config_parse(const char *cfg_str)
+{
+ struct config *cfg = config_alloc("configuration");
+
+ bt_config_parse_pos = cfg_str;
+ bt_config_parse_remain_len = strlen(cfg_str);
+ cf_read_hook = cf_static_read;
+
+ return bt_config_parse__(cfg);
+}
+
+struct config *
+bt_config_file_parse(const char *filepath)
+{
+ struct config *cfg = config_alloc(filepath);
+
+ cfg->file_fd = open(filepath, O_RDONLY);
+ bt_assert_msg(cfg->file_fd > 0, "Open %s", filepath);
+ if (cfg->file_fd < 0)
+ return NULL;
+
+ cf_read_hook = cf_file_read;
+
+ return bt_config_parse__(cfg);
+}
+
+/*
+ * Returns @base raised to the power of @power.
+ */
+uint
+bt_naive_pow(uint base, uint power)
+{
+ uint result = 1;
+ uint i;
+ for (i = 0; i < power; i++)
+ result *= base;
+ return result;
+}
+
+/**
+ * bytes_to_hex - transform data into hexadecimal representation
+ * @buf: preallocated string buffer
+ * @in_data: data for transformation
+ * @size: the length of @in_data
+ *
+ * This function transforms @in_data of length @size into hexadecimal
+ * representation and writes it into @buf.
+ */
+void
+bt_bytes_to_hex(char *buf, const byte *in_data, size_t size)
+{
+ size_t i;
+ for(i = 0; i < size; i++)
+ sprintf(buf + i*2, "%02x", in_data[i]);
+}
+
diff --git a/test/bt-utils.h b/test/bt-utils.h
new file mode 100644
index 00000000..13d267cc
--- /dev/null
+++ b/test/bt-utils.h
@@ -0,0 +1,35 @@
+/*
+ * BIRD Test -- Utils for testing parsing configuration file
+ *
+ * (c) 2015 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRDTEST_UTILS_H_
+#define _BIRDTEST_UTILS_H_
+
+#include "sysdep/config.h"
+
+#define PRIip4 "%d.%d.%d.%d"
+#define ARGip4(x) (_I(x) >> 24) & 0xff, (_I(x) >> 16) & 0xff, (_I(x) >> 8) & 0xff, _I(x) & 0xff
+
+#define PRIip6 "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X"
+#define ARGip6_HIGH(x,i) (((x).addr[(i)] >> 16) & 0xffff)
+#define ARGip6_LOW(x,i) ((x).addr[(i)] & 0xffff)
+#define ARGip6_BOTH(x,i) ARGip6_HIGH(x,i), ARGip6_LOW(x,i)
+#define ARGip6(x) ARGip6_BOTH((x), 0), ARGip6_BOTH((x), 1), ARGip6_BOTH((x), 2), ARGip6_BOTH((x), 3)
+
+#define BT_CONFIG_PARSE_ROUTER_ID "router id 1.1.1.1; \n"
+#define BT_CONFIG_PARSE_STATIC_PROTO "protocol static { ipv4; } \n"
+#define BT_CONFIG_SIMPLE BT_CONFIG_PARSE_ROUTER_ID BT_CONFIG_PARSE_STATIC_PROTO
+
+uint bt_naive_pow(uint base, uint power);
+void bt_bytes_to_hex(char *buf, const byte *in_data, size_t size);
+
+void bt_bird_init(void);
+void bt_bird_cleanup(void);
+struct config *bt_config_parse(const char *cfg);
+struct config *bt_config_file_parse(const char *filepath);
+
+#endif /* _BIRDTEST_UTILS_H_ */