0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-09-19 11:55:21 +00:00

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.
This commit is contained in:
Ondrej Zajicek (work) 2016-11-09 16:36:34 +01:00
parent 8860e991f6
commit 9b0a0ba9e6
60 changed files with 6595 additions and 579 deletions

View File

@ -22,7 +22,7 @@ INSTALL_DATA=@INSTALL_DATA@
client=$(addprefix $(exedir)/,@CLIENT@)
daemon=$(exedir)/bird
protocols = @protocols@
protocols=@protocols@
prefix=@prefix@
exec_prefix=@exec_prefix@
@ -49,24 +49,26 @@ else
endif
# Meta rules
cleangoals := clean distclean
docgoals := docs userdocs progdocs
.PHONY: all daemon cli $(cleangoals) $(docgoals) tags
testgoals := check test tests tests_run
cleangoals := clean distclean testsclean
.PHONY: all daemon cli $(docgoals) $(testgoals) $(cleangoals) tags
all: daemon cli
daemon: $(daemon)
cli: $(client)
# Include directories
dirs := client conf doc filter lib nest $(addprefix proto/,$(protocols)) @sysdep_dirs@
dirs := client conf doc filter lib nest test $(addprefix proto/,$(protocols)) @sysdep_dirs@
conf-y-targets := $(addprefix $(objdir)/conf/,cf-parse.y keywords.h commands.h)
cf-local = $(conf-y-targets): $(s)config.Y
src-o-files = $(patsubst %.c,$(o)%.o,$(src))
tests-target-files = $(patsubst %.c,$(o)%,$(tests_src))
all-daemon = $(exedir)/bird: $(obj)
all-client = $(exedir)/birdc $(exedir)/birdcl: $(obj)
all-daemon = $(daemon): $(obj)
all-client = $(client): $(obj)
s = $(dir $(lastword $(MAKEFILE_LIST)))
ifeq ($(srcdir),.)
@ -109,6 +111,22 @@ $(objdir)/sysdep/paths.h: Makefile
echo >>$@ "#define PATH_CONTROL_SOCKET \"@CONTROL_SOCKET@\""
if test -n "@iproutedir@" ; then echo >>$@ "#define PATH_IPROUTE_DIR \"@iproutedir@\"" ; fi
# Unit tests rules
tests_targets_ok = $(addsuffix .ok,$(tests_targets))
$(tests_targets): %: %.o $(tests_objs)
$(E)echo LD $(LDFLAGS) -o $@ $^ $(LIBS)
$(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
$(tests_targets_ok): %.ok: %
$(Q)$* 2>/dev/null && touch $*.ok
test: testsclean check
check: tests tests_run
tests: $(tests_targets)
tests_run: $(tests_targets_ok)
# Finally include the computed dependencies
ifneq ($(filter-out $(cleangoals),$(MAKECMDGOALS)),)
@ -147,6 +165,9 @@ clean::
rm -f $(addprefix $(exedir)/,bird birdc birdcl)
find $(objdir) -name "*.[od]" -exec rm -f '{}' '+'
testsclean:
rm -f $(tests_targets_ok)
ifeq ($(objdir),obj)
distclean: clean
rm -rf $(objdir)

View File

@ -3,6 +3,8 @@ obj := $(src-o-files)
$(all-daemon)
tests_objs := $(tests_objs) $(src-o-files)
ifdef DEBUG
BISON_DEBUG=-t
#FLEX_DEBUG=-d

View File

@ -49,12 +49,10 @@
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "lib/resource.h"
#include "lib/string.h"
#include "lib/event.h"
#include "sysdep/unix/timer.h"
#include "conf/conf.h"
#include "filter/filter.h"
static jmp_buf conf_jmpbuf;
@ -85,7 +83,7 @@ int undo_available; /* Undo was not requested from last reconfiguration */
* further use. Returns a pointer to the structure.
*/
struct config *
config_alloc(const byte *name)
config_alloc(const char *name)
{
pool *p = rp_new(&root_pool, "Config");
linpool *l = lp_new(p, 4080);
@ -96,6 +94,7 @@ config_alloc(const byte *name)
char *ndup = lp_allocu(l, nlen);
memcpy(ndup, name, nlen);
init_list(&c->tests);
c->mrtdump_file = -1; /* Hack, this should be sysdep-specific */
c->pool = p;
c->mem = l;

View File

@ -9,6 +9,8 @@
#ifndef _BIRD_CONF_H_
#define _BIRD_CONF_H_
#include "sysdep/config.h"
#include "lib/ip.h"
#include "lib/resource.h"
#include "sysdep/unix/timer.h"
@ -21,6 +23,7 @@ struct config {
list protos; /* Configured protocol instances (struct proto_config) */
list tables; /* Configured routing tables (struct rtable_config) */
list logfiles; /* Configured log files (sysdep) */
list tests; /* Configured unit tests */
int mrtdump_file; /* Configured MRTDump file (sysdep, fd in unix) */
char *syslog_name; /* Name used for syslog (NULL -> no syslog) */
@ -60,7 +63,7 @@ struct config {
extern struct config *config; /* Currently active configuration */
extern struct config *new_config; /* Configuration being parsed */
struct config *config_alloc(const byte *name);
struct config *config_alloc(const char *name);
int config_parse(struct config *);
int cli_parse(struct config *);
void config_free(struct config *);
@ -161,6 +164,7 @@ static inline int cf_symbol_is_constant(struct symbol *sym)
/* Parser */
extern char *cf_text;
int cf_parse(void);
/* Sysdep hooks */

View File

@ -56,7 +56,7 @@ if test "$ac_test_CFLAGS" != set ; then
bird_cflags_default=yes
fi
AC_PROG_CC
AC_PROG_CC_C99
if test -z "$GCC" ; then
AC_MSG_ERROR([This program requires the GNU C Compiler.])
fi
@ -220,6 +220,9 @@ BIRD_CHECK_STRUCT_IP_MREQN
if test "$enable_debug" = yes ; then
AC_DEFINE(DEBUGGING)
AC_CHECK_HEADER(execinfo.h, [AC_SEARCH_LIBS([backtrace, backtrace_symbols], [c execinfo], [AC_DEFINE(HAVE_EXECINFO_H)])])
LDFLAGS="$LDFLAGS -rdynamic"
CFLAGS="$CFLAGS -O0 -ggdb -g3 -gdwarf-4"
if test "$enable_memcheck" = yes ; then
AC_CHECK_LIB(dmalloc, dmalloc_debug)
if test $ac_cv_lib_dmalloc_dmalloc_debug != yes ; then

View File

@ -1253,7 +1253,7 @@ foot).
<cf/!&tilde;/ membership operators) can be used to modify or test
eclists, with ECs instead of pairs as arguments.
<tag/lclist/
<tag><label id="type-lclist">lclist/</tag>
Lclist is a data type used for BGP large community lists. Like eclists,
lclists are very similar to clists, but they are sets of LCs instead of
pairs. The same operations (like <cf/add/, <cf/delete/ or <cf/&tilde;/

View File

@ -2,3 +2,7 @@ src := filter.c f-util.c tree.c trie.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
tests_src := tree_test.c filter_test.c trie_test.c
tests_targets := $(tests_targets) $(tests-target-files)
tests_objs := $(tests_objs) $(src-o-files)

View File

@ -323,7 +323,71 @@ f_generate_lc(struct f_inst *t1, struct f_inst *t2, struct f_inst *t3)
return rv;
}
/*
* Remove all new lines and doubled whitespaces
* and convert all tabulators to spaces
* and return a copy of string
*/
char *
assert_copy_expr(const char *start, size_t len)
{
/* XXX: Allocates maybe a little more memory than we really finally need */
char *str = cfg_alloc(len + 1);
char *dst = str;
const char *src = start - 1;
const char *end = start + len;
while (++src < end)
{
if (*src == '\n')
continue;
/* Skip doubled whitespaces */
if (src != start)
{
const char *prev = src - 1;
if ((*src == ' ' || *src == '\t') && (*prev == ' ' || *prev == '\t'))
continue;
}
if (*src == '\t')
*dst = ' ';
else
*dst = *src;
dst++;
}
*dst = '\0';
return str;
}
/*
* assert_done - create f_instruction of bt_assert
* @expr: expression in bt_assert()
* @start: pointer to first char of test expression
* @end: pointer to the last char of test expression
*/
static struct f_inst *
assert_done(struct f_inst *expr, const char *start, const char *end)
{
struct f_inst *i;
i = f_new_inst();
i->code = P('a','s');
i->a1.p = expr;
if (end >= start)
{
i->a2.p = assert_copy_expr(start, end - start + 1);
}
else
{
/* this is a break of lexer buffer */
i->a2.p = "???";
}
return i;
}
CF_DECLS
@ -341,12 +405,13 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
ADD, DELETE, CONTAINS, RESET,
PREPEND, FIRST, LAST, LAST_NONAGGREGATED, MATCH,
EMPTY,
FILTER, WHERE, EVAL)
FILTER, WHERE, EVAL,
BT_ASSERT, BT_TEST_SUITE)
%nonassoc THEN
%nonassoc ELSE
%type <x> term block cmds cmds_int cmd function_body constant constructor print_one print_list var_list var_listn dynamic_attr static_attr function_call symbol bgp_path_expr
%type <x> term block cmds cmds_int cmd function_body constant constructor print_one print_list var_list var_listn dynamic_attr static_attr function_call symbol bgp_path_expr bt_assert
%type <f> filter filter_body where_filter
%type <i> type break_command ec_kind
%type <i32> cnum
@ -356,6 +421,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
%type <px> fprefix
%type <s> decls declsn one_decl function_params
%type <h> bgp_path bgp_path_tail1 bgp_path_tail2
%type <t> get_cf_position
CF_GRAMMAR
@ -375,6 +441,21 @@ filter_eval:
EVAL term { f_eval_int($2); }
;
CF_ADDTO(conf, bt_test_suite)
bt_test_suite:
BT_TEST_SUITE '(' SYM ',' text ')' {
if (!($3->class & SYM_FUNCTION))
cf_error("Function expected");
struct f_bt_test_suite *t = cfg_alloc(sizeof(struct f_bt_test_suite));
t->fn = $3->def;
t->fn_name = $3->name;
t->dsc = $5;
add_tail(&new_config->tests, &t->n);
}
;
type:
INT { $$ = T_INT; }
| BOOL { $$ = T_BOOL; }
@ -835,6 +916,8 @@ term:
| ROA_CHECK '(' rtable ')' { $$ = f_generate_roa_check($3, NULL, NULL); }
| ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_generate_roa_check($3, $5, $7); }
| bt_assert { $$ = $1; }
/* | term '.' LEN { $$->code = P('P','l'); } */
/* function_call is inlined here */
@ -966,6 +1049,7 @@ cmd:
$$->a1.p = $2;
$$->a2.p = build_tree( $4 );
}
| bt_assert ';' { $$ = $1; }
| rtadot dynamic_attr '.' EMPTY ';' { $$ = f_generate_empty($2); }
@ -975,4 +1059,14 @@ cmd:
| rtadot dynamic_attr '.' FILTER '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'f', $2, $6 ); }
;
bt_assert:
BT_ASSERT '(' get_cf_position term get_cf_position ')' { $$ = assert_done($4, $3 + 1, $5 - 1); }
;
get_cf_position:
{
$$ = cf_text;
};
CF_END

