blob: 58b76bbe656ae7f60241432277c4ebe9f6b0d2e6 [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 Sekera909a6a12017-08-08 04:33:53 +020095class KeepAliveReporter(object):
96 """
97 Singleton object which reports test start to parent process
98 """
99 _shared_state = {}
100
101 def __init__(self):
102 self.__dict__ = self._shared_state
103
104 @property
105 def pipe(self):
106 return self._pipe
107
108 @pipe.setter
109 def pipe(self, pipe):
110 if hasattr(self, '_pipe'):
111 raise Exception("Internal error - pipe should only be set once.")
112 self._pipe = pipe
113
114 def send_keep_alive(self, test):
115 """
116 Write current test tmpdir & desc to keep-alive pipe to signal liveness
117 """
118 if isclass(test):
119 desc = test.__name__
120 else:
121 desc = test.shortDescription()
122 if not desc:
123 desc = str(test)
124
125 self.pipe.send((desc, test.vpp_bin, test.tempdir))
126
127
Damjan Marionf56b77a2016-10-03 19:44:57 +0200128class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100129 """This subclass is a base class for VPP test cases that are implemented as
130 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200131 """
132
133 @property
134 def packet_infos(self):
135 """List of packet infos"""
136 return self._packet_infos
137
Klement Sekeradab231a2016-12-21 08:50:14 +0100138 @classmethod
139 def get_packet_count_for_if_idx(cls, dst_if_index):
140 """Get the number of packet info for specified destination if index"""
141 if dst_if_index in cls._packet_count_for_dst_if_idx:
142 return cls._packet_count_for_dst_if_idx[dst_if_index]
143 else:
144 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200145
146 @classmethod
147 def instance(cls):
148 """Return the instance of this testcase"""
149 return cls.test_instance
150
Damjan Marionf56b77a2016-10-03 19:44:57 +0200151 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200152 def set_debug_flags(cls, d):
153 cls.debug_core = False
154 cls.debug_gdb = False
155 cls.debug_gdbserver = False
156 if d is None:
157 return
158 dl = d.lower()
159 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200160 cls.debug_core = True
161 elif dl == "gdb":
162 cls.debug_gdb = True
163 elif dl == "gdbserver":
164 cls.debug_gdbserver = True
165 else:
166 raise Exception("Unrecognized DEBUG option: '%s'" % d)
167
168 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200169 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200170 """ Set-up the test case class based on environment variables """
171 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200172 s = os.getenv("STEP")
173 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200174 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200175 cls.step = False
176 try:
177 d = os.getenv("DEBUG")
178 except:
179 d = None
180 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200181 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100182 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100183 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
184 plugin_path = None
185 if cls.plugin_path is not None:
186 if cls.extern_plugin_path is not None:
187 plugin_path = "%s:%s" % (
188 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100189 else:
190 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100191 elif cls.extern_plugin_path is not None:
192 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100193 debug_cli = ""
194 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
195 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100196 coredump_size = None
197 try:
198 size = os.getenv("COREDUMP_SIZE")
199 if size is not None:
200 coredump_size = "coredump-size %s" % size
201 except:
202 pass
203 if coredump_size is None:
204 coredump_size = "coredump-size unlimited"
205 cls.vpp_cmdline = [cls.vpp_bin, "unix",
206 "{", "nodaemon", debug_cli, coredump_size, "}",
Dave Wallace90c55722017-02-16 11:25:26 -0500207 "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100208 "api-segment", "{", "prefix", cls.shm_prefix, "}",
209 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
210 "disable", "}", "}"]
Klement Sekera47e275b2017-03-21 08:21:25 +0100211 if plugin_path is not None:
212 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200213 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
214
215 @classmethod
216 def wait_for_enter(cls):
217 if cls.debug_gdbserver:
218 print(double_line_delim)
219 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
220 elif cls.debug_gdb:
221 print(double_line_delim)
222 print("Spawned VPP with PID: %d" % cls.vpp.pid)
223 else:
224 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
225 return
226 print(single_line_delim)
227 print("You can debug the VPP using e.g.:")
228 if cls.debug_gdbserver:
229 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
230 print("Now is the time to attach a gdb by running the above "
231 "command, set up breakpoints etc. and then resume VPP from "
232 "within gdb by issuing the 'continue' command")
233 elif cls.debug_gdb:
234 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
235 print("Now is the time to attach a gdb by running the above "
236 "command and set up breakpoints etc.")
237 print(single_line_delim)
238 raw_input("Press ENTER to continue running the testcase...")
239
240 @classmethod
241 def run_vpp(cls):
242 cmdline = cls.vpp_cmdline
243
244 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100245 gdbserver = '/usr/bin/gdbserver'
246 if not os.path.isfile(gdbserver) or \
247 not os.access(gdbserver, os.X_OK):
248 raise Exception("gdbserver binary '%s' does not exist or is "
249 "not executable" % gdbserver)
250
251 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200252 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
253
Klement Sekera931be3a2016-11-03 05:36:01 +0100254 try:
255 cls.vpp = subprocess.Popen(cmdline,
256 stdout=subprocess.PIPE,
257 stderr=subprocess.PIPE,
258 bufsize=1)
259 except Exception as e:
260 cls.logger.critical("Couldn't start vpp: %s" % e)
261 raise
262
Klement Sekera277b89c2016-10-28 13:20:27 +0200263 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100264
Damjan Marionf56b77a2016-10-03 19:44:57 +0200265 @classmethod
266 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200267 """
268 Perform class setup before running the testcase
269 Remove shared memory files, start vpp and connect the vpp-api
270 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100271 gc.collect() # run garbage collection first
Klement Sekera277b89c2016-10-28 13:20:27 +0200272 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200273 cls.tempdir = tempfile.mkdtemp(
274 prefix='vpp-unittest-' + cls.__name__ + '-')
Klement Sekera027dbd52017-04-11 06:01:53 +0200275 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
276 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100277 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
278 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200279 cls.file_handler.setLevel(DEBUG)
280 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200281 cls.shm_prefix = cls.tempdir.split("/")[-1]
282 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200283 cls.logger.info("Temporary dir is %s, shm prefix is %s",
284 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200285 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100286 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100287 cls._captures = []
288 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200289 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100290 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100291 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200292 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200293 cls.reporter = KeepAliveReporter()
294 cls.reporter.send_keep_alive(cls)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200295 # need to catch exceptions here because if we raise, then the cleanup
296 # doesn't get called and we might end with a zombie vpp
297 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200298 cls.run_vpp()
Klement Sekerae4504c62016-12-08 10:16:41 +0100299 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100300 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100301 cls.pump_thread_stop_flag = Event()
302 cls.pump_thread_wakeup_pipe = os.pipe()
303 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100304 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100305 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100306 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100307 if cls.step:
308 hook = StepHook(cls)
309 else:
310 hook = PollHook(cls)
311 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100312 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200313 try:
314 hook.poll_vpp()
315 except:
316 cls.vpp_startup_failed = True
317 cls.logger.critical(
318 "VPP died shortly after startup, check the"
319 " output to standard error for possible cause")
320 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100321 try:
322 cls.vapi.connect()
323 except:
324 if cls.debug_gdbserver:
325 print(colorize("You're running VPP inside gdbserver but "
326 "VPP-API connection failed, did you forget "
327 "to 'continue' VPP from within gdb?", RED))
328 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200329 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100330 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100331 try:
332 cls.quit()
333 except:
334 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100335 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200336
Damjan Marionf56b77a2016-10-03 19:44:57 +0200337 @classmethod
338 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200339 """
340 Disconnect vpp-api, kill vpp and cleanup shared memory files
341 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200342 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
343 cls.vpp.poll()
344 if cls.vpp.returncode is None:
345 print(double_line_delim)
346 print("VPP or GDB server is still running")
347 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100348 raw_input("When done debugging, press ENTER to kill the "
349 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200350
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100351 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
352 cls.pump_thread_stop_flag.set()
353 if hasattr(cls, 'pump_thread'):
354 cls.logger.debug("Waiting for pump thread to stop")
355 cls.pump_thread.join()
356 if hasattr(cls, 'vpp_stderr_reader_thread'):
357 cls.logger.debug("Waiting for stdderr pump to stop")
358 cls.vpp_stderr_reader_thread.join()
359
Klement Sekeraf62ae122016-10-11 11:47:09 +0200360 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100361 if hasattr(cls, 'vapi'):
362 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100363 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200364 cls.vpp.poll()
365 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100366 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200367 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100368 cls.logger.debug("Waiting for vpp to die")
369 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200370 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200371
Klement Sekera3747c752017-04-10 06:30:17 +0200372 if cls.vpp_startup_failed:
373 stdout_log = cls.logger.info
374 stderr_log = cls.logger.critical
375 else:
376 stdout_log = cls.logger.info
377 stderr_log = cls.logger.info
378
Klement Sekerae4504c62016-12-08 10:16:41 +0100379 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200380 stdout_log(single_line_delim)
381 stdout_log('VPP output to stdout while running %s:', cls.__name__)
382 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100383 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200384 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
385 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200386 stdout_log('\n%s', vpp_output)
387 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200388
Klement Sekerae4504c62016-12-08 10:16:41 +0100389 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200390 stderr_log(single_line_delim)
391 stderr_log('VPP output to stderr while running %s:', cls.__name__)
392 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100393 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200394 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
395 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200396 stderr_log('\n%s', vpp_output)
397 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200398
Damjan Marionf56b77a2016-10-03 19:44:57 +0200399 @classmethod
400 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200401 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200402 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200403 cls.file_handler.close()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200404
Damjan Marionf56b77a2016-10-03 19:44:57 +0200405 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200406 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100407 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
408 (self.__class__.__name__, self._testMethodName,
409 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200410 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200411 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700412 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200413 self.logger.info(self.vapi.ppcli("show hardware"))
414 self.logger.info(self.vapi.ppcli("show error"))
415 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100416 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500417 # Save/Dump VPP api trace log
418 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
419 tmp_api_trace = "/tmp/%s" % api_trace
420 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
421 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
422 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
423 vpp_api_trace_log))
424 os.rename(tmp_api_trace, vpp_api_trace_log)
425 self.logger.info(self.vapi.ppcli("api trace dump %s" %
426 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100427 else:
428 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200429
Damjan Marionf56b77a2016-10-03 19:44:57 +0200430 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200431 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200432 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100433 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
434 (self.__class__.__name__, self._testMethodName,
435 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100436 if self.vpp_dead:
437 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100438 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100439 self.vpp_stdout_deque.append(
440 "--- test setUp() for %s.%s(%s) starts here ---\n" %
441 (self.__class__.__name__, self._testMethodName,
442 self._testMethodDoc))
443 self.vpp_stderr_deque.append(
444 "--- test setUp() for %s.%s(%s) starts here ---\n" %
445 (self.__class__.__name__, self._testMethodName,
446 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200447 self.vapi.cli("clear trace")
448 # store the test instance inside the test class - so that objects
449 # holding the class can access instance methods (like assertEqual)
450 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200451
Damjan Marionf56b77a2016-10-03 19:44:57 +0200452 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200453 def pg_enable_capture(cls, interfaces):
454 """
455 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200456
Klement Sekeraf62ae122016-10-11 11:47:09 +0200457 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200458
Klement Sekeraf62ae122016-10-11 11:47:09 +0200459 """
460 for i in interfaces:
461 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200462
Damjan Marionf56b77a2016-10-03 19:44:57 +0200463 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100464 def register_capture(cls, cap_name):
465 """ Register a capture in the testclass """
466 # add to the list of captures with current timestamp
467 cls._captures.append((time.time(), cap_name))
468 # filter out from zombies
469 cls._zombie_captures = [(stamp, name)
470 for (stamp, name) in cls._zombie_captures
471 if name != cap_name]
472
473 @classmethod
474 def pg_start(cls):
475 """ Remove any zombie captures and enable the packet generator """
476 # how long before capture is allowed to be deleted - otherwise vpp
477 # crashes - 100ms seems enough (this shouldn't be needed at all)
478 capture_ttl = 0.1
479 now = time.time()
480 for stamp, cap_name in cls._zombie_captures:
481 wait = stamp + capture_ttl - now
482 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100483 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100484 now = time.time()
485 cls.logger.debug("Removing zombie capture %s" % cap_name)
486 cls.vapi.cli('packet-generator delete %s' % cap_name)
487
Klement Sekeraf62ae122016-10-11 11:47:09 +0200488 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
489 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100490 cls._zombie_captures = cls._captures
491 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200492
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200494 def create_pg_interfaces(cls, interfaces):
495 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100496 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200497
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100498 :param interfaces: iterable indexes of the interfaces.
499 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200500
Klement Sekeraf62ae122016-10-11 11:47:09 +0200501 """
502 result = []
503 for i in interfaces:
504 intf = VppPGInterface(cls, i)
505 setattr(cls, intf.name, intf)
506 result.append(intf)
507 cls.pg_interfaces = result
508 return result
509
Matej Klotton0178d522016-11-04 11:11:44 +0100510 @classmethod
511 def create_loopback_interfaces(cls, interfaces):
512 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100513 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100514
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100515 :param interfaces: iterable indexes of the interfaces.
516 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100517 """
518 result = []
519 for i in interfaces:
520 intf = VppLoInterface(cls, i)
521 setattr(cls, intf.name, intf)
522 result.append(intf)
523 cls.lo_interfaces = result
524 return result
525
Damjan Marionf56b77a2016-10-03 19:44:57 +0200526 @staticmethod
527 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200528 """
529 Extend packet to given size by padding with spaces
530 NOTE: Currently works only when Raw layer is present.
531
532 :param packet: packet
533 :param size: target size
534
535 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200536 packet_len = len(packet) + 4
537 extend = size - packet_len
538 if extend > 0:
539 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200540
Klement Sekeradab231a2016-12-21 08:50:14 +0100541 @classmethod
542 def reset_packet_infos(cls):
543 """ Reset the list of packet info objects and packet counts to zero """
544 cls._packet_infos = {}
545 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200546
Klement Sekeradab231a2016-12-21 08:50:14 +0100547 @classmethod
548 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200549 """
550 Create packet info object containing the source and destination indexes
551 and add it to the testcase's packet info list
552
Klement Sekeradab231a2016-12-21 08:50:14 +0100553 :param VppInterface src_if: source interface
554 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200555
556 :returns: _PacketInfo object
557
558 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200559 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100560 info.index = len(cls._packet_infos)
561 info.src = src_if.sw_if_index
562 info.dst = dst_if.sw_if_index
563 if isinstance(dst_if, VppSubInterface):
564 dst_idx = dst_if.parent.sw_if_index
565 else:
566 dst_idx = dst_if.sw_if_index
567 if dst_idx in cls._packet_count_for_dst_if_idx:
568 cls._packet_count_for_dst_if_idx[dst_idx] += 1
569 else:
570 cls._packet_count_for_dst_if_idx[dst_idx] = 1
571 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200572 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200573
Damjan Marionf56b77a2016-10-03 19:44:57 +0200574 @staticmethod
575 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200576 """
577 Convert _PacketInfo object to packet payload
578
579 :param info: _PacketInfo object
580
581 :returns: string containing serialized data from packet info
582 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100583 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
584 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200585
Damjan Marionf56b77a2016-10-03 19:44:57 +0200586 @staticmethod
587 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200588 """
589 Convert packet payload to _PacketInfo object
590
591 :param payload: packet payload
592
593 :returns: _PacketInfo object containing de-serialized data from payload
594
595 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200596 numbers = payload.split()
597 info = _PacketInfo()
598 info.index = int(numbers[0])
599 info.src = int(numbers[1])
600 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100601 info.ip = int(numbers[3])
602 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200603 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200604
Damjan Marionf56b77a2016-10-03 19:44:57 +0200605 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200606 """
607 Iterate over the packet info list stored in the testcase
608 Start iteration with first element if info is None
609 Continue based on index in info if info is specified
610
611 :param info: info or None
612 :returns: next info in list or None if no more infos
613 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200614 if info is None:
615 next_index = 0
616 else:
617 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100618 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200619 return None
620 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100621 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200622
Klement Sekeraf62ae122016-10-11 11:47:09 +0200623 def get_next_packet_info_for_interface(self, src_index, info):
624 """
625 Search the packet info list for the next packet info with same source
626 interface index
627
628 :param src_index: source interface index to search for
629 :param info: packet info - where to start the search
630 :returns: packet info or None
631
632 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200633 while True:
634 info = self.get_next_packet_info(info)
635 if info is None:
636 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200637 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200638 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200639
Klement Sekeraf62ae122016-10-11 11:47:09 +0200640 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
641 """
642 Search the packet info list for the next packet info with same source
643 and destination interface indexes
644
645 :param src_index: source interface index to search for
646 :param dst_index: destination interface index to search for
647 :param info: packet info - where to start the search
648 :returns: packet info or None
649
650 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200652 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200653 if info is None:
654 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200655 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200656 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200657
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200658 def assert_equal(self, real_value, expected_value, name_or_class=None):
659 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100660 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200661 return
662 try:
663 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
664 msg = msg % (getdoc(name_or_class).strip(),
665 real_value, str(name_or_class(real_value)),
666 expected_value, str(name_or_class(expected_value)))
667 except:
668 msg = "Invalid %s: %s does not match expected value %s" % (
669 name_or_class, real_value, expected_value)
670
671 self.assertEqual(real_value, expected_value, msg)
672
Klement Sekerab17dd962017-01-09 07:43:48 +0100673 def assert_in_range(self,
674 real_value,
675 expected_min,
676 expected_max,
677 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200678 if name is None:
679 msg = None
680 else:
681 msg = "Invalid %s: %s out of range <%s,%s>" % (
682 name, real_value, expected_min, expected_max)
683 self.assertTrue(expected_min <= real_value <= expected_max, msg)
684
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100685 @classmethod
686 def sleep(cls, timeout, remark=None):
687 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000688 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
689 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100690 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000691 after = time.time()
692 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200693 cls.logger.error("unexpected time.sleep() result - "
694 "slept for %ss instead of ~%ss!" % (
695 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000696 if hasattr(cls, 'logger'):
697 cls.logger.debug(
698 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
699 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100700
Damjan Marionf56b77a2016-10-03 19:44:57 +0200701
Klement Sekera87134932017-03-07 11:39:27 +0100702class TestCasePrinter(object):
703 _shared_state = {}
704
705 def __init__(self):
706 self.__dict__ = self._shared_state
707 if not hasattr(self, "_test_case_set"):
708 self._test_case_set = set()
709
710 def print_test_case_heading_if_first_time(self, case):
711 if case.__class__ not in self._test_case_set:
712 print(double_line_delim)
713 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
714 print(double_line_delim)
715 self._test_case_set.add(case.__class__)
716
717
Damjan Marionf56b77a2016-10-03 19:44:57 +0200718class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200719 """
720 @property result_string
721 String variable to store the test case result string.
722 @property errors
723 List variable containing 2-tuples of TestCase instances and strings
724 holding formatted tracebacks. Each tuple represents a test which
725 raised an unexpected exception.
726 @property failures
727 List variable containing 2-tuples of TestCase instances and strings
728 holding formatted tracebacks. Each tuple represents a test where
729 a failure was explicitly signalled using the TestCase.assert*()
730 methods.
731 """
732
Damjan Marionf56b77a2016-10-03 19:44:57 +0200733 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200734 """
Klement Sekerada505f62017-01-04 12:58:53 +0100735 :param stream File descriptor to store where to report test results.
736 Set to the standard error stream by default.
737 :param descriptions Boolean variable to store information if to use
738 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200739 :param verbosity Integer variable to store required verbosity level.
740 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200741 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
742 self.stream = stream
743 self.descriptions = descriptions
744 self.verbosity = verbosity
745 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100746 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200747
Damjan Marionf56b77a2016-10-03 19:44:57 +0200748 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200749 """
750 Record a test succeeded result
751
752 :param test:
753
754 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100755 if hasattr(test, 'logger'):
756 test.logger.debug("--- addSuccess() %s.%s(%s) called"
757 % (test.__class__.__name__,
758 test._testMethodName,
759 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200760 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200761 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200762
Klement Sekeraf62ae122016-10-11 11:47:09 +0200763 def addSkip(self, test, reason):
764 """
765 Record a test skipped.
766
767 :param test:
768 :param reason:
769
770 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100771 if hasattr(test, 'logger'):
772 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
773 % (test.__class__.__name__,
774 test._testMethodName,
775 test._testMethodDoc,
776 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200777 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200778 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200779
Damjan Marionf56b77a2016-10-03 19:44:57 +0200780 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200781 """
782 Record a test failed result
783
784 :param test:
785 :param err: error message
786
787 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100788 if hasattr(test, 'logger'):
789 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
790 % (test.__class__.__name__,
791 test._testMethodName,
792 test._testMethodDoc, err))
793 test.logger.debug("formatted exception is:\n%s" %
794 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200795 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200796 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200797 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200798 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
799 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200800 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200801
Damjan Marionf56b77a2016-10-03 19:44:57 +0200802 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200803 """
804 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200805
Klement Sekeraf62ae122016-10-11 11:47:09 +0200806 :param test:
807 :param err: error message
808
809 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100810 if hasattr(test, 'logger'):
811 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
812 % (test.__class__.__name__,
813 test._testMethodName,
814 test._testMethodDoc, err))
815 test.logger.debug("formatted exception is:\n%s" %
816 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200817 unittest.TestResult.addError(self, test, err)
818 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200819 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200820 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
821 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200822 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200823
Damjan Marionf56b77a2016-10-03 19:44:57 +0200824 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200825 """
826 Get test description
827
828 :param test:
829 :returns: test description
830
831 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200832 # TODO: if none print warning not raise exception
833 short_description = test.shortDescription()
834 if self.descriptions and short_description:
835 return short_description
836 else:
837 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200838
Damjan Marionf56b77a2016-10-03 19:44:57 +0200839 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200840 """
841 Start a test
842
843 :param test:
844
845 """
Klement Sekera87134932017-03-07 11:39:27 +0100846 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200847 unittest.TestResult.startTest(self, test)
848 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200849 self.stream.writeln(
850 "Starting " + self.getDescription(test) + " ...")
851 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200852
Damjan Marionf56b77a2016-10-03 19:44:57 +0200853 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200854 """
855 Stop a test
856
857 :param test:
858
859 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200860 unittest.TestResult.stopTest(self, test)
861 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200862 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100863 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100864 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200865 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200866 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100867 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100868 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200869
Damjan Marionf56b77a2016-10-03 19:44:57 +0200870 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200871 """
872 Print errors from running the test case
873 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200874 self.stream.writeln()
875 self.printErrorList('ERROR', self.errors)
876 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200877
Damjan Marionf56b77a2016-10-03 19:44:57 +0200878 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200879 """
880 Print error list to the output stream together with error type
881 and test case description.
882
883 :param flavour: error type
884 :param errors: iterable errors
885
886 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200887 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200888 self.stream.writeln(double_line_delim)
889 self.stream.writeln("%s: %s" %
890 (flavour, self.getDescription(test)))
891 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200892 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200893
894
Damjan Marionf56b77a2016-10-03 19:44:57 +0200895class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200896 """
Klement Sekera104543f2017-02-03 07:29:43 +0100897 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200898 """
899 @property
900 def resultclass(self):
901 """Class maintaining the results of the tests"""
902 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200903
Klement Sekera909a6a12017-08-08 04:33:53 +0200904 def __init__(self, pipe, stream=sys.stderr, descriptions=True, verbosity=1,
Klement Sekera7a161da2017-01-17 13:42:48 +0100905 failfast=False, buffer=False, resultclass=None):
906 # ignore stream setting here, use hard-coded stdout to be in sync
907 # with prints from VppTestCase methods ...
908 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
909 verbosity, failfast, buffer,
910 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +0200911 reporter = KeepAliveReporter()
912 reporter.pipe = pipe
Klement Sekera7a161da2017-01-17 13:42:48 +0100913
Klement Sekera104543f2017-02-03 07:29:43 +0100914 test_option = "TEST"
915
916 def parse_test_option(self):
917 try:
918 f = os.getenv(self.test_option)
919 except:
920 f = None
921 filter_file_name = None
922 filter_class_name = None
923 filter_func_name = None
924 if f:
925 if '.' in f:
926 parts = f.split('.')
927 if len(parts) > 3:
928 raise Exception("Unrecognized %s option: %s" %
929 (self.test_option, f))
930 if len(parts) > 2:
931 if parts[2] not in ('*', ''):
932 filter_func_name = parts[2]
933 if parts[1] not in ('*', ''):
934 filter_class_name = parts[1]
935 if parts[0] not in ('*', ''):
936 if parts[0].startswith('test_'):
937 filter_file_name = parts[0]
938 else:
939 filter_file_name = 'test_%s' % parts[0]
940 else:
941 if f.startswith('test_'):
942 filter_file_name = f
943 else:
944 filter_file_name = 'test_%s' % f
945 return filter_file_name, filter_class_name, filter_func_name
946
947 def filter_tests(self, tests, filter_file, filter_class, filter_func):
948 result = unittest.suite.TestSuite()
949 for t in tests:
950 if isinstance(t, unittest.suite.TestSuite):
951 # this is a bunch of tests, recursively filter...
952 x = self.filter_tests(t, filter_file, filter_class,
953 filter_func)
954 if x.countTestCases() > 0:
955 result.addTest(x)
956 elif isinstance(t, unittest.TestCase):
957 # this is a single test
958 parts = t.id().split('.')
959 # t.id() for common cases like this:
960 # test_classifier.TestClassifier.test_acl_ip
961 # apply filtering only if it is so
962 if len(parts) == 3:
963 if filter_file and filter_file != parts[0]:
964 continue
965 if filter_class and filter_class != parts[1]:
966 continue
967 if filter_func and filter_func != parts[2]:
968 continue
969 result.addTest(t)
970 else:
971 # unexpected object, don't touch it
972 result.addTest(t)
973 return result
974
Damjan Marionf56b77a2016-10-03 19:44:57 +0200975 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200976 """
977 Run the tests
978
979 :param test:
980
981 """
Klement Sekera3658adc2017-06-07 08:19:47 +0200982 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +0200983 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +0100984 filter_file, filter_class, filter_func = self.parse_test_option()
985 print("Active filters: file=%s, class=%s, function=%s" % (
986 filter_file, filter_class, filter_func))
987 filtered = self.filter_tests(test, filter_file, filter_class,
988 filter_func)
989 print("%s out of %s tests match specified filters" % (
990 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +0200991 if not running_extended_tests():
992 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +0100993 return super(VppTestRunner, self).run(filtered)