10 from time import sleep
11 from Queue import Queue
12 from threading import Thread
13 from inspect import getdoc
14 from hook import StepHook, PollHook
15 from vpp_pg_interface import VppPGInterface
16 from vpp_papi_provider import VppPapiProvider
17 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.
28 class _PacketInfo(object):
29 """Private class to create packet info object.
31 Help process information about the next packet.
32 Set variables to default values.
34 Integer variable to store the index of the packet.
36 Integer variable to store the index of the source packet generator
37 interface of the packet.
39 Integer variable to store the index of the destination packet generator
40 interface of the packet.
42 Object variable to store the copy of the former packet.
52 def pump_output(out, queue):
53 for line in iter(out.readline, b''):
57 class VppTestCase(unittest.TestCase):
59 Subclass of the python unittest.TestCase class.
61 This subclass is a base class for test cases that are implemented as classes
62 It provides methods to create and run test case.
67 def packet_infos(self):
68 """List of packet infos"""
69 return self._packet_infos
72 def packet_infos(self, value):
73 self._packet_infos = value
77 """Return the instance of this testcase"""
78 return cls.test_instance
81 def set_debug_flags(cls, d):
82 cls.debug_core = False
84 cls.debug_gdbserver = False
89 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
90 # give a heads up if this is actually useless
91 cls.logger.critical("WARNING: core size limit is set 0, core "
92 "files will NOT be created")
96 elif dl == "gdbserver":
97 cls.debug_gdbserver = True
99 raise Exception("Unrecognized DEBUG option: '%s'" % d)
102 def setUpConstants(cls):
103 """ Set-up the test case class based on environment variables """
105 s = os.getenv("STEP")
106 cls.step = True if s.lower() in ("y", "yes", "1") else False
110 d = os.getenv("DEBUG")
113 cls.set_debug_flags(d)
114 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
115 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
116 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon",
117 "cli-listen localhost:5002", "}",
118 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
119 if cls.plugin_path is not None:
120 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
121 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
124 def wait_for_enter(cls):
125 if cls.debug_gdbserver:
126 print(double_line_delim)
127 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
129 print(double_line_delim)
130 print("Spawned VPP with PID: %d" % cls.vpp.pid)
132 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
134 print(single_line_delim)
135 print("You can debug the VPP using e.g.:")
136 if cls.debug_gdbserver:
137 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
138 print("Now is the time to attach a gdb by running the above "
139 "command, set up breakpoints etc. and then resume VPP from "
140 "within gdb by issuing the 'continue' command")
142 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
143 print("Now is the time to attach a gdb by running the above "
144 "command and set up breakpoints etc.")
145 print(single_line_delim)
146 raw_input("Press ENTER to continue running the testcase...")
150 cmdline = cls.vpp_cmdline
152 if cls.debug_gdbserver:
153 cmdline = ['gdbserver', 'localhost:7777'] + cls.vpp_cmdline
154 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
156 cls.vpp = subprocess.Popen(cmdline,
157 stdout=subprocess.PIPE,
158 stderr=subprocess.PIPE,
165 Perform class setup before running the testcase
166 Remove shared memory files, start vpp and connect the vpp-api
168 cls.logger = getLogger(cls.__name__)
169 cls.tempdir = tempfile.mkdtemp(
170 prefix='vpp-unittest-' + cls.__name__ + '-')
171 cls.shm_prefix = cls.tempdir.split("/")[-1]
172 os.chdir(cls.tempdir)
173 cls.logger.info("Temporary dir is %s, shm prefix is %s",
174 cls.tempdir, cls.shm_prefix)
177 cls.packet_infos = {}
179 print(double_line_delim)
180 print(colorize(getdoc(cls), YELLOW))
181 print(double_line_delim)
182 # need to catch exceptions here because if we raise, then the cleanup
183 # doesn't get called and we might end with a zombie vpp
187 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
189 cls.vapi.register_hook(StepHook(cls))
191 cls.vapi.register_hook(PollHook(cls))
196 if cls.debug_gdbserver:
197 print(colorize("You're running VPP inside gdbserver but "
198 "VPP-API connection failed, did you forget "
199 "to 'continue' VPP from within gdb?", RED))
201 cls.vpp_stdout_queue = Queue()
202 cls.vpp_stdout_reader_thread = Thread(
203 target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
204 cls.vpp_stdout_reader_thread.start()
205 cls.vpp_stderr_queue = Queue()
206 cls.vpp_stderr_reader_thread = Thread(
207 target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
208 cls.vpp_stderr_reader_thread.start()
217 Disconnect vpp-api, kill vpp and cleanup shared memory files
219 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
221 if cls.vpp.returncode is None:
222 print(double_line_delim)
223 print("VPP or GDB server is still running")
224 print(single_line_delim)
225 raw_input("When done debugging, press ENTER to kill the process"
226 " and finish running the testcase...")
228 if hasattr(cls, 'vpp'):
229 cls.vapi.disconnect()
231 if cls.vpp.returncode is None:
235 if hasattr(cls, 'vpp_stdout_queue'):
236 cls.logger.info(single_line_delim)
237 cls.logger.info('VPP output to stdout while running %s:',
239 cls.logger.info(single_line_delim)
240 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
241 while not cls.vpp_stdout_queue.empty():
242 line = cls.vpp_stdout_queue.get_nowait()
244 cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
246 if hasattr(cls, 'vpp_stderr_queue'):
247 cls.logger.info(single_line_delim)
248 cls.logger.info('VPP output to stderr while running %s:',
250 cls.logger.info(single_line_delim)
251 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
252 while not cls.vpp_stderr_queue.empty():
253 line = cls.vpp_stderr_queue.get_nowait()
255 cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
256 cls.logger.info(single_line_delim)
259 def tearDownClass(cls):
260 """ Perform final cleanup after running all tests in this test-case """
264 """ Show various debug prints after each test """
265 if not self.vpp_dead:
266 self.logger.info(self.vapi.cli("show int"))
267 self.logger.info(self.vapi.cli("show trace"))
268 self.logger.info(self.vapi.cli("show hardware"))
269 self.logger.info(self.vapi.cli("show error"))
270 self.logger.info(self.vapi.cli("show run"))
273 """ Clear trace before running each test"""
274 self.vapi.cli("clear trace")
275 # store the test instance inside the test class - so that objects
276 # holding the class can access instance methods (like assertEqual)
277 type(self).test_instance = self
280 def pg_enable_capture(cls, interfaces):
282 Enable capture on packet-generator interfaces
284 :param interfaces: iterable interface indexes
293 Enable the packet-generator and send all prepared packet streams
294 Remove the packet streams afterwards
296 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
297 cls.vapi.cli('packet-generator enable')
298 sleep(1) # give VPP some time to process the packets
299 for stream in cls.pg_streams:
300 cls.vapi.cli('packet-generator delete %s' % stream)
304 def create_pg_interfaces(cls, interfaces):
306 Create packet-generator interfaces
308 :param interfaces: iterable indexes of the interfaces
313 intf = VppPGInterface(cls, i)
314 setattr(cls, intf.name, intf)
316 cls.pg_interfaces = result
320 def extend_packet(packet, size):
322 Extend packet to given size by padding with spaces
323 NOTE: Currently works only when Raw layer is present.
325 :param packet: packet
326 :param size: target size
329 packet_len = len(packet) + 4
330 extend = size - packet_len
332 packet[Raw].load += ' ' * extend
334 def add_packet_info_to_list(self, info):
336 Add packet info to the testcase's packet info list
338 :param info: packet info
341 info.index = len(self.packet_infos)
342 self.packet_infos[info.index] = info
344 def create_packet_info(self, src_pg_index, dst_pg_index):
346 Create packet info object containing the source and destination indexes
347 and add it to the testcase's packet info list
349 :param src_pg_index: source packet-generator index
350 :param dst_pg_index: destination packet-generator index
352 :returns: _PacketInfo object
356 self.add_packet_info_to_list(info)
357 info.src = src_pg_index
358 info.dst = dst_pg_index
362 def info_to_payload(info):
364 Convert _PacketInfo object to packet payload
366 :param info: _PacketInfo object
368 :returns: string containing serialized data from packet info
370 return "%d %d %d" % (info.index, info.src, info.dst)
373 def payload_to_info(payload):
375 Convert packet payload to _PacketInfo object
377 :param payload: packet payload
379 :returns: _PacketInfo object containing de-serialized data from payload
382 numbers = payload.split()
384 info.index = int(numbers[0])
385 info.src = int(numbers[1])
386 info.dst = int(numbers[2])
389 def get_next_packet_info(self, info):
391 Iterate over the packet info list stored in the testcase
392 Start iteration with first element if info is None
393 Continue based on index in info if info is specified
395 :param info: info or None
396 :returns: next info in list or None if no more infos
401 next_index = info.index + 1
402 if next_index == len(self.packet_infos):
405 return self.packet_infos[next_index]
407 def get_next_packet_info_for_interface(self, src_index, info):
409 Search the packet info list for the next packet info with same source
412 :param src_index: source interface index to search for
413 :param info: packet info - where to start the search
414 :returns: packet info or None
418 info = self.get_next_packet_info(info)
421 if info.src == src_index:
424 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
426 Search the packet info list for the next packet info with same source
427 and destination interface indexes
429 :param src_index: source interface index to search for
430 :param dst_index: destination interface index to search for
431 :param info: packet info - where to start the search
432 :returns: packet info or None
436 info = self.get_next_packet_info_for_interface(src_index, info)
439 if info.dst == dst_index:
443 class VppTestResult(unittest.TestResult):
445 @property result_string
446 String variable to store the test case result string.
448 List variable containing 2-tuples of TestCase instances and strings
449 holding formatted tracebacks. Each tuple represents a test which
450 raised an unexpected exception.
452 List variable containing 2-tuples of TestCase instances and strings
453 holding formatted tracebacks. Each tuple represents a test where
454 a failure was explicitly signalled using the TestCase.assert*()
458 def __init__(self, stream, descriptions, verbosity):
460 :param stream File descriptor to store where to report test results. Set
461 to the standard error stream by default.
462 :param descriptions Boolean variable to store information if to use test
464 :param verbosity Integer variable to store required verbosity level.
466 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
468 self.descriptions = descriptions
469 self.verbosity = verbosity
470 self.result_string = None
472 def addSuccess(self, test):
474 Record a test succeeded result
479 unittest.TestResult.addSuccess(self, test)
480 self.result_string = colorize("OK", GREEN)
482 def addSkip(self, test, reason):
484 Record a test skipped.
490 unittest.TestResult.addSkip(self, test, reason)
491 self.result_string = colorize("SKIP", YELLOW)
493 def addFailure(self, test, err):
495 Record a test failed result
498 :param err: error message
501 unittest.TestResult.addFailure(self, test, err)
502 if hasattr(test, 'tempdir'):
503 self.result_string = colorize("FAIL", RED) + \
504 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
506 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
508 def addError(self, test, err):
510 Record a test error result
513 :param err: error message
516 unittest.TestResult.addError(self, test, err)
517 if hasattr(test, 'tempdir'):
518 self.result_string = colorize("ERROR", RED) + \
519 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
521 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
523 def getDescription(self, test):
528 :returns: test description
531 # TODO: if none print warning not raise exception
532 short_description = test.shortDescription()
533 if self.descriptions and short_description:
534 return short_description
538 def startTest(self, test):
545 unittest.TestResult.startTest(self, test)
546 if self.verbosity > 0:
548 "Starting " + self.getDescription(test) + " ...")
549 self.stream.writeln(single_line_delim)
551 def stopTest(self, test):
558 unittest.TestResult.stopTest(self, test)
559 if self.verbosity > 0:
560 self.stream.writeln(single_line_delim)
561 self.stream.writeln("%-60s%s" %
562 (self.getDescription(test), self.result_string))
563 self.stream.writeln(single_line_delim)
565 self.stream.writeln("%-60s%s" %
566 (self.getDescription(test), self.result_string))
568 def printErrors(self):
570 Print errors from running the test case
572 self.stream.writeln()
573 self.printErrorList('ERROR', self.errors)
574 self.printErrorList('FAIL', self.failures)
576 def printErrorList(self, flavour, errors):
578 Print error list to the output stream together with error type
579 and test case description.
581 :param flavour: error type
582 :param errors: iterable errors
585 for test, err in errors:
586 self.stream.writeln(double_line_delim)
587 self.stream.writeln("%s: %s" %
588 (flavour, self.getDescription(test)))
589 self.stream.writeln(single_line_delim)
590 self.stream.writeln("%s" % err)
593 class VppTestRunner(unittest.TextTestRunner):
595 A basic test runner implementation which prints results on standard error.
598 def resultclass(self):
599 """Class maintaining the results of the tests"""
609 print("Running tests using custom test runner") # debug message
610 return super(VppTestRunner, self).run(test)