blob: f02f2da5e6a5ac1a1e6e4e0cf6f172f41a62d3de [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 Sekeraf62ae122016-10-11 11:47:09 +020011import resource
Klement Sekera3658adc2017-06-07 08:19:47 +020012import faulthandler
Klement Sekerae4504c62016-12-08 10:16:41 +010013from collections import deque
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010014from threading import Thread, Event
Klement Sekera909a6a12017-08-08 04:33:53 +020015from inspect import getdoc, isclass
Klement Sekerab91017a2017-02-09 06:04:36 +010016from traceback import format_exception
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010017from logging import FileHandler, DEBUG, Formatter
18from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020019from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020020from vpp_pg_interface import VppPGInterface
Klement Sekeradab231a2016-12-21 08:50:14 +010021from vpp_sub_interface import VppSubInterface
Matej Klotton0178d522016-11-04 11:11:44 +010022from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020023from vpp_papi_provider import VppPapiProvider
Klement Sekera277b89c2016-10-28 13:20:27 +020024from log import *
Klement Sekera10db26f2017-01-11 08:16:53 +010025from vpp_object import VppObjectRegistry
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010026if os.name == 'posix' and sys.version_info[0] < 3:
27 # using subprocess32 is recommended by python official documentation
28 # @ https://docs.python.org/2/library/subprocess.html
29 import subprocess32 as subprocess
30else:
31 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020032
33"""
34 Test framework module.
35
36 The module provides a set of tools for constructing and running tests and
37 representing the results.
38"""
39
Klement Sekeraf62ae122016-10-11 11:47:09 +020040
Damjan Marionf56b77a2016-10-03 19:44:57 +020041class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020042 """Private class to create packet info object.
43
44 Help process information about the next packet.
45 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020046 """
Matej Klotton86d87c42016-11-11 11:38:55 +010047 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020048 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010049 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020050 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010051 #: Store the index of the destination packet generator interface
52 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020053 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +010054 #: Store expected ip version
55 ip = -1
56 #: Store expected upper protocol
57 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010058 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020059 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020060
Matej Klotton16a14cd2016-12-07 15:09:13 +010061 def __eq__(self, other):
62 index = self.index == other.index
63 src = self.src == other.src
64 dst = self.dst == other.dst
65 data = self.data == other.data
66 return index and src and dst and data
67
Klement Sekeraf62ae122016-10-11 11:47:09 +020068
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010069def pump_output(testclass):
70 """ pump output from vpp stdout/stderr to proper queues """
71 while not testclass.pump_thread_stop_flag.wait(0):
72 readable = select.select([testclass.vpp.stdout.fileno(),
73 testclass.vpp.stderr.fileno(),
74 testclass.pump_thread_wakeup_pipe[0]],
75 [], [])[0]
76 if testclass.vpp.stdout.fileno() in readable:
77 read = os.read(testclass.vpp.stdout.fileno(), 1024)
78 testclass.vpp_stdout_deque.append(read)
79 if testclass.vpp.stderr.fileno() in readable:
80 read = os.read(testclass.vpp.stderr.fileno(), 1024)
81 testclass.vpp_stderr_deque.append(read)
82 # ignoring the dummy pipe here intentionally - the flag will take care
83 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +020084
85
Klement Sekera87134932017-03-07 11:39:27 +010086def running_extended_tests():
87 try:
88 s = os.getenv("EXTENDED_TESTS")
89 return True if s.lower() in ("y", "yes", "1") else False
90 except:
91 return False
92 return False
93
94
Klement Sekerad3e671e2017-09-29 12:36:37 +020095def running_on_centos():
96 try:
97 os_id = os.getenv("OS_ID")
98 return True if "centos" in os_id.lower() else False
99 except:
100 return False
101 return False
102
103
Klement Sekera909a6a12017-08-08 04:33:53 +0200104class KeepAliveReporter(object):
105 """
106 Singleton object which reports test start to parent process
107 """
108 _shared_state = {}
109
110 def __init__(self):
111 self.__dict__ = self._shared_state
112
113 @property
114 def pipe(self):
115 return self._pipe
116
117 @pipe.setter
118 def pipe(self, pipe):
119 if hasattr(self, '_pipe'):
120 raise Exception("Internal error - pipe should only be set once.")
121 self._pipe = pipe
122
123 def send_keep_alive(self, test):
124 """
125 Write current test tmpdir & desc to keep-alive pipe to signal liveness
126 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200127 if self.pipe is None:
128 # if not running forked..
129 return
130
Klement Sekera909a6a12017-08-08 04:33:53 +0200131 if isclass(test):
132 desc = test.__name__
133 else:
134 desc = test.shortDescription()
135 if not desc:
136 desc = str(test)
137
Dave Wallacee2efd122017-09-30 22:04:21 -0400138 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200139
140
Damjan Marionf56b77a2016-10-03 19:44:57 +0200141class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100142 """This subclass is a base class for VPP test cases that are implemented as
143 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200144 """
145
146 @property
147 def packet_infos(self):
148 """List of packet infos"""
149 return self._packet_infos
150
Klement Sekeradab231a2016-12-21 08:50:14 +0100151 @classmethod
152 def get_packet_count_for_if_idx(cls, dst_if_index):
153 """Get the number of packet info for specified destination if index"""
154 if dst_if_index in cls._packet_count_for_dst_if_idx:
155 return cls._packet_count_for_dst_if_idx[dst_if_index]
156 else:
157 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200158
159 @classmethod
160 def instance(cls):
161 """Return the instance of this testcase"""
162 return cls.test_instance
163
Damjan Marionf56b77a2016-10-03 19:44:57 +0200164 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200165 def set_debug_flags(cls, d):
166 cls.debug_core = False
167 cls.debug_gdb = False
168 cls.debug_gdbserver = False
169 if d is None:
170 return
171 dl = d.lower()
172 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200173 cls.debug_core = True
174 elif dl == "gdb":
175 cls.debug_gdb = True
176 elif dl == "gdbserver":
177 cls.debug_gdbserver = True
178 else:
179 raise Exception("Unrecognized DEBUG option: '%s'" % d)
180
181 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200182 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200183 """ Set-up the test case class based on environment variables """
184 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200185 s = os.getenv("STEP")
186 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200187 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200188 cls.step = False
189 try:
190 d = os.getenv("DEBUG")
191 except:
192 d = None
193 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200194 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100195 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100196 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
197 plugin_path = None
198 if cls.plugin_path is not None:
199 if cls.extern_plugin_path is not None:
200 plugin_path = "%s:%s" % (
201 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100202 else:
203 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100204 elif cls.extern_plugin_path is not None:
205 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100206 debug_cli = ""
207 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
208 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100209 coredump_size = None
210 try:
211 size = os.getenv("COREDUMP_SIZE")
212 if size is not None:
213 coredump_size = "coredump-size %s" % size
214 except:
215 pass
216 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400217 coredump_size = "coredump-size unlimited"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100218 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400219 "{", "nodaemon", debug_cli, "full-coredump",
220 coredump_size, "}", "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100221 "api-segment", "{", "prefix", cls.shm_prefix, "}",
222 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
223 "disable", "}", "}"]
Klement Sekera47e275b2017-03-21 08:21:25 +0100224 if plugin_path is not None:
225 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200226 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
227
228 @classmethod
229 def wait_for_enter(cls):
230 if cls.debug_gdbserver:
231 print(double_line_delim)
232 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
233 elif cls.debug_gdb:
234 print(double_line_delim)
235 print("Spawned VPP with PID: %d" % cls.vpp.pid)
236 else:
237 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
238 return
239 print(single_line_delim)
240 print("You can debug the VPP using e.g.:")
241 if cls.debug_gdbserver:
242 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
243 print("Now is the time to attach a gdb by running the above "
244 "command, set up breakpoints etc. and then resume VPP from "
245 "within gdb by issuing the 'continue' command")
246 elif cls.debug_gdb:
247 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
248 print("Now is the time to attach a gdb by running the above "
249 "command and set up breakpoints etc.")
250 print(single_line_delim)
251 raw_input("Press ENTER to continue running the testcase...")
252
253 @classmethod
254 def run_vpp(cls):
255 cmdline = cls.vpp_cmdline
256
257 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100258 gdbserver = '/usr/bin/gdbserver'
259 if not os.path.isfile(gdbserver) or \
260 not os.access(gdbserver, os.X_OK):
261 raise Exception("gdbserver binary '%s' does not exist or is "
262 "not executable" % gdbserver)
263
264 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200265 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
266
Klement Sekera931be3a2016-11-03 05:36:01 +0100267 try:
268 cls.vpp = subprocess.Popen(cmdline,
269 stdout=subprocess.PIPE,
270 stderr=subprocess.PIPE,
271 bufsize=1)
272 except Exception as e:
273 cls.logger.critical("Couldn't start vpp: %s" % e)
274 raise
275
Klement Sekera277b89c2016-10-28 13:20:27 +0200276 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100277
Damjan Marionf56b77a2016-10-03 19:44:57 +0200278 @classmethod
279 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200280 """
281 Perform class setup before running the testcase
282 Remove shared memory files, start vpp and connect the vpp-api
283 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100284 gc.collect() # run garbage collection first
Klement Sekera277b89c2016-10-28 13:20:27 +0200285 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200286 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200287 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera027dbd52017-04-11 06:01:53 +0200288 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
289 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100290 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
291 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200292 cls.file_handler.setLevel(DEBUG)
293 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200294 cls.shm_prefix = cls.tempdir.split("/")[-1]
295 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200296 cls.logger.info("Temporary dir is %s, shm prefix is %s",
297 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200298 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100299 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100300 cls._captures = []
301 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200302 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100303 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100304 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200305 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200306 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200307 # need to catch exceptions here because if we raise, then the cleanup
308 # doesn't get called and we might end with a zombie vpp
309 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200310 cls.run_vpp()
Dave Wallacee2efd122017-09-30 22:04:21 -0400311 cls.reporter.send_keep_alive(cls)
Klement Sekerae4504c62016-12-08 10:16:41 +0100312 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100313 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100314 cls.pump_thread_stop_flag = Event()
315 cls.pump_thread_wakeup_pipe = os.pipe()
316 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100317 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100318 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100319 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100320 if cls.step:
321 hook = StepHook(cls)
322 else:
323 hook = PollHook(cls)
324 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100325 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200326 try:
327 hook.poll_vpp()
328 except:
329 cls.vpp_startup_failed = True
330 cls.logger.critical(
331 "VPP died shortly after startup, check the"
332 " output to standard error for possible cause")
333 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100334 try:
335 cls.vapi.connect()
336 except:
337 if cls.debug_gdbserver:
338 print(colorize("You're running VPP inside gdbserver but "
339 "VPP-API connection failed, did you forget "
340 "to 'continue' VPP from within gdb?", RED))
341 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200342 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100343 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100344 try:
345 cls.quit()
346 except:
347 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100348 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200349
Damjan Marionf56b77a2016-10-03 19:44:57 +0200350 @classmethod
351 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200352 """
353 Disconnect vpp-api, kill vpp and cleanup shared memory files
354 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200355 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
356 cls.vpp.poll()
357 if cls.vpp.returncode is None:
358 print(double_line_delim)
359 print("VPP or GDB server is still running")
360 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100361 raw_input("When done debugging, press ENTER to kill the "
362 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200363
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100364 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
365 cls.pump_thread_stop_flag.set()
366 if hasattr(cls, 'pump_thread'):
367 cls.logger.debug("Waiting for pump thread to stop")
368 cls.pump_thread.join()
369 if hasattr(cls, 'vpp_stderr_reader_thread'):
370 cls.logger.debug("Waiting for stdderr pump to stop")
371 cls.vpp_stderr_reader_thread.join()
372
Klement Sekeraf62ae122016-10-11 11:47:09 +0200373 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100374 if hasattr(cls, 'vapi'):
375 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100376 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200377 cls.vpp.poll()
378 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100379 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200380 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100381 cls.logger.debug("Waiting for vpp to die")
382 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200383 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200384
Klement Sekera3747c752017-04-10 06:30:17 +0200385 if cls.vpp_startup_failed:
386 stdout_log = cls.logger.info
387 stderr_log = cls.logger.critical
388 else:
389 stdout_log = cls.logger.info
390 stderr_log = cls.logger.info
391
Klement Sekerae4504c62016-12-08 10:16:41 +0100392 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200393 stdout_log(single_line_delim)
394 stdout_log('VPP output to stdout while running %s:', cls.__name__)
395 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100396 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200397 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
398 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200399 stdout_log('\n%s', vpp_output)
400 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200401
Klement Sekerae4504c62016-12-08 10:16:41 +0100402 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200403 stderr_log(single_line_delim)
404 stderr_log('VPP output to stderr while running %s:', cls.__name__)
405 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100406 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200407 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
408 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200409 stderr_log('\n%s', vpp_output)
410 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200411
Damjan Marionf56b77a2016-10-03 19:44:57 +0200412 @classmethod
413 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200414 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200415 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200416 cls.file_handler.close()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200417
Damjan Marionf56b77a2016-10-03 19:44:57 +0200418 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200419 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100420 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
421 (self.__class__.__name__, self._testMethodName,
422 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200423 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200424 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700425 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200426 self.logger.info(self.vapi.ppcli("show hardware"))
427 self.logger.info(self.vapi.ppcli("show error"))
428 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100429 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500430 # Save/Dump VPP api trace log
431 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
432 tmp_api_trace = "/tmp/%s" % api_trace
433 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
434 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
435 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
436 vpp_api_trace_log))
437 os.rename(tmp_api_trace, vpp_api_trace_log)
438 self.logger.info(self.vapi.ppcli("api trace dump %s" %
439 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100440 else:
441 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200442
Damjan Marionf56b77a2016-10-03 19:44:57 +0200443 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200444 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200445 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100446 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
447 (self.__class__.__name__, self._testMethodName,
448 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100449 if self.vpp_dead:
450 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100451 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100452 self.vpp_stdout_deque.append(
453 "--- test setUp() for %s.%s(%s) starts here ---\n" %
454 (self.__class__.__name__, self._testMethodName,
455 self._testMethodDoc))
456 self.vpp_stderr_deque.append(
457 "--- test setUp() for %s.%s(%s) starts here ---\n" %
458 (self.__class__.__name__, self._testMethodName,
459 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200460 self.vapi.cli("clear trace")
461 # store the test instance inside the test class - so that objects
462 # holding the class can access instance methods (like assertEqual)
463 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200464
Damjan Marionf56b77a2016-10-03 19:44:57 +0200465 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200466 def pg_enable_capture(cls, interfaces):
467 """
468 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200469
Klement Sekeraf62ae122016-10-11 11:47:09 +0200470 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200471
Klement Sekeraf62ae122016-10-11 11:47:09 +0200472 """
473 for i in interfaces:
474 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200475
Damjan Marionf56b77a2016-10-03 19:44:57 +0200476 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100477 def register_capture(cls, cap_name):
478 """ Register a capture in the testclass """
479 # add to the list of captures with current timestamp
480 cls._captures.append((time.time(), cap_name))
481 # filter out from zombies
482 cls._zombie_captures = [(stamp, name)
483 for (stamp, name) in cls._zombie_captures
484 if name != cap_name]
485
486 @classmethod
487 def pg_start(cls):
488 """ Remove any zombie captures and enable the packet generator """
489 # how long before capture is allowed to be deleted - otherwise vpp
490 # crashes - 100ms seems enough (this shouldn't be needed at all)
491 capture_ttl = 0.1
492 now = time.time()
493 for stamp, cap_name in cls._zombie_captures:
494 wait = stamp + capture_ttl - now
495 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100496 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100497 now = time.time()
498 cls.logger.debug("Removing zombie capture %s" % cap_name)
499 cls.vapi.cli('packet-generator delete %s' % cap_name)
500
Klement Sekeraf62ae122016-10-11 11:47:09 +0200501 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
502 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100503 cls._zombie_captures = cls._captures
504 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200505
Damjan Marionf56b77a2016-10-03 19:44:57 +0200506 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200507 def create_pg_interfaces(cls, interfaces):
508 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100509 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200510
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100511 :param interfaces: iterable indexes of the interfaces.
512 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200513
Klement Sekeraf62ae122016-10-11 11:47:09 +0200514 """
515 result = []
516 for i in interfaces:
517 intf = VppPGInterface(cls, i)
518 setattr(cls, intf.name, intf)
519 result.append(intf)
520 cls.pg_interfaces = result
521 return result
522
Matej Klotton0178d522016-11-04 11:11:44 +0100523 @classmethod
524 def create_loopback_interfaces(cls, interfaces):
525 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100526 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100527
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100528 :param interfaces: iterable indexes of the interfaces.
529 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100530 """
531 result = []
532 for i in interfaces:
533 intf = VppLoInterface(cls, i)
534 setattr(cls, intf.name, intf)
535 result.append(intf)
536 cls.lo_interfaces = result
537 return result
538
Damjan Marionf56b77a2016-10-03 19:44:57 +0200539 @staticmethod
540 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200541 """
542 Extend packet to given size by padding with spaces
543 NOTE: Currently works only when Raw layer is present.
544
545 :param packet: packet
546 :param size: target size
547
548 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200549 packet_len = len(packet) + 4
550 extend = size - packet_len
551 if extend > 0:
552 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200553
Klement Sekeradab231a2016-12-21 08:50:14 +0100554 @classmethod
555 def reset_packet_infos(cls):
556 """ Reset the list of packet info objects and packet counts to zero """
557 cls._packet_infos = {}
558 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200559
Klement Sekeradab231a2016-12-21 08:50:14 +0100560 @classmethod
561 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200562 """
563 Create packet info object containing the source and destination indexes
564 and add it to the testcase's packet info list
565
Klement Sekeradab231a2016-12-21 08:50:14 +0100566 :param VppInterface src_if: source interface
567 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200568
569 :returns: _PacketInfo object
570
571 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200572 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100573 info.index = len(cls._packet_infos)
574 info.src = src_if.sw_if_index
575 info.dst = dst_if.sw_if_index
576 if isinstance(dst_if, VppSubInterface):
577 dst_idx = dst_if.parent.sw_if_index
578 else:
579 dst_idx = dst_if.sw_if_index
580 if dst_idx in cls._packet_count_for_dst_if_idx:
581 cls._packet_count_for_dst_if_idx[dst_idx] += 1
582 else:
583 cls._packet_count_for_dst_if_idx[dst_idx] = 1
584 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200585 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200586
Damjan Marionf56b77a2016-10-03 19:44:57 +0200587 @staticmethod
588 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200589 """
590 Convert _PacketInfo object to packet payload
591
592 :param info: _PacketInfo object
593
594 :returns: string containing serialized data from packet info
595 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100596 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
597 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200598
Damjan Marionf56b77a2016-10-03 19:44:57 +0200599 @staticmethod
600 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200601 """
602 Convert packet payload to _PacketInfo object
603
604 :param payload: packet payload
605
606 :returns: _PacketInfo object containing de-serialized data from payload
607
608 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200609 numbers = payload.split()
610 info = _PacketInfo()
611 info.index = int(numbers[0])
612 info.src = int(numbers[1])
613 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100614 info.ip = int(numbers[3])
615 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200616 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200617
Damjan Marionf56b77a2016-10-03 19:44:57 +0200618 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200619 """
620 Iterate over the packet info list stored in the testcase
621 Start iteration with first element if info is None
622 Continue based on index in info if info is specified
623
624 :param info: info or None
625 :returns: next info in list or None if no more infos
626 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200627 if info is None:
628 next_index = 0
629 else:
630 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100631 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200632 return None
633 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100634 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200635
Klement Sekeraf62ae122016-10-11 11:47:09 +0200636 def get_next_packet_info_for_interface(self, src_index, info):
637 """
638 Search the packet info list for the next packet info with same source
639 interface index
640
641 :param src_index: source interface index to search for
642 :param info: packet info - where to start the search
643 :returns: packet info or None
644
645 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200646 while True:
647 info = self.get_next_packet_info(info)
648 if info is None:
649 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200650 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200652
Klement Sekeraf62ae122016-10-11 11:47:09 +0200653 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
654 """
655 Search the packet info list for the next packet info with same source
656 and destination interface indexes
657
658 :param src_index: source interface index to search for
659 :param dst_index: destination interface index to search for
660 :param info: packet info - where to start the search
661 :returns: packet info or None
662
663 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200664 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200665 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200666 if info is None:
667 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200668 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200669 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200671 def assert_equal(self, real_value, expected_value, name_or_class=None):
672 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100673 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200674 return
675 try:
676 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
677 msg = msg % (getdoc(name_or_class).strip(),
678 real_value, str(name_or_class(real_value)),
679 expected_value, str(name_or_class(expected_value)))
680 except:
681 msg = "Invalid %s: %s does not match expected value %s" % (
682 name_or_class, real_value, expected_value)
683
684 self.assertEqual(real_value, expected_value, msg)
685
Klement Sekerab17dd962017-01-09 07:43:48 +0100686 def assert_in_range(self,
687 real_value,
688 expected_min,
689 expected_max,
690 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200691 if name is None:
692 msg = None
693 else:
694 msg = "Invalid %s: %s out of range <%s,%s>" % (
695 name, real_value, expected_min, expected_max)
696 self.assertTrue(expected_min <= real_value <= expected_max, msg)
697
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100698 @classmethod
699 def sleep(cls, timeout, remark=None):
700 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000701 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
702 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100703 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000704 after = time.time()
705 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200706 cls.logger.error("unexpected time.sleep() result - "
707 "slept for %ss instead of ~%ss!" % (
708 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000709 if hasattr(cls, 'logger'):
710 cls.logger.debug(
711 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
712 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100713
Damjan Marionf56b77a2016-10-03 19:44:57 +0200714
Klement Sekera87134932017-03-07 11:39:27 +0100715class TestCasePrinter(object):
716 _shared_state = {}
717
718 def __init__(self):
719 self.__dict__ = self._shared_state
720 if not hasattr(self, "_test_case_set"):
721 self._test_case_set = set()
722
723 def print_test_case_heading_if_first_time(self, case):
724 if case.__class__ not in self._test_case_set:
725 print(double_line_delim)
726 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
727 print(double_line_delim)
728 self._test_case_set.add(case.__class__)
729
730
Damjan Marionf56b77a2016-10-03 19:44:57 +0200731class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200732 """
733 @property result_string
734 String variable to store the test case result string.
735 @property errors
736 List variable containing 2-tuples of TestCase instances and strings
737 holding formatted tracebacks. Each tuple represents a test which
738 raised an unexpected exception.
739 @property failures
740 List variable containing 2-tuples of TestCase instances and strings
741 holding formatted tracebacks. Each tuple represents a test where
742 a failure was explicitly signalled using the TestCase.assert*()
743 methods.
744 """
745
Damjan Marionf56b77a2016-10-03 19:44:57 +0200746 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200747 """
Klement Sekerada505f62017-01-04 12:58:53 +0100748 :param stream File descriptor to store where to report test results.
749 Set to the standard error stream by default.
750 :param descriptions Boolean variable to store information if to use
751 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200752 :param verbosity Integer variable to store required verbosity level.
753 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200754 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
755 self.stream = stream
756 self.descriptions = descriptions
757 self.verbosity = verbosity
758 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100759 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200760
Damjan Marionf56b77a2016-10-03 19:44:57 +0200761 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200762 """
763 Record a test succeeded result
764
765 :param test:
766
767 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100768 if hasattr(test, 'logger'):
769 test.logger.debug("--- addSuccess() %s.%s(%s) called"
770 % (test.__class__.__name__,
771 test._testMethodName,
772 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200773 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200774 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200775
Klement Sekeraf62ae122016-10-11 11:47:09 +0200776 def addSkip(self, test, reason):
777 """
778 Record a test skipped.
779
780 :param test:
781 :param reason:
782
783 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100784 if hasattr(test, 'logger'):
785 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
786 % (test.__class__.__name__,
787 test._testMethodName,
788 test._testMethodDoc,
789 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200790 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200791 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200792
Klement Sekeraf413bef2017-08-15 07:09:02 +0200793 def symlink_failed(self, test):
794 logger = None
795 if hasattr(test, 'logger'):
796 logger = test.logger
797 if hasattr(test, 'tempdir'):
798 try:
799 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
800 link_path = '%s/%s-FAILED' % (failed_dir,
801 test.tempdir.split("/")[-1])
802 if logger:
803 logger.debug("creating a link to the failed test")
804 logger.debug("os.symlink(%s, %s)" %
805 (test.tempdir, link_path))
806 os.symlink(test.tempdir, link_path)
807 except Exception as e:
808 if logger:
809 logger.error(e)
810
Damjan Marionf56b77a2016-10-03 19:44:57 +0200811 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200812 """
813 Record a test failed result
814
815 :param test:
816 :param err: error message
817
818 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100819 if hasattr(test, 'logger'):
820 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
821 % (test.__class__.__name__,
822 test._testMethodName,
823 test._testMethodDoc, err))
824 test.logger.debug("formatted exception is:\n%s" %
825 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200826 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200827 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200828 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200829 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200830 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200831 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200832 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200833
Damjan Marionf56b77a2016-10-03 19:44:57 +0200834 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200835 """
836 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200837
Klement Sekeraf62ae122016-10-11 11:47:09 +0200838 :param test:
839 :param err: error message
840
841 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100842 if hasattr(test, 'logger'):
843 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
844 % (test.__class__.__name__,
845 test._testMethodName,
846 test._testMethodDoc, err))
847 test.logger.debug("formatted exception is:\n%s" %
848 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200849 unittest.TestResult.addError(self, test, err)
850 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200851 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200852 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200853 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200854 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200855 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200856
Damjan Marionf56b77a2016-10-03 19:44:57 +0200857 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200858 """
859 Get test description
860
861 :param test:
862 :returns: test description
863
864 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200865 # TODO: if none print warning not raise exception
866 short_description = test.shortDescription()
867 if self.descriptions and short_description:
868 return short_description
869 else:
870 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200871
Damjan Marionf56b77a2016-10-03 19:44:57 +0200872 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200873 """
874 Start a test
875
876 :param test:
877
878 """
Klement Sekera87134932017-03-07 11:39:27 +0100879 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200880 unittest.TestResult.startTest(self, test)
881 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200882 self.stream.writeln(
883 "Starting " + self.getDescription(test) + " ...")
884 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200885
Damjan Marionf56b77a2016-10-03 19:44:57 +0200886 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200887 """
888 Stop a test
889
890 :param test:
891
892 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200893 unittest.TestResult.stopTest(self, test)
894 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200895 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100896 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100897 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200898 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200899 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100900 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100901 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200902
Damjan Marionf56b77a2016-10-03 19:44:57 +0200903 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200904 """
905 Print errors from running the test case
906 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200907 self.stream.writeln()
908 self.printErrorList('ERROR', self.errors)
909 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200910
Damjan Marionf56b77a2016-10-03 19:44:57 +0200911 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200912 """
913 Print error list to the output stream together with error type
914 and test case description.
915
916 :param flavour: error type
917 :param errors: iterable errors
918
919 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200920 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200921 self.stream.writeln(double_line_delim)
922 self.stream.writeln("%s: %s" %
923 (flavour, self.getDescription(test)))
924 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200925 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200926
927
Damjan Marionf56b77a2016-10-03 19:44:57 +0200928class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200929 """
Klement Sekera104543f2017-02-03 07:29:43 +0100930 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200931 """
932 @property
933 def resultclass(self):
934 """Class maintaining the results of the tests"""
935 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200936
Klement Sekera3f6ff192017-08-11 06:56:05 +0200937 def __init__(self, pipe=None, stream=sys.stderr, descriptions=True,
938 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +0100939 # ignore stream setting here, use hard-coded stdout to be in sync
940 # with prints from VppTestCase methods ...
941 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
942 verbosity, failfast, buffer,
943 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +0200944 reporter = KeepAliveReporter()
945 reporter.pipe = pipe
Klement Sekera7a161da2017-01-17 13:42:48 +0100946
Klement Sekera104543f2017-02-03 07:29:43 +0100947 test_option = "TEST"
948
949 def parse_test_option(self):
950 try:
951 f = os.getenv(self.test_option)
952 except:
953 f = None
954 filter_file_name = None
955 filter_class_name = None
956 filter_func_name = None
957 if f:
958 if '.' in f:
959 parts = f.split('.')
960 if len(parts) > 3:
961 raise Exception("Unrecognized %s option: %s" %
962 (self.test_option, f))
963 if len(parts) > 2:
964 if parts[2] not in ('*', ''):
965 filter_func_name = parts[2]
966 if parts[1] not in ('*', ''):
967 filter_class_name = parts[1]
968 if parts[0] not in ('*', ''):
969 if parts[0].startswith('test_'):
970 filter_file_name = parts[0]
971 else:
972 filter_file_name = 'test_%s' % parts[0]
973 else:
974 if f.startswith('test_'):
975 filter_file_name = f
976 else:
977 filter_file_name = 'test_%s' % f
978 return filter_file_name, filter_class_name, filter_func_name
979
980 def filter_tests(self, tests, filter_file, filter_class, filter_func):
981 result = unittest.suite.TestSuite()
982 for t in tests:
983 if isinstance(t, unittest.suite.TestSuite):
984 # this is a bunch of tests, recursively filter...
985 x = self.filter_tests(t, filter_file, filter_class,
986 filter_func)
987 if x.countTestCases() > 0:
988 result.addTest(x)
989 elif isinstance(t, unittest.TestCase):
990 # this is a single test
991 parts = t.id().split('.')
992 # t.id() for common cases like this:
993 # test_classifier.TestClassifier.test_acl_ip
994 # apply filtering only if it is so
995 if len(parts) == 3:
996 if filter_file and filter_file != parts[0]:
997 continue
998 if filter_class and filter_class != parts[1]:
999 continue
1000 if filter_func and filter_func != parts[2]:
1001 continue
1002 result.addTest(t)
1003 else:
1004 # unexpected object, don't touch it
1005 result.addTest(t)
1006 return result
1007
Damjan Marionf56b77a2016-10-03 19:44:57 +02001008 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001009 """
1010 Run the tests
1011
1012 :param test:
1013
1014 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001015 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001016 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001017 filter_file, filter_class, filter_func = self.parse_test_option()
1018 print("Active filters: file=%s, class=%s, function=%s" % (
1019 filter_file, filter_class, filter_func))
1020 filtered = self.filter_tests(test, filter_file, filter_class,
1021 filter_func)
1022 print("%s out of %s tests match specified filters" % (
1023 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001024 if not running_extended_tests():
1025 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +01001026 return super(VppTestRunner, self).run(filtered)