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)
1193 self.assert_equal(c, expected_value, "counter `%s'" % counter)
1195 def assert_packet_counter_equal(self, counter, expected_value):
1196 counter_value = self.get_counter(counter)
1198 counter_value, expected_value, "packet counter `%s'" % counter
1201 def assert_error_counter_equal(self, counter, expected_value):
1202 counter_value = self.statistics[counter].sum()
1203 self.assert_equal(counter_value, expected_value, "error counter `%s'" % counter)
1206 def sleep(cls, timeout, remark=None):
1208 # /* Allow sleep(0) to maintain win32 semantics, and as decreed
1209 # * by Guido, only the main thread can be interrupted.
1211 # https://github.com/python/cpython/blob/6673decfa0fb078f60587f5cb5e98460eea137c2/Modules/timemodule.c#L1892 # noqa
1214 if hasattr(os, "sched_yield"):
1220 cls.logger.debug("Starting sleep for %es (%s)", timeout, remark)
1221 before = time.time()
1224 if after - before > 2 * timeout:
1226 "unexpected self.sleep() result - slept for %es instead of ~%es!",
1232 "Finished sleep (%s) - slept %es (wanted %es)",
1238 def virtual_sleep(self, timeout, remark=None):
1239 self.logger.debug("Moving VPP time by %s (%s)", timeout, remark)
1240 self.vapi.cli("set clock adjust %s" % timeout)
1242 def pg_send(self, intf, pkts, worker=None, trace=True):
1243 intf.add_stream(pkts, worker=worker)
1244 self.pg_enable_capture(self.pg_interfaces)
1245 self.pg_start(trace=trace)
1247 def snapshot_stats(self, stats_diff):
1248 """Return snapshot of interesting stats based on diff dictionary."""
1250 for sw_if_index in stats_diff:
1251 for counter in stats_diff[sw_if_index]:
1252 stats_snapshot[counter] = self.statistics[counter]
1253 self.logger.debug(f"Took statistics stats_snapshot: {stats_snapshot}")
1254 return stats_snapshot
1256 def compare_stats_with_snapshot(self, stats_diff, stats_snapshot):
1257 """Assert appropriate difference between current stats and snapshot."""
1258 for sw_if_index in stats_diff:
1259 for cntr, diff in stats_diff[sw_if_index].items():
1260 if sw_if_index == "err":
1262 self.statistics[cntr].sum(),
1263 stats_snapshot[cntr].sum() + diff,
1264 f"'{cntr}' counter value (previous value: "
1265 f"{stats_snapshot[cntr].sum()}, "
1266 f"expected diff: {diff})",
1271 self.statistics[cntr][:, sw_if_index].sum(),
1272 stats_snapshot[cntr][:, sw_if_index].sum() + diff,
1273 f"'{cntr}' counter value (previous value: "
1274 f"{stats_snapshot[cntr][:, sw_if_index].sum()}, "
1275 f"expected diff: {diff})",
1277 except IndexError as e:
1278 # if diff is 0, then this most probably a case where
1279 # test declares multiple interfaces but traffic hasn't
1280 # passed through this one yet - which means the counter
1281 # value is 0 and can be ignored
1284 f"Couldn't sum counter: {cntr} on sw_if_index: {sw_if_index}"
1287 def send_and_assert_no_replies(
1288 self, intf, pkts, remark="", timeout=None, stats_diff=None, trace=True, msg=None
1291 stats_snapshot = self.snapshot_stats(stats_diff)
1293 self.pg_send(intf, pkts)
1298 for i in self.pg_interfaces:
1299 i.assert_nothing_captured(timeout=timeout, remark=remark)
1304 self.logger.debug(f"send_and_assert_no_replies: {msg}")
1305 self.logger.debug(self.vapi.cli("show trace"))
1308 self.compare_stats_with_snapshot(stats_diff, stats_snapshot)
1310 def send_and_expect_load_balancing(
1311 self, input, pkts, outputs, worker=None, trace=True
1313 self.pg_send(input, pkts, worker=worker, trace=trace)
1316 rx = oo._get_capture(1)
1317 self.assertNotEqual(0, len(rx))
1320 self.logger.debug(self.vapi.cli("show trace"))
1323 def send_and_expect_some(self, intf, pkts, output, worker=None, trace=True):
1324 self.pg_send(intf, pkts, worker=worker, trace=trace)
1325 rx = output._get_capture(1)
1327 self.logger.debug(self.vapi.cli("show trace"))
1328 self.assertTrue(len(rx) > 0)
1329 self.assertTrue(len(rx) < len(pkts))
1332 def send_and_expect_only(self, intf, pkts, output, timeout=None, stats_diff=None):
1334 stats_snapshot = self.snapshot_stats(stats_diff)
1336 self.pg_send(intf, pkts)
1337 rx = output.get_capture(len(pkts))
1341 for i in self.pg_interfaces:
1342 if i not in outputs:
1343 i.assert_nothing_captured(timeout=timeout)
1347 self.compare_stats_with_snapshot(stats_diff, stats_snapshot)
1352 def get_testcase_doc_name(test):
1353 return getdoc(test.__class__).splitlines()[0]
1356 def get_test_description(descriptions, test):
1357 short_description = test.shortDescription()
1358 if descriptions and short_description:
1359 return short_description
1364 class TestCaseInfo(object):
1365 def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
1366 self.logger = logger
1367 self.tempdir = tempdir
1368 self.vpp_pid = vpp_pid
1369 self.vpp_bin_path = vpp_bin_path
1370 self.core_crash_test = None
1373 class VppTestResult(unittest.TestResult):
1375 @property result_string
1376 String variable to store the test case result string.
1378 List variable containing 2-tuples of TestCase instances and strings
1379 holding formatted tracebacks. Each tuple represents a test which
1380 raised an unexpected exception.
1382 List variable containing 2-tuples of TestCase instances and strings
1383 holding formatted tracebacks. Each tuple represents a test where
1384 a failure was explicitly signalled using the TestCase.assert*()
1388 failed_test_cases_info = set()
1389 core_crash_test_cases_info = set()
1390 current_test_case_info = None
1392 def __init__(self, stream=None, descriptions=None, verbosity=None, runner=None):
1394 :param stream File descriptor to store where to report test results.
1395 Set to the standard error stream by default.
1396 :param descriptions Boolean variable to store information if to use
1397 test case descriptions.
1398 :param verbosity Integer variable to store required verbosity level.
1400 super(VppTestResult, self).__init__(stream, descriptions, verbosity)
1401 self.stream = stream
1402 self.descriptions = descriptions
1403 self.verbosity = verbosity
1404 self.result_code = TestResultCode.TEST_RUN
1405 self.result_string = None
1406 self.runner = runner
1409 def addSuccess(self, test):
1411 Record a test succeeded result
1416 self.log_result("addSuccess", test)
1417 unittest.TestResult.addSuccess(self, test)
1418 self.result_string = colorize("OK", GREEN)
1419 self.result_code = TestResultCode.PASS
1420 self.send_result_through_pipe(test, self.result_code)
1422 def addExpectedFailure(self, test, err):
1423 self.log_result("addExpectedFailure", test, err)
1424 super().addExpectedFailure(test, err)
1425 self.result_string = colorize("FAIL", GREEN)
1426 self.result_code = TestResultCode.EXPECTED_FAIL
1427 self.send_result_through_pipe(test, self.result_code)
1429 def addUnexpectedSuccess(self, test):
1430 self.log_result("addUnexpectedSuccess", test)
1431 super().addUnexpectedSuccess(test)
1432 self.result_string = colorize("OK", RED)
1433 self.result_code = TestResultCode.UNEXPECTED_PASS
1434 self.send_result_through_pipe(test, self.result_code)
1436 def addSkip(self, test, reason):
1438 Record a test skipped.
1444 self.log_result("addSkip", test, reason=reason)
1445 unittest.TestResult.addSkip(self, test, reason)
1446 self.result_string = colorize("SKIP", YELLOW)
1448 if reason == "not enough cpus":
1449 self.result_code = TestResultCode.SKIP_CPU_SHORTAGE
1451 self.result_code = TestResultCode.SKIP
1452 self.send_result_through_pipe(test, self.result_code)
1454 def symlink_failed(self):
1455 if self.current_test_case_info:
1457 failed_dir = config.failed_dir
1458 link_path = os.path.join(
1460 "%s-FAILED" % os.path.basename(self.current_test_case_info.tempdir),
1463 self.current_test_case_info.logger.debug(
1464 "creating a link to the failed test"
1466 self.current_test_case_info.logger.debug(
1467 "os.symlink(%s, %s)"
1468 % (self.current_test_case_info.tempdir, link_path)
1470 if os.path.exists(link_path):
1471 self.current_test_case_info.logger.debug("symlink already exists")
1473 os.symlink(self.current_test_case_info.tempdir, link_path)
1475 except Exception as e:
1476 self.current_test_case_info.logger.error(e)
1478 def send_result_through_pipe(self, test, result):
1479 if hasattr(self, "test_framework_result_pipe"):
1480 pipe = self.test_framework_result_pipe
1482 pipe.send((test.id(), result))
1484 def log_result(self, fn, test, err=None, reason=None):
1485 if self.current_test_case_info:
1486 if isinstance(test, unittest.suite._ErrorHolder):
1487 test_name = test.description
1489 test_name = "%s.%s(%s)" % (
1490 test.__class__.__name__,
1491 test._testMethodName,
1492 test._testMethodDoc,
1496 extra_msg += f", error is {err}"
1498 extra_msg += f", reason is {reason}"
1499 self.current_test_case_info.logger.debug(
1500 f"--- {fn}() {test_name} called{extra_msg}"
1503 self.current_test_case_info.logger.debug(
1504 "formatted exception is:\n%s" % "".join(format_exception(*err))
1507 def add_error(self, test, err, unittest_fn, result_code):
1508 self.result_code = result_code
1509 if result_code == TestResultCode.FAIL:
1510 self.log_result("addFailure", test, err=err)
1511 error_type_str = colorize("FAIL", RED)
1512 elif result_code == TestResultCode.ERROR:
1513 self.log_result("addError", test, err=err)
1514 error_type_str = colorize("ERROR", RED)
1516 raise Exception(f"Unexpected result code {result_code}")
1518 unittest_fn(self, test, err)
1519 if self.current_test_case_info:
1520 self.result_string = "%s [ temp dir used by test case: %s ]" % (
1522 self.current_test_case_info.tempdir,
1524 self.symlink_failed()
1525 self.failed_test_cases_info.add(self.current_test_case_info)
1526 if is_core_present(self.current_test_case_info.tempdir):
1527 if not self.current_test_case_info.core_crash_test:
1528 if isinstance(test, unittest.suite._ErrorHolder):
1529 test_name = str(test)
1531 test_name = "'{!s}' ({!s})".format(
1532 get_testcase_doc_name(test), test.id()
1534 self.current_test_case_info.core_crash_test = test_name
1535 self.core_crash_test_cases_info.add(self.current_test_case_info)
1537 self.result_string = "%s [no temp dir]" % error_type_str
1539 self.send_result_through_pipe(test, result_code)
1541 def addFailure(self, test, err):
1543 Record a test failed result
1546 :param err: error message
1549 self.add_error(test, err, unittest.TestResult.addFailure, TestResultCode.FAIL)
1551 def addError(self, test, err):
1553 Record a test error result
1556 :param err: error message
1559 self.add_error(test, err, unittest.TestResult.addError, TestResultCode.ERROR)
1561 def getDescription(self, test):
1563 Get test description
1566 :returns: test description
1569 return get_test_description(self.descriptions, test)
1571 def startTest(self, test):
1579 def print_header(test):
1580 if test.__class__ in self.printed:
1583 test_doc = getdoc(test)
1585 raise Exception("No doc string for test '%s'" % test.id())
1587 test_title = test_doc.splitlines()[0].rstrip()
1588 test_title = colorize(test_title, GREEN)
1589 if test.is_tagged_run_solo():
1590 test_title = colorize(f"SOLO RUN: {test_title}", YELLOW)
1592 # This block may overwrite the colorized title above,
1593 # but we want this to stand out and be fixed
1594 if test.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
1595 test_title = colorize(f"FIXME with VPP workers: {test_title}", RED)
1597 if test.has_tag(TestCaseTag.FIXME_ASAN):
1598 test_title = colorize(f"FIXME with ASAN: {test_title}", RED)
1599 test.skip_fixme_asan()
1601 if hasattr(test, "vpp_worker_count"):
1602 if test.vpp_worker_count == 0:
1603 test_title += " [main thread only]"
1604 elif test.vpp_worker_count == 1:
1605 test_title += " [1 worker thread]"
1607 test_title += f" [{test.vpp_worker_count} worker threads]"
1609 if test.__class__.skipped_due_to_cpu_lack:
1610 test_title = colorize(
1611 f"{test_title} [skipped - not enough cpus, "
1612 f"required={test.__class__.get_cpus_required()}, "
1613 f"available={max_vpp_cpus}]",
1617 print(double_line_delim)
1619 print(double_line_delim)
1620 self.printed.append(test.__class__)
1623 self.start_test = time.time()
1624 unittest.TestResult.startTest(self, test)
1625 if self.verbosity > 0:
1626 self.stream.writeln("Starting " + self.getDescription(test) + " ...")
1627 self.stream.writeln(single_line_delim)
1629 def stopTest(self, test):
1631 Called when the given test has been run
1636 unittest.TestResult.stopTest(self, test)
1638 result_code_to_suffix = {
1639 TestResultCode.PASS: "",
1640 TestResultCode.FAIL: "",
1641 TestResultCode.ERROR: "",
1642 TestResultCode.SKIP: "",
1643 TestResultCode.TEST_RUN: "",
1644 TestResultCode.SKIP_CPU_SHORTAGE: "",
1645 TestResultCode.EXPECTED_FAIL: " [EXPECTED FAIL]",
1646 TestResultCode.UNEXPECTED_PASS: " [UNEXPECTED PASS]",
1649 if self.verbosity > 0:
1650 self.stream.writeln(single_line_delim)
1651 self.stream.writeln(
1654 self.getDescription(test),
1656 result_code_to_suffix[self.result_code],
1659 self.stream.writeln(single_line_delim)
1661 self.stream.writeln(
1664 self.getDescription(test),
1665 time.time() - self.start_test,
1667 result_code_to_suffix[self.result_code],
1671 self.send_result_through_pipe(test, TestResultCode.TEST_RUN)
1673 def printErrors(self):
1675 Print errors from running the test case
1677 if len(self.errors) > 0 or len(self.failures) > 0:
1678 self.stream.writeln()
1679 self.printErrorList("ERROR", self.errors)
1680 self.printErrorList("FAIL", self.failures)
1682 # ^^ that is the last output from unittest before summary
1683 if not self.runner.print_summary:
1684 devnull = unittest.runner._WritelnDecorator(open(os.devnull, "w"))
1685 self.stream = devnull
1686 self.runner.stream = devnull
1688 def printErrorList(self, flavour, errors):
1690 Print error list to the output stream together with error type
1691 and test case description.
1693 :param flavour: error type
1694 :param errors: iterable errors
1697 for test, err in errors:
1698 self.stream.writeln(double_line_delim)
1699 self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
1700 self.stream.writeln(single_line_delim)
1701 self.stream.writeln("%s" % err)
1704 class VppTestRunner(unittest.TextTestRunner):
1706 A basic test runner implementation which prints results to standard error.
1710 def resultclass(self):
1711 """Class maintaining the results of the tests"""
1712 return VppTestResult
1716 keep_alive_pipe=None,
1726 # ignore stream setting here, use hard-coded stdout to be in sync
1727 # with prints from VppTestCase methods ...
1728 super(VppTestRunner, self).__init__(
1729 sys.stdout, descriptions, verbosity, failfast, buffer, resultclass, **kwargs
1731 KeepAliveReporter.pipe = keep_alive_pipe
1733 self.orig_stream = self.stream
1734 self.resultclass.test_framework_result_pipe = result_pipe
1736 self.print_summary = print_summary
1738 def _makeResult(self):
1739 return self.resultclass(self.stream, self.descriptions, self.verbosity, self)
1741 def run(self, test):
1748 faulthandler.enable() # emit stack trace to stderr if killed by signal
1750 result = super(VppTestRunner, self).run(test)
1751 if not self.print_summary:
1752 self.stream = self.orig_stream
1753 result.stream = self.orig_stream
1757 class Worker(Thread):
1758 def __init__(self, executable_args, logger, env=None, *args, **kwargs):
1759 super(Worker, self).__init__(*args, **kwargs)
1760 self.logger = logger
1761 self.args = executable_args
1762 if hasattr(self, "testcase") and self.testcase.debug_all:
1763 if self.testcase.debug_gdbserver:
1765 "/usr/bin/gdbserver",
1766 "localhost:{port}".format(port=self.testcase.gdbserver_port),
1768 elif self.testcase.debug_gdb and hasattr(self, "wait_for_gdb"):
1769 self.args.append(self.wait_for_gdb)
1770 self.app_bin = executable_args[0]
1771 self.app_name = os.path.basename(self.app_bin)
1772 if hasattr(self, "role"):
1773 self.app_name += " {role}".format(role=self.role)
1776 env = {} if env is None else env
1777 self.env = copy.deepcopy(env)
1779 def wait_for_enter(self):
1780 if not hasattr(self, "testcase"):
1782 if self.testcase.debug_all and self.testcase.debug_gdbserver:
1784 print(double_line_delim)
1786 "Spawned GDB Server for '{app}' with PID: {pid}".format(
1787 app=self.app_name, pid=self.process.pid
1790 elif self.testcase.debug_all and self.testcase.debug_gdb:
1792 print(double_line_delim)
1794 "Spawned '{app}' with PID: {pid}".format(
1795 app=self.app_name, pid=self.process.pid
1800 print(single_line_delim)
1801 print("You can debug '{app}' using:".format(app=self.app_name))
1802 if self.testcase.debug_gdbserver:
1806 + " -ex 'target remote localhost:{port}'".format(
1807 port=self.testcase.gdbserver_port
1811 "Now is the time to attach gdb by running the above "
1812 "command, set up breakpoints etc., then resume from "
1813 "within gdb by issuing the 'continue' command"
1815 self.testcase.gdbserver_port += 1
1816 elif self.testcase.debug_gdb:
1820 + " -ex 'attach {pid}'".format(pid=self.process.pid)
1823 "Now is the time to attach gdb by running the above "
1824 "command and set up breakpoints etc., then resume from"
1825 " within gdb by issuing the 'continue' command"
1827 print(single_line_delim)
1828 input("Press ENTER to continue running the testcase...")
1831 executable = self.args[0]
1832 if not os.path.exists(executable) or not os.access(
1833 executable, os.F_OK | os.X_OK
1835 # Exit code that means some system file did not exist,
1836 # could not be opened, or had some other kind of error.
1837 self.result = os.EX_OSFILE
1838 raise EnvironmentError(
1839 "executable '%s' is not found or executable." % executable
1842 "Running executable '{app}': '{cmd}'".format(
1843 app=self.app_name, cmd=" ".join(self.args)
1846 env = os.environ.copy()
1847 env.update(self.env)
1848 env["CK_LOG_FILE_NAME"] = "-"
1849 self.process = subprocess.Popen(
1850 ["stdbuf", "-o0", "-e0"] + self.args,
1853 preexec_fn=os.setpgrp,
1854 stdout=subprocess.PIPE,
1855 stderr=subprocess.PIPE,
1857 self.wait_for_enter()
1858 out, err = self.process.communicate()
1859 self.logger.debug("Finished running `{app}'".format(app=self.app_name))
1860 self.logger.info("Return code is `%s'" % self.process.returncode)
1861 self.logger.info(single_line_delim)
1863 "Executable `{app}' wrote to stdout:".format(app=self.app_name)
1865 self.logger.info(single_line_delim)
1866 self.logger.info(out.decode("utf-8"))
1867 self.logger.info(single_line_delim)
1869 "Executable `{app}' wrote to stderr:".format(app=self.app_name)
1871 self.logger.info(single_line_delim)
1872 self.logger.info(err.decode("utf-8"))
1873 self.logger.info(single_line_delim)
1874 self.result = self.process.returncode
1877 if __name__ == "__main__":