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')
117 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
118 debug_cli = "cli-listen localhost:5002"
119 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}",
120 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
121 if cls.plugin_path is not None:
122 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
123 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
126 def wait_for_enter(cls):
127 if cls.debug_gdbserver:
128 print(double_line_delim)
129 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
131 print(double_line_delim)
132 print("Spawned VPP with PID: %d" % cls.vpp.pid)
134 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
136 print(single_line_delim)
137 print("You can debug the VPP using e.g.:")
138 if cls.debug_gdbserver:
139 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
140 print("Now is the time to attach a gdb by running the above "
141 "command, set up breakpoints etc. and then resume VPP from "
142 "within gdb by issuing the 'continue' command")
144 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
145 print("Now is the time to attach a gdb by running the above "
146 "command and set up breakpoints etc.")
147 print(single_line_delim)
148 raw_input("Press ENTER to continue running the testcase...")
152 cmdline = cls.vpp_cmdline
154 if cls.debug_gdbserver:
155 cmdline = ['gdbserver', 'localhost:7777'] + cls.vpp_cmdline
156 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
158 cls.vpp = subprocess.Popen(cmdline,
159 stdout=subprocess.PIPE,
160 stderr=subprocess.PIPE,
167 Perform class setup before running the testcase
168 Remove shared memory files, start vpp and connect the vpp-api
170 cls.logger = getLogger(cls.__name__)
171 cls.tempdir = tempfile.mkdtemp(
172 prefix='vpp-unittest-' + cls.__name__ + '-')
173 cls.shm_prefix = cls.tempdir.split("/")[-1]
174 os.chdir(cls.tempdir)
175 cls.logger.info("Temporary dir is %s, shm prefix is %s",
176 cls.tempdir, cls.shm_prefix)
179 cls.packet_infos = {}
181 print(double_line_delim)
182 print(colorize(getdoc(cls), YELLOW))
183 print(double_line_delim)
184 # need to catch exceptions here because if we raise, then the cleanup
185 # doesn't get called and we might end with a zombie vpp
189 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
191 cls.vapi.register_hook(StepHook(cls))
193 cls.vapi.register_hook(PollHook(cls))
198 if cls.debug_gdbserver:
199 print(colorize("You're running VPP inside gdbserver but "
200 "VPP-API connection failed, did you forget "
201 "to 'continue' VPP from within gdb?", RED))
203 cls.vpp_stdout_queue = Queue()
204 cls.vpp_stdout_reader_thread = Thread(
205 target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
206 cls.vpp_stdout_reader_thread.start()
207 cls.vpp_stderr_queue = Queue()
208 cls.vpp_stderr_reader_thread = Thread(
209 target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
210 cls.vpp_stderr_reader_thread.start()
219 Disconnect vpp-api, kill vpp and cleanup shared memory files
221 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
223 if cls.vpp.returncode is None:
224 print(double_line_delim)
225 print("VPP or GDB server is still running")
226 print(single_line_delim)
227 raw_input("When done debugging, press ENTER to kill the process"
228 " and finish running the testcase...")
230 if hasattr(cls, 'vpp'):
231 cls.vapi.disconnect()
233 if cls.vpp.returncode is None:
237 if hasattr(cls, 'vpp_stdout_queue'):
238 cls.logger.info(single_line_delim)
239 cls.logger.info('VPP output to stdout while running %s:',
241 cls.logger.info(single_line_delim)
242 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
243 while not cls.vpp_stdout_queue.empty():
244 line = cls.vpp_stdout_queue.get_nowait()
246 cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
248 if hasattr(cls, 'vpp_stderr_queue'):
249 cls.logger.info(single_line_delim)
250 cls.logger.info('VPP output to stderr while running %s:',
252 cls.logger.info(single_line_delim)
253 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
254 while not cls.vpp_stderr_queue.empty():
255 line = cls.vpp_stderr_queue.get_nowait()
257 cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
258 cls.logger.info(single_line_delim)
261 def tearDownClass(cls):
262 """ Perform final cleanup after running all tests in this test-case """
266 """ Show various debug prints after each test """
267 if not self.vpp_dead:
268 self.logger.info(self.vapi.cli("show int"))
269 self.logger.info(self.vapi.cli("show trace"))
270 self.logger.info(self.vapi.cli("show hardware"))
271 self.logger.info(self.vapi.cli("show error"))
272 self.logger.info(self.vapi.cli("show run"))
275 """ Clear trace before running each test"""
276 self.vapi.cli("clear trace")
277 # store the test instance inside the test class - so that objects
278 # holding the class can access instance methods (like assertEqual)
279 type(self).test_instance = self
282 def pg_enable_capture(cls, interfaces):
284 Enable capture on packet-generator interfaces
286 :param interfaces: iterable interface indexes
295 Enable the packet-generator and send all prepared packet streams
296 Remove the packet streams afterwards
298 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
299 cls.vapi.cli('packet-generator enable')
300 sleep(1) # give VPP some time to process the packets
301 for stream in cls.pg_streams:
302 cls.vapi.cli('packet-generator delete %s' % stream)
306 def create_pg_interfaces(cls, interfaces):
308 Create packet-generator interfaces
310 :param interfaces: iterable indexes of the interfaces
315 intf = VppPGInterface(cls, i)
316 setattr(cls, intf.name, intf)
318 cls.pg_interfaces = result
322 def extend_packet(packet, size):
324 Extend packet to given size by padding with spaces
325 NOTE: Currently works only when Raw layer is present.
327 :param packet: packet
328 :param size: target size
331 packet_len = len(packet) + 4
332 extend = size - packet_len
334 packet[Raw].load += ' ' * extend
336 def add_packet_info_to_list(self, info):
338 Add packet info to the testcase's packet info list
340 :param info: packet info
343 info.index = len(self.packet_infos)
344 self.packet_infos[info.index] = info
346 def create_packet_info(self, src_pg_index, dst_pg_index):
348 Create packet info object containing the source and destination indexes
349 and add it to the testcase's packet info list
351 :param src_pg_index: source packet-generator index
352 :param dst_pg_index: destination packet-generator index
354 :returns: _PacketInfo object
358 self.add_packet_info_to_list(info)
359 info.src = src_pg_index
360 info.dst = dst_pg_index
364 def info_to_payload(info):
366 Convert _PacketInfo object to packet payload
368 :param info: _PacketInfo object
370 :returns: string containing serialized data from packet info
372 return "%d %d %d" % (info.index, info.src, info.dst)
375 def payload_to_info(payload):
377 Convert packet payload to _PacketInfo object
379 :param payload: packet payload
381 :returns: _PacketInfo object containing de-serialized data from payload
384 numbers = payload.split()
386 info.index = int(numbers[0])
387 info.src = int(numbers[1])
388 info.dst = int(numbers[2])
391 def get_next_packet_info(self, info):
393 Iterate over the packet info list stored in the testcase
394 Start iteration with first element if info is None
395 Continue based on index in info if info is specified
397 :param info: info or None
398 :returns: next info in list or None if no more infos
403 next_index = info.index + 1
404 if next_index == len(self.packet_infos):
407 return self.packet_infos[next_index]
409 def get_next_packet_info_for_interface(self, src_index, info):
411 Search the packet info list for the next packet info with same source
414 :param src_index: source interface index to search for
415 :param info: packet info - where to start the search
416 :returns: packet info or None
420 info = self.get_next_packet_info(info)
423 if info.src == src_index:
426 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
428 Search the packet info list for the next packet info with same source
429 and destination interface indexes
431 :param src_index: source interface index to search for
432 :param dst_index: destination interface index to search for
433 :param info: packet info - where to start the search
434 :returns: packet info or None
438 info = self.get_next_packet_info_for_interface(src_index, info)
441 if info.dst == dst_index:
445 class VppTestResult(unittest.TestResult):
447 @property result_string
448 String variable to store the test case result string.
450 List variable containing 2-tuples of TestCase instances and strings
451 holding formatted tracebacks. Each tuple represents a test which
452 raised an unexpected exception.
454 List variable containing 2-tuples of TestCase instances and strings
455 holding formatted tracebacks. Each tuple represents a test where
456 a failure was explicitly signalled using the TestCase.assert*()
460 def __init__(self, stream, descriptions, verbosity):
462 :param stream File descriptor to store where to report test results. Set
463 to the standard error stream by default.
464 :param descriptions Boolean variable to store information if to use test
466 :param verbosity Integer variable to store required verbosity level.
468 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
470 self.descriptions = descriptions
471 self.verbosity = verbosity
472 self.result_string = None
474 def addSuccess(self, test):
476 Record a test succeeded result
481 unittest.TestResult.addSuccess(self, test)
482 self.result_string = colorize("OK", GREEN)
484 def addSkip(self, test, reason):
486 Record a test skipped.
492 unittest.TestResult.addSkip(self, test, reason)
493 self.result_string = colorize("SKIP", YELLOW)
495 def addFailure(self, test, err):
497 Record a test failed result
500 :param err: error message
503 unittest.TestResult.addFailure(self, test, err)
504 if hasattr(test, 'tempdir'):
505 self.result_string = colorize("FAIL", RED) + \
506 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
508 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
510 def addError(self, test, err):
512 Record a test error result
515 :param err: error message
518 unittest.TestResult.addError(self, test, err)
519 if hasattr(test, 'tempdir'):
520 self.result_string = colorize("ERROR", RED) + \
521 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
523 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
525 def getDescription(self, test):
530 :returns: test description
533 # TODO: if none print warning not raise exception
534 short_description = test.shortDescription()
535 if self.descriptions and short_description:
536 return short_description
540 def startTest(self, test):
547 unittest.TestResult.startTest(self, test)
548 if self.verbosity > 0:
550 "Starting " + self.getDescription(test) + " ...")
551 self.stream.writeln(single_line_delim)
553 def stopTest(self, test):
560 unittest.TestResult.stopTest(self, test)
561 if self.verbosity > 0:
562 self.stream.writeln(single_line_delim)
563 self.stream.writeln("%-60s%s" %
564 (self.getDescription(test), self.result_string))
565 self.stream.writeln(single_line_delim)
567 self.stream.writeln("%-60s%s" %
568 (self.getDescription(test), self.result_string))
570 def printErrors(self):
572 Print errors from running the test case
574 self.stream.writeln()
575 self.printErrorList('ERROR', self.errors)
576 self.printErrorList('FAIL', self.failures)
578 def printErrorList(self, flavour, errors):
580 Print error list to the output stream together with error type
581 and test case description.
583 :param flavour: error type
584 :param errors: iterable errors
587 for test, err in errors:
588 self.stream.writeln(double_line_delim)
589 self.stream.writeln("%s: %s" %
590 (flavour, self.getDescription(test)))
591 self.stream.writeln(single_line_delim)
592 self.stream.writeln("%s" % err)
595 class VppTestRunner(unittest.TextTestRunner):
597 A basic test runner implementation which prints results on standard error.
600 def resultclass(self):
601 """Class maintaining the results of the tests"""
611 print("Running tests using custom test runner") # debug message
612 return super(VppTestRunner, self).run(test)