3 from __future__ import print_function
14 from collections import deque
15 from threading import Thread, Event
16 from inspect import getdoc, isclass
17 from traceback import format_exception
18 from logging import FileHandler, DEBUG, Formatter
19 from scapy.packet import Raw
20 from hook import StepHook, PollHook
21 from vpp_pg_interface import VppPGInterface
22 from vpp_sub_interface import VppSubInterface
23 from vpp_lo_interface import VppLoInterface
24 from vpp_papi_provider import VppPapiProvider
26 from vpp_object import VppObjectRegistry
27 from vpp_punt_socket import vpp_uds_socket_name
28 if os.name == 'posix' and sys.version_info[0] < 3:
29 # using subprocess32 is recommended by python official documentation
30 # @ https://docs.python.org/2/library/subprocess.html
31 import subprocess32 as subprocess
36 Test framework module.
38 The module provides a set of tools for constructing and running tests and
39 representing the results.
43 class _PacketInfo(object):
44 """Private class to create packet info object.
46 Help process information about the next packet.
47 Set variables to default values.
49 #: Store the index of the packet.
51 #: Store the index of the source packet generator interface of the packet.
53 #: Store the index of the destination packet generator interface
56 #: Store expected ip version
58 #: Store expected upper protocol
60 #: Store the copy of the former packet.
63 def __eq__(self, other):
64 index = self.index == other.index
65 src = self.src == other.src
66 dst = self.dst == other.dst
67 data = self.data == other.data
68 return index and src and dst and data
71 def pump_output(testclass):
72 """ pump output from vpp stdout/stderr to proper queues """
75 while not testclass.pump_thread_stop_flag.wait(0):
76 readable = select.select([testclass.vpp.stdout.fileno(),
77 testclass.vpp.stderr.fileno(),
78 testclass.pump_thread_wakeup_pipe[0]],
80 if testclass.vpp.stdout.fileno() in readable:
81 read = os.read(testclass.vpp.stdout.fileno(), 102400)
83 split = read.splitlines(True)
84 if len(stdout_fragment) > 0:
85 split[0] = "%s%s" % (stdout_fragment, split[0])
86 if len(split) > 0 and split[-1].endswith("\n"):
90 stdout_fragment = split[-1]
91 testclass.vpp_stdout_deque.extend(split[:limit])
92 if not testclass.cache_vpp_output:
93 for line in split[:limit]:
94 testclass.logger.debug(
95 "VPP STDOUT: %s" % line.rstrip("\n"))
96 if testclass.vpp.stderr.fileno() in readable:
97 read = os.read(testclass.vpp.stderr.fileno(), 102400)
99 split = read.splitlines(True)
100 if len(stderr_fragment) > 0:
101 split[0] = "%s%s" % (stderr_fragment, split[0])
102 if len(split) > 0 and split[-1].endswith("\n"):
106 stderr_fragment = split[-1]
107 testclass.vpp_stderr_deque.extend(split[:limit])
108 if not testclass.cache_vpp_output:
109 for line in split[:limit]:
110 testclass.logger.debug(
111 "VPP STDERR: %s" % line.rstrip("\n"))
112 # ignoring the dummy pipe here intentionally - the flag will take care
113 # of properly terminating the loop
116 def running_extended_tests():
118 s = os.getenv("EXTENDED_TESTS")
119 return True if s.lower() in ("y", "yes", "1") else False
125 def running_on_centos():
127 os_id = os.getenv("OS_ID")
128 return True if "centos" in os_id.lower() else False
134 class KeepAliveReporter(object):
136 Singleton object which reports test start to parent process
141 self.__dict__ = self._shared_state
148 def pipe(self, pipe):
149 if hasattr(self, '_pipe'):
150 raise Exception("Internal error - pipe should only be set once.")
153 def send_keep_alive(self, test):
155 Write current test tmpdir & desc to keep-alive pipe to signal liveness
157 if self.pipe is None:
158 # if not running forked..
164 desc = test.shortDescription()
168 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
171 class VppTestCase(unittest.TestCase):
172 """This subclass is a base class for VPP test cases that are implemented as
173 classes. It provides methods to create and run test case.
177 def packet_infos(self):
178 """List of packet infos"""
179 return self._packet_infos
182 def get_packet_count_for_if_idx(cls, dst_if_index):
183 """Get the number of packet info for specified destination if index"""
184 if dst_if_index in cls._packet_count_for_dst_if_idx:
185 return cls._packet_count_for_dst_if_idx[dst_if_index]
191 """Return the instance of this testcase"""
192 return cls.test_instance
195 def set_debug_flags(cls, d):
196 cls.debug_core = False
197 cls.debug_gdb = False
198 cls.debug_gdbserver = False
203 cls.debug_core = True
206 elif dl == "gdbserver":
207 cls.debug_gdbserver = True
209 raise Exception("Unrecognized DEBUG option: '%s'" % d)
212 def setUpConstants(cls):
213 """ Set-up the test case class based on environment variables """
215 s = os.getenv("STEP")
216 cls.step = True if s.lower() in ("y", "yes", "1") else False
220 d = os.getenv("DEBUG")
224 c = os.getenv("CACHE_OUTPUT", "1")
225 cls.cache_vpp_output = \
226 False if c.lower() in ("n", "no", "0") else True
228 cls.cache_vpp_output = True
229 cls.set_debug_flags(d)
230 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
231 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
232 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
234 if cls.plugin_path is not None:
235 if cls.extern_plugin_path is not None:
236 plugin_path = "%s:%s" % (
237 cls.plugin_path, cls.extern_plugin_path)
239 plugin_path = cls.plugin_path
240 elif cls.extern_plugin_path is not None:
241 plugin_path = cls.extern_plugin_path
243 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
244 debug_cli = "cli-listen localhost:5002"
247 size = os.getenv("COREDUMP_SIZE")
249 coredump_size = "coredump-size %s" % size
252 if coredump_size is None:
253 coredump_size = "coredump-size unlimited"
254 cls.vpp_cmdline = [cls.vpp_bin, "unix",
255 "{", "nodaemon", debug_cli, "full-coredump",
256 coredump_size, "}", "api-trace", "{", "on", "}",
257 "api-segment", "{", "prefix", cls.shm_prefix, "}",
258 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
260 "punt", "{", "socket", cls.punt_socket_path, "}"]
261 if plugin_path is not None:
262 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
263 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
266 def wait_for_enter(cls):
267 if cls.debug_gdbserver:
268 print(double_line_delim)
269 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
271 print(double_line_delim)
272 print("Spawned VPP with PID: %d" % cls.vpp.pid)
274 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
276 print(single_line_delim)
277 print("You can debug the VPP using e.g.:")
278 if cls.debug_gdbserver:
279 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
280 print("Now is the time to attach a gdb by running the above "
281 "command, set up breakpoints etc. and then resume VPP from "
282 "within gdb by issuing the 'continue' command")
284 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
285 print("Now is the time to attach a gdb by running the above "
286 "command and set up breakpoints etc.")
287 print(single_line_delim)
288 raw_input("Press ENTER to continue running the testcase...")
292 cmdline = cls.vpp_cmdline
294 if cls.debug_gdbserver:
295 gdbserver = '/usr/bin/gdbserver'
296 if not os.path.isfile(gdbserver) or \
297 not os.access(gdbserver, os.X_OK):
298 raise Exception("gdbserver binary '%s' does not exist or is "
299 "not executable" % gdbserver)
301 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
302 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
305 cls.vpp = subprocess.Popen(cmdline,
306 stdout=subprocess.PIPE,
307 stderr=subprocess.PIPE,
309 except Exception as e:
310 cls.logger.critical("Couldn't start vpp: %s" % e)
318 Perform class setup before running the testcase
319 Remove shared memory files, start vpp and connect the vpp-api
321 gc.collect() # run garbage collection first
323 cls.logger = getLogger(cls.__name__)
324 cls.tempdir = tempfile.mkdtemp(
325 prefix='vpp-unittest-%s-' % cls.__name__)
326 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
327 cls.file_handler.setFormatter(
328 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
330 cls.file_handler.setLevel(DEBUG)
331 cls.logger.addHandler(cls.file_handler)
332 cls.shm_prefix = cls.tempdir.split("/")[-1]
333 cls.punt_socket_path = '%s/%s' % (cls.tempdir, vpp_uds_socket_name)
334 os.chdir(cls.tempdir)
335 cls.logger.info("Temporary dir is %s, shm prefix is %s",
336 cls.tempdir, cls.shm_prefix)
338 cls.reset_packet_infos()
340 cls._zombie_captures = []
343 cls.registry = VppObjectRegistry()
344 cls.vpp_startup_failed = False
345 cls.reporter = KeepAliveReporter()
346 # need to catch exceptions here because if we raise, then the cleanup
347 # doesn't get called and we might end with a zombie vpp
350 cls.reporter.send_keep_alive(cls)
351 cls.vpp_stdout_deque = deque()
352 cls.vpp_stderr_deque = deque()
353 cls.pump_thread_stop_flag = Event()
354 cls.pump_thread_wakeup_pipe = os.pipe()
355 cls.pump_thread = Thread(target=pump_output, args=(cls,))
356 cls.pump_thread.daemon = True
357 cls.pump_thread.start()
358 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
363 cls.vapi.register_hook(hook)
364 cls.sleep(0.1, "after vpp startup, before initial poll")
368 cls.vpp_startup_failed = True
370 "VPP died shortly after startup, check the"
371 " output to standard error for possible cause")
376 if cls.debug_gdbserver:
377 print(colorize("You're running VPP inside gdbserver but "
378 "VPP-API connection failed, did you forget "
379 "to 'continue' VPP from within gdb?", RED))
382 t, v, tb = sys.exc_info()
392 Disconnect vpp-api, kill vpp and cleanup shared memory files
394 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
396 if cls.vpp.returncode is None:
397 print(double_line_delim)
398 print("VPP or GDB server is still running")
399 print(single_line_delim)
400 raw_input("When done debugging, press ENTER to kill the "
401 "process and finish running the testcase...")
403 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
404 cls.pump_thread_stop_flag.set()
405 if hasattr(cls, 'pump_thread'):
406 cls.logger.debug("Waiting for pump thread to stop")
407 cls.pump_thread.join()
408 if hasattr(cls, 'vpp_stderr_reader_thread'):
409 cls.logger.debug("Waiting for stdderr pump to stop")
410 cls.vpp_stderr_reader_thread.join()
412 if hasattr(cls, 'vpp'):
413 if hasattr(cls, 'vapi'):
414 cls.vapi.disconnect()
417 if cls.vpp.returncode is None:
418 cls.logger.debug("Sending TERM to vpp")
420 cls.logger.debug("Waiting for vpp to die")
421 cls.vpp.communicate()
424 if cls.vpp_startup_failed:
425 stdout_log = cls.logger.info
426 stderr_log = cls.logger.critical
428 stdout_log = cls.logger.info
429 stderr_log = cls.logger.info
431 if hasattr(cls, 'vpp_stdout_deque'):
432 stdout_log(single_line_delim)
433 stdout_log('VPP output to stdout while running %s:', cls.__name__)
434 stdout_log(single_line_delim)
435 vpp_output = "".join(cls.vpp_stdout_deque)
436 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
438 stdout_log('\n%s', vpp_output)
439 stdout_log(single_line_delim)
441 if hasattr(cls, 'vpp_stderr_deque'):
442 stderr_log(single_line_delim)
443 stderr_log('VPP output to stderr while running %s:', cls.__name__)
444 stderr_log(single_line_delim)
445 vpp_output = "".join(cls.vpp_stderr_deque)
446 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
448 stderr_log('\n%s', vpp_output)
449 stderr_log(single_line_delim)
452 def tearDownClass(cls):
453 """ Perform final cleanup after running all tests in this test-case """
455 cls.file_handler.close()
458 """ Show various debug prints after each test """
459 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
460 (self.__class__.__name__, self._testMethodName,
461 self._testMethodDoc))
462 if not self.vpp_dead:
463 self.logger.debug(self.vapi.cli("show trace"))
464 self.logger.info(self.vapi.ppcli("show interface"))
465 self.logger.info(self.vapi.ppcli("show hardware"))
466 self.logger.info(self.vapi.ppcli("show error"))
467 self.logger.info(self.vapi.ppcli("show run"))
468 self.registry.remove_vpp_config(self.logger)
469 # Save/Dump VPP api trace log
470 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
471 tmp_api_trace = "/tmp/%s" % api_trace
472 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
473 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
474 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
476 os.rename(tmp_api_trace, vpp_api_trace_log)
477 self.logger.info(self.vapi.ppcli("api trace dump %s" %
480 self.registry.unregister_all(self.logger)
483 """ Clear trace before running each test"""
484 self.reporter.send_keep_alive(self)
485 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
486 (self.__class__.__name__, self._testMethodName,
487 self._testMethodDoc))
489 raise Exception("VPP is dead when setting up the test")
490 self.sleep(.1, "during setUp")
491 self.vpp_stdout_deque.append(
492 "--- test setUp() for %s.%s(%s) starts here ---\n" %
493 (self.__class__.__name__, self._testMethodName,
494 self._testMethodDoc))
495 self.vpp_stderr_deque.append(
496 "--- test setUp() for %s.%s(%s) starts here ---\n" %
497 (self.__class__.__name__, self._testMethodName,
498 self._testMethodDoc))
499 self.vapi.cli("clear trace")
500 # store the test instance inside the test class - so that objects
501 # holding the class can access instance methods (like assertEqual)
502 type(self).test_instance = self
505 def pg_enable_capture(cls, interfaces=None):
507 Enable capture on packet-generator interfaces
509 :param interfaces: iterable interface indexes (if None,
510 use self.pg_interfaces)
513 if interfaces is None:
514 interfaces = cls.pg_interfaces
519 def register_capture(cls, cap_name):
520 """ Register a capture in the testclass """
521 # add to the list of captures with current timestamp
522 cls._captures.append((time.time(), cap_name))
523 # filter out from zombies
524 cls._zombie_captures = [(stamp, name)
525 for (stamp, name) in cls._zombie_captures
530 """ Remove any zombie captures and enable the packet generator """
531 # how long before capture is allowed to be deleted - otherwise vpp
532 # crashes - 100ms seems enough (this shouldn't be needed at all)
535 for stamp, cap_name in cls._zombie_captures:
536 wait = stamp + capture_ttl - now
538 cls.sleep(wait, "before deleting capture %s" % cap_name)
540 cls.logger.debug("Removing zombie capture %s" % cap_name)
541 cls.vapi.cli('packet-generator delete %s' % cap_name)
543 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
544 cls.vapi.cli('packet-generator enable')
545 cls._zombie_captures = cls._captures
549 def create_pg_interfaces(cls, interfaces):
551 Create packet-generator interfaces.
553 :param interfaces: iterable indexes of the interfaces.
554 :returns: List of created interfaces.
559 intf = VppPGInterface(cls, i)
560 setattr(cls, intf.name, intf)
562 cls.pg_interfaces = result
566 def create_loopback_interfaces(cls, interfaces):
568 Create loopback interfaces.
570 :param interfaces: iterable indexes of the interfaces.
571 :returns: List of created interfaces.
575 intf = VppLoInterface(cls, i)
576 setattr(cls, intf.name, intf)
578 cls.lo_interfaces = result
582 def extend_packet(packet, size, padding=' '):
584 Extend packet to given size by padding with spaces or custom padding
585 NOTE: Currently works only when Raw layer is present.
587 :param packet: packet
588 :param size: target size
589 :param padding: padding used to extend the payload
592 packet_len = len(packet) + 4
593 extend = size - packet_len
595 num = (extend / len(padding)) + 1
596 packet[Raw].load += (padding * num)[:extend]
599 def reset_packet_infos(cls):
600 """ Reset the list of packet info objects and packet counts to zero """
601 cls._packet_infos = {}
602 cls._packet_count_for_dst_if_idx = {}
605 def create_packet_info(cls, src_if, dst_if):
607 Create packet info object containing the source and destination indexes
608 and add it to the testcase's packet info list
610 :param VppInterface src_if: source interface
611 :param VppInterface dst_if: destination interface
613 :returns: _PacketInfo object
617 info.index = len(cls._packet_infos)
618 info.src = src_if.sw_if_index
619 info.dst = dst_if.sw_if_index
620 if isinstance(dst_if, VppSubInterface):
621 dst_idx = dst_if.parent.sw_if_index
623 dst_idx = dst_if.sw_if_index
624 if dst_idx in cls._packet_count_for_dst_if_idx:
625 cls._packet_count_for_dst_if_idx[dst_idx] += 1
627 cls._packet_count_for_dst_if_idx[dst_idx] = 1
628 cls._packet_infos[info.index] = info
632 def info_to_payload(info):
634 Convert _PacketInfo object to packet payload
636 :param info: _PacketInfo object
638 :returns: string containing serialized data from packet info
640 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
644 def payload_to_info(payload):
646 Convert packet payload to _PacketInfo object
648 :param payload: packet payload
650 :returns: _PacketInfo object containing de-serialized data from payload
653 numbers = payload.split()
655 info.index = int(numbers[0])
656 info.src = int(numbers[1])
657 info.dst = int(numbers[2])
658 info.ip = int(numbers[3])
659 info.proto = int(numbers[4])
662 def get_next_packet_info(self, info):
664 Iterate over the packet info list stored in the testcase
665 Start iteration with first element if info is None
666 Continue based on index in info if info is specified
668 :param info: info or None
669 :returns: next info in list or None if no more infos
674 next_index = info.index + 1
675 if next_index == len(self._packet_infos):
678 return self._packet_infos[next_index]
680 def get_next_packet_info_for_interface(self, src_index, info):
682 Search the packet info list for the next packet info with same source
685 :param src_index: source interface index to search for
686 :param info: packet info - where to start the search
687 :returns: packet info or None
691 info = self.get_next_packet_info(info)
694 if info.src == src_index:
697 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
699 Search the packet info list for the next packet info with same source
700 and destination interface indexes
702 :param src_index: source interface index to search for
703 :param dst_index: destination interface index to search for
704 :param info: packet info - where to start the search
705 :returns: packet info or None
709 info = self.get_next_packet_info_for_interface(src_index, info)
712 if info.dst == dst_index:
715 def assert_equal(self, real_value, expected_value, name_or_class=None):
716 if name_or_class is None:
717 self.assertEqual(real_value, expected_value)
720 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
721 msg = msg % (getdoc(name_or_class).strip(),
722 real_value, str(name_or_class(real_value)),
723 expected_value, str(name_or_class(expected_value)))
725 msg = "Invalid %s: %s does not match expected value %s" % (
726 name_or_class, real_value, expected_value)
728 self.assertEqual(real_value, expected_value, msg)
730 def assert_in_range(self,
738 msg = "Invalid %s: %s out of range <%s,%s>" % (
739 name, real_value, expected_min, expected_max)
740 self.assertTrue(expected_min <= real_value <= expected_max, msg)
743 def sleep(cls, timeout, remark=None):
744 if hasattr(cls, 'logger'):
745 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
749 if after - before > 2 * timeout:
750 cls.logger.error("unexpected time.sleep() result - "
751 "slept for %ss instead of ~%ss!" % (
752 after - before, timeout))
753 if hasattr(cls, 'logger'):
755 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
756 remark, after - before, timeout))
758 def send_and_assert_no_replies(self, intf, pkts, remark=""):
759 self.vapi.cli("clear trace")
760 intf.add_stream(pkts)
761 self.pg_enable_capture(self.pg_interfaces)
764 for i in self.pg_interfaces:
765 i.get_capture(0, timeout=timeout)
766 i.assert_nothing_captured(remark=remark)
769 def send_and_expect(self, input, pkts, output):
770 self.vapi.cli("clear trace")
771 input.add_stream(pkts)
772 self.pg_enable_capture(self.pg_interfaces)
774 rx = output.get_capture(len(pkts))
778 class TestCasePrinter(object):
782 self.__dict__ = self._shared_state
783 if not hasattr(self, "_test_case_set"):
784 self._test_case_set = set()
786 def print_test_case_heading_if_first_time(self, case):
787 if case.__class__ not in self._test_case_set:
788 print(double_line_delim)
789 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
790 print(double_line_delim)
791 self._test_case_set.add(case.__class__)
794 class VppTestResult(unittest.TestResult):
796 @property result_string
797 String variable to store the test case result string.
799 List variable containing 2-tuples of TestCase instances and strings
800 holding formatted tracebacks. Each tuple represents a test which
801 raised an unexpected exception.
803 List variable containing 2-tuples of TestCase instances and strings
804 holding formatted tracebacks. Each tuple represents a test where
805 a failure was explicitly signalled using the TestCase.assert*()
809 def __init__(self, stream, descriptions, verbosity):
811 :param stream File descriptor to store where to report test results.
812 Set to the standard error stream by default.
813 :param descriptions Boolean variable to store information if to use
814 test case descriptions.
815 :param verbosity Integer variable to store required verbosity level.
817 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
819 self.descriptions = descriptions
820 self.verbosity = verbosity
821 self.result_string = None
822 self.printer = TestCasePrinter()
824 def addSuccess(self, test):
826 Record a test succeeded result
831 if hasattr(test, 'logger'):
832 test.logger.debug("--- addSuccess() %s.%s(%s) called"
833 % (test.__class__.__name__,
834 test._testMethodName,
835 test._testMethodDoc))
836 unittest.TestResult.addSuccess(self, test)
837 self.result_string = colorize("OK", GREEN)
839 def addSkip(self, test, reason):
841 Record a test skipped.
847 if hasattr(test, 'logger'):
848 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
849 % (test.__class__.__name__,
850 test._testMethodName,
853 unittest.TestResult.addSkip(self, test, reason)
854 self.result_string = colorize("SKIP", YELLOW)
856 def symlink_failed(self, test):
858 if hasattr(test, 'logger'):
860 if hasattr(test, 'tempdir'):
862 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
863 link_path = '%s/%s-FAILED' % (failed_dir,
864 test.tempdir.split("/")[-1])
866 logger.debug("creating a link to the failed test")
867 logger.debug("os.symlink(%s, %s)" %
868 (test.tempdir, link_path))
869 os.symlink(test.tempdir, link_path)
870 except Exception as e:
874 def send_failure_through_pipe(self, test):
875 if hasattr(self, 'test_framework_failed_pipe'):
876 pipe = self.test_framework_failed_pipe
878 pipe.send(test.__class__)
880 def addFailure(self, test, err):
882 Record a test failed result
885 :param err: error message
888 if hasattr(test, 'logger'):
889 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
890 % (test.__class__.__name__,
891 test._testMethodName,
892 test._testMethodDoc, err))
893 test.logger.debug("formatted exception is:\n%s" %
894 "".join(format_exception(*err)))
895 unittest.TestResult.addFailure(self, test, err)
896 if hasattr(test, 'tempdir'):
897 self.result_string = colorize("FAIL", RED) + \
898 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
899 self.symlink_failed(test)
901 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
903 self.send_failure_through_pipe(test)
905 def addError(self, test, err):
907 Record a test error result
910 :param err: error message
913 if hasattr(test, 'logger'):
914 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
915 % (test.__class__.__name__,
916 test._testMethodName,
917 test._testMethodDoc, err))
918 test.logger.debug("formatted exception is:\n%s" %
919 "".join(format_exception(*err)))
920 unittest.TestResult.addError(self, test, err)
921 if hasattr(test, 'tempdir'):
922 self.result_string = colorize("ERROR", RED) + \
923 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
924 self.symlink_failed(test)
926 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
928 self.send_failure_through_pipe(test)
930 def getDescription(self, test):
935 :returns: test description
938 # TODO: if none print warning not raise exception
939 short_description = test.shortDescription()
940 if self.descriptions and short_description:
941 return short_description
945 def startTest(self, test):
952 self.printer.print_test_case_heading_if_first_time(test)
953 unittest.TestResult.startTest(self, test)
954 if self.verbosity > 0:
956 "Starting " + self.getDescription(test) + " ...")
957 self.stream.writeln(single_line_delim)
959 def stopTest(self, test):
966 unittest.TestResult.stopTest(self, test)
967 if self.verbosity > 0:
968 self.stream.writeln(single_line_delim)
969 self.stream.writeln("%-73s%s" % (self.getDescription(test),
971 self.stream.writeln(single_line_delim)
973 self.stream.writeln("%-73s%s" % (self.getDescription(test),
976 def printErrors(self):
978 Print errors from running the test case
980 self.stream.writeln()
981 self.printErrorList('ERROR', self.errors)
982 self.printErrorList('FAIL', self.failures)
984 def printErrorList(self, flavour, errors):
986 Print error list to the output stream together with error type
987 and test case description.
989 :param flavour: error type
990 :param errors: iterable errors
993 for test, err in errors:
994 self.stream.writeln(double_line_delim)
995 self.stream.writeln("%s: %s" %
996 (flavour, self.getDescription(test)))
997 self.stream.writeln(single_line_delim)
998 self.stream.writeln("%s" % err)
1001 class Filter_by_test_option:
1002 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
1003 self.filter_file_name = filter_file_name
1004 self.filter_class_name = filter_class_name
1005 self.filter_func_name = filter_func_name
1007 def __call__(self, file_name, class_name, func_name):
1008 if self.filter_file_name and file_name != self.filter_file_name:
1010 if self.filter_class_name and class_name != self.filter_class_name:
1012 if self.filter_func_name and func_name != self.filter_func_name:
1017 class VppTestRunner(unittest.TextTestRunner):
1019 A basic test runner implementation which prints results to standard error.
1022 def resultclass(self):
1023 """Class maintaining the results of the tests"""
1024 return VppTestResult
1026 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1027 stream=sys.stderr, descriptions=True,
1028 verbosity=1, failfast=False, buffer=False, resultclass=None):
1029 # ignore stream setting here, use hard-coded stdout to be in sync
1030 # with prints from VppTestCase methods ...
1031 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1032 verbosity, failfast, buffer,
1034 reporter = KeepAliveReporter()
1035 reporter.pipe = keep_alive_pipe
1036 # this is super-ugly, but very simple to implement and works as long
1037 # as we run only one test at the same time
1038 VppTestResult.test_framework_failed_pipe = failed_pipe
1040 test_option = "TEST"
1042 def parse_test_option(self):
1044 f = os.getenv(self.test_option)
1047 filter_file_name = None
1048 filter_class_name = None
1049 filter_func_name = None
1052 parts = f.split('.')
1054 raise Exception("Unrecognized %s option: %s" %
1055 (self.test_option, f))
1057 if parts[2] not in ('*', ''):
1058 filter_func_name = parts[2]
1059 if parts[1] not in ('*', ''):
1060 filter_class_name = parts[1]
1061 if parts[0] not in ('*', ''):
1062 if parts[0].startswith('test_'):
1063 filter_file_name = parts[0]
1065 filter_file_name = 'test_%s' % parts[0]
1067 if f.startswith('test_'):
1068 filter_file_name = f
1070 filter_file_name = 'test_%s' % f
1071 return filter_file_name, filter_class_name, filter_func_name
1074 def filter_tests(tests, filter_cb):
1075 result = unittest.suite.TestSuite()
1077 if isinstance(t, unittest.suite.TestSuite):
1078 # this is a bunch of tests, recursively filter...
1079 x = filter_tests(t, filter_cb)
1080 if x.countTestCases() > 0:
1082 elif isinstance(t, unittest.TestCase):
1083 # this is a single test
1084 parts = t.id().split('.')
1085 # t.id() for common cases like this:
1086 # test_classifier.TestClassifier.test_acl_ip
1087 # apply filtering only if it is so
1089 if not filter_cb(parts[0], parts[1], parts[2]):
1093 # unexpected object, don't touch it
1097 def run(self, test):
1104 faulthandler.enable() # emit stack trace to stderr if killed by signal
1105 print("Running tests using custom test runner") # debug message
1106 filter_file, filter_class, filter_func = self.parse_test_option()
1107 print("Active filters: file=%s, class=%s, function=%s" % (
1108 filter_file, filter_class, filter_func))
1109 filter_cb = Filter_by_test_option(
1110 filter_file, filter_class, filter_func)
1111 filtered = self.filter_tests(test, filter_cb)
1112 print("%s out of %s tests match specified filters" % (
1113 filtered.countTestCases(), test.countTestCases()))
1114 if not running_extended_tests():
1115 print("Not running extended tests (some tests will be skipped)")
1116 return super(VppTestRunner, self).run(filtered)
1119 class Worker(Thread):
1120 def __init__(self, args, logger):
1121 self.logger = logger
1124 super(Worker, self).__init__()
1127 executable = self.args[0]
1128 self.logger.debug("Running executable w/args `%s'" % self.args)
1129 env = os.environ.copy()
1130 env["CK_LOG_FILE_NAME"] = "-"
1131 self.process = subprocess.Popen(
1132 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1133 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1134 out, err = self.process.communicate()
1135 self.logger.debug("Finished running `%s'" % executable)
1136 self.logger.info("Return code is `%s'" % self.process.returncode)
1137 self.logger.info(single_line_delim)
1138 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1139 self.logger.info(single_line_delim)
1140 self.logger.info(out)
1141 self.logger.info(single_line_delim)
1142 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1143 self.logger.info(single_line_delim)
1144 self.logger.error(err)
1145 self.logger.info(single_line_delim)
1146 self.result = self.process.returncode