blob: fbe6c370a3fc2a84ab31105e4decf31e9fc401cf [file] [log] [blame]
Damjan Marionf56b77a2016-10-03 19:44:57 +02001#!/usr/bin/env python
Damjan Marionf56b77a2016-10-03 19:44:57 +02002
Klement Sekeraacb9b8e2017-02-14 02:55:31 +01003from __future__ import print_function
4import gc
5import sys
6import os
7import select
Damjan Marionf56b77a2016-10-03 19:44:57 +02008import unittest
Klement Sekeraf62ae122016-10-11 11:47:09 +02009import tempfile
Klement Sekera277b89c2016-10-28 13:20:27 +020010import time
Klement Sekera3658adc2017-06-07 08:19:47 +020011import faulthandler
Klement Sekera6a6f4f72017-11-09 09:16:39 +010012import random
Dave Wallace42996c02018-02-26 14:40:13 -050013import copy
juraj.linkes184870a2018-07-16 14:22:01 +020014import psutil
Klement Sekerae4504c62016-12-08 10:16:41 +010015from collections import deque
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010016from threading import Thread, Event
Klement Sekera909a6a12017-08-08 04:33:53 +020017from inspect import getdoc, isclass
Klement Sekerab91017a2017-02-09 06:04:36 +010018from traceback import format_exception
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010019from logging import FileHandler, DEBUG, Formatter
20from scapy.packet import Raw
Klement Sekera13a83ef2018-03-21 12:35:51 +010021from hook import StepHook, PollHook, VppDiedError
Klement Sekeraf62ae122016-10-11 11:47:09 +020022from vpp_pg_interface import VppPGInterface
Klement Sekeradab231a2016-12-21 08:50:14 +010023from vpp_sub_interface import VppSubInterface
Matej Klotton0178d522016-11-04 11:11:44 +010024from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020025from vpp_papi_provider import VppPapiProvider
Ole Troan73202102018-08-31 00:29:48 +020026from vpp_papi.vpp_stats import VPPStats
Klement Sekera05742262018-03-14 18:14:49 +010027from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
28 getLogger, colorize
Klement Sekera10db26f2017-01-11 08:16:53 +010029from vpp_object import VppObjectRegistry
juraj.linkes40dd73b2018-09-21 13:55:16 +020030from util import ppp, is_core_present
Klement Sekerad81ae412018-05-16 10:52:54 +020031from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
32from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
33from scapy.layers.inet6 import ICMPv6EchoReply
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010034if os.name == 'posix' and sys.version_info[0] < 3:
35 # using subprocess32 is recommended by python official documentation
36 # @ https://docs.python.org/2/library/subprocess.html
37 import subprocess32 as subprocess
38else:
39 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020040
Klement Sekerad81ae412018-05-16 10:52:54 +020041
juraj.linkescae64f82018-09-19 15:01:47 +020042PASS = 0
43FAIL = 1
44ERROR = 2
45SKIP = 3
46TEST_RUN = 4
47
48
Klement Sekeraebbaf552018-02-17 13:41:33 +010049debug_framework = False
50if os.getenv('TEST_DEBUG', "0") == "1":
51 debug_framework = True
52 import debug_internal
53
54
Klement Sekeraf62ae122016-10-11 11:47:09 +020055"""
56 Test framework module.
57
58 The module provides a set of tools for constructing and running tests and
59 representing the results.
60"""
61
Klement Sekeraf62ae122016-10-11 11:47:09 +020062
Damjan Marionf56b77a2016-10-03 19:44:57 +020063class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020064 """Private class to create packet info object.
65
66 Help process information about the next packet.
67 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020068 """
Matej Klotton86d87c42016-11-11 11:38:55 +010069 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020070 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010071 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020072 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010073 #: Store the index of the destination packet generator interface
74 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020075 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +010076 #: Store expected ip version
77 ip = -1
78 #: Store expected upper protocol
79 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010080 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020081 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020082
Matej Klotton16a14cd2016-12-07 15:09:13 +010083 def __eq__(self, other):
84 index = self.index == other.index
85 src = self.src == other.src
86 dst = self.dst == other.dst
87 data = self.data == other.data
88 return index and src and dst and data
89
Klement Sekeraf62ae122016-10-11 11:47:09 +020090
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010091def pump_output(testclass):
92 """ pump output from vpp stdout/stderr to proper queues """
Klement Sekera6a6f4f72017-11-09 09:16:39 +010093 stdout_fragment = ""
94 stderr_fragment = ""
Neale Ranns16782362018-07-23 05:35:56 -040095 while not testclass.pump_thread_stop_flag.is_set():
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010096 readable = select.select([testclass.vpp.stdout.fileno(),
97 testclass.vpp.stderr.fileno(),
98 testclass.pump_thread_wakeup_pipe[0]],
99 [], [])[0]
100 if testclass.vpp.stdout.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100101 read = os.read(testclass.vpp.stdout.fileno(), 102400)
102 if len(read) > 0:
103 split = read.splitlines(True)
104 if len(stdout_fragment) > 0:
105 split[0] = "%s%s" % (stdout_fragment, split[0])
106 if len(split) > 0 and split[-1].endswith("\n"):
107 limit = None
108 else:
109 limit = -1
110 stdout_fragment = split[-1]
111 testclass.vpp_stdout_deque.extend(split[:limit])
112 if not testclass.cache_vpp_output:
113 for line in split[:limit]:
114 testclass.logger.debug(
115 "VPP STDOUT: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100116 if testclass.vpp.stderr.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100117 read = os.read(testclass.vpp.stderr.fileno(), 102400)
118 if len(read) > 0:
119 split = read.splitlines(True)
120 if len(stderr_fragment) > 0:
121 split[0] = "%s%s" % (stderr_fragment, split[0])
122 if len(split) > 0 and split[-1].endswith("\n"):
123 limit = None
124 else:
125 limit = -1
126 stderr_fragment = split[-1]
127 testclass.vpp_stderr_deque.extend(split[:limit])
128 if not testclass.cache_vpp_output:
129 for line in split[:limit]:
130 testclass.logger.debug(
131 "VPP STDERR: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100132 # ignoring the dummy pipe here intentionally - the flag will take care
133 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +0200134
135
Klement Sekera87134932017-03-07 11:39:27 +0100136def running_extended_tests():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100137 s = os.getenv("EXTENDED_TESTS", "n")
138 return True if s.lower() in ("y", "yes", "1") else False
Klement Sekera87134932017-03-07 11:39:27 +0100139
140
Klement Sekerad3e671e2017-09-29 12:36:37 +0200141def running_on_centos():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100142 os_id = os.getenv("OS_ID", "")
143 return True if "centos" in os_id.lower() else False
Klement Sekerad3e671e2017-09-29 12:36:37 +0200144
145
Klement Sekera909a6a12017-08-08 04:33:53 +0200146class KeepAliveReporter(object):
147 """
148 Singleton object which reports test start to parent process
149 """
150 _shared_state = {}
151
152 def __init__(self):
153 self.__dict__ = self._shared_state
154
155 @property
156 def pipe(self):
157 return self._pipe
158
159 @pipe.setter
160 def pipe(self, pipe):
161 if hasattr(self, '_pipe'):
162 raise Exception("Internal error - pipe should only be set once.")
163 self._pipe = pipe
164
juraj.linkes40dd73b2018-09-21 13:55:16 +0200165 def send_keep_alive(self, test, desc=None):
Klement Sekera909a6a12017-08-08 04:33:53 +0200166 """
167 Write current test tmpdir & desc to keep-alive pipe to signal liveness
168 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200169 if self.pipe is None:
170 # if not running forked..
171 return
172
Klement Sekera909a6a12017-08-08 04:33:53 +0200173 if isclass(test):
juraj.linkes40dd73b2018-09-21 13:55:16 +0200174 desc = '%s (%s)' % (desc, unittest.util.strclass(test))
Klement Sekera909a6a12017-08-08 04:33:53 +0200175 else:
juraj.linkes40dd73b2018-09-21 13:55:16 +0200176 desc = test.id()
Klement Sekera909a6a12017-08-08 04:33:53 +0200177
Dave Wallacee2efd122017-09-30 22:04:21 -0400178 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200179
180
Damjan Marionf56b77a2016-10-03 19:44:57 +0200181class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100182 """This subclass is a base class for VPP test cases that are implemented as
183 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200184 """
185
186 @property
187 def packet_infos(self):
188 """List of packet infos"""
189 return self._packet_infos
190
Klement Sekeradab231a2016-12-21 08:50:14 +0100191 @classmethod
192 def get_packet_count_for_if_idx(cls, dst_if_index):
193 """Get the number of packet info for specified destination if index"""
194 if dst_if_index in cls._packet_count_for_dst_if_idx:
195 return cls._packet_count_for_dst_if_idx[dst_if_index]
196 else:
197 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200198
199 @classmethod
200 def instance(cls):
201 """Return the instance of this testcase"""
202 return cls.test_instance
203
Damjan Marionf56b77a2016-10-03 19:44:57 +0200204 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200205 def set_debug_flags(cls, d):
206 cls.debug_core = False
207 cls.debug_gdb = False
208 cls.debug_gdbserver = False
209 if d is None:
210 return
211 dl = d.lower()
212 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200213 cls.debug_core = True
214 elif dl == "gdb":
215 cls.debug_gdb = True
216 elif dl == "gdbserver":
217 cls.debug_gdbserver = True
218 else:
219 raise Exception("Unrecognized DEBUG option: '%s'" % d)
220
221 @classmethod
juraj.linkes184870a2018-07-16 14:22:01 +0200222 def get_least_used_cpu(self):
223 cpu_usage_list = [set(range(psutil.cpu_count()))]
224 vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name'])
225 if 'vpp_main' == p.info['name']]
226 for vpp_process in vpp_processes:
227 for cpu_usage_set in cpu_usage_list:
228 try:
229 cpu_num = vpp_process.cpu_num()
230 if cpu_num in cpu_usage_set:
231 cpu_usage_set_index = cpu_usage_list.index(
232 cpu_usage_set)
233 if cpu_usage_set_index == len(cpu_usage_list) - 1:
234 cpu_usage_list.append({cpu_num})
235 else:
236 cpu_usage_list[cpu_usage_set_index + 1].add(
237 cpu_num)
238 cpu_usage_set.remove(cpu_num)
239 break
240 except psutil.NoSuchProcess:
241 pass
242
243 for cpu_usage_set in cpu_usage_list:
244 if len(cpu_usage_set) > 0:
245 min_usage_set = cpu_usage_set
246 break
247
248 return random.choice(tuple(min_usage_set))
249
juraj.linkes40dd73b2018-09-21 13:55:16 +0200250 @staticmethod
251 def print_header(cls):
252 if not hasattr(cls, '_header_printed'):
253 print(double_line_delim)
254 print(colorize(getdoc(cls).splitlines()[0], GREEN))
255 print(double_line_delim)
256 cls._header_printed = True
257
juraj.linkes184870a2018-07-16 14:22:01 +0200258 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200259 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200260 """ Set-up the test case class based on environment variables """
Klement Sekera13a83ef2018-03-21 12:35:51 +0100261 s = os.getenv("STEP", "n")
262 cls.step = True if s.lower() in ("y", "yes", "1") else False
263 d = os.getenv("DEBUG", None)
264 c = os.getenv("CACHE_OUTPUT", "1")
265 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
Klement Sekera277b89c2016-10-28 13:20:27 +0200266 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200267 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100268 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100269 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
270 plugin_path = None
271 if cls.plugin_path is not None:
272 if cls.extern_plugin_path is not None:
273 plugin_path = "%s:%s" % (
274 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100275 else:
276 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100277 elif cls.extern_plugin_path is not None:
278 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100279 debug_cli = ""
280 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
281 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100282 coredump_size = None
Klement Sekera13a83ef2018-03-21 12:35:51 +0100283 size = os.getenv("COREDUMP_SIZE")
284 if size is not None:
285 coredump_size = "coredump-size %s" % size
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100286 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400287 coredump_size = "coredump-size unlimited"
juraj.linkes184870a2018-07-16 14:22:01 +0200288
289 cpu_core_number = cls.get_least_used_cpu()
290
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100291 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400292 "{", "nodaemon", debug_cli, "full-coredump",
Jakub Grajciar99743912018-10-09 12:28:21 +0200293 coredump_size, "runtime-dir", cls.tempdir, "}",
294 "api-trace", "{", "on", "}", "api-segment", "{",
295 "prefix", cls.shm_prefix, "}", "cpu", "{",
296 "main-core", str(cpu_core_number), "}", "statseg",
297 "{", "socket-name", cls.stats_sock, "}", "plugins",
298 "{", "plugin", "dpdk_plugin.so", "{", "disable",
299 "}", "plugin", "unittest_plugin.so", "{", "enable",
300 "}", "}", ]
Klement Sekera47e275b2017-03-21 08:21:25 +0100301 if plugin_path is not None:
302 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200303 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
304
305 @classmethod
306 def wait_for_enter(cls):
307 if cls.debug_gdbserver:
308 print(double_line_delim)
309 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
310 elif cls.debug_gdb:
311 print(double_line_delim)
312 print("Spawned VPP with PID: %d" % cls.vpp.pid)
313 else:
314 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
315 return
316 print(single_line_delim)
317 print("You can debug the VPP using e.g.:")
318 if cls.debug_gdbserver:
319 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
320 print("Now is the time to attach a gdb by running the above "
321 "command, set up breakpoints etc. and then resume VPP from "
322 "within gdb by issuing the 'continue' command")
323 elif cls.debug_gdb:
324 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
325 print("Now is the time to attach a gdb by running the above "
326 "command and set up breakpoints etc.")
327 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100328 raw_input("Press ENTER to continue running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200329
330 @classmethod
331 def run_vpp(cls):
332 cmdline = cls.vpp_cmdline
333
334 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100335 gdbserver = '/usr/bin/gdbserver'
336 if not os.path.isfile(gdbserver) or \
337 not os.access(gdbserver, os.X_OK):
338 raise Exception("gdbserver binary '%s' does not exist or is "
339 "not executable" % gdbserver)
340
341 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200342 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
343
Klement Sekera931be3a2016-11-03 05:36:01 +0100344 try:
345 cls.vpp = subprocess.Popen(cmdline,
346 stdout=subprocess.PIPE,
347 stderr=subprocess.PIPE,
348 bufsize=1)
349 except Exception as e:
350 cls.logger.critical("Couldn't start vpp: %s" % e)
351 raise
352
Klement Sekera277b89c2016-10-28 13:20:27 +0200353 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100354
Damjan Marionf56b77a2016-10-03 19:44:57 +0200355 @classmethod
Klement Sekera611864f2018-09-26 11:19:00 +0200356 def wait_for_stats_socket(cls):
357 deadline = time.time() + 3
358 while time.time() < deadline or cls.debug_gdb or cls.debug_gdbserver:
359 if os.path.exists(cls.stats_sock):
360 break
361
362 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200363 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200364 """
365 Perform class setup before running the testcase
366 Remove shared memory files, start vpp and connect the vpp-api
367 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100368 gc.collect() # run garbage collection first
Klement Sekera96867ba2018-02-02 11:27:53 +0100369 random.seed()
juraj.linkes40dd73b2018-09-21 13:55:16 +0200370 cls.print_header(cls)
juraj.linkes184870a2018-07-16 14:22:01 +0200371 if not hasattr(cls, 'logger'):
372 cls.logger = getLogger(cls.__name__)
373 else:
374 cls.logger.name = cls.__name__
Klement Sekeraf62ae122016-10-11 11:47:09 +0200375 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200376 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera611864f2018-09-26 11:19:00 +0200377 cls.stats_sock = "%s/stats.sock" % cls.tempdir
Klement Sekera027dbd52017-04-11 06:01:53 +0200378 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
379 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100380 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
381 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200382 cls.file_handler.setLevel(DEBUG)
383 cls.logger.addHandler(cls.file_handler)
juraj.linkes184870a2018-07-16 14:22:01 +0200384 cls.shm_prefix = os.path.basename(cls.tempdir)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200385 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200386 cls.logger.info("Temporary dir is %s, shm prefix is %s",
387 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200388 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100389 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100390 cls._captures = []
391 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200392 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100393 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100394 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200395 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200396 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200397 # need to catch exceptions here because if we raise, then the cleanup
398 # doesn't get called and we might end with a zombie vpp
399 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200400 cls.run_vpp()
juraj.linkes40dd73b2018-09-21 13:55:16 +0200401 cls.reporter.send_keep_alive(cls, 'setUpClass')
402 VppTestResult.current_test_case_info = TestCaseInfo(
403 cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin)
Klement Sekerae4504c62016-12-08 10:16:41 +0100404 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100405 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100406 cls.pump_thread_stop_flag = Event()
407 cls.pump_thread_wakeup_pipe = os.pipe()
408 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100409 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100410 cls.pump_thread.start()
Klement Sekera611864f2018-09-26 11:19:00 +0200411 if cls.debug_gdb or cls.debug_gdbserver:
412 read_timeout = 0
413 else:
414 read_timeout = 5
415 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls,
416 read_timeout)
Klement Sekera085f5c02016-11-24 01:59:16 +0100417 if cls.step:
418 hook = StepHook(cls)
419 else:
420 hook = PollHook(cls)
421 cls.vapi.register_hook(hook)
Klement Sekera611864f2018-09-26 11:19:00 +0200422 cls.wait_for_stats_socket()
423 cls.statistics = VPPStats(socketname=cls.stats_sock)
Klement Sekera3747c752017-04-10 06:30:17 +0200424 try:
425 hook.poll_vpp()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100426 except VppDiedError:
Klement Sekera3747c752017-04-10 06:30:17 +0200427 cls.vpp_startup_failed = True
428 cls.logger.critical(
429 "VPP died shortly after startup, check the"
430 " output to standard error for possible cause")
431 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100432 try:
433 cls.vapi.connect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100434 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100435 try:
436 cls.vapi.disconnect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100437 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100438 pass
Klement Sekera085f5c02016-11-24 01:59:16 +0100439 if cls.debug_gdbserver:
440 print(colorize("You're running VPP inside gdbserver but "
441 "VPP-API connection failed, did you forget "
442 "to 'continue' VPP from within gdb?", RED))
443 raise
Klement Sekera13a83ef2018-03-21 12:35:51 +0100444 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100445 try:
446 cls.quit()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100447 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100448 pass
Klement Sekera13a83ef2018-03-21 12:35:51 +0100449 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200450
Damjan Marionf56b77a2016-10-03 19:44:57 +0200451 @classmethod
452 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200453 """
454 Disconnect vpp-api, kill vpp and cleanup shared memory files
455 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200456 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
457 cls.vpp.poll()
458 if cls.vpp.returncode is None:
459 print(double_line_delim)
460 print("VPP or GDB server is still running")
461 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100462 raw_input("When done debugging, press ENTER to kill the "
463 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200464
juraj.linkes184870a2018-07-16 14:22:01 +0200465 # first signal that we want to stop the pump thread, then wake it up
466 if hasattr(cls, 'pump_thread_stop_flag'):
467 cls.pump_thread_stop_flag.set()
468 if hasattr(cls, 'pump_thread_wakeup_pipe'):
469 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100470 if hasattr(cls, 'pump_thread'):
471 cls.logger.debug("Waiting for pump thread to stop")
472 cls.pump_thread.join()
473 if hasattr(cls, 'vpp_stderr_reader_thread'):
474 cls.logger.debug("Waiting for stdderr pump to stop")
475 cls.vpp_stderr_reader_thread.join()
476
Klement Sekeraf62ae122016-10-11 11:47:09 +0200477 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100478 if hasattr(cls, 'vapi'):
479 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100480 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200481 cls.vpp.poll()
482 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100483 cls.logger.debug("Sending TERM to vpp")
Klement Sekera611864f2018-09-26 11:19:00 +0200484 cls.vpp.kill()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100485 cls.logger.debug("Waiting for vpp to die")
486 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200487 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200488
Klement Sekera3747c752017-04-10 06:30:17 +0200489 if cls.vpp_startup_failed:
490 stdout_log = cls.logger.info
491 stderr_log = cls.logger.critical
492 else:
493 stdout_log = cls.logger.info
494 stderr_log = cls.logger.info
495
Klement Sekerae4504c62016-12-08 10:16:41 +0100496 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200497 stdout_log(single_line_delim)
498 stdout_log('VPP output to stdout while running %s:', cls.__name__)
499 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100500 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200501 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
502 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200503 stdout_log('\n%s', vpp_output)
504 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200505
Klement Sekerae4504c62016-12-08 10:16:41 +0100506 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200507 stderr_log(single_line_delim)
508 stderr_log('VPP output to stderr while running %s:', cls.__name__)
509 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100510 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200511 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
512 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200513 stderr_log('\n%s', vpp_output)
514 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200515
Damjan Marionf56b77a2016-10-03 19:44:57 +0200516 @classmethod
517 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200518 """ Perform final cleanup after running all tests in this test-case """
juraj.linkes40dd73b2018-09-21 13:55:16 +0200519 cls.reporter.send_keep_alive(cls, 'tearDownClass')
Damjan Marionf56b77a2016-10-03 19:44:57 +0200520 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200521 cls.file_handler.close()
Klement Sekeraebbaf552018-02-17 13:41:33 +0100522 cls.reset_packet_infos()
523 if debug_framework:
524 debug_internal.on_tear_down_class(cls)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200525
Damjan Marionf56b77a2016-10-03 19:44:57 +0200526 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200527 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100528 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
529 (self.__class__.__name__, self._testMethodName,
530 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200531 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200532 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700533 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200534 self.logger.info(self.vapi.ppcli("show hardware"))
Ole Troan73202102018-08-31 00:29:48 +0200535 self.logger.info(self.statistics.set_errors_str())
Jan49c0fca2016-10-26 15:44:27 +0200536 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marion07a38572018-01-21 06:44:18 -0800537 self.logger.info(self.vapi.ppcli("show log"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100538 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500539 # Save/Dump VPP api trace log
540 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
541 tmp_api_trace = "/tmp/%s" % api_trace
542 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
543 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
544 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
545 vpp_api_trace_log))
546 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500547 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500548 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100549 else:
550 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200551
Damjan Marionf56b77a2016-10-03 19:44:57 +0200552 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200553 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200554 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100555 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
556 (self.__class__.__name__, self._testMethodName,
557 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100558 if self.vpp_dead:
559 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100560 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100561 self.vpp_stdout_deque.append(
562 "--- test setUp() for %s.%s(%s) starts here ---\n" %
563 (self.__class__.__name__, self._testMethodName,
564 self._testMethodDoc))
565 self.vpp_stderr_deque.append(
566 "--- test setUp() for %s.%s(%s) starts here ---\n" %
567 (self.__class__.__name__, self._testMethodName,
568 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200569 self.vapi.cli("clear trace")
570 # store the test instance inside the test class - so that objects
571 # holding the class can access instance methods (like assertEqual)
572 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200573
Damjan Marionf56b77a2016-10-03 19:44:57 +0200574 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200575 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200576 """
577 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200578
Klement Sekera75e7d132017-09-20 08:26:30 +0200579 :param interfaces: iterable interface indexes (if None,
580 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200581
Klement Sekeraf62ae122016-10-11 11:47:09 +0200582 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200583 if interfaces is None:
584 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200585 for i in interfaces:
586 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200587
Damjan Marionf56b77a2016-10-03 19:44:57 +0200588 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100589 def register_capture(cls, cap_name):
590 """ Register a capture in the testclass """
591 # add to the list of captures with current timestamp
592 cls._captures.append((time.time(), cap_name))
593 # filter out from zombies
594 cls._zombie_captures = [(stamp, name)
595 for (stamp, name) in cls._zombie_captures
596 if name != cap_name]
597
598 @classmethod
599 def pg_start(cls):
600 """ Remove any zombie captures and enable the packet generator """
601 # how long before capture is allowed to be deleted - otherwise vpp
602 # crashes - 100ms seems enough (this shouldn't be needed at all)
603 capture_ttl = 0.1
604 now = time.time()
605 for stamp, cap_name in cls._zombie_captures:
606 wait = stamp + capture_ttl - now
607 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100608 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100609 now = time.time()
610 cls.logger.debug("Removing zombie capture %s" % cap_name)
611 cls.vapi.cli('packet-generator delete %s' % cap_name)
612
Klement Sekeraf62ae122016-10-11 11:47:09 +0200613 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
614 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100615 cls._zombie_captures = cls._captures
616 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200617
Damjan Marionf56b77a2016-10-03 19:44:57 +0200618 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200619 def create_pg_interfaces(cls, interfaces):
620 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100621 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200622
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100623 :param interfaces: iterable indexes of the interfaces.
624 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200625
Klement Sekeraf62ae122016-10-11 11:47:09 +0200626 """
627 result = []
628 for i in interfaces:
629 intf = VppPGInterface(cls, i)
630 setattr(cls, intf.name, intf)
631 result.append(intf)
632 cls.pg_interfaces = result
633 return result
634
Matej Klotton0178d522016-11-04 11:11:44 +0100635 @classmethod
Klement Sekerab9ef2732018-06-24 22:49:33 +0200636 def create_loopback_interfaces(cls, count):
Matej Klotton0178d522016-11-04 11:11:44 +0100637 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100638 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100639
Klement Sekerab9ef2732018-06-24 22:49:33 +0200640 :param count: number of interfaces created.
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100641 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100642 """
Klement Sekerab9ef2732018-06-24 22:49:33 +0200643 result = [VppLoInterface(cls) for i in range(count)]
644 for intf in result:
Matej Klotton0178d522016-11-04 11:11:44 +0100645 setattr(cls, intf.name, intf)
Matej Klotton0178d522016-11-04 11:11:44 +0100646 cls.lo_interfaces = result
647 return result
648
Damjan Marionf56b77a2016-10-03 19:44:57 +0200649 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200650 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200651 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200652 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200653 NOTE: Currently works only when Raw layer is present.
654
655 :param packet: packet
656 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200657 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200658
659 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200660 packet_len = len(packet) + 4
661 extend = size - packet_len
662 if extend > 0:
Klement Sekera75e7d132017-09-20 08:26:30 +0200663 num = (extend / len(padding)) + 1
664 packet[Raw].load += (padding * num)[:extend]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200665
Klement Sekeradab231a2016-12-21 08:50:14 +0100666 @classmethod
667 def reset_packet_infos(cls):
668 """ Reset the list of packet info objects and packet counts to zero """
669 cls._packet_infos = {}
670 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200671
Klement Sekeradab231a2016-12-21 08:50:14 +0100672 @classmethod
673 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200674 """
675 Create packet info object containing the source and destination indexes
676 and add it to the testcase's packet info list
677
Klement Sekeradab231a2016-12-21 08:50:14 +0100678 :param VppInterface src_if: source interface
679 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200680
681 :returns: _PacketInfo object
682
683 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200684 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100685 info.index = len(cls._packet_infos)
686 info.src = src_if.sw_if_index
687 info.dst = dst_if.sw_if_index
688 if isinstance(dst_if, VppSubInterface):
689 dst_idx = dst_if.parent.sw_if_index
690 else:
691 dst_idx = dst_if.sw_if_index
692 if dst_idx in cls._packet_count_for_dst_if_idx:
693 cls._packet_count_for_dst_if_idx[dst_idx] += 1
694 else:
695 cls._packet_count_for_dst_if_idx[dst_idx] = 1
696 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200697 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200698
Damjan Marionf56b77a2016-10-03 19:44:57 +0200699 @staticmethod
700 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200701 """
702 Convert _PacketInfo object to packet payload
703
704 :param info: _PacketInfo object
705
706 :returns: string containing serialized data from packet info
707 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100708 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
709 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200710
Damjan Marionf56b77a2016-10-03 19:44:57 +0200711 @staticmethod
712 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200713 """
714 Convert packet payload to _PacketInfo object
715
716 :param payload: packet payload
717
718 :returns: _PacketInfo object containing de-serialized data from payload
719
720 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200721 numbers = payload.split()
722 info = _PacketInfo()
723 info.index = int(numbers[0])
724 info.src = int(numbers[1])
725 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100726 info.ip = int(numbers[3])
727 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200728 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200729
Damjan Marionf56b77a2016-10-03 19:44:57 +0200730 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200731 """
732 Iterate over the packet info list stored in the testcase
733 Start iteration with first element if info is None
734 Continue based on index in info if info is specified
735
736 :param info: info or None
737 :returns: next info in list or None if no more infos
738 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200739 if info is None:
740 next_index = 0
741 else:
742 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100743 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200744 return None
745 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100746 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200747
Klement Sekeraf62ae122016-10-11 11:47:09 +0200748 def get_next_packet_info_for_interface(self, src_index, info):
749 """
750 Search the packet info list for the next packet info with same source
751 interface index
752
753 :param src_index: source interface index to search for
754 :param info: packet info - where to start the search
755 :returns: packet info or None
756
757 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200758 while True:
759 info = self.get_next_packet_info(info)
760 if info is None:
761 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200762 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200763 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200764
Klement Sekeraf62ae122016-10-11 11:47:09 +0200765 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
766 """
767 Search the packet info list for the next packet info with same source
768 and destination interface indexes
769
770 :param src_index: source interface index to search for
771 :param dst_index: destination interface index to search for
772 :param info: packet info - where to start the search
773 :returns: packet info or None
774
775 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200776 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200777 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200778 if info is None:
779 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200780 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200781 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200782
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200783 def assert_equal(self, real_value, expected_value, name_or_class=None):
784 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100785 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200786 return
787 try:
788 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
789 msg = msg % (getdoc(name_or_class).strip(),
790 real_value, str(name_or_class(real_value)),
791 expected_value, str(name_or_class(expected_value)))
Klement Sekera13a83ef2018-03-21 12:35:51 +0100792 except Exception:
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200793 msg = "Invalid %s: %s does not match expected value %s" % (
794 name_or_class, real_value, expected_value)
795
796 self.assertEqual(real_value, expected_value, msg)
797
Klement Sekerab17dd962017-01-09 07:43:48 +0100798 def assert_in_range(self,
799 real_value,
800 expected_min,
801 expected_max,
802 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200803 if name is None:
804 msg = None
805 else:
806 msg = "Invalid %s: %s out of range <%s,%s>" % (
807 name, real_value, expected_min, expected_max)
808 self.assertTrue(expected_min <= real_value <= expected_max, msg)
809
Klement Sekerad81ae412018-05-16 10:52:54 +0200810 def assert_packet_checksums_valid(self, packet,
811 ignore_zero_udp_checksums=True):
Klement Sekera31da2e32018-06-24 22:49:55 +0200812 received = packet.__class__(str(packet))
813 self.logger.debug(
814 ppp("Verifying packet checksums for packet:", received))
Klement Sekerad81ae412018-05-16 10:52:54 +0200815 udp_layers = ['UDP', 'UDPerror']
816 checksum_fields = ['cksum', 'chksum']
817 checksums = []
818 counter = 0
Klement Sekera31da2e32018-06-24 22:49:55 +0200819 temp = received.__class__(str(received))
Klement Sekerad81ae412018-05-16 10:52:54 +0200820 while True:
821 layer = temp.getlayer(counter)
822 if layer:
823 for cf in checksum_fields:
824 if hasattr(layer, cf):
825 if ignore_zero_udp_checksums and \
826 0 == getattr(layer, cf) and \
827 layer.name in udp_layers:
828 continue
829 delattr(layer, cf)
830 checksums.append((counter, cf))
831 else:
832 break
833 counter = counter + 1
Klement Sekera31da2e32018-06-24 22:49:55 +0200834 if 0 == len(checksums):
835 return
Klement Sekerad81ae412018-05-16 10:52:54 +0200836 temp = temp.__class__(str(temp))
837 for layer, cf in checksums:
Klement Sekera31da2e32018-06-24 22:49:55 +0200838 calc_sum = getattr(temp[layer], cf)
839 self.assert_equal(
840 getattr(received[layer], cf), calc_sum,
841 "packet checksum on layer #%d: %s" % (layer, temp[layer].name))
842 self.logger.debug(
843 "Checksum field `%s` on `%s` layer has correct value `%s`" %
844 (cf, temp[layer].name, calc_sum))
Klement Sekerad81ae412018-05-16 10:52:54 +0200845
846 def assert_checksum_valid(self, received_packet, layer,
847 field_name='chksum',
848 ignore_zero_checksum=False):
849 """ Check checksum of received packet on given layer """
850 received_packet_checksum = getattr(received_packet[layer], field_name)
851 if ignore_zero_checksum and 0 == received_packet_checksum:
852 return
853 recalculated = received_packet.__class__(str(received_packet))
854 delattr(recalculated[layer], field_name)
855 recalculated = recalculated.__class__(str(recalculated))
856 self.assert_equal(received_packet_checksum,
857 getattr(recalculated[layer], field_name),
858 "packet checksum on layer: %s" % layer)
859
860 def assert_ip_checksum_valid(self, received_packet,
861 ignore_zero_checksum=False):
862 self.assert_checksum_valid(received_packet, 'IP',
863 ignore_zero_checksum=ignore_zero_checksum)
864
865 def assert_tcp_checksum_valid(self, received_packet,
866 ignore_zero_checksum=False):
867 self.assert_checksum_valid(received_packet, 'TCP',
868 ignore_zero_checksum=ignore_zero_checksum)
869
870 def assert_udp_checksum_valid(self, received_packet,
871 ignore_zero_checksum=True):
872 self.assert_checksum_valid(received_packet, 'UDP',
873 ignore_zero_checksum=ignore_zero_checksum)
874
875 def assert_embedded_icmp_checksum_valid(self, received_packet):
876 if received_packet.haslayer(IPerror):
877 self.assert_checksum_valid(received_packet, 'IPerror')
878 if received_packet.haslayer(TCPerror):
879 self.assert_checksum_valid(received_packet, 'TCPerror')
880 if received_packet.haslayer(UDPerror):
881 self.assert_checksum_valid(received_packet, 'UDPerror',
882 ignore_zero_checksum=True)
883 if received_packet.haslayer(ICMPerror):
884 self.assert_checksum_valid(received_packet, 'ICMPerror')
885
886 def assert_icmp_checksum_valid(self, received_packet):
887 self.assert_checksum_valid(received_packet, 'ICMP')
888 self.assert_embedded_icmp_checksum_valid(received_packet)
889
890 def assert_icmpv6_checksum_valid(self, pkt):
891 if pkt.haslayer(ICMPv6DestUnreach):
892 self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
893 self.assert_embedded_icmp_checksum_valid(pkt)
894 if pkt.haslayer(ICMPv6EchoRequest):
895 self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
896 if pkt.haslayer(ICMPv6EchoReply):
897 self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
898
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100899 @classmethod
900 def sleep(cls, timeout, remark=None):
901 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000902 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
903 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100904 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000905 after = time.time()
906 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200907 cls.logger.error("unexpected time.sleep() result - "
908 "slept for %ss instead of ~%ss!" % (
909 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000910 if hasattr(cls, 'logger'):
911 cls.logger.debug(
912 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
913 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100914
Neale Ranns947ea622018-06-07 23:48:20 -0700915 def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
Neale Ranns52fae862018-01-08 04:41:42 -0800916 self.vapi.cli("clear trace")
917 intf.add_stream(pkts)
918 self.pg_enable_capture(self.pg_interfaces)
919 self.pg_start()
Neale Ranns947ea622018-06-07 23:48:20 -0700920 if not timeout:
921 timeout = 1
Neale Ranns52fae862018-01-08 04:41:42 -0800922 for i in self.pg_interfaces:
923 i.get_capture(0, timeout=timeout)
924 i.assert_nothing_captured(remark=remark)
925 timeout = 0.1
926
927 def send_and_expect(self, input, pkts, output):
928 self.vapi.cli("clear trace")
929 input.add_stream(pkts)
930 self.pg_enable_capture(self.pg_interfaces)
931 self.pg_start()
932 rx = output.get_capture(len(pkts))
933 return rx
934
Damjan Marionf56b77a2016-10-03 19:44:57 +0200935
juraj.linkes184870a2018-07-16 14:22:01 +0200936def get_testcase_doc_name(test):
937 return getdoc(test.__class__).splitlines()[0]
938
939
940def get_test_description(descriptions, test):
juraj.linkes184870a2018-07-16 14:22:01 +0200941 short_description = test.shortDescription()
942 if descriptions and short_description:
943 return short_description
944 else:
945 return str(test)
946
947
juraj.linkes40dd73b2018-09-21 13:55:16 +0200948class TestCaseInfo(object):
949 def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
950 self.logger = logger
951 self.tempdir = tempdir
952 self.vpp_pid = vpp_pid
953 self.vpp_bin_path = vpp_bin_path
954 self.core_crash_test = None
Klement Sekera87134932017-03-07 11:39:27 +0100955
956
Damjan Marionf56b77a2016-10-03 19:44:57 +0200957class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200958 """
959 @property result_string
960 String variable to store the test case result string.
961 @property errors
962 List variable containing 2-tuples of TestCase instances and strings
963 holding formatted tracebacks. Each tuple represents a test which
964 raised an unexpected exception.
965 @property failures
966 List variable containing 2-tuples of TestCase instances and strings
967 holding formatted tracebacks. Each tuple represents a test where
968 a failure was explicitly signalled using the TestCase.assert*()
969 methods.
970 """
971
juraj.linkes40dd73b2018-09-21 13:55:16 +0200972 failed_test_cases_info = set()
973 core_crash_test_cases_info = set()
974 current_test_case_info = None
975
Damjan Marionf56b77a2016-10-03 19:44:57 +0200976 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200977 """
Klement Sekerada505f62017-01-04 12:58:53 +0100978 :param stream File descriptor to store where to report test results.
979 Set to the standard error stream by default.
980 :param descriptions Boolean variable to store information if to use
981 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200982 :param verbosity Integer variable to store required verbosity level.
983 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200984 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
985 self.stream = stream
986 self.descriptions = descriptions
987 self.verbosity = verbosity
988 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200989
Damjan Marionf56b77a2016-10-03 19:44:57 +0200990 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200991 """
992 Record a test succeeded result
993
994 :param test:
995
996 """
juraj.linkes40dd73b2018-09-21 13:55:16 +0200997 if self.current_test_case_info:
998 self.current_test_case_info.logger.debug(
999 "--- addSuccess() %s.%s(%s) called" % (test.__class__.__name__,
1000 test._testMethodName,
1001 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +02001002 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +02001003 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001004
juraj.linkescae64f82018-09-19 15:01:47 +02001005 self.send_result_through_pipe(test, PASS)
1006
Klement Sekeraf62ae122016-10-11 11:47:09 +02001007 def addSkip(self, test, reason):
1008 """
1009 Record a test skipped.
1010
1011 :param test:
1012 :param reason:
1013
1014 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001015 if self.current_test_case_info:
1016 self.current_test_case_info.logger.debug(
1017 "--- addSkip() %s.%s(%s) called, reason is %s" %
1018 (test.__class__.__name__, test._testMethodName,
1019 test._testMethodDoc, reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001020 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +02001021 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +02001022
juraj.linkescae64f82018-09-19 15:01:47 +02001023 self.send_result_through_pipe(test, SKIP)
1024
juraj.linkes40dd73b2018-09-21 13:55:16 +02001025 def symlink_failed(self):
1026 if self.current_test_case_info:
Klement Sekeraf413bef2017-08-15 07:09:02 +02001027 try:
1028 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
juraj.linkes40dd73b2018-09-21 13:55:16 +02001029 link_path = os.path.join(
1030 failed_dir,
1031 '%s-FAILED' %
1032 os.path.basename(self.current_test_case_info.tempdir))
1033 if self.current_test_case_info.logger:
1034 self.current_test_case_info.logger.debug(
1035 "creating a link to the failed test")
1036 self.current_test_case_info.logger.debug(
1037 "os.symlink(%s, %s)" %
1038 (self.current_test_case_info.tempdir, link_path))
juraj.linkes184870a2018-07-16 14:22:01 +02001039 if os.path.exists(link_path):
juraj.linkes40dd73b2018-09-21 13:55:16 +02001040 if self.current_test_case_info.logger:
1041 self.current_test_case_info.logger.debug(
1042 'symlink already exists')
juraj.linkes184870a2018-07-16 14:22:01 +02001043 else:
juraj.linkes40dd73b2018-09-21 13:55:16 +02001044 os.symlink(self.current_test_case_info.tempdir, link_path)
juraj.linkes184870a2018-07-16 14:22:01 +02001045
Klement Sekeraf413bef2017-08-15 07:09:02 +02001046 except Exception as e:
juraj.linkes40dd73b2018-09-21 13:55:16 +02001047 if self.current_test_case_info.logger:
1048 self.current_test_case_info.logger.error(e)
Klement Sekeraf413bef2017-08-15 07:09:02 +02001049
juraj.linkescae64f82018-09-19 15:01:47 +02001050 def send_result_through_pipe(self, test, result):
1051 if hasattr(self, 'test_framework_result_pipe'):
1052 pipe = self.test_framework_result_pipe
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001053 if pipe:
juraj.linkescae64f82018-09-19 15:01:47 +02001054 pipe.send((test.id(), result))
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001055
juraj.linkes40dd73b2018-09-21 13:55:16 +02001056 def log_error(self, test, err, fn_name):
1057 if self.current_test_case_info:
1058 if isinstance(test, unittest.suite._ErrorHolder):
1059 test_name = test.description
1060 else:
1061 test_name = '%s.%s(%s)' % (test.__class__.__name__,
1062 test._testMethodName,
1063 test._testMethodDoc)
1064 self.current_test_case_info.logger.debug(
1065 "--- %s() %s called, err is %s" %
1066 (fn_name, test_name, err))
1067 self.current_test_case_info.logger.debug(
1068 "formatted exception is:\n%s" %
1069 "".join(format_exception(*err)))
1070
1071 def add_error(self, test, err, unittest_fn, error_type):
1072 if error_type == FAIL:
1073 self.log_error(test, err, 'addFailure')
1074 error_type_str = colorize("FAIL", RED)
1075 elif error_type == ERROR:
1076 self.log_error(test, err, 'addError')
1077 error_type_str = colorize("ERROR", RED)
1078 else:
1079 raise Exception('Error type %s cannot be used to record an '
1080 'error or a failure' % error_type)
1081
1082 unittest_fn(self, test, err)
1083 if self.current_test_case_info:
1084 self.result_string = "%s [ temp dir used by test case: %s ]" % \
1085 (error_type_str,
1086 self.current_test_case_info.tempdir)
1087 self.symlink_failed()
1088 self.failed_test_cases_info.add(self.current_test_case_info)
1089 if is_core_present(self.current_test_case_info.tempdir):
1090 if not self.current_test_case_info.core_crash_test:
1091 if isinstance(test, unittest.suite._ErrorHolder):
1092 test_name = str(test)
1093 else:
1094 test_name = "'{}' ({})".format(
1095 get_testcase_doc_name(test), test.id())
1096 self.current_test_case_info.core_crash_test = test_name
1097 self.core_crash_test_cases_info.add(
1098 self.current_test_case_info)
1099 else:
1100 self.result_string = '%s [no temp dir]' % error_type_str
1101
1102 self.send_result_through_pipe(test, error_type)
1103
Damjan Marionf56b77a2016-10-03 19:44:57 +02001104 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001105 """
1106 Record a test failed result
1107
1108 :param test:
1109 :param err: error message
1110
1111 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001112 self.add_error(test, err, unittest.TestResult.addFailure, FAIL)
juraj.linkescae64f82018-09-19 15:01:47 +02001113
Damjan Marionf56b77a2016-10-03 19:44:57 +02001114 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001115 """
1116 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +02001117
Klement Sekeraf62ae122016-10-11 11:47:09 +02001118 :param test:
1119 :param err: error message
1120
1121 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001122 self.add_error(test, err, unittest.TestResult.addError, ERROR)
juraj.linkescae64f82018-09-19 15:01:47 +02001123
Damjan Marionf56b77a2016-10-03 19:44:57 +02001124 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001125 """
1126 Get test description
1127
1128 :param test:
1129 :returns: test description
1130
1131 """
juraj.linkes184870a2018-07-16 14:22:01 +02001132 return get_test_description(self.descriptions, test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001133
Damjan Marionf56b77a2016-10-03 19:44:57 +02001134 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001135 """
1136 Start a test
1137
1138 :param test:
1139
1140 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001141 test.print_header(test.__class__)
1142
Damjan Marionf56b77a2016-10-03 19:44:57 +02001143 unittest.TestResult.startTest(self, test)
1144 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001145 self.stream.writeln(
1146 "Starting " + self.getDescription(test) + " ...")
1147 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001148
Damjan Marionf56b77a2016-10-03 19:44:57 +02001149 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001150 """
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001151 Called when the given test has been run
Klement Sekeraf62ae122016-10-11 11:47:09 +02001152
1153 :param test:
1154
1155 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001156 unittest.TestResult.stopTest(self, test)
1157 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001158 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +01001159 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001160 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001161 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001162 else:
Klement Sekera52e84f32017-01-13 07:25:25 +01001163 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001164 self.result_string))
juraj.linkescae64f82018-09-19 15:01:47 +02001165
1166 self.send_result_through_pipe(test, TEST_RUN)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001167
Damjan Marionf56b77a2016-10-03 19:44:57 +02001168 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001169 """
1170 Print errors from running the test case
1171 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001172 self.stream.writeln()
1173 self.printErrorList('ERROR', self.errors)
1174 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001175
Damjan Marionf56b77a2016-10-03 19:44:57 +02001176 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001177 """
1178 Print error list to the output stream together with error type
1179 and test case description.
1180
1181 :param flavour: error type
1182 :param errors: iterable errors
1183
1184 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001185 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001186 self.stream.writeln(double_line_delim)
1187 self.stream.writeln("%s: %s" %
1188 (flavour, self.getDescription(test)))
1189 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001190 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001191
1192
Damjan Marionf56b77a2016-10-03 19:44:57 +02001193class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001194 """
Klement Sekera104543f2017-02-03 07:29:43 +01001195 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001196 """
1197 @property
1198 def resultclass(self):
1199 """Class maintaining the results of the tests"""
1200 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001201
juraj.linkes184870a2018-07-16 14:22:01 +02001202 def __init__(self, keep_alive_pipe=None, descriptions=True, verbosity=1,
juraj.linkescae64f82018-09-19 15:01:47 +02001203 result_pipe=None, failfast=False, buffer=False,
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001204 resultclass=None):
juraj.linkes40dd73b2018-09-21 13:55:16 +02001205
Klement Sekera7a161da2017-01-17 13:42:48 +01001206 # ignore stream setting here, use hard-coded stdout to be in sync
1207 # with prints from VppTestCase methods ...
1208 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1209 verbosity, failfast, buffer,
1210 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001211 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001212 reporter.pipe = keep_alive_pipe
Klement Sekera104543f2017-02-03 07:29:43 +01001213
juraj.linkescae64f82018-09-19 15:01:47 +02001214 VppTestResult.test_framework_result_pipe = result_pipe
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001215
Damjan Marionf56b77a2016-10-03 19:44:57 +02001216 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001217 """
1218 Run the tests
1219
1220 :param test:
1221
1222 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001223 faulthandler.enable() # emit stack trace to stderr if killed by signal
juraj.linkes184870a2018-07-16 14:22:01 +02001224
1225 result = super(VppTestRunner, self).run(test)
1226 return result
Neale Ranns812ed392017-10-16 04:20:13 -07001227
1228
1229class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001230 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001231 self.logger = logger
1232 self.args = args
1233 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001234 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001235 super(Worker, self).__init__()
1236
1237 def run(self):
1238 executable = self.args[0]
1239 self.logger.debug("Running executable w/args `%s'" % self.args)
1240 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001241 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001242 env["CK_LOG_FILE_NAME"] = "-"
1243 self.process = subprocess.Popen(
1244 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1245 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1246 out, err = self.process.communicate()
1247 self.logger.debug("Finished running `%s'" % executable)
1248 self.logger.info("Return code is `%s'" % self.process.returncode)
1249 self.logger.info(single_line_delim)
1250 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1251 self.logger.info(single_line_delim)
1252 self.logger.info(out)
1253 self.logger.info(single_line_delim)
1254 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1255 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001256 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001257 self.logger.info(single_line_delim)
1258 self.result = self.process.returncode