10 from time import sleep
11 from inspect import getdoc
12 from hook import PollHook
13 from vpp_pg_interface import VppPGInterface
14 from vpp_papi_provider import VppPapiProvider
16 from scapy.packet import Raw
21 Test framework module.
23 The module provides a set of tools for constructing and running tests and
24 representing the results.
27 handler = StreamHandler(sys.stdout)
28 getLogger().addHandler(handler)
30 verbose = int(os.getenv("V", 0))
33 # 40 = ERROR, 30 = WARNING, 20 = INFO, 10 = DEBUG, 0 = NOTSET (all messages)
34 getLogger().setLevel(40 - 10 * verbose)
35 getLogger("scapy.runtime").addHandler(handler)
36 getLogger("scapy.runtime").setLevel(ERROR)
38 # Static variables to store color formatting strings.
40 # These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
41 # the color of the text to be printed in the terminal. Variable COLOR_RESET
42 # is used to revert the text color to the default one.
43 if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
48 COLOR_RESET = '\033[0m'
57 """ @var formatting delimiter consisting of '=' characters """
58 double_line_delim = '=' * 70
59 """ @var formatting delimiter consisting of '-' characters """
60 single_line_delim = '-' * 70
63 class _PacketInfo(object):
64 """Private class to create packet info object.
66 Help process information about the next packet.
67 Set variables to default values.
69 Integer variable to store the index of the packet.
71 Integer variable to store the index of the source packet generator
72 interface of the packet.
74 Integer variable to store the index of the destination packet generator
75 interface of the packet.
77 Object variable to store the copy of the former packet.
87 class VppTestCase(unittest.TestCase):
89 Subclass of the python unittest.TestCase class.
91 This subclass is a base class for test cases that are implemented as classes
92 It provides methods to create and run test case.
97 def packet_infos(self):
98 """List of packet infos"""
99 return self._packet_infos
102 def packet_infos(self, value):
103 self._packet_infos = value
107 """Return the instance of this testcase"""
108 return cls.test_instance
111 def setUpConstants(cls):
112 """ Set-up the test case class based on environment variables """
114 cls.interactive = True if int(os.getenv("I")) > 0 else False
116 cls.interactive = False
117 if cls.interactive and resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
118 # give a heads up if this is actually useless
119 critical("WARNING: core size limit is set 0, core files will NOT "
121 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
122 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
123 cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon",
124 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
125 if cls.plugin_path is not None:
126 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
127 info("vpp_cmdline: %s" % cls.vpp_cmdline)
132 Perform class setup before running the testcase
133 Remove shared memory files, start vpp and connect the vpp-api
135 cls.tempdir = tempfile.mkdtemp(
136 prefix='vpp-unittest-' + cls.__name__ + '-')
137 cls.shm_prefix = cls.tempdir.split("/")[-1]
138 os.chdir(cls.tempdir)
139 info("Temporary dir is %s, shm prefix is %s",
140 cls.tempdir, cls.shm_prefix)
143 cls.packet_infos = {}
145 print(double_line_delim)
146 print(YELLOW + getdoc(cls) + COLOR_RESET)
147 print(double_line_delim)
148 # need to catch exceptions here because if we raise, then the cleanup
149 # doesn't get called and we might end with a zombie vpp
151 cls.vpp = subprocess.Popen(cls.vpp_cmdline, stderr=subprocess.PIPE)
152 debug("Spawned VPP with PID: %d" % cls.vpp.pid)
154 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
155 cls.vapi.register_hook(PollHook(cls))
164 Disconnect vpp-api, kill vpp and cleanup shared memory files
166 if hasattr(cls, 'vpp'):
167 cls.vapi.disconnect()
169 if cls.vpp.returncode is None:
174 def tearDownClass(cls):
175 """ Perform final cleanup after running all tests in this test-case """
179 """ Show various debug prints after each test """
180 if not self.vpp_dead:
181 info(self.vapi.cli("show int"))
182 info(self.vapi.cli("show trace"))
183 info(self.vapi.cli("show hardware"))
184 info(self.vapi.cli("show error"))
185 info(self.vapi.cli("show run"))
188 """ Clear trace before running each test"""
189 self.vapi.cli("clear trace")
190 # store the test instance inside the test class - so that objects
191 # holding the class can access instance methods (like assertEqual)
192 type(self).test_instance = self
195 def pg_enable_capture(cls, interfaces):
197 Enable capture on packet-generator interfaces
199 :param interfaces: iterable interface indexes
208 Enable the packet-generator and send all prepared packet streams
209 Remove the packet streams afterwards
211 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
212 cls.vapi.cli('packet-generator enable')
213 sleep(1) # give VPP some time to process the packets
214 for stream in cls.pg_streams:
215 cls.vapi.cli('packet-generator delete %s' % stream)
219 def create_pg_interfaces(cls, interfaces):
221 Create packet-generator interfaces
223 :param interfaces: iterable indexes of the interfaces
228 intf = VppPGInterface(cls, i)
229 setattr(cls, intf.name, intf)
231 cls.pg_interfaces = result
235 def extend_packet(packet, size):
237 Extend packet to given size by padding with spaces
238 NOTE: Currently works only when Raw layer is present.
240 :param packet: packet
241 :param size: target size
244 packet_len = len(packet) + 4
245 extend = size - packet_len
247 packet[Raw].load += ' ' * extend
249 def add_packet_info_to_list(self, info):
251 Add packet info to the testcase's packet info list
253 :param info: packet info
256 info.index = len(self.packet_infos)
257 self.packet_infos[info.index] = info
259 def create_packet_info(self, src_pg_index, dst_pg_index):
261 Create packet info object containing the source and destination indexes
262 and add it to the testcase's packet info list
264 :param src_pg_index: source packet-generator index
265 :param dst_pg_index: destination packet-generator index
267 :returns: _PacketInfo object
271 self.add_packet_info_to_list(info)
272 info.src = src_pg_index
273 info.dst = dst_pg_index
277 def info_to_payload(info):
279 Convert _PacketInfo object to packet payload
281 :param info: _PacketInfo object
283 :returns: string containing serialized data from packet info
285 return "%d %d %d" % (info.index, info.src, info.dst)
288 def payload_to_info(payload):
290 Convert packet payload to _PacketInfo object
292 :param payload: packet payload
294 :returns: _PacketInfo object containing de-serialized data from payload
297 numbers = payload.split()
299 info.index = int(numbers[0])
300 info.src = int(numbers[1])
301 info.dst = int(numbers[2])
304 def get_next_packet_info(self, info):
306 Iterate over the packet info list stored in the testcase
307 Start iteration with first element if info is None
308 Continue based on index in info if info is specified
310 :param info: info or None
311 :returns: next info in list or None if no more infos
316 next_index = info.index + 1
317 if next_index == len(self.packet_infos):
320 return self.packet_infos[next_index]
322 def get_next_packet_info_for_interface(self, src_index, info):
324 Search the packet info list for the next packet info with same source
327 :param src_index: source interface index to search for
328 :param info: packet info - where to start the search
329 :returns: packet info or None
333 info = self.get_next_packet_info(info)
336 if info.src == src_index:
339 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
341 Search the packet info list for the next packet info with same source
342 and destination interface indexes
344 :param src_index: source interface index to search for
345 :param dst_index: destination interface index to search for
346 :param info: packet info - where to start the search
347 :returns: packet info or None
351 info = self.get_next_packet_info_for_interface(src_index, info)
354 if info.dst == dst_index:
358 class VppTestResult(unittest.TestResult):
360 @property result_string
361 String variable to store the test case result string.
363 List variable containing 2-tuples of TestCase instances and strings
364 holding formatted tracebacks. Each tuple represents a test which
365 raised an unexpected exception.
367 List variable containing 2-tuples of TestCase instances and strings
368 holding formatted tracebacks. Each tuple represents a test where
369 a failure was explicitly signalled using the TestCase.assert*()
373 def __init__(self, stream, descriptions, verbosity):
375 :param stream File descriptor to store where to report test results. Set
376 to the standard error stream by default.
377 :param descriptions Boolean variable to store information if to use test
379 :param verbosity Integer variable to store required verbosity level.
381 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
383 self.descriptions = descriptions
384 self.verbosity = verbosity
385 self.result_string = None
387 def addSuccess(self, test):
389 Record a test succeeded result
394 unittest.TestResult.addSuccess(self, test)
395 self.result_string = GREEN + "OK" + COLOR_RESET
397 def addSkip(self, test, reason):
399 Record a test skipped.
405 unittest.TestResult.addSkip(self, test, reason)
406 self.result_string = YELLOW + "SKIP" + COLOR_RESET
408 def addFailure(self, test, err):
410 Record a test failed result
413 :param err: error message
416 unittest.TestResult.addFailure(self, test, err)
417 if hasattr(test, 'tempdir'):
418 self.result_string = RED + "FAIL" + COLOR_RESET + \
419 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
421 self.result_string = RED + "FAIL" + COLOR_RESET + ' [no temp dir]'
423 def addError(self, test, err):
425 Record a test error result
428 :param err: error message
431 unittest.TestResult.addError(self, test, err)
432 if hasattr(test, 'tempdir'):
433 self.result_string = RED + "ERROR" + COLOR_RESET + \
434 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
436 self.result_string = RED + "ERROR" + COLOR_RESET + ' [no temp dir]'
438 def getDescription(self, test):
443 :returns: test description
446 # TODO: if none print warning not raise exception
447 short_description = test.shortDescription()
448 if self.descriptions and short_description:
449 return short_description
453 def startTest(self, test):
460 unittest.TestResult.startTest(self, test)
461 if self.verbosity > 0:
463 "Starting " + self.getDescription(test) + " ...")
464 self.stream.writeln(single_line_delim)
466 def stopTest(self, test):
473 unittest.TestResult.stopTest(self, test)
474 if self.verbosity > 0:
475 self.stream.writeln(single_line_delim)
476 self.stream.writeln("%-60s%s" %
477 (self.getDescription(test), self.result_string))
478 self.stream.writeln(single_line_delim)
480 self.stream.writeln("%-60s%s" %
481 (self.getDescription(test), self.result_string))
483 def printErrors(self):
485 Print errors from running the test case
487 self.stream.writeln()
488 self.printErrorList('ERROR', self.errors)
489 self.printErrorList('FAIL', self.failures)
491 def printErrorList(self, flavour, errors):
493 Print error list to the output stream together with error type
494 and test case description.
496 :param flavour: error type
497 :param errors: iterable errors
500 for test, err in errors:
501 self.stream.writeln(double_line_delim)
502 self.stream.writeln("%s: %s" %
503 (flavour, self.getDescription(test)))
504 self.stream.writeln(single_line_delim)
505 self.stream.writeln("%s" % err)
508 class VppTestRunner(unittest.TextTestRunner):
510 A basic test runner implementation which prints results on standard error.
513 def resultclass(self):
514 """Class maintaining the results of the tests"""
524 print("Running tests using custom test runner") # debug message
525 return super(VppTestRunner, self).run(test)