3 from __future__ import print_function
19 from collections import deque
20 from threading import Thread, Event
21 from inspect import getdoc, isclass
22 from traceback import format_exception
23 from logging import FileHandler, DEBUG, Formatter
27 from scapy.packet import Raw
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
35 from vpp_papi.vpp_stats import VPPStats
36 from vpp_papi.vpp_transport_socket import VppTransportSocketIOError
37 from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
39 from vpp_object import VppObjectRegistry
40 from util import ppp, is_core_present
41 from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
42 from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
43 from scapy.layers.inet6 import ICMPv6EchoReply
45 logger = logging.getLogger(__name__)
47 # Set up an empty logger for the testcase that can be overridden as necessary
48 null_logger = logging.getLogger('VppTestCase')
49 null_logger.addHandler(logging.NullHandler())
58 class BoolEnvironmentVariable(object):
60 def __init__(self, env_var_name, default='n', true_values=None):
61 self.name = env_var_name
62 self.default = default
63 self.true_values = true_values if true_values is not None else \
67 return os.getenv(self.name, self.default).lower() in self.true_values
69 if sys.version_info[0] == 2:
70 __nonzero__ = __bool__
73 return 'BoolEnvironmentVariable(%r, default=%r, true_values=%r)' % \
74 (self.name, self.default, self.true_values)
77 debug_framework = BoolEnvironmentVariable('TEST_DEBUG')
82 Test framework module.
84 The module provides a set of tools for constructing and running tests and
85 representing the results.
89 class VppDiedError(Exception):
90 """ exception for reporting that the subprocess has died."""
92 signals_by_value = {v: k for k, v in signal.__dict__.items() if
93 k.startswith('SIG') and not k.startswith('SIG_')}
95 def __init__(self, rv=None, testcase=None, method_name=None):
97 self.signal_name = None
98 self.testcase = testcase
99 self.method_name = method_name
102 self.signal_name = VppDiedError.signals_by_value[-rv]
103 except (KeyError, TypeError):
106 if testcase is None and method_name is None:
109 in_msg = ' while running %s.%s' % (testcase, method_name)
112 msg = "VPP subprocess died unexpectedly%s with return code: %d%s."\
113 % (in_msg, self.rv, ' [%s]' %
115 self.signal_name is not None else ''))
117 msg = "VPP subprocess died unexpectedly%s." % in_msg
119 super(VppDiedError, self).__init__(msg)
122 class _PacketInfo(object):
123 """Private class to create packet info object.
125 Help process information about the next packet.
126 Set variables to default values.
128 #: Store the index of the packet.
130 #: Store the index of the source packet generator interface of the packet.
132 #: Store the index of the destination packet generator interface
135 #: Store expected ip version
137 #: Store expected upper protocol
139 #: Store the copy of the former packet.
142 def __eq__(self, other):
143 index = self.index == other.index
144 src = self.src == other.src
145 dst = self.dst == other.dst
146 data = self.data == other.data
147 return index and src and dst and data
150 def pump_output(testclass):
151 """ pump output from vpp stdout/stderr to proper queues """
154 while not testclass.pump_thread_stop_flag.is_set():
155 readable = select.select([testclass.vpp.stdout.fileno(),
156 testclass.vpp.stderr.fileno(),
157 testclass.pump_thread_wakeup_pipe[0]],
159 if testclass.vpp.stdout.fileno() in readable:
160 read = os.read(testclass.vpp.stdout.fileno(), 102400)
162 split = read.decode('ascii',
163 errors='backslashreplace').splitlines(True)
164 if len(stdout_fragment) > 0:
165 split[0] = "%s%s" % (stdout_fragment, split[0])
166 if len(split) > 0 and split[-1].endswith("\n"):
170 stdout_fragment = split[-1]
171 testclass.vpp_stdout_deque.extend(split[:limit])
172 if not testclass.cache_vpp_output:
173 for line in split[:limit]:
174 testclass.logger.info(
175 "VPP STDOUT: %s" % line.rstrip("\n"))
176 if testclass.vpp.stderr.fileno() in readable:
177 read = os.read(testclass.vpp.stderr.fileno(), 102400)
179 split = read.decode('ascii',
180 errors='backslashreplace').splitlines(True)
181 if len(stderr_fragment) > 0:
182 split[0] = "%s%s" % (stderr_fragment, split[0])
183 if len(split) > 0 and split[-1].endswith("\n"):
187 stderr_fragment = split[-1]
189 testclass.vpp_stderr_deque.extend(split[:limit])
190 if not testclass.cache_vpp_output:
191 for line in split[:limit]:
192 testclass.logger.error(
193 "VPP STDERR: %s" % line.rstrip("\n"))
194 # ignoring the dummy pipe here intentionally - the
195 # flag will take care of properly terminating the loop
198 def _is_skip_aarch64_set():
199 return BoolEnvironmentVariable('SKIP_AARCH64')
202 is_skip_aarch64_set = _is_skip_aarch64_set()
205 def _is_platform_aarch64():
206 return platform.machine() == 'aarch64'
209 is_platform_aarch64 = _is_platform_aarch64()
212 def _running_extended_tests():
213 return BoolEnvironmentVariable("EXTENDED_TESTS")
216 running_extended_tests = _running_extended_tests()
219 def _running_gcov_tests():
220 return BoolEnvironmentVariable("GCOV_TESTS")
223 running_gcov_tests = _running_gcov_tests()
226 class KeepAliveReporter(object):
228 Singleton object which reports test start to parent process
233 self.__dict__ = self._shared_state
241 def pipe(self, pipe):
242 if self._pipe is not None:
243 raise Exception("Internal error - pipe should only be set once.")
246 def send_keep_alive(self, test, desc=None):
248 Write current test tmpdir & desc to keep-alive pipe to signal liveness
250 if self.pipe is None:
251 # if not running forked..
255 desc = '%s (%s)' % (desc, unittest.util.strclass(test))
259 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
262 class TestCaseTag(Enum):
263 # marks the suites that must run at the end
264 # using only a single test runner
266 # marks the suites broken on VPP multi-worker
267 FIXME_VPP_WORKERS = 2
270 def create_tag_decorator(e):
273 cls.test_tags.append(e)
274 except AttributeError:
280 tag_run_solo = create_tag_decorator(TestCaseTag.RUN_SOLO)
281 tag_fixme_vpp_workers = create_tag_decorator(TestCaseTag.FIXME_VPP_WORKERS)
284 class VppTestCase(unittest.TestCase):
285 """This subclass is a base class for VPP test cases that are implemented as
286 classes. It provides methods to create and run test case.
289 extra_vpp_punt_config = []
290 extra_vpp_plugin_config = []
292 vapi_response_timeout = 5
295 def packet_infos(self):
296 """List of packet infos"""
297 return self._packet_infos
300 def get_packet_count_for_if_idx(cls, dst_if_index):
301 """Get the number of packet info for specified destination if index"""
302 if dst_if_index in cls._packet_count_for_dst_if_idx:
303 return cls._packet_count_for_dst_if_idx[dst_if_index]
308 def has_tag(cls, tag):
309 """ if the test case has a given tag - return true """
311 return tag in cls.test_tags
312 except AttributeError:
317 def is_tagged_run_solo(cls):
318 """ if the test case class is timing-sensitive - return true """
319 return cls.has_tag(TestCaseTag.RUN_SOLO)
323 """Return the instance of this testcase"""
324 return cls.test_instance
327 def set_debug_flags(cls, d):
328 cls.gdbserver_port = 7777
329 cls.debug_core = False
330 cls.debug_gdb = False
331 cls.debug_gdbserver = False
332 cls.debug_all = False
337 cls.debug_core = True
338 elif dl == "gdb" or dl == "gdb-all":
340 elif dl == "gdbserver" or dl == "gdbserver-all":
341 cls.debug_gdbserver = True
343 raise Exception("Unrecognized DEBUG option: '%s'" % d)
344 if dl == "gdb-all" or dl == "gdbserver-all":
348 def get_least_used_cpu():
349 cpu_usage_list = [set(range(psutil.cpu_count()))]
350 vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name'])
351 if 'vpp_main' == p.info['name']]
352 for vpp_process in vpp_processes:
353 for cpu_usage_set in cpu_usage_list:
355 cpu_num = vpp_process.cpu_num()
356 if cpu_num in cpu_usage_set:
357 cpu_usage_set_index = cpu_usage_list.index(
359 if cpu_usage_set_index == len(cpu_usage_list) - 1:
360 cpu_usage_list.append({cpu_num})
362 cpu_usage_list[cpu_usage_set_index + 1].add(
364 cpu_usage_set.remove(cpu_num)
366 except psutil.NoSuchProcess:
369 for cpu_usage_set in cpu_usage_list:
370 if len(cpu_usage_set) > 0:
371 min_usage_set = cpu_usage_set
374 return random.choice(tuple(min_usage_set))
377 def setUpConstants(cls):
378 """ Set-up the test case class based on environment variables """
379 cls.step = BoolEnvironmentVariable('STEP')
380 d = os.getenv("DEBUG", None)
381 # inverted case to handle '' == True
382 c = os.getenv("CACHE_OUTPUT", "1")
383 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
384 cls.set_debug_flags(d)
385 cls.vpp_bin = os.getenv('VPP_BIN', "vpp")
386 cls.plugin_path = os.getenv('VPP_PLUGIN_PATH')
387 cls.test_plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
388 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
390 if cls.plugin_path is not None:
391 if cls.extern_plugin_path is not None:
392 plugin_path = "%s:%s" % (
393 cls.plugin_path, cls.extern_plugin_path)
395 plugin_path = cls.plugin_path
396 elif cls.extern_plugin_path is not None:
397 plugin_path = cls.extern_plugin_path
399 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
400 debug_cli = "cli-listen localhost:5002"
402 size = os.getenv("COREDUMP_SIZE")
404 coredump_size = "coredump-size %s" % size
405 if coredump_size is None:
406 coredump_size = "coredump-size unlimited"
408 cpu_core_number = cls.get_least_used_cpu()
409 if not hasattr(cls, "worker_config"):
410 cls.worker_config = os.getenv("VPP_WORKER_CONFIG", "")
411 if cls.worker_config != "":
412 if cls.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
413 cls.worker_config = ""
415 default_variant = os.getenv("VARIANT")
416 if default_variant is not None:
417 default_variant = "defaults { %s 100 }" % default_variant
421 api_fuzzing = os.getenv("API_FUZZ")
422 if api_fuzzing is None:
425 cls.vpp_cmdline = [cls.vpp_bin, "unix",
426 "{", "nodaemon", debug_cli, "full-coredump",
427 coredump_size, "runtime-dir", cls.tempdir, "}",
428 "api-trace", "{", "on", "}", "api-segment", "{",
429 "prefix", cls.shm_prefix, "}", "cpu", "{",
430 "main-core", str(cpu_core_number),
431 cls.worker_config, "}",
432 "physmem", "{", "max-size", "32m", "}",
433 "statseg", "{", "socket-name", cls.stats_sock, "}",
434 "socksvr", "{", "socket-name", cls.api_sock, "}",
435 "node { ", default_variant, "}",
436 "api-fuzz {", api_fuzzing, "}",
438 "{", "plugin", "dpdk_plugin.so", "{", "disable",
439 "}", "plugin", "rdma_plugin.so", "{", "disable",
440 "}", "plugin", "lisp_unittest_plugin.so", "{",
442 "}", "plugin", "unittest_plugin.so", "{", "enable",
443 "}"] + cls.extra_vpp_plugin_config + ["}", ]
445 if cls.extra_vpp_punt_config is not None:
446 cls.vpp_cmdline.extend(cls.extra_vpp_punt_config)
447 if plugin_path is not None:
448 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
449 if cls.test_plugin_path is not None:
450 cls.vpp_cmdline.extend(["test_plugin_path", cls.test_plugin_path])
452 cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline)
453 cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline))
456 def wait_for_enter(cls):
457 if cls.debug_gdbserver:
458 print(double_line_delim)
459 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
461 print(double_line_delim)
462 print("Spawned VPP with PID: %d" % cls.vpp.pid)
464 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
466 print(single_line_delim)
467 print("You can debug VPP using:")
468 if cls.debug_gdbserver:
469 print("sudo gdb " + cls.vpp_bin +
470 " -ex 'target remote localhost:{port}'"
471 .format(port=cls.gdbserver_port))
472 print("Now is the time to attach gdb by running the above "
473 "command, set up breakpoints etc., then resume VPP from "
474 "within gdb by issuing the 'continue' command")
475 cls.gdbserver_port += 1
477 print("sudo gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
478 print("Now is the time to attach gdb by running the above "
479 "command and set up breakpoints etc., then resume VPP from"
480 " within gdb by issuing the 'continue' command")
481 print(single_line_delim)
482 input("Press ENTER to continue running the testcase...")
486 cmdline = cls.vpp_cmdline
488 if cls.debug_gdbserver:
489 gdbserver = '/usr/bin/gdbserver'
490 if not os.path.isfile(gdbserver) or \
491 not os.access(gdbserver, os.X_OK):
492 raise Exception("gdbserver binary '%s' does not exist or is "
493 "not executable" % gdbserver)
495 cmdline = [gdbserver, 'localhost:{port}'
496 .format(port=cls.gdbserver_port)] + cls.vpp_cmdline
497 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
500 cls.vpp = subprocess.Popen(cmdline,
501 stdout=subprocess.PIPE,
502 stderr=subprocess.PIPE)
503 except subprocess.CalledProcessError as e:
504 cls.logger.critical("Subprocess returned with non-0 return code: ("
508 cls.logger.critical("Subprocess returned with OS error: "
509 "(%s) %s", e.errno, e.strerror)
511 except Exception as e:
512 cls.logger.exception("Subprocess returned unexpected from "
519 def wait_for_coredump(cls):
520 corefile = cls.tempdir + "/core"
521 if os.path.isfile(corefile):
522 cls.logger.error("Waiting for coredump to complete: %s", corefile)
523 curr_size = os.path.getsize(corefile)
524 deadline = time.time() + 60
526 while time.time() < deadline:
529 curr_size = os.path.getsize(corefile)
530 if size == curr_size:
534 cls.logger.error("Timed out waiting for coredump to complete:"
537 cls.logger.error("Coredump complete: %s, size %d",
543 Perform class setup before running the testcase
544 Remove shared memory files, start vpp and connect the vpp-api
546 super(VppTestCase, cls).setUpClass()
547 gc.collect() # run garbage collection first
548 cls.logger = get_logger(cls.__name__)
549 seed = os.environ["RND_SEED"]
551 if hasattr(cls, 'parallel_handler'):
552 cls.logger.addHandler(cls.parallel_handler)
553 cls.logger.propagate = False
555 cls.tempdir = tempfile.mkdtemp(
556 prefix='vpp-unittest-%s-' % cls.__name__)
557 cls.stats_sock = "%s/stats.sock" % cls.tempdir
558 cls.api_sock = "%s/api.sock" % cls.tempdir
559 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
560 cls.file_handler.setFormatter(
561 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
563 cls.file_handler.setLevel(DEBUG)
564 cls.logger.addHandler(cls.file_handler)
565 cls.logger.debug("--- setUpClass() for %s called ---" %
567 cls.shm_prefix = os.path.basename(cls.tempdir) # Only used for VAPI
568 os.chdir(cls.tempdir)
569 cls.logger.info("Temporary dir is %s, api socket is %s",
570 cls.tempdir, cls.api_sock)
571 cls.logger.debug("Random seed is %s" % seed)
573 cls.reset_packet_infos()
577 cls.registry = VppObjectRegistry()
578 cls.vpp_startup_failed = False
579 cls.reporter = KeepAliveReporter()
580 # need to catch exceptions here because if we raise, then the cleanup
581 # doesn't get called and we might end with a zombie vpp
584 cls.reporter.send_keep_alive(cls, 'setUpClass')
585 VppTestResult.current_test_case_info = TestCaseInfo(
586 cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin)
587 cls.vpp_stdout_deque = deque()
588 cls.vpp_stderr_deque = deque()
589 cls.pump_thread_stop_flag = Event()
590 cls.pump_thread_wakeup_pipe = os.pipe()
591 cls.pump_thread = Thread(target=pump_output, args=(cls,))
592 cls.pump_thread.daemon = True
593 cls.pump_thread.start()
594 if cls.debug_gdb or cls.debug_gdbserver:
595 cls.vapi_response_timeout = 0
596 cls.vapi = VppPapiProvider(cls.__name__, cls,
597 cls.vapi_response_timeout)
599 hook = hookmodule.StepHook(cls)
601 hook = hookmodule.PollHook(cls)
602 cls.vapi.register_hook(hook)
603 cls.statistics = VPPStats(socketname=cls.stats_sock)
607 cls.vpp_startup_failed = True
609 "VPP died shortly after startup, check the"
610 " output to standard error for possible cause")
614 except (vpp_papi.VPPIOError, Exception) as e:
615 cls.logger.debug("Exception connecting to vapi: %s" % e)
616 cls.vapi.disconnect()
618 if cls.debug_gdbserver:
619 print(colorize("You're running VPP inside gdbserver but "
620 "VPP-API connection failed, did you forget "
621 "to 'continue' VPP from within gdb?", RED))
623 except vpp_papi.VPPRuntimeError as e:
624 cls.logger.debug("%s" % e)
627 except Exception as e:
628 cls.logger.debug("Exception connecting to VPP: %s" % e)
633 def _debug_quit(cls):
634 if (cls.debug_gdbserver or cls.debug_gdb):
638 if cls.vpp.returncode is None:
640 print(double_line_delim)
641 print("VPP or GDB server is still running")
642 print(single_line_delim)
643 input("When done debugging, press ENTER to kill the "
644 "process and finish running the testcase...")
645 except AttributeError:
651 Disconnect vpp-api, kill vpp and cleanup shared memory files
655 # first signal that we want to stop the pump thread, then wake it up
656 if hasattr(cls, 'pump_thread_stop_flag'):
657 cls.pump_thread_stop_flag.set()
658 if hasattr(cls, 'pump_thread_wakeup_pipe'):
659 os.write(cls.pump_thread_wakeup_pipe[1], b'ding dong wake up')
660 if hasattr(cls, 'pump_thread'):
661 cls.logger.debug("Waiting for pump thread to stop")
662 cls.pump_thread.join()
663 if hasattr(cls, 'vpp_stderr_reader_thread'):
664 cls.logger.debug("Waiting for stderr pump to stop")
665 cls.vpp_stderr_reader_thread.join()
667 if hasattr(cls, 'vpp'):
668 if hasattr(cls, 'vapi'):
669 cls.logger.debug(cls.vapi.vpp.get_stats())
670 cls.logger.debug("Disconnecting class vapi client on %s",
672 cls.vapi.disconnect()
673 cls.logger.debug("Deleting class vapi attribute on %s",
677 if cls.vpp.returncode is None:
678 cls.wait_for_coredump()
679 cls.logger.debug("Sending TERM to vpp")
681 cls.logger.debug("Waiting for vpp to die")
683 outs, errs = cls.vpp.communicate(timeout=5)
684 except subprocess.TimeoutExpired:
686 outs, errs = cls.vpp.communicate()
687 cls.logger.debug("Deleting class vpp attribute on %s",
689 cls.vpp.stdout.close()
690 cls.vpp.stderr.close()
693 if cls.vpp_startup_failed:
694 stdout_log = cls.logger.info
695 stderr_log = cls.logger.critical
697 stdout_log = cls.logger.info
698 stderr_log = cls.logger.info
700 if hasattr(cls, 'vpp_stdout_deque'):
701 stdout_log(single_line_delim)
702 stdout_log('VPP output to stdout while running %s:', cls.__name__)
703 stdout_log(single_line_delim)
704 vpp_output = "".join(cls.vpp_stdout_deque)
705 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
707 stdout_log('\n%s', vpp_output)
708 stdout_log(single_line_delim)
710 if hasattr(cls, 'vpp_stderr_deque'):
711 stderr_log(single_line_delim)
712 stderr_log('VPP output to stderr while running %s:', cls.__name__)
713 stderr_log(single_line_delim)
714 vpp_output = "".join(cls.vpp_stderr_deque)
715 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
717 stderr_log('\n%s', vpp_output)
718 stderr_log(single_line_delim)
721 def tearDownClass(cls):
722 """ Perform final cleanup after running all tests in this test-case """
723 cls.logger.debug("--- tearDownClass() for %s called ---" %
725 cls.reporter.send_keep_alive(cls, 'tearDownClass')
727 cls.file_handler.close()
728 cls.reset_packet_infos()
730 debug_internal.on_tear_down_class(cls)
732 def show_commands_at_teardown(self):
733 """ Allow subclass specific teardown logging additions."""
734 self.logger.info("--- No test specific show commands provided. ---")
737 """ Show various debug prints after each test """
738 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
739 (self.__class__.__name__, self._testMethodName,
740 self._testMethodDoc))
743 if not self.vpp_dead:
744 self.logger.debug(self.vapi.cli("show trace max 1000"))
745 self.logger.info(self.vapi.ppcli("show interface"))
746 self.logger.info(self.vapi.ppcli("show hardware"))
747 self.logger.info(self.statistics.set_errors_str())
748 self.logger.info(self.vapi.ppcli("show run"))
749 self.logger.info(self.vapi.ppcli("show log"))
750 self.logger.info(self.vapi.ppcli("show bihash"))
751 self.logger.info("Logging testcase specific show commands.")
752 self.show_commands_at_teardown()
753 self.registry.remove_vpp_config(self.logger)
754 # Save/Dump VPP api trace log
755 m = self._testMethodName
756 api_trace = "vpp_api_trace.%s.%d.log" % (m, self.vpp.pid)
757 tmp_api_trace = "/tmp/%s" % api_trace
758 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
759 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
760 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
762 os.rename(tmp_api_trace, vpp_api_trace_log)
763 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
765 except VppTransportSocketIOError:
766 self.logger.debug("VppTransportSocketIOError: Vpp dead. "
767 "Cannot log show commands.")
770 self.registry.unregister_all(self.logger)
773 """ Clear trace before running each test"""
774 super(VppTestCase, self).setUp()
775 self.reporter.send_keep_alive(self)
778 raise VppDiedError(rv=None, testcase=self.__class__.__name__,
779 method_name=self._testMethodName)
780 self.sleep(.1, "during setUp")
781 self.vpp_stdout_deque.append(
782 "--- test setUp() for %s.%s(%s) starts here ---\n" %
783 (self.__class__.__name__, self._testMethodName,
784 self._testMethodDoc))
785 self.vpp_stderr_deque.append(
786 "--- test setUp() for %s.%s(%s) starts here ---\n" %
787 (self.__class__.__name__, self._testMethodName,
788 self._testMethodDoc))
789 self.vapi.cli("clear trace")
790 # store the test instance inside the test class - so that objects
791 # holding the class can access instance methods (like assertEqual)
792 type(self).test_instance = self
795 def pg_enable_capture(cls, interfaces=None):
797 Enable capture on packet-generator interfaces
799 :param interfaces: iterable interface indexes (if None,
800 use self.pg_interfaces)
803 if interfaces is None:
804 interfaces = cls.pg_interfaces
809 def register_capture(cls, cap_name):
810 """ Register a capture in the testclass """
811 # add to the list of captures with current timestamp
812 cls._captures.append((time.time(), cap_name))
815 def get_vpp_time(cls):
816 # processes e.g. "Time now 2.190522, Wed, 11 Mar 2020 17:29:54 GMT"
817 # returns float("2.190522")
818 timestr = cls.vapi.cli('show clock')
819 head, sep, tail = timestr.partition(',')
820 head, sep, tail = head.partition('Time now')
824 def sleep_on_vpp_time(cls, sec):
825 """ Sleep according to time in VPP world """
826 # On a busy system with many processes
827 # we might end up with VPP time being slower than real world
828 # So take that into account when waiting for VPP to do something
829 start_time = cls.get_vpp_time()
830 while cls.get_vpp_time() - start_time < sec:
834 def pg_start(cls, trace=True):
835 """ Enable the PG, wait till it is done, then clean up """
837 cls.vapi.cli("clear trace")
838 cls.vapi.cli("trace add pg-input 1000")
839 cls.vapi.cli('packet-generator enable')
840 # PG, when starts, runs to completion -
841 # so let's avoid a race condition,
842 # and wait a little till it's done.
843 # Then clean it up - and then be gone.
844 deadline = time.time() + 300
845 while cls.vapi.cli('show packet-generator').find("Yes") != -1:
846 cls.sleep(0.01) # yield
847 if time.time() > deadline:
848 cls.logger.error("Timeout waiting for pg to stop")
850 for stamp, cap_name in cls._captures:
851 cls.vapi.cli('packet-generator delete %s' % cap_name)
855 def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0):
857 Create packet-generator interfaces.
859 :param interfaces: iterable indexes of the interfaces.
860 :returns: List of created interfaces.
865 intf = VppPGInterface(cls, i, gso, gso_size)
866 setattr(cls, intf.name, intf)
868 cls.pg_interfaces = result
872 def create_loopback_interfaces(cls, count):
874 Create loopback interfaces.
876 :param count: number of interfaces created.
877 :returns: List of created interfaces.
879 result = [VppLoInterface(cls) for i in range(count)]
881 setattr(cls, intf.name, intf)
882 cls.lo_interfaces = result
886 def create_bvi_interfaces(cls, count):
888 Create BVI interfaces.
890 :param count: number of interfaces created.
891 :returns: List of created interfaces.
893 result = [VppBviInterface(cls) for i in range(count)]
895 setattr(cls, intf.name, intf)
896 cls.bvi_interfaces = result
900 def extend_packet(packet, size, padding=' '):
902 Extend packet to given size by padding with spaces or custom padding
903 NOTE: Currently works only when Raw layer is present.
905 :param packet: packet
906 :param size: target size
907 :param padding: padding used to extend the payload
910 packet_len = len(packet) + 4
911 extend = size - packet_len
913 num = (extend // len(padding)) + 1
914 packet[Raw].load += (padding * num)[:extend].encode("ascii")
917 def reset_packet_infos(cls):
918 """ Reset the list of packet info objects and packet counts to zero """
919 cls._packet_infos = {}
920 cls._packet_count_for_dst_if_idx = {}
923 def create_packet_info(cls, src_if, dst_if):
925 Create packet info object containing the source and destination indexes
926 and add it to the testcase's packet info list
928 :param VppInterface src_if: source interface
929 :param VppInterface dst_if: destination interface
931 :returns: _PacketInfo object
935 info.index = len(cls._packet_infos)
936 info.src = src_if.sw_if_index
937 info.dst = dst_if.sw_if_index
938 if isinstance(dst_if, VppSubInterface):
939 dst_idx = dst_if.parent.sw_if_index
941 dst_idx = dst_if.sw_if_index
942 if dst_idx in cls._packet_count_for_dst_if_idx:
943 cls._packet_count_for_dst_if_idx[dst_idx] += 1
945 cls._packet_count_for_dst_if_idx[dst_idx] = 1
946 cls._packet_infos[info.index] = info
950 def info_to_payload(info):
952 Convert _PacketInfo object to packet payload
954 :param info: _PacketInfo object
956 :returns: string containing serialized data from packet info
958 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
962 def payload_to_info(payload, payload_field='load'):
964 Convert packet payload to _PacketInfo object
966 :param payload: packet payload
967 :type payload: <class 'scapy.packet.Raw'>
968 :param payload_field: packet fieldname of payload "load" for
969 <class 'scapy.packet.Raw'>
970 :type payload_field: str
971 :returns: _PacketInfo object containing de-serialized data from payload
974 numbers = getattr(payload, payload_field).split()
976 info.index = int(numbers[0])
977 info.src = int(numbers[1])
978 info.dst = int(numbers[2])
979 info.ip = int(numbers[3])
980 info.proto = int(numbers[4])
983 def get_next_packet_info(self, info):
985 Iterate over the packet info list stored in the testcase
986 Start iteration with first element if info is None
987 Continue based on index in info if info is specified
989 :param info: info or None
990 :returns: next info in list or None if no more infos
995 next_index = info.index + 1
996 if next_index == len(self._packet_infos):
999 return self._packet_infos[next_index]
1001 def get_next_packet_info_for_interface(self, src_index, info):
1003 Search the packet info list for the next packet info with same source
1006 :param src_index: source interface index to search for
1007 :param info: packet info - where to start the search
1008 :returns: packet info or None
1012 info = self.get_next_packet_info(info)
1015 if info.src == src_index:
1018 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
1020 Search the packet info list for the next packet info with same source
1021 and destination interface indexes
1023 :param src_index: source interface index to search for
1024 :param dst_index: destination interface index to search for
1025 :param info: packet info - where to start the search
1026 :returns: packet info or None
1030 info = self.get_next_packet_info_for_interface(src_index, info)
1033 if info.dst == dst_index:
1036 def assert_equal(self, real_value, expected_value, name_or_class=None):
1037 if name_or_class is None:
1038 self.assertEqual(real_value, expected_value)
1041 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
1042 msg = msg % (getdoc(name_or_class).strip(),
1043 real_value, str(name_or_class(real_value)),
1044 expected_value, str(name_or_class(expected_value)))
1046 msg = "Invalid %s: %s does not match expected value %s" % (
1047 name_or_class, real_value, expected_value)
1049 self.assertEqual(real_value, expected_value, msg)
1051 def assert_in_range(self,
1059 msg = "Invalid %s: %s out of range <%s,%s>" % (
1060 name, real_value, expected_min, expected_max)
1061 self.assertTrue(expected_min <= real_value <= expected_max, msg)
1063 def assert_packet_checksums_valid(self, packet,
1064 ignore_zero_udp_checksums=True):
1065 received = packet.__class__(scapy.compat.raw(packet))
1066 udp_layers = ['UDP', 'UDPerror']
1067 checksum_fields = ['cksum', 'chksum']
1070 temp = received.__class__(scapy.compat.raw(received))
1072 layer = temp.getlayer(counter)
1074 layer = layer.copy()
1075 layer.remove_payload()
1076 for cf in checksum_fields:
1077 if hasattr(layer, cf):
1078 if ignore_zero_udp_checksums and \
1079 0 == getattr(layer, cf) and \
1080 layer.name in udp_layers:
1082 delattr(temp.getlayer(counter), cf)
1083 checksums.append((counter, cf))
1086 counter = counter + 1
1087 if 0 == len(checksums):
1089 temp = temp.__class__(scapy.compat.raw(temp))
1090 for layer, cf in checksums:
1091 calc_sum = getattr(temp[layer], cf)
1093 getattr(received[layer], cf), calc_sum,
1094 "packet checksum on layer #%d: %s" % (layer, temp[layer].name))
1096 "Checksum field `%s` on `%s` layer has correct value `%s`" %
1097 (cf, temp[layer].name, calc_sum))
1099 def assert_checksum_valid(self, received_packet, layer,
1100 field_name='chksum',
1101 ignore_zero_checksum=False):
1102 """ Check checksum of received packet on given layer """
1103 received_packet_checksum = getattr(received_packet[layer], field_name)
1104 if ignore_zero_checksum and 0 == received_packet_checksum:
1106 recalculated = received_packet.__class__(
1107 scapy.compat.raw(received_packet))
1108 delattr(recalculated[layer], field_name)
1109 recalculated = recalculated.__class__(scapy.compat.raw(recalculated))
1110 self.assert_equal(received_packet_checksum,
1111 getattr(recalculated[layer], field_name),
1112 "packet checksum on layer: %s" % layer)
1114 def assert_ip_checksum_valid(self, received_packet,
1115 ignore_zero_checksum=False):
1116 self.assert_checksum_valid(received_packet, 'IP',
1117 ignore_zero_checksum=ignore_zero_checksum)
1119 def assert_tcp_checksum_valid(self, received_packet,
1120 ignore_zero_checksum=False):
1121 self.assert_checksum_valid(received_packet, 'TCP',
1122 ignore_zero_checksum=ignore_zero_checksum)
1124 def assert_udp_checksum_valid(self, received_packet,
1125 ignore_zero_checksum=True):
1126 self.assert_checksum_valid(received_packet, 'UDP',
1127 ignore_zero_checksum=ignore_zero_checksum)
1129 def assert_embedded_icmp_checksum_valid(self, received_packet):
1130 if received_packet.haslayer(IPerror):
1131 self.assert_checksum_valid(received_packet, 'IPerror')
1132 if received_packet.haslayer(TCPerror):
1133 self.assert_checksum_valid(received_packet, 'TCPerror')
1134 if received_packet.haslayer(UDPerror):
1135 self.assert_checksum_valid(received_packet, 'UDPerror',
1136 ignore_zero_checksum=True)
1137 if received_packet.haslayer(ICMPerror):
1138 self.assert_checksum_valid(received_packet, 'ICMPerror')
1140 def assert_icmp_checksum_valid(self, received_packet):
1141 self.assert_checksum_valid(received_packet, 'ICMP')
1142 self.assert_embedded_icmp_checksum_valid(received_packet)
1144 def assert_icmpv6_checksum_valid(self, pkt):
1145 if pkt.haslayer(ICMPv6DestUnreach):
1146 self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
1147 self.assert_embedded_icmp_checksum_valid(pkt)
1148 if pkt.haslayer(ICMPv6EchoRequest):
1149 self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
1150 if pkt.haslayer(ICMPv6EchoReply):
1151 self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
1153 def get_packet_counter(self, counter):
1154 if counter.startswith("/"):
1155 counter_value = self.statistics.get_counter(counter)
1157 counters = self.vapi.cli("sh errors").split('\n')
1159 for i in range(1, len(counters) - 1):
1160 results = counters[i].split()
1161 if results[1] == counter:
1162 counter_value = int(results[0])
1164 return counter_value
1166 def assert_packet_counter_equal(self, counter, expected_value):
1167 counter_value = self.get_packet_counter(counter)
1168 self.assert_equal(counter_value, expected_value,
1169 "packet counter `%s'" % counter)
1171 def assert_error_counter_equal(self, counter, expected_value):
1172 counter_value = self.statistics.get_err_counter(counter)
1173 self.assert_equal(counter_value, expected_value,
1174 "error counter `%s'" % counter)
1177 def sleep(cls, timeout, remark=None):
1179 # /* Allow sleep(0) to maintain win32 semantics, and as decreed
1180 # * by Guido, only the main thread can be interrupted.
1182 # https://github.com/python/cpython/blob/6673decfa0fb078f60587f5cb5e98460eea137c2/Modules/timemodule.c#L1892 # noqa
1185 if hasattr(os, 'sched_yield'):
1191 cls.logger.debug("Starting sleep for %es (%s)", timeout, remark)
1192 before = time.time()
1195 if after - before > 2 * timeout:
1196 cls.logger.error("unexpected self.sleep() result - "
1197 "slept for %es instead of ~%es!",
1198 after - before, timeout)
1201 "Finished sleep (%s) - slept %es (wanted %es)",
1202 remark, after - before, timeout)
1204 def pg_send(self, intf, pkts, worker=None, trace=True):
1205 intf.add_stream(pkts, worker=worker)
1206 self.pg_enable_capture(self.pg_interfaces)
1207 self.pg_start(trace=trace)
1209 def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
1210 self.pg_send(intf, pkts)
1213 for i in self.pg_interfaces:
1214 i.get_capture(0, timeout=timeout)
1215 i.assert_nothing_captured(remark=remark)
1218 def send_and_expect(self, intf, pkts, output, n_rx=None, worker=None,
1222 self.pg_send(intf, pkts, worker=worker, trace=trace)
1223 rx = output.get_capture(n_rx)
1226 def send_and_expect_only(self, intf, pkts, output, timeout=None):
1227 self.pg_send(intf, pkts)
1228 rx = output.get_capture(len(pkts))
1232 for i in self.pg_interfaces:
1233 if i not in outputs:
1234 i.get_capture(0, timeout=timeout)
1235 i.assert_nothing_captured()
1241 def get_testcase_doc_name(test):
1242 return getdoc(test.__class__).splitlines()[0]
1245 def get_test_description(descriptions, test):
1246 short_description = test.shortDescription()
1247 if descriptions and short_description:
1248 return short_description
1253 class TestCaseInfo(object):
1254 def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
1255 self.logger = logger
1256 self.tempdir = tempdir
1257 self.vpp_pid = vpp_pid
1258 self.vpp_bin_path = vpp_bin_path
1259 self.core_crash_test = None
1262 class VppTestResult(unittest.TestResult):
1264 @property result_string
1265 String variable to store the test case result string.
1267 List variable containing 2-tuples of TestCase instances and strings
1268 holding formatted tracebacks. Each tuple represents a test which
1269 raised an unexpected exception.
1271 List variable containing 2-tuples of TestCase instances and strings
1272 holding formatted tracebacks. Each tuple represents a test where
1273 a failure was explicitly signalled using the TestCase.assert*()
1277 failed_test_cases_info = set()
1278 core_crash_test_cases_info = set()
1279 current_test_case_info = None
1281 def __init__(self, stream=None, descriptions=None, verbosity=None,
1284 :param stream File descriptor to store where to report test results.
1285 Set to the standard error stream by default.
1286 :param descriptions Boolean variable to store information if to use
1287 test case descriptions.
1288 :param verbosity Integer variable to store required verbosity level.
1290 super(VppTestResult, self).__init__(stream, descriptions, verbosity)
1291 self.stream = stream
1292 self.descriptions = descriptions
1293 self.verbosity = verbosity
1294 self.result_string = None
1295 self.runner = runner
1297 def addSuccess(self, test):
1299 Record a test succeeded result
1304 if self.current_test_case_info:
1305 self.current_test_case_info.logger.debug(
1306 "--- addSuccess() %s.%s(%s) called" % (test.__class__.__name__,
1307 test._testMethodName,
1308 test._testMethodDoc))
1309 unittest.TestResult.addSuccess(self, test)
1310 self.result_string = colorize("OK", GREEN)
1312 self.send_result_through_pipe(test, PASS)
1314 def addSkip(self, test, reason):
1316 Record a test skipped.
1322 if self.current_test_case_info:
1323 self.current_test_case_info.logger.debug(
1324 "--- addSkip() %s.%s(%s) called, reason is %s" %
1325 (test.__class__.__name__, test._testMethodName,
1326 test._testMethodDoc, reason))
1327 unittest.TestResult.addSkip(self, test, reason)
1328 self.result_string = colorize("SKIP", YELLOW)
1330 self.send_result_through_pipe(test, SKIP)
1332 def symlink_failed(self):
1333 if self.current_test_case_info:
1335 failed_dir = os.getenv('FAILED_DIR')
1336 link_path = os.path.join(
1339 os.path.basename(self.current_test_case_info.tempdir))
1341 self.current_test_case_info.logger.debug(
1342 "creating a link to the failed test")
1343 self.current_test_case_info.logger.debug(
1344 "os.symlink(%s, %s)" %
1345 (self.current_test_case_info.tempdir, link_path))
1346 if os.path.exists(link_path):
1347 self.current_test_case_info.logger.debug(
1348 'symlink already exists')
1350 os.symlink(self.current_test_case_info.tempdir, link_path)
1352 except Exception as e:
1353 self.current_test_case_info.logger.error(e)
1355 def send_result_through_pipe(self, test, result):
1356 if hasattr(self, 'test_framework_result_pipe'):
1357 pipe = self.test_framework_result_pipe
1359 pipe.send((test.id(), result))
1361 def log_error(self, test, err, fn_name):
1362 if self.current_test_case_info:
1363 if isinstance(test, unittest.suite._ErrorHolder):
1364 test_name = test.description
1366 test_name = '%s.%s(%s)' % (test.__class__.__name__,
1367 test._testMethodName,
1368 test._testMethodDoc)
1369 self.current_test_case_info.logger.debug(
1370 "--- %s() %s called, err is %s" %
1371 (fn_name, test_name, err))
1372 self.current_test_case_info.logger.debug(
1373 "formatted exception is:\n%s" %
1374 "".join(format_exception(*err)))
1376 def add_error(self, test, err, unittest_fn, error_type):
1377 if error_type == FAIL:
1378 self.log_error(test, err, 'addFailure')
1379 error_type_str = colorize("FAIL", RED)
1380 elif error_type == ERROR:
1381 self.log_error(test, err, 'addError')
1382 error_type_str = colorize("ERROR", RED)
1384 raise Exception('Error type %s cannot be used to record an '
1385 'error or a failure' % error_type)
1387 unittest_fn(self, test, err)
1388 if self.current_test_case_info:
1389 self.result_string = "%s [ temp dir used by test case: %s ]" % \
1391 self.current_test_case_info.tempdir)
1392 self.symlink_failed()
1393 self.failed_test_cases_info.add(self.current_test_case_info)
1394 if is_core_present(self.current_test_case_info.tempdir):
1395 if not self.current_test_case_info.core_crash_test:
1396 if isinstance(test, unittest.suite._ErrorHolder):
1397 test_name = str(test)
1399 test_name = "'{!s}' ({!s})".format(
1400 get_testcase_doc_name(test), test.id())
1401 self.current_test_case_info.core_crash_test = test_name
1402 self.core_crash_test_cases_info.add(
1403 self.current_test_case_info)
1405 self.result_string = '%s [no temp dir]' % error_type_str
1407 self.send_result_through_pipe(test, error_type)
1409 def addFailure(self, test, err):
1411 Record a test failed result
1414 :param err: error message
1417 self.add_error(test, err, unittest.TestResult.addFailure, FAIL)
1419 def addError(self, test, err):
1421 Record a test error result
1424 :param err: error message
1427 self.add_error(test, err, unittest.TestResult.addError, ERROR)
1429 def getDescription(self, test):
1431 Get test description
1434 :returns: test description
1437 return get_test_description(self.descriptions, test)
1439 def startTest(self, test):
1447 def print_header(test):
1448 test_doc = getdoc(test)
1450 raise Exception("No doc string for test '%s'" % test.id())
1451 test_title = test_doc.splitlines()[0]
1452 test_title_colored = colorize(test_title, GREEN)
1453 if test.is_tagged_run_solo():
1454 # long live PEP-8 and 80 char width limitation...
1456 test_title_colored = colorize("SOLO RUN: " + test_title, c)
1458 # This block may overwrite the colorized title above,
1459 # but we want this to stand out and be fixed
1460 if test.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
1462 w = "FIXME with VPP workers: "
1463 test_title_colored = colorize(w + test_title, c)
1465 if not hasattr(test.__class__, '_header_printed'):
1466 print(double_line_delim)
1467 print(test_title_colored)
1468 print(double_line_delim)
1469 test.__class__._header_printed = True
1472 self.start_test = time.time()
1473 unittest.TestResult.startTest(self, test)
1474 if self.verbosity > 0:
1475 self.stream.writeln(
1476 "Starting " + self.getDescription(test) + " ...")
1477 self.stream.writeln(single_line_delim)
1479 def stopTest(self, test):
1481 Called when the given test has been run
1486 unittest.TestResult.stopTest(self, test)
1488 if self.verbosity > 0:
1489 self.stream.writeln(single_line_delim)
1490 self.stream.writeln("%-73s%s" % (self.getDescription(test),
1491 self.result_string))
1492 self.stream.writeln(single_line_delim)
1494 self.stream.writeln("%-68s %4.2f %s" %
1495 (self.getDescription(test),
1496 time.time() - self.start_test,
1497 self.result_string))
1499 self.send_result_through_pipe(test, TEST_RUN)
1501 def printErrors(self):
1503 Print errors from running the test case
1505 if len(self.errors) > 0 or len(self.failures) > 0:
1506 self.stream.writeln()
1507 self.printErrorList('ERROR', self.errors)
1508 self.printErrorList('FAIL', self.failures)
1510 # ^^ that is the last output from unittest before summary
1511 if not self.runner.print_summary:
1512 devnull = unittest.runner._WritelnDecorator(open(os.devnull, 'w'))
1513 self.stream = devnull
1514 self.runner.stream = devnull
1516 def printErrorList(self, flavour, errors):
1518 Print error list to the output stream together with error type
1519 and test case description.
1521 :param flavour: error type
1522 :param errors: iterable errors
1525 for test, err in errors:
1526 self.stream.writeln(double_line_delim)
1527 self.stream.writeln("%s: %s" %
1528 (flavour, self.getDescription(test)))
1529 self.stream.writeln(single_line_delim)
1530 self.stream.writeln("%s" % err)
1533 class VppTestRunner(unittest.TextTestRunner):
1535 A basic test runner implementation which prints results to standard error.
1539 def resultclass(self):
1540 """Class maintaining the results of the tests"""
1541 return VppTestResult
1543 def __init__(self, keep_alive_pipe=None, descriptions=True, verbosity=1,
1544 result_pipe=None, failfast=False, buffer=False,
1545 resultclass=None, print_summary=True, **kwargs):
1546 # ignore stream setting here, use hard-coded stdout to be in sync
1547 # with prints from VppTestCase methods ...
1548 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1549 verbosity, failfast, buffer,
1550 resultclass, **kwargs)
1551 KeepAliveReporter.pipe = keep_alive_pipe
1553 self.orig_stream = self.stream
1554 self.resultclass.test_framework_result_pipe = result_pipe
1556 self.print_summary = print_summary
1558 def _makeResult(self):
1559 return self.resultclass(self.stream,
1564 def run(self, test):
1571 faulthandler.enable() # emit stack trace to stderr if killed by signal
1573 result = super(VppTestRunner, self).run(test)
1574 if not self.print_summary:
1575 self.stream = self.orig_stream
1576 result.stream = self.orig_stream
1580 class Worker(Thread):
1581 def __init__(self, executable_args, logger, env=None, *args, **kwargs):
1582 super(Worker, self).__init__(*args, **kwargs)
1583 self.logger = logger
1584 self.args = executable_args
1585 if hasattr(self, 'testcase') and self.testcase.debug_all:
1586 if self.testcase.debug_gdbserver:
1587 self.args = ['/usr/bin/gdbserver', 'localhost:{port}'
1588 .format(port=self.testcase.gdbserver_port)] + args
1589 elif self.testcase.debug_gdb and hasattr(self, 'wait_for_gdb'):
1590 self.args.append(self.wait_for_gdb)
1591 self.app_bin = executable_args[0]
1592 self.app_name = os.path.basename(self.app_bin)
1593 if hasattr(self, 'role'):
1594 self.app_name += ' {role}'.format(role=self.role)
1597 env = {} if env is None else env
1598 self.env = copy.deepcopy(env)
1600 def wait_for_enter(self):
1601 if not hasattr(self, 'testcase'):
1603 if self.testcase.debug_all and self.testcase.debug_gdbserver:
1605 print(double_line_delim)
1606 print("Spawned GDB Server for '{app}' with PID: {pid}"
1607 .format(app=self.app_name, pid=self.process.pid))
1608 elif self.testcase.debug_all and self.testcase.debug_gdb:
1610 print(double_line_delim)
1611 print("Spawned '{app}' with PID: {pid}"
1612 .format(app=self.app_name, pid=self.process.pid))
1615 print(single_line_delim)
1616 print("You can debug '{app}' using:".format(app=self.app_name))
1617 if self.testcase.debug_gdbserver:
1618 print("sudo gdb " + self.app_bin +
1619 " -ex 'target remote localhost:{port}'"
1620 .format(port=self.testcase.gdbserver_port))
1621 print("Now is the time to attach gdb by running the above "
1622 "command, set up breakpoints etc., then resume from "
1623 "within gdb by issuing the 'continue' command")
1624 self.testcase.gdbserver_port += 1
1625 elif self.testcase.debug_gdb:
1626 print("sudo gdb " + self.app_bin +
1627 " -ex 'attach {pid}'".format(pid=self.process.pid))
1628 print("Now is the time to attach gdb by running the above "
1629 "command and set up breakpoints etc., then resume from"
1630 " within gdb by issuing the 'continue' command")
1631 print(single_line_delim)
1632 input("Press ENTER to continue running the testcase...")
1635 executable = self.args[0]
1636 if not os.path.exists(executable) or not os.access(
1637 executable, os.F_OK | os.X_OK):
1638 # Exit code that means some system file did not exist,
1639 # could not be opened, or had some other kind of error.
1640 self.result = os.EX_OSFILE
1641 raise EnvironmentError(
1642 "executable '%s' is not found or executable." % executable)
1643 self.logger.debug("Running executable: '{app}'"
1644 .format(app=' '.join(self.args)))
1645 env = os.environ.copy()
1646 env.update(self.env)
1647 env["CK_LOG_FILE_NAME"] = "-"
1648 self.process = subprocess.Popen(
1649 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1650 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1651 self.wait_for_enter()
1652 out, err = self.process.communicate()
1653 self.logger.debug("Finished running `{app}'".format(app=self.app_name))
1654 self.logger.info("Return code is `%s'" % self.process.returncode)
1655 self.logger.info(single_line_delim)
1656 self.logger.info("Executable `{app}' wrote to stdout:"
1657 .format(app=self.app_name))
1658 self.logger.info(single_line_delim)
1659 self.logger.info(out.decode('utf-8'))
1660 self.logger.info(single_line_delim)
1661 self.logger.info("Executable `{app}' wrote to stderr:"
1662 .format(app=self.app_name))
1663 self.logger.info(single_line_delim)
1664 self.logger.info(err.decode('utf-8'))
1665 self.logger.info(single_line_delim)
1666 self.result = self.process.returncode
1669 if __name__ == '__main__':