blob: fdaba2b84d93fb42a8befc4f38c04662964651d4 [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 Sekera31da2e32018-06-24 22:49:55 +020028from util import ppp
Klement Sekerad81ae412018-05-16 10:52:54 +020029from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
30from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
31from scapy.layers.inet6 import ICMPv6EchoReply
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010032if os.name == 'posix' and sys.version_info[0] < 3:
33 # using subprocess32 is recommended by python official documentation
34 # @ https://docs.python.org/2/library/subprocess.html
35 import subprocess32 as subprocess
36else:
37 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020038
Klement Sekerad81ae412018-05-16 10:52:54 +020039
Klement Sekeraebbaf552018-02-17 13:41:33 +010040debug_framework = False
41if os.getenv('TEST_DEBUG', "0") == "1":
42 debug_framework = True
43 import debug_internal
44
45
Klement Sekeraf62ae122016-10-11 11:47:09 +020046"""
47 Test framework module.
48
49 The module provides a set of tools for constructing and running tests and
50 representing the results.
51"""
52
Klement Sekeraf62ae122016-10-11 11:47:09 +020053
Damjan Marionf56b77a2016-10-03 19:44:57 +020054class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020055 """Private class to create packet info object.
56
57 Help process information about the next packet.
58 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020059 """
Matej Klotton86d87c42016-11-11 11:38:55 +010060 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020061 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010062 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020063 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010064 #: Store the index of the destination packet generator interface
65 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020066 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +010067 #: Store expected ip version
68 ip = -1
69 #: Store expected upper protocol
70 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010071 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020072 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020073
Matej Klotton16a14cd2016-12-07 15:09:13 +010074 def __eq__(self, other):
75 index = self.index == other.index
76 src = self.src == other.src
77 dst = self.dst == other.dst
78 data = self.data == other.data
79 return index and src and dst and data
80
Klement Sekeraf62ae122016-10-11 11:47:09 +020081
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010082def pump_output(testclass):
83 """ pump output from vpp stdout/stderr to proper queues """
Klement Sekera6a6f4f72017-11-09 09:16:39 +010084 stdout_fragment = ""
85 stderr_fragment = ""
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010086 while not testclass.pump_thread_stop_flag.wait(0):
87 readable = select.select([testclass.vpp.stdout.fileno(),
88 testclass.vpp.stderr.fileno(),
89 testclass.pump_thread_wakeup_pipe[0]],
90 [], [])[0]
91 if testclass.vpp.stdout.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +010092 read = os.read(testclass.vpp.stdout.fileno(), 102400)
93 if len(read) > 0:
94 split = read.splitlines(True)
95 if len(stdout_fragment) > 0:
96 split[0] = "%s%s" % (stdout_fragment, split[0])
97 if len(split) > 0 and split[-1].endswith("\n"):
98 limit = None
99 else:
100 limit = -1
101 stdout_fragment = split[-1]
102 testclass.vpp_stdout_deque.extend(split[:limit])
103 if not testclass.cache_vpp_output:
104 for line in split[:limit]:
105 testclass.logger.debug(
106 "VPP STDOUT: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100107 if testclass.vpp.stderr.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100108 read = os.read(testclass.vpp.stderr.fileno(), 102400)
109 if len(read) > 0:
110 split = read.splitlines(True)
111 if len(stderr_fragment) > 0:
112 split[0] = "%s%s" % (stderr_fragment, split[0])
113 if len(split) > 0 and split[-1].endswith("\n"):
114 limit = None
115 else:
116 limit = -1
117 stderr_fragment = split[-1]
118 testclass.vpp_stderr_deque.extend(split[:limit])
119 if not testclass.cache_vpp_output:
120 for line in split[:limit]:
121 testclass.logger.debug(
122 "VPP STDERR: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100123 # ignoring the dummy pipe here intentionally - the flag will take care
124 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +0200125
126
Klement Sekera87134932017-03-07 11:39:27 +0100127def running_extended_tests():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100128 s = os.getenv("EXTENDED_TESTS", "n")
129 return True if s.lower() in ("y", "yes", "1") else False
Klement Sekera87134932017-03-07 11:39:27 +0100130
131
Klement Sekerad3e671e2017-09-29 12:36:37 +0200132def running_on_centos():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100133 os_id = os.getenv("OS_ID", "")
134 return True if "centos" in os_id.lower() else False
Klement Sekerad3e671e2017-09-29 12:36:37 +0200135
136
Klement Sekera909a6a12017-08-08 04:33:53 +0200137class KeepAliveReporter(object):
138 """
139 Singleton object which reports test start to parent process
140 """
141 _shared_state = {}
142
143 def __init__(self):
144 self.__dict__ = self._shared_state
145
146 @property
147 def pipe(self):
148 return self._pipe
149
150 @pipe.setter
151 def pipe(self, pipe):
152 if hasattr(self, '_pipe'):
153 raise Exception("Internal error - pipe should only be set once.")
154 self._pipe = pipe
155
156 def send_keep_alive(self, test):
157 """
158 Write current test tmpdir & desc to keep-alive pipe to signal liveness
159 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200160 if self.pipe is None:
161 # if not running forked..
162 return
163
Klement Sekera909a6a12017-08-08 04:33:53 +0200164 if isclass(test):
165 desc = test.__name__
166 else:
167 desc = test.shortDescription()
168 if not desc:
169 desc = str(test)
170
Dave Wallacee2efd122017-09-30 22:04:21 -0400171 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200172
173
Damjan Marionf56b77a2016-10-03 19:44:57 +0200174class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100175 """This subclass is a base class for VPP test cases that are implemented as
176 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200177 """
178
179 @property
180 def packet_infos(self):
181 """List of packet infos"""
182 return self._packet_infos
183
Klement Sekeradab231a2016-12-21 08:50:14 +0100184 @classmethod
185 def get_packet_count_for_if_idx(cls, dst_if_index):
186 """Get the number of packet info for specified destination if index"""
187 if dst_if_index in cls._packet_count_for_dst_if_idx:
188 return cls._packet_count_for_dst_if_idx[dst_if_index]
189 else:
190 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200191
192 @classmethod
193 def instance(cls):
194 """Return the instance of this testcase"""
195 return cls.test_instance
196
Damjan Marionf56b77a2016-10-03 19:44:57 +0200197 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200198 def set_debug_flags(cls, d):
199 cls.debug_core = False
200 cls.debug_gdb = False
201 cls.debug_gdbserver = False
202 if d is None:
203 return
204 dl = d.lower()
205 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200206 cls.debug_core = True
207 elif dl == "gdb":
208 cls.debug_gdb = True
209 elif dl == "gdbserver":
210 cls.debug_gdbserver = True
211 else:
212 raise Exception("Unrecognized DEBUG option: '%s'" % d)
213
214 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200215 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200216 """ Set-up the test case class based on environment variables """
Klement Sekera13a83ef2018-03-21 12:35:51 +0100217 s = os.getenv("STEP", "n")
218 cls.step = True if s.lower() in ("y", "yes", "1") else False
219 d = os.getenv("DEBUG", None)
220 c = os.getenv("CACHE_OUTPUT", "1")
221 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
Klement Sekera277b89c2016-10-28 13:20:27 +0200222 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200223 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100224 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100225 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
226 plugin_path = None
227 if cls.plugin_path is not None:
228 if cls.extern_plugin_path is not None:
229 plugin_path = "%s:%s" % (
230 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100231 else:
232 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100233 elif cls.extern_plugin_path is not None:
234 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100235 debug_cli = ""
236 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
237 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100238 coredump_size = None
Klement Sekera13a83ef2018-03-21 12:35:51 +0100239 size = os.getenv("COREDUMP_SIZE")
240 if size is not None:
241 coredump_size = "coredump-size %s" % size
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100242 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400243 coredump_size = "coredump-size unlimited"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100244 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400245 "{", "nodaemon", debug_cli, "full-coredump",
246 coredump_size, "}", "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100247 "api-segment", "{", "prefix", cls.shm_prefix, "}",
248 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
Klement Sekera05742262018-03-14 18:14:49 +0100249 "disable", "}", "}", ]
Klement Sekera47e275b2017-03-21 08:21:25 +0100250 if plugin_path is not None:
251 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200252 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
253
254 @classmethod
255 def wait_for_enter(cls):
256 if cls.debug_gdbserver:
257 print(double_line_delim)
258 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
259 elif cls.debug_gdb:
260 print(double_line_delim)
261 print("Spawned VPP with PID: %d" % cls.vpp.pid)
262 else:
263 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
264 return
265 print(single_line_delim)
266 print("You can debug the VPP using e.g.:")
267 if cls.debug_gdbserver:
268 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
269 print("Now is the time to attach a gdb by running the above "
270 "command, set up breakpoints etc. and then resume VPP from "
271 "within gdb by issuing the 'continue' command")
272 elif cls.debug_gdb:
273 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
274 print("Now is the time to attach a gdb by running the above "
275 "command and set up breakpoints etc.")
276 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100277 raw_input("Press ENTER to continue running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200278
279 @classmethod
280 def run_vpp(cls):
281 cmdline = cls.vpp_cmdline
282
283 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100284 gdbserver = '/usr/bin/gdbserver'
285 if not os.path.isfile(gdbserver) or \
286 not os.access(gdbserver, os.X_OK):
287 raise Exception("gdbserver binary '%s' does not exist or is "
288 "not executable" % gdbserver)
289
290 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200291 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
292
Klement Sekera931be3a2016-11-03 05:36:01 +0100293 try:
294 cls.vpp = subprocess.Popen(cmdline,
295 stdout=subprocess.PIPE,
296 stderr=subprocess.PIPE,
297 bufsize=1)
298 except Exception as e:
299 cls.logger.critical("Couldn't start vpp: %s" % e)
300 raise
301
Klement Sekera277b89c2016-10-28 13:20:27 +0200302 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100303
Damjan Marionf56b77a2016-10-03 19:44:57 +0200304 @classmethod
305 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200306 """
307 Perform class setup before running the testcase
308 Remove shared memory files, start vpp and connect the vpp-api
309 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100310 gc.collect() # run garbage collection first
Klement Sekera96867ba2018-02-02 11:27:53 +0100311 random.seed()
Klement Sekera277b89c2016-10-28 13:20:27 +0200312 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200313 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200314 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera027dbd52017-04-11 06:01:53 +0200315 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
316 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100317 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
318 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200319 cls.file_handler.setLevel(DEBUG)
320 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200321 cls.shm_prefix = cls.tempdir.split("/")[-1]
322 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200323 cls.logger.info("Temporary dir is %s, shm prefix is %s",
324 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200325 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100326 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100327 cls._captures = []
328 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200329 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100330 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100331 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200332 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200333 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200334 # need to catch exceptions here because if we raise, then the cleanup
335 # doesn't get called and we might end with a zombie vpp
336 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200337 cls.run_vpp()
Dave Wallacee2efd122017-09-30 22:04:21 -0400338 cls.reporter.send_keep_alive(cls)
Klement Sekerae4504c62016-12-08 10:16:41 +0100339 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100340 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100341 cls.pump_thread_stop_flag = Event()
342 cls.pump_thread_wakeup_pipe = os.pipe()
343 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100344 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100345 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100346 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100347 if cls.step:
348 hook = StepHook(cls)
349 else:
350 hook = PollHook(cls)
351 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100352 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200353 try:
354 hook.poll_vpp()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100355 except VppDiedError:
Klement Sekera3747c752017-04-10 06:30:17 +0200356 cls.vpp_startup_failed = True
357 cls.logger.critical(
358 "VPP died shortly after startup, check the"
359 " output to standard error for possible cause")
360 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100361 try:
362 cls.vapi.connect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100363 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100364 try:
365 cls.vapi.disconnect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100366 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100367 pass
Klement Sekera085f5c02016-11-24 01:59:16 +0100368 if cls.debug_gdbserver:
369 print(colorize("You're running VPP inside gdbserver but "
370 "VPP-API connection failed, did you forget "
371 "to 'continue' VPP from within gdb?", RED))
372 raise
Klement Sekera13a83ef2018-03-21 12:35:51 +0100373 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100374 try:
375 cls.quit()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100376 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100377 pass
Klement Sekera13a83ef2018-03-21 12:35:51 +0100378 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200379
Damjan Marionf56b77a2016-10-03 19:44:57 +0200380 @classmethod
381 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200382 """
383 Disconnect vpp-api, kill vpp and cleanup shared memory files
384 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200385 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
386 cls.vpp.poll()
387 if cls.vpp.returncode is None:
388 print(double_line_delim)
389 print("VPP or GDB server is still running")
390 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100391 raw_input("When done debugging, press ENTER to kill the "
392 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200393
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100394 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
395 cls.pump_thread_stop_flag.set()
396 if hasattr(cls, 'pump_thread'):
397 cls.logger.debug("Waiting for pump thread to stop")
398 cls.pump_thread.join()
399 if hasattr(cls, 'vpp_stderr_reader_thread'):
400 cls.logger.debug("Waiting for stdderr pump to stop")
401 cls.vpp_stderr_reader_thread.join()
402
Klement Sekeraf62ae122016-10-11 11:47:09 +0200403 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100404 if hasattr(cls, 'vapi'):
405 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100406 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200407 cls.vpp.poll()
408 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100409 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200410 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100411 cls.logger.debug("Waiting for vpp to die")
412 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200413 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200414
Klement Sekera3747c752017-04-10 06:30:17 +0200415 if cls.vpp_startup_failed:
416 stdout_log = cls.logger.info
417 stderr_log = cls.logger.critical
418 else:
419 stdout_log = cls.logger.info
420 stderr_log = cls.logger.info
421
Klement Sekerae4504c62016-12-08 10:16:41 +0100422 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200423 stdout_log(single_line_delim)
424 stdout_log('VPP output to stdout while running %s:', cls.__name__)
425 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100426 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200427 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
428 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200429 stdout_log('\n%s', vpp_output)
430 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200431
Klement Sekerae4504c62016-12-08 10:16:41 +0100432 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200433 stderr_log(single_line_delim)
434 stderr_log('VPP output to stderr while running %s:', cls.__name__)
435 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100436 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200437 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
438 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200439 stderr_log('\n%s', vpp_output)
440 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200441
Damjan Marionf56b77a2016-10-03 19:44:57 +0200442 @classmethod
443 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200444 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200445 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200446 cls.file_handler.close()
Klement Sekeraebbaf552018-02-17 13:41:33 +0100447 cls.reset_packet_infos()
448 if debug_framework:
449 debug_internal.on_tear_down_class(cls)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200450
Damjan Marionf56b77a2016-10-03 19:44:57 +0200451 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200452 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100453 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
454 (self.__class__.__name__, self._testMethodName,
455 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200456 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200457 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700458 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200459 self.logger.info(self.vapi.ppcli("show hardware"))
460 self.logger.info(self.vapi.ppcli("show error"))
461 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marion07a38572018-01-21 06:44:18 -0800462 self.logger.info(self.vapi.ppcli("show log"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100463 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500464 # Save/Dump VPP api trace log
465 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
466 tmp_api_trace = "/tmp/%s" % api_trace
467 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
468 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
469 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
470 vpp_api_trace_log))
471 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500472 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500473 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100474 else:
475 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200476
Damjan Marionf56b77a2016-10-03 19:44:57 +0200477 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200478 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200479 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100480 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
481 (self.__class__.__name__, self._testMethodName,
482 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100483 if self.vpp_dead:
484 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100485 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100486 self.vpp_stdout_deque.append(
487 "--- test setUp() for %s.%s(%s) starts here ---\n" %
488 (self.__class__.__name__, self._testMethodName,
489 self._testMethodDoc))
490 self.vpp_stderr_deque.append(
491 "--- test setUp() for %s.%s(%s) starts here ---\n" %
492 (self.__class__.__name__, self._testMethodName,
493 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200494 self.vapi.cli("clear trace")
495 # store the test instance inside the test class - so that objects
496 # holding the class can access instance methods (like assertEqual)
497 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200498
Damjan Marionf56b77a2016-10-03 19:44:57 +0200499 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200500 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200501 """
502 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200503
Klement Sekera75e7d132017-09-20 08:26:30 +0200504 :param interfaces: iterable interface indexes (if None,
505 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200506
Klement Sekeraf62ae122016-10-11 11:47:09 +0200507 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200508 if interfaces is None:
509 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200510 for i in interfaces:
511 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200512
Damjan Marionf56b77a2016-10-03 19:44:57 +0200513 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100514 def register_capture(cls, cap_name):
515 """ Register a capture in the testclass """
516 # add to the list of captures with current timestamp
517 cls._captures.append((time.time(), cap_name))
518 # filter out from zombies
519 cls._zombie_captures = [(stamp, name)
520 for (stamp, name) in cls._zombie_captures
521 if name != cap_name]
522
523 @classmethod
524 def pg_start(cls):
525 """ Remove any zombie captures and enable the packet generator """
526 # how long before capture is allowed to be deleted - otherwise vpp
527 # crashes - 100ms seems enough (this shouldn't be needed at all)
528 capture_ttl = 0.1
529 now = time.time()
530 for stamp, cap_name in cls._zombie_captures:
531 wait = stamp + capture_ttl - now
532 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100533 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100534 now = time.time()
535 cls.logger.debug("Removing zombie capture %s" % cap_name)
536 cls.vapi.cli('packet-generator delete %s' % cap_name)
537
Klement Sekeraf62ae122016-10-11 11:47:09 +0200538 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
539 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100540 cls._zombie_captures = cls._captures
541 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200542
Damjan Marionf56b77a2016-10-03 19:44:57 +0200543 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200544 def create_pg_interfaces(cls, interfaces):
545 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100546 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200547
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100548 :param interfaces: iterable indexes of the interfaces.
549 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200550
Klement Sekeraf62ae122016-10-11 11:47:09 +0200551 """
552 result = []
553 for i in interfaces:
554 intf = VppPGInterface(cls, i)
555 setattr(cls, intf.name, intf)
556 result.append(intf)
557 cls.pg_interfaces = result
558 return result
559
Matej Klotton0178d522016-11-04 11:11:44 +0100560 @classmethod
Klement Sekerab9ef2732018-06-24 22:49:33 +0200561 def create_loopback_interfaces(cls, count):
Matej Klotton0178d522016-11-04 11:11:44 +0100562 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100563 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100564
Klement Sekerab9ef2732018-06-24 22:49:33 +0200565 :param count: number of interfaces created.
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100566 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100567 """
Klement Sekerab9ef2732018-06-24 22:49:33 +0200568 result = [VppLoInterface(cls) for i in range(count)]
569 for intf in result:
Matej Klotton0178d522016-11-04 11:11:44 +0100570 setattr(cls, intf.name, intf)
Matej Klotton0178d522016-11-04 11:11:44 +0100571 cls.lo_interfaces = result
572 return result
573
Damjan Marionf56b77a2016-10-03 19:44:57 +0200574 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200575 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200576 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200577 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200578 NOTE: Currently works only when Raw layer is present.
579
580 :param packet: packet
581 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200582 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200583
584 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200585 packet_len = len(packet) + 4
586 extend = size - packet_len
587 if extend > 0:
Klement Sekera75e7d132017-09-20 08:26:30 +0200588 num = (extend / len(padding)) + 1
589 packet[Raw].load += (padding * num)[:extend]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200590
Klement Sekeradab231a2016-12-21 08:50:14 +0100591 @classmethod
592 def reset_packet_infos(cls):
593 """ Reset the list of packet info objects and packet counts to zero """
594 cls._packet_infos = {}
595 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200596
Klement Sekeradab231a2016-12-21 08:50:14 +0100597 @classmethod
598 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200599 """
600 Create packet info object containing the source and destination indexes
601 and add it to the testcase's packet info list
602
Klement Sekeradab231a2016-12-21 08:50:14 +0100603 :param VppInterface src_if: source interface
604 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200605
606 :returns: _PacketInfo object
607
608 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200609 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100610 info.index = len(cls._packet_infos)
611 info.src = src_if.sw_if_index
612 info.dst = dst_if.sw_if_index
613 if isinstance(dst_if, VppSubInterface):
614 dst_idx = dst_if.parent.sw_if_index
615 else:
616 dst_idx = dst_if.sw_if_index
617 if dst_idx in cls._packet_count_for_dst_if_idx:
618 cls._packet_count_for_dst_if_idx[dst_idx] += 1
619 else:
620 cls._packet_count_for_dst_if_idx[dst_idx] = 1
621 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200622 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200623
Damjan Marionf56b77a2016-10-03 19:44:57 +0200624 @staticmethod
625 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200626 """
627 Convert _PacketInfo object to packet payload
628
629 :param info: _PacketInfo object
630
631 :returns: string containing serialized data from packet info
632 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100633 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
634 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200635
Damjan Marionf56b77a2016-10-03 19:44:57 +0200636 @staticmethod
637 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200638 """
639 Convert packet payload to _PacketInfo object
640
641 :param payload: packet payload
642
643 :returns: _PacketInfo object containing de-serialized data from payload
644
645 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200646 numbers = payload.split()
647 info = _PacketInfo()
648 info.index = int(numbers[0])
649 info.src = int(numbers[1])
650 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100651 info.ip = int(numbers[3])
652 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200653 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200654
Damjan Marionf56b77a2016-10-03 19:44:57 +0200655 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200656 """
657 Iterate over the packet info list stored in the testcase
658 Start iteration with first element if info is None
659 Continue based on index in info if info is specified
660
661 :param info: info or None
662 :returns: next info in list or None if no more infos
663 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200664 if info is None:
665 next_index = 0
666 else:
667 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100668 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200669 return None
670 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100671 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200672
Klement Sekeraf62ae122016-10-11 11:47:09 +0200673 def get_next_packet_info_for_interface(self, src_index, info):
674 """
675 Search the packet info list for the next packet info with same source
676 interface index
677
678 :param src_index: source interface index to search for
679 :param info: packet info - where to start the search
680 :returns: packet info or None
681
682 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200683 while True:
684 info = self.get_next_packet_info(info)
685 if info is None:
686 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200687 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200688 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200689
Klement Sekeraf62ae122016-10-11 11:47:09 +0200690 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
691 """
692 Search the packet info list for the next packet info with same source
693 and destination interface indexes
694
695 :param src_index: source interface index to search for
696 :param dst_index: destination interface index to search for
697 :param info: packet info - where to start the search
698 :returns: packet info or None
699
700 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200701 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200702 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200703 if info is None:
704 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200705 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200706 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200707
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200708 def assert_equal(self, real_value, expected_value, name_or_class=None):
709 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100710 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200711 return
712 try:
713 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
714 msg = msg % (getdoc(name_or_class).strip(),
715 real_value, str(name_or_class(real_value)),
716 expected_value, str(name_or_class(expected_value)))
Klement Sekera13a83ef2018-03-21 12:35:51 +0100717 except Exception:
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200718 msg = "Invalid %s: %s does not match expected value %s" % (
719 name_or_class, real_value, expected_value)
720
721 self.assertEqual(real_value, expected_value, msg)
722
Klement Sekerab17dd962017-01-09 07:43:48 +0100723 def assert_in_range(self,
724 real_value,
725 expected_min,
726 expected_max,
727 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200728 if name is None:
729 msg = None
730 else:
731 msg = "Invalid %s: %s out of range <%s,%s>" % (
732 name, real_value, expected_min, expected_max)
733 self.assertTrue(expected_min <= real_value <= expected_max, msg)
734
Klement Sekerad81ae412018-05-16 10:52:54 +0200735 def assert_packet_checksums_valid(self, packet,
736 ignore_zero_udp_checksums=True):
Klement Sekera31da2e32018-06-24 22:49:55 +0200737 received = packet.__class__(str(packet))
738 self.logger.debug(
739 ppp("Verifying packet checksums for packet:", received))
Klement Sekerad81ae412018-05-16 10:52:54 +0200740 udp_layers = ['UDP', 'UDPerror']
741 checksum_fields = ['cksum', 'chksum']
742 checksums = []
743 counter = 0
Klement Sekera31da2e32018-06-24 22:49:55 +0200744 temp = received.__class__(str(received))
Klement Sekerad81ae412018-05-16 10:52:54 +0200745 while True:
746 layer = temp.getlayer(counter)
747 if layer:
748 for cf in checksum_fields:
749 if hasattr(layer, cf):
750 if ignore_zero_udp_checksums and \
751 0 == getattr(layer, cf) and \
752 layer.name in udp_layers:
753 continue
754 delattr(layer, cf)
755 checksums.append((counter, cf))
756 else:
757 break
758 counter = counter + 1
Klement Sekera31da2e32018-06-24 22:49:55 +0200759 if 0 == len(checksums):
760 return
Klement Sekerad81ae412018-05-16 10:52:54 +0200761 temp = temp.__class__(str(temp))
762 for layer, cf in checksums:
Klement Sekera31da2e32018-06-24 22:49:55 +0200763 calc_sum = getattr(temp[layer], cf)
764 self.assert_equal(
765 getattr(received[layer], cf), calc_sum,
766 "packet checksum on layer #%d: %s" % (layer, temp[layer].name))
767 self.logger.debug(
768 "Checksum field `%s` on `%s` layer has correct value `%s`" %
769 (cf, temp[layer].name, calc_sum))
Klement Sekerad81ae412018-05-16 10:52:54 +0200770
771 def assert_checksum_valid(self, received_packet, layer,
772 field_name='chksum',
773 ignore_zero_checksum=False):
774 """ Check checksum of received packet on given layer """
775 received_packet_checksum = getattr(received_packet[layer], field_name)
776 if ignore_zero_checksum and 0 == received_packet_checksum:
777 return
778 recalculated = received_packet.__class__(str(received_packet))
779 delattr(recalculated[layer], field_name)
780 recalculated = recalculated.__class__(str(recalculated))
781 self.assert_equal(received_packet_checksum,
782 getattr(recalculated[layer], field_name),
783 "packet checksum on layer: %s" % layer)
784
785 def assert_ip_checksum_valid(self, received_packet,
786 ignore_zero_checksum=False):
787 self.assert_checksum_valid(received_packet, 'IP',
788 ignore_zero_checksum=ignore_zero_checksum)
789
790 def assert_tcp_checksum_valid(self, received_packet,
791 ignore_zero_checksum=False):
792 self.assert_checksum_valid(received_packet, 'TCP',
793 ignore_zero_checksum=ignore_zero_checksum)
794
795 def assert_udp_checksum_valid(self, received_packet,
796 ignore_zero_checksum=True):
797 self.assert_checksum_valid(received_packet, 'UDP',
798 ignore_zero_checksum=ignore_zero_checksum)
799
800 def assert_embedded_icmp_checksum_valid(self, received_packet):
801 if received_packet.haslayer(IPerror):
802 self.assert_checksum_valid(received_packet, 'IPerror')
803 if received_packet.haslayer(TCPerror):
804 self.assert_checksum_valid(received_packet, 'TCPerror')
805 if received_packet.haslayer(UDPerror):
806 self.assert_checksum_valid(received_packet, 'UDPerror',
807 ignore_zero_checksum=True)
808 if received_packet.haslayer(ICMPerror):
809 self.assert_checksum_valid(received_packet, 'ICMPerror')
810
811 def assert_icmp_checksum_valid(self, received_packet):
812 self.assert_checksum_valid(received_packet, 'ICMP')
813 self.assert_embedded_icmp_checksum_valid(received_packet)
814
815 def assert_icmpv6_checksum_valid(self, pkt):
816 if pkt.haslayer(ICMPv6DestUnreach):
817 self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
818 self.assert_embedded_icmp_checksum_valid(pkt)
819 if pkt.haslayer(ICMPv6EchoRequest):
820 self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
821 if pkt.haslayer(ICMPv6EchoReply):
822 self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
823
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100824 @classmethod
825 def sleep(cls, timeout, remark=None):
826 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000827 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
828 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100829 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000830 after = time.time()
831 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200832 cls.logger.error("unexpected time.sleep() result - "
833 "slept for %ss instead of ~%ss!" % (
834 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000835 if hasattr(cls, 'logger'):
836 cls.logger.debug(
837 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
838 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100839
Neale Ranns52fae862018-01-08 04:41:42 -0800840 def send_and_assert_no_replies(self, intf, pkts, remark=""):
841 self.vapi.cli("clear trace")
842 intf.add_stream(pkts)
843 self.pg_enable_capture(self.pg_interfaces)
844 self.pg_start()
845 timeout = 1
846 for i in self.pg_interfaces:
847 i.get_capture(0, timeout=timeout)
848 i.assert_nothing_captured(remark=remark)
849 timeout = 0.1
850
851 def send_and_expect(self, input, pkts, output):
852 self.vapi.cli("clear trace")
853 input.add_stream(pkts)
854 self.pg_enable_capture(self.pg_interfaces)
855 self.pg_start()
856 rx = output.get_capture(len(pkts))
857 return rx
858
Damjan Marionf56b77a2016-10-03 19:44:57 +0200859
Klement Sekera87134932017-03-07 11:39:27 +0100860class TestCasePrinter(object):
861 _shared_state = {}
862
863 def __init__(self):
864 self.__dict__ = self._shared_state
865 if not hasattr(self, "_test_case_set"):
866 self._test_case_set = set()
867
868 def print_test_case_heading_if_first_time(self, case):
869 if case.__class__ not in self._test_case_set:
870 print(double_line_delim)
871 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
872 print(double_line_delim)
873 self._test_case_set.add(case.__class__)
874
875
Damjan Marionf56b77a2016-10-03 19:44:57 +0200876class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200877 """
878 @property result_string
879 String variable to store the test case result string.
880 @property errors
881 List variable containing 2-tuples of TestCase instances and strings
882 holding formatted tracebacks. Each tuple represents a test which
883 raised an unexpected exception.
884 @property failures
885 List variable containing 2-tuples of TestCase instances and strings
886 holding formatted tracebacks. Each tuple represents a test where
887 a failure was explicitly signalled using the TestCase.assert*()
888 methods.
889 """
890
Damjan Marionf56b77a2016-10-03 19:44:57 +0200891 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200892 """
Klement Sekerada505f62017-01-04 12:58:53 +0100893 :param stream File descriptor to store where to report test results.
894 Set to the standard error stream by default.
895 :param descriptions Boolean variable to store information if to use
896 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200897 :param verbosity Integer variable to store required verbosity level.
898 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200899 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
900 self.stream = stream
901 self.descriptions = descriptions
902 self.verbosity = verbosity
903 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100904 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200905
Damjan Marionf56b77a2016-10-03 19:44:57 +0200906 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200907 """
908 Record a test succeeded result
909
910 :param test:
911
912 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100913 if hasattr(test, 'logger'):
914 test.logger.debug("--- addSuccess() %s.%s(%s) called"
915 % (test.__class__.__name__,
916 test._testMethodName,
917 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200918 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200919 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200920
Klement Sekeraf62ae122016-10-11 11:47:09 +0200921 def addSkip(self, test, reason):
922 """
923 Record a test skipped.
924
925 :param test:
926 :param reason:
927
928 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100929 if hasattr(test, 'logger'):
930 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
931 % (test.__class__.__name__,
932 test._testMethodName,
933 test._testMethodDoc,
934 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200935 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200936 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200937
Klement Sekeraf413bef2017-08-15 07:09:02 +0200938 def symlink_failed(self, test):
939 logger = None
940 if hasattr(test, 'logger'):
941 logger = test.logger
942 if hasattr(test, 'tempdir'):
943 try:
944 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
945 link_path = '%s/%s-FAILED' % (failed_dir,
946 test.tempdir.split("/")[-1])
947 if logger:
948 logger.debug("creating a link to the failed test")
949 logger.debug("os.symlink(%s, %s)" %
950 (test.tempdir, link_path))
951 os.symlink(test.tempdir, link_path)
952 except Exception as e:
953 if logger:
954 logger.error(e)
955
Klement Sekeradf2b9802017-10-05 10:26:03 +0200956 def send_failure_through_pipe(self, test):
957 if hasattr(self, 'test_framework_failed_pipe'):
958 pipe = self.test_framework_failed_pipe
959 if pipe:
Klement Sekera4c5422e2018-06-22 13:19:45 +0200960 if test.__class__.__name__ == "_ErrorHolder":
961 x = str(test)
962 if x.startswith("setUpClass"):
963 # x looks like setUpClass (test_function.test_class)
964 cls = x.split(".")[1].split(")")[0]
965 for t in self.test_suite:
966 if t.__class__.__name__ == cls:
967 pipe.send(t.__class__)
968 break
969 else:
970 raise Exception("Can't find class name `%s' "
971 "(from ErrorHolder) in test suite "
972 "`%s'" % (cls, self.test_suite))
973 else:
974 raise Exception("FIXME: unexpected special case - "
975 "ErrorHolder description is `%s'" %
976 str(test))
977 else:
978 pipe.send(test.__class__)
Klement Sekeradf2b9802017-10-05 10:26:03 +0200979
Damjan Marionf56b77a2016-10-03 19:44:57 +0200980 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200981 """
982 Record a test failed result
983
984 :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("--- addFailure() %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)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200995 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200996 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200997 self.result_string = colorize("FAIL", 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("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +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 addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001006 """
1007 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +02001008
Klement Sekeraf62ae122016-10-11 11:47:09 +02001009 :param test:
1010 :param err: error message
1011
1012 """
Klement Sekerab91017a2017-02-09 06:04:36 +01001013 if hasattr(test, 'logger'):
1014 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
1015 % (test.__class__.__name__,
1016 test._testMethodName,
1017 test._testMethodDoc, err))
1018 test.logger.debug("formatted exception is:\n%s" %
1019 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001020 unittest.TestResult.addError(self, test, err)
1021 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +02001022 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +02001023 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +02001024 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +02001025 else:
Klement Sekera277b89c2016-10-28 13:20:27 +02001026 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +02001027
Klement Sekeradf2b9802017-10-05 10:26:03 +02001028 self.send_failure_through_pipe(test)
1029
Damjan Marionf56b77a2016-10-03 19:44:57 +02001030 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001031 """
1032 Get test description
1033
1034 :param test:
1035 :returns: test description
1036
1037 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001038 # TODO: if none print warning not raise exception
1039 short_description = test.shortDescription()
1040 if self.descriptions and short_description:
1041 return short_description
1042 else:
1043 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001044
Damjan Marionf56b77a2016-10-03 19:44:57 +02001045 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001046 """
1047 Start a test
1048
1049 :param test:
1050
1051 """
Klement Sekera87134932017-03-07 11:39:27 +01001052 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001053 unittest.TestResult.startTest(self, test)
1054 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001055 self.stream.writeln(
1056 "Starting " + self.getDescription(test) + " ...")
1057 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001058
Damjan Marionf56b77a2016-10-03 19:44:57 +02001059 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001060 """
1061 Stop a test
1062
1063 :param test:
1064
1065 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001066 unittest.TestResult.stopTest(self, test)
1067 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001068 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +01001069 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001070 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001071 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001072 else:
Klement Sekera52e84f32017-01-13 07:25:25 +01001073 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001074 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +02001075
Damjan Marionf56b77a2016-10-03 19:44:57 +02001076 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001077 """
1078 Print errors from running the test case
1079 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001080 self.stream.writeln()
1081 self.printErrorList('ERROR', self.errors)
1082 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001083
Damjan Marionf56b77a2016-10-03 19:44:57 +02001084 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001085 """
1086 Print error list to the output stream together with error type
1087 and test case description.
1088
1089 :param flavour: error type
1090 :param errors: iterable errors
1091
1092 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001093 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001094 self.stream.writeln(double_line_delim)
1095 self.stream.writeln("%s: %s" %
1096 (flavour, self.getDescription(test)))
1097 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001098 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001099
1100
Klement Sekeradf2b9802017-10-05 10:26:03 +02001101class Filter_by_test_option:
1102 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
1103 self.filter_file_name = filter_file_name
1104 self.filter_class_name = filter_class_name
1105 self.filter_func_name = filter_func_name
1106
1107 def __call__(self, file_name, class_name, func_name):
1108 if self.filter_file_name and file_name != self.filter_file_name:
1109 return False
1110 if self.filter_class_name and class_name != self.filter_class_name:
1111 return False
1112 if self.filter_func_name and func_name != self.filter_func_name:
1113 return False
1114 return True
1115
1116
Damjan Marionf56b77a2016-10-03 19:44:57 +02001117class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001118 """
Klement Sekera104543f2017-02-03 07:29:43 +01001119 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001120 """
1121 @property
1122 def resultclass(self):
1123 """Class maintaining the results of the tests"""
1124 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001125
Klement Sekeradf2b9802017-10-05 10:26:03 +02001126 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1127 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +02001128 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +01001129 # ignore stream setting here, use hard-coded stdout to be in sync
1130 # with prints from VppTestCase methods ...
1131 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1132 verbosity, failfast, buffer,
1133 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001134 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001135 reporter.pipe = keep_alive_pipe
1136 # this is super-ugly, but very simple to implement and works as long
1137 # as we run only one test at the same time
1138 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +01001139
Klement Sekera104543f2017-02-03 07:29:43 +01001140 test_option = "TEST"
1141
1142 def parse_test_option(self):
Klement Sekera13a83ef2018-03-21 12:35:51 +01001143 f = os.getenv(self.test_option, None)
Klement Sekera104543f2017-02-03 07:29:43 +01001144 filter_file_name = None
1145 filter_class_name = None
1146 filter_func_name = None
1147 if f:
1148 if '.' in f:
1149 parts = f.split('.')
1150 if len(parts) > 3:
1151 raise Exception("Unrecognized %s option: %s" %
1152 (self.test_option, f))
1153 if len(parts) > 2:
1154 if parts[2] not in ('*', ''):
1155 filter_func_name = parts[2]
1156 if parts[1] not in ('*', ''):
1157 filter_class_name = parts[1]
1158 if parts[0] not in ('*', ''):
1159 if parts[0].startswith('test_'):
1160 filter_file_name = parts[0]
1161 else:
1162 filter_file_name = 'test_%s' % parts[0]
1163 else:
1164 if f.startswith('test_'):
1165 filter_file_name = f
1166 else:
1167 filter_file_name = 'test_%s' % f
1168 return filter_file_name, filter_class_name, filter_func_name
1169
Klement Sekeradf2b9802017-10-05 10:26:03 +02001170 @staticmethod
1171 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001172 result = unittest.suite.TestSuite()
1173 for t in tests:
1174 if isinstance(t, unittest.suite.TestSuite):
1175 # this is a bunch of tests, recursively filter...
Klement Sekera05742262018-03-14 18:14:49 +01001176 x = VppTestRunner.filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001177 if x.countTestCases() > 0:
1178 result.addTest(x)
1179 elif isinstance(t, unittest.TestCase):
1180 # this is a single test
1181 parts = t.id().split('.')
1182 # t.id() for common cases like this:
1183 # test_classifier.TestClassifier.test_acl_ip
1184 # apply filtering only if it is so
1185 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001186 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001187 continue
1188 result.addTest(t)
1189 else:
1190 # unexpected object, don't touch it
1191 result.addTest(t)
1192 return result
1193
Damjan Marionf56b77a2016-10-03 19:44:57 +02001194 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001195 """
1196 Run the tests
1197
1198 :param test:
1199
1200 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001201 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001202 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001203 filter_file, filter_class, filter_func = self.parse_test_option()
1204 print("Active filters: file=%s, class=%s, function=%s" % (
1205 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001206 filter_cb = Filter_by_test_option(
1207 filter_file, filter_class, filter_func)
1208 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001209 print("%s out of %s tests match specified filters" % (
1210 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001211 if not running_extended_tests():
1212 print("Not running extended tests (some tests will be skipped)")
Klement Sekera4c5422e2018-06-22 13:19:45 +02001213 # super-ugly hack #2
1214 VppTestResult.test_suite = filtered
Klement Sekera104543f2017-02-03 07:29:43 +01001215 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001216
1217
1218class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001219 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001220 self.logger = logger
1221 self.args = args
1222 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001223 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001224 super(Worker, self).__init__()
1225
1226 def run(self):
1227 executable = self.args[0]
1228 self.logger.debug("Running executable w/args `%s'" % self.args)
1229 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001230 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001231 env["CK_LOG_FILE_NAME"] = "-"
1232 self.process = subprocess.Popen(
1233 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1234 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1235 out, err = self.process.communicate()
1236 self.logger.debug("Finished running `%s'" % executable)
1237 self.logger.info("Return code is `%s'" % self.process.returncode)
1238 self.logger.info(single_line_delim)
1239 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1240 self.logger.info(single_line_delim)
1241 self.logger.info(out)
1242 self.logger.info(single_line_delim)
1243 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1244 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001245 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001246 self.logger.info(single_line_delim)
1247 self.result = self.process.returncode