]> git.proxmox.com Git - mirror_frr.git/blame - tests/helpers/python/frrtest.py
Merge pull request #7450 from ranjanyash54/dev_5
[mirror_frr.git] / tests / helpers / python / frrtest.py
CommitLineData
cbbf41cb
CF
1#
2# Test helpers for FRR
3#
4# Copyright (C) 2017 by David Lamparter & Christian Franke,
5# Open Source Routing / NetDEF Inc.
6#
447a8fe9 7# This file is part of FRRouting (FRR)
cbbf41cb
CF
8#
9# FRR is free software; you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the
11# Free Software Foundation; either version 2, or (at your option) any
12# later version.
13#
14# FRR is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17# General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with FRR; see the file COPYING. If not, write to the Free
21# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22# 02111-1307, USA.
23#
24
25import subprocess
26import sys
27import re
28import inspect
29import os
1ea8289e 30import difflib
cbbf41cb
CF
31
32import frrsix
33
34#
35# These are the gritty internals of the TestMultiOut implementation.
36# See below for the definition of actual TestMultiOut tests.
37#
38
43dac2ba
DL
39srcbase = os.path.abspath(inspect.getsourcefile(frrsix))
40for i in range(0, 3):
41 srcbase = os.path.dirname(srcbase)
701a0192 42
43
43dac2ba
DL
44def binpath(srcpath):
45 return os.path.relpath(os.path.abspath(srcpath), srcbase)
46
701a0192 47
cbbf41cb
CF
48class MultiTestFailure(Exception):
49 pass
50
701a0192 51
cbbf41cb
CF
52class MetaTestMultiOut(type):
53 def __getattr__(cls, name):
701a0192 54 if name.startswith("_"):
cbbf41cb
CF
55 raise AttributeError
56
701a0192 57 internal_name = "_{}".format(name)
cbbf41cb
CF
58 if internal_name not in dir(cls):
59 raise AttributeError
60
61 def registrar(*args, **kwargs):
701a0192 62 cls._add_test(getattr(cls, internal_name), *args, **kwargs)
63
cbbf41cb
CF
64 return registrar
65
701a0192 66
cbbf41cb
CF
67@frrsix.add_metaclass(MetaTestMultiOut)
68class _TestMultiOut(object):
69 def _run_tests(self):
701a0192 70 if "tests_run" in dir(self.__class__) and self.tests_run:
cbbf41cb
CF
71 return
72 self.__class__.tests_run = True
73 basedir = os.path.dirname(inspect.getsourcefile(type(self)))
74 program = os.path.join(basedir, self.program)
43dac2ba 75 proc = subprocess.Popen([binpath(program)], stdout=subprocess.PIPE)
701a0192 76 self.output, _ = proc.communicate("")
cbbf41cb
CF
77 self.exitcode = proc.wait()
78
79 self.__class__.testresults = {}
80 for test in self.tests:
81 try:
82 test(self)
83 except MultiTestFailure:
84 self.testresults[test] = sys.exc_info()
85 else:
86 self.testresults[test] = None
87
88 def _exit_cleanly(self):
89 if self.exitcode != 0:
90 raise MultiTestFailure("Program did not terminate with exit code 0")
91
92 @classmethod
93 def _add_test(cls, method, *args, **kwargs):
701a0192 94 if "tests" not in dir(cls):
95 setattr(cls, "tests", [])
9d83fa42
CF
96 if method is not cls._exit_cleanly:
97 cls._add_test(cls._exit_cleanly)
cbbf41cb
CF
98
99 def matchfunction(self):
100 method(self, *args, **kwargs)
701a0192 101
cbbf41cb
CF
102 cls.tests.append(matchfunction)
103
104 def testfunction(self):
105 self._run_tests()
106 result = self.testresults[matchfunction]
107 if result is not None:
108 frrsix.reraise(*result)
109
701a0192 110 testname = re.sub(r"[^A-Za-z0-9]", "_", "%r%r" % (args, kwargs))
111 testname = re.sub(r"__*", "_", testname)
112 testname = testname.strip("_")
cbbf41cb 113 if not testname:
701a0192 114 testname = method.__name__.strip("_")
cbbf41cb
CF
115 if "test_%s" % testname in dir(cls):
116 index = 2
701a0192 117 while "test_%s_%d" % (testname, index) in dir(cls):
cbbf41cb
CF
118 index += 1
119 testname = "%s_%d" % (testname, index)
701a0192 120 setattr(cls, "test_%s" % testname, testfunction)
121
cbbf41cb
CF
122
123#
124# This class houses the actual TestMultiOut tests types.
125# If you want to add a new test type, you probably do it here.
126#
127# Say you want to add a test type called foobarlicious. Then define
128# a function _foobarlicious here that takes self and the test arguments
129# when called. That function should check the output in self.output
130# to see whether it matches the expectation of foobarlicious with the
131# given arguments and should then adjust self.output according to how
132# much output it consumed.
133# If the output doesn't meet the expectations, MultiTestFailure can be
134# raised, however that should only be done after self.output has been
135# modified according to consumed content.
136#
137
701a0192 138re_okfail = re.compile(r"(?:[3[12]m|^)?(?P<ret>OK|failed)".encode("utf8"), re.MULTILINE)
139
140
cbbf41cb
CF
141class TestMultiOut(_TestMultiOut):
142 def _onesimple(self, line):
143 if type(line) is str:
701a0192 144 line = line.encode("utf8")
cbbf41cb
CF
145 idx = self.output.find(line)
146 if idx != -1:
701a0192 147 self.output = self.output[idx + len(line) :]
cbbf41cb
CF
148 else:
149 raise MultiTestFailure("%r could not be found" % line)
150
151 def _okfail(self, line, okfail=re_okfail):
152 self._onesimple(line)
153
154 m = okfail.search(self.output)
155 if m is None:
701a0192 156 raise MultiTestFailure("OK/fail not found")
157 self.output = self.output[m.end() :]
158
159 if m.group("ret") != "OK".encode("utf8"):
160 raise MultiTestFailure("Test output indicates failure")
cbbf41cb 161
cbbf41cb
CF
162
163#
164# This class implements a test comparing the output of a program against
165# an existing reference output
166#
167
701a0192 168
cbbf41cb 169class TestRefMismatch(Exception):
1ea8289e 170 def __init__(self, _test, outtext, reftext):
701a0192 171 self.outtext = outtext.decode("utf8") if type(outtext) is bytes else outtext
172 self.reftext = reftext.decode("utf8") if type(reftext) is bytes else reftext
1ea8289e
CF
173
174 def __str__(self):
701a0192 175 rv = "Expected output and actual output differ:\n"
176 rv += "\n".join(
177 difflib.unified_diff(
178 self.reftext.splitlines(),
179 self.outtext.splitlines(),
180 "outtext",
181 "reftext",
182 lineterm="",
183 )
184 )
1ea8289e
CF
185 return rv
186
701a0192 187
cbbf41cb
CF
188class TestExitNonzero(Exception):
189 pass
190
701a0192 191
cbbf41cb
CF
192class TestRefOut(object):
193 def test_refout(self):
194 basedir = os.path.dirname(inspect.getsourcefile(type(self)))
195 program = os.path.join(basedir, self.program)
196
701a0192 197 if getattr(self, "built_refin", False):
198 refin = binpath(program) + ".in"
b37ccace 199 else:
701a0192 200 refin = program + ".in"
201 if getattr(self, "built_refout", False):
202 refout = binpath(program) + ".refout"
b37ccace 203 else:
701a0192 204 refout = program + ".refout"
cbbf41cb 205
701a0192 206 intext = ""
cbbf41cb 207 if os.path.exists(refin):
701a0192 208 with open(refin, "rb") as f:
cbbf41cb 209 intext = f.read()
701a0192 210 with open(refout, "rb") as f:
cbbf41cb
CF
211 reftext = f.read()
212
701a0192 213 proc = subprocess.Popen(
214 [binpath(program)], stdin=subprocess.PIPE, stdout=subprocess.PIPE
215 )
216 outtext, _ = proc.communicate(intext)
cbbf41cb
CF
217 if outtext != reftext:
218 raise TestRefMismatch(self, outtext, reftext)
219 if proc.wait() != 0:
220 raise TestExitNonzero(self)