blob: 9c9351e4dacd88f1a10867d10fe56648ed17d172 [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 Sekeraacb9b8e2017-02-14 02:55:31 +010028if os.name == 'posix' and sys.version_info[0] < 3:
29 # using subprocess32 is recommended by python official documentation
30 # @ https://docs.python.org/2/library/subprocess.html
31 import subprocess32 as subprocess
32else:
33 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020034
Klement Sekeraebbaf552018-02-17 13:41:33 +010035debug_framework = False
36if os.getenv('TEST_DEBUG', "0") == "1":
37 debug_framework = True
38 import debug_internal
39
40
Klement Sekeraf62ae122016-10-11 11:47:09 +020041"""
42 Test framework module.
43
44 The module provides a set of tools for constructing and running tests and
45 representing the results.
46"""
47
Klement Sekeraf62ae122016-10-11 11:47:09 +020048
Damjan Marionf56b77a2016-10-03 19:44:57 +020049class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020050 """Private class to create packet info object.
51
52 Help process information about the next packet.
53 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020054 """
Matej Klotton86d87c42016-11-11 11:38:55 +010055 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020056 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010057 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020058 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010059 #: Store the index of the destination packet generator interface
60 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020061 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +010062 #: Store expected ip version
63 ip = -1
64 #: Store expected upper protocol
65 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010066 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020067 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020068
Matej Klotton16a14cd2016-12-07 15:09:13 +010069 def __eq__(self, other):
70 index = self.index == other.index
71 src = self.src == other.src
72 dst = self.dst == other.dst
73 data = self.data == other.data
74 return index and src and dst and data
75
Klement Sekeraf62ae122016-10-11 11:47:09 +020076
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010077def pump_output(testclass):
78 """ pump output from vpp stdout/stderr to proper queues """
Klement Sekera6a6f4f72017-11-09 09:16:39 +010079 stdout_fragment = ""
80 stderr_fragment = ""
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010081 while not testclass.pump_thread_stop_flag.wait(0):
82 readable = select.select([testclass.vpp.stdout.fileno(),
83 testclass.vpp.stderr.fileno(),
84 testclass.pump_thread_wakeup_pipe[0]],
85 [], [])[0]
86 if testclass.vpp.stdout.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +010087 read = os.read(testclass.vpp.stdout.fileno(), 102400)
88 if len(read) > 0:
89 split = read.splitlines(True)
90 if len(stdout_fragment) > 0:
91 split[0] = "%s%s" % (stdout_fragment, split[0])
92 if len(split) > 0 and split[-1].endswith("\n"):
93 limit = None
94 else:
95 limit = -1
96 stdout_fragment = split[-1]
97 testclass.vpp_stdout_deque.extend(split[:limit])
98 if not testclass.cache_vpp_output:
99 for line in split[:limit]:
100 testclass.logger.debug(
101 "VPP STDOUT: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100102 if testclass.vpp.stderr.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100103 read = os.read(testclass.vpp.stderr.fileno(), 102400)
104 if len(read) > 0:
105 split = read.splitlines(True)
106 if len(stderr_fragment) > 0:
107 split[0] = "%s%s" % (stderr_fragment, split[0])
108 if len(split) > 0 and split[-1].endswith("\n"):
109 limit = None
110 else:
111 limit = -1
112 stderr_fragment = split[-1]
113 testclass.vpp_stderr_deque.extend(split[:limit])
114 if not testclass.cache_vpp_output:
115 for line in split[:limit]:
116 testclass.logger.debug(
117 "VPP STDERR: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100118 # ignoring the dummy pipe here intentionally - the flag will take care
119 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +0200120
121
Klement Sekera87134932017-03-07 11:39:27 +0100122def running_extended_tests():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100123 s = os.getenv("EXTENDED_TESTS", "n")
124 return True if s.lower() in ("y", "yes", "1") else False
Klement Sekera87134932017-03-07 11:39:27 +0100125
126
Klement Sekerad3e671e2017-09-29 12:36:37 +0200127def running_on_centos():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100128 os_id = os.getenv("OS_ID", "")
129 return True if "centos" in os_id.lower() else False
Klement Sekerad3e671e2017-09-29 12:36:37 +0200130
131
Klement Sekera909a6a12017-08-08 04:33:53 +0200132class KeepAliveReporter(object):
133 """
134 Singleton object which reports test start to parent process
135 """
136 _shared_state = {}
137
138 def __init__(self):
139 self.__dict__ = self._shared_state
140
141 @property
142 def pipe(self):
143 return self._pipe
144
145 @pipe.setter
146 def pipe(self, pipe):
147 if hasattr(self, '_pipe'):
148 raise Exception("Internal error - pipe should only be set once.")
149 self._pipe = pipe
150
151 def send_keep_alive(self, test):
152 """
153 Write current test tmpdir & desc to keep-alive pipe to signal liveness
154 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200155 if self.pipe is None:
156 # if not running forked..
157 return
158
Klement Sekera909a6a12017-08-08 04:33:53 +0200159 if isclass(test):
160 desc = test.__name__
161 else:
162 desc = test.shortDescription()
163 if not desc:
164 desc = str(test)
165
Dave Wallacee2efd122017-09-30 22:04:21 -0400166 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200167
168
Damjan Marionf56b77a2016-10-03 19:44:57 +0200169class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100170 """This subclass is a base class for VPP test cases that are implemented as
171 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200172 """
173
174 @property
175 def packet_infos(self):
176 """List of packet infos"""
177 return self._packet_infos
178
Klement Sekeradab231a2016-12-21 08:50:14 +0100179 @classmethod
180 def get_packet_count_for_if_idx(cls, dst_if_index):
181 """Get the number of packet info for specified destination if index"""
182 if dst_if_index in cls._packet_count_for_dst_if_idx:
183 return cls._packet_count_for_dst_if_idx[dst_if_index]
184 else:
185 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200186
187 @classmethod
188 def instance(cls):
189 """Return the instance of this testcase"""
190 return cls.test_instance
191
Damjan Marionf56b77a2016-10-03 19:44:57 +0200192 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200193 def set_debug_flags(cls, d):
194 cls.debug_core = False
195 cls.debug_gdb = False
196 cls.debug_gdbserver = False
197 if d is None:
198 return
199 dl = d.lower()
200 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200201 cls.debug_core = True
202 elif dl == "gdb":
203 cls.debug_gdb = True
204 elif dl == "gdbserver":
205 cls.debug_gdbserver = True
206 else:
207 raise Exception("Unrecognized DEBUG option: '%s'" % d)
208
209 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200210 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200211 """ Set-up the test case class based on environment variables """
Klement Sekera13a83ef2018-03-21 12:35:51 +0100212 s = os.getenv("STEP", "n")
213 cls.step = True if s.lower() in ("y", "yes", "1") else False
214 d = os.getenv("DEBUG", None)
215 c = os.getenv("CACHE_OUTPUT", "1")
216 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
Klement Sekera277b89c2016-10-28 13:20:27 +0200217 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200218 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100219 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100220 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
221 plugin_path = None
222 if cls.plugin_path is not None:
223 if cls.extern_plugin_path is not None:
224 plugin_path = "%s:%s" % (
225 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100226 else:
227 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100228 elif cls.extern_plugin_path is not None:
229 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100230 debug_cli = ""
231 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
232 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100233 coredump_size = None
Klement Sekera13a83ef2018-03-21 12:35:51 +0100234 size = os.getenv("COREDUMP_SIZE")
235 if size is not None:
236 coredump_size = "coredump-size %s" % size
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100237 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400238 coredump_size = "coredump-size unlimited"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100239 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400240 "{", "nodaemon", debug_cli, "full-coredump",
241 coredump_size, "}", "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100242 "api-segment", "{", "prefix", cls.shm_prefix, "}",
243 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
Klement Sekera05742262018-03-14 18:14:49 +0100244 "disable", "}", "}", ]
Klement Sekera47e275b2017-03-21 08:21:25 +0100245 if plugin_path is not None:
246 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200247 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
248
249 @classmethod
250 def wait_for_enter(cls):
251 if cls.debug_gdbserver:
252 print(double_line_delim)
253 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
254 elif cls.debug_gdb:
255 print(double_line_delim)
256 print("Spawned VPP with PID: %d" % cls.vpp.pid)
257 else:
258 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
259 return
260 print(single_line_delim)
261 print("You can debug the VPP using e.g.:")
262 if cls.debug_gdbserver:
263 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
264 print("Now is the time to attach a gdb by running the above "
265 "command, set up breakpoints etc. and then resume VPP from "
266 "within gdb by issuing the 'continue' command")
267 elif cls.debug_gdb:
268 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
269 print("Now is the time to attach a gdb by running the above "
270 "command and set up breakpoints etc.")
271 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100272 raw_input("Press ENTER to continue running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200273
274 @classmethod
275 def run_vpp(cls):
276 cmdline = cls.vpp_cmdline
277
278 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100279 gdbserver = '/usr/bin/gdbserver'
280 if not os.path.isfile(gdbserver) or \
281 not os.access(gdbserver, os.X_OK):
282 raise Exception("gdbserver binary '%s' does not exist or is "
283 "not executable" % gdbserver)
284
285 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200286 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
287
Klement Sekera931be3a2016-11-03 05:36:01 +0100288 try:
289 cls.vpp = subprocess.Popen(cmdline,
290 stdout=subprocess.PIPE,
291 stderr=subprocess.PIPE,
292 bufsize=1)
293 except Exception as e:
294 cls.logger.critical("Couldn't start vpp: %s" % e)
295 raise
296
Klement Sekera277b89c2016-10-28 13:20:27 +0200297 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100298
Damjan Marionf56b77a2016-10-03 19:44:57 +0200299 @classmethod
300 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200301 """
302 Perform class setup before running the testcase
303 Remove shared memory files, start vpp and connect the vpp-api
304 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100305 gc.collect() # run garbage collection first
Klement Sekera96867ba2018-02-02 11:27:53 +0100306 random.seed()
Klement Sekera277b89c2016-10-28 13:20:27 +0200307 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200308 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200309 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera027dbd52017-04-11 06:01:53 +0200310 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
311 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100312 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
313 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200314 cls.file_handler.setLevel(DEBUG)
315 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200316 cls.shm_prefix = cls.tempdir.split("/")[-1]
317 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200318 cls.logger.info("Temporary dir is %s, shm prefix is %s",
319 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200320 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100321 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100322 cls._captures = []
323 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200324 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100325 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100326 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200327 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200328 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200329 # need to catch exceptions here because if we raise, then the cleanup
330 # doesn't get called and we might end with a zombie vpp
331 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200332 cls.run_vpp()
Dave Wallacee2efd122017-09-30 22:04:21 -0400333 cls.reporter.send_keep_alive(cls)
Klement Sekerae4504c62016-12-08 10:16:41 +0100334 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100335 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100336 cls.pump_thread_stop_flag = Event()
337 cls.pump_thread_wakeup_pipe = os.pipe()
338 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100339 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100340 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100341 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100342 if cls.step:
343 hook = StepHook(cls)
344 else:
345 hook = PollHook(cls)
346 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100347 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200348 try:
349 hook.poll_vpp()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100350 except VppDiedError:
Klement Sekera3747c752017-04-10 06:30:17 +0200351 cls.vpp_startup_failed = True
352 cls.logger.critical(
353 "VPP died shortly after startup, check the"
354 " output to standard error for possible cause")
355 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100356 try:
357 cls.vapi.connect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100358 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100359 try:
360 cls.vapi.disconnect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100361 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100362 pass
Klement Sekera085f5c02016-11-24 01:59:16 +0100363 if cls.debug_gdbserver:
364 print(colorize("You're running VPP inside gdbserver but "
365 "VPP-API connection failed, did you forget "
366 "to 'continue' VPP from within gdb?", RED))
367 raise
Klement Sekera13a83ef2018-03-21 12:35:51 +0100368 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100369 try:
370 cls.quit()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100371 except Exception:
Klement Sekera085f5c02016-11-24 01:59:16 +0100372 pass
Klement Sekera13a83ef2018-03-21 12:35:51 +0100373 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200374
Damjan Marionf56b77a2016-10-03 19:44:57 +0200375 @classmethod
376 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200377 """
378 Disconnect vpp-api, kill vpp and cleanup shared memory files
379 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200380 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
381 cls.vpp.poll()
382 if cls.vpp.returncode is None:
383 print(double_line_delim)
384 print("VPP or GDB server is still running")
385 print(single_line_delim)
Klement Sekerae1ace192018-03-23 21:54:12 +0100386 raw_input("When done debugging, press ENTER to kill the "
387 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200388
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100389 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
390 cls.pump_thread_stop_flag.set()
391 if hasattr(cls, 'pump_thread'):
392 cls.logger.debug("Waiting for pump thread to stop")
393 cls.pump_thread.join()
394 if hasattr(cls, 'vpp_stderr_reader_thread'):
395 cls.logger.debug("Waiting for stdderr pump to stop")
396 cls.vpp_stderr_reader_thread.join()
397
Klement Sekeraf62ae122016-10-11 11:47:09 +0200398 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100399 if hasattr(cls, 'vapi'):
400 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100401 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200402 cls.vpp.poll()
403 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100404 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200405 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100406 cls.logger.debug("Waiting for vpp to die")
407 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200408 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200409
Klement Sekera3747c752017-04-10 06:30:17 +0200410 if cls.vpp_startup_failed:
411 stdout_log = cls.logger.info
412 stderr_log = cls.logger.critical
413 else:
414 stdout_log = cls.logger.info
415 stderr_log = cls.logger.info
416
Klement Sekerae4504c62016-12-08 10:16:41 +0100417 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200418 stdout_log(single_line_delim)
419 stdout_log('VPP output to stdout while running %s:', cls.__name__)
420 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100421 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200422 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
423 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200424 stdout_log('\n%s', vpp_output)
425 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200426
Klement Sekerae4504c62016-12-08 10:16:41 +0100427 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200428 stderr_log(single_line_delim)
429 stderr_log('VPP output to stderr while running %s:', cls.__name__)
430 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100431 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200432 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
433 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200434 stderr_log('\n%s', vpp_output)
435 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200436
Damjan Marionf56b77a2016-10-03 19:44:57 +0200437 @classmethod
438 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200439 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200440 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200441 cls.file_handler.close()
Klement Sekeraebbaf552018-02-17 13:41:33 +0100442 cls.reset_packet_infos()
443 if debug_framework:
444 debug_internal.on_tear_down_class(cls)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200445
Damjan Marionf56b77a2016-10-03 19:44:57 +0200446 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200447 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100448 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
449 (self.__class__.__name__, self._testMethodName,
450 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200451 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200452 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700453 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200454 self.logger.info(self.vapi.ppcli("show hardware"))
455 self.logger.info(self.vapi.ppcli("show error"))
456 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100457 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500458 # Save/Dump VPP api trace log
459 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
460 tmp_api_trace = "/tmp/%s" % api_trace
461 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
462 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
463 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
464 vpp_api_trace_log))
465 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500466 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500467 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100468 else:
469 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200470
Damjan Marionf56b77a2016-10-03 19:44:57 +0200471 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200472 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200473 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100474 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
475 (self.__class__.__name__, self._testMethodName,
476 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100477 if self.vpp_dead:
478 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100479 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100480 self.vpp_stdout_deque.append(
481 "--- test setUp() for %s.%s(%s) starts here ---\n" %
482 (self.__class__.__name__, self._testMethodName,
483 self._testMethodDoc))
484 self.vpp_stderr_deque.append(
485 "--- test setUp() for %s.%s(%s) starts here ---\n" %
486 (self.__class__.__name__, self._testMethodName,
487 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200488 self.vapi.cli("clear trace")
489 # store the test instance inside the test class - so that objects
490 # holding the class can access instance methods (like assertEqual)
491 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200492
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200494 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200495 """
496 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200497
Klement Sekera75e7d132017-09-20 08:26:30 +0200498 :param interfaces: iterable interface indexes (if None,
499 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200500
Klement Sekeraf62ae122016-10-11 11:47:09 +0200501 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200502 if interfaces is None:
503 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200504 for i in interfaces:
505 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200506
Damjan Marionf56b77a2016-10-03 19:44:57 +0200507 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100508 def register_capture(cls, cap_name):
509 """ Register a capture in the testclass """
510 # add to the list of captures with current timestamp
511 cls._captures.append((time.time(), cap_name))
512 # filter out from zombies
513 cls._zombie_captures = [(stamp, name)
514 for (stamp, name) in cls._zombie_captures
515 if name != cap_name]
516
517 @classmethod
518 def pg_start(cls):
519 """ Remove any zombie captures and enable the packet generator """
520 # how long before capture is allowed to be deleted - otherwise vpp
521 # crashes - 100ms seems enough (this shouldn't be needed at all)
522 capture_ttl = 0.1
523 now = time.time()
524 for stamp, cap_name in cls._zombie_captures:
525 wait = stamp + capture_ttl - now
526 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100527 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100528 now = time.time()
529 cls.logger.debug("Removing zombie capture %s" % cap_name)
530 cls.vapi.cli('packet-generator delete %s' % cap_name)
531
Klement Sekeraf62ae122016-10-11 11:47:09 +0200532 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
533 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100534 cls._zombie_captures = cls._captures
535 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200536
Damjan Marionf56b77a2016-10-03 19:44:57 +0200537 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200538 def create_pg_interfaces(cls, interfaces):
539 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100540 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200541
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100542 :param interfaces: iterable indexes of the interfaces.
543 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200544
Klement Sekeraf62ae122016-10-11 11:47:09 +0200545 """
546 result = []
547 for i in interfaces:
548 intf = VppPGInterface(cls, i)
549 setattr(cls, intf.name, intf)
550 result.append(intf)
551 cls.pg_interfaces = result
552 return result
553
Matej Klotton0178d522016-11-04 11:11:44 +0100554 @classmethod
555 def create_loopback_interfaces(cls, interfaces):
556 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100557 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100558
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100559 :param interfaces: iterable indexes of the interfaces.
560 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100561 """
562 result = []
563 for i in interfaces:
564 intf = VppLoInterface(cls, i)
565 setattr(cls, intf.name, intf)
566 result.append(intf)
567 cls.lo_interfaces = result
568 return result
569
Damjan Marionf56b77a2016-10-03 19:44:57 +0200570 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200571 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200572 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200573 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200574 NOTE: Currently works only when Raw layer is present.
575
576 :param packet: packet
577 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200578 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200579
580 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200581 packet_len = len(packet) + 4
582 extend = size - packet_len
583 if extend > 0:
Klement Sekera75e7d132017-09-20 08:26:30 +0200584 num = (extend / len(padding)) + 1
585 packet[Raw].load += (padding * num)[:extend]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200586
Klement Sekeradab231a2016-12-21 08:50:14 +0100587 @classmethod
588 def reset_packet_infos(cls):
589 """ Reset the list of packet info objects and packet counts to zero """
590 cls._packet_infos = {}
591 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200592
Klement Sekeradab231a2016-12-21 08:50:14 +0100593 @classmethod
594 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200595 """
596 Create packet info object containing the source and destination indexes
597 and add it to the testcase's packet info list
598
Klement Sekeradab231a2016-12-21 08:50:14 +0100599 :param VppInterface src_if: source interface
600 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200601
602 :returns: _PacketInfo object
603
604 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200605 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100606 info.index = len(cls._packet_infos)
607 info.src = src_if.sw_if_index
608 info.dst = dst_if.sw_if_index
609 if isinstance(dst_if, VppSubInterface):
610 dst_idx = dst_if.parent.sw_if_index
611 else:
612 dst_idx = dst_if.sw_if_index
613 if dst_idx in cls._packet_count_for_dst_if_idx:
614 cls._packet_count_for_dst_if_idx[dst_idx] += 1
615 else:
616 cls._packet_count_for_dst_if_idx[dst_idx] = 1
617 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200618 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200619
Damjan Marionf56b77a2016-10-03 19:44:57 +0200620 @staticmethod
621 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200622 """
623 Convert _PacketInfo object to packet payload
624
625 :param info: _PacketInfo object
626
627 :returns: string containing serialized data from packet info
628 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100629 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
630 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200631
Damjan Marionf56b77a2016-10-03 19:44:57 +0200632 @staticmethod
633 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200634 """
635 Convert packet payload to _PacketInfo object
636
637 :param payload: packet payload
638
639 :returns: _PacketInfo object containing de-serialized data from payload
640
641 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200642 numbers = payload.split()
643 info = _PacketInfo()
644 info.index = int(numbers[0])
645 info.src = int(numbers[1])
646 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100647 info.ip = int(numbers[3])
648 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200649 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200650
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200652 """
653 Iterate over the packet info list stored in the testcase
654 Start iteration with first element if info is None
655 Continue based on index in info if info is specified
656
657 :param info: info or None
658 :returns: next info in list or None if no more infos
659 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200660 if info is None:
661 next_index = 0
662 else:
663 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100664 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200665 return None
666 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100667 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200668
Klement Sekeraf62ae122016-10-11 11:47:09 +0200669 def get_next_packet_info_for_interface(self, src_index, info):
670 """
671 Search the packet info list for the next packet info with same source
672 interface index
673
674 :param src_index: source interface index to search for
675 :param info: packet info - where to start the search
676 :returns: packet info or None
677
678 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200679 while True:
680 info = self.get_next_packet_info(info)
681 if info is None:
682 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200683 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200684 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200685
Klement Sekeraf62ae122016-10-11 11:47:09 +0200686 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
687 """
688 Search the packet info list for the next packet info with same source
689 and destination interface indexes
690
691 :param src_index: source interface index to search for
692 :param dst_index: destination interface index to search for
693 :param info: packet info - where to start the search
694 :returns: packet info or None
695
696 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200697 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200698 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200699 if info is None:
700 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200701 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200702 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200703
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200704 def assert_equal(self, real_value, expected_value, name_or_class=None):
705 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100706 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200707 return
708 try:
709 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
710 msg = msg % (getdoc(name_or_class).strip(),
711 real_value, str(name_or_class(real_value)),
712 expected_value, str(name_or_class(expected_value)))
Klement Sekera13a83ef2018-03-21 12:35:51 +0100713 except Exception:
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200714 msg = "Invalid %s: %s does not match expected value %s" % (
715 name_or_class, real_value, expected_value)
716
717 self.assertEqual(real_value, expected_value, msg)
718
Klement Sekerab17dd962017-01-09 07:43:48 +0100719 def assert_in_range(self,
720 real_value,
721 expected_min,
722 expected_max,
723 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200724 if name is None:
725 msg = None
726 else:
727 msg = "Invalid %s: %s out of range <%s,%s>" % (
728 name, real_value, expected_min, expected_max)
729 self.assertTrue(expected_min <= real_value <= expected_max, msg)
730
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100731 @classmethod
732 def sleep(cls, timeout, remark=None):
733 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000734 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
735 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100736 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000737 after = time.time()
738 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200739 cls.logger.error("unexpected time.sleep() result - "
740 "slept for %ss instead of ~%ss!" % (
741 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000742 if hasattr(cls, 'logger'):
743 cls.logger.debug(
744 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
745 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100746
Neale Ranns52fae862018-01-08 04:41:42 -0800747 def send_and_assert_no_replies(self, intf, pkts, remark=""):
748 self.vapi.cli("clear trace")
749 intf.add_stream(pkts)
750 self.pg_enable_capture(self.pg_interfaces)
751 self.pg_start()
752 timeout = 1
753 for i in self.pg_interfaces:
754 i.get_capture(0, timeout=timeout)
755 i.assert_nothing_captured(remark=remark)
756 timeout = 0.1
757
758 def send_and_expect(self, input, pkts, output):
759 self.vapi.cli("clear trace")
760 input.add_stream(pkts)
761 self.pg_enable_capture(self.pg_interfaces)
762 self.pg_start()
763 rx = output.get_capture(len(pkts))
764 return rx
765
Damjan Marionf56b77a2016-10-03 19:44:57 +0200766
Klement Sekera87134932017-03-07 11:39:27 +0100767class TestCasePrinter(object):
768 _shared_state = {}
769
770 def __init__(self):
771 self.__dict__ = self._shared_state
772 if not hasattr(self, "_test_case_set"):
773 self._test_case_set = set()
774
775 def print_test_case_heading_if_first_time(self, case):
776 if case.__class__ not in self._test_case_set:
777 print(double_line_delim)
778 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
779 print(double_line_delim)
780 self._test_case_set.add(case.__class__)
781
782
Damjan Marionf56b77a2016-10-03 19:44:57 +0200783class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200784 """
785 @property result_string
786 String variable to store the test case result string.
787 @property errors
788 List variable containing 2-tuples of TestCase instances and strings
789 holding formatted tracebacks. Each tuple represents a test which
790 raised an unexpected exception.
791 @property failures
792 List variable containing 2-tuples of TestCase instances and strings
793 holding formatted tracebacks. Each tuple represents a test where
794 a failure was explicitly signalled using the TestCase.assert*()
795 methods.
796 """
797
Damjan Marionf56b77a2016-10-03 19:44:57 +0200798 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200799 """
Klement Sekerada505f62017-01-04 12:58:53 +0100800 :param stream File descriptor to store where to report test results.
801 Set to the standard error stream by default.
802 :param descriptions Boolean variable to store information if to use
803 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200804 :param verbosity Integer variable to store required verbosity level.
805 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200806 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
807 self.stream = stream
808 self.descriptions = descriptions
809 self.verbosity = verbosity
810 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100811 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200812
Damjan Marionf56b77a2016-10-03 19:44:57 +0200813 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200814 """
815 Record a test succeeded result
816
817 :param test:
818
819 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100820 if hasattr(test, 'logger'):
821 test.logger.debug("--- addSuccess() %s.%s(%s) called"
822 % (test.__class__.__name__,
823 test._testMethodName,
824 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200825 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200826 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200827
Klement Sekeraf62ae122016-10-11 11:47:09 +0200828 def addSkip(self, test, reason):
829 """
830 Record a test skipped.
831
832 :param test:
833 :param reason:
834
835 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100836 if hasattr(test, 'logger'):
837 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
838 % (test.__class__.__name__,
839 test._testMethodName,
840 test._testMethodDoc,
841 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200842 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200843 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200844
Klement Sekeraf413bef2017-08-15 07:09:02 +0200845 def symlink_failed(self, test):
846 logger = None
847 if hasattr(test, 'logger'):
848 logger = test.logger
849 if hasattr(test, 'tempdir'):
850 try:
851 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
852 link_path = '%s/%s-FAILED' % (failed_dir,
853 test.tempdir.split("/")[-1])
854 if logger:
855 logger.debug("creating a link to the failed test")
856 logger.debug("os.symlink(%s, %s)" %
857 (test.tempdir, link_path))
858 os.symlink(test.tempdir, link_path)
859 except Exception as e:
860 if logger:
861 logger.error(e)
862
Klement Sekeradf2b9802017-10-05 10:26:03 +0200863 def send_failure_through_pipe(self, test):
864 if hasattr(self, 'test_framework_failed_pipe'):
865 pipe = self.test_framework_failed_pipe
866 if pipe:
867 pipe.send(test.__class__)
868
Damjan Marionf56b77a2016-10-03 19:44:57 +0200869 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200870 """
871 Record a test failed result
872
873 :param test:
874 :param err: error message
875
876 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100877 if hasattr(test, 'logger'):
878 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
879 % (test.__class__.__name__,
880 test._testMethodName,
881 test._testMethodDoc, err))
882 test.logger.debug("formatted exception is:\n%s" %
883 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200884 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200885 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200886 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200887 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200888 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200889 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200890 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200891
Klement Sekeradf2b9802017-10-05 10:26:03 +0200892 self.send_failure_through_pipe(test)
893
Damjan Marionf56b77a2016-10-03 19:44:57 +0200894 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200895 """
896 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200897
Klement Sekeraf62ae122016-10-11 11:47:09 +0200898 :param test:
899 :param err: error message
900
901 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100902 if hasattr(test, 'logger'):
903 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
904 % (test.__class__.__name__,
905 test._testMethodName,
906 test._testMethodDoc, err))
907 test.logger.debug("formatted exception is:\n%s" %
908 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200909 unittest.TestResult.addError(self, test, err)
910 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200911 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200912 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200913 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200914 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200915 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200916
Klement Sekeradf2b9802017-10-05 10:26:03 +0200917 self.send_failure_through_pipe(test)
918
Damjan Marionf56b77a2016-10-03 19:44:57 +0200919 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200920 """
921 Get test description
922
923 :param test:
924 :returns: test description
925
926 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200927 # TODO: if none print warning not raise exception
928 short_description = test.shortDescription()
929 if self.descriptions and short_description:
930 return short_description
931 else:
932 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200933
Damjan Marionf56b77a2016-10-03 19:44:57 +0200934 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200935 """
936 Start a test
937
938 :param test:
939
940 """
Klement Sekera87134932017-03-07 11:39:27 +0100941 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200942 unittest.TestResult.startTest(self, test)
943 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200944 self.stream.writeln(
945 "Starting " + self.getDescription(test) + " ...")
946 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200947
Damjan Marionf56b77a2016-10-03 19:44:57 +0200948 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200949 """
950 Stop a test
951
952 :param test:
953
954 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200955 unittest.TestResult.stopTest(self, test)
956 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200957 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100958 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100959 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200960 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200961 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100962 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100963 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200964
Damjan Marionf56b77a2016-10-03 19:44:57 +0200965 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200966 """
967 Print errors from running the test case
968 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200969 self.stream.writeln()
970 self.printErrorList('ERROR', self.errors)
971 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200972
Damjan Marionf56b77a2016-10-03 19:44:57 +0200973 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200974 """
975 Print error list to the output stream together with error type
976 and test case description.
977
978 :param flavour: error type
979 :param errors: iterable errors
980
981 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200982 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200983 self.stream.writeln(double_line_delim)
984 self.stream.writeln("%s: %s" %
985 (flavour, self.getDescription(test)))
986 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200987 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200988
989
Klement Sekeradf2b9802017-10-05 10:26:03 +0200990class Filter_by_test_option:
991 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
992 self.filter_file_name = filter_file_name
993 self.filter_class_name = filter_class_name
994 self.filter_func_name = filter_func_name
995
996 def __call__(self, file_name, class_name, func_name):
997 if self.filter_file_name and file_name != self.filter_file_name:
998 return False
999 if self.filter_class_name and class_name != self.filter_class_name:
1000 return False
1001 if self.filter_func_name and func_name != self.filter_func_name:
1002 return False
1003 return True
1004
1005
Damjan Marionf56b77a2016-10-03 19:44:57 +02001006class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001007 """
Klement Sekera104543f2017-02-03 07:29:43 +01001008 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001009 """
1010 @property
1011 def resultclass(self):
1012 """Class maintaining the results of the tests"""
1013 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001014
Klement Sekeradf2b9802017-10-05 10:26:03 +02001015 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1016 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +02001017 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +01001018 # ignore stream setting here, use hard-coded stdout to be in sync
1019 # with prints from VppTestCase methods ...
1020 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1021 verbosity, failfast, buffer,
1022 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001023 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001024 reporter.pipe = keep_alive_pipe
1025 # this is super-ugly, but very simple to implement and works as long
1026 # as we run only one test at the same time
1027 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +01001028
Klement Sekera104543f2017-02-03 07:29:43 +01001029 test_option = "TEST"
1030
1031 def parse_test_option(self):
Klement Sekera13a83ef2018-03-21 12:35:51 +01001032 f = os.getenv(self.test_option, None)
Klement Sekera104543f2017-02-03 07:29:43 +01001033 filter_file_name = None
1034 filter_class_name = None
1035 filter_func_name = None
1036 if f:
1037 if '.' in f:
1038 parts = f.split('.')
1039 if len(parts) > 3:
1040 raise Exception("Unrecognized %s option: %s" %
1041 (self.test_option, f))
1042 if len(parts) > 2:
1043 if parts[2] not in ('*', ''):
1044 filter_func_name = parts[2]
1045 if parts[1] not in ('*', ''):
1046 filter_class_name = parts[1]
1047 if parts[0] not in ('*', ''):
1048 if parts[0].startswith('test_'):
1049 filter_file_name = parts[0]
1050 else:
1051 filter_file_name = 'test_%s' % parts[0]
1052 else:
1053 if f.startswith('test_'):
1054 filter_file_name = f
1055 else:
1056 filter_file_name = 'test_%s' % f
1057 return filter_file_name, filter_class_name, filter_func_name
1058
Klement Sekeradf2b9802017-10-05 10:26:03 +02001059 @staticmethod
1060 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001061 result = unittest.suite.TestSuite()
1062 for t in tests:
1063 if isinstance(t, unittest.suite.TestSuite):
1064 # this is a bunch of tests, recursively filter...
Klement Sekera05742262018-03-14 18:14:49 +01001065 x = VppTestRunner.filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001066 if x.countTestCases() > 0:
1067 result.addTest(x)
1068 elif isinstance(t, unittest.TestCase):
1069 # this is a single test
1070 parts = t.id().split('.')
1071 # t.id() for common cases like this:
1072 # test_classifier.TestClassifier.test_acl_ip
1073 # apply filtering only if it is so
1074 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001075 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001076 continue
1077 result.addTest(t)
1078 else:
1079 # unexpected object, don't touch it
1080 result.addTest(t)
1081 return result
1082
Damjan Marionf56b77a2016-10-03 19:44:57 +02001083 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001084 """
1085 Run the tests
1086
1087 :param test:
1088
1089 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001090 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001091 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001092 filter_file, filter_class, filter_func = self.parse_test_option()
1093 print("Active filters: file=%s, class=%s, function=%s" % (
1094 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001095 filter_cb = Filter_by_test_option(
1096 filter_file, filter_class, filter_func)
1097 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001098 print("%s out of %s tests match specified filters" % (
1099 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001100 if not running_extended_tests():
1101 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +01001102 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001103
1104
1105class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001106 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001107 self.logger = logger
1108 self.args = args
1109 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001110 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001111 super(Worker, self).__init__()
1112
1113 def run(self):
1114 executable = self.args[0]
1115 self.logger.debug("Running executable w/args `%s'" % self.args)
1116 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001117 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001118 env["CK_LOG_FILE_NAME"] = "-"
1119 self.process = subprocess.Popen(
1120 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1121 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1122 out, err = self.process.communicate()
1123 self.logger.debug("Finished running `%s'" % executable)
1124 self.logger.info("Return code is `%s'" % self.process.returncode)
1125 self.logger.info(single_line_delim)
1126 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1127 self.logger.info(single_line_delim)
1128 self.logger.info(out)
1129 self.logger.info(single_line_delim)
1130 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1131 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001132 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001133 self.logger.info(single_line_delim)
1134 self.result = self.process.returncode