blob: 4ecb66fe408f208d2576e8a817f9c369acd93ff6 [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 Ranns947ea622018-06-07 23:48:20 -0700840 def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
Neale Ranns52fae862018-01-08 04:41:42 -0800841 self.vapi.cli("clear trace")
842 intf.add_stream(pkts)
843 self.pg_enable_capture(self.pg_interfaces)
844 self.pg_start()
Neale Ranns947ea622018-06-07 23:48:20 -0700845 if not timeout:
846 timeout = 1
Neale Ranns52fae862018-01-08 04:41:42 -0800847 for i in self.pg_interfaces:
848 i.get_capture(0, timeout=timeout)
849 i.assert_nothing_captured(remark=remark)
850 timeout = 0.1
851
852 def send_and_expect(self, input, pkts, output):
853 self.vapi.cli("clear trace")
854 input.add_stream(pkts)
855 self.pg_enable_capture(self.pg_interfaces)
856 self.pg_start()
857 rx = output.get_capture(len(pkts))
858 return rx
859
Damjan Marionf56b77a2016-10-03 19:44:57 +0200860
Klement Sekera87134932017-03-07 11:39:27 +0100861class TestCasePrinter(object):
862 _shared_state = {}
863
864 def __init__(self):
865 self.__dict__ = self._shared_state
866 if not hasattr(self, "_test_case_set"):
867 self._test_case_set = set()
868
869 def print_test_case_heading_if_first_time(self, case):
870 if case.__class__ not in self._test_case_set:
871 print(double_line_delim)
872 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
873 print(double_line_delim)
874 self._test_case_set.add(case.__class__)
875
876
Damjan Marionf56b77a2016-10-03 19:44:57 +0200877class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200878 """
879 @property result_string
880 String variable to store the test case result string.
881 @property errors
882 List variable containing 2-tuples of TestCase instances and strings
883 holding formatted tracebacks. Each tuple represents a test which
884 raised an unexpected exception.
885 @property failures
886 List variable containing 2-tuples of TestCase instances and strings
887 holding formatted tracebacks. Each tuple represents a test where
888 a failure was explicitly signalled using the TestCase.assert*()
889 methods.
890 """
891
Damjan Marionf56b77a2016-10-03 19:44:57 +0200892 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200893 """
Klement Sekerada505f62017-01-04 12:58:53 +0100894 :param stream File descriptor to store where to report test results.
895 Set to the standard error stream by default.
896 :param descriptions Boolean variable to store information if to use
897 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200898 :param verbosity Integer variable to store required verbosity level.
899 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200900 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
901 self.stream = stream
902 self.descriptions = descriptions
903 self.verbosity = verbosity
904 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100905 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200906
Damjan Marionf56b77a2016-10-03 19:44:57 +0200907 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200908 """
909 Record a test succeeded result
910
911 :param test:
912
913 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100914 if hasattr(test, 'logger'):
915 test.logger.debug("--- addSuccess() %s.%s(%s) called"
916 % (test.__class__.__name__,
917 test._testMethodName,
918 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200919 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200920 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200921
Klement Sekeraf62ae122016-10-11 11:47:09 +0200922 def addSkip(self, test, reason):
923 """
924 Record a test skipped.
925
926 :param test:
927 :param reason:
928
929 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100930 if hasattr(test, 'logger'):
931 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
932 % (test.__class__.__name__,
933 test._testMethodName,
934 test._testMethodDoc,
935 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200936 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200937 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200938
Klement Sekeraf413bef2017-08-15 07:09:02 +0200939 def symlink_failed(self, test):
940 logger = None
941 if hasattr(test, 'logger'):
942 logger = test.logger
943 if hasattr(test, 'tempdir'):
944 try:
945 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
946 link_path = '%s/%s-FAILED' % (failed_dir,
947 test.tempdir.split("/")[-1])
948 if logger:
949 logger.debug("creating a link to the failed test")
950 logger.debug("os.symlink(%s, %s)" %
951 (test.tempdir, link_path))
952 os.symlink(test.tempdir, link_path)
953 except Exception as e:
954 if logger:
955 logger.error(e)
956
Klement Sekeradf2b9802017-10-05 10:26:03 +0200957 def send_failure_through_pipe(self, test):
958 if hasattr(self, 'test_framework_failed_pipe'):
959 pipe = self.test_framework_failed_pipe
960 if pipe:
Klement Sekera4c5422e2018-06-22 13:19:45 +0200961 if test.__class__.__name__ == "_ErrorHolder":
962 x = str(test)
963 if x.startswith("setUpClass"):
964 # x looks like setUpClass (test_function.test_class)
965 cls = x.split(".")[1].split(")")[0]
966 for t in self.test_suite:
967 if t.__class__.__name__ == cls:
968 pipe.send(t.__class__)
969 break
970 else:
971 raise Exception("Can't find class name `%s' "
972 "(from ErrorHolder) in test suite "
973 "`%s'" % (cls, self.test_suite))
974 else:
975 raise Exception("FIXME: unexpected special case - "
976 "ErrorHolder description is `%s'" %
977 str(test))
978 else:
979 pipe.send(test.__class__)
Klement Sekeradf2b9802017-10-05 10:26:03 +0200980
Damjan Marionf56b77a2016-10-03 19:44:57 +0200981 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200982 """
983 Record a test failed result
984
985 :param test:
986 :param err: error message
987
988 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100989 if hasattr(test, 'logger'):
990 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
991 % (test.__class__.__name__,
992 test._testMethodName,
993 test._testMethodDoc, err))
994 test.logger.debug("formatted exception is:\n%s" %
995 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200996 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200997 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200998 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200999 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +02001000 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +02001001 else:
Klement Sekera277b89c2016-10-28 13:20:27 +02001002 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +02001003
Klement Sekeradf2b9802017-10-05 10:26:03 +02001004 self.send_failure_through_pipe(test)
1005
Damjan Marionf56b77a2016-10-03 19:44:57 +02001006 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001007 """
1008 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +02001009
Klement Sekeraf62ae122016-10-11 11:47:09 +02001010 :param test:
1011 :param err: error message
1012
1013 """
Klement Sekerab91017a2017-02-09 06:04:36 +01001014 if hasattr(test, 'logger'):
1015 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
1016 % (test.__class__.__name__,
1017 test._testMethodName,
1018 test._testMethodDoc, err))
1019 test.logger.debug("formatted exception is:\n%s" %
1020 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001021 unittest.TestResult.addError(self, test, err)
1022 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +02001023 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +02001024 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +02001025 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +02001026 else:
Klement Sekera277b89c2016-10-28 13:20:27 +02001027 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +02001028
Klement Sekeradf2b9802017-10-05 10:26:03 +02001029 self.send_failure_through_pipe(test)
1030
Damjan Marionf56b77a2016-10-03 19:44:57 +02001031 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001032 """
1033 Get test description
1034
1035 :param test:
1036 :returns: test description
1037
1038 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001039 # TODO: if none print warning not raise exception
1040 short_description = test.shortDescription()
1041 if self.descriptions and short_description:
1042 return short_description
1043 else:
1044 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001045
Damjan Marionf56b77a2016-10-03 19:44:57 +02001046 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001047 """
1048 Start a test
1049
1050 :param test:
1051
1052 """
Klement Sekera87134932017-03-07 11:39:27 +01001053 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001054 unittest.TestResult.startTest(self, test)
1055 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001056 self.stream.writeln(
1057 "Starting " + self.getDescription(test) + " ...")
1058 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001059
Damjan Marionf56b77a2016-10-03 19:44:57 +02001060 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001061 """
1062 Stop a test
1063
1064 :param test:
1065
1066 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001067 unittest.TestResult.stopTest(self, test)
1068 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001069 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +01001070 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001071 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001072 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001073 else:
Klement Sekera52e84f32017-01-13 07:25:25 +01001074 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001075 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +02001076
Damjan Marionf56b77a2016-10-03 19:44:57 +02001077 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001078 """
1079 Print errors from running the test case
1080 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001081 self.stream.writeln()
1082 self.printErrorList('ERROR', self.errors)
1083 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001084
Damjan Marionf56b77a2016-10-03 19:44:57 +02001085 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001086 """
1087 Print error list to the output stream together with error type
1088 and test case description.
1089
1090 :param flavour: error type
1091 :param errors: iterable errors
1092
1093 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001094 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001095 self.stream.writeln(double_line_delim)
1096 self.stream.writeln("%s: %s" %
1097 (flavour, self.getDescription(test)))
1098 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001099 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001100
1101
Klement Sekeradf2b9802017-10-05 10:26:03 +02001102class Filter_by_test_option:
1103 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
1104 self.filter_file_name = filter_file_name
1105 self.filter_class_name = filter_class_name
1106 self.filter_func_name = filter_func_name
1107
1108 def __call__(self, file_name, class_name, func_name):
1109 if self.filter_file_name and file_name != self.filter_file_name:
1110 return False
1111 if self.filter_class_name and class_name != self.filter_class_name:
1112 return False
1113 if self.filter_func_name and func_name != self.filter_func_name:
1114 return False
1115 return True
1116
1117
Damjan Marionf56b77a2016-10-03 19:44:57 +02001118class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001119 """
Klement Sekera104543f2017-02-03 07:29:43 +01001120 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001121 """
1122 @property
1123 def resultclass(self):
1124 """Class maintaining the results of the tests"""
1125 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001126
Klement Sekeradf2b9802017-10-05 10:26:03 +02001127 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1128 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +02001129 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +01001130 # ignore stream setting here, use hard-coded stdout to be in sync
1131 # with prints from VppTestCase methods ...
1132 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1133 verbosity, failfast, buffer,
1134 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001135 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001136 reporter.pipe = keep_alive_pipe
1137 # this is super-ugly, but very simple to implement and works as long
1138 # as we run only one test at the same time
1139 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +01001140
Klement Sekera104543f2017-02-03 07:29:43 +01001141 test_option = "TEST"
1142
1143 def parse_test_option(self):
Klement Sekera13a83ef2018-03-21 12:35:51 +01001144 f = os.getenv(self.test_option, None)
Klement Sekera104543f2017-02-03 07:29:43 +01001145 filter_file_name = None
1146 filter_class_name = None
1147 filter_func_name = None
1148 if f:
1149 if '.' in f:
1150 parts = f.split('.')
1151 if len(parts) > 3:
1152 raise Exception("Unrecognized %s option: %s" %
1153 (self.test_option, f))
1154 if len(parts) > 2:
1155 if parts[2] not in ('*', ''):
1156 filter_func_name = parts[2]
1157 if parts[1] not in ('*', ''):
1158 filter_class_name = parts[1]
1159 if parts[0] not in ('*', ''):
1160 if parts[0].startswith('test_'):
1161 filter_file_name = parts[0]
1162 else:
1163 filter_file_name = 'test_%s' % parts[0]
1164 else:
1165 if f.startswith('test_'):
1166 filter_file_name = f
1167 else:
1168 filter_file_name = 'test_%s' % f
1169 return filter_file_name, filter_class_name, filter_func_name
1170
Klement Sekeradf2b9802017-10-05 10:26:03 +02001171 @staticmethod
1172 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001173 result = unittest.suite.TestSuite()
1174 for t in tests:
1175 if isinstance(t, unittest.suite.TestSuite):
1176 # this is a bunch of tests, recursively filter...
Klement Sekera05742262018-03-14 18:14:49 +01001177 x = VppTestRunner.filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001178 if x.countTestCases() > 0:
1179 result.addTest(x)
1180 elif isinstance(t, unittest.TestCase):
1181 # this is a single test
1182 parts = t.id().split('.')
1183 # t.id() for common cases like this:
1184 # test_classifier.TestClassifier.test_acl_ip
1185 # apply filtering only if it is so
1186 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001187 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001188 continue
1189 result.addTest(t)
1190 else:
1191 # unexpected object, don't touch it
1192 result.addTest(t)
1193 return result
1194
Damjan Marionf56b77a2016-10-03 19:44:57 +02001195 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001196 """
1197 Run the tests
1198
1199 :param test:
1200
1201 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001202 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001203 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001204 filter_file, filter_class, filter_func = self.parse_test_option()
1205 print("Active filters: file=%s, class=%s, function=%s" % (
1206 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001207 filter_cb = Filter_by_test_option(
1208 filter_file, filter_class, filter_func)
1209 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001210 print("%s out of %s tests match specified filters" % (
1211 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001212 if not running_extended_tests():
1213 print("Not running extended tests (some tests will be skipped)")
Klement Sekera4c5422e2018-06-22 13:19:45 +02001214 # super-ugly hack #2
1215 VppTestResult.test_suite = filtered
Klement Sekera104543f2017-02-03 07:29:43 +01001216 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001217
1218
1219class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001220 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001221 self.logger = logger
1222 self.args = args
1223 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001224 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001225 super(Worker, self).__init__()
1226
1227 def run(self):
1228 executable = self.args[0]
1229 self.logger.debug("Running executable w/args `%s'" % self.args)
1230 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001231 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001232 env["CK_LOG_FILE_NAME"] = "-"
1233 self.process = subprocess.Popen(
1234 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1235 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1236 out, err = self.process.communicate()
1237 self.logger.debug("Finished running `%s'" % executable)
1238 self.logger.info("Return code is `%s'" % self.process.returncode)
1239 self.logger.info(single_line_delim)
1240 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1241 self.logger.info(single_line_delim)
1242 self.logger.info(out)
1243 self.logger.info(single_line_delim)
1244 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1245 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001246 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001247 self.logger.info(single_line_delim)
1248 self.result = self.process.returncode