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 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
118 if cls.plugin_path is not None:
119 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
120 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
123 def wait_for_enter(cls):
124 if cls.debug_gdbserver:
125 print(double_line_delim)
126 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
128 print(double_line_delim)
129 print("Spawned VPP with PID: %d" % cls.vpp.pid)
131 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
133 print(single_line_delim)
134 print("You can debug the VPP using e.g.:")
135 if cls.debug_gdbserver:
136 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
137 print("Now is the time to attach a gdb by running the above "
138 "command, set up breakpoints etc. and then resume VPP from "
139 "within gdb by issuing the 'continue' command")
141 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
142 print("Now is the time to attach a gdb by running the above "
143 "command and set up breakpoints etc.")
144 print(single_line_delim)
145 raw_input("Press ENTER to continue running the testcase...")
149 cmdline = cls.vpp_cmdline
151 if cls.debug_gdbserver:
152 cmdline = ['gdbserver', 'localhost:7777'] + cls.vpp_cmdline
153 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
155 cls.vpp = subprocess.Popen(cmdline,
156 stdout=subprocess.PIPE,
157 stderr=subprocess.PIPE,
164 Perform class setup before running the testcase
165 Remove shared memory files, start vpp and connect the vpp-api
167 cls.logger = getLogger(cls.__name__)
168 cls.tempdir = tempfile.mkdtemp(
169 prefix='vpp-unittest-' + cls.__name__ + '-')
170 cls.shm_prefix = cls.tempdir.split("/")[-1]
171 os.chdir(cls.tempdir)
172 cls.logger.info("Temporary dir is %s, shm prefix is %s",
173 cls.tempdir, cls.shm_prefix)
176 cls.packet_infos = {}
178 print(double_line_delim)
179 print(colorize(getdoc(cls), YELLOW))
180 print(double_line_delim)
181 # need to catch exceptions here because if we raise, then the cleanup
182 # doesn't get called and we might end with a zombie vpp
186 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
188 cls.vapi.register_hook(StepHook(cls))
190 cls.vapi.register_hook(PollHook(cls))
195 if cls.debug_gdbserver:
196 print(colorize("You're running VPP inside gdbserver but "
197 "VPP-API connection failed, did you forget "
198 "to 'continue' VPP from within gdb?", RED))
200 cls.vpp_stdout_queue = Queue()
201 cls.vpp_stdout_reader_thread = Thread(
202 target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
203 cls.vpp_stdout_reader_thread.start()
204 cls.vpp_stderr_queue = Queue()
205 cls.vpp_stderr_reader_thread = Thread(
206 target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
207 cls.vpp_stderr_reader_thread.start()
216 Disconnect vpp-api, kill vpp and cleanup shared memory files
218 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
220 if cls.vpp.returncode is None:
221 print(double_line_delim)
222 print("VPP or GDB server is still running")
223 print(single_line_delim)
224 raw_input("When done debugging, press ENTER to kill the process"
225 " and finish running the testcase...")
227 if hasattr(cls, 'vpp'):
228 cls.vapi.disconnect()
230 if cls.vpp.returncode is None:
234 if hasattr(cls, 'vpp_stdout_queue'):
235 cls.logger.info(single_line_delim)
236 cls.logger.info('VPP output to stdout while running %s:',
238 cls.logger.info(single_line_delim)
239 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
240 while not cls.vpp_stdout_queue.empty():
241 line = cls.vpp_stdout_queue.get_nowait()
243 cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
245 if hasattr(cls, 'vpp_stderr_queue'):
246 cls.logger.info(single_line_delim)
247 cls.logger.info('VPP output to stderr while running %s:',
249 cls.logger.info(single_line_delim)
250 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
251 while not cls.vpp_stderr_queue.empty():
252 line = cls.vpp_stderr_queue.get_nowait()
254 cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
255 cls.logger.info(single_line_delim)
258 def tearDownClass(cls):
259 """ Perform final cleanup after running all tests in this test-case """
263 """ Show various debug prints after each test """
264 if not self.vpp_dead:
265 self.logger.info(self.vapi.cli("show int"))
266 self.logger.info(self.vapi.cli("show trace"))
267 self.logger.info(self.vapi.cli("show hardware"))
268 self.logger.info(self.vapi.cli("show error"))
269 self.logger.info(self.vapi.cli("show run"))
272 """ Clear trace before running each test"""
273 self.vapi.cli("clear trace")
274 # store the test instance inside the test class - so that objects
275 # holding the class can access instance methods (like assertEqual)
276 type(self).test_instance = self
279 def pg_enable_capture(cls, interfaces):
281 Enable capture on packet-generator interfaces
283 :param interfaces: iterable interface indexes
292 Enable the packet-generator and send all prepared packet streams
293 Remove the packet streams afterwards
295 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
296 cls.vapi.cli('packet-generator enable')
297 sleep(1) # give VPP some time to process the packets
298 for stream in cls.pg_streams:
299 cls.vapi.cli('packet-generator delete %s' % stream)
303 def create_pg_interfaces(cls, interfaces):
305 Create packet-generator interfaces
307 :param interfaces: iterable indexes of the interfaces
312 intf = VppPGInterface(cls, i)
313 setattr(cls, intf.name, intf)
315 cls.pg_interfaces = result
319 def extend_packet(packet, size):
321 Extend packet to given size by padding with spaces
322 NOTE: Currently works only when Raw layer is present.
324 :param packet: packet
325 :param size: target size
328 packet_len = len(packet) + 4
329 extend = size - packet_len
331 packet[Raw].load += ' ' * extend
333 def add_packet_info_to_list(self, info):
335 Add packet info to the testcase's packet info list
337 :param info: packet info
340 info.index = len(self.packet_infos)
341 self.packet_infos[info.index] = info
343 def create_packet_info(self, src_pg_index, dst_pg_index):
345 Create packet info object containing the source and destination indexes
346 and add it to the testcase's packet info list
348 :param src_pg_index: source packet-generator index
349 :param dst_pg_index: destination packet-generator index
351 :returns: _PacketInfo object
355 self.add_packet_info_to_list(info)
356 info.src = src_pg_index
357 info.dst = dst_pg_index
361 def info_to_payload(info):
363 Convert _PacketInfo object to packet payload
365 :param info: _PacketInfo object
367 :returns: string containing serialized data from packet info
369 return "%d %d %d" % (info.index, info.src, info.dst)
372 def payload_to_info(payload):
374 Convert packet payload to _PacketInfo object
376 :param payload: packet payload
378 :returns: _PacketInfo object containing de-serialized data from payload
381 numbers = payload.split()
383 info.index = int(numbers[0])
384 info.src = int(numbers[1])
385 info.dst = int(numbers[2])
388 def get_next_packet_info(self, info):
390 Iterate over the packet info list stored in the testcase
391 Start iteration with first element if info is None
392 Continue based on index in info if info is specified
394 :param info: info or None
395 :returns: next info in list or None if no more infos
400 next_index = info.index + 1
401 if next_index == len(self.packet_infos):
404 return self.packet_infos[next_index]
406 def get_next_packet_info_for_interface(self, src_index, info):
408 Search the packet info list for the next packet info with same source
411 :param src_index: source interface index to search for
412 :param info: packet info - where to start the search
413 :returns: packet info or None
417 info = self.get_next_packet_info(info)
420 if info.src == src_index:
423 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
425 Search the packet info list for the next packet info with same source
426 and destination interface indexes
428 :param src_index: source interface index to search for
429 :param dst_index: destination interface index to search for
430 :param info: packet info - where to start the search
431 :returns: packet info or None
435 info = self.get_next_packet_info_for_interface(src_index, info)
438 if info.dst == dst_index:
442 class VppTestResult(unittest.TestResult):
444 @property result_string
445 String variable to store the test case result string.
447 List variable containing 2-tuples of TestCase instances and strings
448 holding formatted tracebacks. Each tuple represents a test which
449 raised an unexpected exception.
451 List variable containing 2-tuples of TestCase instances and strings
452 holding formatted tracebacks. Each tuple represents a test where
453 a failure was explicitly signalled using the TestCase.assert*()
457 def __init__(self, stream, descriptions, verbosity):
459 :param stream File descriptor to store where to report test results. Set
460 to the standard error stream by default.
461 :param descriptions Boolean variable to store information if to use test
463 :param verbosity Integer variable to store required verbosity level.
465 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
467 self.descriptions = descriptions
468 self.verbosity = verbosity
469 self.result_string = None
471 def addSuccess(self, test):
473 Record a test succeeded result
478 unittest.TestResult.addSuccess(self, test)
479 self.result_string = colorize("OK", GREEN)
481 def addSkip(self, test, reason):
483 Record a test skipped.
489 unittest.TestResult.addSkip(self, test, reason)
490 self.result_string = colorize("SKIP", YELLOW)
492 def addFailure(self, test, err):
494 Record a test failed result
497 :param err: error message
500 unittest.TestResult.addFailure(self, test, err)
501 if hasattr(test, 'tempdir'):
502 self.result_string = colorize("FAIL", RED) + \
503 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
505 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
507 def addError(self, test, err):
509 Record a test error result
512 :param err: error message
515 unittest.TestResult.addError(self, test, err)
516 if hasattr(test, 'tempdir'):
517 self.result_string = colorize("ERROR", RED) + \
518 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
520 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
522 def getDescription(self, test):
527 :returns: test description
530 # TODO: if none print warning not raise exception
531 short_description = test.shortDescription()
532 if self.descriptions and short_description:
533 return short_description
537 def startTest(self, test):
544 unittest.TestResult.startTest(self, test)
545 if self.verbosity > 0:
547 "Starting " + self.getDescription(test) + " ...")
548 self.stream.writeln(single_line_delim)
550 def stopTest(self, test):
557 unittest.TestResult.stopTest(self, test)
558 if self.verbosity > 0:
559 self.stream.writeln(single_line_delim)
560 self.stream.writeln("%-60s%s" %
561 (self.getDescription(test), self.result_string))
562 self.stream.writeln(single_line_delim)
564 self.stream.writeln("%-60s%s" %
565 (self.getDescription(test), self.result_string))
567 def printErrors(self):
569 Print errors from running the test case
571 self.stream.writeln()
572 self.printErrorList('ERROR', self.errors)
573 self.printErrorList('FAIL', self.failures)
575 def printErrorList(self, flavour, errors):
577 Print error list to the output stream together with error type
578 and test case description.
580 :param flavour: error type
581 :param errors: iterable errors
584 for test, err in errors:
585 self.stream.writeln(double_line_delim)
586 self.stream.writeln("%s: %s" %
587 (flavour, self.getDescription(test)))
588 self.stream.writeln(single_line_delim)
589 self.stream.writeln("%s" % err)
592 class VppTestRunner(unittest.TextTestRunner):
594 A basic test runner implementation which prints results on standard error.
597 def resultclass(self):
598 """Class maintaining the results of the tests"""
608 print("Running tests using custom test runner") # debug message
609 return super(VppTestRunner, self).run(test)