blob: 78aebf61e83799c03c3b356e61dbf84ec6aec65b [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 Sekera6a6f4f72017-11-09 09:16:39 +010013import random
Dave Wallace42996c02018-02-26 14:40:13 -050014import copy
Klement Sekerae4504c62016-12-08 10:16:41 +010015from collections import deque
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010016from threading import Thread, Event
Klement Sekera909a6a12017-08-08 04:33:53 +020017from inspect import getdoc, isclass
Klement Sekerab91017a2017-02-09 06:04:36 +010018from traceback import format_exception
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010019from logging import FileHandler, DEBUG, Formatter
20from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020021from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020022from vpp_pg_interface import VppPGInterface
Klement Sekeradab231a2016-12-21 08:50:14 +010023from vpp_sub_interface import VppSubInterface
Matej Klotton0178d522016-11-04 11:11:44 +010024from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020025from vpp_papi_provider import VppPapiProvider
Klement Sekera277b89c2016-10-28 13:20:27 +020026from log import *
Klement Sekera10db26f2017-01-11 08:16:53 +010027from vpp_object import VppObjectRegistry
Klement Sekera75e7d132017-09-20 08:26:30 +020028from vpp_punt_socket import vpp_uds_socket_name
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010029if os.name == 'posix' and sys.version_info[0] < 3:
30 # using subprocess32 is recommended by python official documentation
31 # @ https://docs.python.org/2/library/subprocess.html
32 import subprocess32 as subprocess
33else:
34 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020035
Klement Sekeraebbaf552018-02-17 13:41:33 +010036debug_framework = False
37if os.getenv('TEST_DEBUG', "0") == "1":
38 debug_framework = True
39 import debug_internal
40
41
Klement Sekeraf62ae122016-10-11 11:47:09 +020042"""
43 Test framework module.
44
45 The module provides a set of tools for constructing and running tests and
46 representing the results.
47"""
48
Klement Sekeraf62ae122016-10-11 11:47:09 +020049
Damjan Marionf56b77a2016-10-03 19:44:57 +020050class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020051 """Private class to create packet info object.
52
53 Help process information about the next packet.
54 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020055 """
Matej Klotton86d87c42016-11-11 11:38:55 +010056 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020057 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010058 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020059 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010060 #: Store the index of the destination packet generator interface
61 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020062 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +010063 #: Store expected ip version
64 ip = -1
65 #: Store expected upper protocol
66 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010067 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020068 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020069
Matej Klotton16a14cd2016-12-07 15:09:13 +010070 def __eq__(self, other):
71 index = self.index == other.index
72 src = self.src == other.src
73 dst = self.dst == other.dst
74 data = self.data == other.data
75 return index and src and dst and data
76
Klement Sekeraf62ae122016-10-11 11:47:09 +020077
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010078def pump_output(testclass):
79 """ pump output from vpp stdout/stderr to proper queues """
Klement Sekera6a6f4f72017-11-09 09:16:39 +010080 stdout_fragment = ""
81 stderr_fragment = ""
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010082 while not testclass.pump_thread_stop_flag.wait(0):
83 readable = select.select([testclass.vpp.stdout.fileno(),
84 testclass.vpp.stderr.fileno(),
85 testclass.pump_thread_wakeup_pipe[0]],
86 [], [])[0]
87 if testclass.vpp.stdout.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +010088 read = os.read(testclass.vpp.stdout.fileno(), 102400)
89 if len(read) > 0:
90 split = read.splitlines(True)
91 if len(stdout_fragment) > 0:
92 split[0] = "%s%s" % (stdout_fragment, split[0])
93 if len(split) > 0 and split[-1].endswith("\n"):
94 limit = None
95 else:
96 limit = -1
97 stdout_fragment = split[-1]
98 testclass.vpp_stdout_deque.extend(split[:limit])
99 if not testclass.cache_vpp_output:
100 for line in split[:limit]:
101 testclass.logger.debug(
102 "VPP STDOUT: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100103 if testclass.vpp.stderr.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100104 read = os.read(testclass.vpp.stderr.fileno(), 102400)
105 if len(read) > 0:
106 split = read.splitlines(True)
107 if len(stderr_fragment) > 0:
108 split[0] = "%s%s" % (stderr_fragment, split[0])
109 if len(split) > 0 and split[-1].endswith("\n"):
110 limit = None
111 else:
112 limit = -1
113 stderr_fragment = split[-1]
114 testclass.vpp_stderr_deque.extend(split[:limit])
115 if not testclass.cache_vpp_output:
116 for line in split[:limit]:
117 testclass.logger.debug(
118 "VPP STDERR: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100119 # ignoring the dummy pipe here intentionally - the flag will take care
120 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +0200121
122
Klement Sekera87134932017-03-07 11:39:27 +0100123def running_extended_tests():
124 try:
125 s = os.getenv("EXTENDED_TESTS")
126 return True if s.lower() in ("y", "yes", "1") else False
127 except:
128 return False
129 return False
130
131
Klement Sekerad3e671e2017-09-29 12:36:37 +0200132def running_on_centos():
133 try:
134 os_id = os.getenv("OS_ID")
135 return True if "centos" in os_id.lower() else False
136 except:
137 return False
138 return False
139
140
Klement Sekera909a6a12017-08-08 04:33:53 +0200141class KeepAliveReporter(object):
142 """
143 Singleton object which reports test start to parent process
144 """
145 _shared_state = {}
146
147 def __init__(self):
148 self.__dict__ = self._shared_state
149
150 @property
151 def pipe(self):
152 return self._pipe
153
154 @pipe.setter
155 def pipe(self, pipe):
156 if hasattr(self, '_pipe'):
157 raise Exception("Internal error - pipe should only be set once.")
158 self._pipe = pipe
159
160 def send_keep_alive(self, test):
161 """
162 Write current test tmpdir & desc to keep-alive pipe to signal liveness
163 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200164 if self.pipe is None:
165 # if not running forked..
166 return
167
Klement Sekera909a6a12017-08-08 04:33:53 +0200168 if isclass(test):
169 desc = test.__name__
170 else:
171 desc = test.shortDescription()
172 if not desc:
173 desc = str(test)
174
Dave Wallacee2efd122017-09-30 22:04:21 -0400175 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200176
177
Damjan Marionf56b77a2016-10-03 19:44:57 +0200178class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100179 """This subclass is a base class for VPP test cases that are implemented as
180 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200181 """
182
183 @property
184 def packet_infos(self):
185 """List of packet infos"""
186 return self._packet_infos
187
Klement Sekeradab231a2016-12-21 08:50:14 +0100188 @classmethod
189 def get_packet_count_for_if_idx(cls, dst_if_index):
190 """Get the number of packet info for specified destination if index"""
191 if dst_if_index in cls._packet_count_for_dst_if_idx:
192 return cls._packet_count_for_dst_if_idx[dst_if_index]
193 else:
194 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200195
196 @classmethod
197 def instance(cls):
198 """Return the instance of this testcase"""
199 return cls.test_instance
200
Damjan Marionf56b77a2016-10-03 19:44:57 +0200201 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200202 def set_debug_flags(cls, d):
203 cls.debug_core = False
204 cls.debug_gdb = False
205 cls.debug_gdbserver = False
206 if d is None:
207 return
208 dl = d.lower()
209 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200210 cls.debug_core = True
211 elif dl == "gdb":
212 cls.debug_gdb = True
213 elif dl == "gdbserver":
214 cls.debug_gdbserver = True
215 else:
216 raise Exception("Unrecognized DEBUG option: '%s'" % d)
217
218 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200219 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200220 """ Set-up the test case class based on environment variables """
221 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200222 s = os.getenv("STEP")
223 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200224 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200225 cls.step = False
226 try:
227 d = os.getenv("DEBUG")
228 except:
229 d = None
Klement Sekeraa3d933c2017-11-06 09:46:00 +0100230 try:
231 c = os.getenv("CACHE_OUTPUT", "1")
232 cls.cache_vpp_output = \
Klement Sekerae1783992017-11-07 03:19:16 +0100233 False if c.lower() in ("n", "no", "0") else True
Klement Sekeraa3d933c2017-11-06 09:46:00 +0100234 except:
235 cls.cache_vpp_output = True
Klement Sekera277b89c2016-10-28 13:20:27 +0200236 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200237 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100238 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100239 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
240 plugin_path = None
241 if cls.plugin_path is not None:
242 if cls.extern_plugin_path is not None:
243 plugin_path = "%s:%s" % (
244 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100245 else:
246 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100247 elif cls.extern_plugin_path is not None:
248 plugin_path = cls.extern_plugin_path
Klement Sekera01bbbe92016-11-02 09:25:05 +0100249 debug_cli = ""
250 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
251 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100252 coredump_size = None
253 try:
254 size = os.getenv("COREDUMP_SIZE")
255 if size is not None:
256 coredump_size = "coredump-size %s" % size
257 except:
258 pass
259 if coredump_size is None:
Dave Wallacee2efd122017-09-30 22:04:21 -0400260 coredump_size = "coredump-size unlimited"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100261 cls.vpp_cmdline = [cls.vpp_bin, "unix",
Dave Wallacee2efd122017-09-30 22:04:21 -0400262 "{", "nodaemon", debug_cli, "full-coredump",
263 coredump_size, "}", "api-trace", "{", "on", "}",
Damjan Marion374e2c52017-03-09 20:38:15 +0100264 "api-segment", "{", "prefix", cls.shm_prefix, "}",
265 "plugins", "{", "plugin", "dpdk_plugin.so", "{",
Klement Sekera75e7d132017-09-20 08:26:30 +0200266 "disable", "}", "}",
267 "punt", "{", "socket", cls.punt_socket_path, "}"]
Klement Sekera47e275b2017-03-21 08:21:25 +0100268 if plugin_path is not None:
269 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200270 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
271
272 @classmethod
273 def wait_for_enter(cls):
274 if cls.debug_gdbserver:
275 print(double_line_delim)
276 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
277 elif cls.debug_gdb:
278 print(double_line_delim)
279 print("Spawned VPP with PID: %d" % cls.vpp.pid)
280 else:
281 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
282 return
283 print(single_line_delim)
284 print("You can debug the VPP using e.g.:")
285 if cls.debug_gdbserver:
286 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
287 print("Now is the time to attach a gdb by running the above "
288 "command, set up breakpoints etc. and then resume VPP from "
289 "within gdb by issuing the 'continue' command")
290 elif cls.debug_gdb:
291 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
292 print("Now is the time to attach a gdb by running the above "
293 "command and set up breakpoints etc.")
294 print(single_line_delim)
295 raw_input("Press ENTER to continue running the testcase...")
296
297 @classmethod
298 def run_vpp(cls):
299 cmdline = cls.vpp_cmdline
300
301 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100302 gdbserver = '/usr/bin/gdbserver'
303 if not os.path.isfile(gdbserver) or \
304 not os.access(gdbserver, os.X_OK):
305 raise Exception("gdbserver binary '%s' does not exist or is "
306 "not executable" % gdbserver)
307
308 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200309 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
310
Klement Sekera931be3a2016-11-03 05:36:01 +0100311 try:
312 cls.vpp = subprocess.Popen(cmdline,
313 stdout=subprocess.PIPE,
314 stderr=subprocess.PIPE,
315 bufsize=1)
316 except Exception as e:
317 cls.logger.critical("Couldn't start vpp: %s" % e)
318 raise
319
Klement Sekera277b89c2016-10-28 13:20:27 +0200320 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100321
Damjan Marionf56b77a2016-10-03 19:44:57 +0200322 @classmethod
323 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200324 """
325 Perform class setup before running the testcase
326 Remove shared memory files, start vpp and connect the vpp-api
327 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100328 gc.collect() # run garbage collection first
Klement Sekera96867ba2018-02-02 11:27:53 +0100329 random.seed()
Klement Sekera277b89c2016-10-28 13:20:27 +0200330 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200331 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200332 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera027dbd52017-04-11 06:01:53 +0200333 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
334 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100335 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
336 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200337 cls.file_handler.setLevel(DEBUG)
338 cls.logger.addHandler(cls.file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200339 cls.shm_prefix = cls.tempdir.split("/")[-1]
Klement Sekera75e7d132017-09-20 08:26:30 +0200340 cls.punt_socket_path = '%s/%s' % (cls.tempdir, vpp_uds_socket_name)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200341 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200342 cls.logger.info("Temporary dir is %s, shm prefix is %s",
343 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200344 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100345 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100346 cls._captures = []
347 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200348 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100349 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100350 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200351 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200352 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200353 # need to catch exceptions here because if we raise, then the cleanup
354 # doesn't get called and we might end with a zombie vpp
355 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200356 cls.run_vpp()
Dave Wallacee2efd122017-09-30 22:04:21 -0400357 cls.reporter.send_keep_alive(cls)
Klement Sekerae4504c62016-12-08 10:16:41 +0100358 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100359 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100360 cls.pump_thread_stop_flag = Event()
361 cls.pump_thread_wakeup_pipe = os.pipe()
362 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100363 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100364 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100365 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100366 if cls.step:
367 hook = StepHook(cls)
368 else:
369 hook = PollHook(cls)
370 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100371 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera3747c752017-04-10 06:30:17 +0200372 try:
373 hook.poll_vpp()
374 except:
375 cls.vpp_startup_failed = True
376 cls.logger.critical(
377 "VPP died shortly after startup, check the"
378 " output to standard error for possible cause")
379 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100380 try:
381 cls.vapi.connect()
382 except:
383 if cls.debug_gdbserver:
384 print(colorize("You're running VPP inside gdbserver but "
385 "VPP-API connection failed, did you forget "
386 "to 'continue' VPP from within gdb?", RED))
387 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200388 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100389 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100390 try:
391 cls.quit()
392 except:
393 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100394 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200395
Damjan Marionf56b77a2016-10-03 19:44:57 +0200396 @classmethod
397 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200398 """
399 Disconnect vpp-api, kill vpp and cleanup shared memory files
400 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200401 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
402 cls.vpp.poll()
403 if cls.vpp.returncode is None:
404 print(double_line_delim)
405 print("VPP or GDB server is still running")
406 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100407 raw_input("When done debugging, press ENTER to kill the "
408 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200409
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100410 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
411 cls.pump_thread_stop_flag.set()
412 if hasattr(cls, 'pump_thread'):
413 cls.logger.debug("Waiting for pump thread to stop")
414 cls.pump_thread.join()
415 if hasattr(cls, 'vpp_stderr_reader_thread'):
416 cls.logger.debug("Waiting for stdderr pump to stop")
417 cls.vpp_stderr_reader_thread.join()
418
Klement Sekeraf62ae122016-10-11 11:47:09 +0200419 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100420 if hasattr(cls, 'vapi'):
421 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100422 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200423 cls.vpp.poll()
424 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100425 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200426 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100427 cls.logger.debug("Waiting for vpp to die")
428 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200429 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200430
Klement Sekera3747c752017-04-10 06:30:17 +0200431 if cls.vpp_startup_failed:
432 stdout_log = cls.logger.info
433 stderr_log = cls.logger.critical
434 else:
435 stdout_log = cls.logger.info
436 stderr_log = cls.logger.info
437
Klement Sekerae4504c62016-12-08 10:16:41 +0100438 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200439 stdout_log(single_line_delim)
440 stdout_log('VPP output to stdout while running %s:', cls.__name__)
441 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100442 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200443 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
444 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200445 stdout_log('\n%s', vpp_output)
446 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200447
Klement Sekerae4504c62016-12-08 10:16:41 +0100448 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200449 stderr_log(single_line_delim)
450 stderr_log('VPP output to stderr while running %s:', cls.__name__)
451 stderr_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100452 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200453 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
454 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200455 stderr_log('\n%s', vpp_output)
456 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200457
Damjan Marionf56b77a2016-10-03 19:44:57 +0200458 @classmethod
459 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200460 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200461 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200462 cls.file_handler.close()
Klement Sekeraebbaf552018-02-17 13:41:33 +0100463 cls.reset_packet_infos()
464 if debug_framework:
465 debug_internal.on_tear_down_class(cls)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200466
Damjan Marionf56b77a2016-10-03 19:44:57 +0200467 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200468 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100469 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
470 (self.__class__.__name__, self._testMethodName,
471 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200472 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200473 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns88fc83e2017-04-05 08:11:14 -0700474 self.logger.info(self.vapi.ppcli("show interface"))
Jan49c0fca2016-10-26 15:44:27 +0200475 self.logger.info(self.vapi.ppcli("show hardware"))
476 self.logger.info(self.vapi.ppcli("show error"))
477 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100478 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500479 # Save/Dump VPP api trace log
480 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
481 tmp_api_trace = "/tmp/%s" % api_trace
482 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
483 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
484 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
485 vpp_api_trace_log))
486 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500487 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500488 vpp_api_trace_log))
Klement Sekera1b686402017-03-02 11:29:19 +0100489 else:
490 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200491
Damjan Marionf56b77a2016-10-03 19:44:57 +0200492 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200493 """ Clear trace before running each test"""
Klement Sekera909a6a12017-08-08 04:33:53 +0200494 self.reporter.send_keep_alive(self)
Klement Sekerab91017a2017-02-09 06:04:36 +0100495 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
496 (self.__class__.__name__, self._testMethodName,
497 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100498 if self.vpp_dead:
499 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100500 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100501 self.vpp_stdout_deque.append(
502 "--- test setUp() for %s.%s(%s) starts here ---\n" %
503 (self.__class__.__name__, self._testMethodName,
504 self._testMethodDoc))
505 self.vpp_stderr_deque.append(
506 "--- test setUp() for %s.%s(%s) starts here ---\n" %
507 (self.__class__.__name__, self._testMethodName,
508 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200509 self.vapi.cli("clear trace")
510 # store the test instance inside the test class - so that objects
511 # holding the class can access instance methods (like assertEqual)
512 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200513
Damjan Marionf56b77a2016-10-03 19:44:57 +0200514 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200515 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200516 """
517 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200518
Klement Sekera75e7d132017-09-20 08:26:30 +0200519 :param interfaces: iterable interface indexes (if None,
520 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200521
Klement Sekeraf62ae122016-10-11 11:47:09 +0200522 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200523 if interfaces is None:
524 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200525 for i in interfaces:
526 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200527
Damjan Marionf56b77a2016-10-03 19:44:57 +0200528 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100529 def register_capture(cls, cap_name):
530 """ Register a capture in the testclass """
531 # add to the list of captures with current timestamp
532 cls._captures.append((time.time(), cap_name))
533 # filter out from zombies
534 cls._zombie_captures = [(stamp, name)
535 for (stamp, name) in cls._zombie_captures
536 if name != cap_name]
537
538 @classmethod
539 def pg_start(cls):
540 """ Remove any zombie captures and enable the packet generator """
541 # how long before capture is allowed to be deleted - otherwise vpp
542 # crashes - 100ms seems enough (this shouldn't be needed at all)
543 capture_ttl = 0.1
544 now = time.time()
545 for stamp, cap_name in cls._zombie_captures:
546 wait = stamp + capture_ttl - now
547 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100548 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100549 now = time.time()
550 cls.logger.debug("Removing zombie capture %s" % cap_name)
551 cls.vapi.cli('packet-generator delete %s' % cap_name)
552
Klement Sekeraf62ae122016-10-11 11:47:09 +0200553 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
554 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100555 cls._zombie_captures = cls._captures
556 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200557
Damjan Marionf56b77a2016-10-03 19:44:57 +0200558 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200559 def create_pg_interfaces(cls, interfaces):
560 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100561 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200562
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100563 :param interfaces: iterable indexes of the interfaces.
564 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200565
Klement Sekeraf62ae122016-10-11 11:47:09 +0200566 """
567 result = []
568 for i in interfaces:
569 intf = VppPGInterface(cls, i)
570 setattr(cls, intf.name, intf)
571 result.append(intf)
572 cls.pg_interfaces = result
573 return result
574
Matej Klotton0178d522016-11-04 11:11:44 +0100575 @classmethod
576 def create_loopback_interfaces(cls, interfaces):
577 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100578 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100579
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100580 :param interfaces: iterable indexes of the interfaces.
581 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100582 """
583 result = []
584 for i in interfaces:
585 intf = VppLoInterface(cls, i)
586 setattr(cls, intf.name, intf)
587 result.append(intf)
588 cls.lo_interfaces = result
589 return result
590
Damjan Marionf56b77a2016-10-03 19:44:57 +0200591 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200592 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200593 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200594 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200595 NOTE: Currently works only when Raw layer is present.
596
597 :param packet: packet
598 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200599 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200600
601 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200602 packet_len = len(packet) + 4
603 extend = size - packet_len
604 if extend > 0:
Klement Sekera75e7d132017-09-20 08:26:30 +0200605 num = (extend / len(padding)) + 1
606 packet[Raw].load += (padding * num)[:extend]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200607
Klement Sekeradab231a2016-12-21 08:50:14 +0100608 @classmethod
609 def reset_packet_infos(cls):
610 """ Reset the list of packet info objects and packet counts to zero """
611 cls._packet_infos = {}
612 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200613
Klement Sekeradab231a2016-12-21 08:50:14 +0100614 @classmethod
615 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200616 """
617 Create packet info object containing the source and destination indexes
618 and add it to the testcase's packet info list
619
Klement Sekeradab231a2016-12-21 08:50:14 +0100620 :param VppInterface src_if: source interface
621 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200622
623 :returns: _PacketInfo object
624
625 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200626 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100627 info.index = len(cls._packet_infos)
628 info.src = src_if.sw_if_index
629 info.dst = dst_if.sw_if_index
630 if isinstance(dst_if, VppSubInterface):
631 dst_idx = dst_if.parent.sw_if_index
632 else:
633 dst_idx = dst_if.sw_if_index
634 if dst_idx in cls._packet_count_for_dst_if_idx:
635 cls._packet_count_for_dst_if_idx[dst_idx] += 1
636 else:
637 cls._packet_count_for_dst_if_idx[dst_idx] = 1
638 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200639 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200640
Damjan Marionf56b77a2016-10-03 19:44:57 +0200641 @staticmethod
642 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200643 """
644 Convert _PacketInfo object to packet payload
645
646 :param info: _PacketInfo object
647
648 :returns: string containing serialized data from packet info
649 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100650 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
651 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200652
Damjan Marionf56b77a2016-10-03 19:44:57 +0200653 @staticmethod
654 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200655 """
656 Convert packet payload to _PacketInfo object
657
658 :param payload: packet payload
659
660 :returns: _PacketInfo object containing de-serialized data from payload
661
662 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200663 numbers = payload.split()
664 info = _PacketInfo()
665 info.index = int(numbers[0])
666 info.src = int(numbers[1])
667 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100668 info.ip = int(numbers[3])
669 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200671
Damjan Marionf56b77a2016-10-03 19:44:57 +0200672 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200673 """
674 Iterate over the packet info list stored in the testcase
675 Start iteration with first element if info is None
676 Continue based on index in info if info is specified
677
678 :param info: info or None
679 :returns: next info in list or None if no more infos
680 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200681 if info is None:
682 next_index = 0
683 else:
684 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100685 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200686 return None
687 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100688 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200689
Klement Sekeraf62ae122016-10-11 11:47:09 +0200690 def get_next_packet_info_for_interface(self, src_index, info):
691 """
692 Search the packet info list for the next packet info with same source
693 interface index
694
695 :param src_index: source interface index to search for
696 :param info: packet info - where to start the search
697 :returns: packet info or None
698
699 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200700 while True:
701 info = self.get_next_packet_info(info)
702 if info is None:
703 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200704 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200705 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200706
Klement Sekeraf62ae122016-10-11 11:47:09 +0200707 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
708 """
709 Search the packet info list for the next packet info with same source
710 and destination interface indexes
711
712 :param src_index: source interface index to search for
713 :param dst_index: destination interface index to search for
714 :param info: packet info - where to start the search
715 :returns: packet info or None
716
717 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200718 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200719 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200720 if info is None:
721 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200722 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200723 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200724
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200725 def assert_equal(self, real_value, expected_value, name_or_class=None):
726 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100727 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200728 return
729 try:
730 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
731 msg = msg % (getdoc(name_or_class).strip(),
732 real_value, str(name_or_class(real_value)),
733 expected_value, str(name_or_class(expected_value)))
734 except:
735 msg = "Invalid %s: %s does not match expected value %s" % (
736 name_or_class, real_value, expected_value)
737
738 self.assertEqual(real_value, expected_value, msg)
739
Klement Sekerab17dd962017-01-09 07:43:48 +0100740 def assert_in_range(self,
741 real_value,
742 expected_min,
743 expected_max,
744 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200745 if name is None:
746 msg = None
747 else:
748 msg = "Invalid %s: %s out of range <%s,%s>" % (
749 name, real_value, expected_min, expected_max)
750 self.assertTrue(expected_min <= real_value <= expected_max, msg)
751
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100752 @classmethod
753 def sleep(cls, timeout, remark=None):
754 if hasattr(cls, 'logger'):
Klement Sekera3cfa5582017-04-19 07:10:58 +0000755 cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
756 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +0100757 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +0000758 after = time.time()
759 if after - before > 2 * timeout:
Klement Sekera60c12232017-07-18 10:33:06 +0200760 cls.logger.error("unexpected time.sleep() result - "
761 "slept for %ss instead of ~%ss!" % (
762 after - before, timeout))
Klement Sekera3cfa5582017-04-19 07:10:58 +0000763 if hasattr(cls, 'logger'):
764 cls.logger.debug(
765 "Finished sleep (%s) - slept %ss (wanted %ss)" % (
766 remark, after - before, timeout))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100767
Neale Ranns52fae862018-01-08 04:41:42 -0800768 def send_and_assert_no_replies(self, intf, pkts, remark=""):
769 self.vapi.cli("clear trace")
770 intf.add_stream(pkts)
771 self.pg_enable_capture(self.pg_interfaces)
772 self.pg_start()
773 timeout = 1
774 for i in self.pg_interfaces:
775 i.get_capture(0, timeout=timeout)
776 i.assert_nothing_captured(remark=remark)
777 timeout = 0.1
778
779 def send_and_expect(self, input, pkts, output):
780 self.vapi.cli("clear trace")
781 input.add_stream(pkts)
782 self.pg_enable_capture(self.pg_interfaces)
783 self.pg_start()
784 rx = output.get_capture(len(pkts))
785 return rx
786
Damjan Marionf56b77a2016-10-03 19:44:57 +0200787
Klement Sekera87134932017-03-07 11:39:27 +0100788class TestCasePrinter(object):
789 _shared_state = {}
790
791 def __init__(self):
792 self.__dict__ = self._shared_state
793 if not hasattr(self, "_test_case_set"):
794 self._test_case_set = set()
795
796 def print_test_case_heading_if_first_time(self, case):
797 if case.__class__ not in self._test_case_set:
798 print(double_line_delim)
799 print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
800 print(double_line_delim)
801 self._test_case_set.add(case.__class__)
802
803
Damjan Marionf56b77a2016-10-03 19:44:57 +0200804class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200805 """
806 @property result_string
807 String variable to store the test case result string.
808 @property errors
809 List variable containing 2-tuples of TestCase instances and strings
810 holding formatted tracebacks. Each tuple represents a test which
811 raised an unexpected exception.
812 @property failures
813 List variable containing 2-tuples of TestCase instances and strings
814 holding formatted tracebacks. Each tuple represents a test where
815 a failure was explicitly signalled using the TestCase.assert*()
816 methods.
817 """
818
Damjan Marionf56b77a2016-10-03 19:44:57 +0200819 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200820 """
Klement Sekerada505f62017-01-04 12:58:53 +0100821 :param stream File descriptor to store where to report test results.
822 Set to the standard error stream by default.
823 :param descriptions Boolean variable to store information if to use
824 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200825 :param verbosity Integer variable to store required verbosity level.
826 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200827 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
828 self.stream = stream
829 self.descriptions = descriptions
830 self.verbosity = verbosity
831 self.result_string = None
Klement Sekera87134932017-03-07 11:39:27 +0100832 self.printer = TestCasePrinter()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200833
Damjan Marionf56b77a2016-10-03 19:44:57 +0200834 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200835 """
836 Record a test succeeded result
837
838 :param test:
839
840 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100841 if hasattr(test, 'logger'):
842 test.logger.debug("--- addSuccess() %s.%s(%s) called"
843 % (test.__class__.__name__,
844 test._testMethodName,
845 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200846 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200847 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200848
Klement Sekeraf62ae122016-10-11 11:47:09 +0200849 def addSkip(self, test, reason):
850 """
851 Record a test skipped.
852
853 :param test:
854 :param reason:
855
856 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100857 if hasattr(test, 'logger'):
858 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
859 % (test.__class__.__name__,
860 test._testMethodName,
861 test._testMethodDoc,
862 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200863 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200864 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200865
Klement Sekeraf413bef2017-08-15 07:09:02 +0200866 def symlink_failed(self, test):
867 logger = None
868 if hasattr(test, 'logger'):
869 logger = test.logger
870 if hasattr(test, 'tempdir'):
871 try:
872 failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
873 link_path = '%s/%s-FAILED' % (failed_dir,
874 test.tempdir.split("/")[-1])
875 if logger:
876 logger.debug("creating a link to the failed test")
877 logger.debug("os.symlink(%s, %s)" %
878 (test.tempdir, link_path))
879 os.symlink(test.tempdir, link_path)
880 except Exception as e:
881 if logger:
882 logger.error(e)
883
Klement Sekeradf2b9802017-10-05 10:26:03 +0200884 def send_failure_through_pipe(self, test):
885 if hasattr(self, 'test_framework_failed_pipe'):
886 pipe = self.test_framework_failed_pipe
887 if pipe:
888 pipe.send(test.__class__)
889
Damjan Marionf56b77a2016-10-03 19:44:57 +0200890 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200891 """
892 Record a test failed result
893
894 :param test:
895 :param err: error message
896
897 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100898 if hasattr(test, 'logger'):
899 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
900 % (test.__class__.__name__,
901 test._testMethodName,
902 test._testMethodDoc, err))
903 test.logger.debug("formatted exception is:\n%s" %
904 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200905 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200906 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200907 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200908 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200909 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200910 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200911 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200912
Klement Sekeradf2b9802017-10-05 10:26:03 +0200913 self.send_failure_through_pipe(test)
914
Damjan Marionf56b77a2016-10-03 19:44:57 +0200915 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200916 """
917 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200918
Klement Sekeraf62ae122016-10-11 11:47:09 +0200919 :param test:
920 :param err: error message
921
922 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100923 if hasattr(test, 'logger'):
924 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
925 % (test.__class__.__name__,
926 test._testMethodName,
927 test._testMethodDoc, err))
928 test.logger.debug("formatted exception is:\n%s" %
929 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200930 unittest.TestResult.addError(self, test, err)
931 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200932 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200933 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
Klement Sekeraf413bef2017-08-15 07:09:02 +0200934 self.symlink_failed(test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200935 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200936 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200937
Klement Sekeradf2b9802017-10-05 10:26:03 +0200938 self.send_failure_through_pipe(test)
939
Damjan Marionf56b77a2016-10-03 19:44:57 +0200940 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200941 """
942 Get test description
943
944 :param test:
945 :returns: test description
946
947 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200948 # TODO: if none print warning not raise exception
949 short_description = test.shortDescription()
950 if self.descriptions and short_description:
951 return short_description
952 else:
953 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200954
Damjan Marionf56b77a2016-10-03 19:44:57 +0200955 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200956 """
957 Start a test
958
959 :param test:
960
961 """
Klement Sekera87134932017-03-07 11:39:27 +0100962 self.printer.print_test_case_heading_if_first_time(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200963 unittest.TestResult.startTest(self, test)
964 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200965 self.stream.writeln(
966 "Starting " + self.getDescription(test) + " ...")
967 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200968
Damjan Marionf56b77a2016-10-03 19:44:57 +0200969 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200970 """
971 Stop a test
972
973 :param test:
974
975 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200976 unittest.TestResult.stopTest(self, test)
977 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200978 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100979 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100980 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200981 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200982 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100983 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100984 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200985
Damjan Marionf56b77a2016-10-03 19:44:57 +0200986 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200987 """
988 Print errors from running the test case
989 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200990 self.stream.writeln()
991 self.printErrorList('ERROR', self.errors)
992 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200993
Damjan Marionf56b77a2016-10-03 19:44:57 +0200994 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200995 """
996 Print error list to the output stream together with error type
997 and test case description.
998
999 :param flavour: error type
1000 :param errors: iterable errors
1001
1002 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001003 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001004 self.stream.writeln(double_line_delim)
1005 self.stream.writeln("%s: %s" %
1006 (flavour, self.getDescription(test)))
1007 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001008 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001009
1010
Klement Sekeradf2b9802017-10-05 10:26:03 +02001011class Filter_by_test_option:
1012 def __init__(self, filter_file_name, filter_class_name, filter_func_name):
1013 self.filter_file_name = filter_file_name
1014 self.filter_class_name = filter_class_name
1015 self.filter_func_name = filter_func_name
1016
1017 def __call__(self, file_name, class_name, func_name):
1018 if self.filter_file_name and file_name != self.filter_file_name:
1019 return False
1020 if self.filter_class_name and class_name != self.filter_class_name:
1021 return False
1022 if self.filter_func_name and func_name != self.filter_func_name:
1023 return False
1024 return True
1025
1026
Damjan Marionf56b77a2016-10-03 19:44:57 +02001027class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001028 """
Klement Sekera104543f2017-02-03 07:29:43 +01001029 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001030 """
1031 @property
1032 def resultclass(self):
1033 """Class maintaining the results of the tests"""
1034 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001035
Klement Sekeradf2b9802017-10-05 10:26:03 +02001036 def __init__(self, keep_alive_pipe=None, failed_pipe=None,
1037 stream=sys.stderr, descriptions=True,
Klement Sekera3f6ff192017-08-11 06:56:05 +02001038 verbosity=1, failfast=False, buffer=False, resultclass=None):
Klement Sekera7a161da2017-01-17 13:42:48 +01001039 # ignore stream setting here, use hard-coded stdout to be in sync
1040 # with prints from VppTestCase methods ...
1041 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1042 verbosity, failfast, buffer,
1043 resultclass)
Klement Sekera909a6a12017-08-08 04:33:53 +02001044 reporter = KeepAliveReporter()
Klement Sekeradf2b9802017-10-05 10:26:03 +02001045 reporter.pipe = keep_alive_pipe
1046 # this is super-ugly, but very simple to implement and works as long
1047 # as we run only one test at the same time
1048 VppTestResult.test_framework_failed_pipe = failed_pipe
Klement Sekera7a161da2017-01-17 13:42:48 +01001049
Klement Sekera104543f2017-02-03 07:29:43 +01001050 test_option = "TEST"
1051
1052 def parse_test_option(self):
1053 try:
1054 f = os.getenv(self.test_option)
1055 except:
1056 f = None
1057 filter_file_name = None
1058 filter_class_name = None
1059 filter_func_name = None
1060 if f:
1061 if '.' in f:
1062 parts = f.split('.')
1063 if len(parts) > 3:
1064 raise Exception("Unrecognized %s option: %s" %
1065 (self.test_option, f))
1066 if len(parts) > 2:
1067 if parts[2] not in ('*', ''):
1068 filter_func_name = parts[2]
1069 if parts[1] not in ('*', ''):
1070 filter_class_name = parts[1]
1071 if parts[0] not in ('*', ''):
1072 if parts[0].startswith('test_'):
1073 filter_file_name = parts[0]
1074 else:
1075 filter_file_name = 'test_%s' % parts[0]
1076 else:
1077 if f.startswith('test_'):
1078 filter_file_name = f
1079 else:
1080 filter_file_name = 'test_%s' % f
1081 return filter_file_name, filter_class_name, filter_func_name
1082
Klement Sekeradf2b9802017-10-05 10:26:03 +02001083 @staticmethod
1084 def filter_tests(tests, filter_cb):
Klement Sekera104543f2017-02-03 07:29:43 +01001085 result = unittest.suite.TestSuite()
1086 for t in tests:
1087 if isinstance(t, unittest.suite.TestSuite):
1088 # this is a bunch of tests, recursively filter...
Klement Sekeradf2b9802017-10-05 10:26:03 +02001089 x = filter_tests(t, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001090 if x.countTestCases() > 0:
1091 result.addTest(x)
1092 elif isinstance(t, unittest.TestCase):
1093 # this is a single test
1094 parts = t.id().split('.')
1095 # t.id() for common cases like this:
1096 # test_classifier.TestClassifier.test_acl_ip
1097 # apply filtering only if it is so
1098 if len(parts) == 3:
Klement Sekeradf2b9802017-10-05 10:26:03 +02001099 if not filter_cb(parts[0], parts[1], parts[2]):
Klement Sekera104543f2017-02-03 07:29:43 +01001100 continue
1101 result.addTest(t)
1102 else:
1103 # unexpected object, don't touch it
1104 result.addTest(t)
1105 return result
1106
Damjan Marionf56b77a2016-10-03 19:44:57 +02001107 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001108 """
1109 Run the tests
1110
1111 :param test:
1112
1113 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001114 faulthandler.enable() # emit stack trace to stderr if killed by signal
Klement Sekeraf62ae122016-10-11 11:47:09 +02001115 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +01001116 filter_file, filter_class, filter_func = self.parse_test_option()
1117 print("Active filters: file=%s, class=%s, function=%s" % (
1118 filter_file, filter_class, filter_func))
Klement Sekeradf2b9802017-10-05 10:26:03 +02001119 filter_cb = Filter_by_test_option(
1120 filter_file, filter_class, filter_func)
1121 filtered = self.filter_tests(test, filter_cb)
Klement Sekera104543f2017-02-03 07:29:43 +01001122 print("%s out of %s tests match specified filters" % (
1123 filtered.countTestCases(), test.countTestCases()))
Klement Sekera3747c752017-04-10 06:30:17 +02001124 if not running_extended_tests():
1125 print("Not running extended tests (some tests will be skipped)")
Klement Sekera104543f2017-02-03 07:29:43 +01001126 return super(VppTestRunner, self).run(filtered)
Neale Ranns812ed392017-10-16 04:20:13 -07001127
1128
1129class Worker(Thread):
Dave Wallace42996c02018-02-26 14:40:13 -05001130 def __init__(self, args, logger, env={}):
Neale Ranns812ed392017-10-16 04:20:13 -07001131 self.logger = logger
1132 self.args = args
1133 self.result = None
Dave Wallace42996c02018-02-26 14:40:13 -05001134 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001135 super(Worker, self).__init__()
1136
1137 def run(self):
1138 executable = self.args[0]
1139 self.logger.debug("Running executable w/args `%s'" % self.args)
1140 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001141 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001142 env["CK_LOG_FILE_NAME"] = "-"
1143 self.process = subprocess.Popen(
1144 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1145 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1146 out, err = self.process.communicate()
1147 self.logger.debug("Finished running `%s'" % executable)
1148 self.logger.info("Return code is `%s'" % self.process.returncode)
1149 self.logger.info(single_line_delim)
1150 self.logger.info("Executable `%s' wrote to stdout:" % executable)
1151 self.logger.info(single_line_delim)
1152 self.logger.info(out)
1153 self.logger.info(single_line_delim)
1154 self.logger.info("Executable `%s' wrote to stderr:" % executable)
1155 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001156 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001157 self.logger.info(single_line_delim)
1158 self.result = self.process.returncode