blob: f90197b9b6fa203c7ffe00a0033355be32ffd087 [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
Klement Sekerae4504c62016-12-08 10:16:41 +010014from collections import deque
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010015from threading import Thread, Event
Klement Sekera909a6a12017-08-08 04:33:53 +020016from inspect import getdoc, isclass
Klement Sekerab91017a2017-02-09 06:04:36 +010017from traceback import format_exception
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010018from logging import FileHandler, DEBUG, Formatter
19from scapy.packet import Raw
Klement Sekera13a83ef2018-03-21 12:35:51 +010020from hook import StepHook, PollHook, VppDiedError
Klement Sekeraf62ae122016-10-11 11:47:09 +020021from vpp_pg_interface import VppPGInterface
Klement Sekeradab231a2016-12-21 08:50:14 +010022from vpp_sub_interface import VppSubInterface
Matej Klotton0178d522016-11-04 11:11:44 +010023from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020024from vpp_papi_provider import VppPapiProvider
Klement Sekera05742262018-03-14 18:14:49 +010025from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
26 getLogger, colorize
Klement Sekera10db26f2017-01-11 08:16:53 +010027from vpp_object import VppObjectRegistry
Klement Sekerad81ae412018-05-16 10:52:54 +020028from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
29from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
30from scapy.layers.inet6 import ICMPv6EchoReply
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010031if os.name == 'posix' and sys.version_info[0] < 3:
32 # using subprocess32 is recommended by python official documentation
33 # @ https://docs.python.org/2/library/subprocess.html
34 import subprocess32 as subprocess
35else:
36 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020037
Klement Sekerad81ae412018-05-16 10:52:54 +020038
Klement Sekeraebbaf552018-02-17 13:41:33 +010039debug_framework = False
40if os.getenv('TEST_DEBUG', "0") == "1":
41 debug_framework = True
42 import debug_internal
43
44
Klement Sekeraf62ae122016-10-11 11:47:09 +020045"""
46 Test framework module.
47
48 The module provides a set of tools for constructing and running tests and
49 representing the results.
50"""
51
Klement Sekeraf62ae122016-10-11 11:47:09 +020052
Damjan Marionf56b77a2016-10-03 19:44:57 +020053class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020054 """Private class to create packet info object.
55
56 Help process information about the next packet.
57 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020058 """
Matej Klotton86d87c42016-11-11 11:38:55 +010059 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020060 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010061 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020062 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010063 #: Store the index of the destination packet generator interface
64 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020065 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +010066 #: Store expected ip version
67 ip = -1
68 #: Store expected upper protocol
69 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010070 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020071 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020072
Matej Klotton16a14cd2016-12-07 15:09:13 +010073 def __eq__(self, other):
74 index = self.index == other.index
75 src = self.src == other.src
76 dst = self.dst == other.dst
77 data = self.data == other.data
78 return index and src and dst and data
79
Klement Sekeraf62ae122016-10-11 11:47:09 +020080
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010081def pump_output(testclass):
82 """ pump output from vpp stdout/stderr to proper queues """
Klement Sekera6a6f4f72017-11-09 09:16:39 +010083 stdout_fragment = ""
84 stderr_fragment = ""
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010085 while not testclass.pump_thread_stop_flag.wait(0):
86 readable = select.select([testclass.vpp.stdout.fileno(),
87 testclass.vpp.stderr.fileno(),
88 testclass.pump_thread_wakeup_pipe[0]],
89 [], [])[0]
90 if testclass.vpp.stdout.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +010091 read = os.read(testclass.vpp.stdout.fileno(), 102400)
92 if len(read) > 0:
93 split = read.splitlines(True)
94 if len(stdout_fragment) > 0:
95 split[0] = "%s%s" % (stdout_fragment, split[0])
96 if len(split) > 0 and split[-1].endswith("\n"):
97 limit = None
98 else:
99 limit = -1
100 stdout_fragment = split[-1]
101 testclass.vpp_stdout_deque.extend(split[:limit])
102 if not testclass.cache_vpp_output:
103 for line in split[:limit]:
104 testclass.logger.debug(
105 "VPP STDOUT: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100106 if testclass.vpp.stderr.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100107 read = os.read(testclass.vpp.stderr.fileno(), 102400)
108 if len(read) > 0:
109 split = read.splitlines(True)
110 if len(stderr_fragment) > 0:
111 split[0] = "%s%s" % (stderr_fragment, split[0])
112 if len(split) > 0 and split[-1].endswith("\n"):
113 limit = None
114 else:
115 limit = -1
116 stderr_fragment = split[-1]
117 testclass.vpp_stderr_deque.extend(split[:limit])
118 if not testclass.cache_vpp_output:
119 for line in split[:limit]:
120 testclass.logger.debug(
121 "VPP STDERR: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100122 # ignoring the dummy pipe here intentionally - the flag will take care
123 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +0200124
125
Klement Sekera87134932017-03-07 11:39:27 +0100126def running_extended_tests():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100127 s = os.getenv("EXTENDED_TESTS", "n")
128 return True if s.lower() in ("y", "yes", "1") else False
Klement Sekera87134932017-03-07 11:39:27 +0100129
130
Klement Sekerad3e671e2017-09-29 12:36:37 +0200131def running_on_centos():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100132 os_id = os.getenv("OS_ID", "")
133 return True if "centos" in os_id.lower() else False
Klement Sekerad3e671e2017-09-29 12:36:37 +0200134
135
Klement Sekera909a6a12017-08-08 04:33:53 +0200136class KeepAliveReporter(object):
137 """
138 Singleton object which reports test start to parent process
139 """
140 _shared_state = {}
141
142 def __init__(self):
143 self.__dict__ = self._shared_state
144
145 @property
146 def pipe(self):
147 return self._pipe
148
149 @pipe.setter
150 def pipe(self, pipe):
151 if hasattr(self, '_pipe'):
152 raise Exception("Internal error - pipe should only be set once.")
153 self._pipe = pipe
154
155 def send_keep_alive(self, test):
156 """
157 Write current test tmpdir & desc to keep-alive pipe to signal liveness
158 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200159 if self.pipe is None:
160 # if not running forked..
161 return
162
Klement Sekera909a6a12017-08-08 04:33:53 +0200163 if isclass(test):
164 desc = test.__name__
165 else:
166 desc = test.shortDescription()
167 if not desc:
168 desc = str(test)
169
Dave Wallacee2efd122017-09-30 22:04:21 -0400170 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200171
172
Damjan Marionf56b77a2016-10-03 19:44:57 +0200173class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100174 """This subclass is a base class for VPP test cases that are implemented as
175 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200176 """
177
178 @property
179 def packet_infos(self):
180 """List of packet infos"""
181 return self._packet_infos
182
Klement Sekeradab231a2016-12-21 08:50:14 +0100183 @classmethod
184 def get_packet_count_for_if_idx(cls, dst_if_index):
185 """Get the number of packet info for specified destination if index"""
186 if dst_if_index in cls._packet_count_for_dst_if_idx:
187 return cls._packet_count_for_dst_if_idx[dst_if_index]
188 else:
189 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200190
191 @classmethod
192 def instance(cls):
193 """Return the instance of this testcase"""
194 return cls.test_instance
195
Damjan Marionf56b77a2016-10-03 19:44:57 +0200196 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200197 def set_debug_flags(cls, d):
198 cls.debug_core = False
199 cls.debug_gdb = False
200 cls.debug_gdbserver = False
201 if d is None:
202 return
203 dl = d.lower()
204 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200205 cls.debug_core = True
206 elif dl == "gdb":
207 cls.debug_gdb = True
208 elif dl == "gdbserver":
209 cls.debug_gdbserver = True
210 else:
211 raise Exception("Unrecognized DEBUG option: '%s'" % d)
212
213 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200214 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200215 """ Set-up the test case class based on environment variables """
Klement Sekera13a83ef2018-03-21 12:35:51 +0100216 s = os.getenv("STEP", "n")
217 cls.step = True if s.lower() in ("y", "yes", "1") else False
218 d = os.getenv("DEBUG", None)
219 c = os.getenv("CACHE_OUTPUT", "1")
220 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
Klement Sekera277b89c2016-10-28 13:20:27 +0200221 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200222 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100223 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100224 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
225 plugin_path = None
226 if cls.plugin_path is not None:
227 if cls.extern_plugin_path is not None:
228 plugin_path = "%s:%s" % (
229 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100230 else:
231 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100232 elif cls.extern_plugin_path is not None:
233 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100234 debug_cli = ""
235 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
236 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100237 coredump_size = None
Klement Sekera13a83ef2018-03-21 12:35:51 +0100238 size = os.getenv("COREDUMP_SIZE")
239 if size is not None:
240 coredump_size = "coredump-size %s" % size
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100241 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400242 coredump_size = "coredump-size unlimited"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100243 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400244 "{", "nodaemon", debug_cli, "full-coredump",
245 coredump_size, "}", "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100246 "api-segment", "{", "prefix", cls.shm_prefix, "}",
247 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
Klement Sekera05742262018-03-14 18:14:49 +0100248 "disable", "}", "}", ]
Klement Sekera47e275b2017-03-21 08:21:25 +0100249 if plugin_path is not None:
250 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200251 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
252
253 @classmethod
254 def wait_for_enter(cls):
255 if cls.debug_gdbserver:
256 print(double_line_delim)
257 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
258 elif cls.debug_gdb:
259 print(double_line_delim)
260 print("Spawned VPP with PID: %d" % cls.vpp.pid)
261 else:
262 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
263 return
264 print(single_line_delim)
265 print("You can debug the VPP using e.g.:")
266 if cls.debug_gdbserver:
267 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
268 print("Now is the time to attach a gdb by running the above "
269 "command, set up breakpoints etc. and then resume VPP from "
270 "within gdb by issuing the 'continue' command")
271 elif cls.debug_gdb:
272 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
273 print("Now is the time to attach a gdb by running the above "
274 "command and set up breakpoints etc.")
275 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100276 raw_input("Press ENTER to continue running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200277
278 @classmethod
279 def run_vpp(cls):
280 cmdline = cls.vpp_cmdline
281
282 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100283 gdbserver = '/usr/bin/gdbserver'
284 if not os.path.isfile(gdbserver) or \
285 not os.access(gdbserver, os.X_OK):
286 raise Exception("gdbserver binary '%s' does not exist or is "
287 "not executable" % gdbserver)
288
289 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200290 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
291
Klement Sekera931be3a2016-11-03 05:36:01 +0100292 try:
293 cls.vpp = subprocess.Popen(cmdline,
294 stdout=subprocess.PIPE,
295 stderr=subprocess.PIPE,
296 bufsize=1)
297 except Exception as e:
298 cls.logger.critical("Couldn't start vpp: %s" % e)
299 raise
300
Klement Sekera277b89c2016-10-28 13:20:27 +0200301 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100302
Damjan Marionf56b77a2016-10-03 19:44:57 +0200303 @classmethod
304 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200305 """
306 Perform class setup before running the testcase
307 Remove shared memory files, start vpp and connect the vpp-api
308 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100309 gc.collect() # run garbage collection first
Klement Sekera96867ba2018-02-02 11:27:53 +0100310 random.seed()
Klement Sekera277b89c2016-10-28 13:20:27 +0200311 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200312 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200313 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera027dbd52017-04-11 06:01:53 +0200314 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
315 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100316 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
317 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200318 cls.file_handler.setLevel(DEBUG)
319 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200320 cls.shm_prefix = cls.tempdir.split("/")[-1]
321 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200322 cls.logger.info("Temporary dir is %s, shm prefix is %s",
323 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200324 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100325 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100326 cls._captures = []
327 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200328 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100329 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100330 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200331 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200332 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200333 # need to catch exceptions here because if we raise, then the cleanup
334 # doesn't get called and we might end with a zombie vpp
335 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200336 cls.run_vpp()
Dave Wallacee2efd122017-09-30 22:04:21 -0400337 cls.reporter.send_keep_alive(cls)
Klement Sekerae4504c62016-12-08 10:16:41 +0100338 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100339 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100340 cls.pump_thread_stop_flag = Event()
341 cls.pump_thread_wakeup_pipe = os.pipe()
342 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100343 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100344 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100345 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100346 if cls.step:
347 hook = StepHook(cls)
348 else:
349 hook = PollHook(cls)
350 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100351 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200352 try:
353 hook.poll_vpp()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100354 except VppDiedError:
Klement Sekera3747c752017-04-10 06:30:17 +0200355 cls.vpp_startup_failed = True
356 cls.logger.critical(
357 "VPP died shortly after startup, check the"
358 " output to standard error for possible cause")
359 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100360 try:
361 cls.vapi.connect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100362 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100363 try:
364 cls.vapi.disconnect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100365 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100366 pass
Klement Sekera085f5c02016-11-24 01:59:16 +0100367 if cls.debug_gdbserver:
368 print(colorize("You're running VPP inside gdbserver but "
369 "VPP-API connection failed, did you forget "
370 "to 'continue' VPP from within gdb?", RED))
371 raise
Klement Sekera13a83ef2018-03-21 12:35:51 +0100372 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100373 try:
374 cls.quit()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100375 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100376 pass
Klement Sekera13a83ef2018-03-21 12:35:51 +0100377 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200378
Damjan Marionf56b77a2016-10-03 19:44:57 +0200379 @classmethod
380 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200381 """
382 Disconnect vpp-api, kill vpp and cleanup shared memory files
383 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200384 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
385 cls.vpp.poll()
386 if cls.vpp.returncode is None:
387 print(double_line_delim)
388 print("VPP or GDB server is still running")
389 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100390 raw_input("When done debugging, press ENTER to kill the "
391 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200392
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100393 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
394 cls.pump_thread_stop_flag.set()
395 if hasattr(cls, 'pump_thread'):
396 cls.logger.debug("Waiting for pump thread to stop")
397 cls.pump_thread.join()
398 if hasattr(cls, 'vpp_stderr_reader_thread'):
399 cls.logger.debug("Waiting for stdderr pump to stop")
400 cls.vpp_stderr_reader_thread.join()
401
Klement Sekeraf62ae122016-10-11 11:47:09 +0200402 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100403 if hasattr(cls, 'vapi'):
404 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100405 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200406 cls.vpp.poll()
407 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100408 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200409 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100410 cls.logger.debug("Waiting for vpp to die")
411 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200412 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200413
Klement Sekera3747c752017-04-10 06:30:17 +0200414 if cls.vpp_startup_failed:
415 stdout_log = cls.logger.info
416 stderr_log = cls.logger.critical
417 else:
418 stdout_log = cls.logger.info
419 stderr_log = cls.logger.info
420
Klement Sekerae4504c62016-12-08 10:16:41 +0100421 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200422 stdout_log(single_line_delim)
423 stdout_log('VPP output to stdout while running %s:', cls.__name__)
424 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100425 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200426 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
427 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200428 stdout_log('\n%s', vpp_output)
429 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200430
Klement Sekerae4504c62016-12-08 10:16:41 +0100431 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200432 stderr_log(single_line_delim)
433 stderr_log('VPP output to stderr while running %s:', cls.__name__)
434 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100435 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200436 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
437 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200438 stderr_log('\n%s', vpp_output)
439 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200440
Damjan Marionf56b77a2016-10-03 19:44:57 +0200441 @classmethod
442 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200443 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200444 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200445 cls.file_handler.close()
Klement Sekeraebbaf552018-02-17 13:41:33 +0100446 cls.reset_packet_infos()
447 if debug_framework:
448 debug_internal.on_tear_down_class(cls)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200449
Damjan Marionf56b77a2016-10-03 19:44:57 +0200450 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200451 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100452 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
453 (self.__class__.__name__, self._testMethodName,
454 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200455 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200456 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700457 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200458 self.logger.info(self.vapi.ppcli("show hardware"))
459 self.logger.info(self.vapi.ppcli("show error"))
460 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marion07a38572018-01-21 06:44:18 -0800461 self.logger.info(self.vapi.ppcli("show log"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100462 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500463 # Save/Dump VPP api trace log
464 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
465 tmp_api_trace = "/tmp/%s" % api_trace
466 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
467 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
468 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
469 vpp_api_trace_log))
470 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500471 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500472 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100473 else:
474 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200475
Damjan Marionf56b77a2016-10-03 19:44:57 +0200476 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200477 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200478 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100479 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
480 (self.__class__.__name__, self._testMethodName,
481 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100482 if self.vpp_dead:
483 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100484 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100485 self.vpp_stdout_deque.append(
486 "--- test setUp() for %s.%s(%s) starts here ---\n" %
487 (self.__class__.__name__, self._testMethodName,
488 self._testMethodDoc))
489 self.vpp_stderr_deque.append(
490 "--- test setUp() for %s.%s(%s) starts here ---\n" %
491 (self.__class__.__name__, self._testMethodName,
492 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200493 self.vapi.cli("clear trace")
494 # store the test instance inside the test class - so that objects
495 # holding the class can access instance methods (like assertEqual)
496 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200497
Damjan Marionf56b77a2016-10-03 19:44:57 +0200498 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200499 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200500 """
501 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200502
Klement Sekera75e7d132017-09-20 08:26:30 +0200503 :param interfaces: iterable interface indexes (if None,
504 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200505
Klement Sekeraf62ae122016-10-11 11:47:09 +0200506 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200507 if interfaces is None:
508 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200509 for i in interfaces:
510 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200511
Damjan Marionf56b77a2016-10-03 19:44:57 +0200512 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100513 def register_capture(cls, cap_name):
514 """ Register a capture in the testclass """
515 # add to the list of captures with current timestamp
516 cls._captures.append((time.time(), cap_name))
517 # filter out from zombies
518 cls._zombie_captures = [(stamp, name)
519 for (stamp, name) in cls._zombie_captures
520 if name != cap_name]
521
522 @classmethod
523 def pg_start(cls):
524 """ Remove any zombie captures and enable the packet generator """
525 # how long before capture is allowed to be deleted - otherwise vpp
526 # crashes - 100ms seems enough (this shouldn't be needed at all)
527 capture_ttl = 0.1
528 now = time.time()
529 for stamp, cap_name in cls._zombie_captures:
530 wait = stamp + capture_ttl - now
531 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100532 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100533 now = time.time()
534 cls.logger.debug("Removing zombie capture %s" % cap_name)
535 cls.vapi.cli('packet-generator delete %s' % cap_name)
536
Klement Sekeraf62ae122016-10-11 11:47:09 +0200537 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
538 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100539 cls._zombie_captures = cls._captures
540 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200541
Damjan Marionf56b77a2016-10-03 19:44:57 +0200542 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200543 def create_pg_interfaces(cls, interfaces):
544 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100545 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200546
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100547 :param interfaces: iterable indexes of the interfaces.
548 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200549
Klement Sekeraf62ae122016-10-11 11:47:09 +0200550 """
551 result = []
552 for i in interfaces:
553 intf = VppPGInterface(cls, i)
554 setattr(cls, intf.name, intf)
555 result.append(intf)
556 cls.pg_interfaces = result
557 return result
558
Matej Klotton0178d522016-11-04 11:11:44 +0100559 @classmethod
560 def create_loopback_interfaces(cls, interfaces):
561 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100562 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100563
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100564 :param interfaces: iterable indexes of the interfaces.
565 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100566 """
567 result = []
568 for i in interfaces:
569 intf = VppLoInterface(cls, i)
570 setattr(cls, intf.name, intf)
571 result.append(intf)
572 cls.lo_interfaces = result
573 return result
574
Damjan Marionf56b77a2016-10-03 19:44:57 +0200575 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200576 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200577 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200578 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200579 NOTE: Currently works only when Raw layer is present.
580
581 :param packet: packet
582 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200583 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200584
585 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200586 packet_len = len(packet) + 4
587 extend = size - packet_len
588 if extend > 0:
Klement Sekera75e7d132017-09-20 08:26:30 +0200589 num = (extend / len(padding)) + 1
590 packet[Raw].load += (padding * num)[:extend]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200591
Klement Sekeradab231a2016-12-21 08:50:14 +0100592 @classmethod
593 def reset_packet_infos(cls):
594 """ Reset the list of packet info objects and packet counts to zero """
595 cls._packet_infos = {}
596 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200597
Klement Sekeradab231a2016-12-21 08:50:14 +0100598 @classmethod
599 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200600 """
601 Create packet info object containing the source and destination indexes
602 and add it to the testcase's packet info list
603
Klement Sekeradab231a2016-12-21 08:50:14 +0100604 :param VppInterface src_if: source interface
605 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200606
607 :returns: _PacketInfo object
608
609 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200610 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100611 info.index = len(cls._packet_infos)
612 info.src = src_if.sw_if_index
613 info.dst = dst_if.sw_if_index
614 if isinstance(dst_if, VppSubInterface):
615 dst_idx = dst_if.parent.sw_if_index
616 else:
617 dst_idx = dst_if.sw_if_index
618 if dst_idx in cls._packet_count_for_dst_if_idx:
619 cls._packet_count_for_dst_if_idx[dst_idx] += 1
620 else:
621 cls._packet_count_for_dst_if_idx[dst_idx] = 1
622 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200623 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200624
Damjan Marionf56b77a2016-10-03 19:44:57 +0200625 @staticmethod
626 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200627 """
628 Convert _PacketInfo object to packet payload
629
630 :param info: _PacketInfo object
631
632 :returns: string containing serialized data from packet info
633 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100634 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
635 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200636
Damjan Marionf56b77a2016-10-03 19:44:57 +0200637 @staticmethod
638 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200639 """
640 Convert packet payload to _PacketInfo object
641
642 :param payload: packet payload
643
644 :returns: _PacketInfo object containing de-serialized data from payload
645
646 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200647 numbers = payload.split()
648 info = _PacketInfo()
649 info.index = int(numbers[0])
650 info.src = int(numbers[1])
651 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100652 info.ip = int(numbers[3])
653 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200654 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200655
Damjan Marionf56b77a2016-10-03 19:44:57 +0200656 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200657 """
658 Iterate over the packet info list stored in the testcase
659 Start iteration with first element if info is None
660 Continue based on index in info if info is specified
661
662 :param info: info or None
663 :returns: next info in list or None if no more infos
664 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200665 if info is None:
666 next_index = 0
667 else:
668 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100669 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670 return None
671 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100672 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200673
Klement Sekeraf62ae122016-10-11 11:47:09 +0200674 def get_next_packet_info_for_interface(self, src_index, info):
675 """
676 Search the packet info list for the next packet info with same source
677 interface index
678
679 :param src_index: source interface index to search for
680 :param info: packet info - where to start the search
681 :returns: packet info or None
682
683 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200684 while True:
685 info = self.get_next_packet_info(info)
686 if info is None:
687 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200688 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200689 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200690
Klement Sekeraf62ae122016-10-11 11:47:09 +0200691 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
692 """
693 Search the packet info list for the next packet info with same source
694 and destination interface indexes
695
696 :param src_index: source interface index to search for
697 :param dst_index: destination interface index to search for
698 :param info: packet info - where to start the search
699 :returns: packet info or None
700
701 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200702 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200703 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200704 if info is None:
705 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200706 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200707 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200708
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200709 def assert_equal(self, real_value, expected_value, name_or_class=None):
710 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100711 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200712 return
713 try:
714 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
715 msg = msg % (getdoc(name_or_class).strip(),
716 real_value, str(name_or_class(real_value)),
717 expected_value, str(name_or_class(expected_value)))
Klement Sekera13a83ef2018-03-21 12:35:51 +0100718 except Exception:
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200719 msg = "Invalid %s: %s does not match expected value %s" % (
720 name_or_class, real_value, expected_value)
721
722 self.assertEqual(real_value, expected_value, msg)
723
Klement Sekerab17dd962017-01-09 07:43:48 +0100724 def assert_in_range(self,
725 real_value,
726 expected_min,
727 expected_max,
728 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200729 if name is None:
730 msg = None
731 else:
732 msg = "Invalid %s: %s out of range <%s,%s>" % (
733 name, real_value, expected_min, expected_max)
734 self.assertTrue(expected_min <= real_value <= expected_max, msg)
735
Klement Sekerad81ae412018-05-16 10:52:54 +0200736 def assert_packet_checksums_valid(self, packet,
737 ignore_zero_udp_checksums=True):
738 udp_layers = ['UDP', 'UDPerror']
739 checksum_fields = ['cksum', 'chksum']
740 checksums = []
741 counter = 0
742 temp = packet.__class__(str(packet))
743 while True:
744 layer = temp.getlayer(counter)
745 if layer:
746 for cf in checksum_fields:
747 if hasattr(layer, cf):
748 if ignore_zero_udp_checksums and \
749 0 == getattr(layer, cf) and \
750 layer.name in udp_layers:
751 continue
752 delattr(layer, cf)
753 checksums.append((counter, cf))
754 else:
755 break
756 counter = counter + 1
757 temp = temp.__class__(str(temp))
758 for layer, cf in checksums:
759 self.assert_equal(getattr(packet[layer], cf),
760 getattr(temp[layer], cf),
761 "packet checksum on layer #%d: %s" % (
762 layer, temp[layer].name))
763
764 def assert_checksum_valid(self, received_packet, layer,
765 field_name='chksum',
766 ignore_zero_checksum=False):
767 """ Check checksum of received packet on given layer """
768 received_packet_checksum = getattr(received_packet[layer], field_name)
769 if ignore_zero_checksum and 0 == received_packet_checksum:
770 return
771 recalculated = received_packet.__class__(str(received_packet))
772 delattr(recalculated[layer], field_name)
773 recalculated = recalculated.__class__(str(recalculated))
774 self.assert_equal(received_packet_checksum,
775 getattr(recalculated[layer], field_name),
776 "packet checksum on layer: %s" % layer)
777
778 def assert_ip_checksum_valid(self, received_packet,
779 ignore_zero_checksum=False):
780 self.assert_checksum_valid(received_packet, 'IP',
781 ignore_zero_checksum=ignore_zero_checksum)
782
783 def assert_tcp_checksum_valid(self, received_packet,
784 ignore_zero_checksum=False):
785 self.assert_checksum_valid(received_packet, 'TCP',
786 ignore_zero_checksum=ignore_zero_checksum)
787
788 def assert_udp_checksum_valid(self, received_packet,
789 ignore_zero_checksum=True):
790 self.assert_checksum_valid(received_packet, 'UDP',
791 ignore_zero_checksum=ignore_zero_checksum)
792
793 def assert_embedded_icmp_checksum_valid(self, received_packet):
794 if received_packet.haslayer(IPerror):
795 self.assert_checksum_valid(received_packet, 'IPerror')
796 if received_packet.haslayer(TCPerror):
797 self.assert_checksum_valid(received_packet, 'TCPerror')
798 if received_packet.haslayer(UDPerror):
799 self.assert_checksum_valid(received_packet, 'UDPerror',
800 ignore_zero_checksum=True)
801 if received_packet.haslayer(ICMPerror):
802 self.assert_checksum_valid(received_packet, 'ICMPerror')
803
804 def assert_icmp_checksum_valid(self, received_packet):
805 self.assert_checksum_valid(received_packet, 'ICMP')
806 self.assert_embedded_icmp_checksum_valid(received_packet)
807
808 def assert_icmpv6_checksum_valid(self, pkt):
809 if pkt.haslayer(ICMPv6DestUnreach):
810 self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
811 self.assert_embedded_icmp_checksum_valid(pkt)
812 if pkt.haslayer(ICMPv6EchoRequest):
813 self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
814 if pkt.haslayer(ICMPv6EchoReply):
815 self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
816
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100817 @classmethod
818 def sleep(cls, timeout, remark=None):
819 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000820 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
821 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100822 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000823 after = time.time()
824 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200825 cls.logger.error("unexpected time.sleep() result - "
826 "slept for %ss instead of ~%ss!" % (
827 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000828 if hasattr(cls, 'logger'):
829 cls.logger.debug(
830 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
831 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100832
Neale Ranns52fae862018-01-08 04:41:42 -0800833 def send_and_assert_no_replies(self, intf, pkts, remark=""):
834 self.vapi.cli("clear trace")
835 intf.add_stream(pkts)
836 self.pg_enable_capture(self.pg_interfaces)
837 self.pg_start()
838 timeout = 1
839 for i in self.pg_interfaces:
840 i.get_capture(0, timeout=timeout)
841 i.assert_nothing_captured(remark=remark)
842 timeout = 0.1
843
844 def send_and_expect(self, input, pkts, output):
845 self.vapi.cli("clear trace")
846 input.add_stream(pkts)
847 self.pg_enable_capture(self.pg_interfaces)
848 self.pg_start()
849 rx = output.get_capture(len(pkts))
850 return rx
851
Damjan Marionf56b77a2016-10-03 19:44:57 +0200852
Klement Sekera87134932017-03-07 11:39:27 +0100853class TestCasePrinter(object):
854 _shared_state = {}
855
856 def __init__(self):
857 self.__dict__ = self._shared_state
858 if not hasattr(self, "_test_case_set"):
859 self._test_case_set = set()
860
861 def print_test_case_heading_if_first_time(self, case):
862 if case.__class__ not in self._test_case_set:
863 print(double_line_delim)
864 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
865 print(double_line_delim)
866 self._test_case_set.add(case.__class__)
867
868
Damjan Marionf56b77a2016-10-03 19:44:57 +0200869class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200870 """
871 @property result_string
872 String variable to store the test case result string.
873 @property errors
874 List variable containing 2-tuples of TestCase instances and strings
875 holding formatted tracebacks. Each tuple represents a test which
876 raised an unexpected exception.
877 @property failures
878 List variable containing 2-tuples of TestCase instances and strings
879 holding formatted tracebacks. Each tuple represents a test where
880 a failure was explicitly signalled using the TestCase.assert*()
881 methods.
882 """
883
Damjan Marionf56b77a2016-10-03 19:44:57 +0200884 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200885 """
Klement Sekerada505f62017-01-04 12:58:53 +0100886 :param stream File descriptor to store where to report test results.
887 Set to the standard error stream by default.
888 :param descriptions Boolean variable to store information if to use
889 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200890 :param verbosity Integer variable to store required verbosity level.
891 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200892 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
893 self.stream = stream
894 self.descriptions = descriptions
895 self.verbosity = verbosity
896 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100897 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200898
Damjan Marionf56b77a2016-10-03 19:44:57 +0200899 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200900 """
901 Record a test succeeded result
902
903 :param test:
904
905 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100906 if hasattr(test, 'logger'):
907 test.logger.debug("--- addSuccess() %s.%s(%s) called"
908 % (test.__class__.__name__,
909 test._testMethodName,
910 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200911 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200912 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200913
Klement Sekeraf62ae122016-10-11 11:47:09 +0200914 def addSkip(self, test, reason):
915 """
916 Record a test skipped.
917
918 :param test:
919 :param reason:
920
921 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100922 if hasattr(test, 'logger'):
923 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
924 % (test.__class__.__name__,
925 test._testMethodName,
926 test._testMethodDoc,
927 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200928 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200929 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200930
Klement Sekeraf413bef2017-08-15 07:09:02 +0200931 def symlink_failed(self, test):
932 logger = None
933 if hasattr(test, 'logger'):
934 logger = test.logger
935 if hasattr(test, 'tempdir'):
936 try:
937 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
938 link_path = '%s/%s-FAILED' % (failed_dir,
939 test.tempdir.split("/")[-1])
940 if logger:
941 logger.debug("creating a link to the failed test")
942 logger.debug("os.symlink(%s, %s)" %
943 (test.tempdir, link_path))
944 os.symlink(test.tempdir, link_path)
945 except Exception as e:
946 if logger:
947 logger.error(e)
948
Klement Sekeradf2b9802017-10-05 10:26:03 +0200949 def send_failure_through_pipe(self, test):
950 if hasattr(self, 'test_framework_failed_pipe'):
951 pipe = self.test_framework_failed_pipe
952 if pipe:
953 pipe.send(test.__class__)
954
Damjan Marionf56b77a2016-10-03 19:44:57 +0200955 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200956 """
957 Record a test failed result
958
959 :param test:
960 :param err: error message
961
962 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100963 if hasattr(test, 'logger'):
964 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
965 % (test.__class__.__name__,
966 test._testMethodName,
967 test._testMethodDoc, err))
968 test.logger.debug("formatted exception is:\n%s" %
969 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200970 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200971 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200972 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200973 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200974 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200975 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200976 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200977
Klement Sekeradf2b9802017-10-05 10:26:03 +0200978 self.send_failure_through_pipe(test)
979
Damjan Marionf56b77a2016-10-03 19:44:57 +0200980 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200981 """
982 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200983
Klement Sekeraf62ae122016-10-11 11:47:09 +0200984 :param test:
985 :param err: error message
986
987 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100988 if hasattr(test, 'logger'):
989 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
990 % (test.__class__.__name__,
991 test._testMethodName,
992 test._testMethodDoc, err))
993 test.logger.debug("formatted exception is:\n%s" %
994 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200995 unittest.TestResult.addError(self, test, err)
996 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200997 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200998 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200999 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +02001000 else:
Klement Sekera277b89c2016-10-28 13:20:27 +02001001 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +02001002
Klement Sekeradf2b9802017-10-05 10:26:03 +02001003 self.send_failure_through_pipe(test)
1004
Damjan Marionf56b77a2016-10-03 19:44:57 +02001005 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001006 """
1007 Get test description
1008
1009 :param test:
1010 :returns: test description
1011
1012 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001013 # TODO: if none print warning not raise exception
1014 short_description = test.shortDescription()
1015 if self.descriptions and short_description:
1016 return short_description
1017 else:
1018 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001019
Damjan Marionf56b77a2016-10-03 19:44:57 +02001020 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001021 """
1022 Start a test
1023
1024 :param test:
1025
1026 """
Klement Sekera87134932017-03-07 11:39:27 +01001027 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001028 unittest.TestResult.startTest(self, test)
1029 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001030 self.stream.writeln(
1031 "Starting " + self.getDescription(test) + " ...")
1032 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001033
Damjan Marionf56b77a2016-10-03 19:44:57 +02001034 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001035 """
1036 Stop a test
1037
1038 :param test:
1039
1040 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001041 unittest.TestResult.stopTest(self, test)
1042 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001043 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +01001044 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001045 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001046 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001047 else:
Klement Sekera52e84f32017-01-13 07:25:25 +01001048 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001049 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +02001050
Damjan Marionf56b77a2016-10-03 19:44:57 +02001051 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001052 """
1053 Print errors from running the test case
1054 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001055 self.stream.writeln()
1056 self.printErrorList('ERROR', self.errors)
1057 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001058
Damjan Marionf56b77a2016-10-03 19:44:57 +02001059 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001060 """
1061 Print error list to the output stream together with error type
1062 and test case description.
1063
1064 :param flavour: error type
1065 :param errors: iterable errors
1066
1067 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001068 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001069 self.stream.writeln(double_line_delim)
1070 self.stream.writeln("%s: %s" %
1071 (flavour, self.getDescription(test)))
1072 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001073 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001074
1075
Klement Sekeradf2b9802017-10-05 10:26:03 +02001076class Filter_by_test_option:
1077 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
1078 self.filter_file_name = filter_file_name
1079 self.filter_class_name = filter_class_name
1080 self.filter_func_name = filter_func_name
1081
1082 def __call__(self, file_name, class_name, func_name):
1083 if self.filter_file_name and file_name != self.filter_file_name:
1084 return False
1085 if self.filter_class_name and class_name != self.filter_class_name:
1086 return False
1087 if self.filter_func_name and func_name != self.filter_func_name:
1088 return False
1089 return True
1090
1091
Damjan Marionf56b77a2016-10-03 19:44:57 +02001092class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001093 """
Klement Sekera104543f2017-02-03 07:29:43 +01001094 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001095 """
1096 @property
1097 def resultclass(self):
1098 """Class maintaining the results of the tests"""
1099 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001100
Klement Sekeradf2b9802017-10-05 10:26:03 +02001101 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1102 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +02001103 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +01001104 # ignore stream setting here, use hard-coded stdout to be in sync
1105 # with prints from VppTestCase methods ...
1106 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1107 verbosity, failfast, buffer,
1108 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001109 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001110 reporter.pipe = keep_alive_pipe
1111 # this is super-ugly, but very simple to implement and works as long
1112 # as we run only one test at the same time
1113 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +01001114
Klement Sekera104543f2017-02-03 07:29:43 +01001115 test_option = "TEST"
1116
1117 def parse_test_option(self):
Klement Sekera13a83ef2018-03-21 12:35:51 +01001118 f = os.getenv(self.test_option, None)
Klement Sekera104543f2017-02-03 07:29:43 +01001119 filter_file_name = None
1120 filter_class_name = None
1121 filter_func_name = None
1122 if f:
1123 if '.' in f:
1124 parts = f.split('.')
1125 if len(parts) > 3:
1126 raise Exception("Unrecognized %s option: %s" %
1127 (self.test_option, f))
1128 if len(parts) > 2:
1129 if parts[2] not in ('*', ''):
1130 filter_func_name = parts[2]
1131 if parts[1] not in ('*', ''):
1132 filter_class_name = parts[1]
1133 if parts[0] not in ('*', ''):
1134 if parts[0].startswith('test_'):
1135 filter_file_name = parts[0]
1136 else:
1137 filter_file_name = 'test_%s' % parts[0]
1138 else:
1139 if f.startswith('test_'):
1140 filter_file_name = f
1141 else:
1142 filter_file_name = 'test_%s' % f
1143 return filter_file_name, filter_class_name, filter_func_name
1144
Klement Sekeradf2b9802017-10-05 10:26:03 +02001145 @staticmethod
1146 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001147 result = unittest.suite.TestSuite()
1148 for t in tests:
1149 if isinstance(t, unittest.suite.TestSuite):
1150 # this is a bunch of tests, recursively filter...
Klement Sekera05742262018-03-14 18:14:49 +01001151 x = VppTestRunner.filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001152 if x.countTestCases() > 0:
1153 result.addTest(x)
1154 elif isinstance(t, unittest.TestCase):
1155 # this is a single test
1156 parts = t.id().split('.')
1157 # t.id() for common cases like this:
1158 # test_classifier.TestClassifier.test_acl_ip
1159 # apply filtering only if it is so
1160 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001161 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001162 continue
1163 result.addTest(t)
1164 else:
1165 # unexpected object, don't touch it
1166 result.addTest(t)
1167 return result
1168
Damjan Marionf56b77a2016-10-03 19:44:57 +02001169 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001170 """
1171 Run the tests
1172
1173 :param test:
1174
1175 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001176 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001177 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001178 filter_file, filter_class, filter_func = self.parse_test_option()
1179 print("Active filters: file=%s, class=%s, function=%s" % (
1180 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001181 filter_cb = Filter_by_test_option(
1182 filter_file, filter_class, filter_func)
1183 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001184 print("%s out of %s tests match specified filters" % (
1185 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001186 if not running_extended_tests():
1187 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +01001188 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001189
1190
1191class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001192 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001193 self.logger = logger
1194 self.args = args
1195 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001196 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001197 super(Worker, self).__init__()
1198
1199 def run(self):
1200 executable = self.args[0]
1201 self.logger.debug("Running executable w/args `%s'" % self.args)
1202 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001203 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001204 env["CK_LOG_FILE_NAME"] = "-"
1205 self.process = subprocess.Popen(
1206 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1207 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1208 out, err = self.process.communicate()
1209 self.logger.debug("Finished running `%s'" % executable)
1210 self.logger.info("Return code is `%s'" % self.process.returncode)
1211 self.logger.info(single_line_delim)
1212 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1213 self.logger.info(single_line_delim)
1214 self.logger.info(out)
1215 self.logger.info(single_line_delim)
1216 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1217 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001218 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001219 self.logger.info(single_line_delim)
1220 self.result = self.process.returncode