summaryrefslogtreecommitdiffstats
path: root/tests/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'tests/helpers')
-rw-r--r--tests/helpers/c/main.c172
-rw-r--r--tests/helpers/c/prng.c95
-rw-r--r--tests/helpers/c/prng.h22
-rw-r--r--tests/helpers/c/tests.h17
-rw-r--r--tests/helpers/python/frrsix.py72
-rw-r--r--tests/helpers/python/frrtest.py211
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)