blob: 973214503a840303e85e2a0f3059a7d7f5b9a42e [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"))
Damjan Marion07a38572018-01-21 06:44:18 -0800457 self.logger.info(self.vapi.ppcli("show log"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100458 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500459 # Save/Dump VPP api trace log
460 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
461 tmp_api_trace = "/tmp/%s" % api_trace
462 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
463 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
464 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
465 vpp_api_trace_log))
466 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500467 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500468 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100469 else:
470 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200471
Damjan Marionf56b77a2016-10-03 19:44:57 +0200472 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200473 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200474 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100475 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
476 (self.__class__.__name__, self._testMethodName,
477 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100478 if self.vpp_dead:
479 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100480 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100481 self.vpp_stdout_deque.append(
482 "--- test setUp() for %s.%s(%s) starts here ---\n" %
483 (self.__class__.__name__, self._testMethodName,
484 self._testMethodDoc))
485 self.vpp_stderr_deque.append(
486 "--- test setUp() for %s.%s(%s) starts here ---\n" %
487 (self.__class__.__name__, self._testMethodName,
488 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200489 self.vapi.cli("clear trace")
490 # store the test instance inside the test class - so that objects
491 # holding the class can access instance methods (like assertEqual)
492 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493
Damjan Marionf56b77a2016-10-03 19:44:57 +0200494 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200495 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200496 """
497 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200498
Klement Sekera75e7d132017-09-20 08:26:30 +0200499 :param interfaces: iterable interface indexes (if None,
500 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200501
Klement Sekeraf62ae122016-10-11 11:47:09 +0200502 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200503 if interfaces is None:
504 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200505 for i in interfaces:
506 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200507
Damjan Marionf56b77a2016-10-03 19:44:57 +0200508 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100509 def register_capture(cls, cap_name):
510 """ Register a capture in the testclass """
511 # add to the list of captures with current timestamp
512 cls._captures.append((time.time(), cap_name))
513 # filter out from zombies
514 cls._zombie_captures = [(stamp, name)
515 for (stamp, name) in cls._zombie_captures
516 if name != cap_name]
517
518 @classmethod
519 def pg_start(cls):
520 """ Remove any zombie captures and enable the packet generator """
521 # how long before capture is allowed to be deleted - otherwise vpp
522 # crashes - 100ms seems enough (this shouldn't be needed at all)
523 capture_ttl = 0.1
524 now = time.time()
525 for stamp, cap_name in cls._zombie_captures:
526 wait = stamp + capture_ttl - now
527 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100528 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100529 now = time.time()
530 cls.logger.debug("Removing zombie capture %s" % cap_name)
531 cls.vapi.cli('packet-generator delete %s' % cap_name)
532
Klement Sekeraf62ae122016-10-11 11:47:09 +0200533 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
534 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100535 cls._zombie_captures = cls._captures
536 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200537
Damjan Marionf56b77a2016-10-03 19:44:57 +0200538 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200539 def create_pg_interfaces(cls, interfaces):
540 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100541 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200542
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100543 :param interfaces: iterable indexes of the interfaces.
544 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200545
Klement Sekeraf62ae122016-10-11 11:47:09 +0200546 """
547 result = []
548 for i in interfaces:
549 intf = VppPGInterface(cls, i)
550 setattr(cls, intf.name, intf)
551 result.append(intf)
552 cls.pg_interfaces = result
553 return result
554
Matej Klotton0178d522016-11-04 11:11:44 +0100555 @classmethod
556 def create_loopback_interfaces(cls, interfaces):
557 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100558 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100559
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100560 :param interfaces: iterable indexes of the interfaces.
561 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100562 """
563 result = []
564 for i in interfaces:
565 intf = VppLoInterface(cls, i)
566 setattr(cls, intf.name, intf)
567 result.append(intf)
568 cls.lo_interfaces = result
569 return result
570
Damjan Marionf56b77a2016-10-03 19:44:57 +0200571 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200572 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200573 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200574 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200575 NOTE: Currently works only when Raw layer is present.
576
577 :param packet: packet
578 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200579 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200580
581 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200582 packet_len = len(packet) + 4
583 extend = size - packet_len
584 if extend > 0:
Klement Sekera75e7d132017-09-20 08:26:30 +0200585 num = (extend / len(padding)) + 1
586 packet[Raw].load += (padding * num)[:extend]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200587
Klement Sekeradab231a2016-12-21 08:50:14 +0100588 @classmethod
589 def reset_packet_infos(cls):
590 """ Reset the list of packet info objects and packet counts to zero """
591 cls._packet_infos = {}
592 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200593
Klement Sekeradab231a2016-12-21 08:50:14 +0100594 @classmethod
595 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200596 """
597 Create packet info object containing the source and destination indexes
598 and add it to the testcase's packet info list
599
Klement Sekeradab231a2016-12-21 08:50:14 +0100600 :param VppInterface src_if: source interface
601 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200602
603 :returns: _PacketInfo object
604
605 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200606 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100607 info.index = len(cls._packet_infos)
608 info.src = src_if.sw_if_index
609 info.dst = dst_if.sw_if_index
610 if isinstance(dst_if, VppSubInterface):
611 dst_idx = dst_if.parent.sw_if_index
612 else:
613 dst_idx = dst_if.sw_if_index
614 if dst_idx in cls._packet_count_for_dst_if_idx:
615 cls._packet_count_for_dst_if_idx[dst_idx] += 1
616 else:
617 cls._packet_count_for_dst_if_idx[dst_idx] = 1
618 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200619 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200620
Damjan Marionf56b77a2016-10-03 19:44:57 +0200621 @staticmethod
622 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200623 """
624 Convert _PacketInfo object to packet payload
625
626 :param info: _PacketInfo object
627
628 :returns: string containing serialized data from packet info
629 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100630 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
631 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200632
Damjan Marionf56b77a2016-10-03 19:44:57 +0200633 @staticmethod
634 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200635 """
636 Convert packet payload to _PacketInfo object
637
638 :param payload: packet payload
639
640 :returns: _PacketInfo object containing de-serialized data from payload
641
642 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200643 numbers = payload.split()
644 info = _PacketInfo()
645 info.index = int(numbers[0])
646 info.src = int(numbers[1])
647 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100648 info.ip = int(numbers[3])
649 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200650 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651
Damjan Marionf56b77a2016-10-03 19:44:57 +0200652 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200653 """
654 Iterate over the packet info list stored in the testcase
655 Start iteration with first element if info is None
656 Continue based on index in info if info is specified
657
658 :param info: info or None
659 :returns: next info in list or None if no more infos
660 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200661 if info is None:
662 next_index = 0
663 else:
664 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100665 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200666 return None
667 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100668 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200669
Klement Sekeraf62ae122016-10-11 11:47:09 +0200670 def get_next_packet_info_for_interface(self, src_index, info):
671 """
672 Search the packet info list for the next packet info with same source
673 interface index
674
675 :param src_index: source interface index to search for
676 :param info: packet info - where to start the search
677 :returns: packet info or None
678
679 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200680 while True:
681 info = self.get_next_packet_info(info)
682 if info is None:
683 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200684 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200685 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200686
Klement Sekeraf62ae122016-10-11 11:47:09 +0200687 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
688 """
689 Search the packet info list for the next packet info with same source
690 and destination interface indexes
691
692 :param src_index: source interface index to search for
693 :param dst_index: destination interface index to search for
694 :param info: packet info - where to start the search
695 :returns: packet info or None
696
697 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200698 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200699 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200700 if info is None:
701 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200702 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200703 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200704
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200705 def assert_equal(self, real_value, expected_value, name_or_class=None):
706 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100707 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200708 return
709 try:
710 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
711 msg = msg % (getdoc(name_or_class).strip(),
712 real_value, str(name_or_class(real_value)),
713 expected_value, str(name_or_class(expected_value)))
Klement Sekera13a83ef2018-03-21 12:35:51 +0100714 except Exception:
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200715 msg = "Invalid %s: %s does not match expected value %s" % (
716 name_or_class, real_value, expected_value)
717
718 self.assertEqual(real_value, expected_value, msg)
719
Klement Sekerab17dd962017-01-09 07:43:48 +0100720 def assert_in_range(self,
721 real_value,
722 expected_min,
723 expected_max,
724 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200725 if name is None:
726 msg = None
727 else:
728 msg = "Invalid %s: %s out of range <%s,%s>" % (
729 name, real_value, expected_min, expected_max)
730 self.assertTrue(expected_min <= real_value <= expected_max, msg)
731
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100732 @classmethod
733 def sleep(cls, timeout, remark=None):
734 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000735 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
736 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100737 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000738 after = time.time()
739 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200740 cls.logger.error("unexpected time.sleep() result - "
741 "slept for %ss instead of ~%ss!" % (
742 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000743 if hasattr(cls, 'logger'):
744 cls.logger.debug(
745 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
746 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100747
Neale Ranns52fae862018-01-08 04:41:42 -0800748 def send_and_assert_no_replies(self, intf, pkts, remark=""):
749 self.vapi.cli("clear trace")
750 intf.add_stream(pkts)
751 self.pg_enable_capture(self.pg_interfaces)
752 self.pg_start()
753 timeout = 1
754 for i in self.pg_interfaces:
755 i.get_capture(0, timeout=timeout)
756 i.assert_nothing_captured(remark=remark)
757 timeout = 0.1
758
759 def send_and_expect(self, input, pkts, output):
760 self.vapi.cli("clear trace")
761 input.add_stream(pkts)
762 self.pg_enable_capture(self.pg_interfaces)
763 self.pg_start()
764 rx = output.get_capture(len(pkts))
765 return rx
766
Damjan Marionf56b77a2016-10-03 19:44:57 +0200767
Klement Sekera87134932017-03-07 11:39:27 +0100768class TestCasePrinter(object):
769 _shared_state = {}
770
771 def __init__(self):
772 self.__dict__ = self._shared_state
773 if not hasattr(self, "_test_case_set"):
774 self._test_case_set = set()
775
776 def print_test_case_heading_if_first_time(self, case):
777 if case.__class__ not in self._test_case_set:
778 print(double_line_delim)
779 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
780 print(double_line_delim)
781 self._test_case_set.add(case.__class__)
782
783
Damjan Marionf56b77a2016-10-03 19:44:57 +0200784class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200785 """
786 @property result_string
787 String variable to store the test case result string.
788 @property errors
789 List variable containing 2-tuples of TestCase instances and strings
790 holding formatted tracebacks. Each tuple represents a test which
791 raised an unexpected exception.
792 @property failures
793 List variable containing 2-tuples of TestCase instances and strings
794 holding formatted tracebacks. Each tuple represents a test where
795 a failure was explicitly signalled using the TestCase.assert*()
796 methods.
797 """
798
Damjan Marionf56b77a2016-10-03 19:44:57 +0200799 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200800 """
Klement Sekerada505f62017-01-04 12:58:53 +0100801 :param stream File descriptor to store where to report test results.
802 Set to the standard error stream by default.
803 :param descriptions Boolean variable to store information if to use
804 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200805 :param verbosity Integer variable to store required verbosity level.
806 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200807 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
808 self.stream = stream
809 self.descriptions = descriptions
810 self.verbosity = verbosity
811 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100812 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200813
Damjan Marionf56b77a2016-10-03 19:44:57 +0200814 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200815 """
816 Record a test succeeded result
817
818 :param test:
819
820 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100821 if hasattr(test, 'logger'):
822 test.logger.debug("--- addSuccess() %s.%s(%s) called"
823 % (test.__class__.__name__,
824 test._testMethodName,
825 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200826 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200827 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200828
Klement Sekeraf62ae122016-10-11 11:47:09 +0200829 def addSkip(self, test, reason):
830 """
831 Record a test skipped.
832
833 :param test:
834 :param reason:
835
836 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100837 if hasattr(test, 'logger'):
838 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
839 % (test.__class__.__name__,
840 test._testMethodName,
841 test._testMethodDoc,
842 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200843 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200844 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200845
Klement Sekeraf413bef2017-08-15 07:09:02 +0200846 def symlink_failed(self, test):
847 logger = None
848 if hasattr(test, 'logger'):
849 logger = test.logger
850 if hasattr(test, 'tempdir'):
851 try:
852 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
853 link_path = '%s/%s-FAILED' % (failed_dir,
854 test.tempdir.split("/")[-1])
855 if logger:
856 logger.debug("creating a link to the failed test")
857 logger.debug("os.symlink(%s, %s)" %
858 (test.tempdir, link_path))
859 os.symlink(test.tempdir, link_path)
860 except Exception as e:
861 if logger:
862 logger.error(e)
863
Klement Sekeradf2b9802017-10-05 10:26:03 +0200864 def send_failure_through_pipe(self, test):
865 if hasattr(self, 'test_framework_failed_pipe'):
866 pipe = self.test_framework_failed_pipe
867 if pipe:
868 pipe.send(test.__class__)
869
Damjan Marionf56b77a2016-10-03 19:44:57 +0200870 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200871 """
872 Record a test failed result
873
874 :param test:
875 :param err: error message
876
877 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100878 if hasattr(test, 'logger'):
879 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
880 % (test.__class__.__name__,
881 test._testMethodName,
882 test._testMethodDoc, err))
883 test.logger.debug("formatted exception is:\n%s" %
884 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200885 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200886 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200887 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200888 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200889 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200890 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200891 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200892
Klement Sekeradf2b9802017-10-05 10:26:03 +0200893 self.send_failure_through_pipe(test)
894
Damjan Marionf56b77a2016-10-03 19:44:57 +0200895 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200896 """
897 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200898
Klement Sekeraf62ae122016-10-11 11:47:09 +0200899 :param test:
900 :param err: error message
901
902 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100903 if hasattr(test, 'logger'):
904 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
905 % (test.__class__.__name__,
906 test._testMethodName,
907 test._testMethodDoc, err))
908 test.logger.debug("formatted exception is:\n%s" %
909 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200910 unittest.TestResult.addError(self, test, err)
911 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200912 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200913 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200914 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200915 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200916 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200917
Klement Sekeradf2b9802017-10-05 10:26:03 +0200918 self.send_failure_through_pipe(test)
919
Damjan Marionf56b77a2016-10-03 19:44:57 +0200920 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200921 """
922 Get test description
923
924 :param test:
925 :returns: test description
926
927 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200928 # TODO: if none print warning not raise exception
929 short_description = test.shortDescription()
930 if self.descriptions and short_description:
931 return short_description
932 else:
933 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200934
Damjan Marionf56b77a2016-10-03 19:44:57 +0200935 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200936 """
937 Start a test
938
939 :param test:
940
941 """
Klement Sekera87134932017-03-07 11:39:27 +0100942 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200943 unittest.TestResult.startTest(self, test)
944 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200945 self.stream.writeln(
946 "Starting " + self.getDescription(test) + " ...")
947 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200948
Damjan Marionf56b77a2016-10-03 19:44:57 +0200949 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200950 """
951 Stop a test
952
953 :param test:
954
955 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200956 unittest.TestResult.stopTest(self, test)
957 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200958 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100959 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100960 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200961 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200962 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100963 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100964 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200965
Damjan Marionf56b77a2016-10-03 19:44:57 +0200966 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200967 """
968 Print errors from running the test case
969 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200970 self.stream.writeln()
971 self.printErrorList('ERROR', self.errors)
972 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200973
Damjan Marionf56b77a2016-10-03 19:44:57 +0200974 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200975 """
976 Print error list to the output stream together with error type
977 and test case description.
978
979 :param flavour: error type
980 :param errors: iterable errors
981
982 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200983 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200984 self.stream.writeln(double_line_delim)
985 self.stream.writeln("%s: %s" %
986 (flavour, self.getDescription(test)))
987 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200988 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200989
990
Klement Sekeradf2b9802017-10-05 10:26:03 +0200991class Filter_by_test_option:
992 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
993 self.filter_file_name = filter_file_name
994 self.filter_class_name = filter_class_name
995 self.filter_func_name = filter_func_name
996
997 def __call__(self, file_name, class_name, func_name):
998 if self.filter_file_name and file_name != self.filter_file_name:
999 return False
1000 if self.filter_class_name and class_name != self.filter_class_name:
1001 return False
1002 if self.filter_func_name and func_name != self.filter_func_name:
1003 return False
1004 return True
1005
1006
Damjan Marionf56b77a2016-10-03 19:44:57 +02001007class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001008 """
Klement Sekera104543f2017-02-03 07:29:43 +01001009 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001010 """
1011 @property
1012 def resultclass(self):
1013 """Class maintaining the results of the tests"""
1014 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001015
Klement Sekeradf2b9802017-10-05 10:26:03 +02001016 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1017 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +02001018 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +01001019 # ignore stream setting here, use hard-coded stdout to be in sync
1020 # with prints from VppTestCase methods ...
1021 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1022 verbosity, failfast, buffer,
1023 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001024 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001025 reporter.pipe = keep_alive_pipe
1026 # this is super-ugly, but very simple to implement and works as long
1027 # as we run only one test at the same time
1028 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +01001029
Klement Sekera104543f2017-02-03 07:29:43 +01001030 test_option = "TEST"
1031
1032 def parse_test_option(self):
Klement Sekera13a83ef2018-03-21 12:35:51 +01001033 f = os.getenv(self.test_option, None)
Klement Sekera104543f2017-02-03 07:29:43 +01001034 filter_file_name = None
1035 filter_class_name = None
1036 filter_func_name = None
1037 if f:
1038 if '.' in f:
1039 parts = f.split('.')
1040 if len(parts) > 3:
1041 raise Exception("Unrecognized %s option: %s" %
1042 (self.test_option, f))
1043 if len(parts) > 2:
1044 if parts[2] not in ('*', ''):
1045 filter_func_name = parts[2]
1046 if parts[1] not in ('*', ''):
1047 filter_class_name = parts[1]
1048 if parts[0] not in ('*', ''):
1049 if parts[0].startswith('test_'):
1050 filter_file_name = parts[0]
1051 else:
1052 filter_file_name = 'test_%s' % parts[0]
1053 else:
1054 if f.startswith('test_'):
1055 filter_file_name = f
1056 else:
1057 filter_file_name = 'test_%s' % f
1058 return filter_file_name, filter_class_name, filter_func_name
1059
Klement Sekeradf2b9802017-10-05 10:26:03 +02001060 @staticmethod
1061 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001062 result = unittest.suite.TestSuite()
1063 for t in tests:
1064 if isinstance(t, unittest.suite.TestSuite):
1065 # this is a bunch of tests, recursively filter...
Klement Sekera05742262018-03-14 18:14:49 +01001066 x = VppTestRunner.filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001067 if x.countTestCases() > 0:
1068 result.addTest(x)
1069 elif isinstance(t, unittest.TestCase):
1070 # this is a single test
1071 parts = t.id().split('.')
1072 # t.id() for common cases like this:
1073 # test_classifier.TestClassifier.test_acl_ip
1074 # apply filtering only if it is so
1075 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001076 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001077 continue
1078 result.addTest(t)
1079 else:
1080 # unexpected object, don't touch it
1081 result.addTest(t)
1082 return result
1083
Damjan Marionf56b77a2016-10-03 19:44:57 +02001084 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001085 """
1086 Run the tests
1087
1088 :param test:
1089
1090 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001091 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001092 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001093 filter_file, filter_class, filter_func = self.parse_test_option()
1094 print("Active filters: file=%s, class=%s, function=%s" % (
1095 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001096 filter_cb = Filter_by_test_option(
1097 filter_file, filter_class, filter_func)
1098 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001099 print("%s out of %s tests match specified filters" % (
1100 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001101 if not running_extended_tests():
1102 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +01001103 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001104
1105
1106class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001107 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001108 self.logger = logger
1109 self.args = args
1110 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001111 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001112 super(Worker, self).__init__()
1113
1114 def run(self):
1115 executable = self.args[0]
1116 self.logger.debug("Running executable w/args `%s'" % self.args)
1117 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001118 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001119 env["CK_LOG_FILE_NAME"] = "-"
1120 self.process = subprocess.Popen(
1121 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1122 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1123 out, err = self.process.communicate()
1124 self.logger.debug("Finished running `%s'" % executable)
1125 self.logger.info("Return code is `%s'" % self.process.returncode)
1126 self.logger.info(single_line_delim)
1127 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1128 self.logger.info(single_line_delim)
1129 self.logger.info(out)
1130 self.logger.info(single_line_delim)
1131 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1132 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001133 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001134 self.logger.info(single_line_delim)
1135 self.result = self.process.returncode