3 from __future__ import print_function
18 from collections import deque
19 from threading import Thread, Event
20 from inspect import getdoc, isclass
21 from traceback import format_exception
22 from logging import FileHandler, DEBUG, Formatter
24 from abc import ABC, abstractmethod
25 from struct import pack, unpack
27 from config import config, available_cpus, num_cpus, max_vpp_cpus
28 import hook as hookmodule
29 from vpp_pg_interface import VppPGInterface
30 from vpp_sub_interface import VppSubInterface
31 from vpp_lo_interface import VppLoInterface
32 from vpp_bvi_interface import VppBviInterface
33 from vpp_papi_provider import VppPapiProvider
34 from vpp_papi import VppEnum
36 from vpp_papi.vpp_stats import VPPStats
37 from vpp_papi.vpp_transport_socket import VppTransportSocketIOError
47 from vpp_object import VppObjectRegistry
48 from util import ppp, is_core_present
49 from test_result_code import TestResultCode
51 logger = logging.getLogger(__name__)
53 # Set up an empty logger for the testcase that can be overridden as necessary
54 null_logger = logging.getLogger("VppTestCase")
55 null_logger.addHandler(logging.NullHandler())
58 if config.debug_framework:
62 Test framework module.
64 The module provides a set of tools for constructing and running tests and
65 representing the results.
69 class VppDiedError(Exception):
70 """exception for reporting that the subprocess has died."""
74 for k, v in signal.__dict__.items()
75 if k.startswith("SIG") and not k.startswith("SIG_")
78 def __init__(self, rv=None, testcase=None, method_name=None):
80 self.signal_name = None
81 self.testcase = testcase
82 self.method_name = method_name
85 self.signal_name = VppDiedError.signals_by_value[-rv]
86 except (KeyError, TypeError):
89 if testcase is None and method_name is None:
92 in_msg = " while running %s.%s" % (testcase, method_name)
95 msg = "VPP subprocess died unexpectedly%s with return code: %d%s." % (
98 " [%s]" % (self.signal_name if self.signal_name is not None else ""),
101 msg = "VPP subprocess died unexpectedly%s." % in_msg
103 super(VppDiedError, self).__init__(msg)
106 class _PacketInfo(object):
107 """Private class to create packet info object.
109 Help process information about the next packet.
110 Set variables to default values.
113 #: Store the index of the packet.
115 #: Store the index of the source packet generator interface of the packet.
117 #: Store the index of the destination packet generator interface
120 #: Store expected ip version
122 #: Store expected upper protocol
124 #: Store the copy of the former packet.
127 def __eq__(self, other):
128 index = self.index == other.index
129 src = self.src == other.src
130 dst = self.dst == other.dst
131 data = self.data == other.data
132 return index and src and dst and data
135 def pump_output(testclass):
136 """pump output from vpp stdout/stderr to proper queues"""
139 while not testclass.pump_thread_stop_flag.is_set():
140 readable = select.select(
142 testclass.vpp.stdout.fileno(),
143 testclass.vpp.stderr.fileno(),
144 testclass.pump_thread_wakeup_pipe[0],
149 if testclass.vpp.stdout.fileno() in readable:
150 read = os.read(testclass.vpp.stdout.fileno(), 102400)
152 split = read.decode("ascii", errors="backslashreplace").splitlines(True)
153 if len(stdout_fragment) > 0:
154 split[0] = "%s%s" % (stdout_fragment, split[0])
155 if len(split) > 0 and split[-1].endswith("\n"):
159 stdout_fragment = split[-1]
160 testclass.vpp_stdout_deque.extend(split[:limit])
161 if not config.cache_vpp_output:
162 for line in split[:limit]:
163 testclass.logger.info("VPP STDOUT: %s" % line.rstrip("\n"))
164 if testclass.vpp.stderr.fileno() in readable:
165 read = os.read(testclass.vpp.stderr.fileno(), 102400)
167 split = read.decode("ascii", errors="backslashreplace").splitlines(True)
168 if len(stderr_fragment) > 0:
169 split[0] = "%s%s" % (stderr_fragment, split[0])
170 if len(split) > 0 and split[-1].endswith("\n"):
174 stderr_fragment = split[-1]
176 testclass.vpp_stderr_deque.extend(split[:limit])
177 if not config.cache_vpp_output:
178 for line in split[:limit]:
179 testclass.logger.error("VPP STDERR: %s" % line.rstrip("\n"))
180 # ignoring the dummy pipe here intentionally - the
181 # flag will take care of properly terminating the loop
184 def _is_platform_aarch64():
185 return platform.machine() == "aarch64"
188 is_platform_aarch64 = _is_platform_aarch64()
191 class KeepAliveReporter(object):
193 Singleton object which reports test start to parent process
199 self.__dict__ = self._shared_state
207 def pipe(self, pipe):
208 if self._pipe is not None:
209 raise Exception("Internal error - pipe should only be set once.")
212 def send_keep_alive(self, test, desc=None):
214 Write current test tmpdir & desc to keep-alive pipe to signal liveness
216 if self.pipe is None:
217 # if not running forked..
221 desc = "%s (%s)" % (desc, unittest.util.strclass(test))
225 self.pipe.send((desc, config.vpp, test.tempdir, test.vpp.pid))
228 class TestCaseTag(Enum):
229 # marks the suites that must run at the end
230 # using only a single test runner
232 # marks the suites broken on VPP multi-worker
233 FIXME_VPP_WORKERS = 2
234 # marks the suites broken when ASan is enabled
238 def create_tag_decorator(e):
241 cls.test_tags.append(e)
242 except AttributeError:
249 tag_run_solo = create_tag_decorator(TestCaseTag.RUN_SOLO)
250 tag_fixme_vpp_workers = create_tag_decorator(TestCaseTag.FIXME_VPP_WORKERS)
251 tag_fixme_asan = create_tag_decorator(TestCaseTag.FIXME_ASAN)
265 class CPUInterface(ABC):
267 skipped_due_to_cpu_lack = False
271 def get_cpus_required(cls):
275 def assign_cpus(cls, cpus):
279 class VppTestCase(CPUInterface, unittest.TestCase):
280 """This subclass is a base class for VPP test cases that are implemented as
281 classes. It provides methods to create and run test case.
284 extra_vpp_statseg_config = ""
285 extra_vpp_config = []
286 extra_vpp_plugin_config = []
288 vapi_response_timeout = 5
289 remove_configured_vpp_objects_on_tear_down = True
292 def packet_infos(self):
293 """List of packet infos"""
294 return self._packet_infos
297 def get_packet_count_for_if_idx(cls, dst_if_index):
298 """Get the number of packet info for specified destination if index"""
299 if dst_if_index in cls._packet_count_for_dst_if_idx:
300 return cls._packet_count_for_dst_if_idx[dst_if_index]
305 def has_tag(cls, tag):
306 """if the test case has a given tag - return true"""
308 return tag in cls.test_tags
309 except AttributeError:
314 def is_tagged_run_solo(cls):
315 """if the test case class is timing-sensitive - return true"""
316 return cls.has_tag(TestCaseTag.RUN_SOLO)
319 def skip_fixme_asan(cls):
320 """if @tag_fixme_asan & ASan is enabled - mark for skip"""
321 if cls.has_tag(TestCaseTag.FIXME_ASAN):
322 vpp_extra_cmake_args = os.environ.get("VPP_EXTRA_CMAKE_ARGS", "")
323 if "DVPP_ENABLE_SANITIZE_ADDR=ON" in vpp_extra_cmake_args:
324 cls = unittest.skip("Skipping @tag_fixme_asan tests")(cls)
328 """Return the instance of this testcase"""
329 return cls.test_instance
332 def set_debug_flags(cls, d):
333 cls.gdbserver_port = 7777
334 cls.debug_core = False
335 cls.debug_gdb = False
336 cls.debug_gdbserver = False
337 cls.debug_all = False
338 cls.debug_attach = False
343 cls.debug_core = True
344 elif dl == "gdb" or dl == "gdb-all":
346 elif dl == "gdbserver" or dl == "gdbserver-all":
347 cls.debug_gdbserver = True
349 cls.debug_attach = True
351 raise Exception("Unrecognized DEBUG option: '%s'" % d)
352 if dl == "gdb-all" or dl == "gdbserver-all":
356 def get_vpp_worker_count(cls):
357 if not hasattr(cls, "vpp_worker_count"):
358 if cls.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
359 cls.vpp_worker_count = 0
361 cls.vpp_worker_count = config.vpp_worker_count
362 return cls.vpp_worker_count
365 def get_cpus_required(cls):
366 return 1 + cls.get_vpp_worker_count()
369 def setUpConstants(cls):
370 """Set-up the test case class based on environment variables"""
371 cls.step = config.step
372 cls.plugin_path = ":".join(config.vpp_plugin_dir)
373 cls.test_plugin_path = ":".join(config.vpp_test_plugin_dir)
374 cls.extern_plugin_path = ":".join(config.extern_plugin_dir)
376 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
377 debug_cli = "cli-listen localhost:5002"
378 size = re.search(r"\d+[gG]", config.coredump_size)
380 coredump_size = f"coredump-size {config.coredump_size}".lower()
382 coredump_size = "coredump-size unlimited"
383 default_variant = config.variant
384 if default_variant is not None:
385 default_variant = "defaults { %s 100 }" % default_variant
389 api_fuzzing = config.api_fuzz
390 if api_fuzzing is None:
411 cls.get_api_segment_prefix(),
418 if cls.extern_plugin_path not in (None, ""):
419 cls.extra_vpp_plugin_config.append("add-path %s" % cls.extern_plugin_path)
420 if cls.get_vpp_worker_count():
421 cls.vpp_cmdline.extend(
422 ["corelist-workers", ",".join([str(x) for x in cls.cpus[1:]])]
424 cls.vpp_cmdline.extend(
435 cls.get_stats_sock_path(),
436 cls.extra_vpp_statseg_config,
441 cls.get_api_sock_path(),
462 "lisp_unittest_plugin.so",
467 "unittest_plugin.so",
472 + cls.extra_vpp_plugin_config
478 if cls.extra_vpp_config is not None:
479 cls.vpp_cmdline.extend(cls.extra_vpp_config)
481 if not cls.debug_attach:
482 cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline)
483 cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline))
486 def wait_for_enter(cls):
487 if cls.debug_gdbserver:
488 print(double_line_delim)
489 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
491 print(double_line_delim)
492 print("Spawned VPP with PID: %d" % cls.vpp.pid)
494 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
496 print(single_line_delim)
497 print("You can debug VPP using:")
498 if cls.debug_gdbserver:
500 f"sudo gdb {config.vpp} "
501 f"-ex 'target remote localhost:{cls.gdbserver_port}'"
504 "Now is the time to attach gdb by running the above "
505 "command, set up breakpoints etc., then resume VPP from "
506 "within gdb by issuing the 'continue' command"
508 cls.gdbserver_port += 1
510 print(f"sudo gdb {config.vpp} -ex 'attach {cls.vpp.pid}'")
512 "Now is the time to attach gdb by running the above "
513 "command and set up breakpoints etc., then resume VPP from"
514 " within gdb by issuing the 'continue' command"
516 print(single_line_delim)
517 input("Press ENTER to continue running the testcase...")
525 cls.logger.debug(f"Assigned cpus: {cls.cpus}")
526 cmdline = cls.vpp_cmdline
528 if cls.debug_gdbserver:
529 gdbserver = "/usr/bin/gdbserver"
530 if not os.path.isfile(gdbserver) or not os.access(gdbserver, os.X_OK):
532 "gdbserver binary '%s' does not exist or is "
533 "not executable" % gdbserver
538 "localhost:{port}".format(port=cls.gdbserver_port),
540 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
543 cls.vpp = subprocess.Popen(
544 cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE
546 except subprocess.CalledProcessError as e:
548 "Subprocess returned with non-0 return code: (%s)", e.returncode
553 "Subprocess returned with OS error: (%s) %s", e.errno, e.strerror
556 except Exception as e:
557 cls.logger.exception("Subprocess returned unexpected from %s:", cmdline)
563 def wait_for_coredump(cls):
564 corefile = cls.tempdir + "/core"
565 if os.path.isfile(corefile):
566 cls.logger.error("Waiting for coredump to complete: %s", corefile)
567 curr_size = os.path.getsize(corefile)
568 deadline = time.time() + 60
570 while time.time() < deadline:
573 curr_size = os.path.getsize(corefile)
574 if size == curr_size:
579 "Timed out waiting for coredump to complete: %s", corefile
582 cls.logger.error("Coredump complete: %s, size %d", corefile, curr_size)
585 def get_stats_sock_path(cls):
586 return "%s/stats.sock" % cls.tempdir
589 def get_api_sock_path(cls):
590 return "%s/api.sock" % cls.tempdir
593 def get_api_segment_prefix(cls):
594 return os.path.basename(cls.tempdir) # Only used for VAPI
597 def get_tempdir(cls):
599 tmpdir = f"{config.tmp_dir}/unittest-attach-gdb"
601 tmpdir = f"{config.tmp_dir}/vpp-unittest-{cls.__name__}"
602 if config.wipe_tmp_dir:
603 shutil.rmtree(tmpdir, ignore_errors=True)
608 def create_file_handler(cls):
609 if config.log_dir is None:
610 cls.file_handler = FileHandler(f"{cls.tempdir}/log.txt")
613 logdir = f"{config.log_dir}/vpp-unittest-{cls.__name__}"
614 if config.wipe_tmp_dir:
615 shutil.rmtree(logdir, ignore_errors=True)
617 cls.file_handler = FileHandler(f"{logdir}/log.txt")
622 Perform class setup before running the testcase
623 Remove shared memory files, start vpp and connect the vpp-api
625 super(VppTestCase, cls).setUpClass()
626 cls.logger = get_logger(cls.__name__)
627 random.seed(config.rnd_seed)
628 if hasattr(cls, "parallel_handler"):
629 cls.logger.addHandler(cls.parallel_handler)
630 cls.logger.propagate = False
631 cls.set_debug_flags(config.debug)
632 cls.tempdir = cls.get_tempdir()
633 cls.create_file_handler()
634 cls.file_handler.setFormatter(
635 Formatter(fmt="%(asctime)s,%(msecs)03d %(message)s", datefmt="%H:%M:%S")
637 cls.file_handler.setLevel(DEBUG)
638 cls.logger.addHandler(cls.file_handler)
639 cls.logger.debug("--- setUpClass() for %s called ---" % cls.__name__)
640 os.chdir(cls.tempdir)
642 "Temporary dir is %s, api socket is %s",
644 cls.get_api_sock_path(),
646 cls.logger.debug("Random seed is %s", config.rnd_seed)
648 cls.reset_packet_infos()
653 cls.registry = VppObjectRegistry()
654 cls.vpp_startup_failed = False
655 cls.reporter = KeepAliveReporter()
656 # need to catch exceptions here because if we raise, then the cleanup
657 # doesn't get called and we might end with a zombie vpp
663 cls.reporter.send_keep_alive(cls, "setUpClass")
664 VppTestResult.current_test_case_info = TestCaseInfo(
665 cls.logger, cls.tempdir, cls.vpp.pid, config.vpp
667 cls.vpp_stdout_deque = deque()
668 cls.vpp_stderr_deque = deque()
669 if not cls.debug_attach:
670 cls.pump_thread_stop_flag = Event()
671 cls.pump_thread_wakeup_pipe = os.pipe()
672 cls.pump_thread = Thread(target=pump_output, args=(cls,))
673 cls.pump_thread.daemon = True
674 cls.pump_thread.start()
675 if cls.debug_gdb or cls.debug_gdbserver or cls.debug_attach:
676 cls.vapi_response_timeout = 0
677 cls.vapi = VppPapiProvider(cls.__name__, cls, cls.vapi_response_timeout)
679 hook = hookmodule.StepHook(cls)
681 hook = hookmodule.PollHook(cls)
682 cls.vapi.register_hook(hook)
683 cls.statistics = VPPStats(socketname=cls.get_stats_sock_path())
687 cls.vpp_startup_failed = True
689 "VPP died shortly after startup, check the"
690 " output to standard error for possible cause"
695 except (vpp_papi.VPPIOError, Exception) as e:
696 cls.logger.debug("Exception connecting to vapi: %s" % e)
697 cls.vapi.disconnect()
699 if cls.debug_gdbserver:
702 "You're running VPP inside gdbserver but "
703 "VPP-API connection failed, did you forget "
704 "to 'continue' VPP from within gdb?",
710 last_line = cls.vapi.cli("show thread").split("\n")[-2]
711 cls.vpp_worker_count = int(last_line.split(" ")[0])
712 print("Detected VPP with %s workers." % cls.vpp_worker_count)
713 except vpp_papi.VPPRuntimeError as e:
714 cls.logger.debug("%s" % e)
717 except Exception as e:
718 cls.logger.debug("Exception connecting to VPP: %s" % e)
723 def _debug_quit(cls):
724 if cls.debug_gdbserver or cls.debug_gdb:
728 if cls.vpp.returncode is None:
730 print(double_line_delim)
731 print("VPP or GDB server is still running")
732 print(single_line_delim)
734 "When done debugging, press ENTER to kill the "
735 "process and finish running the testcase..."
737 except AttributeError:
743 Disconnect vpp-api, kill vpp and cleanup shared memory files
747 # first signal that we want to stop the pump thread, then wake it up
748 if hasattr(cls, "pump_thread_stop_flag"):
749 cls.pump_thread_stop_flag.set()
750 if hasattr(cls, "pump_thread_wakeup_pipe"):
751 os.write(cls.pump_thread_wakeup_pipe[1], b"ding dong wake up")
752 if hasattr(cls, "pump_thread"):
753 cls.logger.debug("Waiting for pump thread to stop")
754 cls.pump_thread.join()
755 if hasattr(cls, "vpp_stderr_reader_thread"):
756 cls.logger.debug("Waiting for stderr pump to stop")
757 cls.vpp_stderr_reader_thread.join()
759 if hasattr(cls, "vpp"):
760 if hasattr(cls, "vapi"):
761 cls.logger.debug(cls.vapi.vpp.get_stats())
762 cls.logger.debug("Disconnecting class vapi client on %s", cls.__name__)
763 cls.vapi.disconnect()
764 cls.logger.debug("Deleting class vapi attribute on %s", cls.__name__)
767 if not cls.debug_attach and cls.vpp.returncode is None:
768 cls.wait_for_coredump()
769 cls.logger.debug("Sending TERM to vpp")
771 cls.logger.debug("Waiting for vpp to die")
773 outs, errs = cls.vpp.communicate(timeout=5)
774 except subprocess.TimeoutExpired:
776 outs, errs = cls.vpp.communicate()
777 cls.logger.debug("Deleting class vpp attribute on %s", cls.__name__)
778 if not cls.debug_attach:
779 cls.vpp.stdout.close()
780 cls.vpp.stderr.close()
783 if cls.vpp_startup_failed:
784 stdout_log = cls.logger.info
785 stderr_log = cls.logger.critical
787 stdout_log = cls.logger.info
788 stderr_log = cls.logger.info
790 if hasattr(cls, "vpp_stdout_deque"):
791 stdout_log(single_line_delim)
792 stdout_log("VPP output to stdout while running %s:", cls.__name__)
793 stdout_log(single_line_delim)
794 vpp_output = "".join(cls.vpp_stdout_deque)
795 with open(cls.tempdir + "/vpp_stdout.txt", "w") as f:
797 stdout_log("\n%s", vpp_output)
798 stdout_log(single_line_delim)
800 if hasattr(cls, "vpp_stderr_deque"):
801 stderr_log(single_line_delim)
802 stderr_log("VPP output to stderr while running %s:", cls.__name__)
803 stderr_log(single_line_delim)
804 vpp_output = "".join(cls.vpp_stderr_deque)
805 with open(cls.tempdir + "/vpp_stderr.txt", "w") as f:
807 stderr_log("\n%s", vpp_output)
808 stderr_log(single_line_delim)
811 def tearDownClass(cls):
812 """Perform final cleanup after running all tests in this test-case"""
813 cls.logger.debug("--- tearDownClass() for %s called ---" % cls.__name__)
814 cls.reporter.send_keep_alive(cls, "tearDownClass")
816 cls.file_handler.close()
817 cls.reset_packet_infos()
818 if config.debug_framework:
819 debug_internal.on_tear_down_class(cls)
821 def show_commands_at_teardown(self):
822 """Allow subclass specific teardown logging additions."""
823 self.logger.info("--- No test specific show commands provided. ---")
826 """Show various debug prints after each test"""
828 "--- tearDown() for %s.%s(%s) called ---"
829 % (self.__class__.__name__, self._testMethodName, self._testMethodDoc)
833 if not self.vpp_dead:
834 self.logger.debug(self.vapi.cli("show trace max 1000"))
835 self.logger.info(self.vapi.ppcli("show interface"))
836 self.logger.info(self.vapi.ppcli("show hardware"))
837 self.logger.info(self.statistics.set_errors_str())
838 self.logger.info(self.vapi.ppcli("show run"))
839 self.logger.info(self.vapi.ppcli("show log"))
840 self.logger.info(self.vapi.ppcli("show bihash"))
841 self.logger.info("Logging testcase specific show commands.")
842 self.show_commands_at_teardown()
843 if self.remove_configured_vpp_objects_on_tear_down:
844 self.registry.remove_vpp_config(self.logger)
845 # Save/Dump VPP api trace log
846 m = self._testMethodName
847 api_trace = "vpp_api_trace.%s.%d.log" % (m, self.vpp.pid)
848 tmp_api_trace = "/tmp/%s" % api_trace
849 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
850 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
851 self.logger.info("Moving %s to %s\n" % (tmp_api_trace, vpp_api_trace_log))
852 shutil.move(tmp_api_trace, vpp_api_trace_log)
853 except VppTransportSocketIOError:
855 "VppTransportSocketIOError: Vpp dead. Cannot log show commands."
859 self.registry.unregister_all(self.logger)
862 """Clear trace before running each test"""
863 super(VppTestCase, self).setUp()
864 self.reporter.send_keep_alive(self)
868 testcase=self.__class__.__name__,
869 method_name=self._testMethodName,
871 self.sleep(0.1, "during setUp")
872 self.vpp_stdout_deque.append(
873 "--- test setUp() for %s.%s(%s) starts here ---\n"
874 % (self.__class__.__name__, self._testMethodName, self._testMethodDoc)
876 self.vpp_stderr_deque.append(
877 "--- test setUp() for %s.%s(%s) starts here ---\n"
878 % (self.__class__.__name__, self._testMethodName, self._testMethodDoc)
880 self.vapi.cli("clear trace")
881 # store the test instance inside the test class - so that objects
882 # holding the class can access instance methods (like assertEqual)
883 type(self).test_instance = self
886 def pg_enable_capture(cls, interfaces=None):
888 Enable capture on packet-generator interfaces
890 :param interfaces: iterable interface indexes (if None,
891 use self.pg_interfaces)
894 if interfaces is None:
895 interfaces = cls.pg_interfaces
900 def register_pcap(cls, intf, worker):
901 """Register a pcap in the testclass"""
902 # add to the list of captures with current timestamp
903 cls._pcaps.append((intf, worker))
906 def get_vpp_time(cls):
907 # processes e.g. "Time now 2.190522, Wed, 11 Mar 2020 17:29:54 GMT"
908 # returns float("2.190522")
909 timestr = cls.vapi.cli("show clock")
910 head, sep, tail = timestr.partition(",")
911 head, sep, tail = head.partition("Time now")
915 def sleep_on_vpp_time(cls, sec):
916 """Sleep according to time in VPP world"""
917 # On a busy system with many processes
918 # we might end up with VPP time being slower than real world
919 # So take that into account when waiting for VPP to do something
920 start_time = cls.get_vpp_time()
921 while cls.get_vpp_time() - start_time < sec:
925 def pg_start(cls, trace=True):
926 """Enable the PG, wait till it is done, then clean up"""
927 for intf, worker in cls._old_pcaps:
928 intf.handle_old_pcap_file(intf.get_in_path(worker), intf.in_history_counter)
931 cls.vapi.cli("clear trace")
932 cls.vapi.cli("trace add pg-input 1000")
933 cls.vapi.cli("packet-generator enable")
934 # PG, when starts, runs to completion -
935 # so let's avoid a race condition,
936 # and wait a little till it's done.
937 # Then clean it up - and then be gone.
938 deadline = time.time() + 300
939 while cls.vapi.cli("show packet-generator").find("Yes") != -1:
940 cls.sleep(0.01) # yield
941 if time.time() > deadline:
942 cls.logger.error("Timeout waiting for pg to stop")
944 for intf, worker in cls._pcaps:
945 cls.vapi.cli("packet-generator delete %s" % intf.get_cap_name(worker))
946 cls._old_pcaps = cls._pcaps
950 def create_pg_interfaces_internal(cls, interfaces, gso=0, gso_size=0, mode=None):
952 Create packet-generator interfaces.
954 :param interfaces: iterable indexes of the interfaces.
955 :returns: List of created interfaces.
960 intf = VppPGInterface(cls, i, gso, gso_size, mode)
961 setattr(cls, intf.name, intf)
963 cls.pg_interfaces = result
967 def create_pg_ip4_interfaces(cls, interfaces, gso=0, gso_size=0):
968 pgmode = VppEnum.vl_api_pg_interface_mode_t
969 return cls.create_pg_interfaces_internal(
970 interfaces, gso, gso_size, pgmode.PG_API_MODE_IP4
974 def create_pg_ip6_interfaces(cls, interfaces, gso=0, gso_size=0):
975 pgmode = VppEnum.vl_api_pg_interface_mode_t
976 return cls.create_pg_interfaces_internal(
977 interfaces, gso, gso_size, pgmode.PG_API_MODE_IP6
981 def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0):
982 pgmode = VppEnum.vl_api_pg_interface_mode_t
983 return cls.create_pg_interfaces_internal(
984 interfaces, gso, gso_size, pgmode.PG_API_MODE_ETHERNET
988 def create_pg_ethernet_interfaces(cls, interfaces, gso=0, gso_size=0):
989 pgmode = VppEnum.vl_api_pg_interface_mode_t
990 return cls.create_pg_interfaces_internal(
991 interfaces, gso, gso_size, pgmode.PG_API_MODE_ETHERNET
995 def create_loopback_interfaces(cls, count):
997 Create loopback interfaces.
999 :param count: number of interfaces created.
1000 :returns: List of created interfaces.
1002 result = [VppLoInterface(cls) for i in range(count)]
1004 setattr(cls, intf.name, intf)
1005 cls.lo_interfaces = result
1009 def create_bvi_interfaces(cls, count):
1011 Create BVI interfaces.
1013 :param count: number of interfaces created.
1014 :returns: List of created interfaces.
1016 result = [VppBviInterface(cls) for i in range(count)]
1018 setattr(cls, intf.name, intf)
1019 cls.bvi_interfaces = result
1023 def reset_packet_infos(cls):
1024 """Reset the list of packet info objects and packet counts to zero"""
1025 cls._packet_infos = {}
1026 cls._packet_count_for_dst_if_idx = {}
1029 def create_packet_info(cls, src_if, dst_if):
1031 Create packet info object containing the source and destination indexes
1032 and add it to the testcase's packet info list
1034 :param VppInterface src_if: source interface
1035 :param VppInterface dst_if: destination interface
1037 :returns: _PacketInfo object
1040 info = _PacketInfo()
1041 info.index = len(cls._packet_infos)
1042 info.src = src_if.sw_if_index
1043 info.dst = dst_if.sw_if_index
1044 if isinstance(dst_if, VppSubInterface):
1045 dst_idx = dst_if.parent.sw_if_index
1047 dst_idx = dst_if.sw_if_index
1048 if dst_idx in cls._packet_count_for_dst_if_idx:
1049 cls._packet_count_for_dst_if_idx[dst_idx] += 1
1051 cls._packet_count_for_dst_if_idx[dst_idx] = 1
1052 cls._packet_infos[info.index] = info
1056 def info_to_payload(info):
1058 Convert _PacketInfo object to packet payload
1060 :param info: _PacketInfo object
1062 :returns: string containing serialized data from packet info
1065 # retrieve payload, currently 18 bytes (4 x ints + 1 short)
1066 return pack("iiiih", info.index, info.src, info.dst, info.ip, info.proto)
1068 def get_next_packet_info(self, info):
1070 Iterate over the packet info list stored in the testcase
1071 Start iteration with first element if info is None
1072 Continue based on index in info if info is specified
1074 :param info: info or None
1075 :returns: next info in list or None if no more infos
1080 next_index = info.index + 1
1081 if next_index == len(self._packet_infos):
1084 return self._packet_infos[next_index]
1086 def get_next_packet_info_for_interface(self, src_index, info):
1088 Search the packet info list for the next packet info with same source
1091 :param src_index: source interface index to search for
1092 :param info: packet info - where to start the search
1093 :returns: packet info or None
1097 info = self.get_next_packet_info(info)
1100 if info.src == src_index:
1103 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
1105 Search the packet info list for the next packet info with same source
1106 and destination interface indexes
1108 :param src_index: source interface index to search for
1109 :param dst_index: destination interface index to search for
1110 :param info: packet info - where to start the search
1111 :returns: packet info or None
1115 info = self.get_next_packet_info_for_interface(src_index, info)
1118 if info.dst == dst_index:
1121 def assert_equal(self, real_value, expected_value, name_or_class=None):
1122 if name_or_class is None:
1123 self.assertEqual(real_value, expected_value)
1126 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
1128 getdoc(name_or_class).strip(),
1130 str(name_or_class(real_value)),
1132 str(name_or_class(expected_value)),
1135 msg = "Invalid %s: %s does not match expected value %s" % (
1141 self.assertEqual(real_value, expected_value, msg)
1143 def assert_in_range(self, real_value, expected_min, expected_max, name=None):
1147 msg = "Invalid %s: %s out of range <%s,%s>" % (
1153 self.assertTrue(expected_min <= real_value <= expected_max, msg)
1155 def assert_ip_checksum_valid(self, received_packet, ignore_zero_checksum=False):
1156 self.assert_checksum_valid(
1157 received_packet, "IP", ignore_zero_checksum=ignore_zero_checksum
1160 def assert_tcp_checksum_valid(self, received_packet, ignore_zero_checksum=False):
1161 self.assert_checksum_valid(
1162 received_packet, "TCP", ignore_zero_checksum=ignore_zero_checksum
1165 def assert_udp_checksum_valid(self, received_packet, ignore_zero_checksum=True):
1166 self.assert_checksum_valid(
1167 received_packet, "UDP", ignore_zero_checksum=ignore_zero_checksum
1170 def assert_icmp_checksum_valid(self, received_packet):
1171 self.assert_checksum_valid(received_packet, "ICMP")
1172 self.assert_embedded_icmp_checksum_valid(received_packet)
1174 def get_counter(self, counter):
1175 if counter.startswith("/"):
1176 counter_value = self.statistics.get_counter(counter)
1178 counters = self.vapi.cli("sh errors").split("\n")
1180 for i in range(1, len(counters) - 1):
1181 results = counters[i].split()
1182 if results[1] == counter:
1183 counter_value = int(results[0])
1185 return counter_value
1187 def assert_counter_equal(self, counter, expected_value, thread=None, index=0):
1188 c = self.get_counter(counter)
1189 if thread is not None:
1190 c = c[thread][index]
1192 c = sum(x[index] for x in c)
1194 "validate counter `%s[%s]', expected: %s, real value: %s"
1195 % (counter, index, expected_value, c)
1197 self.assert_equal(c, expected_value, "counter `%s[%s]'" % (counter, index))
1199 def assert_packet_counter_equal(self, counter, expected_value):
1200 counter_value = self.get_counter(counter)
1202 counter_value, expected_value, "packet counter `%s'" % counter
1205 def assert_error_counter_equal(self, counter, expected_value):
1206 counter_value = self.statistics[counter].sum()
1207 self.assert_equal(counter_value, expected_value, "error counter `%s'" % counter)
1210 def sleep(cls, timeout, remark=None):
1211 # /* Allow sleep(0) to maintain win32 semantics, and as decreed
1212 # * by Guido, only the main thread can be interrupted.
1214 # https://github.com/python/cpython/blob/6673decfa0fb078f60587f5cb5e98460eea137c2/Modules/timemodule.c#L1892 # noqa
1217 if hasattr(os, "sched_yield"):
1223 cls.logger.debug("Starting sleep for %es (%s)", timeout, remark)
1224 before = time.time()
1227 if after - before > 2 * timeout:
1229 "unexpected self.sleep() result - slept for %es instead of ~%es!",
1235 "Finished sleep (%s) - slept %es (wanted %es)",
1241 def virtual_sleep(self, timeout, remark=None):
1242 self.logger.debug("Moving VPP time by %s (%s)", timeout, remark)
1243 self.vapi.cli("set clock adjust %s" % timeout)
1245 def pg_send(self, intf, pkts, worker=None, trace=True):
1246 intf.add_stream(pkts, worker=worker)
1247 self.pg_enable_capture(self.pg_interfaces)
1248 self.pg_start(trace=trace)
1250 def snapshot_stats(self, stats_diff):
1251 """Return snapshot of interesting stats based on diff dictionary."""
1253 for sw_if_index in stats_diff:
1254 for counter in stats_diff[sw_if_index]:
1255 stats_snapshot[counter] = self.statistics[counter]
1256 self.logger.debug(f"Took statistics stats_snapshot: {stats_snapshot}")
1257 return stats_snapshot
1259 def compare_stats_with_snapshot(self, stats_diff, stats_snapshot):
1260 """Assert appropriate difference between current stats and snapshot."""
1261 for sw_if_index in stats_diff:
1262 for cntr, diff in stats_diff[sw_if_index].items():
1263 if sw_if_index == "err":
1265 self.statistics[cntr].sum(),
1266 stats_snapshot[cntr].sum() + diff,
1267 f"'{cntr}' counter value (previous value: "
1268 f"{stats_snapshot[cntr].sum()}, "
1269 f"expected diff: {diff})",
1274 self.statistics[cntr][:, sw_if_index].sum(),
1275 stats_snapshot[cntr][:, sw_if_index].sum() + diff,
1276 f"'{cntr}' counter value (previous value: "
1277 f"{stats_snapshot[cntr][:, sw_if_index].sum()}, "
1278 f"expected diff: {diff})",
1280 except IndexError as e:
1281 # if diff is 0, then this most probably a case where
1282 # test declares multiple interfaces but traffic hasn't
1283 # passed through this one yet - which means the counter
1284 # value is 0 and can be ignored
1287 f"Couldn't sum counter: {cntr} on sw_if_index: {sw_if_index}"
1290 def send_and_assert_no_replies(
1291 self, intf, pkts, remark="", timeout=None, stats_diff=None, trace=True, msg=None
1294 stats_snapshot = self.snapshot_stats(stats_diff)
1296 self.pg_send(intf, pkts)
1301 for i in self.pg_interfaces:
1302 i.assert_nothing_captured(timeout=timeout, remark=remark)
1307 self.logger.debug(f"send_and_assert_no_replies: {msg}")
1308 self.logger.debug(self.vapi.cli("show trace"))
1311 self.compare_stats_with_snapshot(stats_diff, stats_snapshot)
1313 def send_and_expect_load_balancing(
1314 self, input, pkts, outputs, worker=None, trace=True
1316 self.pg_send(input, pkts, worker=worker, trace=trace)
1319 rx = oo._get_capture(1)
1320 self.assertNotEqual(0, len(rx))
1323 self.logger.debug(self.vapi.cli("show trace"))
1326 def send_and_expect_some(self, intf, pkts, output, worker=None, trace=True):
1327 self.pg_send(intf, pkts, worker=worker, trace=trace)
1328 rx = output._get_capture(1)
1330 self.logger.debug(self.vapi.cli("show trace"))
1331 self.assertTrue(len(rx) > 0)
1332 self.assertTrue(len(rx) < len(pkts))
1335 def send_and_expect_only(self, intf, pkts, output, timeout=None, stats_diff=None):
1337 stats_snapshot = self.snapshot_stats(stats_diff)
1339 self.pg_send(intf, pkts)
1340 rx = output.get_capture(len(pkts))
1344 for i in self.pg_interfaces:
1345 if i not in outputs:
1346 i.assert_nothing_captured(timeout=timeout)
1350 self.compare_stats_with_snapshot(stats_diff, stats_snapshot)
1355 def get_testcase_doc_name(test):
1356 return getdoc(test.__class__).splitlines()[0]
1359 def get_test_description(descriptions, test):
1360 short_description = test.shortDescription()
1361 if descriptions and short_description:
1362 return short_description
1367 class TestCaseInfo(object):
1368 def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
1369 self.logger = logger
1370 self.tempdir = tempdir
1371 self.vpp_pid = vpp_pid
1372 self.vpp_bin_path = vpp_bin_path
1373 self.core_crash_test = None
1376 class VppTestResult(unittest.TestResult):
1378 @property result_string
1379 String variable to store the test case result string.
1381 List variable containing 2-tuples of TestCase instances and strings
1382 holding formatted tracebacks. Each tuple represents a test which
1383 raised an unexpected exception.
1385 List variable containing 2-tuples of TestCase instances and strings
1386 holding formatted tracebacks. Each tuple represents a test where
1387 a failure was explicitly signalled using the TestCase.assert*()
1391 failed_test_cases_info = set()
1392 core_crash_test_cases_info = set()
1393 current_test_case_info = None
1395 def __init__(self, stream=None, descriptions=None, verbosity=None, runner=None):
1397 :param stream File descriptor to store where to report test results.
1398 Set to the standard error stream by default.
1399 :param descriptions Boolean variable to store information if to use
1400 test case descriptions.
1401 :param verbosity Integer variable to store required verbosity level.
1403 super(VppTestResult, self).__init__(stream, descriptions, verbosity)
1404 self.stream = stream
1405 self.descriptions = descriptions
1406 self.verbosity = verbosity
1407 self.result_code = TestResultCode.TEST_RUN
1408 self.result_string = None
1409 self.runner = runner
1412 def addSuccess(self, test):
1414 Record a test succeeded result
1419 self.log_result("addSuccess", test)
1420 unittest.TestResult.addSuccess(self, test)
1421 self.result_string = colorize("OK", GREEN)
1422 self.result_code = TestResultCode.PASS
1423 self.send_result_through_pipe(test, self.result_code)
1425 def addExpectedFailure(self, test, err):
1426 self.log_result("addExpectedFailure", test, err)
1427 super().addExpectedFailure(test, err)
1428 self.result_string = colorize("FAIL", GREEN)
1429 self.result_code = TestResultCode.EXPECTED_FAIL
1430 self.send_result_through_pipe(test, self.result_code)
1432 def addUnexpectedSuccess(self, test):
1433 self.log_result("addUnexpectedSuccess", test)
1434 super().addUnexpectedSuccess(test)
1435 self.result_string = colorize("OK", RED)
1436 self.result_code = TestResultCode.UNEXPECTED_PASS
1437 self.send_result_through_pipe(test, self.result_code)
1439 def addSkip(self, test, reason):
1441 Record a test skipped.
1447 self.log_result("addSkip", test, reason=reason)
1448 unittest.TestResult.addSkip(self, test, reason)
1449 self.result_string = colorize("SKIP", YELLOW)
1451 if reason == "not enough cpus":
1452 self.result_code = TestResultCode.SKIP_CPU_SHORTAGE
1454 self.result_code = TestResultCode.SKIP
1455 self.send_result_through_pipe(test, self.result_code)
1457 def symlink_failed(self):
1458 if self.current_test_case_info:
1460 failed_dir = config.failed_dir
1461 link_path = os.path.join(
1463 "%s-FAILED" % os.path.basename(self.current_test_case_info.tempdir),
1466 self.current_test_case_info.logger.debug(
1467 "creating a link to the failed test"
1469 self.current_test_case_info.logger.debug(
1470 "os.symlink(%s, %s)"
1471 % (self.current_test_case_info.tempdir, link_path)
1473 if os.path.exists(link_path):
1474 self.current_test_case_info.logger.debug("symlink already exists")
1476 os.symlink(self.current_test_case_info.tempdir, link_path)
1478 except Exception as e:
1479 self.current_test_case_info.logger.error(e)
1481 def send_result_through_pipe(self, test, result):
1482 if hasattr(self, "test_framework_result_pipe"):
1483 pipe = self.test_framework_result_pipe
1485 pipe.send((test.id(), result))
1487 def log_result(self, fn, test, err=None, reason=None):
1488 if self.current_test_case_info:
1489 if isinstance(test, unittest.suite._ErrorHolder):
1490 test_name = test.description
1492 test_name = "%s.%s(%s)" % (
1493 test.__class__.__name__,
1494 test._testMethodName,
1495 test._testMethodDoc,
1499 extra_msg += f", error is {err}"
1501 extra_msg += f", reason is {reason}"
1502 self.current_test_case_info.logger.debug(
1503 f"--- {fn}() {test_name} called{extra_msg}"
1506 self.current_test_case_info.logger.debug(
1507 "formatted exception is:\n%s" % "".join(format_exception(*err))
1510 def add_error(self, test, err, unittest_fn, result_code):
1511 self.result_code = result_code
1512 if result_code == TestResultCode.FAIL:
1513 self.log_result("addFailure", test, err=err)
1514 error_type_str = colorize("FAIL", RED)
1515 elif result_code == TestResultCode.ERROR:
1516 self.log_result("addError", test, err=err)
1517 error_type_str = colorize("ERROR", RED)
1519 raise Exception(f"Unexpected result code {result_code}")
1521 unittest_fn(self, test, err)
1522 if self.current_test_case_info:
1523 self.result_string = "%s [ temp dir used by test case: %s ]" % (
1525 self.current_test_case_info.tempdir,
1527 self.symlink_failed()
1528 self.failed_test_cases_info.add(self.current_test_case_info)
1529 if is_core_present(self.current_test_case_info.tempdir):
1530 if not self.current_test_case_info.core_crash_test:
1531 if isinstance(test, unittest.suite._ErrorHolder):
1532 test_name = str(test)
1534 test_name = "'{!s}' ({!s})".format(
1535 get_testcase_doc_name(test), test.id()
1537 self.current_test_case_info.core_crash_test = test_name
1538 self.core_crash_test_cases_info.add(self.current_test_case_info)
1540 self.result_string = "%s [no temp dir]" % error_type_str
1542 self.send_result_through_pipe(test, result_code)
1544 def addFailure(self, test, err):
1546 Record a test failed result
1549 :param err: error message
1552 self.add_error(test, err, unittest.TestResult.addFailure, TestResultCode.FAIL)
1554 def addError(self, test, err):
1556 Record a test error result
1559 :param err: error message
1562 self.add_error(test, err, unittest.TestResult.addError, TestResultCode.ERROR)
1564 def getDescription(self, test):
1566 Get test description
1569 :returns: test description
1572 return get_test_description(self.descriptions, test)
1574 def startTest(self, test):
1582 def print_header(test):
1583 if test.__class__ in self.printed:
1586 test_doc = getdoc(test)
1588 raise Exception("No doc string for test '%s'" % test.id())
1590 test_title = test_doc.splitlines()[0].rstrip()
1591 test_title = colorize(test_title, GREEN)
1592 if test.is_tagged_run_solo():
1593 test_title = colorize(f"SOLO RUN: {test_title}", YELLOW)
1595 # This block may overwrite the colorized title above,
1596 # but we want this to stand out and be fixed
1597 if test.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
1598 test_title = colorize(f"FIXME with VPP workers: {test_title}", RED)
1600 if test.has_tag(TestCaseTag.FIXME_ASAN):
1601 test_title = colorize(f"FIXME with ASAN: {test_title}", RED)
1602 test.skip_fixme_asan()
1604 if hasattr(test, "vpp_worker_count"):
1605 if test.vpp_worker_count == 0:
1606 test_title += " [main thread only]"
1607 elif test.vpp_worker_count == 1:
1608 test_title += " [1 worker thread]"
1610 test_title += f" [{test.vpp_worker_count} worker threads]"
1612 if test.__class__.skipped_due_to_cpu_lack:
1613 test_title = colorize(
1614 f"{test_title} [skipped - not enough cpus, "
1615 f"required={test.__class__.get_cpus_required()}, "
1616 f"available={max_vpp_cpus}]",
1620 print(double_line_delim)
1622 print(double_line_delim)
1623 self.printed.append(test.__class__)
1626 self.start_test = time.time()
1627 unittest.TestResult.startTest(self, test)
1628 if self.verbosity > 0:
1629 self.stream.writeln("Starting " + self.getDescription(test) + " ...")
1630 self.stream.writeln(single_line_delim)
1632 def stopTest(self, test):
1634 Called when the given test has been run
1639 unittest.TestResult.stopTest(self, test)
1641 result_code_to_suffix = {
1642 TestResultCode.PASS: "",
1643 TestResultCode.FAIL: "",
1644 TestResultCode.ERROR: "",
1645 TestResultCode.SKIP: "",
1646 TestResultCode.TEST_RUN: "",
1647 TestResultCode.SKIP_CPU_SHORTAGE: "",
1648 TestResultCode.EXPECTED_FAIL: " [EXPECTED FAIL]",
1649 TestResultCode.UNEXPECTED_PASS: " [UNEXPECTED PASS]",
1652 if self.verbosity > 0:
1653 self.stream.writeln(single_line_delim)
1654 self.stream.writeln(
1657 self.getDescription(test),
1659 result_code_to_suffix[self.result_code],
1662 self.stream.writeln(single_line_delim)
1664 self.stream.writeln(
1667 self.getDescription(test),
1668 time.time() - self.start_test,
1670 result_code_to_suffix[self.result_code],
1674 self.send_result_through_pipe(test, TestResultCode.TEST_RUN)
1676 def printErrors(self):
1678 Print errors from running the test case
1680 if len(self.errors) > 0 or len(self.failures) > 0:
1681 self.stream.writeln()
1682 self.printErrorList("ERROR", self.errors)
1683 self.printErrorList("FAIL", self.failures)
1685 # ^^ that is the last output from unittest before summary
1686 if not self.runner.print_summary:
1687 devnull = unittest.runner._WritelnDecorator(open(os.devnull, "w"))
1688 self.stream = devnull
1689 self.runner.stream = devnull
1691 def printErrorList(self, flavour, errors):
1693 Print error list to the output stream together with error type
1694 and test case description.
1696 :param flavour: error type
1697 :param errors: iterable errors
1700 for test, err in errors:
1701 self.stream.writeln(double_line_delim)
1702 self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
1703 self.stream.writeln(single_line_delim)
1704 self.stream.writeln("%s" % err)
1707 class VppTestRunner(unittest.TextTestRunner):
1709 A basic test runner implementation which prints results to standard error.
1713 def resultclass(self):
1714 """Class maintaining the results of the tests"""
1715 return VppTestResult
1719 keep_alive_pipe=None,
1729 # ignore stream setting here, use hard-coded stdout to be in sync
1730 # with prints from VppTestCase methods ...
1731 super(VppTestRunner, self).__init__(
1732 sys.stdout, descriptions, verbosity, failfast, buffer, resultclass, **kwargs
1734 KeepAliveReporter.pipe = keep_alive_pipe
1736 self.orig_stream = self.stream
1737 self.resultclass.test_framework_result_pipe = result_pipe
1739 self.print_summary = print_summary
1741 def _makeResult(self):
1742 return self.resultclass(self.stream, self.descriptions, self.verbosity, self)
1744 def run(self, test):
1751 faulthandler.enable() # emit stack trace to stderr if killed by signal
1753 result = super(VppTestRunner, self).run(test)
1754 if not self.print_summary:
1755 self.stream = self.orig_stream
1756 result.stream = self.orig_stream
1760 class Worker(Thread):
1761 def __init__(self, executable_args, logger, env=None, *args, **kwargs):
1762 super(Worker, self).__init__(*args, **kwargs)
1763 self.logger = logger
1764 self.args = executable_args
1765 if hasattr(self, "testcase") and self.testcase.debug_all:
1766 if self.testcase.debug_gdbserver:
1768 "/usr/bin/gdbserver",
1769 "localhost:{port}".format(port=self.testcase.gdbserver_port),
1771 elif self.testcase.debug_gdb and hasattr(self, "wait_for_gdb"):
1772 self.args.append(self.wait_for_gdb)
1773 self.app_bin = executable_args[0]
1774 self.app_name = os.path.basename(self.app_bin)
1775 if hasattr(self, "role"):
1776 self.app_name += " {role}".format(role=self.role)
1779 env = {} if env is None else env
1780 self.env = copy.deepcopy(env)
1782 def wait_for_enter(self):
1783 if not hasattr(self, "testcase"):
1785 if self.testcase.debug_all and self.testcase.debug_gdbserver:
1787 print(double_line_delim)
1789 "Spawned GDB Server for '{app}' with PID: {pid}".format(
1790 app=self.app_name, pid=self.process.pid
1793 elif self.testcase.debug_all and self.testcase.debug_gdb:
1795 print(double_line_delim)
1797 "Spawned '{app}' with PID: {pid}".format(
1798 app=self.app_name, pid=self.process.pid
1803 print(single_line_delim)
1804 print("You can debug '{app}' using:".format(app=self.app_name))
1805 if self.testcase.debug_gdbserver:
1809 + " -ex 'target remote localhost:{port}'".format(
1810 port=self.testcase.gdbserver_port
1814 "Now is the time to attach gdb by running the above "
1815 "command, set up breakpoints etc., then resume from "
1816 "within gdb by issuing the 'continue' command"
1818 self.testcase.gdbserver_port += 1
1819 elif self.testcase.debug_gdb:
1823 + " -ex 'attach {pid}'".format(pid=self.process.pid)
1826 "Now is the time to attach gdb by running the above "
1827 "command and set up breakpoints etc., then resume from"
1828 " within gdb by issuing the 'continue' command"
1830 print(single_line_delim)
1831 input("Press ENTER to continue running the testcase...")
1834 executable = self.args[0]
1835 if not os.path.exists(executable) or not os.access(
1836 executable, os.F_OK | os.X_OK
1838 # Exit code that means some system file did not exist,
1839 # could not be opened, or had some other kind of error.
1840 self.result = os.EX_OSFILE
1841 raise EnvironmentError(
1842 "executable '%s' is not found or executable." % executable
1845 "Running executable '{app}': '{cmd}'".format(
1846 app=self.app_name, cmd=" ".join(self.args)
1849 env = os.environ.copy()
1850 env.update(self.env)
1851 env["CK_LOG_FILE_NAME"] = "-"
1852 self.process = subprocess.Popen(
1853 ["stdbuf", "-o0", "-e0"] + self.args,
1856 preexec_fn=os.setpgrp,
1857 stdout=subprocess.PIPE,
1858 stderr=subprocess.PIPE,
1860 self.wait_for_enter()
1861 out, err = self.process.communicate()
1862 self.logger.debug("Finished running `{app}'".format(app=self.app_name))
1863 self.logger.info("Return code is `%s'" % self.process.returncode)
1864 self.logger.info(single_line_delim)
1866 "Executable `{app}' wrote to stdout:".format(app=self.app_name)
1868 self.logger.info(single_line_delim)
1869 self.logger.info(out.decode("utf-8"))
1870 self.logger.info(single_line_delim)
1872 "Executable `{app}' wrote to stderr:".format(app=self.app_name)
1874 self.logger.info(single_line_delim)
1875 self.logger.info(err.decode("utf-8"))
1876 self.logger.info(single_line_delim)
1877 self.result = self.process.returncode
1880 if __name__ == "__main__":