View File

@ -52,6 +52,8 @@
#define CMP_ERROR 999
void (*bt_assert_hook)(int result, struct f_inst *assert);
static struct adata *
adata_empty(struct linpool *pool, int l)
{
@ -563,8 +565,8 @@ f_rta_cow(void)
static struct tbf rl_runtime_err = TBF_DEFAULT_LOG_LIMITS;
#define runtime(x) do { \
log_rl(&rl_runtime_err, L_ERR "filters, line %d: %s", what->lineno, x); \
#define runtime(fmt, ...) do { \
log_rl(&rl_runtime_err, L_ERR "filters, line %d: " fmt, what->lineno, ##__VA_ARGS__); \
res.type = T_RETURN; \
res.val.i = F_ERROR; \
return res; \
@ -1475,6 +1477,17 @@ interpret(struct f_inst *what)
break;
case P('a', 's'): /* Birdtest Assert */
ONEARG;
if (v1.type != T_BOOL)
runtime("Should be boolean value");
res.type = v1.type;
res.val = v1.val;
CALL(bt_assert_hook, res.val.i, what);
break;
default:
bug( "Unknown instruction %d (%c)", what->code, what->code & 0xff);

View File

@ -16,16 +16,16 @@
struct f_inst { /* Instruction */
struct f_inst *next; /* Structure is 16 bytes, anyway */
u16 code;
u16 aux;
u16 code; /* Instruction code, see the interpret() function and P() macro */
u16 aux; /* Extension to instruction code, T_*, EA_*, EAF_* */
union {
int i;
void *p;
} a1;
} a1; /* The first argument */
union {
int i;
void *p;
} a2;
} a2; /* The second argument */
int lineno;
};
@ -55,7 +55,7 @@ struct f_prefix {
};
struct f_val {
int type;
int type; /* T_* */
union {
uint i;
u64 ec;
@ -205,4 +205,15 @@ struct f_trie
#define FF_FORCE_TMPATTR 1 /* Force all attributes to be temporary */
/* Bird Tests */
struct f_bt_test_suite {
node n; /* Node in config->tests */
struct f_inst *fn; /* Root of function */
const char *fn_name; /* Name of test */
const char *dsc; /* Description */
};
/* Hook for call bt_assert() function in configuration */
extern void (*bt_assert_hook)(int result, struct f_inst *assert);
#endif

87
filter/filter_test.c Normal file
View File

@ -0,0 +1,87 @@
/*
* Filters: Tests
*
* (c) 2015 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <string.h>
#include <stdlib.h>
#include "test/birdtest.h"
#include "test/bt-utils.h"
#include "filter/filter.h"
#include "conf/conf.h"
#define BT_CONFIG_FILE "filter/test.conf"
static struct config *
parse_config_file(const void *filename_void)
{
bt_bird_init();
size_t fn_size = strlen((const char *) filename_void) + 1;
char *filename = alloca(fn_size);
strncpy(filename, filename_void, fn_size);
struct config *c = bt_config_file_parse(filename);
bt_bird_cleanup();
return c;
}
static int
run_function(const void *parsed_fn_def)
{
/* XXX: const -> non-const */
struct f_inst *f = (struct f_inst *) parsed_fn_def;
linpool *tmp = lp_new(&root_pool, 4096);
struct f_val res = f_eval(f, tmp);
rfree(tmp);
if (res.type == T_RETURN && res.val.i >= F_REJECT)
return BT_FAILURE;
return BT_SUCCESS;
}
static void
bt_assert_filter(int result, struct f_inst *assert)
{
int bt_suit_case_result = BT_SUCCESS;
if (!result)
{
bt_result = BT_FAILURE;
bt_suite_result = BT_FAILURE;
bt_suit_case_result = BT_FAILURE;
}
bt_log_suite_case_result(bt_suit_case_result, "Assertion at line %d (%s)", assert->lineno, (char *) assert->a2.p);
}
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
struct config *c = parse_config_file(BT_CONFIG_FILE);
if (c)
{
bt_assert_hook = bt_assert_filter;
struct f_bt_test_suite *t;
WALK_LIST(t, c->tests)
bt_test_suite_base(run_function, t->fn_name, t->fn, BT_FORKING, BT_TIMEOUT, "%s", t->dsc);
}
return bt_exit_value();
}

