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_shmem import VppTransportShmemIOError
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 = 'running %s.%s ' % (testcase, method_name)
111 msg = "VPP subprocess died %sunexpectedly with return code: %d%s." % (
114 ' [%s]' % (self.signal_name if
115 self.signal_name is not None else ''))
116 super(VppDiedError, self).__init__(msg)
119 class _PacketInfo(object):
120 """Private class to create packet info object.
122 Help process information about the next packet.
123 Set variables to default values.
125 #: Store the index of the packet.
127 #: Store the index of the source packet generator interface of the packet.
129 #: Store the index of the destination packet generator interface
132 #: Store expected ip version
134 #: Store expected upper protocol
136 #: Store the copy of the former packet.
139 def __eq__(self, other):
140 index = self.index == other.index
141 src = self.src == other.src
142 dst = self.dst == other.dst
143 data = self.data == other.data
144 return index and src and dst and data
147 def pump_output(testclass):
148 """ pump output from vpp stdout/stderr to proper queues """
151 while not testclass.pump_thread_stop_flag.is_set():
152 readable = select.select([testclass.vpp.stdout.fileno(),
153 testclass.vpp.stderr.fileno(),
154 testclass.pump_thread_wakeup_pipe[0]],
156 if testclass.vpp.stdout.fileno() in readable:
157 read = os.read(testclass.vpp.stdout.fileno(), 102400)
159 split = read.decode('ascii',
160 errors='backslashreplace').splitlines(True)
161 if len(stdout_fragment) > 0:
162 split[0] = "%s%s" % (stdout_fragment, split[0])
163 if len(split) > 0 and split[-1].endswith("\n"):
167 stdout_fragment = split[-1]
168 testclass.vpp_stdout_deque.extend(split[:limit])
169 if not testclass.cache_vpp_output:
170 for line in split[:limit]:
171 testclass.logger.info(
172 "VPP STDOUT: %s" % line.rstrip("\n"))
173 if testclass.vpp.stderr.fileno() in readable:
174 read = os.read(testclass.vpp.stderr.fileno(), 102400)
176 split = read.decode('ascii',
177 errors='backslashreplace').splitlines(True)
178 if len(stderr_fragment) > 0:
179 split[0] = "%s%s" % (stderr_fragment, split[0])
180 if len(split) > 0 and split[-1].endswith("\n"):
184 stderr_fragment = split[-1]
186 testclass.vpp_stderr_deque.extend(split[:limit])
187 if not testclass.cache_vpp_output:
188 for line in split[:limit]:
189 testclass.logger.error(
190 "VPP STDERR: %s" % line.rstrip("\n"))
191 # ignoring the dummy pipe here intentionally - the
192 # flag will take care of properly terminating the loop
195 def _is_skip_aarch64_set():
196 return BoolEnvironmentVariable('SKIP_AARCH64')
199 is_skip_aarch64_set = _is_skip_aarch64_set()
202 def _is_platform_aarch64():
203 return platform.machine() == 'aarch64'
206 is_platform_aarch64 = _is_platform_aarch64()
209 def _running_extended_tests():
210 return BoolEnvironmentVariable("EXTENDED_TESTS")
213 running_extended_tests = _running_extended_tests()
216 def _running_gcov_tests():
217 return BoolEnvironmentVariable("GCOV_TESTS")
220 running_gcov_tests = _running_gcov_tests()
223 class KeepAliveReporter(object):
225 Singleton object which reports test start to parent process
230 self.__dict__ = self._shared_state
238 def pipe(self, pipe):
239 if self._pipe is not None:
240 raise Exception("Internal error - pipe should only be set once.")
243 def send_keep_alive(self, test, desc=None):
245 Write current test tmpdir & desc to keep-alive pipe to signal liveness
247 if self.pipe is None:
248 # if not running forked..
252 desc = '%s (%s)' % (desc, unittest.util.strclass(test))
256 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
259 class TestCaseTag(Enum):
263 def create_tag_decorator(e):
266 cls.test_tags.append(e)
267 except AttributeError:
272 tag_run_solo = create_tag_decorator(TestCaseTag.RUN_SOLO)
275 class VppTestCase(unittest.TestCase):
276 """This subclass is a base class for VPP test cases that are implemented as
277 classes. It provides methods to create and run test case.
280 extra_vpp_punt_config = []
281 extra_vpp_plugin_config = []
283 vapi_response_timeout = 5
286 def packet_infos(self):
287 """List of packet infos"""
288 return self._packet_infos
291 def get_packet_count_for_if_idx(cls, dst_if_index):
292 """Get the number of packet info for specified destination if index"""
293 if dst_if_index in cls._packet_count_for_dst_if_idx:
294 return cls._packet_count_for_dst_if_idx[dst_if_index]
299 def has_tag(cls, tag):
300 """ if the test case has a given tag - return true """
302 return tag in cls.test_tags
303 except AttributeError:
308 def is_tagged_run_solo(cls):
309 """ if the test case class is timing-sensitive - return true """
310 return cls.has_tag(TestCaseTag.RUN_SOLO)
314 """Return the instance of this testcase"""
315 return cls.test_instance
318 def set_debug_flags(cls, d):
319 cls.gdbserver_port = 7777
320 cls.debug_core = False
321 cls.debug_gdb = False
322 cls.debug_gdbserver = False
323 cls.debug_all = False
328 cls.debug_core = True
329 elif dl == "gdb" or dl == "gdb-all":
331 elif dl == "gdbserver" or dl == "gdbserver-all":
332 cls.debug_gdbserver = True
334 raise Exception("Unrecognized DEBUG option: '%s'" % d)
335 if dl == "gdb-all" or dl == "gdbserver-all":
339 def get_least_used_cpu():
340 cpu_usage_list = [set(range(psutil.cpu_count()))]
341 vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name'])
342 if 'vpp_main' == p.info['name']]
343 for vpp_process in vpp_processes:
344 for cpu_usage_set in cpu_usage_list:
346 cpu_num = vpp_process.cpu_num()
347 if cpu_num in cpu_usage_set:
348 cpu_usage_set_index = cpu_usage_list.index(
350 if cpu_usage_set_index == len(cpu_usage_list) - 1:
351 cpu_usage_list.append({cpu_num})
353 cpu_usage_list[cpu_usage_set_index + 1].add(
355 cpu_usage_set.remove(cpu_num)
357 except psutil.NoSuchProcess:
360 for cpu_usage_set in cpu_usage_list:
361 if len(cpu_usage_set) > 0:
362 min_usage_set = cpu_usage_set
365 return random.choice(tuple(min_usage_set))
368 def setUpConstants(cls):
369 """ Set-up the test case class based on environment variables """
370 cls.step = BoolEnvironmentVariable('STEP')
371 d = os.getenv("DEBUG", None)
372 # inverted case to handle '' == True
373 c = os.getenv("CACHE_OUTPUT", "1")
374 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
375 cls.set_debug_flags(d)
376 cls.vpp_bin = os.getenv('VPP_BIN', "vpp")
377 cls.plugin_path = os.getenv('VPP_PLUGIN_PATH')
378 cls.test_plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
379 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
381 if cls.plugin_path is not None:
382 if cls.extern_plugin_path is not None:
383 plugin_path = "%s:%s" % (
384 cls.plugin_path, cls.extern_plugin_path)
386 plugin_path = cls.plugin_path
387 elif cls.extern_plugin_path is not None:
388 plugin_path = cls.extern_plugin_path
390 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
391 debug_cli = "cli-listen localhost:5002"
393 size = os.getenv("COREDUMP_SIZE")
395 coredump_size = "coredump-size %s" % size
396 if coredump_size is None:
397 coredump_size = "coredump-size unlimited"
399 cpu_core_number = cls.get_least_used_cpu()
400 if not hasattr(cls, "worker_config"):
401 cls.worker_config = ""
403 default_variant = os.getenv("VARIANT")
404 if default_variant is not None:
405 default_variant = "defaults { %s 100 }" % default_variant
409 api_fuzzing = os.getenv("API_FUZZ")
410 if api_fuzzing is None:
413 cls.vpp_cmdline = [cls.vpp_bin, "unix",
414 "{", "nodaemon", debug_cli, "full-coredump",
415 coredump_size, "runtime-dir", cls.tempdir, "}",
416 "api-trace", "{", "on", "}", "api-segment", "{",
417 "prefix", cls.shm_prefix, "}", "cpu", "{",
418 "main-core", str(cpu_core_number),
419 cls.worker_config, "}",
420 "physmem", "{", "max-size", "32m", "}",
421 "statseg", "{", "socket-name", cls.stats_sock, "}",
422 "socksvr", "{", "socket-name", cls.api_sock, "}",
423 "node { ", default_variant, "}",
424 "api-fuzz {", api_fuzzing, "}",
426 "{", "plugin", "dpdk_plugin.so", "{", "disable",
427 "}", "plugin", "rdma_plugin.so", "{", "disable",
428 "}", "plugin", "lisp_unittest_plugin.so", "{",
430 "}", "plugin", "unittest_plugin.so", "{", "enable",
431 "}"] + cls.extra_vpp_plugin_config + ["}", ]
433 if cls.extra_vpp_punt_config is not None:
434 cls.vpp_cmdline.extend(cls.extra_vpp_punt_config)
435 if plugin_path is not None:
436 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
437 if cls.test_plugin_path is not None:
438 cls.vpp_cmdline.extend(["test_plugin_path", cls.test_plugin_path])
440 cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline)
441 cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline))
444 def wait_for_enter(cls):
445 if cls.debug_gdbserver:
446 print(double_line_delim)
447 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
449 print(double_line_delim)
450 print("Spawned VPP with PID: %d" % cls.vpp.pid)
452 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
454 print(single_line_delim)
455 print("You can debug VPP using:")
456 if cls.debug_gdbserver:
457 print("sudo gdb " + cls.vpp_bin +
458 " -ex 'target remote localhost:{port}'"
459 .format(port=cls.gdbserver_port))
460 print("Now is the time to attach gdb by running the above "
461 "command, set up breakpoints etc., then resume VPP from "
462 "within gdb by issuing the 'continue' command")
463 cls.gdbserver_port += 1
465 print("sudo gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
466 print("Now is the time to attach gdb by running the above "
467 "command and set up breakpoints etc., then resume VPP from"
468 " within gdb by issuing the 'continue' command")
469 print(single_line_delim)
470 input("Press ENTER to continue running the testcase...")
474 cmdline = cls.vpp_cmdline
476 if cls.debug_gdbserver:
477 gdbserver = '/usr/bin/gdbserver'
478 if not os.path.isfile(gdbserver) or \
479 not os.access(gdbserver, os.X_OK):
480 raise Exception("gdbserver binary '%s' does not exist or is "
481 "not executable" % gdbserver)
483 cmdline = [gdbserver, 'localhost:{port}'
484 .format(port=cls.gdbserver_port)] + cls.vpp_cmdline
485 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
488 cls.vpp = subprocess.Popen(cmdline,
489 stdout=subprocess.PIPE,
490 stderr=subprocess.PIPE)
491 except subprocess.CalledProcessError as e:
492 cls.logger.critical("Subprocess returned with non-0 return code: ("
496 cls.logger.critical("Subprocess returned with OS error: "
497 "(%s) %s", e.errno, e.strerror)
499 except Exception as e:
500 cls.logger.exception("Subprocess returned unexpected from "
507 def wait_for_coredump(cls):
508 corefile = cls.tempdir + "/core"
509 if os.path.isfile(corefile):
510 cls.logger.error("Waiting for coredump to complete: %s", corefile)
511 curr_size = os.path.getsize(corefile)
512 deadline = time.time() + 60
514 while time.time() < deadline:
517 curr_size = os.path.getsize(corefile)
518 if size == curr_size:
522 cls.logger.error("Timed out waiting for coredump to complete:"
525 cls.logger.error("Coredump complete: %s, size %d",
531 Perform class setup before running the testcase
532 Remove shared memory files, start vpp and connect the vpp-api
534 super(VppTestCase, cls).setUpClass()
535 gc.collect() # run garbage collection first
536 cls.logger = get_logger(cls.__name__)
537 seed = os.environ["RND_SEED"]
539 if hasattr(cls, 'parallel_handler'):
540 cls.logger.addHandler(cls.parallel_handler)
541 cls.logger.propagate = False
543 cls.tempdir = tempfile.mkdtemp(
544 prefix='vpp-unittest-%s-' % cls.__name__)
545 cls.stats_sock = "%s/stats.sock" % cls.tempdir
546 cls.api_sock = "%s/api.sock" % cls.tempdir
547 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
548 cls.file_handler.setFormatter(
549 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
551 cls.file_handler.setLevel(DEBUG)
552 cls.logger.addHandler(cls.file_handler)
553 cls.logger.debug("--- setUpClass() for %s called ---" %
555 cls.shm_prefix = os.path.basename(cls.tempdir)
556 os.chdir(cls.tempdir)
557 cls.logger.info("Temporary dir is %s, shm prefix is %s",
558 cls.tempdir, cls.shm_prefix)
559 cls.logger.debug("Random seed is %s" % seed)
561 cls.reset_packet_infos()
565 cls.registry = VppObjectRegistry()
566 cls.vpp_startup_failed = False
567 cls.reporter = KeepAliveReporter()
568 # need to catch exceptions here because if we raise, then the cleanup
569 # doesn't get called and we might end with a zombie vpp
572 cls.reporter.send_keep_alive(cls, 'setUpClass')
573 VppTestResult.current_test_case_info = TestCaseInfo(
574 cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin)
575 cls.vpp_stdout_deque = deque()
576 cls.vpp_stderr_deque = deque()
577 cls.pump_thread_stop_flag = Event()
578 cls.pump_thread_wakeup_pipe = os.pipe()
579 cls.pump_thread = Thread(target=pump_output, args=(cls,))
580 cls.pump_thread.daemon = True
581 cls.pump_thread.start()
582 if cls.debug_gdb or cls.debug_gdbserver:
583 cls.vapi_response_timeout = 0
584 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls,
585 cls.vapi_response_timeout)
587 hook = hookmodule.StepHook(cls)
589 hook = hookmodule.PollHook(cls)
590 cls.vapi.register_hook(hook)
591 cls.statistics = VPPStats(socketname=cls.stats_sock)
595 cls.vpp_startup_failed = True
597 "VPP died shortly after startup, check the"
598 " output to standard error for possible cause")
602 except vpp_papi.VPPIOError as e:
603 cls.logger.debug("Exception connecting to vapi: %s" % e)
604 cls.vapi.disconnect()
606 if cls.debug_gdbserver:
607 print(colorize("You're running VPP inside gdbserver but "
608 "VPP-API connection failed, did you forget "
609 "to 'continue' VPP from within gdb?", RED))
611 except vpp_papi.VPPRuntimeError as e:
612 cls.logger.debug("%s" % e)
615 except Exception as e:
616 cls.logger.debug("Exception connecting to VPP: %s" % e)
621 def _debug_quit(cls):
622 if (cls.debug_gdbserver or cls.debug_gdb):
626 if cls.vpp.returncode is None:
628 print(double_line_delim)
629 print("VPP or GDB server is still running")
630 print(single_line_delim)
631 input("When done debugging, press ENTER to kill the "
632 "process and finish running the testcase...")
633 except AttributeError:
639 Disconnect vpp-api, kill vpp and cleanup shared memory files
643 # first signal that we want to stop the pump thread, then wake it up
644 if hasattr(cls, 'pump_thread_stop_flag'):
645 cls.pump_thread_stop_flag.set()
646 if hasattr(cls, 'pump_thread_wakeup_pipe'):
647 os.write(cls.pump_thread_wakeup_pipe[1], b'ding dong wake up')
648 if hasattr(cls, 'pump_thread'):
649 cls.logger.debug("Waiting for pump thread to stop")
650 cls.pump_thread.join()
651 if hasattr(cls, 'vpp_stderr_reader_thread'):
652 cls.logger.debug("Waiting for stderr pump to stop")
653 cls.vpp_stderr_reader_thread.join()
655 if hasattr(cls, 'vpp'):
656 if hasattr(cls, 'vapi'):
657 cls.logger.debug(cls.vapi.vpp.get_stats())
658 cls.logger.debug("Disconnecting class vapi client on %s",
660 cls.vapi.disconnect()
661 cls.logger.debug("Deleting class vapi attribute on %s",
665 if cls.vpp.returncode is None:
666 cls.wait_for_coredump()
667 cls.logger.debug("Sending TERM to vpp")
669 cls.logger.debug("Waiting for vpp to die")
670 cls.vpp.communicate()
671 cls.logger.debug("Deleting class vpp attribute on %s",
675 if cls.vpp_startup_failed:
676 stdout_log = cls.logger.info
677 stderr_log = cls.logger.critical
679 stdout_log = cls.logger.info
680 stderr_log = cls.logger.info
682 if hasattr(cls, 'vpp_stdout_deque'):
683 stdout_log(single_line_delim)
684 stdout_log('VPP output to stdout while running %s:', cls.__name__)
685 stdout_log(single_line_delim)
686 vpp_output = "".join(cls.vpp_stdout_deque)
687 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
689 stdout_log('\n%s', vpp_output)
690 stdout_log(single_line_delim)
692 if hasattr(cls, 'vpp_stderr_deque'):
693 stderr_log(single_line_delim)
694 stderr_log('VPP output to stderr while running %s:', cls.__name__)
695 stderr_log(single_line_delim)
696 vpp_output = "".join(cls.vpp_stderr_deque)
697 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
699 stderr_log('\n%s', vpp_output)
700 stderr_log(single_line_delim)
703 def tearDownClass(cls):
704 """ Perform final cleanup after running all tests in this test-case """
705 cls.logger.debug("--- tearDownClass() for %s called ---" %
707 cls.reporter.send_keep_alive(cls, 'tearDownClass')
709 cls.file_handler.close()
710 cls.reset_packet_infos()
712 debug_internal.on_tear_down_class(cls)
714 def show_commands_at_teardown(self):
715 """ Allow subclass specific teardown logging additions."""
716 self.logger.info("--- No test specific show commands provided. ---")
719 """ Show various debug prints after each test """
720 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
721 (self.__class__.__name__, self._testMethodName,
722 self._testMethodDoc))
725 if not self.vpp_dead:
726 self.logger.debug(self.vapi.cli("show trace max 1000"))
727 self.logger.info(self.vapi.ppcli("show interface"))
728 self.logger.info(self.vapi.ppcli("show hardware"))
729 self.logger.info(self.statistics.set_errors_str())
730 self.logger.info(self.vapi.ppcli("show run"))
731 self.logger.info(self.vapi.ppcli("show log"))
732 self.logger.info(self.vapi.ppcli("show bihash"))
733 self.logger.info("Logging testcase specific show commands.")
734 self.show_commands_at_teardown()
735 self.registry.remove_vpp_config(self.logger)
736 # Save/Dump VPP api trace log
737 m = self._testMethodName
738 api_trace = "vpp_api_trace.%s.%d.log" % (m, self.vpp.pid)
739 tmp_api_trace = "/tmp/%s" % api_trace
740 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
741 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
742 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
744 os.rename(tmp_api_trace, vpp_api_trace_log)
745 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
747 except VppTransportShmemIOError:
748 self.logger.debug("VppTransportShmemIOError: Vpp dead. "
749 "Cannot log show commands.")
752 self.registry.unregister_all(self.logger)
755 """ Clear trace before running each test"""
756 super(VppTestCase, self).setUp()
757 self.reporter.send_keep_alive(self)
760 raise VppDiedError(rv=None, testcase=self.__class__.__name__,
761 method_name=self._testMethodName)
762 self.sleep(.1, "during setUp")
763 self.vpp_stdout_deque.append(
764 "--- test setUp() for %s.%s(%s) starts here ---\n" %
765 (self.__class__.__name__, self._testMethodName,
766 self._testMethodDoc))
767 self.vpp_stderr_deque.append(
768 "--- test setUp() for %s.%s(%s) starts here ---\n" %
769 (self.__class__.__name__, self._testMethodName,
770 self._testMethodDoc))
771 self.vapi.cli("clear trace")
772 # store the test instance inside the test class - so that objects
773 # holding the class can access instance methods (like assertEqual)
774 type(self).test_instance = self
777 def pg_enable_capture(cls, interfaces=None):
779 Enable capture on packet-generator interfaces
781 :param interfaces: iterable interface indexes (if None,
782 use self.pg_interfaces)
785 if interfaces is None:
786 interfaces = cls.pg_interfaces
791 def register_capture(cls, cap_name):
792 """ Register a capture in the testclass """
793 # add to the list of captures with current timestamp
794 cls._captures.append((time.time(), cap_name))
797 def get_vpp_time(cls):
798 # processes e.g. "Time now 2.190522, Wed, 11 Mar 2020 17:29:54 GMT"
799 # returns float("2.190522")
800 timestr = cls.vapi.cli('show clock')
801 head, sep, tail = timestr.partition(',')
802 head, sep, tail = head.partition('Time now')
806 def sleep_on_vpp_time(cls, sec):
807 """ Sleep according to time in VPP world """
808 # On a busy system with many processes
809 # we might end up with VPP time being slower than real world
810 # So take that into account when waiting for VPP to do something
811 start_time = cls.get_vpp_time()
812 while cls.get_vpp_time() - start_time < sec:
817 """ Enable the PG, wait till it is done, then clean up """
818 cls.vapi.cli("trace add pg-input 1000")
819 cls.vapi.cli('packet-generator enable')
820 # PG, when starts, runs to completion -
821 # so let's avoid a race condition,
822 # and wait a little till it's done.
823 # Then clean it up - and then be gone.
824 deadline = time.time() + 300
825 while cls.vapi.cli('show packet-generator').find("Yes") != -1:
826 cls.sleep(0.01) # yield
827 if time.time() > deadline:
828 cls.logger.error("Timeout waiting for pg to stop")
830 for stamp, cap_name in cls._captures:
831 cls.vapi.cli('packet-generator delete %s' % cap_name)
835 def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0):
837 Create packet-generator interfaces.
839 :param interfaces: iterable indexes of the interfaces.
840 :returns: List of created interfaces.
845 intf = VppPGInterface(cls, i, gso, gso_size)
846 setattr(cls, intf.name, intf)
848 cls.pg_interfaces = result
852 def create_loopback_interfaces(cls, count):
854 Create loopback interfaces.
856 :param count: number of interfaces created.
857 :returns: List of created interfaces.
859 result = [VppLoInterface(cls) for i in range(count)]
861 setattr(cls, intf.name, intf)
862 cls.lo_interfaces = result
866 def create_bvi_interfaces(cls, count):
868 Create BVI interfaces.
870 :param count: number of interfaces created.
871 :returns: List of created interfaces.
873 result = [VppBviInterface(cls) for i in range(count)]
875 setattr(cls, intf.name, intf)
876 cls.bvi_interfaces = result
880 def extend_packet(packet, size, padding=' '):
882 Extend packet to given size by padding with spaces or custom padding
883 NOTE: Currently works only when Raw layer is present.
885 :param packet: packet
886 :param size: target size
887 :param padding: padding used to extend the payload
890 packet_len = len(packet) + 4
891 extend = size - packet_len
893 num = (extend // len(padding)) + 1
894 packet[Raw].load += (padding * num)[:extend].encode("ascii")
897 def reset_packet_infos(cls):
898 """ Reset the list of packet info objects and packet counts to zero """
899 cls._packet_infos = {}
900 cls._packet_count_for_dst_if_idx = {}
903 def create_packet_info(cls, src_if, dst_if):
905 Create packet info object containing the source and destination indexes
906 and add it to the testcase's packet info list
908 :param VppInterface src_if: source interface
909 :param VppInterface dst_if: destination interface
911 :returns: _PacketInfo object
915 info.index = len(cls._packet_infos)
916 info.src = src_if.sw_if_index
917 info.dst = dst_if.sw_if_index
918 if isinstance(dst_if, VppSubInterface):
919 dst_idx = dst_if.parent.sw_if_index
921 dst_idx = dst_if.sw_if_index
922 if dst_idx in cls._packet_count_for_dst_if_idx:
923 cls._packet_count_for_dst_if_idx[dst_idx] += 1
925 cls._packet_count_for_dst_if_idx[dst_idx] = 1
926 cls._packet_infos[info.index] = info
930 def info_to_payload(info):
932 Convert _PacketInfo object to packet payload
934 :param info: _PacketInfo object
936 :returns: string containing serialized data from packet info
938 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
942 def payload_to_info(payload, payload_field='load'):
944 Convert packet payload to _PacketInfo object
946 :param payload: packet payload
947 :type payload: <class 'scapy.packet.Raw'>
948 :param payload_field: packet fieldname of payload "load" for
949 <class 'scapy.packet.Raw'>
950 :type payload_field: str
951 :returns: _PacketInfo object containing de-serialized data from payload
954 numbers = getattr(payload, payload_field).split()
956 info.index = int(numbers[0])
957 info.src = int(numbers[1])
958 info.dst = int(numbers[2])
959 info.ip = int(numbers[3])
960 info.proto = int(numbers[4])
963 def get_next_packet_info(self, info):
965 Iterate over the packet info list stored in the testcase
966 Start iteration with first element if info is None
967 Continue based on index in info if info is specified
969 :param info: info or None
970 :returns: next info in list or None if no more infos
975 next_index = info.index + 1
976 if next_index == len(self._packet_infos):
979 return self._packet_infos[next_index]
981 def get_next_packet_info_for_interface(self, src_index, info):
983 Search the packet info list for the next packet info with same source
986 :param src_index: source interface index to search for
987 :param info: packet info - where to start the search
988 :returns: packet info or None
992 info = self.get_next_packet_info(info)
995 if info.src == src_index:
998 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
1000 Search the packet info list for the next packet info with same source
1001 and destination interface indexes
1003 :param src_index: source interface index to search for
1004 :param dst_index: destination interface index to search for
1005 :param info: packet info - where to start the search
1006 :returns: packet info or None
1010 info = self.get_next_packet_info_for_interface(src_index, info)
1013 if info.dst == dst_index:
1016 def assert_equal(self, real_value, expected_value, name_or_class=None):
1017 if name_or_class is None:
1018 self.assertEqual(real_value, expected_value)
1021 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
1022 msg = msg % (getdoc(name_or_class).strip(),
1023 real_value, str(name_or_class(real_value)),
1024 expected_value, str(name_or_class(expected_value)))
1026 msg = "Invalid %s: %s does not match expected value %s" % (
1027 name_or_class, real_value, expected_value)
1029 self.assertEqual(real_value, expected_value, msg)
1031 def assert_in_range(self,
1039 msg = "Invalid %s: %s out of range <%s,%s>" % (
1040 name, real_value, expected_min, expected_max)
1041 self.assertTrue(expected_min <= real_value <= expected_max, msg)
1043 def assert_packet_checksums_valid(self, packet,
1044 ignore_zero_udp_checksums=True):
1045 received = packet.__class__(scapy.compat.raw(packet))
1046 udp_layers = ['UDP', 'UDPerror']
1047 checksum_fields = ['cksum', 'chksum']
1050 temp = received.__class__(scapy.compat.raw(received))
1052 layer = temp.getlayer(counter)
1054 layer = layer.copy()
1055 layer.remove_payload()
1056 for cf in checksum_fields:
1057 if hasattr(layer, cf):
1058 if ignore_zero_udp_checksums and \
1059 0 == getattr(layer, cf) and \
1060 layer.name in udp_layers:
1062 delattr(temp.getlayer(counter), cf)
1063 checksums.append((counter, cf))
1066 counter = counter + 1
1067 if 0 == len(checksums):
1069 temp = temp.__class__(scapy.compat.raw(temp))
1070 for layer, cf in checksums:
1071 calc_sum = getattr(temp[layer], cf)
1073 getattr(received[layer], cf), calc_sum,
1074 "packet checksum on layer #%d: %s" % (layer, temp[layer].name))
1076 "Checksum field `%s` on `%s` layer has correct value `%s`" %
1077 (cf, temp[layer].name, calc_sum))
1079 def assert_checksum_valid(self, received_packet, layer,
1080 field_name='chksum',
1081 ignore_zero_checksum=False):
1082 """ Check checksum of received packet on given layer """
1083 received_packet_checksum = getattr(received_packet[layer], field_name)
1084 if ignore_zero_checksum and 0 == received_packet_checksum:
1086 recalculated = received_packet.__class__(
1087 scapy.compat.raw(received_packet))
1088 delattr(recalculated[layer], field_name)
1089 recalculated = recalculated.__class__(scapy.compat.raw(recalculated))
1090 self.assert_equal(received_packet_checksum,
1091 getattr(recalculated[layer], field_name),
1092 "packet checksum on layer: %s" % layer)
1094 def assert_ip_checksum_valid(self, received_packet,
1095 ignore_zero_checksum=False):
1096 self.assert_checksum_valid(received_packet, 'IP',
1097 ignore_zero_checksum=ignore_zero_checksum)
1099 def assert_tcp_checksum_valid(self, received_packet,
1100 ignore_zero_checksum=False):
1101 self.assert_checksum_valid(received_packet, 'TCP',
1102 ignore_zero_checksum=ignore_zero_checksum)
1104 def assert_udp_checksum_valid(self, received_packet,
1105 ignore_zero_checksum=True):
1106 self.assert_checksum_valid(received_packet, 'UDP',
1107 ignore_zero_checksum=ignore_zero_checksum)
1109 def assert_embedded_icmp_checksum_valid(self, received_packet):
1110 if received_packet.haslayer(IPerror):
1111 self.assert_checksum_valid(received_packet, 'IPerror')
1112 if received_packet.haslayer(TCPerror):
1113 self.assert_checksum_valid(received_packet, 'TCPerror')
1114 if received_packet.haslayer(UDPerror):
1115 self.assert_checksum_valid(received_packet, 'UDPerror',
1116 ignore_zero_checksum=True)
1117 if received_packet.haslayer(ICMPerror):
1118 self.assert_checksum_valid(received_packet, 'ICMPerror')
1120 def assert_icmp_checksum_valid(self, received_packet):
1121 self.assert_checksum_valid(received_packet, 'ICMP')
1122 self.assert_embedded_icmp_checksum_valid(received_packet)
1124 def assert_icmpv6_checksum_valid(self, pkt):
1125 if pkt.haslayer(ICMPv6DestUnreach):
1126 self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
1127 self.assert_embedded_icmp_checksum_valid(pkt)
1128 if pkt.haslayer(ICMPv6EchoRequest):
1129 self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
1130 if pkt.haslayer(ICMPv6EchoReply):
1131 self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
1133 def get_packet_counter(self, counter):
1134 if counter.startswith("/"):
1135 counter_value = self.statistics.get_counter(counter)
1137 counters = self.vapi.cli("sh errors").split('\n')
1139 for i in range(1, len(counters) - 1):
1140 results = counters[i].split()
1141 if results[1] == counter:
1142 counter_value = int(results[0])
1144 return counter_value
1146 def assert_packet_counter_equal(self, counter, expected_value):
1147 counter_value = self.get_packet_counter(counter)
1148 self.assert_equal(counter_value, expected_value,
1149 "packet counter `%s'" % counter)
1151 def assert_error_counter_equal(self, counter, expected_value):
1152 counter_value = self.statistics.get_err_counter(counter)
1153 self.assert_equal(counter_value, expected_value,
1154 "error counter `%s'" % counter)
1157 def sleep(cls, timeout, remark=None):
1159 # /* Allow sleep(0) to maintain win32 semantics, and as decreed
1160 # * by Guido, only the main thread can be interrupted.
1162 # https://github.com/python/cpython/blob/6673decfa0fb078f60587f5cb5e98460eea137c2/Modules/timemodule.c#L1892 # noqa
1165 if hasattr(os, 'sched_yield'):
1171 cls.logger.debug("Starting sleep for %es (%s)", timeout, remark)
1172 before = time.time()
1175 if after - before > 2 * timeout:
1176 cls.logger.error("unexpected self.sleep() result - "
1177 "slept for %es instead of ~%es!",
1178 after - before, timeout)
1181 "Finished sleep (%s) - slept %es (wanted %es)",
1182 remark, after - before, timeout)
1184 def pg_send(self, intf, pkts, worker=None):
1185 self.vapi.cli("clear trace")
1186 intf.add_stream(pkts, worker=worker)
1187 self.pg_enable_capture(self.pg_interfaces)
1190 def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
1191 self.pg_send(intf, pkts)
1194 for i in self.pg_interfaces:
1195 i.get_capture(0, timeout=timeout)
1196 i.assert_nothing_captured(remark=remark)
1199 def send_and_expect(self, intf, pkts, output, n_rx=None, worker=None):
1202 self.pg_send(intf, pkts, worker=worker)
1203 rx = output.get_capture(n_rx)
1206 def send_and_expect_only(self, intf, pkts, output, timeout=None):
1207 self.pg_send(intf, pkts)
1208 rx = output.get_capture(len(pkts))
1212 for i in self.pg_interfaces:
1213 if i not in outputs:
1214 i.get_capture(0, timeout=timeout)
1215 i.assert_nothing_captured()
1221 def get_testcase_doc_name(test):
1222 return getdoc(test.__class__).splitlines()[0]
1225 def get_test_description(descriptions, test):
1226 short_description = test.shortDescription()
1227 if descriptions and short_description:
1228 return short_description
1233 class TestCaseInfo(object):
1234 def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
1235 self.logger = logger
1236 self.tempdir = tempdir
1237 self.vpp_pid = vpp_pid
1238 self.vpp_bin_path = vpp_bin_path
1239 self.core_crash_test = None
1242 class VppTestResult(unittest.TestResult):
1244 @property result_string
1245 String variable to store the test case result string.
1247 List variable containing 2-tuples of TestCase instances and strings
1248 holding formatted tracebacks. Each tuple represents a test which
1249 raised an unexpected exception.
1251 List variable containing 2-tuples of TestCase instances and strings
1252 holding formatted tracebacks. Each tuple represents a test where
1253 a failure was explicitly signalled using the TestCase.assert*()
1257 failed_test_cases_info = set()
1258 core_crash_test_cases_info = set()
1259 current_test_case_info = None
1261 def __init__(self, stream=None, descriptions=None, verbosity=None,
1264 :param stream File descriptor to store where to report test results.
1265 Set to the standard error stream by default.
1266 :param descriptions Boolean variable to store information if to use
1267 test case descriptions.
1268 :param verbosity Integer variable to store required verbosity level.
1270 super(VppTestResult, self).__init__(stream, descriptions, verbosity)
1271 self.stream = stream
1272 self.descriptions = descriptions
1273 self.verbosity = verbosity
1274 self.result_string = None
1275 self.runner = runner
1277 def addSuccess(self, test):
1279 Record a test succeeded result
1284 if self.current_test_case_info:
1285 self.current_test_case_info.logger.debug(
1286 "--- addSuccess() %s.%s(%s) called" % (test.__class__.__name__,
1287 test._testMethodName,
1288 test._testMethodDoc))
1289 unittest.TestResult.addSuccess(self, test)
1290 self.result_string = colorize("OK", GREEN)
1292 self.send_result_through_pipe(test, PASS)
1294 def addSkip(self, test, reason):
1296 Record a test skipped.
1302 if self.current_test_case_info:
1303 self.current_test_case_info.logger.debug(
1304 "--- addSkip() %s.%s(%s) called, reason is %s" %
1305 (test.__class__.__name__, test._testMethodName,
1306 test._testMethodDoc, reason))
1307 unittest.TestResult.addSkip(self, test, reason)
1308 self.result_string = colorize("SKIP", YELLOW)
1310 self.send_result_through_pipe(test, SKIP)
1312 def symlink_failed(self):
1313 if self.current_test_case_info:
1315 failed_dir = os.getenv('FAILED_DIR')
1316 link_path = os.path.join(
1319 os.path.basename(self.current_test_case_info.tempdir))
1321 self.current_test_case_info.logger.debug(
1322 "creating a link to the failed test")
1323 self.current_test_case_info.logger.debug(
1324 "os.symlink(%s, %s)" %
1325 (self.current_test_case_info.tempdir, link_path))
1326 if os.path.exists(link_path):
1327 self.current_test_case_info.logger.debug(
1328 'symlink already exists')
1330 os.symlink(self.current_test_case_info.tempdir, link_path)
1332 except Exception as e:
1333 self.current_test_case_info.logger.error(e)
1335 def send_result_through_pipe(self, test, result):
1336 if hasattr(self, 'test_framework_result_pipe'):
1337 pipe = self.test_framework_result_pipe
1339 pipe.send((test.id(), result))
1341 def log_error(self, test, err, fn_name):
1342 if self.current_test_case_info:
1343 if isinstance(test, unittest.suite._ErrorHolder):
1344 test_name = test.description
1346 test_name = '%s.%s(%s)' % (test.__class__.__name__,
1347 test._testMethodName,
1348 test._testMethodDoc)
1349 self.current_test_case_info.logger.debug(
1350 "--- %s() %s called, err is %s" %
1351 (fn_name, test_name, err))
1352 self.current_test_case_info.logger.debug(
1353 "formatted exception is:\n%s" %
1354 "".join(format_exception(*err)))
1356 def add_error(self, test, err, unittest_fn, error_type):
1357 if error_type == FAIL:
1358 self.log_error(test, err, 'addFailure')
1359 error_type_str = colorize("FAIL", RED)
1360 elif error_type == ERROR:
1361 self.log_error(test, err, 'addError')
1362 error_type_str = colorize("ERROR", RED)
1364 raise Exception('Error type %s cannot be used to record an '
1365 'error or a failure' % error_type)
1367 unittest_fn(self, test, err)
1368 if self.current_test_case_info:
1369 self.result_string = "%s [ temp dir used by test case: %s ]" % \
1371 self.current_test_case_info.tempdir)
1372 self.symlink_failed()
1373 self.failed_test_cases_info.add(self.current_test_case_info)
1374 if is_core_present(self.current_test_case_info.tempdir):
1375 if not self.current_test_case_info.core_crash_test:
1376 if isinstance(test, unittest.suite._ErrorHolder):
1377 test_name = str(test)
1379 test_name = "'{!s}' ({!s})".format(
1380 get_testcase_doc_name(test), test.id())
1381 self.current_test_case_info.core_crash_test = test_name
1382 self.core_crash_test_cases_info.add(
1383 self.current_test_case_info)
1385 self.result_string = '%s [no temp dir]' % error_type_str
1387 self.send_result_through_pipe(test, error_type)
1389 def addFailure(self, test, err):
1391 Record a test failed result
1394 :param err: error message
1397 self.add_error(test, err, unittest.TestResult.addFailure, FAIL)
1399 def addError(self, test, err):
1401 Record a test error result
1404 :param err: error message
1407 self.add_error(test, err, unittest.TestResult.addError, ERROR)
1409 def getDescription(self, test):
1411 Get test description
1414 :returns: test description
1417 return get_test_description(self.descriptions, test)
1419 def startTest(self, test):
1427 def print_header(test):
1428 test_doc = getdoc(test)
1430 raise Exception("No doc string for test '%s'" % test.id())
1431 test_title = test_doc.splitlines()[0]
1432 test_title_colored = colorize(test_title, GREEN)
1433 if test.is_tagged_run_solo():
1434 # long live PEP-8 and 80 char width limitation...
1436 test_title_colored = colorize("SOLO RUN: " + test_title, c)
1438 if not hasattr(test.__class__, '_header_printed'):
1439 print(double_line_delim)
1440 print(test_title_colored)
1441 print(double_line_delim)
1442 test.__class__._header_printed = True
1445 self.start_test = time.time()
1446 unittest.TestResult.startTest(self, test)
1447 if self.verbosity > 0:
1448 self.stream.writeln(
1449 "Starting " + self.getDescription(test) + " ...")
1450 self.stream.writeln(single_line_delim)
1452 def stopTest(self, test):
1454 Called when the given test has been run
1459 unittest.TestResult.stopTest(self, test)
1461 if self.verbosity > 0:
1462 self.stream.writeln(single_line_delim)
1463 self.stream.writeln("%-73s%s" % (self.getDescription(test),
1464 self.result_string))
1465 self.stream.writeln(single_line_delim)
1467 self.stream.writeln("%-68s %4.2f %s" %
1468 (self.getDescription(test),
1469 time.time() - self.start_test,
1470 self.result_string))
1472 self.send_result_through_pipe(test, TEST_RUN)
1474 def printErrors(self):
1476 Print errors from running the test case
1478 if len(self.errors) > 0 or len(self.failures) > 0:
1479 self.stream.writeln()
1480 self.printErrorList('ERROR', self.errors)
1481 self.printErrorList('FAIL', self.failures)
1483 # ^^ that is the last output from unittest before summary
1484 if not self.runner.print_summary:
1485 devnull = unittest.runner._WritelnDecorator(open(os.devnull, 'w'))
1486 self.stream = devnull
1487 self.runner.stream = devnull
1489 def printErrorList(self, flavour, errors):
1491 Print error list to the output stream together with error type
1492 and test case description.
1494 :param flavour: error type
1495 :param errors: iterable errors
1498 for test, err in errors:
1499 self.stream.writeln(double_line_delim)
1500 self.stream.writeln("%s: %s" %
1501 (flavour, self.getDescription(test)))
1502 self.stream.writeln(single_line_delim)
1503 self.stream.writeln("%s" % err)
1506 class VppTestRunner(unittest.TextTestRunner):
1508 A basic test runner implementation which prints results to standard error.
1512 def resultclass(self):
1513 """Class maintaining the results of the tests"""
1514 return VppTestResult
1516 def __init__(self, keep_alive_pipe=None, descriptions=True, verbosity=1,
1517 result_pipe=None, failfast=False, buffer=False,
1518 resultclass=None, print_summary=True, **kwargs):
1519 # ignore stream setting here, use hard-coded stdout to be in sync
1520 # with prints from VppTestCase methods ...
1521 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1522 verbosity, failfast, buffer,
1523 resultclass, **kwargs)
1524 KeepAliveReporter.pipe = keep_alive_pipe
1526 self.orig_stream = self.stream
1527 self.resultclass.test_framework_result_pipe = result_pipe
1529 self.print_summary = print_summary
1531 def _makeResult(self):
1532 return self.resultclass(self.stream,
1537 def run(self, test):
1544 faulthandler.enable() # emit stack trace to stderr if killed by signal
1546 result = super(VppTestRunner, self).run(test)
1547 if not self.print_summary:
1548 self.stream = self.orig_stream
1549 result.stream = self.orig_stream
1553 class Worker(Thread):
1554 def __init__(self, executable_args, logger, env=None, *args, **kwargs):
1555 super(Worker, self).__init__(*args, **kwargs)
1556 self.logger = logger
1557 self.args = executable_args
1558 if hasattr(self, 'testcase') and self.testcase.debug_all:
1559 if self.testcase.debug_gdbserver:
1560 self.args = ['/usr/bin/gdbserver', 'localhost:{port}'
1561 .format(port=self.testcase.gdbserver_port)] + args
1562 elif self.testcase.debug_gdb and hasattr(self, 'wait_for_gdb'):
1563 self.args.append(self.wait_for_gdb)
1564 self.app_bin = executable_args[0]
1565 self.app_name = os.path.basename(self.app_bin)
1566 if hasattr(self, 'role'):
1567 self.app_name += ' {role}'.format(role=self.role)
1570 env = {} if env is None else env
1571 self.env = copy.deepcopy(env)
1573 def wait_for_enter(self):
1574 if not hasattr(self, 'testcase'):
1576 if self.testcase.debug_all and self.testcase.debug_gdbserver:
1578 print(double_line_delim)
1579 print("Spawned GDB Server for '{app}' with PID: {pid}"
1580 .format(app=self.app_name, pid=self.process.pid))
1581 elif self.testcase.debug_all and self.testcase.debug_gdb:
1583 print(double_line_delim)
1584 print("Spawned '{app}' with PID: {pid}"
1585 .format(app=self.app_name, pid=self.process.pid))
1588 print(single_line_delim)
1589 print("You can debug '{app}' using:".format(app=self.app_name))
1590 if self.testcase.debug_gdbserver:
1591 print("sudo gdb " + self.app_bin +
1592 " -ex 'target remote localhost:{port}'"
1593 .format(port=self.testcase.gdbserver_port))
1594 print("Now is the time to attach gdb by running the above "
1595 "command, set up breakpoints etc., then resume from "
1596 "within gdb by issuing the 'continue' command")
1597 self.testcase.gdbserver_port += 1
1598 elif self.testcase.debug_gdb:
1599 print("sudo gdb " + self.app_bin +
1600 " -ex 'attach {pid}'".format(pid=self.process.pid))
1601 print("Now is the time to attach gdb by running the above "
1602 "command and set up breakpoints etc., then resume from"
1603 " within gdb by issuing the 'continue' command")
1604 print(single_line_delim)
1605 input("Press ENTER to continue running the testcase...")
1608 executable = self.args[0]
1609 if not os.path.exists(executable) or not os.access(
1610 executable, os.F_OK | os.X_OK):
1611 # Exit code that means some system file did not exist,
1612 # could not be opened, or had some other kind of error.
1613 self.result = os.EX_OSFILE
1614 raise EnvironmentError(
1615 "executable '%s' is not found or executable." % executable)
1616 self.logger.debug("Running executable: '{app}'"
1617 .format(app=' '.join(self.args)))
1618 env = os.environ.copy()
1619 env.update(self.env)
1620 env["CK_LOG_FILE_NAME"] = "-"
1621 self.process = subprocess.Popen(
1622 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1623 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1624 self.wait_for_enter()
1625 out, err = self.process.communicate()
1626 self.logger.debug("Finished running `{app}'".format(app=self.app_name))
1627 self.logger.info("Return code is `%s'" % self.process.returncode)
1628 self.logger.info(single_line_delim)
1629 self.logger.info("Executable `{app}' wrote to stdout:"
1630 .format(app=self.app_name))
1631 self.logger.info(single_line_delim)
1632 self.logger.info(out.decode('utf-8'))
1633 self.logger.info(single_line_delim)
1634 self.logger.info("Executable `{app}' wrote to stderr:"
1635 .format(app=self.app_name))
1636 self.logger.info(single_line_delim)
1637 self.logger.info(err.decode('utf-8'))
1638 self.logger.info(single_line_delim)
1639 self.result = self.process.returncode
1642 if __name__ == '__main__':