blob: 00f55ad893e5bc60940d7626cd741aac637b8ee3 [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)
Klement Sekeraa3d933c2017-11-06 09:46:00 +010079 if not testclass.cache_vpp_output:
80 for line in read.splitlines():
81 testclass.logger.debug("VPP STDOUT: %s" % line)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010082 if testclass.vpp.stderr.fileno() in readable:
83 read = os.read(testclass.vpp.stderr.fileno(), 1024)
84 testclass.vpp_stderr_deque.append(read)
Klement Sekeraa3d933c2017-11-06 09:46:00 +010085 if not testclass.cache_vpp_output:
86 for line in read.splitlines():
87 testclass.logger.debug("VPP STDERR: %s" % line)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010088 # ignoring the dummy pipe here intentionally - the flag will take care
89 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +020090
91
Klement Sekera87134932017-03-07 11:39:27 +010092def running_extended_tests():
93 try:
94 s = os.getenv("EXTENDED_TESTS")
95 return True if s.lower() in ("y", "yes", "1") else False
96 except:
97 return False
98 return False
99
100
Klement Sekerad3e671e2017-09-29 12:36:37 +0200101def running_on_centos():
102 try:
103 os_id = os.getenv("OS_ID")
104 return True if "centos" in os_id.lower() else False
105 except:
106 return False
107 return False
108
109
Klement Sekera909a6a12017-08-08 04:33:53 +0200110class KeepAliveReporter(object):
111 """
112 Singleton object which reports test start to parent process
113 """
114 _shared_state = {}
115
116 def __init__(self):
117 self.__dict__ = self._shared_state
118
119 @property
120 def pipe(self):
121 return self._pipe
122
123 @pipe.setter
124 def pipe(self, pipe):
125 if hasattr(self, '_pipe'):
126 raise Exception("Internal error - pipe should only be set once.")
127 self._pipe = pipe
128
129 def send_keep_alive(self, test):
130 """
131 Write current test tmpdir & desc to keep-alive pipe to signal liveness
132 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200133 if self.pipe is None:
134 # if not running forked..
135 return
136
Klement Sekera909a6a12017-08-08 04:33:53 +0200137 if isclass(test):
138 desc = test.__name__
139 else:
140 desc = test.shortDescription()
141 if not desc:
142 desc = str(test)
143
Dave Wallacee2efd122017-09-30 22:04:21 -0400144 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200145
146
Damjan Marionf56b77a2016-10-03 19:44:57 +0200147class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100148 """This subclass is a base class for VPP test cases that are implemented as
149 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200150 """
151
152 @property
153 def packet_infos(self):
154 """List of packet infos"""
155 return self._packet_infos
156
Klement Sekeradab231a2016-12-21 08:50:14 +0100157 @classmethod
158 def get_packet_count_for_if_idx(cls, dst_if_index):
159 """Get the number of packet info for specified destination if index"""
160 if dst_if_index in cls._packet_count_for_dst_if_idx:
161 return cls._packet_count_for_dst_if_idx[dst_if_index]
162 else:
163 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200164
165 @classmethod
166 def instance(cls):
167 """Return the instance of this testcase"""
168 return cls.test_instance
169
Damjan Marionf56b77a2016-10-03 19:44:57 +0200170 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200171 def set_debug_flags(cls, d):
172 cls.debug_core = False
173 cls.debug_gdb = False
174 cls.debug_gdbserver = False
175 if d is None:
176 return
177 dl = d.lower()
178 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200179 cls.debug_core = True
180 elif dl == "gdb":
181 cls.debug_gdb = True
182 elif dl == "gdbserver":
183 cls.debug_gdbserver = True
184 else:
185 raise Exception("Unrecognized DEBUG option: '%s'" % d)
186
187 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200188 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200189 """ Set-up the test case class based on environment variables """
190 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200191 s = os.getenv("STEP")
192 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200193 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200194 cls.step = False
195 try:
196 d = os.getenv("DEBUG")
197 except:
198 d = None
Klement Sekeraa3d933c2017-11-06 09:46:00 +0100199 try:
200 c = os.getenv("CACHE_OUTPUT", "1")
201 cls.cache_vpp_output = \
202 True if c.lower() in ("y", "yes", "1") else False
203 except:
204 cls.cache_vpp_output = True
Klement Sekera277b89c2016-10-28 13:20:27 +0200205 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200206 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100207 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100208 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
209 plugin_path = None
210 if cls.plugin_path is not None:
211 if cls.extern_plugin_path is not None:
212 plugin_path = "%s:%s" % (
213 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100214 else:
215 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100216 elif cls.extern_plugin_path is not None:
217 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100218 debug_cli = ""
219 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
220 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100221 coredump_size = None
222 try:
223 size = os.getenv("COREDUMP_SIZE")
224 if size is not None:
225 coredump_size = "coredump-size %s" % size
226 except:
227 pass
228 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400229 coredump_size = "coredump-size unlimited"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100230 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400231 "{", "nodaemon", debug_cli, "full-coredump",
232 coredump_size, "}", "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100233 "api-segment", "{", "prefix", cls.shm_prefix, "}",
234 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
235 "disable", "}", "}"]
Klement Sekera47e275b2017-03-21 08:21:25 +0100236 if plugin_path is not None:
237 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200238 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
239
240 @classmethod
241 def wait_for_enter(cls):
242 if cls.debug_gdbserver:
243 print(double_line_delim)
244 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
245 elif cls.debug_gdb:
246 print(double_line_delim)
247 print("Spawned VPP with PID: %d" % cls.vpp.pid)
248 else:
249 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
250 return
251 print(single_line_delim)
252 print("You can debug the VPP using e.g.:")
253 if cls.debug_gdbserver:
254 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
255 print("Now is the time to attach a gdb by running the above "
256 "command, set up breakpoints etc. and then resume VPP from "
257 "within gdb by issuing the 'continue' command")
258 elif cls.debug_gdb:
259 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
260 print("Now is the time to attach a gdb by running the above "
261 "command and set up breakpoints etc.")
262 print(single_line_delim)
263 raw_input("Press ENTER to continue running the testcase...")
264
265 @classmethod
266 def run_vpp(cls):
267 cmdline = cls.vpp_cmdline
268
269 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100270 gdbserver = '/usr/bin/gdbserver'
271 if not os.path.isfile(gdbserver) or \
272 not os.access(gdbserver, os.X_OK):
273 raise Exception("gdbserver binary '%s' does not exist or is "
274 "not executable" % gdbserver)
275
276 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200277 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
278
Klement Sekera931be3a2016-11-03 05:36:01 +0100279 try:
280 cls.vpp = subprocess.Popen(cmdline,
281 stdout=subprocess.PIPE,
282 stderr=subprocess.PIPE,
283 bufsize=1)
284 except Exception as e:
285 cls.logger.critical("Couldn't start vpp: %s" % e)
286 raise
287
Klement Sekera277b89c2016-10-28 13:20:27 +0200288 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100289
Damjan Marionf56b77a2016-10-03 19:44:57 +0200290 @classmethod
291 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200292 """
293 Perform class setup before running the testcase
294 Remove shared memory files, start vpp and connect the vpp-api
295 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100296 gc.collect() # run garbage collection first
Klement Sekera277b89c2016-10-28 13:20:27 +0200297 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200298 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200299 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera027dbd52017-04-11 06:01:53 +0200300 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
301 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100302 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
303 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200304 cls.file_handler.setLevel(DEBUG)
305 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200306 cls.shm_prefix = cls.tempdir.split("/")[-1]
307 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200308 cls.logger.info("Temporary dir is %s, shm prefix is %s",
309 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200310 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100311 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100312 cls._captures = []
313 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200314 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100315 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100316 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200317 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200318 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200319 # need to catch exceptions here because if we raise, then the cleanup
320 # doesn't get called and we might end with a zombie vpp
321 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200322 cls.run_vpp()
Dave Wallacee2efd122017-09-30 22:04:21 -0400323 cls.reporter.send_keep_alive(cls)
Klement Sekerae4504c62016-12-08 10:16:41 +0100324 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100325 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100326 cls.pump_thread_stop_flag = Event()
327 cls.pump_thread_wakeup_pipe = os.pipe()
328 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100329 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100330 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100331 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100332 if cls.step:
333 hook = StepHook(cls)
334 else:
335 hook = PollHook(cls)
336 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100337 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200338 try:
339 hook.poll_vpp()
340 except:
341 cls.vpp_startup_failed = True
342 cls.logger.critical(
343 "VPP died shortly after startup, check the"
344 " output to standard error for possible cause")
345 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100346 try:
347 cls.vapi.connect()
348 except:
349 if cls.debug_gdbserver:
350 print(colorize("You're running VPP inside gdbserver but "
351 "VPP-API connection failed, did you forget "
352 "to 'continue' VPP from within gdb?", RED))
353 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200354 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100355 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100356 try:
357 cls.quit()
358 except:
359 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100360 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200361
Damjan Marionf56b77a2016-10-03 19:44:57 +0200362 @classmethod
363 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200364 """
365 Disconnect vpp-api, kill vpp and cleanup shared memory files
366 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200367 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
368 cls.vpp.poll()
369 if cls.vpp.returncode is None:
370 print(double_line_delim)
371 print("VPP or GDB server is still running")
372 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100373 raw_input("When done debugging, press ENTER to kill the "
374 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200375
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100376 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
377 cls.pump_thread_stop_flag.set()
378 if hasattr(cls, 'pump_thread'):
379 cls.logger.debug("Waiting for pump thread to stop")
380 cls.pump_thread.join()
381 if hasattr(cls, 'vpp_stderr_reader_thread'):
382 cls.logger.debug("Waiting for stdderr pump to stop")
383 cls.vpp_stderr_reader_thread.join()
384
Klement Sekeraf62ae122016-10-11 11:47:09 +0200385 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100386 if hasattr(cls, 'vapi'):
387 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100388 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200389 cls.vpp.poll()
390 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100391 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200392 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100393 cls.logger.debug("Waiting for vpp to die")
394 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200395 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200396
Klement Sekera3747c752017-04-10 06:30:17 +0200397 if cls.vpp_startup_failed:
398 stdout_log = cls.logger.info
399 stderr_log = cls.logger.critical
400 else:
401 stdout_log = cls.logger.info
402 stderr_log = cls.logger.info
403
Klement Sekerae4504c62016-12-08 10:16:41 +0100404 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200405 stdout_log(single_line_delim)
406 stdout_log('VPP output to stdout while running %s:', cls.__name__)
407 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100408 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200409 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
410 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200411 stdout_log('\n%s', vpp_output)
412 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200413
Klement Sekerae4504c62016-12-08 10:16:41 +0100414 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200415 stderr_log(single_line_delim)
416 stderr_log('VPP output to stderr while running %s:', cls.__name__)
417 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100418 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200419 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
420 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200421 stderr_log('\n%s', vpp_output)
422 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200423
Damjan Marionf56b77a2016-10-03 19:44:57 +0200424 @classmethod
425 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200426 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200427 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200428 cls.file_handler.close()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200429
Damjan Marionf56b77a2016-10-03 19:44:57 +0200430 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200431 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100432 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
433 (self.__class__.__name__, self._testMethodName,
434 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200435 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200436 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700437 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200438 self.logger.info(self.vapi.ppcli("show hardware"))
439 self.logger.info(self.vapi.ppcli("show error"))
440 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100441 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500442 # Save/Dump VPP api trace log
443 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
444 tmp_api_trace = "/tmp/%s" % api_trace
445 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
446 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
447 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
448 vpp_api_trace_log))
449 os.rename(tmp_api_trace, vpp_api_trace_log)
450 self.logger.info(self.vapi.ppcli("api trace dump %s" %
451 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100452 else:
453 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200454
Damjan Marionf56b77a2016-10-03 19:44:57 +0200455 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200456 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200457 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100458 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
459 (self.__class__.__name__, self._testMethodName,
460 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100461 if self.vpp_dead:
462 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100463 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100464 self.vpp_stdout_deque.append(
465 "--- test setUp() for %s.%s(%s) starts here ---\n" %
466 (self.__class__.__name__, self._testMethodName,
467 self._testMethodDoc))
468 self.vpp_stderr_deque.append(
469 "--- test setUp() for %s.%s(%s) starts here ---\n" %
470 (self.__class__.__name__, self._testMethodName,
471 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200472 self.vapi.cli("clear trace")
473 # store the test instance inside the test class - so that objects
474 # holding the class can access instance methods (like assertEqual)
475 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200476
Damjan Marionf56b77a2016-10-03 19:44:57 +0200477 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200478 def pg_enable_capture(cls, interfaces):
479 """
480 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200481
Klement Sekeraf62ae122016-10-11 11:47:09 +0200482 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200483
Klement Sekeraf62ae122016-10-11 11:47:09 +0200484 """
485 for i in interfaces:
486 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200487
Damjan Marionf56b77a2016-10-03 19:44:57 +0200488 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100489 def register_capture(cls, cap_name):
490 """ Register a capture in the testclass """
491 # add to the list of captures with current timestamp
492 cls._captures.append((time.time(), cap_name))
493 # filter out from zombies
494 cls._zombie_captures = [(stamp, name)
495 for (stamp, name) in cls._zombie_captures
496 if name != cap_name]
497
498 @classmethod
499 def pg_start(cls):
500 """ Remove any zombie captures and enable the packet generator """
501 # how long before capture is allowed to be deleted - otherwise vpp
502 # crashes - 100ms seems enough (this shouldn't be needed at all)
503 capture_ttl = 0.1
504 now = time.time()
505 for stamp, cap_name in cls._zombie_captures:
506 wait = stamp + capture_ttl - now
507 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100508 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100509 now = time.time()
510 cls.logger.debug("Removing zombie capture %s" % cap_name)
511 cls.vapi.cli('packet-generator delete %s' % cap_name)
512
Klement Sekeraf62ae122016-10-11 11:47:09 +0200513 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
514 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100515 cls._zombie_captures = cls._captures
516 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200517
Damjan Marionf56b77a2016-10-03 19:44:57 +0200518 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200519 def create_pg_interfaces(cls, interfaces):
520 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100521 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200522
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100523 :param interfaces: iterable indexes of the interfaces.
524 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200525
Klement Sekeraf62ae122016-10-11 11:47:09 +0200526 """
527 result = []
528 for i in interfaces:
529 intf = VppPGInterface(cls, i)
530 setattr(cls, intf.name, intf)
531 result.append(intf)
532 cls.pg_interfaces = result
533 return result
534
Matej Klotton0178d522016-11-04 11:11:44 +0100535 @classmethod
536 def create_loopback_interfaces(cls, interfaces):
537 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100538 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100539
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100540 :param interfaces: iterable indexes of the interfaces.
541 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100542 """
543 result = []
544 for i in interfaces:
545 intf = VppLoInterface(cls, i)
546 setattr(cls, intf.name, intf)
547 result.append(intf)
548 cls.lo_interfaces = result
549 return result
550
Damjan Marionf56b77a2016-10-03 19:44:57 +0200551 @staticmethod
552 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200553 """
554 Extend packet to given size by padding with spaces
555 NOTE: Currently works only when Raw layer is present.
556
557 :param packet: packet
558 :param size: target size
559
560 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200561 packet_len = len(packet) + 4
562 extend = size - packet_len
563 if extend > 0:
564 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200565
Klement Sekeradab231a2016-12-21 08:50:14 +0100566 @classmethod
567 def reset_packet_infos(cls):
568 """ Reset the list of packet info objects and packet counts to zero """
569 cls._packet_infos = {}
570 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200571
Klement Sekeradab231a2016-12-21 08:50:14 +0100572 @classmethod
573 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200574 """
575 Create packet info object containing the source and destination indexes
576 and add it to the testcase's packet info list
577
Klement Sekeradab231a2016-12-21 08:50:14 +0100578 :param VppInterface src_if: source interface
579 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200580
581 :returns: _PacketInfo object
582
583 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200584 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100585 info.index = len(cls._packet_infos)
586 info.src = src_if.sw_if_index
587 info.dst = dst_if.sw_if_index
588 if isinstance(dst_if, VppSubInterface):
589 dst_idx = dst_if.parent.sw_if_index
590 else:
591 dst_idx = dst_if.sw_if_index
592 if dst_idx in cls._packet_count_for_dst_if_idx:
593 cls._packet_count_for_dst_if_idx[dst_idx] += 1
594 else:
595 cls._packet_count_for_dst_if_idx[dst_idx] = 1
596 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200597 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200598
Damjan Marionf56b77a2016-10-03 19:44:57 +0200599 @staticmethod
600 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200601 """
602 Convert _PacketInfo object to packet payload
603
604 :param info: _PacketInfo object
605
606 :returns: string containing serialized data from packet info
607 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100608 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
609 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200610
Damjan Marionf56b77a2016-10-03 19:44:57 +0200611 @staticmethod
612 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200613 """
614 Convert packet payload to _PacketInfo object
615
616 :param payload: packet payload
617
618 :returns: _PacketInfo object containing de-serialized data from payload
619
620 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200621 numbers = payload.split()
622 info = _PacketInfo()
623 info.index = int(numbers[0])
624 info.src = int(numbers[1])
625 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100626 info.ip = int(numbers[3])
627 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200628 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200629
Damjan Marionf56b77a2016-10-03 19:44:57 +0200630 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200631 """
632 Iterate over the packet info list stored in the testcase
633 Start iteration with first element if info is None
634 Continue based on index in info if info is specified
635
636 :param info: info or None
637 :returns: next info in list or None if no more infos
638 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200639 if info is None:
640 next_index = 0
641 else:
642 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100643 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200644 return None
645 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100646 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200647
Klement Sekeraf62ae122016-10-11 11:47:09 +0200648 def get_next_packet_info_for_interface(self, src_index, info):
649 """
650 Search the packet info list for the next packet info with same source
651 interface index
652
653 :param src_index: source interface index to search for
654 :param info: packet info - where to start the search
655 :returns: packet info or None
656
657 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200658 while True:
659 info = self.get_next_packet_info(info)
660 if info is None:
661 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200662 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200663 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200664
Klement Sekeraf62ae122016-10-11 11:47:09 +0200665 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
666 """
667 Search the packet info list for the next packet info with same source
668 and destination interface indexes
669
670 :param src_index: source interface index to search for
671 :param dst_index: destination interface index to search for
672 :param info: packet info - where to start the search
673 :returns: packet info or None
674
675 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200676 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200677 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200678 if info is None:
679 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200680 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200681 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200682
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200683 def assert_equal(self, real_value, expected_value, name_or_class=None):
684 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100685 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200686 return
687 try:
688 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
689 msg = msg % (getdoc(name_or_class).strip(),
690 real_value, str(name_or_class(real_value)),
691 expected_value, str(name_or_class(expected_value)))
692 except:
693 msg = "Invalid %s: %s does not match expected value %s" % (
694 name_or_class, real_value, expected_value)
695
696 self.assertEqual(real_value, expected_value, msg)
697
Klement Sekerab17dd962017-01-09 07:43:48 +0100698 def assert_in_range(self,
699 real_value,
700 expected_min,
701 expected_max,
702 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200703 if name is None:
704 msg = None
705 else:
706 msg = "Invalid %s: %s out of range <%s,%s>" % (
707 name, real_value, expected_min, expected_max)
708 self.assertTrue(expected_min <= real_value <= expected_max, msg)
709
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100710 @classmethod
711 def sleep(cls, timeout, remark=None):
712 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000713 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
714 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100715 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000716 after = time.time()
717 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200718 cls.logger.error("unexpected time.sleep() result - "
719 "slept for %ss instead of ~%ss!" % (
720 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000721 if hasattr(cls, 'logger'):
722 cls.logger.debug(
723 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
724 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100725
Damjan Marionf56b77a2016-10-03 19:44:57 +0200726
Klement Sekera87134932017-03-07 11:39:27 +0100727class TestCasePrinter(object):
728 _shared_state = {}
729
730 def __init__(self):
731 self.__dict__ = self._shared_state
732 if not hasattr(self, "_test_case_set"):
733 self._test_case_set = set()
734
735 def print_test_case_heading_if_first_time(self, case):
736 if case.__class__ not in self._test_case_set:
737 print(double_line_delim)
738 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
739 print(double_line_delim)
740 self._test_case_set.add(case.__class__)
741
742
Damjan Marionf56b77a2016-10-03 19:44:57 +0200743class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200744 """
745 @property result_string
746 String variable to store the test case result string.
747 @property errors
748 List variable containing 2-tuples of TestCase instances and strings
749 holding formatted tracebacks. Each tuple represents a test which
750 raised an unexpected exception.
751 @property failures
752 List variable containing 2-tuples of TestCase instances and strings
753 holding formatted tracebacks. Each tuple represents a test where
754 a failure was explicitly signalled using the TestCase.assert*()
755 methods.
756 """
757
Damjan Marionf56b77a2016-10-03 19:44:57 +0200758 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200759 """
Klement Sekerada505f62017-01-04 12:58:53 +0100760 :param stream File descriptor to store where to report test results.
761 Set to the standard error stream by default.
762 :param descriptions Boolean variable to store information if to use
763 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200764 :param verbosity Integer variable to store required verbosity level.
765 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200766 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
767 self.stream = stream
768 self.descriptions = descriptions
769 self.verbosity = verbosity
770 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100771 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200772
Damjan Marionf56b77a2016-10-03 19:44:57 +0200773 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200774 """
775 Record a test succeeded result
776
777 :param test:
778
779 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100780 if hasattr(test, 'logger'):
781 test.logger.debug("--- addSuccess() %s.%s(%s) called"
782 % (test.__class__.__name__,
783 test._testMethodName,
784 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200785 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200786 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200787
Klement Sekeraf62ae122016-10-11 11:47:09 +0200788 def addSkip(self, test, reason):
789 """
790 Record a test skipped.
791
792 :param test:
793 :param reason:
794
795 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100796 if hasattr(test, 'logger'):
797 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
798 % (test.__class__.__name__,
799 test._testMethodName,
800 test._testMethodDoc,
801 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200802 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200803 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200804
Klement Sekeraf413bef2017-08-15 07:09:02 +0200805 def symlink_failed(self, test):
806 logger = None
807 if hasattr(test, 'logger'):
808 logger = test.logger
809 if hasattr(test, 'tempdir'):
810 try:
811 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
812 link_path = '%s/%s-FAILED' % (failed_dir,
813 test.tempdir.split("/")[-1])
814 if logger:
815 logger.debug("creating a link to the failed test")
816 logger.debug("os.symlink(%s, %s)" %
817 (test.tempdir, link_path))
818 os.symlink(test.tempdir, link_path)
819 except Exception as e:
820 if logger:
821 logger.error(e)
822
Klement Sekeradf2b9802017-10-05 10:26:03 +0200823 def send_failure_through_pipe(self, test):
824 if hasattr(self, 'test_framework_failed_pipe'):
825 pipe = self.test_framework_failed_pipe
826 if pipe:
827 pipe.send(test.__class__)
828
Damjan Marionf56b77a2016-10-03 19:44:57 +0200829 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200830 """
831 Record a test failed result
832
833 :param test:
834 :param err: error message
835
836 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100837 if hasattr(test, 'logger'):
838 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
839 % (test.__class__.__name__,
840 test._testMethodName,
841 test._testMethodDoc, err))
842 test.logger.debug("formatted exception is:\n%s" %
843 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200844 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200845 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200846 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200847 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200848 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200849 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200850 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200851
Klement Sekeradf2b9802017-10-05 10:26:03 +0200852 self.send_failure_through_pipe(test)
853
Damjan Marionf56b77a2016-10-03 19:44:57 +0200854 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200855 """
856 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200857
Klement Sekeraf62ae122016-10-11 11:47:09 +0200858 :param test:
859 :param err: error message
860
861 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100862 if hasattr(test, 'logger'):
863 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
864 % (test.__class__.__name__,
865 test._testMethodName,
866 test._testMethodDoc, err))
867 test.logger.debug("formatted exception is:\n%s" %
868 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200869 unittest.TestResult.addError(self, test, err)
870 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200871 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200872 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200873 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200874 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200875 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200876
Klement Sekeradf2b9802017-10-05 10:26:03 +0200877 self.send_failure_through_pipe(test)
878
Damjan Marionf56b77a2016-10-03 19:44:57 +0200879 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200880 """
881 Get test description
882
883 :param test:
884 :returns: test description
885
886 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200887 # TODO: if none print warning not raise exception
888 short_description = test.shortDescription()
889 if self.descriptions and short_description:
890 return short_description
891 else:
892 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200893
Damjan Marionf56b77a2016-10-03 19:44:57 +0200894 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200895 """
896 Start a test
897
898 :param test:
899
900 """
Klement Sekera87134932017-03-07 11:39:27 +0100901 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200902 unittest.TestResult.startTest(self, test)
903 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200904 self.stream.writeln(
905 "Starting " + self.getDescription(test) + " ...")
906 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200907
Damjan Marionf56b77a2016-10-03 19:44:57 +0200908 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200909 """
910 Stop a test
911
912 :param test:
913
914 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200915 unittest.TestResult.stopTest(self, test)
916 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200917 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100918 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100919 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200920 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200921 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100922 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100923 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200924
Damjan Marionf56b77a2016-10-03 19:44:57 +0200925 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200926 """
927 Print errors from running the test case
928 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200929 self.stream.writeln()
930 self.printErrorList('ERROR', self.errors)
931 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200932
Damjan Marionf56b77a2016-10-03 19:44:57 +0200933 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200934 """
935 Print error list to the output stream together with error type
936 and test case description.
937
938 :param flavour: error type
939 :param errors: iterable errors
940
941 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200942 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200943 self.stream.writeln(double_line_delim)
944 self.stream.writeln("%s: %s" %
945 (flavour, self.getDescription(test)))
946 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200947 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200948
949
Klement Sekeradf2b9802017-10-05 10:26:03 +0200950class Filter_by_test_option:
951 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
952 self.filter_file_name = filter_file_name
953 self.filter_class_name = filter_class_name
954 self.filter_func_name = filter_func_name
955
956 def __call__(self, file_name, class_name, func_name):
957 if self.filter_file_name and file_name != self.filter_file_name:
958 return False
959 if self.filter_class_name and class_name != self.filter_class_name:
960 return False
961 if self.filter_func_name and func_name != self.filter_func_name:
962 return False
963 return True
964
965
Damjan Marionf56b77a2016-10-03 19:44:57 +0200966class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200967 """
Klement Sekera104543f2017-02-03 07:29:43 +0100968 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200969 """
970 @property
971 def resultclass(self):
972 """Class maintaining the results of the tests"""
973 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200974
Klement Sekeradf2b9802017-10-05 10:26:03 +0200975 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
976 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +0200977 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +0100978 # ignore stream setting here, use hard-coded stdout to be in sync
979 # with prints from VppTestCase methods ...
980 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
981 verbosity, failfast, buffer,
982 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +0200983 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +0200984 reporter.pipe = keep_alive_pipe
985 # this is super-ugly, but very simple to implement and works as long
986 # as we run only one test at the same time
987 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +0100988
Klement Sekera104543f2017-02-03 07:29:43 +0100989 test_option = "TEST"
990
991 def parse_test_option(self):
992 try:
993 f = os.getenv(self.test_option)
994 except:
995 f = None
996 filter_file_name = None
997 filter_class_name = None
998 filter_func_name = None
999 if f:
1000 if '.' in f:
1001 parts = f.split('.')
1002 if len(parts) > 3:
1003 raise Exception("Unrecognized %s option: %s" %
1004 (self.test_option, f))
1005 if len(parts) > 2:
1006 if parts[2] not in ('*', ''):
1007 filter_func_name = parts[2]
1008 if parts[1] not in ('*', ''):
1009 filter_class_name = parts[1]
1010 if parts[0] not in ('*', ''):
1011 if parts[0].startswith('test_'):
1012 filter_file_name = parts[0]
1013 else:
1014 filter_file_name = 'test_%s' % parts[0]
1015 else:
1016 if f.startswith('test_'):
1017 filter_file_name = f
1018 else:
1019 filter_file_name = 'test_%s' % f
1020 return filter_file_name, filter_class_name, filter_func_name
1021
Klement Sekeradf2b9802017-10-05 10:26:03 +02001022 @staticmethod
1023 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001024 result = unittest.suite.TestSuite()
1025 for t in tests:
1026 if isinstance(t, unittest.suite.TestSuite):
1027 # this is a bunch of tests, recursively filter...
Klement Sekeradf2b9802017-10-05 10:26:03 +02001028 x = filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001029 if x.countTestCases() > 0:
1030 result.addTest(x)
1031 elif isinstance(t, unittest.TestCase):
1032 # this is a single test
1033 parts = t.id().split('.')
1034 # t.id() for common cases like this:
1035 # test_classifier.TestClassifier.test_acl_ip
1036 # apply filtering only if it is so
1037 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001038 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001039 continue
1040 result.addTest(t)
1041 else:
1042 # unexpected object, don't touch it
1043 result.addTest(t)
1044 return result
1045
Damjan Marionf56b77a2016-10-03 19:44:57 +02001046 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001047 """
1048 Run the tests
1049
1050 :param test:
1051
1052 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001053 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001054 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001055 filter_file, filter_class, filter_func = self.parse_test_option()
1056 print("Active filters: file=%s, class=%s, function=%s" % (
1057 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001058 filter_cb = Filter_by_test_option(
1059 filter_file, filter_class, filter_func)
1060 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001061 print("%s out of %s tests match specified filters" % (
1062 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001063 if not running_extended_tests():
1064 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +01001065 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001066
1067
1068class Worker(Thread):
1069 def __init__(self, args, logger):
1070 self.logger = logger
1071 self.args = args
1072 self.result = None
1073 super(Worker, self).__init__()
1074
1075 def run(self):
1076 executable = self.args[0]
1077 self.logger.debug("Running executable w/args `%s'" % self.args)
1078 env = os.environ.copy()
1079 env["CK_LOG_FILE_NAME"] = "-"
1080 self.process = subprocess.Popen(
1081 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1082 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1083 out, err = self.process.communicate()
1084 self.logger.debug("Finished running `%s'" % executable)
1085 self.logger.info("Return code is `%s'" % self.process.returncode)
1086 self.logger.info(single_line_delim)
1087 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1088 self.logger.info(single_line_delim)
1089 self.logger.info(out)
1090 self.logger.info(single_line_delim)
1091 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1092 self.logger.info(single_line_delim)
1093 self.logger.error(err)
1094 self.logger.info(single_line_delim)
1095 self.result = self.process.returncode