File diff suppressed because it is too large Load Diff

View File

@ -2,4 +2,5 @@
print "Entering include";
print "Should be 2: ", 1+1;
print "Leaving include";
i = 42;

View File

@ -18,6 +18,7 @@ protocol direct {
protocol kernel {
disabled;
ipv4; # Must be specified at least one channel
# learn; # Learn all routes from the kernel
# scan time 10; # Scan kernel tables every 10 seconds
}
@ -25,51 +26,58 @@ protocol kernel {
protocol static {
# disabled;
import filter { print "ahoj";
print source;
if source = RTS_STATIC then {
print "It is from static";
}
print from;
from = 1.2.3.4;
print from;
print scope;
scope = SCOPE_HOST;
print scope;
if !(scope ~ [ SCOPE_HOST, SCOPE_SITE ]) then {
print "Failed in test";
quitbird;
}
preference = 15;
print preference;
preference = 29;
print preference;
rip_metric = 1;
print rip_metric;
rip_metric = rip_metric + 5;
print rip_metric;
bgp_community = - empty - ;
print "nazdar";
bgp_community = add(bgp_community, (1,2));
print "cau";
bgp_community = add(bgp_community, (2,3));
bgp_community.add((4,5));
print "community = ", bgp_community;
bgp_community.delete((2,3));
print "community = ", bgp_community;
bgp_community.empty;
print "community = ", bgp_community;
print "done";
};
ipv4 {
export all;
import filter {
print "ahoj";
print source;
if source = RTS_STATIC then {
print "It is from static";
}
print from;
from = 1.2.3.4;
print from;
print scope;
scope = SCOPE_HOST;
print scope;
if !(scope ~ [ SCOPE_HOST, SCOPE_SITE ]) then {
print "Failed in test";
quitbird;
}
preference = 15;
print preference;
preference = 29;
print preference;
rip_metric = 1;
print rip_metric;
rip_metric = rip_metric + 5;
print rip_metric;
#
# TODO: uncomment this part after finishing BGP integration version
#
# bgp_community = -empty-;
# print "hi";
# bgp_community = add(bgp_community, (1,2));
# print "hello";
# bgp_community = add(bgp_community, (2,3));
# bgp_community.add((4,5));
# print "community = ", bgp_community;
# bgp_community.delete((2,3));
# print "community = ", bgp_community;
# bgp_community.empty;
# print "community = ", bgp_community;
# print "done";
};
};
route 0.0.0.0/0 via 195.113.31.113;
route 62.168.0.0/25 reject;
route 1.2.3.4/32 via 195.113.31.124;
# route 10.0.0.0/8 reject;
# route 10.1.1.0:255.255.255.0 via 62.168.0.3;
# route 10.1.2.0:255.255.255.0 via 62.168.0.3;
# route 10.1.3.0:255.255.255.0 via 62.168.0.4;
# route 10.2.0.0/24 via "arc0";
export all;
route 10.0.0.0/8 reject;
route 10.1.1.0:255.255.255.0 via 62.168.0.3;
route 10.1.2.0:255.255.255.0 via 62.168.0.3;
route 10.1.3.0:255.255.255.0 via 62.168.0.4;
route 10.2.0.0/24 via "arc0";
}

View File

@ -9,6 +9,8 @@ router id 62.168.0.1;
define xyzzy = (120+10);
protocol device {}
function callme(int arg1; int arg2)
int local1;
int local2;
@ -30,7 +32,7 @@ function fifteen()
return 15;
}
function paths()
function _paths()
bgpmask pm1;
bgpmask pm2;
bgppath p2;
@ -97,7 +99,7 @@ ip p;
pair pp;
int set is;
prefix set pxs;
string s;
string str;
{
print "Testing filter language:";
i = four;
@ -129,8 +131,8 @@ string s;
print "Testing pairs: (1,2) = ", (1,2), " = ", pp;
print "Testing enums: ", RTS_DUMMY, " ", RTS_STATIC;
s = "Hello";
print "Testing string: ", s, " true: ", s ~ "Hell*", " false: ", s ~ "ell*";
str = "Hello";
print "Testing string: ", str, " true: ", str ~ "Hell*", " false: ", str ~ "ell*";
b = true;
print "Testing bool: ", b, ", ", !b;
@ -156,11 +158,12 @@ string s;
i = fifteen();
print "Testing function calls: 15 = ", i;
paths();
_paths();
print "done";
quitbird;
# print "*** FAIL: this is unreachable";
return 0;
print "*** FAIL: this is unreachable";
quitbird; # quit with err exit code 1
}
filter testf

View File

@ -0,0 +1,113 @@
router id 62.168.0.1;
function net_martian()
{
return net ~ [ 169.254.0.0/16+, 172.16.0.0/12+, 192.168.0.0/16+, 10.0.0.0/8+,
127.0.0.0/8+, 224.0.0.0/4+, 240.0.0.0/4+, 0.0.0.0/32-, 0.0.0.0/0{25,32}, 0.0.0.0/0{0,7} ];
}
function net_local()
{
return net ~ [ 12.10.0.0/16+, 34.10.0.0/16+ ];
}
function rt_import(int asn; int set peer_asns; prefix set peer_nets)
{
if ! (net ~ peer_nets) then return false;
if ! (bgp_path.last ~ peer_asns) then return false;
if bgp_path.first != asn then return false;
if bgp_path.len > 64 then return false;
if bgp_next_hop != from then return false;
return true;
}
function rt_import_all(int asn)
{
if net_martian() || net_local() then return false;
if bgp_path.first != asn then return false;
if bgp_path.len > 64 then return false;
if bgp_next_hop != from then return false;
return true;
}
function rt_import_rs(int asn)
{
if net_martian() || net_local() then return false;
if bgp_path.len > 64 then return false;
return true;
}
function rt_export()
{
if proto = "static_bgp" then return true;
if source != RTS_BGP then return false;
if net_martian() then return false;
if bgp_path.len > 64 then return false;
# return bgp_next_hop ~ [ 100.1.1.1, 100.1.1.2, 200.1.1.1 ];
return bgp_path.first ~ [ 345, 346 ];
}
function rt_export_all()
{
if proto = "static_bgp" then return true;
if source != RTS_BGP then return false;
if net_martian() then return false;
if bgp_path.len > 64 then return false;
return true;
}
filter bgp_in_uplink_123
{
if ! rt_import_all(123) then reject;
accept;
}
filter bgp_out_uplink_123
{
if ! rt_export() then reject;
accept;
}
filter bgp_in_peer_234
{
if ! rt_import(234, [ 234, 1234, 2345, 3456 ],
[ 12.34.0.0/16, 23.34.0.0/16, 34.56.0.0/16 ])
then reject;
accept;
}
filter bgp_out_peer_234
{
if ! rt_export() then reject;
accept;
}
filter bgp_in_rs
{
if ! rt_import_rs(bgp_path.last) then reject;
accept;
}
filter bgp_out_rs
{
if ! rt_export() then reject;
accept;
}
filter bgp_in_client_345
{
if ! rt_import(345, [ 345 ], [ 34.5.0.0/16 ]) then reject;
accept;
}
filter bgp_out_client_345
{
if ! rt_export_all() then reject;
accept;
}

304
filter/tree_test.c Normal file
View File

@ -0,0 +1,304 @@
/*
* Filters: Utility Functions Tests
*
* (c) 2015 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "test/birdtest.h"
#include "test/bt-utils.h"
#include "filter/filter.h"
#include "conf/conf.h"
#define MAX_TREE_HEIGHT 13
static void
start_conf_env(void)
{
bt_bird_init();
pool *p = rp_new(&root_pool, "helper_pool");
linpool *l = lp_new(p, 4080);
cfg_mem = l;
}
static struct f_tree *
new_tree(uint id)
{
struct f_tree *tree = f_new_tree();
tree->from.type = tree->to.type = T_INT;
tree->from.val.i = tree->to.val.i = id;
return tree;
}
/*
* Show subtree in infix notation
*/
static void
show_subtree(struct f_tree *node)
{
if (!node)
return;
show_subtree(node->left);
if (node->from.val.i == node->to.val.i)
bt_debug("%u ", node->from.val.i);
else
bt_debug("%u..%u ", node->from.val.i, node->to.val.i);
show_subtree(node->right);
}
static void
show_tree2(struct f_tree *root_node, const char *tree_name)
{
bt_debug("%s: \n", tree_name);
bt_debug("[ ");
show_subtree(root_node);
bt_debug("]\n\n");
}
#define show_tree(tree) show_tree2(tree, #tree);
static uint
get_nodes_count_full_bin_tree(uint height)
{
return (bt_naive_pow(2, height+1) - 1);
}
static struct f_tree *
get_balanced_full_subtree(uint height, uint idx)
{
struct f_tree *node = new_tree(idx);
if (height > 0)
{
uint nodes_in_subtree = get_nodes_count_full_bin_tree(--height);
node->left = get_balanced_full_subtree(height, idx - nodes_in_subtree/2 - 1);
node->right = get_balanced_full_subtree(height, idx + nodes_in_subtree/2 + 1);
}
return node;
}
static struct f_tree *
get_balanced_full_tree(uint height)
{
return get_balanced_full_subtree(height, get_nodes_count_full_bin_tree(height)/2);
}
static struct f_tree *
get_degenerated_left_tree(uint nodes_count)
{
struct f_tree *old = NULL;
struct f_tree *new = NULL;
uint i;
for (i = 0; i < nodes_count; i++)
{
old = new;
new = new_tree(nodes_count-1-i);
new->left = old;
}
return new;
}
static struct f_tree *
get_random_degenerated_left_tree(uint nodes_count)
{
struct f_tree *tree = get_degenerated_left_tree(nodes_count);
size_t avaible_indexes_size = nodes_count * sizeof(byte);
byte *avaible_indexes = malloc(avaible_indexes_size);
memset(avaible_indexes, 0, avaible_indexes_size);
struct f_tree *n;
for (n = tree; n; n = n->left)
{
uint selected_idx;
do
{
selected_idx = bt_random() % nodes_count;
} while(avaible_indexes[selected_idx] != 0);
avaible_indexes[selected_idx] = 1;
n->from.type = n->to.type = T_INT;
n->from.val.i = n->to.val.i = selected_idx;
}
free(avaible_indexes);
return tree;
}
static struct f_tree *
get_balanced_tree_with_ranged_values(uint nodes_count)
{
struct f_tree *tree = get_degenerated_left_tree(nodes_count);
uint idx = 0;
struct f_tree *n;
for (n = tree; n; n = n->left)
{
n->from.type = n->to.type = T_INT;
n->from.val.i = idx;
idx += (uint)bt_random() / nodes_count; /* (... / nodes_count) preventing overflow an uint idx */
n->to.val.i = idx++;
}
return build_tree(tree);
}
static int
t_balancing(void)
{
start_conf_env();
uint height;
for (height = 1; height < MAX_TREE_HEIGHT; height++)
{
uint nodes_count = get_nodes_count_full_bin_tree(height);
struct f_tree *simple_degenerated_tree = get_degenerated_left_tree(nodes_count);
show_tree(simple_degenerated_tree);
struct f_tree *expected_balanced_tree = get_balanced_full_tree(height);
show_tree(expected_balanced_tree);
struct f_tree *balanced_tree_from_simple = build_tree(simple_degenerated_tree);
show_tree(balanced_tree_from_simple);
bt_assert(same_tree(balanced_tree_from_simple, expected_balanced_tree));
}
return BT_SUCCESS;
}
static int
t_balancing_random(void)
{
start_conf_env();
uint height;
for (height = 1; height < MAX_TREE_HEIGHT; height++)
{
uint nodes_count = get_nodes_count_full_bin_tree(height);
struct f_tree *expected_balanced_tree = get_balanced_full_tree(height);
uint i;
for(i = 0; i < 10; i++)
{
struct f_tree *random_degenerated_tree = get_random_degenerated_left_tree(nodes_count);
show_tree(random_degenerated_tree);
struct f_tree *balanced_tree_from_random = build_tree(random_degenerated_tree);
show_tree(expected_balanced_tree);
show_tree(balanced_tree_from_random);
bt_assert(same_tree(balanced_tree_from_random, expected_balanced_tree));
}
}
return BT_SUCCESS;
}
static int
t_find(void)
{
start_conf_env();
uint height;
for (height = 1; height < MAX_TREE_HEIGHT; height++)
{
uint nodes_count = get_nodes_count_full_bin_tree(height);
struct f_tree *tree = get_balanced_full_tree(height);
show_tree(tree);
struct f_val looking_up_value = {
.type = T_INT
};
for(looking_up_value.val.i = 0; looking_up_value.val.i < nodes_count; looking_up_value.val.i++)
{
struct f_tree *found_tree = find_tree(tree, looking_up_value);
bt_assert((val_compare(looking_up_value, found_tree->from) == 0) && (val_compare(looking_up_value, found_tree->to) == 0));
}
}
return BT_SUCCESS;
}
static uint
get_max_value_in_unbalanced_tree(struct f_tree *node, uint max)
{
if (!node)
return max;
if (node->to.val.i > max)
max = node->to.val.i;
uint max_left = get_max_value_in_unbalanced_tree(node->left, max);
if (max_left > max)
max = max_left;
uint max_right = get_max_value_in_unbalanced_tree(node->right, max);
if (max_right > max)
max = max_right;
return max;
}
static int
t_find_ranges(void)
{
start_conf_env();
uint height;
for (height = 1; height < MAX_TREE_HEIGHT; height++)
{
uint nodes_count = get_nodes_count_full_bin_tree(height);
struct f_tree *tree = get_balanced_tree_with_ranged_values(nodes_count);
uint max_value = get_max_value_in_unbalanced_tree(tree, 0);
show_tree(tree);
bt_debug("max_value: %u \n", max_value);
struct f_val needle = {
.type = T_INT
};
uint *i = &needle.val.i;
for(*i = 0; *i <= max_value; *i += (uint)bt_random()/nodes_count)
{
struct f_tree *found_tree = find_tree(tree, needle);
bt_debug("searching: %u \n", *i);
bt_assert(
(val_compare(needle, found_tree->from) == 0) || (val_compare(needle, found_tree->to) == 0) ||
((val_compare(needle, found_tree->from) == 1) && (val_compare(needle, found_tree->to) == -1))
);
}
}
return BT_SUCCESS;
}
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
bt_test_suite(t_balancing, "Balancing strong unbalanced trees");
bt_test_suite(t_balancing_random, "Balancing random unbalanced trees");
bt_test_suite(t_find, "Finding values in trees");
bt_test_suite(t_find_ranges, "Finding values in trees with random ranged values");
return bt_exit_value();
}

