blob: 227428ef9c2c490da8f9a5dffff9bf1f918a1c60 [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
180 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100181 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200182 print(double_line_delim)
183 # need to catch exceptions here because if we raise, then the cleanup
184 # doesn't get called and we might end with a zombie vpp
185 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200186 cls.run_vpp()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200187 cls.vpp_dead = False
188 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
Klement Sekera277b89c2016-10-28 13:20:27 +0200189 if cls.step:
190 cls.vapi.register_hook(StepHook(cls))
191 else:
192 cls.vapi.register_hook(PollHook(cls))
193 time.sleep(0.1)
194 try:
195 cls.vapi.connect()
196 except:
197 if cls.debug_gdbserver:
198 print(colorize("You're running VPP inside gdbserver but "
199 "VPP-API connection failed, did you forget "
200 "to 'continue' VPP from within gdb?", RED))
201 raise
202 cls.vpp_stdout_queue = Queue()
203 cls.vpp_stdout_reader_thread = Thread(
204 target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
205 cls.vpp_stdout_reader_thread.start()
206 cls.vpp_stderr_queue = Queue()
207 cls.vpp_stderr_reader_thread = Thread(
208 target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
209 cls.vpp_stderr_reader_thread.start()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200210 except:
Klement Sekera931be3a2016-11-03 05:36:01 +0100211 if hasattr(cls, 'vpp'):
212 cls.vpp.terminate()
213 del cls.vpp
Klement Sekera277b89c2016-10-28 13:20:27 +0200214 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200215
Damjan Marionf56b77a2016-10-03 19:44:57 +0200216 @classmethod
217 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200218 """
219 Disconnect vpp-api, kill vpp and cleanup shared memory files
220 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200221 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
222 cls.vpp.poll()
223 if cls.vpp.returncode is None:
224 print(double_line_delim)
225 print("VPP or GDB server is still running")
226 print(single_line_delim)
227 raw_input("When done debugging, press ENTER to kill the process"
228 " and finish running the testcase...")
229
Klement Sekeraf62ae122016-10-11 11:47:09 +0200230 if hasattr(cls, 'vpp'):
231 cls.vapi.disconnect()
232 cls.vpp.poll()
233 if cls.vpp.returncode is None:
234 cls.vpp.terminate()
235 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200236
Klement Sekera277b89c2016-10-28 13:20:27 +0200237 if hasattr(cls, 'vpp_stdout_queue'):
238 cls.logger.info(single_line_delim)
239 cls.logger.info('VPP output to stdout while running %s:',
240 cls.__name__)
241 cls.logger.info(single_line_delim)
242 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
243 while not cls.vpp_stdout_queue.empty():
244 line = cls.vpp_stdout_queue.get_nowait()
245 f.write(line)
246 cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
247
248 if hasattr(cls, 'vpp_stderr_queue'):
249 cls.logger.info(single_line_delim)
250 cls.logger.info('VPP output to stderr while running %s:',
251 cls.__name__)
252 cls.logger.info(single_line_delim)
253 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
254 while not cls.vpp_stderr_queue.empty():
255 line = cls.vpp_stderr_queue.get_nowait()
256 f.write(line)
257 cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
258 cls.logger.info(single_line_delim)
259
Damjan Marionf56b77a2016-10-03 19:44:57 +0200260 @classmethod
261 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200262 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200263 cls.quit()
264
Damjan Marionf56b77a2016-10-03 19:44:57 +0200265 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200266 """ Show various debug prints after each test """
267 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200268 self.logger.info(self.vapi.ppcli("show int"))
269 self.logger.debug(self.vapi.cli("show trace"))
270 self.logger.info(self.vapi.ppcli("show hardware"))
271 self.logger.info(self.vapi.ppcli("show error"))
272 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200273
Damjan Marionf56b77a2016-10-03 19:44:57 +0200274 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200275 """ Clear trace before running each test"""
276 self.vapi.cli("clear trace")
277 # store the test instance inside the test class - so that objects
278 # holding the class can access instance methods (like assertEqual)
279 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200280
Damjan Marionf56b77a2016-10-03 19:44:57 +0200281 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200282 def pg_enable_capture(cls, interfaces):
283 """
284 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200285
Klement Sekeraf62ae122016-10-11 11:47:09 +0200286 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200287
Klement Sekeraf62ae122016-10-11 11:47:09 +0200288 """
289 for i in interfaces:
290 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200291
Damjan Marionf56b77a2016-10-03 19:44:57 +0200292 @classmethod
293 def pg_start(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200294 """
295 Enable the packet-generator and send all prepared packet streams
296 Remove the packet streams afterwards
297 """
298 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
299 cls.vapi.cli('packet-generator enable')
300 sleep(1) # give VPP some time to process the packets
Damjan Marionf56b77a2016-10-03 19:44:57 +0200301 for stream in cls.pg_streams:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200302 cls.vapi.cli('packet-generator delete %s' % stream)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200303 cls.pg_streams = []
304
Damjan Marionf56b77a2016-10-03 19:44:57 +0200305 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200306 def create_pg_interfaces(cls, interfaces):
307 """
308 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200309
Klement Sekeraf62ae122016-10-11 11:47:09 +0200310 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200311
Klement Sekeraf62ae122016-10-11 11:47:09 +0200312 """
313 result = []
314 for i in interfaces:
315 intf = VppPGInterface(cls, i)
316 setattr(cls, intf.name, intf)
317 result.append(intf)
318 cls.pg_interfaces = result
319 return result
320
Matej Klotton0178d522016-11-04 11:11:44 +0100321 @classmethod
322 def create_loopback_interfaces(cls, interfaces):
323 """
324 Create loopback interfaces
325
326 :param interfaces: iterable indexes of the interfaces
327
328 """
329 result = []
330 for i in interfaces:
331 intf = VppLoInterface(cls, i)
332 setattr(cls, intf.name, intf)
333 result.append(intf)
334 cls.lo_interfaces = result
335 return result
336
Damjan Marionf56b77a2016-10-03 19:44:57 +0200337 @staticmethod
338 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200339 """
340 Extend packet to given size by padding with spaces
341 NOTE: Currently works only when Raw layer is present.
342
343 :param packet: packet
344 :param size: target size
345
346 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200347 packet_len = len(packet) + 4
348 extend = size - packet_len
349 if extend > 0:
350 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200351
Damjan Marionf56b77a2016-10-03 19:44:57 +0200352 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200353 """
354 Add packet info to the testcase's packet info list
355
356 :param info: packet info
357
358 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200359 info.index = len(self.packet_infos)
360 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200361
Klement Sekeraf62ae122016-10-11 11:47:09 +0200362 def create_packet_info(self, src_pg_index, dst_pg_index):
363 """
364 Create packet info object containing the source and destination indexes
365 and add it to the testcase's packet info list
366
367 :param src_pg_index: source packet-generator index
368 :param dst_pg_index: destination packet-generator index
369
370 :returns: _PacketInfo object
371
372 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200373 info = _PacketInfo()
374 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200375 info.src = src_pg_index
376 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200377 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200378
Damjan Marionf56b77a2016-10-03 19:44:57 +0200379 @staticmethod
380 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200381 """
382 Convert _PacketInfo object to packet payload
383
384 :param info: _PacketInfo object
385
386 :returns: string containing serialized data from packet info
387 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200388 return "%d %d %d" % (info.index, info.src, info.dst)
389
Damjan Marionf56b77a2016-10-03 19:44:57 +0200390 @staticmethod
391 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200392 """
393 Convert packet payload to _PacketInfo object
394
395 :param payload: packet payload
396
397 :returns: _PacketInfo object containing de-serialized data from payload
398
399 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200400 numbers = payload.split()
401 info = _PacketInfo()
402 info.index = int(numbers[0])
403 info.src = int(numbers[1])
404 info.dst = int(numbers[2])
405 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200406
Damjan Marionf56b77a2016-10-03 19:44:57 +0200407 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200408 """
409 Iterate over the packet info list stored in the testcase
410 Start iteration with first element if info is None
411 Continue based on index in info if info is specified
412
413 :param info: info or None
414 :returns: next info in list or None if no more infos
415 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200416 if info is None:
417 next_index = 0
418 else:
419 next_index = info.index + 1
420 if next_index == len(self.packet_infos):
421 return None
422 else:
423 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200424
Klement Sekeraf62ae122016-10-11 11:47:09 +0200425 def get_next_packet_info_for_interface(self, src_index, info):
426 """
427 Search the packet info list for the next packet info with same source
428 interface index
429
430 :param src_index: source interface index to search for
431 :param info: packet info - where to start the search
432 :returns: packet info or None
433
434 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200435 while True:
436 info = self.get_next_packet_info(info)
437 if info is None:
438 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200439 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200440 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200441
Klement Sekeraf62ae122016-10-11 11:47:09 +0200442 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
443 """
444 Search the packet info list for the next packet info with same source
445 and destination interface indexes
446
447 :param src_index: source interface index to search for
448 :param dst_index: destination interface index to search for
449 :param info: packet info - where to start the search
450 :returns: packet info or None
451
452 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200453 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200454 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200455 if info is None:
456 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200457 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200458 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200459
460
Damjan Marionf56b77a2016-10-03 19:44:57 +0200461class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200462 """
463 @property result_string
464 String variable to store the test case result string.
465 @property errors
466 List variable containing 2-tuples of TestCase instances and strings
467 holding formatted tracebacks. Each tuple represents a test which
468 raised an unexpected exception.
469 @property failures
470 List variable containing 2-tuples of TestCase instances and strings
471 holding formatted tracebacks. Each tuple represents a test where
472 a failure was explicitly signalled using the TestCase.assert*()
473 methods.
474 """
475
Damjan Marionf56b77a2016-10-03 19:44:57 +0200476 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200477 """
478 :param stream File descriptor to store where to report test results. Set
479 to the standard error stream by default.
480 :param descriptions Boolean variable to store information if to use test
481 case descriptions.
482 :param verbosity Integer variable to store required verbosity level.
483 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200484 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
485 self.stream = stream
486 self.descriptions = descriptions
487 self.verbosity = verbosity
488 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200489
Damjan Marionf56b77a2016-10-03 19:44:57 +0200490 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200491 """
492 Record a test succeeded result
493
494 :param test:
495
496 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200497 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200498 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200499
Klement Sekeraf62ae122016-10-11 11:47:09 +0200500 def addSkip(self, test, reason):
501 """
502 Record a test skipped.
503
504 :param test:
505 :param reason:
506
507 """
508 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200509 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200510
Damjan Marionf56b77a2016-10-03 19:44:57 +0200511 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200512 """
513 Record a test failed result
514
515 :param test:
516 :param err: error message
517
518 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200519 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200520 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200521 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200522 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
523 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200524 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200525
Damjan Marionf56b77a2016-10-03 19:44:57 +0200526 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200527 """
528 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200529
Klement Sekeraf62ae122016-10-11 11:47:09 +0200530 :param test:
531 :param err: error message
532
533 """
534 unittest.TestResult.addError(self, test, err)
535 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200536 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200537 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
538 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200539 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200540
Damjan Marionf56b77a2016-10-03 19:44:57 +0200541 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200542 """
543 Get test description
544
545 :param test:
546 :returns: test description
547
548 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200549 # TODO: if none print warning not raise exception
550 short_description = test.shortDescription()
551 if self.descriptions and short_description:
552 return short_description
553 else:
554 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200555
Damjan Marionf56b77a2016-10-03 19:44:57 +0200556 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200557 """
558 Start a test
559
560 :param test:
561
562 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200563 unittest.TestResult.startTest(self, test)
564 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200565 self.stream.writeln(
566 "Starting " + self.getDescription(test) + " ...")
567 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200568
Damjan Marionf56b77a2016-10-03 19:44:57 +0200569 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200570 """
571 Stop a test
572
573 :param test:
574
575 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200576 unittest.TestResult.stopTest(self, test)
577 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200578 self.stream.writeln(single_line_delim)
579 self.stream.writeln("%-60s%s" %
580 (self.getDescription(test), self.result_string))
581 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200582 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200583 self.stream.writeln("%-60s%s" %
584 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200585
Damjan Marionf56b77a2016-10-03 19:44:57 +0200586 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200587 """
588 Print errors from running the test case
589 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200590 self.stream.writeln()
591 self.printErrorList('ERROR', self.errors)
592 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200593
Damjan Marionf56b77a2016-10-03 19:44:57 +0200594 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200595 """
596 Print error list to the output stream together with error type
597 and test case description.
598
599 :param flavour: error type
600 :param errors: iterable errors
601
602 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200603 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200604 self.stream.writeln(double_line_delim)
605 self.stream.writeln("%s: %s" %
606 (flavour, self.getDescription(test)))
607 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200608 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200609
610
Damjan Marionf56b77a2016-10-03 19:44:57 +0200611class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200612 """
613 A basic test runner implementation which prints results on standard error.
614 """
615 @property
616 def resultclass(self):
617 """Class maintaining the results of the tests"""
618 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200619
Damjan Marionf56b77a2016-10-03 19:44:57 +0200620 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200621 """
622 Run the tests
623
624 :param test:
625
626 """
627 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200628 return super(VppTestRunner, self).run(test)