blob: 315556a8833ec8dbecdf0fdd18499bd75b30f3ee [file] [log] [blame]
Damjan Marionf56b77a2016-10-03 19:44:57 +02001#!/usr/bin/env python
Damjan Marionf56b77a2016-10-03 19:44:57 +02002
Damjan Marionf56b77a2016-10-03 19:44:57 +02003import subprocess
4import unittest
Klement Sekeraf62ae122016-10-11 11:47:09 +02005import tempfile
Klement Sekera277b89c2016-10-28 13:20:27 +02006import time
Klement Sekeraf62ae122016-10-11 11:47:09 +02007import resource
8from time import sleep
Klement Sekera277b89c2016-10-28 13:20:27 +02009from Queue import Queue
10from threading import Thread
Damjan Marionf56b77a2016-10-03 19:44:57 +020011from inspect import getdoc
Klement Sekera277b89c2016-10-28 13:20:27 +020012from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020013from vpp_pg_interface import VppPGInterface
Matej Klotton0178d522016-11-04 11:11:44 +010014from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020015from vpp_papi_provider import VppPapiProvider
Damjan Marionf56b77a2016-10-03 19:44:57 +020016from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020017from log import *
Klement Sekeraf62ae122016-10-11 11:47:09 +020018
19"""
20 Test framework module.
21
22 The module provides a set of tools for constructing and running tests and
23 representing the results.
24"""
25
Klement Sekeraf62ae122016-10-11 11:47:09 +020026
Damjan Marionf56b77a2016-10-03 19:44:57 +020027class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020028 """Private class to create packet info object.
29
30 Help process information about the next packet.
31 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020032 """
Matej Klotton86d87c42016-11-11 11:38:55 +010033 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020034 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010035 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020036 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010037 #: Store the index of the destination packet generator interface
38 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020039 dst = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010040 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020041 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020042
Klement Sekeraf62ae122016-10-11 11:47:09 +020043
Klement Sekera277b89c2016-10-28 13:20:27 +020044def pump_output(out, queue):
45 for line in iter(out.readline, b''):
46 queue.put(line)
47
48
Damjan Marionf56b77a2016-10-03 19:44:57 +020049class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010050 """This subclass is a base class for VPP test cases that are implemented as
51 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020052 """
53
54 @property
55 def packet_infos(self):
56 """List of packet infos"""
57 return self._packet_infos
58
59 @packet_infos.setter
60 def packet_infos(self, value):
61 self._packet_infos = value
62
63 @classmethod
64 def instance(cls):
65 """Return the instance of this testcase"""
66 return cls.test_instance
67
Damjan Marionf56b77a2016-10-03 19:44:57 +020068 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +020069 def set_debug_flags(cls, d):
70 cls.debug_core = False
71 cls.debug_gdb = False
72 cls.debug_gdbserver = False
73 if d is None:
74 return
75 dl = d.lower()
76 if dl == "core":
77 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
78 # give a heads up if this is actually useless
79 cls.logger.critical("WARNING: core size limit is set 0, core "
80 "files will NOT be created")
81 cls.debug_core = True
82 elif dl == "gdb":
83 cls.debug_gdb = True
84 elif dl == "gdbserver":
85 cls.debug_gdbserver = True
86 else:
87 raise Exception("Unrecognized DEBUG option: '%s'" % d)
88
89 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +020090 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +020091 """ Set-up the test case class based on environment variables """
92 try:
Klement Sekera277b89c2016-10-28 13:20:27 +020093 s = os.getenv("STEP")
94 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +020095 except:
Klement Sekera277b89c2016-10-28 13:20:27 +020096 cls.step = False
97 try:
98 d = os.getenv("DEBUG")
99 except:
100 d = None
101 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200102 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100103 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100104 debug_cli = ""
105 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
106 debug_cli = "cli-listen localhost:5002"
107 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200108 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100109 if cls.plugin_path is not None:
110 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200111 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
112
113 @classmethod
114 def wait_for_enter(cls):
115 if cls.debug_gdbserver:
116 print(double_line_delim)
117 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
118 elif cls.debug_gdb:
119 print(double_line_delim)
120 print("Spawned VPP with PID: %d" % cls.vpp.pid)
121 else:
122 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
123 return
124 print(single_line_delim)
125 print("You can debug the VPP using e.g.:")
126 if cls.debug_gdbserver:
127 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
128 print("Now is the time to attach a gdb by running the above "
129 "command, set up breakpoints etc. and then resume VPP from "
130 "within gdb by issuing the 'continue' command")
131 elif cls.debug_gdb:
132 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
133 print("Now is the time to attach a gdb by running the above "
134 "command and set up breakpoints etc.")
135 print(single_line_delim)
136 raw_input("Press ENTER to continue running the testcase...")
137
138 @classmethod
139 def run_vpp(cls):
140 cmdline = cls.vpp_cmdline
141
142 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100143 gdbserver = '/usr/bin/gdbserver'
144 if not os.path.isfile(gdbserver) or \
145 not os.access(gdbserver, os.X_OK):
146 raise Exception("gdbserver binary '%s' does not exist or is "
147 "not executable" % gdbserver)
148
149 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200150 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
151
Klement Sekera931be3a2016-11-03 05:36:01 +0100152 try:
153 cls.vpp = subprocess.Popen(cmdline,
154 stdout=subprocess.PIPE,
155 stderr=subprocess.PIPE,
156 bufsize=1)
157 except Exception as e:
158 cls.logger.critical("Couldn't start vpp: %s" % e)
159 raise
160
Klement Sekera277b89c2016-10-28 13:20:27 +0200161 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100162
Damjan Marionf56b77a2016-10-03 19:44:57 +0200163 @classmethod
164 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200165 """
166 Perform class setup before running the testcase
167 Remove shared memory files, start vpp and connect the vpp-api
168 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200169 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200170 cls.tempdir = tempfile.mkdtemp(
171 prefix='vpp-unittest-' + cls.__name__ + '-')
172 cls.shm_prefix = cls.tempdir.split("/")[-1]
173 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200174 cls.logger.info("Temporary dir is %s, shm prefix is %s",
175 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200176 cls.setUpConstants()
177 cls.pg_streams = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200178 cls.packet_infos = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200179 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100180 cls.vpp_dead = False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200181 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100182 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200183 print(double_line_delim)
184 # need to catch exceptions here because if we raise, then the cleanup
185 # doesn't get called and we might end with a zombie vpp
186 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200187 cls.run_vpp()
Klement Sekera277b89c2016-10-28 13:20:27 +0200188 cls.vpp_stdout_queue = Queue()
189 cls.vpp_stdout_reader_thread = Thread(
190 target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
191 cls.vpp_stdout_reader_thread.start()
192 cls.vpp_stderr_queue = Queue()
193 cls.vpp_stderr_reader_thread = Thread(
194 target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
195 cls.vpp_stderr_reader_thread.start()
Klement Sekera085f5c02016-11-24 01:59:16 +0100196 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
197 if cls.step:
198 hook = StepHook(cls)
199 else:
200 hook = PollHook(cls)
201 cls.vapi.register_hook(hook)
202 time.sleep(0.1)
203 hook.poll_vpp()
204 try:
205 cls.vapi.connect()
206 except:
207 if cls.debug_gdbserver:
208 print(colorize("You're running VPP inside gdbserver but "
209 "VPP-API connection failed, did you forget "
210 "to 'continue' VPP from within gdb?", RED))
211 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200212 except:
Klement Sekera085f5c02016-11-24 01:59:16 +0100213 try:
214 cls.quit()
215 except:
216 pass
Klement Sekera277b89c2016-10-28 13:20:27 +0200217 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200218
Damjan Marionf56b77a2016-10-03 19:44:57 +0200219 @classmethod
220 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200221 """
222 Disconnect vpp-api, kill vpp and cleanup shared memory files
223 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200224 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
225 cls.vpp.poll()
226 if cls.vpp.returncode is None:
227 print(double_line_delim)
228 print("VPP or GDB server is still running")
229 print(single_line_delim)
230 raw_input("When done debugging, press ENTER to kill the process"
231 " and finish running the testcase...")
232
Klement Sekeraf62ae122016-10-11 11:47:09 +0200233 if hasattr(cls, 'vpp'):
234 cls.vapi.disconnect()
235 cls.vpp.poll()
236 if cls.vpp.returncode is None:
237 cls.vpp.terminate()
238 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200239
Klement Sekera277b89c2016-10-28 13:20:27 +0200240 if hasattr(cls, 'vpp_stdout_queue'):
241 cls.logger.info(single_line_delim)
242 cls.logger.info('VPP output to stdout while running %s:',
243 cls.__name__)
244 cls.logger.info(single_line_delim)
245 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
246 while not cls.vpp_stdout_queue.empty():
247 line = cls.vpp_stdout_queue.get_nowait()
248 f.write(line)
249 cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
250
251 if hasattr(cls, 'vpp_stderr_queue'):
252 cls.logger.info(single_line_delim)
253 cls.logger.info('VPP output to stderr while running %s:',
254 cls.__name__)
255 cls.logger.info(single_line_delim)
256 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
257 while not cls.vpp_stderr_queue.empty():
258 line = cls.vpp_stderr_queue.get_nowait()
259 f.write(line)
260 cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
261 cls.logger.info(single_line_delim)
262
Damjan Marionf56b77a2016-10-03 19:44:57 +0200263 @classmethod
264 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200265 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200266 cls.quit()
267
Damjan Marionf56b77a2016-10-03 19:44:57 +0200268 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200269 """ Show various debug prints after each test """
270 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200271 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000272 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200273 self.logger.info(self.vapi.ppcli("show hardware"))
274 self.logger.info(self.vapi.ppcli("show error"))
275 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200276
Damjan Marionf56b77a2016-10-03 19:44:57 +0200277 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200278 """ Clear trace before running each test"""
279 self.vapi.cli("clear trace")
280 # store the test instance inside the test class - so that objects
281 # holding the class can access instance methods (like assertEqual)
282 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200283
Damjan Marionf56b77a2016-10-03 19:44:57 +0200284 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200285 def pg_enable_capture(cls, interfaces):
286 """
287 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200288
Klement Sekeraf62ae122016-10-11 11:47:09 +0200289 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200290
Klement Sekeraf62ae122016-10-11 11:47:09 +0200291 """
292 for i in interfaces:
293 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200294
Damjan Marionf56b77a2016-10-03 19:44:57 +0200295 @classmethod
296 def pg_start(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200297 """
298 Enable the packet-generator and send all prepared packet streams
299 Remove the packet streams afterwards
300 """
301 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
302 cls.vapi.cli('packet-generator enable')
303 sleep(1) # give VPP some time to process the packets
Damjan Marionf56b77a2016-10-03 19:44:57 +0200304 for stream in cls.pg_streams:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200305 cls.vapi.cli('packet-generator delete %s' % stream)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200306 cls.pg_streams = []
307
Damjan Marionf56b77a2016-10-03 19:44:57 +0200308 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200309 def create_pg_interfaces(cls, interfaces):
310 """
311 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200312
Klement Sekeraf62ae122016-10-11 11:47:09 +0200313 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200314
Klement Sekeraf62ae122016-10-11 11:47:09 +0200315 """
316 result = []
317 for i in interfaces:
318 intf = VppPGInterface(cls, i)
319 setattr(cls, intf.name, intf)
320 result.append(intf)
321 cls.pg_interfaces = result
322 return result
323
Matej Klotton0178d522016-11-04 11:11:44 +0100324 @classmethod
325 def create_loopback_interfaces(cls, interfaces):
326 """
327 Create loopback interfaces
328
329 :param interfaces: iterable indexes of the interfaces
330
331 """
332 result = []
333 for i in interfaces:
334 intf = VppLoInterface(cls, i)
335 setattr(cls, intf.name, intf)
336 result.append(intf)
337 cls.lo_interfaces = result
338 return result
339
Damjan Marionf56b77a2016-10-03 19:44:57 +0200340 @staticmethod
341 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200342 """
343 Extend packet to given size by padding with spaces
344 NOTE: Currently works only when Raw layer is present.
345
346 :param packet: packet
347 :param size: target size
348
349 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200350 packet_len = len(packet) + 4
351 extend = size - packet_len
352 if extend > 0:
353 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200354
Damjan Marionf56b77a2016-10-03 19:44:57 +0200355 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200356 """
357 Add packet info to the testcase's packet info list
358
359 :param info: packet info
360
361 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200362 info.index = len(self.packet_infos)
363 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200364
Klement Sekeraf62ae122016-10-11 11:47:09 +0200365 def create_packet_info(self, src_pg_index, dst_pg_index):
366 """
367 Create packet info object containing the source and destination indexes
368 and add it to the testcase's packet info list
369
370 :param src_pg_index: source packet-generator index
371 :param dst_pg_index: destination packet-generator index
372
373 :returns: _PacketInfo object
374
375 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200376 info = _PacketInfo()
377 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200378 info.src = src_pg_index
379 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200380 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200381
Damjan Marionf56b77a2016-10-03 19:44:57 +0200382 @staticmethod
383 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200384 """
385 Convert _PacketInfo object to packet payload
386
387 :param info: _PacketInfo object
388
389 :returns: string containing serialized data from packet info
390 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200391 return "%d %d %d" % (info.index, info.src, info.dst)
392
Damjan Marionf56b77a2016-10-03 19:44:57 +0200393 @staticmethod
394 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200395 """
396 Convert packet payload to _PacketInfo object
397
398 :param payload: packet payload
399
400 :returns: _PacketInfo object containing de-serialized data from payload
401
402 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200403 numbers = payload.split()
404 info = _PacketInfo()
405 info.index = int(numbers[0])
406 info.src = int(numbers[1])
407 info.dst = int(numbers[2])
408 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200409
Damjan Marionf56b77a2016-10-03 19:44:57 +0200410 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200411 """
412 Iterate over the packet info list stored in the testcase
413 Start iteration with first element if info is None
414 Continue based on index in info if info is specified
415
416 :param info: info or None
417 :returns: next info in list or None if no more infos
418 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200419 if info is None:
420 next_index = 0
421 else:
422 next_index = info.index + 1
423 if next_index == len(self.packet_infos):
424 return None
425 else:
426 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200427
Klement Sekeraf62ae122016-10-11 11:47:09 +0200428 def get_next_packet_info_for_interface(self, src_index, info):
429 """
430 Search the packet info list for the next packet info with same source
431 interface index
432
433 :param src_index: source interface index to search for
434 :param info: packet info - where to start the search
435 :returns: packet info or None
436
437 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200438 while True:
439 info = self.get_next_packet_info(info)
440 if info is None:
441 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200442 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200443 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200444
Klement Sekeraf62ae122016-10-11 11:47:09 +0200445 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
446 """
447 Search the packet info list for the next packet info with same source
448 and destination interface indexes
449
450 :param src_index: source interface index to search for
451 :param dst_index: destination interface index to search for
452 :param info: packet info - where to start the search
453 :returns: packet info or None
454
455 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200456 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200457 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200458 if info is None:
459 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200460 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200461 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200462
463
Damjan Marionf56b77a2016-10-03 19:44:57 +0200464class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200465 """
466 @property result_string
467 String variable to store the test case result string.
468 @property errors
469 List variable containing 2-tuples of TestCase instances and strings
470 holding formatted tracebacks. Each tuple represents a test which
471 raised an unexpected exception.
472 @property failures
473 List variable containing 2-tuples of TestCase instances and strings
474 holding formatted tracebacks. Each tuple represents a test where
475 a failure was explicitly signalled using the TestCase.assert*()
476 methods.
477 """
478
Damjan Marionf56b77a2016-10-03 19:44:57 +0200479 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200480 """
481 :param stream File descriptor to store where to report test results. Set
482 to the standard error stream by default.
483 :param descriptions Boolean variable to store information if to use test
484 case descriptions.
485 :param verbosity Integer variable to store required verbosity level.
486 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200487 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
488 self.stream = stream
489 self.descriptions = descriptions
490 self.verbosity = verbosity
491 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200492
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200494 """
495 Record a test succeeded result
496
497 :param test:
498
499 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200500 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200501 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200502
Klement Sekeraf62ae122016-10-11 11:47:09 +0200503 def addSkip(self, test, reason):
504 """
505 Record a test skipped.
506
507 :param test:
508 :param reason:
509
510 """
511 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200512 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200513
Damjan Marionf56b77a2016-10-03 19:44:57 +0200514 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200515 """
516 Record a test failed result
517
518 :param test:
519 :param err: error message
520
521 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200522 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200523 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200524 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200525 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
526 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200527 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200528
Damjan Marionf56b77a2016-10-03 19:44:57 +0200529 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200530 """
531 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200532
Klement Sekeraf62ae122016-10-11 11:47:09 +0200533 :param test:
534 :param err: error message
535
536 """
537 unittest.TestResult.addError(self, test, err)
538 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200539 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200540 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
541 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200542 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200543
Damjan Marionf56b77a2016-10-03 19:44:57 +0200544 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200545 """
546 Get test description
547
548 :param test:
549 :returns: test description
550
551 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200552 # TODO: if none print warning not raise exception
553 short_description = test.shortDescription()
554 if self.descriptions and short_description:
555 return short_description
556 else:
557 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200558
Damjan Marionf56b77a2016-10-03 19:44:57 +0200559 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200560 """
561 Start a test
562
563 :param test:
564
565 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200566 unittest.TestResult.startTest(self, test)
567 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200568 self.stream.writeln(
569 "Starting " + self.getDescription(test) + " ...")
570 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200571
Damjan Marionf56b77a2016-10-03 19:44:57 +0200572 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200573 """
574 Stop a test
575
576 :param test:
577
578 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200579 unittest.TestResult.stopTest(self, test)
580 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200581 self.stream.writeln(single_line_delim)
582 self.stream.writeln("%-60s%s" %
583 (self.getDescription(test), self.result_string))
584 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200585 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200586 self.stream.writeln("%-60s%s" %
587 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200588
Damjan Marionf56b77a2016-10-03 19:44:57 +0200589 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200590 """
591 Print errors from running the test case
592 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200593 self.stream.writeln()
594 self.printErrorList('ERROR', self.errors)
595 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200596
Damjan Marionf56b77a2016-10-03 19:44:57 +0200597 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200598 """
599 Print error list to the output stream together with error type
600 and test case description.
601
602 :param flavour: error type
603 :param errors: iterable errors
604
605 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200606 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200607 self.stream.writeln(double_line_delim)
608 self.stream.writeln("%s: %s" %
609 (flavour, self.getDescription(test)))
610 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200611 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200612
613
Damjan Marionf56b77a2016-10-03 19:44:57 +0200614class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200615 """
616 A basic test runner implementation which prints results on standard error.
617 """
618 @property
619 def resultclass(self):
620 """Class maintaining the results of the tests"""
621 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200622
Damjan Marionf56b77a2016-10-03 19:44:57 +0200623 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200624 """
625 Run the tests
626
627 :param test:
628
629 """
630 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200631 return super(VppTestRunner, self).run(test)