184
filter/trie_test.c Normal file
View File

@ -0,0 +1,184 @@
/*
* Filters: Utility Functions Tests
*
* (c) 2015 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "test/birdtest.h"
#include "test/bt-utils.h"
#include "filter/filter.h"
#include "conf/conf.h"
#define TESTS_NUM 10
#define PREFIXES_NUM 10
#define PREFIX_TESTS_NUM 10000
#define BIG_BUFFER_SIZE 10000
/* Wrapping structure for storing f_prefixes structures in list */
struct f_prefix_node {
node n;
struct f_prefix prefix;
};
static u32
xrandom(u32 max)
{
return (bt_random() % max);
}
static int
is_prefix_included(list *prefixes, struct f_prefix *needle)
{
struct f_prefix_node *n;
WALK_LIST(n, *prefixes)
{
ip6_addr cmask = ip6_mkmask(MIN(n->prefix.net.pxlen, needle->net.pxlen));
ip6_addr ip = net6_prefix(&n->prefix.net);
ip6_addr needle_ip = net6_prefix(&needle->net);
if ((ipa_compare(ipa_and(ip, cmask), ipa_and(needle_ip, cmask)) == 0) &&
(n->prefix.lo <= needle->net.pxlen) && (needle->net.pxlen <= n->prefix.hi))
{
bt_debug("FOUND\t" PRIip6 "/%d %d-%d\n", ARGip6(net6_prefix(&n->prefix.net)), n->prefix.net.pxlen, n->prefix.lo, n->prefix.hi);
return 1; /* OK */
}
}
return 0; /* FAIL */
}
static struct f_prefix
get_random_ip6_prefix(void)
{
struct f_prefix p;
u8 pxlen = xrandom(120)+8;
ip6_addr ip6 = ip6_build(bt_random(),bt_random(),bt_random(),bt_random());
net_addr_ip6 net6 = NET_ADDR_IP6(ip6, pxlen);
p.net = *((net_addr*) &net6);
if (bt_random() % 2)
{
p.lo = 0;
p.hi = p.net.pxlen;
}
else
{
p.lo = p.net.pxlen;
p.hi = net_max_prefix_length[p.net.type];
}
return p;
}
static void
generate_random_ipv6_prefixes(list *prefixes)
{
int i;
for (i = 0; i < PREFIXES_NUM; i++)
{
struct f_prefix f = get_random_ip6_prefix();
struct f_prefix_node *px = calloc(1, sizeof(struct f_prefix_node));
px->prefix = f;
bt_debug("ADD\t" PRIip6 "/%d %d-%d\n", ARGip6(net6_prefix(&px->prefix.net)), px->prefix.net.pxlen, px->prefix.lo, px->prefix.hi);
add_tail(prefixes, &px->n);
}
}
static int
t_match_net(void)
{
bt_bird_init();
bt_config_parse(BT_CONFIG_SIMPLE);
uint round;
for (round = 0; round < TESTS_NUM; round++)
{
list prefixes; /* of structs f_extended_prefix */
init_list(&prefixes);
struct f_trie *trie = f_new_trie(config->mem, sizeof(struct f_trie_node));
generate_random_ipv6_prefixes(&prefixes);
struct f_prefix_node *n;
WALK_LIST(n, prefixes)
{
trie_add_prefix(trie, &n->prefix.net, n->prefix.lo, n->prefix.hi);
}
int i;
for (i = 0; i < PREFIX_TESTS_NUM; i++)
{
struct f_prefix f = get_random_ip6_prefix();
bt_debug("TEST\t" PRIip6 "/%d\n", ARGip6(net6_prefix(&f.net)), f.net.pxlen);
int should_be = is_prefix_included(&prefixes, &f);
int is_there = trie_match_net(trie, &f.net);
bt_assert_msg(should_be == is_there, "Prefix " PRIip6 "/%d %s", ARGip6(net6_prefix(&f.net)), f.net.pxlen, (should_be ? "should be found in trie" : "should not be found in trie"));
}
struct f_prefix_node *nxt;
WALK_LIST_DELSAFE(n, nxt, prefixes)
{
free(n);
}
}
return BT_SUCCESS;
}
static int
t_trie_same(void)
{
bt_bird_init();
bt_config_parse(BT_CONFIG_SIMPLE);
int round;
for (round = 0; round < TESTS_NUM*4; round++)
{
struct f_trie * trie1 = f_new_trie(config->mem, sizeof(struct f_trie_node));
struct f_trie * trie2 = f_new_trie(config->mem, sizeof(struct f_trie_node));
list prefixes; /* a list of f_extended_prefix structures */
init_list(&prefixes);
int i;
for (i = 0; i < 100; i++)
generate_random_ipv6_prefixes(&prefixes);
struct f_prefix_node *n;
WALK_LIST(n, prefixes)
{
trie_add_prefix(trie1, &n->prefix.net, n->prefix.lo, n->prefix.hi);
}
WALK_LIST_BACKWARDS(n, prefixes)
{
trie_add_prefix(trie2, &n->prefix.net, n->prefix.lo, n->prefix.hi);
}
bt_assert(trie_same(trie1, trie2));
struct f_prefix_node *nxt;
WALK_LIST_DELSAFE(n, nxt, prefixes)
{
free(n);
}
}
return BT_SUCCESS;
}
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
bt_test_suite(t_match_net, "Testing random prefix matching");
bt_test_suite(t_trie_same, "A trie filled forward should be same with a trie filled backward.");
return bt_exit_value();
}

