blob: 02ffb7add4f172362ccfd71824ef77797ea18c68 [file] [log] [blame]
Damjan Marionf56b77a2016-10-03 19:44:57 +02001#!/usr/bin/env python
Damjan Marionf56b77a2016-10-03 19:44:57 +02002
Klement Sekeraf62ae122016-10-11 11:47:09 +02003from abc import *
Damjan Marionf56b77a2016-10-03 19:44:57 +02004import os
Peter Ginchev749294d2016-10-25 13:22:15 +03005import sys
Damjan Marionf56b77a2016-10-03 19:44:57 +02006import subprocess
7import unittest
Klement Sekeraf62ae122016-10-11 11:47:09 +02008import tempfile
9import resource
10from time import sleep
Damjan Marionf56b77a2016-10-03 19:44:57 +020011from inspect import getdoc
Klement Sekeraf62ae122016-10-11 11:47:09 +020012from hook import PollHook
13from vpp_pg_interface import VppPGInterface
14from vpp_papi_provider import VppPapiProvider
Damjan Marionf56b77a2016-10-03 19:44:57 +020015
Damjan Marionf56b77a2016-10-03 19:44:57 +020016from scapy.packet import Raw
17
Klement Sekeraf62ae122016-10-11 11:47:09 +020018from logging import *
19
20"""
21 Test framework module.
22
23 The module provides a set of tools for constructing and running tests and
24 representing the results.
25"""
26
27handler = StreamHandler(sys.stdout)
28getLogger().addHandler(handler)
29try:
30 verbose = int(os.getenv("V", 0))
31except:
32 verbose = 0
33# 40 = ERROR, 30 = WARNING, 20 = INFO, 10 = DEBUG, 0 = NOTSET (all messages)
34getLogger().setLevel(40 - 10 * verbose)
35getLogger("scapy.runtime").addHandler(handler)
36getLogger("scapy.runtime").setLevel(ERROR)
37
38# Static variables to store color formatting strings.
Damjan Marionf56b77a2016-10-03 19:44:57 +020039#
Klement Sekeraf62ae122016-10-11 11:47:09 +020040# These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
41# the color of the text to be printed in the terminal. Variable COLOR_RESET
42# is used to revert the text color to the default one.
Peter Ginchev749294d2016-10-25 13:22:15 +030043if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
44 RED = '\033[91m'
45 GREEN = '\033[92m'
46 YELLOW = '\033[93m'
47 LPURPLE = '\033[94m'
Klement Sekeraf62ae122016-10-11 11:47:09 +020048 COLOR_RESET = '\033[0m'
Peter Ginchev749294d2016-10-25 13:22:15 +030049else:
50 RED = ''
51 GREEN = ''
52 YELLOW = ''
53 LPURPLE = ''
Klement Sekeraf62ae122016-10-11 11:47:09 +020054 COLOR_RESET = ''
Peter Ginchev749294d2016-10-25 13:22:15 +030055
Damjan Marionf56b77a2016-10-03 19:44:57 +020056
Klement Sekeraf62ae122016-10-11 11:47:09 +020057""" @var formatting delimiter consisting of '=' characters """
58double_line_delim = '=' * 70
59""" @var formatting delimiter consisting of '-' characters """
60single_line_delim = '-' * 70
61
62
Damjan Marionf56b77a2016-10-03 19:44:57 +020063class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020064 """Private class to create packet info object.
65
66 Help process information about the next packet.
67 Set variables to default values.
68 @property index
69 Integer variable to store the index of the packet.
70 @property src
71 Integer variable to store the index of the source packet generator
72 interface of the packet.
73 @property dst
74 Integer variable to store the index of the destination packet generator
75 interface of the packet.
76 @property data
77 Object variable to store the copy of the former packet.
78
79
80 """
Damjan Marionf56b77a2016-10-03 19:44:57 +020081 index = -1
82 src = -1
83 dst = -1
84 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020085
Klement Sekeraf62ae122016-10-11 11:47:09 +020086
Damjan Marionf56b77a2016-10-03 19:44:57 +020087class VppTestCase(unittest.TestCase):
Klement Sekeraf62ae122016-10-11 11:47:09 +020088 """
89 Subclass of the python unittest.TestCase class.
Damjan Marionf56b77a2016-10-03 19:44:57 +020090
Klement Sekeraf62ae122016-10-11 11:47:09 +020091 This subclass is a base class for test cases that are implemented as classes
92 It provides methods to create and run test case.
93
94 """
95
96 @property
97 def packet_infos(self):
98 """List of packet infos"""
99 return self._packet_infos
100
101 @packet_infos.setter
102 def packet_infos(self, value):
103 self._packet_infos = value
104
105 @classmethod
106 def instance(cls):
107 """Return the instance of this testcase"""
108 return cls.test_instance
109
Damjan Marionf56b77a2016-10-03 19:44:57 +0200110 @classmethod
111 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200112 """ Set-up the test case class based on environment variables """
113 try:
114 cls.interactive = True if int(os.getenv("I")) > 0 else False
115 except:
116 cls.interactive = False
117 if cls.interactive and resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
118 # give a heads up if this is actually useless
119 critical("WARNING: core size limit is set 0, core files will NOT "
120 "be created")
Damjan Marionf56b77a2016-10-03 19:44:57 +0200121 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100122 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekeraf62ae122016-10-11 11:47:09 +0200123 cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon",
124 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100125 if cls.plugin_path is not None:
126 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekeraf62ae122016-10-11 11:47:09 +0200127 info("vpp_cmdline: %s" % cls.vpp_cmdline)
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100128
Damjan Marionf56b77a2016-10-03 19:44:57 +0200129 @classmethod
130 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200131 """
132 Perform class setup before running the testcase
133 Remove shared memory files, start vpp and connect the vpp-api
134 """
135 cls.tempdir = tempfile.mkdtemp(
136 prefix='vpp-unittest-' + cls.__name__ + '-')
137 cls.shm_prefix = cls.tempdir.split("/")[-1]
138 os.chdir(cls.tempdir)
139 info("Temporary dir is %s, shm prefix is %s",
140 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200141 cls.setUpConstants()
142 cls.pg_streams = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200143 cls.packet_infos = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200144 cls.verbose = 0
145 print(double_line_delim)
146 print(YELLOW + getdoc(cls) + COLOR_RESET)
147 print(double_line_delim)
148 # need to catch exceptions here because if we raise, then the cleanup
149 # doesn't get called and we might end with a zombie vpp
150 try:
151 cls.vpp = subprocess.Popen(cls.vpp_cmdline, stderr=subprocess.PIPE)
152 debug("Spawned VPP with PID: %d" % cls.vpp.pid)
153 cls.vpp_dead = False
154 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
155 cls.vapi.register_hook(PollHook(cls))
156 cls.vapi.connect()
157 except:
158 cls.vpp.terminate()
159 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200160
Damjan Marionf56b77a2016-10-03 19:44:57 +0200161 @classmethod
162 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200163 """
164 Disconnect vpp-api, kill vpp and cleanup shared memory files
165 """
166 if hasattr(cls, 'vpp'):
167 cls.vapi.disconnect()
168 cls.vpp.poll()
169 if cls.vpp.returncode is None:
170 cls.vpp.terminate()
171 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200172
Damjan Marionf56b77a2016-10-03 19:44:57 +0200173 @classmethod
174 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200175 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200176 cls.quit()
177
Damjan Marionf56b77a2016-10-03 19:44:57 +0200178 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200179 """ Show various debug prints after each test """
180 if not self.vpp_dead:
181 info(self.vapi.cli("show int"))
182 info(self.vapi.cli("show trace"))
183 info(self.vapi.cli("show hardware"))
184 info(self.vapi.cli("show error"))
185 info(self.vapi.cli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200186
Damjan Marionf56b77a2016-10-03 19:44:57 +0200187 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200188 """ Clear trace before running each test"""
189 self.vapi.cli("clear trace")
190 # store the test instance inside the test class - so that objects
191 # holding the class can access instance methods (like assertEqual)
192 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200193
Damjan Marionf56b77a2016-10-03 19:44:57 +0200194 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200195 def pg_enable_capture(cls, interfaces):
196 """
197 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200198
Klement Sekeraf62ae122016-10-11 11:47:09 +0200199 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200200
Klement Sekeraf62ae122016-10-11 11:47:09 +0200201 """
202 for i in interfaces:
203 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200204
Damjan Marionf56b77a2016-10-03 19:44:57 +0200205 @classmethod
206 def pg_start(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200207 """
208 Enable the packet-generator and send all prepared packet streams
209 Remove the packet streams afterwards
210 """
211 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
212 cls.vapi.cli('packet-generator enable')
213 sleep(1) # give VPP some time to process the packets
Damjan Marionf56b77a2016-10-03 19:44:57 +0200214 for stream in cls.pg_streams:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200215 cls.vapi.cli('packet-generator delete %s' % stream)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200216 cls.pg_streams = []
217
Damjan Marionf56b77a2016-10-03 19:44:57 +0200218 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200219 def create_pg_interfaces(cls, interfaces):
220 """
221 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200222
Klement Sekeraf62ae122016-10-11 11:47:09 +0200223 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200224
Klement Sekeraf62ae122016-10-11 11:47:09 +0200225 """
226 result = []
227 for i in interfaces:
228 intf = VppPGInterface(cls, i)
229 setattr(cls, intf.name, intf)
230 result.append(intf)
231 cls.pg_interfaces = result
232 return result
233
Damjan Marionf56b77a2016-10-03 19:44:57 +0200234 @staticmethod
235 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200236 """
237 Extend packet to given size by padding with spaces
238 NOTE: Currently works only when Raw layer is present.
239
240 :param packet: packet
241 :param size: target size
242
243 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200244 packet_len = len(packet) + 4
245 extend = size - packet_len
246 if extend > 0:
247 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200248
Damjan Marionf56b77a2016-10-03 19:44:57 +0200249 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200250 """
251 Add packet info to the testcase's packet info list
252
253 :param info: packet info
254
255 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200256 info.index = len(self.packet_infos)
257 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200258
Klement Sekeraf62ae122016-10-11 11:47:09 +0200259 def create_packet_info(self, src_pg_index, dst_pg_index):
260 """
261 Create packet info object containing the source and destination indexes
262 and add it to the testcase's packet info list
263
264 :param src_pg_index: source packet-generator index
265 :param dst_pg_index: destination packet-generator index
266
267 :returns: _PacketInfo object
268
269 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200270 info = _PacketInfo()
271 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200272 info.src = src_pg_index
273 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200274 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200275
Damjan Marionf56b77a2016-10-03 19:44:57 +0200276 @staticmethod
277 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200278 """
279 Convert _PacketInfo object to packet payload
280
281 :param info: _PacketInfo object
282
283 :returns: string containing serialized data from packet info
284 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200285 return "%d %d %d" % (info.index, info.src, info.dst)
286
Damjan Marionf56b77a2016-10-03 19:44:57 +0200287 @staticmethod
288 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200289 """
290 Convert packet payload to _PacketInfo object
291
292 :param payload: packet payload
293
294 :returns: _PacketInfo object containing de-serialized data from payload
295
296 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200297 numbers = payload.split()
298 info = _PacketInfo()
299 info.index = int(numbers[0])
300 info.src = int(numbers[1])
301 info.dst = int(numbers[2])
302 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200303
Damjan Marionf56b77a2016-10-03 19:44:57 +0200304 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200305 """
306 Iterate over the packet info list stored in the testcase
307 Start iteration with first element if info is None
308 Continue based on index in info if info is specified
309
310 :param info: info or None
311 :returns: next info in list or None if no more infos
312 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200313 if info is None:
314 next_index = 0
315 else:
316 next_index = info.index + 1
317 if next_index == len(self.packet_infos):
318 return None
319 else:
320 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200321
Klement Sekeraf62ae122016-10-11 11:47:09 +0200322 def get_next_packet_info_for_interface(self, src_index, info):
323 """
324 Search the packet info list for the next packet info with same source
325 interface index
326
327 :param src_index: source interface index to search for
328 :param info: packet info - where to start the search
329 :returns: packet info or None
330
331 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200332 while True:
333 info = self.get_next_packet_info(info)
334 if info is None:
335 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200336 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200337 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200338
Klement Sekeraf62ae122016-10-11 11:47:09 +0200339 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
340 """
341 Search the packet info list for the next packet info with same source
342 and destination interface indexes
343
344 :param src_index: source interface index to search for
345 :param dst_index: destination interface index to search for
346 :param info: packet info - where to start the search
347 :returns: packet info or None
348
349 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200350 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200351 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200352 if info is None:
353 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200354 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200355 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200356
357
Damjan Marionf56b77a2016-10-03 19:44:57 +0200358class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200359 """
360 @property result_string
361 String variable to store the test case result string.
362 @property errors
363 List variable containing 2-tuples of TestCase instances and strings
364 holding formatted tracebacks. Each tuple represents a test which
365 raised an unexpected exception.
366 @property failures
367 List variable containing 2-tuples of TestCase instances and strings
368 holding formatted tracebacks. Each tuple represents a test where
369 a failure was explicitly signalled using the TestCase.assert*()
370 methods.
371 """
372
Damjan Marionf56b77a2016-10-03 19:44:57 +0200373 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200374 """
375 :param stream File descriptor to store where to report test results. Set
376 to the standard error stream by default.
377 :param descriptions Boolean variable to store information if to use test
378 case descriptions.
379 :param verbosity Integer variable to store required verbosity level.
380 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200381 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
382 self.stream = stream
383 self.descriptions = descriptions
384 self.verbosity = verbosity
385 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200386
Damjan Marionf56b77a2016-10-03 19:44:57 +0200387 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200388 """
389 Record a test succeeded result
390
391 :param test:
392
393 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200394 unittest.TestResult.addSuccess(self, test)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200395 self.result_string = GREEN + "OK" + COLOR_RESET
Damjan Marionf56b77a2016-10-03 19:44:57 +0200396
Klement Sekeraf62ae122016-10-11 11:47:09 +0200397 def addSkip(self, test, reason):
398 """
399 Record a test skipped.
400
401 :param test:
402 :param reason:
403
404 """
405 unittest.TestResult.addSkip(self, test, reason)
406 self.result_string = YELLOW + "SKIP" + COLOR_RESET
407
Damjan Marionf56b77a2016-10-03 19:44:57 +0200408 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200409 """
410 Record a test failed result
411
412 :param test:
413 :param err: error message
414
415 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200416 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200417 if hasattr(test, 'tempdir'):
418 self.result_string = RED + "FAIL" + COLOR_RESET + \
419 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
420 else:
421 self.result_string = RED + "FAIL" + COLOR_RESET + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200422
Damjan Marionf56b77a2016-10-03 19:44:57 +0200423 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200424 """
425 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200426
Klement Sekeraf62ae122016-10-11 11:47:09 +0200427 :param test:
428 :param err: error message
429
430 """
431 unittest.TestResult.addError(self, test, err)
432 if hasattr(test, 'tempdir'):
433 self.result_string = RED + "ERROR" + COLOR_RESET + \
434 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
435 else:
436 self.result_string = RED + "ERROR" + COLOR_RESET + ' [no temp dir]'
437
Damjan Marionf56b77a2016-10-03 19:44:57 +0200438 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200439 """
440 Get test description
441
442 :param test:
443 :returns: test description
444
445 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200446 # TODO: if none print warning not raise exception
447 short_description = test.shortDescription()
448 if self.descriptions and short_description:
449 return short_description
450 else:
451 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200452
Damjan Marionf56b77a2016-10-03 19:44:57 +0200453 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200454 """
455 Start a test
456
457 :param test:
458
459 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200460 unittest.TestResult.startTest(self, test)
461 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200462 self.stream.writeln(
463 "Starting " + self.getDescription(test) + " ...")
464 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200465
Damjan Marionf56b77a2016-10-03 19:44:57 +0200466 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200467 """
468 Stop a test
469
470 :param test:
471
472 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200473 unittest.TestResult.stopTest(self, test)
474 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200475 self.stream.writeln(single_line_delim)
476 self.stream.writeln("%-60s%s" %
477 (self.getDescription(test), self.result_string))
478 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200479 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200480 self.stream.writeln("%-60s%s" %
481 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200482
Damjan Marionf56b77a2016-10-03 19:44:57 +0200483 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200484 """
485 Print errors from running the test case
486 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200487 self.stream.writeln()
488 self.printErrorList('ERROR', self.errors)
489 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200490
Damjan Marionf56b77a2016-10-03 19:44:57 +0200491 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200492 """
493 Print error list to the output stream together with error type
494 and test case description.
495
496 :param flavour: error type
497 :param errors: iterable errors
498
499 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200500 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200501 self.stream.writeln(double_line_delim)
502 self.stream.writeln("%s: %s" %
503 (flavour, self.getDescription(test)))
504 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200505 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200506
507
Damjan Marionf56b77a2016-10-03 19:44:57 +0200508class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200509 """
510 A basic test runner implementation which prints results on standard error.
511 """
512 @property
513 def resultclass(self):
514 """Class maintaining the results of the tests"""
515 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200516
Damjan Marionf56b77a2016-10-03 19:44:57 +0200517 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200518 """
519 Run the tests
520
521 :param test:
522
523 """
524 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200525 return super(VppTestRunner, self).run(test)