]>
git.proxmox.com Git - mirror_frr.git/blob - tests/helpers/python/frrtest.py
4 # Copyright (C) 2017 by David Lamparter & Christian Franke,
5 # Open Source Routing / NetDEF Inc.
7 # This file is part of FRRouting (FRR)
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
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.
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
35 # These are the gritty internals of the TestMultiOut implementation.
36 # See below for the definition of actual TestMultiOut tests.
39 srcbase
= os
.path
.abspath(inspect
.getsourcefile(frrsix
))
41 srcbase
= os
.path
.dirname(srcbase
)
45 return os
.path
.relpath(os
.path
.abspath(srcpath
), srcbase
)
48 class MultiTestFailure(Exception):
52 class MetaTestMultiOut(type):
53 def __getattr__(cls
, name
):
54 if name
.startswith("_"):
57 internal_name
= "_{}".format(name
)
58 if internal_name
not in dir(cls
):
61 def registrar(*args
, **kwargs
):
62 cls
._add
_test
(getattr(cls
, internal_name
), *args
, **kwargs
)
67 @frrsix.add_metaclass(MetaTestMultiOut
)
68 class _TestMultiOut(object):
70 if "tests_run" in dir(self
.__class
__) and self
.tests_run
:
72 self
.__class
__.tests_run
= True
73 basedir
= os
.path
.dirname(inspect
.getsourcefile(type(self
)))
74 program
= os
.path
.join(basedir
, self
.program
)
75 proc
= subprocess
.Popen([binpath(program
)], stdout
=subprocess
.PIPE
)
76 self
.output
, _
= proc
.communicate("")
77 self
.exitcode
= proc
.wait()
79 self
.__class
__.testresults
= {}
80 for test
in self
.tests
:
83 except MultiTestFailure
:
84 self
.testresults
[test
] = sys
.exc_info()
86 self
.testresults
[test
] = None
88 def _exit_cleanly(self
):
89 if self
.exitcode
!= 0:
90 raise MultiTestFailure("Program did not terminate with exit code 0")
93 def _add_test(cls
, method
, *args
, **kwargs
):
94 if "tests" not in dir(cls
):
95 setattr(cls
, "tests", [])
96 if method
is not cls
._exit
_cleanly
:
97 cls
._add
_test
(cls
._exit
_cleanly
)
99 def matchfunction(self
):
100 method(self
, *args
, **kwargs
)
102 cls
.tests
.append(matchfunction
)
104 def testfunction(self
):
106 result
= self
.testresults
[matchfunction
]
107 if result
is not None:
108 frrsix
.reraise(*result
)
110 testname
= re
.sub(r
"[^A-Za-z0-9]", "_", "%r%r" % (args
, kwargs
))
111 testname
= re
.sub(r
"__*", "_", testname
)
112 testname
= testname
.strip("_")
114 testname
= method
.__name
__.strip("_")
115 if "test_%s" % testname
in dir(cls
):
117 while "test_%s_%d" % (testname
, index
) in dir(cls
):
119 testname
= "%s_%d" % (testname
, index
)
120 setattr(cls
, "test_%s" % testname
, testfunction
)
124 # This class houses the actual TestMultiOut tests types.
125 # If you want to add a new test type, you probably do it here.
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.
138 re_okfail
= re
.compile(r
"(?:[3[12]m|^)?(?P<ret>OK|failed)".encode("utf8"), re
.MULTILINE
)
141 class TestMultiOut(_TestMultiOut
):
142 def _onesimple(self
, line
):
143 if type(line
) is str:
144 line
= line
.encode("utf8")
145 idx
= self
.output
.find(line
)
147 self
.output
= self
.output
[idx
+ len(line
) :]
149 raise MultiTestFailure("%r could not be found" % line
)
151 def _okfail(self
, line
, okfail
=re_okfail
):
152 self
._onesimple
(line
)
154 m
= okfail
.search(self
.output
)
156 raise MultiTestFailure("OK/fail not found")
157 self
.output
= self
.output
[m
.end() :]
159 if m
.group("ret") != "OK".encode("utf8"):
160 raise MultiTestFailure("Test output indicates failure")
164 # This class implements a test comparing the output of a program against
165 # an existing reference output
169 class TestRefMismatch(Exception):
170 def __init__(self
, _test
, outtext
, reftext
):
171 self
.outtext
= outtext
172 self
.reftext
= reftext
175 rv
= "Expected output and actual output differ:\n"
177 difflib
.unified_diff(
178 self
.reftext
.splitlines(),
179 self
.outtext
.splitlines(),
188 class TestExitNonzero(Exception):
192 class TestRefOut(object):
193 def test_refout(self
):
194 basedir
= os
.path
.dirname(inspect
.getsourcefile(type(self
)))
195 program
= os
.path
.join(basedir
, self
.program
)
197 if getattr(self
, "built_refin", False):
198 refin
= binpath(program
) + ".in"
200 refin
= program
+ ".in"
201 if getattr(self
, "built_refout", False):
202 refout
= binpath(program
) + ".refout"
204 refout
= program
+ ".refout"
207 if os
.path
.exists(refin
):
208 with
open(refin
, "rb") as f
:
210 with
open(refout
, "rb") as f
:
213 proc
= subprocess
.Popen(
214 [binpath(program
)], stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
216 outtext
, _
= proc
.communicate(intext
)
218 # Get rid of newline problems (Windows vs Unix Style)
219 outtext_str
= outtext
.decode("utf8").replace("\r\n", "\n").replace("\r", "\n")
220 reftext_str
= reftext
.decode("utf8").replace("\r\n", "\n").replace("\r", "\n")
222 if outtext_str
!= reftext_str
:
223 raise TestRefMismatch(self
, outtext_str
, reftext_str
)
225 raise TestExitNonzero(self
)