View File

@ -1,3 +1,7 @@
src := bitops.c checksum.c event.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c tbf.c xmalloc.c
obj := $(src-o-files)
$(all-daemon)
tests_src := heap_test.c buffer_test.c event_test.c bitops_test.c patmatch_test.c fletcher16_test.c slist_test.c checksum_test.c lists_test.c mac_test.c ip_test.c hash_test.c printf_test.c
tests_targets := $(tests_targets) $(tests-target-files)
tests_objs := $(tests_objs) $(src-o-files)

View File

@ -56,7 +56,6 @@ static inline int u64_cmp(u64 i1, u64 i2)
#define NULL ((void *) 0)
#endif
/* Macros for gcc attributes */
#define NORET __attribute__((noreturn))
@ -150,7 +149,7 @@ void bug(const char *msg, ...) NORET;
#define L_FATAL "\010" /* Fatal errors */
#define L_BUG "\011" /* BIRD bugs */
void debug(const char *msg, ...); /* Printf to debug output */
void debug(const char *msg, ...); /* Printf to debug output */
/* Debugging */

View File

@ -9,6 +9,8 @@
#ifndef _BIRD_BITOPTS_H_
#define _BIRD_BITOPTS_H_
#include "sysdep/config.h"
/*
* Bit mask operations:
*

123
lib/bitops_test.c Normal file
View File

@ -0,0 +1,123 @@
/*
* BIRD Library -- Generic Bit Operations Tests
*
* (c) 2015 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "test/birdtest.h"
#include "test/bt-utils.h" /* naive_pow() */
#include "lib/bitops.h"
#define MAX_NUM 1000
#define CHECK_BIT(var,pos) ((var) & (u32)(1<<(pos)))
static int
t_mkmask(void)
{
int i;
u32 compute, expect;
bt_assert(u32_mkmask(0) == 0x00000000);
for (i = 1; i <= 32; i++)
{
compute = u32_mkmask(i);
expect = (u32) (0xffffffff << (32-i));
bt_assert_msg(compute == expect, "u32_mkmask(%d) = 0x%08X, expected 0x%08X", i, compute, expect);
}
return BT_SUCCESS;
}
static int
u32_masklen_expected(u32 mask)
{
int j, expect = 0;
int valid = 0;
for (j = 0; j <= 32; j++)
if (mask == (j ? (0xffffffff << (32-j)) : 0)) /* Shifting 32-bit value by 32 bits is undefined behavior */
valid = 1;
if (!valid && mask != 0)
expect = 255;
else
for (j = 0; j <= 31; j++)
if (CHECK_BIT(mask, (31-j)))
expect = j+1;
else
break;
return expect;
}
static void
check_mask(u32 mask)
{
int expected, masklen;
expected = u32_masklen_expected(mask);
masklen = u32_masklen(mask);
int ok = (expected == masklen);
bt_debug("u32_masklen(Ox%08x) = %d, expected %d %s\n", mask, masklen, expected, ok ? "OK" : "FAIL!");
bt_assert(ok);
}
static int
t_masklen(void)
{
u32 i;
check_mask(0x82828282);
check_mask(0x00000000);
for (i = 0; i <= 32; i++)
check_mask(((u32) (i ? (0xffffffff << (32-i)) : 0)) & 0xffffffff); /* Shifting 32-bit value by 32 bits is undefined behavior */
for (i = 0; i <= MAX_NUM; i++)
check_mask(bt_random());
return BT_SUCCESS;
}
static void
check_log2(u32 n)
{
u32 log = u32_log2(n);
u32 low = bt_naive_pow(2, log);
u32 high = bt_naive_pow(2, log+1);
bt_assert_msg(n >= low && n < high,
"u32_log2(%u) = %u, %u should be in the range <%u, %u)",
n, log, n, low, high);
}
static int
t_log2(void)
{
u32 i;
for (i = 0; i < 31; i++)
bt_assert(u32_log2(bt_naive_pow(2, i+1)) == i+1);
for (i = 1; i < MAX_NUM; i++)
check_log2(i);
for (i = 1; i < MAX_NUM; i++)
check_log2(((u32) bt_random()) % 0x0fffffff);
return BT_SUCCESS;
}