diff options
Diffstat (limited to 'tests/helpers')
-rw-r--r-- | tests/helpers/c/main.c | 172 | ||||
-rw-r--r-- | tests/helpers/c/prng.c | 95 | ||||
-rw-r--r-- | tests/helpers/c/prng.h | 22 | ||||
-rw-r--r-- | tests/helpers/c/tests.h | 17 | ||||
-rw-r--r-- | tests/helpers/python/frrsix.py | 72 | ||||
-rw-r--r-- | tests/helpers/python/frrtest.py | 211 |
6 files changed, 589 insertions, 0 deletions
diff --git a/tests/helpers/c/main.c b/tests/helpers/c/main.c new file mode 100644 index 0000000..8af53a2 --- /dev/null +++ b/tests/helpers/c/main.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +#include <zebra.h> + +#include <lib/version.h> +#include "getopt.h" +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "lib_vty.h" + +extern void test_init(void); + +struct event_loop *master; + +struct option longopts[] = {{"daemon", no_argument, NULL, 'd'}, + {"config_file", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"vty_addr", required_argument, NULL, 'A'}, + {"vty_port", required_argument, NULL, 'P'}, + {"version", no_argument, NULL, 'v'}, + {0}}; + +DEFUN (daemon_exit, + daemon_exit_cmd, + "daemon-exit", + "Make the daemon exit\n") +{ + exit(0); +} + +static int timer_count; +static void test_timer(struct event *thread) +{ + int *count = EVENT_ARG(thread); + + printf("run %d of timer\n", (*count)++); + event_add_timer(master, test_timer, count, 5, NULL); +} + +static void test_timer_init(void) +{ + event_add_timer(master, test_timer, &timer_count, 10, NULL); +} + +static void test_vty_init(void) +{ + install_element(VIEW_NODE, &daemon_exit_cmd); +} + +/* Help information display. */ +static void usage(char *progname, int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + progname); + else { + printf("Usage : %s [OPTION...]\n\ +Daemon which does 'slow' things.\n\n\ +-d, --daemon Runs in daemon mode\n\ +-f, --config_file Set configuration file name\n\ +-A, --vty_addr Set vty's bind address\n\ +-P, --vty_port Set vty's port number\n\ +-v, --version Print program version\n\ +-h, --help Display this help and exit\n\ +\n\ +Report bugs to %s\n", + progname, FRR_BUG_ADDRESS); + } + exit(status); +} + + +/* main routine. */ +int main(int argc, char **argv) +{ + char *p; + char *vty_addr = NULL; + int vty_port = 4000; + int daemon_mode = 0; + char *progname; + struct event thread; + char *config_file = NULL; + + /* Set umask before anything for security */ + umask(0027); + + /* get program name */ + progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); + + /* master init. */ + master = event_master_create(NULL); + + while (1) { + int opt; + + opt = getopt_long(argc, argv, "dhf:A:P:v", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'f': + config_file = optarg; + break; + case 'd': + daemon_mode = 1; + break; + case 'A': + vty_addr = optarg; + break; + case 'P': + /* Deal with atoi() returning 0 on failure */ + if (strcmp(optarg, "0") == 0) { + vty_port = 0; + break; + } + vty_port = atoi(optarg); + vty_port = (vty_port ? vty_port : 4000); + break; + case 'v': + print_version(progname); + exit(0); + break; + case 'h': + usage(progname, 0); + break; + default: + usage(progname, 1); + break; + } + } + + /* Library inits. */ + cmd_init(1); + vty_init(master, false); + lib_cmd_init(); + nb_init(master, NULL, 0, false); + + /* OSPF vty inits. */ + test_vty_init(); + + /* Change to the daemon program. */ + if (daemon_mode && daemon(0, 0) < 0) { + fprintf(stderr, "daemon failed: %s", strerror(errno)); + exit(1); + } + + /* Create VTY socket */ + vty_serv_start(vty_addr, vty_port, "/tmp/.heavy.sock"); + + /* Configuration file read*/ + if (!config_file) + usage(progname, 1); + vty_read_config(NULL, config_file, NULL); + + test_timer_init(); + + test_init(); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/helpers/c/prng.c b/tests/helpers/c/prng.c new file mode 100644 index 0000000..612c433 --- /dev/null +++ b/tests/helpers/c/prng.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Very simple prng to allow for randomized tests with reproducable + * results. + * + * Copyright (C) 2012 by Open Source Routing. + * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2017 Christian Franke + * + * This file is part of Quagga + */ + +#include <zebra.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "prng.h" + +struct prng { + uint64_t state; +}; + +struct prng *prng_new(unsigned long long seed) +{ + struct prng *rv = calloc(sizeof(*rv), 1); + assert(rv); + + rv->state = seed; + + return rv; +} + +/* + * This implementation has originally been provided to musl libc by + * Szabolcs Nagy <nsz at port70 dot net> in 2013 under the terms of + * the MIT license. + * It is a simple LCG which D.E. Knuth attributes to C.E. Haynes in + * TAOCP Vol2 3.3.4 + */ +int prng_rand(struct prng *prng) +{ + prng->state = 6364136223846793005ULL * prng->state + 1; + return prng->state >> 33; +} + +const char *prng_fuzz(struct prng *prng, const char *string, + const char *charset, unsigned int operations) +{ + static char buf[256]; + unsigned int charset_len; + unsigned int i; + unsigned int offset; + unsigned int op; + unsigned int character; + + assert(strlen(string) < sizeof(buf)); + + strncpy(buf, string, sizeof(buf)); + charset_len = strlen(charset); + + for (i = 0; i < operations; i++) { + offset = prng_rand(prng) % strlen(buf); + op = prng_rand(prng) % 3; + + switch (op) { + case 0: + /* replace */ + character = prng_rand(prng) % charset_len; + buf[offset] = charset[character]; + break; + case 1: + /* remove */ + memmove(buf + offset, buf + offset + 1, + strlen(buf) - offset); + break; + case 2: + /* insert */ + assert(strlen(buf) + 1 < sizeof(buf)); + + memmove(buf + offset + 1, buf + offset, + strlen(buf) + 1 - offset); + character = prng_rand(prng) % charset_len; + buf[offset] = charset[character]; + break; + } + } + return buf; +} + +void prng_free(struct prng *prng) +{ + free(prng); +} diff --git a/tests/helpers/c/prng.h b/tests/helpers/c/prng.h new file mode 100644 index 0000000..6b10bde --- /dev/null +++ b/tests/helpers/c/prng.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Very simple prng to allow for randomized tests with reproducable + * results. + * + * Copyright (C) 2012 by Open Source Routing. + * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") + * + * This file is part of Quagga + */ +#ifndef _PRNG_H +#define _PRNG_H + +struct prng; + +struct prng *prng_new(unsigned long long seed); +int prng_rand(struct prng *); +const char *prng_fuzz(struct prng *, const char *string, const char *charset, + unsigned int operations); +void prng_free(struct prng *); + +#endif diff --git a/tests/helpers/c/tests.h b/tests/helpers/c/tests.h new file mode 100644 index 0000000..40f17cc --- /dev/null +++ b/tests/helpers/c/tests.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test wrappers common header file + * + * Copyright (C) 2015 by David Lamparter, + * for Open Source Routing./ NetDEF, Inc. + * + * This file is part of Quagga + */ + +#ifndef _QUAGGA_TESTS_H +#define _QUAGGA_TESTS_H + +extern void test_init(void); +extern void test_init_cmd(void); + +#endif /* _QUAGGA_TESTS_H */ diff --git a/tests/helpers/python/frrsix.py b/tests/helpers/python/frrsix.py new file mode 100644 index 0000000..8af9647 --- /dev/null +++ b/tests/helpers/python/frrsix.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT +# +# Copyright (c) 2010-2017 Benjamin Peterson +# + +# +# This code is taken from the six python2 to python3 compatibility module +# + +import sys + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get("__slots__") + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + + return wrapper + + +if PY3: + import builtins + + exec_ = getattr(builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + + +else: + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) diff --git a/tests/helpers/python/frrtest.py b/tests/helpers/python/frrtest.py new file mode 100644 index 0000000..3faa2a6 --- /dev/null +++ b/tests/helpers/python/frrtest.py @@ -0,0 +1,211 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Test helpers for FRR +# +# Copyright (C) 2017 by David Lamparter & Christian Franke, +# Open Source Routing / NetDEF Inc. +# +# This file is part of FRRouting (FRR) +# + +import subprocess +import sys +import re +import inspect +import os +import difflib + +import frrsix + +# +# These are the gritty internals of the TestMultiOut implementation. +# See below for the definition of actual TestMultiOut tests. +# + +srcbase = os.path.abspath(inspect.getsourcefile(frrsix)) +for i in range(0, 3): + srcbase = os.path.dirname(srcbase) + + +def binpath(srcpath): + return os.path.relpath(os.path.abspath(srcpath), srcbase) + + +class MultiTestFailure(Exception): + pass + + +class MetaTestMultiOut(type): + def __getattr__(cls, name): + if name.startswith("_"): + raise AttributeError + + internal_name = "_{}".format(name) + if internal_name not in dir(cls): + raise AttributeError + + def registrar(*args, **kwargs): + cls._add_test(getattr(cls, internal_name), *args, **kwargs) + + return registrar + + +@frrsix.add_metaclass(MetaTestMultiOut) +class _TestMultiOut(object): + def _run_tests(self): + if "tests_run" in dir(self.__class__) and self.tests_run: + return + self.__class__.tests_run = True + basedir = os.path.dirname(inspect.getsourcefile(type(self))) + program = os.path.join(basedir, self.program) + proc = subprocess.Popen([binpath(program)], stdout=subprocess.PIPE) + self.output, _ = proc.communicate("") + self.exitcode = proc.wait() + + self.__class__.testresults = {} + for test in self.tests: + try: + test(self) + except MultiTestFailure: + self.testresults[test] = sys.exc_info() + else: + self.testresults[test] = None + + def _exit_cleanly(self): + if self.exitcode != 0: + raise MultiTestFailure("Program did not terminate with exit code 0") + + @classmethod + def _add_test(cls, method, *args, **kwargs): + if "tests" not in dir(cls): + setattr(cls, "tests", []) + if method is not cls._exit_cleanly: + cls._add_test(cls._exit_cleanly) + + def matchfunction(self): + method(self, *args, **kwargs) + + cls.tests.append(matchfunction) + + def testfunction(self): + self._run_tests() + result = self.testresults[matchfunction] + if result is not None: + frrsix.reraise(*result) + + testname = re.sub(r"[^A-Za-z0-9]", "_", "%r%r" % (args, kwargs)) + testname = re.sub(r"__*", "_", testname) + testname = testname.strip("_") + if not testname: + testname = method.__name__.strip("_") + if "test_%s" % testname in dir(cls): + index = 2 + while "test_%s_%d" % (testname, index) in dir(cls): + index += 1 + testname = "%s_%d" % (testname, index) + setattr(cls, "test_%s" % testname, testfunction) + + +# +# This class houses the actual TestMultiOut tests types. +# If you want to add a new test type, you probably do it here. +# +# Say you want to add a test type called foobarlicious. Then define +# a function _foobarlicious here that takes self and the test arguments +# when called. That function should check the output in self.output +# to see whether it matches the expectation of foobarlicious with the +# given arguments and should then adjust self.output according to how +# much output it consumed. +# If the output doesn't meet the expectations, MultiTestFailure can be +# raised, however that should only be done after self.output has been +# modified according to consumed content. +# + +re_okfail = re.compile(r"(?:[3[12]m|^)?(?P<ret>OK|failed)".encode("utf8"), re.MULTILINE) + + +class TestMultiOut(_TestMultiOut): + def _onesimple(self, line): + if type(line) is str: + line = line.encode("utf8") + idx = self.output.find(line) + if idx != -1: + self.output = self.output[idx + len(line) :] + else: + raise MultiTestFailure("%r could not be found" % line) + + def _okfail(self, line, okfail=re_okfail): + self._onesimple(line) + + m = okfail.search(self.output) + if m is None: + raise MultiTestFailure("OK/fail not found") + self.output = self.output[m.end() :] + + if m.group("ret") != "OK".encode("utf8"): + raise MultiTestFailure("Test output indicates failure") + + +# +# This class implements a test comparing the output of a program against +# an existing reference output +# + + +class TestRefMismatch(Exception): + def __init__(self, _test, outtext, reftext): + self.outtext = outtext + self.reftext = reftext + + def __str__(self): + rv = "Expected output and actual output differ:\n" + rv += "\n".join( + difflib.unified_diff( + self.reftext.splitlines(), + self.outtext.splitlines(), + "outtext", + "reftext", + lineterm="", + ) + ) + return rv + + +class TestExitNonzero(Exception): + pass + + +class TestRefOut(object): + def test_refout(self): + basedir = os.path.dirname(inspect.getsourcefile(type(self))) + program = os.path.join(basedir, self.program) + + if getattr(self, "built_refin", False): + refin = binpath(program) + ".in" + else: + refin = program + ".in" + if getattr(self, "built_refout", False): + refout = binpath(program) + ".refout" + else: + refout = program + ".refout" + + intext = "" + if os.path.exists(refin): + with open(refin, "rb") as f: + intext = f.read() + with open(refout, "rb") as f: + reftext = f.read() + + proc = subprocess.Popen( + [binpath(program)], stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + outtext, _ = proc.communicate(intext) + + # Get rid of newline problems (Windows vs Unix Style) + outtext_str = outtext.decode("utf8").replace("\r\n", "\n").replace("\r", "\n") + reftext_str = reftext.decode("utf8").replace("\r\n", "\n").replace("\r", "\n") + + if outtext_str != reftext_str: + raise TestRefMismatch(self, outtext_str, reftext_str) + if proc.wait() != 0: + raise TestExitNonzero(self) |