blob: 90e0574a890d2dc3f8bc6a5c0ad56e1c0a3f600a [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 Sekerae4504c62016-12-08 10:16:41 +010012from collections import deque
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010013from threading import Thread, Event
Damjan Marionf56b77a2016-10-03 19:44:57 +020014from inspect import getdoc
Klement Sekerab91017a2017-02-09 06:04:36 +010015from traceback import format_exception
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010016from logging import FileHandler, DEBUG, Formatter
17from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020018from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020019from vpp_pg_interface import VppPGInterface
Klement Sekeradab231a2016-12-21 08:50:14 +010020from vpp_sub_interface import VppSubInterface
Matej Klotton0178d522016-11-04 11:11:44 +010021from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020022from vpp_papi_provider import VppPapiProvider
Klement Sekera277b89c2016-10-28 13:20:27 +020023from log import *
Klement Sekera10db26f2017-01-11 08:16:53 +010024from vpp_object import VppObjectRegistry
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010025if os.name == 'posix' and sys.version_info[0] < 3:
26 # using subprocess32 is recommended by python official documentation
27 # @ https://docs.python.org/2/library/subprocess.html
28 import subprocess32 as subprocess
29else:
30 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020031
32"""
33 Test framework module.
34
35 The module provides a set of tools for constructing and running tests and
36 representing the results.
37"""
38
Klement Sekeraf62ae122016-10-11 11:47:09 +020039
Damjan Marionf56b77a2016-10-03 19:44:57 +020040class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020041 """Private class to create packet info object.
42
43 Help process information about the next packet.
44 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020045 """
Matej Klotton86d87c42016-11-11 11:38:55 +010046 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020047 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010048 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020049 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010050 #: Store the index of the destination packet generator interface
51 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020052 dst = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010053 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020054 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020055
Matej Klotton16a14cd2016-12-07 15:09:13 +010056 def __eq__(self, other):
57 index = self.index == other.index
58 src = self.src == other.src
59 dst = self.dst == other.dst
60 data = self.data == other.data
61 return index and src and dst and data
62
Klement Sekeraf62ae122016-10-11 11:47:09 +020063
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010064def pump_output(testclass):
65 """ pump output from vpp stdout/stderr to proper queues """
66 while not testclass.pump_thread_stop_flag.wait(0):
67 readable = select.select([testclass.vpp.stdout.fileno(),
68 testclass.vpp.stderr.fileno(),
69 testclass.pump_thread_wakeup_pipe[0]],
70 [], [])[0]
71 if testclass.vpp.stdout.fileno() in readable:
72 read = os.read(testclass.vpp.stdout.fileno(), 1024)
73 testclass.vpp_stdout_deque.append(read)
74 if testclass.vpp.stderr.fileno() in readable:
75 read = os.read(testclass.vpp.stderr.fileno(), 1024)
76 testclass.vpp_stderr_deque.append(read)
77 # ignoring the dummy pipe here intentionally - the flag will take care
78 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +020079
80
Damjan Marionf56b77a2016-10-03 19:44:57 +020081class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010082 """This subclass is a base class for VPP test cases that are implemented as
83 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020084 """
85
86 @property
87 def packet_infos(self):
88 """List of packet infos"""
89 return self._packet_infos
90
Klement Sekeradab231a2016-12-21 08:50:14 +010091 @classmethod
92 def get_packet_count_for_if_idx(cls, dst_if_index):
93 """Get the number of packet info for specified destination if index"""
94 if dst_if_index in cls._packet_count_for_dst_if_idx:
95 return cls._packet_count_for_dst_if_idx[dst_if_index]
96 else:
97 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +020098
99 @classmethod
100 def instance(cls):
101 """Return the instance of this testcase"""
102 return cls.test_instance
103
Damjan Marionf56b77a2016-10-03 19:44:57 +0200104 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200105 def set_debug_flags(cls, d):
106 cls.debug_core = False
107 cls.debug_gdb = False
108 cls.debug_gdbserver = False
109 if d is None:
110 return
111 dl = d.lower()
112 if dl == "core":
113 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
114 # give a heads up if this is actually useless
Klement Sekerab17dd962017-01-09 07:43:48 +0100115 print(colorize("WARNING: core size limit is set 0, core files "
116 "will NOT be created", RED))
Klement Sekera277b89c2016-10-28 13:20:27 +0200117 cls.debug_core = True
118 elif dl == "gdb":
119 cls.debug_gdb = True
120 elif dl == "gdbserver":
121 cls.debug_gdbserver = True
122 else:
123 raise Exception("Unrecognized DEBUG option: '%s'" % d)
124
125 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200126 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200127 """ Set-up the test case class based on environment variables """
128 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200129 s = os.getenv("STEP")
130 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200131 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200132 cls.step = False
133 try:
134 d = os.getenv("DEBUG")
135 except:
136 d = None
137 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200138 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100139 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100140 debug_cli = ""
141 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
142 debug_cli = "cli-listen localhost:5002"
Klement Sekerada505f62017-01-04 12:58:53 +0100143 cls.vpp_cmdline = [cls.vpp_bin,
144 "unix", "{", "nodaemon", debug_cli, "}",
Dave Wallace90c55722017-02-16 11:25:26 -0500145 "api-trace", "{", "on", "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200146 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100147 if cls.plugin_path is not None:
148 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200149 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
150
151 @classmethod
152 def wait_for_enter(cls):
153 if cls.debug_gdbserver:
154 print(double_line_delim)
155 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
156 elif cls.debug_gdb:
157 print(double_line_delim)
158 print("Spawned VPP with PID: %d" % cls.vpp.pid)
159 else:
160 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
161 return
162 print(single_line_delim)
163 print("You can debug the VPP using e.g.:")
164 if cls.debug_gdbserver:
165 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
166 print("Now is the time to attach a gdb by running the above "
167 "command, set up breakpoints etc. and then resume VPP from "
168 "within gdb by issuing the 'continue' command")
169 elif cls.debug_gdb:
170 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
171 print("Now is the time to attach a gdb by running the above "
172 "command and set up breakpoints etc.")
173 print(single_line_delim)
174 raw_input("Press ENTER to continue running the testcase...")
175
176 @classmethod
177 def run_vpp(cls):
178 cmdline = cls.vpp_cmdline
179
180 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100181 gdbserver = '/usr/bin/gdbserver'
182 if not os.path.isfile(gdbserver) or \
183 not os.access(gdbserver, os.X_OK):
184 raise Exception("gdbserver binary '%s' does not exist or is "
185 "not executable" % gdbserver)
186
187 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200188 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
189
Klement Sekera931be3a2016-11-03 05:36:01 +0100190 try:
191 cls.vpp = subprocess.Popen(cmdline,
192 stdout=subprocess.PIPE,
193 stderr=subprocess.PIPE,
194 bufsize=1)
195 except Exception as e:
196 cls.logger.critical("Couldn't start vpp: %s" % e)
197 raise
198
Klement Sekera277b89c2016-10-28 13:20:27 +0200199 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100200
Damjan Marionf56b77a2016-10-03 19:44:57 +0200201 @classmethod
202 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200203 """
204 Perform class setup before running the testcase
205 Remove shared memory files, start vpp and connect the vpp-api
206 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100207 gc.collect() # run garbage collection first
Klement Sekera277b89c2016-10-28 13:20:27 +0200208 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200209 cls.tempdir = tempfile.mkdtemp(
210 prefix='vpp-unittest-' + cls.__name__ + '-')
Klement Sekera6c7440c2016-12-23 09:16:39 +0100211 file_handler = FileHandler("%s/log.txt" % cls.tempdir)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100212 file_handler.setFormatter(
213 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
214 datefmt="%H:%M:%S"))
Klement Sekera6c7440c2016-12-23 09:16:39 +0100215 file_handler.setLevel(DEBUG)
216 cls.logger.addHandler(file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200217 cls.shm_prefix = cls.tempdir.split("/")[-1]
218 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200219 cls.logger.info("Temporary dir is %s, shm prefix is %s",
220 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200221 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100222 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100223 cls._captures = []
224 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200225 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100226 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100227 cls.registry = VppObjectRegistry()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200228 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100229 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200230 print(double_line_delim)
231 # need to catch exceptions here because if we raise, then the cleanup
232 # doesn't get called and we might end with a zombie vpp
233 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200234 cls.run_vpp()
Klement Sekerae4504c62016-12-08 10:16:41 +0100235 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100236 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100237 cls.pump_thread_stop_flag = Event()
238 cls.pump_thread_wakeup_pipe = os.pipe()
239 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100240 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100241 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100242 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100243 if cls.step:
244 hook = StepHook(cls)
245 else:
246 hook = PollHook(cls)
247 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100248 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera085f5c02016-11-24 01:59:16 +0100249 hook.poll_vpp()
250 try:
251 cls.vapi.connect()
252 except:
253 if cls.debug_gdbserver:
254 print(colorize("You're running VPP inside gdbserver but "
255 "VPP-API connection failed, did you forget "
256 "to 'continue' VPP from within gdb?", RED))
257 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200258 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100259 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100260 try:
261 cls.quit()
262 except:
263 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100264 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200265
Damjan Marionf56b77a2016-10-03 19:44:57 +0200266 @classmethod
267 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200268 """
269 Disconnect vpp-api, kill vpp and cleanup shared memory files
270 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200271 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
272 cls.vpp.poll()
273 if cls.vpp.returncode is None:
274 print(double_line_delim)
275 print("VPP or GDB server is still running")
276 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100277 raw_input("When done debugging, press ENTER to kill the "
278 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200279
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100280 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
281 cls.pump_thread_stop_flag.set()
282 if hasattr(cls, 'pump_thread'):
283 cls.logger.debug("Waiting for pump thread to stop")
284 cls.pump_thread.join()
285 if hasattr(cls, 'vpp_stderr_reader_thread'):
286 cls.logger.debug("Waiting for stdderr pump to stop")
287 cls.vpp_stderr_reader_thread.join()
288
Klement Sekeraf62ae122016-10-11 11:47:09 +0200289 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100290 if hasattr(cls, 'vapi'):
291 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100292 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200293 cls.vpp.poll()
294 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100295 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200296 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100297 cls.logger.debug("Waiting for vpp to die")
298 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200299 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200300
Klement Sekerae4504c62016-12-08 10:16:41 +0100301 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200302 cls.logger.info(single_line_delim)
303 cls.logger.info('VPP output to stdout while running %s:',
304 cls.__name__)
305 cls.logger.info(single_line_delim)
306 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100307 vpp_output = "".join(cls.vpp_stdout_deque)
308 f.write(vpp_output)
309 cls.logger.info('\n%s', vpp_output)
310 cls.logger.info(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200311
Klement Sekerae4504c62016-12-08 10:16:41 +0100312 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200313 cls.logger.info(single_line_delim)
314 cls.logger.info('VPP output to stderr while running %s:',
315 cls.__name__)
316 cls.logger.info(single_line_delim)
317 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100318 vpp_output = "".join(cls.vpp_stderr_deque)
319 f.write(vpp_output)
320 cls.logger.info('\n%s', vpp_output)
Klement Sekera277b89c2016-10-28 13:20:27 +0200321 cls.logger.info(single_line_delim)
322
Damjan Marionf56b77a2016-10-03 19:44:57 +0200323 @classmethod
324 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200325 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200326 cls.quit()
327
Damjan Marionf56b77a2016-10-03 19:44:57 +0200328 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200329 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100330 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
331 (self.__class__.__name__, self._testMethodName,
332 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200333 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200334 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000335 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200336 self.logger.info(self.vapi.ppcli("show hardware"))
337 self.logger.info(self.vapi.ppcli("show error"))
338 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100339 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500340 # Save/Dump VPP api trace log
341 api_trace = "vpp_api_trace.%s.log" % self._testMethodName
342 tmp_api_trace = "/tmp/%s" % api_trace
343 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
344 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
345 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
346 vpp_api_trace_log))
347 os.rename(tmp_api_trace, vpp_api_trace_log)
348 self.logger.info(self.vapi.ppcli("api trace dump %s" %
349 vpp_api_trace_log))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200350
Damjan Marionf56b77a2016-10-03 19:44:57 +0200351 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200352 """ Clear trace before running each test"""
Klement Sekerab91017a2017-02-09 06:04:36 +0100353 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
354 (self.__class__.__name__, self._testMethodName,
355 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100356 if self.vpp_dead:
357 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100358 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100359 self.vpp_stdout_deque.append(
360 "--- test setUp() for %s.%s(%s) starts here ---\n" %
361 (self.__class__.__name__, self._testMethodName,
362 self._testMethodDoc))
363 self.vpp_stderr_deque.append(
364 "--- test setUp() for %s.%s(%s) starts here ---\n" %
365 (self.__class__.__name__, self._testMethodName,
366 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200367 self.vapi.cli("clear trace")
368 # store the test instance inside the test class - so that objects
369 # holding the class can access instance methods (like assertEqual)
370 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200371
Damjan Marionf56b77a2016-10-03 19:44:57 +0200372 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200373 def pg_enable_capture(cls, interfaces):
374 """
375 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200376
Klement Sekeraf62ae122016-10-11 11:47:09 +0200377 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200378
Klement Sekeraf62ae122016-10-11 11:47:09 +0200379 """
380 for i in interfaces:
381 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200382
Damjan Marionf56b77a2016-10-03 19:44:57 +0200383 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100384 def register_capture(cls, cap_name):
385 """ Register a capture in the testclass """
386 # add to the list of captures with current timestamp
387 cls._captures.append((time.time(), cap_name))
388 # filter out from zombies
389 cls._zombie_captures = [(stamp, name)
390 for (stamp, name) in cls._zombie_captures
391 if name != cap_name]
392
393 @classmethod
394 def pg_start(cls):
395 """ Remove any zombie captures and enable the packet generator """
396 # how long before capture is allowed to be deleted - otherwise vpp
397 # crashes - 100ms seems enough (this shouldn't be needed at all)
398 capture_ttl = 0.1
399 now = time.time()
400 for stamp, cap_name in cls._zombie_captures:
401 wait = stamp + capture_ttl - now
402 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100403 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100404 now = time.time()
405 cls.logger.debug("Removing zombie capture %s" % cap_name)
406 cls.vapi.cli('packet-generator delete %s' % cap_name)
407
Klement Sekeraf62ae122016-10-11 11:47:09 +0200408 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
409 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100410 cls._zombie_captures = cls._captures
411 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200412
Damjan Marionf56b77a2016-10-03 19:44:57 +0200413 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200414 def create_pg_interfaces(cls, interfaces):
415 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100416 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200417
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100418 :param interfaces: iterable indexes of the interfaces.
419 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200420
Klement Sekeraf62ae122016-10-11 11:47:09 +0200421 """
422 result = []
423 for i in interfaces:
424 intf = VppPGInterface(cls, i)
425 setattr(cls, intf.name, intf)
426 result.append(intf)
427 cls.pg_interfaces = result
428 return result
429
Matej Klotton0178d522016-11-04 11:11:44 +0100430 @classmethod
431 def create_loopback_interfaces(cls, interfaces):
432 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100433 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100434
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100435 :param interfaces: iterable indexes of the interfaces.
436 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100437 """
438 result = []
439 for i in interfaces:
440 intf = VppLoInterface(cls, i)
441 setattr(cls, intf.name, intf)
442 result.append(intf)
443 cls.lo_interfaces = result
444 return result
445
Damjan Marionf56b77a2016-10-03 19:44:57 +0200446 @staticmethod
447 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200448 """
449 Extend packet to given size by padding with spaces
450 NOTE: Currently works only when Raw layer is present.
451
452 :param packet: packet
453 :param size: target size
454
455 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200456 packet_len = len(packet) + 4
457 extend = size - packet_len
458 if extend > 0:
459 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200460
Klement Sekeradab231a2016-12-21 08:50:14 +0100461 @classmethod
462 def reset_packet_infos(cls):
463 """ Reset the list of packet info objects and packet counts to zero """
464 cls._packet_infos = {}
465 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200466
Klement Sekeradab231a2016-12-21 08:50:14 +0100467 @classmethod
468 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200469 """
470 Create packet info object containing the source and destination indexes
471 and add it to the testcase's packet info list
472
Klement Sekeradab231a2016-12-21 08:50:14 +0100473 :param VppInterface src_if: source interface
474 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200475
476 :returns: _PacketInfo object
477
478 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200479 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100480 info.index = len(cls._packet_infos)
481 info.src = src_if.sw_if_index
482 info.dst = dst_if.sw_if_index
483 if isinstance(dst_if, VppSubInterface):
484 dst_idx = dst_if.parent.sw_if_index
485 else:
486 dst_idx = dst_if.sw_if_index
487 if dst_idx in cls._packet_count_for_dst_if_idx:
488 cls._packet_count_for_dst_if_idx[dst_idx] += 1
489 else:
490 cls._packet_count_for_dst_if_idx[dst_idx] = 1
491 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200492 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493
Damjan Marionf56b77a2016-10-03 19:44:57 +0200494 @staticmethod
495 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200496 """
497 Convert _PacketInfo object to packet payload
498
499 :param info: _PacketInfo object
500
501 :returns: string containing serialized data from packet info
502 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200503 return "%d %d %d" % (info.index, info.src, info.dst)
504
Damjan Marionf56b77a2016-10-03 19:44:57 +0200505 @staticmethod
506 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200507 """
508 Convert packet payload to _PacketInfo object
509
510 :param payload: packet payload
511
512 :returns: _PacketInfo object containing de-serialized data from payload
513
514 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200515 numbers = payload.split()
516 info = _PacketInfo()
517 info.index = int(numbers[0])
518 info.src = int(numbers[1])
519 info.dst = int(numbers[2])
520 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200521
Damjan Marionf56b77a2016-10-03 19:44:57 +0200522 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200523 """
524 Iterate over the packet info list stored in the testcase
525 Start iteration with first element if info is None
526 Continue based on index in info if info is specified
527
528 :param info: info or None
529 :returns: next info in list or None if no more infos
530 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200531 if info is None:
532 next_index = 0
533 else:
534 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100535 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200536 return None
537 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100538 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200539
Klement Sekeraf62ae122016-10-11 11:47:09 +0200540 def get_next_packet_info_for_interface(self, src_index, info):
541 """
542 Search the packet info list for the next packet info with same source
543 interface index
544
545 :param src_index: source interface index to search for
546 :param info: packet info - where to start the search
547 :returns: packet info or None
548
549 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200550 while True:
551 info = self.get_next_packet_info(info)
552 if info is None:
553 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200554 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200555 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200556
Klement Sekeraf62ae122016-10-11 11:47:09 +0200557 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
558 """
559 Search the packet info list for the next packet info with same source
560 and destination interface indexes
561
562 :param src_index: source interface index to search for
563 :param dst_index: destination interface index to search for
564 :param info: packet info - where to start the search
565 :returns: packet info or None
566
567 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200568 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200569 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200570 if info is None:
571 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200572 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200573 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200574
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200575 def assert_equal(self, real_value, expected_value, name_or_class=None):
576 if name_or_class is None:
577 self.assertEqual(real_value, expected_value, msg)
578 return
579 try:
580 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
581 msg = msg % (getdoc(name_or_class).strip(),
582 real_value, str(name_or_class(real_value)),
583 expected_value, str(name_or_class(expected_value)))
584 except:
585 msg = "Invalid %s: %s does not match expected value %s" % (
586 name_or_class, real_value, expected_value)
587
588 self.assertEqual(real_value, expected_value, msg)
589
Klement Sekerab17dd962017-01-09 07:43:48 +0100590 def assert_in_range(self,
591 real_value,
592 expected_min,
593 expected_max,
594 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200595 if name is None:
596 msg = None
597 else:
598 msg = "Invalid %s: %s out of range <%s,%s>" % (
599 name, real_value, expected_min, expected_max)
600 self.assertTrue(expected_min <= real_value <= expected_max, msg)
601
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100602 @classmethod
603 def sleep(cls, timeout, remark=None):
604 if hasattr(cls, 'logger'):
605 cls.logger.debug("Sleeping for %ss (%s)" % (timeout, remark))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100606 time.sleep(timeout)
607
Damjan Marionf56b77a2016-10-03 19:44:57 +0200608
Damjan Marionf56b77a2016-10-03 19:44:57 +0200609class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200610 """
611 @property result_string
612 String variable to store the test case result string.
613 @property errors
614 List variable containing 2-tuples of TestCase instances and strings
615 holding formatted tracebacks. Each tuple represents a test which
616 raised an unexpected exception.
617 @property failures
618 List variable containing 2-tuples of TestCase instances and strings
619 holding formatted tracebacks. Each tuple represents a test where
620 a failure was explicitly signalled using the TestCase.assert*()
621 methods.
622 """
623
Damjan Marionf56b77a2016-10-03 19:44:57 +0200624 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200625 """
Klement Sekerada505f62017-01-04 12:58:53 +0100626 :param stream File descriptor to store where to report test results.
627 Set to the standard error stream by default.
628 :param descriptions Boolean variable to store information if to use
629 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200630 :param verbosity Integer variable to store required verbosity level.
631 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200632 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
633 self.stream = stream
634 self.descriptions = descriptions
635 self.verbosity = verbosity
636 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200637
Damjan Marionf56b77a2016-10-03 19:44:57 +0200638 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200639 """
640 Record a test succeeded result
641
642 :param test:
643
644 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100645 if hasattr(test, 'logger'):
646 test.logger.debug("--- addSuccess() %s.%s(%s) called"
647 % (test.__class__.__name__,
648 test._testMethodName,
649 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200650 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200651 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200652
Klement Sekeraf62ae122016-10-11 11:47:09 +0200653 def addSkip(self, test, reason):
654 """
655 Record a test skipped.
656
657 :param test:
658 :param reason:
659
660 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100661 if hasattr(test, 'logger'):
662 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
663 % (test.__class__.__name__,
664 test._testMethodName,
665 test._testMethodDoc,
666 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200667 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200668 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200669
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200671 """
672 Record a test failed result
673
674 :param test:
675 :param err: error message
676
677 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100678 if hasattr(test, 'logger'):
679 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
680 % (test.__class__.__name__,
681 test._testMethodName,
682 test._testMethodDoc, err))
683 test.logger.debug("formatted exception is:\n%s" %
684 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200685 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200686 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200687 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200688 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
689 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200690 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200691
Damjan Marionf56b77a2016-10-03 19:44:57 +0200692 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200693 """
694 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200695
Klement Sekeraf62ae122016-10-11 11:47:09 +0200696 :param test:
697 :param err: error message
698
699 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100700 if hasattr(test, 'logger'):
701 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
702 % (test.__class__.__name__,
703 test._testMethodName,
704 test._testMethodDoc, err))
705 test.logger.debug("formatted exception is:\n%s" %
706 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200707 unittest.TestResult.addError(self, test, err)
708 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200709 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200710 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
711 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200712 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200713
Damjan Marionf56b77a2016-10-03 19:44:57 +0200714 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200715 """
716 Get test description
717
718 :param test:
719 :returns: test description
720
721 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200722 # TODO: if none print warning not raise exception
723 short_description = test.shortDescription()
724 if self.descriptions and short_description:
725 return short_description
726 else:
727 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200728
Damjan Marionf56b77a2016-10-03 19:44:57 +0200729 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200730 """
731 Start a test
732
733 :param test:
734
735 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200736 unittest.TestResult.startTest(self, test)
737 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200738 self.stream.writeln(
739 "Starting " + self.getDescription(test) + " ...")
740 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200741
Damjan Marionf56b77a2016-10-03 19:44:57 +0200742 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200743 """
744 Stop a test
745
746 :param test:
747
748 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200749 unittest.TestResult.stopTest(self, test)
750 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200751 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100752 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100753 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200754 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200755 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100756 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100757 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200758
Damjan Marionf56b77a2016-10-03 19:44:57 +0200759 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200760 """
761 Print errors from running the test case
762 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200763 self.stream.writeln()
764 self.printErrorList('ERROR', self.errors)
765 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200766
Damjan Marionf56b77a2016-10-03 19:44:57 +0200767 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200768 """
769 Print error list to the output stream together with error type
770 and test case description.
771
772 :param flavour: error type
773 :param errors: iterable errors
774
775 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200776 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200777 self.stream.writeln(double_line_delim)
778 self.stream.writeln("%s: %s" %
779 (flavour, self.getDescription(test)))
780 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200781 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200782
783
Damjan Marionf56b77a2016-10-03 19:44:57 +0200784class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200785 """
Klement Sekera104543f2017-02-03 07:29:43 +0100786 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200787 """
788 @property
789 def resultclass(self):
790 """Class maintaining the results of the tests"""
791 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200792
Klement Sekera7a161da2017-01-17 13:42:48 +0100793 def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
794 failfast=False, buffer=False, resultclass=None):
795 # ignore stream setting here, use hard-coded stdout to be in sync
796 # with prints from VppTestCase methods ...
797 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
798 verbosity, failfast, buffer,
799 resultclass)
800
Klement Sekera104543f2017-02-03 07:29:43 +0100801 test_option = "TEST"
802
803 def parse_test_option(self):
804 try:
805 f = os.getenv(self.test_option)
806 except:
807 f = None
808 filter_file_name = None
809 filter_class_name = None
810 filter_func_name = None
811 if f:
812 if '.' in f:
813 parts = f.split('.')
814 if len(parts) > 3:
815 raise Exception("Unrecognized %s option: %s" %
816 (self.test_option, f))
817 if len(parts) > 2:
818 if parts[2] not in ('*', ''):
819 filter_func_name = parts[2]
820 if parts[1] not in ('*', ''):
821 filter_class_name = parts[1]
822 if parts[0] not in ('*', ''):
823 if parts[0].startswith('test_'):
824 filter_file_name = parts[0]
825 else:
826 filter_file_name = 'test_%s' % parts[0]
827 else:
828 if f.startswith('test_'):
829 filter_file_name = f
830 else:
831 filter_file_name = 'test_%s' % f
832 return filter_file_name, filter_class_name, filter_func_name
833
834 def filter_tests(self, tests, filter_file, filter_class, filter_func):
835 result = unittest.suite.TestSuite()
836 for t in tests:
837 if isinstance(t, unittest.suite.TestSuite):
838 # this is a bunch of tests, recursively filter...
839 x = self.filter_tests(t, filter_file, filter_class,
840 filter_func)
841 if x.countTestCases() > 0:
842 result.addTest(x)
843 elif isinstance(t, unittest.TestCase):
844 # this is a single test
845 parts = t.id().split('.')
846 # t.id() for common cases like this:
847 # test_classifier.TestClassifier.test_acl_ip
848 # apply filtering only if it is so
849 if len(parts) == 3:
850 if filter_file and filter_file != parts[0]:
851 continue
852 if filter_class and filter_class != parts[1]:
853 continue
854 if filter_func and filter_func != parts[2]:
855 continue
856 result.addTest(t)
857 else:
858 # unexpected object, don't touch it
859 result.addTest(t)
860 return result
861
Damjan Marionf56b77a2016-10-03 19:44:57 +0200862 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200863 """
864 Run the tests
865
866 :param test:
867
868 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100869 gc.disable() # disable garbage collection, we'll do that manually
Klement Sekeraf62ae122016-10-11 11:47:09 +0200870 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +0100871 filter_file, filter_class, filter_func = self.parse_test_option()
872 print("Active filters: file=%s, class=%s, function=%s" % (
873 filter_file, filter_class, filter_func))
874 filtered = self.filter_tests(test, filter_file, filter_class,
875 filter_func)
876 print("%s out of %s tests match specified filters" % (
877 filtered.countTestCases(), test.countTestCases()))
878 return super(VppTestRunner, self).run(filtered)