blob: b10592cf1e78173b796a40c627ea61c50eebdf21 [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
5import subprocess
6import unittest
Klement Sekeraf62ae122016-10-11 11:47:09 +02007import tempfile
Klement Sekera277b89c2016-10-28 13:20:27 +02008import time
Klement Sekeraf62ae122016-10-11 11:47:09 +02009import resource
10from time import sleep
Klement Sekera277b89c2016-10-28 13:20:27 +020011from Queue import Queue
12from threading import Thread
Damjan Marionf56b77a2016-10-03 19:44:57 +020013from inspect import getdoc
Klement Sekera277b89c2016-10-28 13:20:27 +020014from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020015from vpp_pg_interface import VppPGInterface
16from vpp_papi_provider import VppPapiProvider
Damjan Marionf56b77a2016-10-03 19:44:57 +020017from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020018from log import *
Klement Sekeraf62ae122016-10-11 11:47:09 +020019
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
Klement Sekeraf62ae122016-10-11 11:47:09 +020027
Damjan Marionf56b77a2016-10-03 19:44:57 +020028class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020029 """Private class to create packet info object.
30
31 Help process information about the next packet.
32 Set variables to default values.
33 @property index
34 Integer variable to store the index of the packet.
35 @property src
36 Integer variable to store the index of the source packet generator
37 interface of the packet.
38 @property dst
39 Integer variable to store the index of the destination packet generator
40 interface of the packet.
41 @property data
42 Object variable to store the copy of the former packet.
43
44
45 """
Damjan Marionf56b77a2016-10-03 19:44:57 +020046 index = -1
47 src = -1
48 dst = -1
49 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020050
Klement Sekeraf62ae122016-10-11 11:47:09 +020051
Klement Sekera277b89c2016-10-28 13:20:27 +020052def pump_output(out, queue):
53 for line in iter(out.readline, b''):
54 queue.put(line)
55
56
Damjan Marionf56b77a2016-10-03 19:44:57 +020057class VppTestCase(unittest.TestCase):
Klement Sekeraf62ae122016-10-11 11:47:09 +020058 """
59 Subclass of the python unittest.TestCase class.
Damjan Marionf56b77a2016-10-03 19:44:57 +020060
Klement Sekeraf62ae122016-10-11 11:47:09 +020061 This subclass is a base class for test cases that are implemented as classes
62 It provides methods to create and run test case.
63
64 """
65
66 @property
67 def packet_infos(self):
68 """List of packet infos"""
69 return self._packet_infos
70
71 @packet_infos.setter
72 def packet_infos(self, value):
73 self._packet_infos = value
74
75 @classmethod
76 def instance(cls):
77 """Return the instance of this testcase"""
78 return cls.test_instance
79
Damjan Marionf56b77a2016-10-03 19:44:57 +020080 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +020081 def set_debug_flags(cls, d):
82 cls.debug_core = False
83 cls.debug_gdb = False
84 cls.debug_gdbserver = False
85 if d is None:
86 return
87 dl = d.lower()
88 if dl == "core":
89 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
90 # give a heads up if this is actually useless
91 cls.logger.critical("WARNING: core size limit is set 0, core "
92 "files will NOT be created")
93 cls.debug_core = True
94 elif dl == "gdb":
95 cls.debug_gdb = True
96 elif dl == "gdbserver":
97 cls.debug_gdbserver = True
98 else:
99 raise Exception("Unrecognized DEBUG option: '%s'" % d)
100
101 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200102 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200103 """ Set-up the test case class based on environment variables """
104 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200105 s = os.getenv("STEP")
106 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200107 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200108 cls.step = False
109 try:
110 d = os.getenv("DEBUG")
111 except:
112 d = None
113 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200114 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100115 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100116 debug_cli = ""
117 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
118 debug_cli = "cli-listen localhost:5002"
119 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200120 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100121 if cls.plugin_path is not None:
122 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200123 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
124
125 @classmethod
126 def wait_for_enter(cls):
127 if cls.debug_gdbserver:
128 print(double_line_delim)
129 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
130 elif cls.debug_gdb:
131 print(double_line_delim)
132 print("Spawned VPP with PID: %d" % cls.vpp.pid)
133 else:
134 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
135 return
136 print(single_line_delim)
137 print("You can debug the VPP using e.g.:")
138 if cls.debug_gdbserver:
139 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
140 print("Now is the time to attach a gdb by running the above "
141 "command, set up breakpoints etc. and then resume VPP from "
142 "within gdb by issuing the 'continue' command")
143 elif cls.debug_gdb:
144 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
145 print("Now is the time to attach a gdb by running the above "
146 "command and set up breakpoints etc.")
147 print(single_line_delim)
148 raw_input("Press ENTER to continue running the testcase...")
149
150 @classmethod
151 def run_vpp(cls):
152 cmdline = cls.vpp_cmdline
153
154 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100155 gdbserver = '/usr/bin/gdbserver'
156 if not os.path.isfile(gdbserver) or \
157 not os.access(gdbserver, os.X_OK):
158 raise Exception("gdbserver binary '%s' does not exist or is "
159 "not executable" % gdbserver)
160
161 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200162 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
163
Klement Sekera931be3a2016-11-03 05:36:01 +0100164 try:
165 cls.vpp = subprocess.Popen(cmdline,
166 stdout=subprocess.PIPE,
167 stderr=subprocess.PIPE,
168 bufsize=1)
169 except Exception as e:
170 cls.logger.critical("Couldn't start vpp: %s" % e)
171 raise
172
Klement Sekera277b89c2016-10-28 13:20:27 +0200173 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100174
Damjan Marionf56b77a2016-10-03 19:44:57 +0200175 @classmethod
176 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200177 """
178 Perform class setup before running the testcase
179 Remove shared memory files, start vpp and connect the vpp-api
180 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200181 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200182 cls.tempdir = tempfile.mkdtemp(
183 prefix='vpp-unittest-' + cls.__name__ + '-')
184 cls.shm_prefix = cls.tempdir.split("/")[-1]
185 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200186 cls.logger.info("Temporary dir is %s, shm prefix is %s",
187 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200188 cls.setUpConstants()
189 cls.pg_streams = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200190 cls.packet_infos = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200191 cls.verbose = 0
192 print(double_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200193 print(colorize(getdoc(cls), YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200194 print(double_line_delim)
195 # need to catch exceptions here because if we raise, then the cleanup
196 # doesn't get called and we might end with a zombie vpp
197 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200198 cls.run_vpp()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200199 cls.vpp_dead = False
200 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
Klement Sekera277b89c2016-10-28 13:20:27 +0200201 if cls.step:
202 cls.vapi.register_hook(StepHook(cls))
203 else:
204 cls.vapi.register_hook(PollHook(cls))
205 time.sleep(0.1)
206 try:
207 cls.vapi.connect()
208 except:
209 if cls.debug_gdbserver:
210 print(colorize("You're running VPP inside gdbserver but "
211 "VPP-API connection failed, did you forget "
212 "to 'continue' VPP from within gdb?", RED))
213 raise
214 cls.vpp_stdout_queue = Queue()
215 cls.vpp_stdout_reader_thread = Thread(
216 target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
217 cls.vpp_stdout_reader_thread.start()
218 cls.vpp_stderr_queue = Queue()
219 cls.vpp_stderr_reader_thread = Thread(
220 target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
221 cls.vpp_stderr_reader_thread.start()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200222 except:
Klement Sekera931be3a2016-11-03 05:36:01 +0100223 if hasattr(cls, 'vpp'):
224 cls.vpp.terminate()
225 del cls.vpp
Klement Sekera277b89c2016-10-28 13:20:27 +0200226 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200227
Damjan Marionf56b77a2016-10-03 19:44:57 +0200228 @classmethod
229 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200230 """
231 Disconnect vpp-api, kill vpp and cleanup shared memory files
232 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200233 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
234 cls.vpp.poll()
235 if cls.vpp.returncode is None:
236 print(double_line_delim)
237 print("VPP or GDB server is still running")
238 print(single_line_delim)
239 raw_input("When done debugging, press ENTER to kill the process"
240 " and finish running the testcase...")
241
Klement Sekeraf62ae122016-10-11 11:47:09 +0200242 if hasattr(cls, 'vpp'):
243 cls.vapi.disconnect()
244 cls.vpp.poll()
245 if cls.vpp.returncode is None:
246 cls.vpp.terminate()
247 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200248
Klement Sekera277b89c2016-10-28 13:20:27 +0200249 if hasattr(cls, 'vpp_stdout_queue'):
250 cls.logger.info(single_line_delim)
251 cls.logger.info('VPP output to stdout while running %s:',
252 cls.__name__)
253 cls.logger.info(single_line_delim)
254 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
255 while not cls.vpp_stdout_queue.empty():
256 line = cls.vpp_stdout_queue.get_nowait()
257 f.write(line)
258 cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
259
260 if hasattr(cls, 'vpp_stderr_queue'):
261 cls.logger.info(single_line_delim)
262 cls.logger.info('VPP output to stderr while running %s:',
263 cls.__name__)
264 cls.logger.info(single_line_delim)
265 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
266 while not cls.vpp_stderr_queue.empty():
267 line = cls.vpp_stderr_queue.get_nowait()
268 f.write(line)
269 cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
270 cls.logger.info(single_line_delim)
271
Damjan Marionf56b77a2016-10-03 19:44:57 +0200272 @classmethod
273 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200274 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200275 cls.quit()
276
Damjan Marionf56b77a2016-10-03 19:44:57 +0200277 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200278 """ Show various debug prints after each test """
279 if not self.vpp_dead:
Klement Sekera277b89c2016-10-28 13:20:27 +0200280 self.logger.info(self.vapi.cli("show int"))
281 self.logger.info(self.vapi.cli("show trace"))
282 self.logger.info(self.vapi.cli("show hardware"))
283 self.logger.info(self.vapi.cli("show error"))
284 self.logger.info(self.vapi.cli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200285
Damjan Marionf56b77a2016-10-03 19:44:57 +0200286 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200287 """ Clear trace before running each test"""
288 self.vapi.cli("clear trace")
289 # store the test instance inside the test class - so that objects
290 # holding the class can access instance methods (like assertEqual)
291 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200292
Damjan Marionf56b77a2016-10-03 19:44:57 +0200293 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200294 def pg_enable_capture(cls, interfaces):
295 """
296 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200297
Klement Sekeraf62ae122016-10-11 11:47:09 +0200298 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200299
Klement Sekeraf62ae122016-10-11 11:47:09 +0200300 """
301 for i in interfaces:
302 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200303
Damjan Marionf56b77a2016-10-03 19:44:57 +0200304 @classmethod
305 def pg_start(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200306 """
307 Enable the packet-generator and send all prepared packet streams
308 Remove the packet streams afterwards
309 """
310 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
311 cls.vapi.cli('packet-generator enable')
312 sleep(1) # give VPP some time to process the packets
Damjan Marionf56b77a2016-10-03 19:44:57 +0200313 for stream in cls.pg_streams:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200314 cls.vapi.cli('packet-generator delete %s' % stream)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200315 cls.pg_streams = []
316
Damjan Marionf56b77a2016-10-03 19:44:57 +0200317 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200318 def create_pg_interfaces(cls, interfaces):
319 """
320 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200321
Klement Sekeraf62ae122016-10-11 11:47:09 +0200322 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200323
Klement Sekeraf62ae122016-10-11 11:47:09 +0200324 """
325 result = []
326 for i in interfaces:
327 intf = VppPGInterface(cls, i)
328 setattr(cls, intf.name, intf)
329 result.append(intf)
330 cls.pg_interfaces = result
331 return result
332
Damjan Marionf56b77a2016-10-03 19:44:57 +0200333 @staticmethod
334 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200335 """
336 Extend packet to given size by padding with spaces
337 NOTE: Currently works only when Raw layer is present.
338
339 :param packet: packet
340 :param size: target size
341
342 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200343 packet_len = len(packet) + 4
344 extend = size - packet_len
345 if extend > 0:
346 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200347
Damjan Marionf56b77a2016-10-03 19:44:57 +0200348 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200349 """
350 Add packet info to the testcase's packet info list
351
352 :param info: packet info
353
354 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200355 info.index = len(self.packet_infos)
356 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200357
Klement Sekeraf62ae122016-10-11 11:47:09 +0200358 def create_packet_info(self, src_pg_index, dst_pg_index):
359 """
360 Create packet info object containing the source and destination indexes
361 and add it to the testcase's packet info list
362
363 :param src_pg_index: source packet-generator index
364 :param dst_pg_index: destination packet-generator index
365
366 :returns: _PacketInfo object
367
368 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200369 info = _PacketInfo()
370 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200371 info.src = src_pg_index
372 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200373 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200374
Damjan Marionf56b77a2016-10-03 19:44:57 +0200375 @staticmethod
376 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200377 """
378 Convert _PacketInfo object to packet payload
379
380 :param info: _PacketInfo object
381
382 :returns: string containing serialized data from packet info
383 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200384 return "%d %d %d" % (info.index, info.src, info.dst)
385
Damjan Marionf56b77a2016-10-03 19:44:57 +0200386 @staticmethod
387 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200388 """
389 Convert packet payload to _PacketInfo object
390
391 :param payload: packet payload
392
393 :returns: _PacketInfo object containing de-serialized data from payload
394
395 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200396 numbers = payload.split()
397 info = _PacketInfo()
398 info.index = int(numbers[0])
399 info.src = int(numbers[1])
400 info.dst = int(numbers[2])
401 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200402
Damjan Marionf56b77a2016-10-03 19:44:57 +0200403 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200404 """
405 Iterate over the packet info list stored in the testcase
406 Start iteration with first element if info is None
407 Continue based on index in info if info is specified
408
409 :param info: info or None
410 :returns: next info in list or None if no more infos
411 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200412 if info is None:
413 next_index = 0
414 else:
415 next_index = info.index + 1
416 if next_index == len(self.packet_infos):
417 return None
418 else:
419 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200420
Klement Sekeraf62ae122016-10-11 11:47:09 +0200421 def get_next_packet_info_for_interface(self, src_index, info):
422 """
423 Search the packet info list for the next packet info with same source
424 interface index
425
426 :param src_index: source interface index to search for
427 :param info: packet info - where to start the search
428 :returns: packet info or None
429
430 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200431 while True:
432 info = self.get_next_packet_info(info)
433 if info is None:
434 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200435 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200436 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200437
Klement Sekeraf62ae122016-10-11 11:47:09 +0200438 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
439 """
440 Search the packet info list for the next packet info with same source
441 and destination interface indexes
442
443 :param src_index: source interface index to search for
444 :param dst_index: destination interface index to search for
445 :param info: packet info - where to start the search
446 :returns: packet info or None
447
448 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200449 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200450 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200451 if info is None:
452 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200453 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200454 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200455
456
Damjan Marionf56b77a2016-10-03 19:44:57 +0200457class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200458 """
459 @property result_string
460 String variable to store the test case result string.
461 @property errors
462 List variable containing 2-tuples of TestCase instances and strings
463 holding formatted tracebacks. Each tuple represents a test which
464 raised an unexpected exception.
465 @property failures
466 List variable containing 2-tuples of TestCase instances and strings
467 holding formatted tracebacks. Each tuple represents a test where
468 a failure was explicitly signalled using the TestCase.assert*()
469 methods.
470 """
471
Damjan Marionf56b77a2016-10-03 19:44:57 +0200472 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200473 """
474 :param stream File descriptor to store where to report test results. Set
475 to the standard error stream by default.
476 :param descriptions Boolean variable to store information if to use test
477 case descriptions.
478 :param verbosity Integer variable to store required verbosity level.
479 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200480 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
481 self.stream = stream
482 self.descriptions = descriptions
483 self.verbosity = verbosity
484 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200485
Damjan Marionf56b77a2016-10-03 19:44:57 +0200486 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200487 """
488 Record a test succeeded result
489
490 :param test:
491
492 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200494 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200495
Klement Sekeraf62ae122016-10-11 11:47:09 +0200496 def addSkip(self, test, reason):
497 """
498 Record a test skipped.
499
500 :param test:
501 :param reason:
502
503 """
504 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200505 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200506
Damjan Marionf56b77a2016-10-03 19:44:57 +0200507 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200508 """
509 Record a test failed result
510
511 :param test:
512 :param err: error message
513
514 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200515 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200516 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200517 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200518 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
519 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200520 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200521
Damjan Marionf56b77a2016-10-03 19:44:57 +0200522 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200523 """
524 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200525
Klement Sekeraf62ae122016-10-11 11:47:09 +0200526 :param test:
527 :param err: error message
528
529 """
530 unittest.TestResult.addError(self, test, err)
531 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200532 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200533 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
534 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200535 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200536
Damjan Marionf56b77a2016-10-03 19:44:57 +0200537 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200538 """
539 Get test description
540
541 :param test:
542 :returns: test description
543
544 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200545 # TODO: if none print warning not raise exception
546 short_description = test.shortDescription()
547 if self.descriptions and short_description:
548 return short_description
549 else:
550 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200551
Damjan Marionf56b77a2016-10-03 19:44:57 +0200552 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200553 """
554 Start a test
555
556 :param test:
557
558 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200559 unittest.TestResult.startTest(self, test)
560 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200561 self.stream.writeln(
562 "Starting " + self.getDescription(test) + " ...")
563 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200564
Damjan Marionf56b77a2016-10-03 19:44:57 +0200565 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200566 """
567 Stop a test
568
569 :param test:
570
571 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200572 unittest.TestResult.stopTest(self, test)
573 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200574 self.stream.writeln(single_line_delim)
575 self.stream.writeln("%-60s%s" %
576 (self.getDescription(test), self.result_string))
577 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200578 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200579 self.stream.writeln("%-60s%s" %
580 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200581
Damjan Marionf56b77a2016-10-03 19:44:57 +0200582 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200583 """
584 Print errors from running the test case
585 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200586 self.stream.writeln()
587 self.printErrorList('ERROR', self.errors)
588 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200589
Damjan Marionf56b77a2016-10-03 19:44:57 +0200590 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200591 """
592 Print error list to the output stream together with error type
593 and test case description.
594
595 :param flavour: error type
596 :param errors: iterable errors
597
598 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200599 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200600 self.stream.writeln(double_line_delim)
601 self.stream.writeln("%s: %s" %
602 (flavour, self.getDescription(test)))
603 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200604 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200605
606
Damjan Marionf56b77a2016-10-03 19:44:57 +0200607class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200608 """
609 A basic test runner implementation which prints results on standard error.
610 """
611 @property
612 def resultclass(self):
613 """Class maintaining the results of the tests"""
614 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200615
Damjan Marionf56b77a2016-10-03 19:44:57 +0200616 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200617 """
618 Run the tests
619
620 :param test:
621
622 """
623 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200624 return super(VppTestRunner, self).run(test)