summaryrefslogtreecommitdiffstats
path: root/tests/helpers/python
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /tests/helpers/python
parentInitial commit. (diff)
downloadfrr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz
frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/helpers/python')
-rw-r--r--tests/helpers/python/frrsix.py89
-rw-r--r--tests/helpers/python/frrtest.py225
2 files changed, 314 insertions, 0 deletions
diff --git a/tests/helpers/python/frrsix.py b/tests/helpers/python/frrsix.py
new file mode 100644
index 0000000..df737d9
--- /dev/null
+++ b/tests/helpers/python/frrsix.py
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2010-2017 Benjamin Peterson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+#
+# 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..584fa90
--- /dev/null
+++ b/tests/helpers/python/frrtest.py
@@ -0,0 +1,225 @@
+#
+# Test helpers for FRR
+#
+# Copyright (C) 2017 by David Lamparter & Christian Franke,
+# Open Source Routing / NetDEF Inc.
+#
+# This file is part of FRRouting (FRR)
+#
+# FRR is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any
+# later version.
+#
+# FRR is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FRR; see the file COPYING. If not, write to the Free
+# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+#
+
+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)