blob: 2618b267706d065ca2f015d0519b05d16d9dde63 [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 Sekerae4504c62016-12-08 10:16:41 +01009from collections import deque
Klement Sekera277b89c2016-10-28 13:20:27 +020010from 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
Matej Klotton16a14cd2016-12-07 15:09:13 +010043 def __eq__(self, other):
44 index = self.index == other.index
45 src = self.src == other.src
46 dst = self.dst == other.dst
47 data = self.data == other.data
48 return index and src and dst and data
49
Klement Sekeraf62ae122016-10-11 11:47:09 +020050
Klement Sekerae4504c62016-12-08 10:16:41 +010051def pump_output(out, deque):
Klement Sekera277b89c2016-10-28 13:20:27 +020052 for line in iter(out.readline, b''):
Klement Sekerae4504c62016-12-08 10:16:41 +010053 deque.append(line)
Klement Sekera277b89c2016-10-28 13:20:27 +020054
55
Damjan Marionf56b77a2016-10-03 19:44:57 +020056class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010057 """This subclass is a base class for VPP test cases that are implemented as
58 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020059 """
60
61 @property
62 def packet_infos(self):
63 """List of packet infos"""
64 return self._packet_infos
65
66 @packet_infos.setter
67 def packet_infos(self, value):
68 self._packet_infos = value
69
70 @classmethod
71 def instance(cls):
72 """Return the instance of this testcase"""
73 return cls.test_instance
74
Damjan Marionf56b77a2016-10-03 19:44:57 +020075 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +020076 def set_debug_flags(cls, d):
77 cls.debug_core = False
78 cls.debug_gdb = False
79 cls.debug_gdbserver = False
80 if d is None:
81 return
82 dl = d.lower()
83 if dl == "core":
84 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
85 # give a heads up if this is actually useless
86 cls.logger.critical("WARNING: core size limit is set 0, core "
87 "files will NOT be created")
88 cls.debug_core = True
89 elif dl == "gdb":
90 cls.debug_gdb = True
91 elif dl == "gdbserver":
92 cls.debug_gdbserver = True
93 else:
94 raise Exception("Unrecognized DEBUG option: '%s'" % d)
95
96 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +020097 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +020098 """ Set-up the test case class based on environment variables """
99 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200100 s = os.getenv("STEP")
101 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200102 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200103 cls.step = False
104 try:
105 d = os.getenv("DEBUG")
106 except:
107 d = None
108 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200109 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100110 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100111 debug_cli = ""
112 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
113 debug_cli = "cli-listen localhost:5002"
114 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200115 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100116 if cls.plugin_path is not None:
117 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200118 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
119
120 @classmethod
121 def wait_for_enter(cls):
122 if cls.debug_gdbserver:
123 print(double_line_delim)
124 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
125 elif cls.debug_gdb:
126 print(double_line_delim)
127 print("Spawned VPP with PID: %d" % cls.vpp.pid)
128 else:
129 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
130 return
131 print(single_line_delim)
132 print("You can debug the VPP using e.g.:")
133 if cls.debug_gdbserver:
134 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
135 print("Now is the time to attach a gdb by running the above "
136 "command, set up breakpoints etc. and then resume VPP from "
137 "within gdb by issuing the 'continue' command")
138 elif cls.debug_gdb:
139 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
140 print("Now is the time to attach a gdb by running the above "
141 "command and set up breakpoints etc.")
142 print(single_line_delim)
143 raw_input("Press ENTER to continue running the testcase...")
144
145 @classmethod
146 def run_vpp(cls):
147 cmdline = cls.vpp_cmdline
148
149 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100150 gdbserver = '/usr/bin/gdbserver'
151 if not os.path.isfile(gdbserver) or \
152 not os.access(gdbserver, os.X_OK):
153 raise Exception("gdbserver binary '%s' does not exist or is "
154 "not executable" % gdbserver)
155
156 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200157 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
158
Klement Sekera931be3a2016-11-03 05:36:01 +0100159 try:
160 cls.vpp = subprocess.Popen(cmdline,
161 stdout=subprocess.PIPE,
162 stderr=subprocess.PIPE,
163 bufsize=1)
164 except Exception as e:
165 cls.logger.critical("Couldn't start vpp: %s" % e)
166 raise
167
Klement Sekera277b89c2016-10-28 13:20:27 +0200168 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100169
Damjan Marionf56b77a2016-10-03 19:44:57 +0200170 @classmethod
171 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200172 """
173 Perform class setup before running the testcase
174 Remove shared memory files, start vpp and connect the vpp-api
175 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200176 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200177 cls.tempdir = tempfile.mkdtemp(
178 prefix='vpp-unittest-' + cls.__name__ + '-')
179 cls.shm_prefix = cls.tempdir.split("/")[-1]
180 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200181 cls.logger.info("Temporary dir is %s, shm prefix is %s",
182 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200183 cls.setUpConstants()
184 cls.pg_streams = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200185 cls.packet_infos = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200186 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100187 cls.vpp_dead = False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200188 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100189 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200190 print(double_line_delim)
191 # need to catch exceptions here because if we raise, then the cleanup
192 # doesn't get called and we might end with a zombie vpp
193 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200194 cls.run_vpp()
Klement Sekerae4504c62016-12-08 10:16:41 +0100195 cls.vpp_stdout_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100196 cls.vpp_stdout_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100197 cls.vpp.stdout, cls.vpp_stdout_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200198 cls.vpp_stdout_reader_thread.start()
Klement Sekerae4504c62016-12-08 10:16:41 +0100199 cls.vpp_stderr_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100200 cls.vpp_stderr_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100201 cls.vpp.stderr, cls.vpp_stderr_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200202 cls.vpp_stderr_reader_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100203 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100204 if cls.step:
205 hook = StepHook(cls)
206 else:
207 hook = PollHook(cls)
208 cls.vapi.register_hook(hook)
209 time.sleep(0.1)
210 hook.poll_vpp()
211 try:
212 cls.vapi.connect()
213 except:
214 if cls.debug_gdbserver:
215 print(colorize("You're running VPP inside gdbserver but "
216 "VPP-API connection failed, did you forget "
217 "to 'continue' VPP from within gdb?", RED))
218 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200219 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100220 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100221 try:
222 cls.quit()
223 except:
224 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100225 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200226
Damjan Marionf56b77a2016-10-03 19:44:57 +0200227 @classmethod
228 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200229 """
230 Disconnect vpp-api, kill vpp and cleanup shared memory files
231 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200232 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
233 cls.vpp.poll()
234 if cls.vpp.returncode is None:
235 print(double_line_delim)
236 print("VPP or GDB server is still running")
237 print(single_line_delim)
238 raw_input("When done debugging, press ENTER to kill the process"
239 " and finish running the testcase...")
240
Klement Sekeraf62ae122016-10-11 11:47:09 +0200241 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100242 if hasattr(cls, 'vapi'):
243 cls.vapi.disconnect()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200244 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 Sekerae4504c62016-12-08 10:16:41 +0100249 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200250 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')
Klement Sekerae4504c62016-12-08 10:16:41 +0100255 vpp_output = "".join(cls.vpp_stdout_deque)
256 f.write(vpp_output)
257 cls.logger.info('\n%s', vpp_output)
258 cls.logger.info(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200259
Klement Sekerae4504c62016-12-08 10:16:41 +0100260 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200261 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')
Klement Sekerae4504c62016-12-08 10:16:41 +0100266 vpp_output = "".join(cls.vpp_stderr_deque)
267 f.write(vpp_output)
268 cls.logger.info('\n%s', vpp_output)
Klement Sekera277b89c2016-10-28 13:20:27 +0200269 cls.logger.info(single_line_delim)
270
Damjan Marionf56b77a2016-10-03 19:44:57 +0200271 @classmethod
272 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200273 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200274 cls.quit()
275
Damjan Marionf56b77a2016-10-03 19:44:57 +0200276 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200277 """ Show various debug prints after each test """
278 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200279 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000280 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200281 self.logger.info(self.vapi.ppcli("show hardware"))
282 self.logger.info(self.vapi.ppcli("show error"))
283 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200284
Damjan Marionf56b77a2016-10-03 19:44:57 +0200285 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200286 """ Clear trace before running each test"""
Klement Sekera0c1519b2016-12-08 05:03:32 +0100287 if self.vpp_dead:
288 raise Exception("VPP is dead when setting up the test")
Klement Sekerae4504c62016-12-08 10:16:41 +0100289 time.sleep(.1)
290 self.vpp_stdout_deque.append(
291 "--- test setUp() for %s.%s(%s) starts here ---\n" %
292 (self.__class__.__name__, self._testMethodName,
293 self._testMethodDoc))
294 self.vpp_stderr_deque.append(
295 "--- test setUp() for %s.%s(%s) starts here ---\n" %
296 (self.__class__.__name__, self._testMethodName,
297 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200298 self.vapi.cli("clear trace")
299 # store the test instance inside the test class - so that objects
300 # holding the class can access instance methods (like assertEqual)
301 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200302
Damjan Marionf56b77a2016-10-03 19:44:57 +0200303 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200304 def pg_enable_capture(cls, interfaces):
305 """
306 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200307
Klement Sekeraf62ae122016-10-11 11:47:09 +0200308 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200309
Klement Sekeraf62ae122016-10-11 11:47:09 +0200310 """
311 for i in interfaces:
312 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200313
Damjan Marionf56b77a2016-10-03 19:44:57 +0200314 @classmethod
Klement Sekerae4504c62016-12-08 10:16:41 +0100315 def pg_start(cls, sleep_time=1):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200316 """
317 Enable the packet-generator and send all prepared packet streams
318 Remove the packet streams afterwards
319 """
320 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
321 cls.vapi.cli('packet-generator enable')
Klement Sekerae4504c62016-12-08 10:16:41 +0100322 sleep(sleep_time) # give VPP some time to process the packets
Damjan Marionf56b77a2016-10-03 19:44:57 +0200323 for stream in cls.pg_streams:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200324 cls.vapi.cli('packet-generator delete %s' % stream)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200325 cls.pg_streams = []
326
Damjan Marionf56b77a2016-10-03 19:44:57 +0200327 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200328 def create_pg_interfaces(cls, interfaces):
329 """
330 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200331
Klement Sekeraf62ae122016-10-11 11:47:09 +0200332 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200333
Klement Sekeraf62ae122016-10-11 11:47:09 +0200334 """
335 result = []
336 for i in interfaces:
337 intf = VppPGInterface(cls, i)
338 setattr(cls, intf.name, intf)
339 result.append(intf)
340 cls.pg_interfaces = result
341 return result
342
Matej Klotton0178d522016-11-04 11:11:44 +0100343 @classmethod
344 def create_loopback_interfaces(cls, interfaces):
345 """
346 Create loopback interfaces
347
348 :param interfaces: iterable indexes of the interfaces
349
350 """
351 result = []
352 for i in interfaces:
353 intf = VppLoInterface(cls, i)
354 setattr(cls, intf.name, intf)
355 result.append(intf)
356 cls.lo_interfaces = result
357 return result
358
Damjan Marionf56b77a2016-10-03 19:44:57 +0200359 @staticmethod
360 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200361 """
362 Extend packet to given size by padding with spaces
363 NOTE: Currently works only when Raw layer is present.
364
365 :param packet: packet
366 :param size: target size
367
368 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200369 packet_len = len(packet) + 4
370 extend = size - packet_len
371 if extend > 0:
372 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200373
Damjan Marionf56b77a2016-10-03 19:44:57 +0200374 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200375 """
376 Add packet info to the testcase's packet info list
377
378 :param info: packet info
379
380 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200381 info.index = len(self.packet_infos)
382 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200383
Klement Sekeraf62ae122016-10-11 11:47:09 +0200384 def create_packet_info(self, src_pg_index, dst_pg_index):
385 """
386 Create packet info object containing the source and destination indexes
387 and add it to the testcase's packet info list
388
389 :param src_pg_index: source packet-generator index
390 :param dst_pg_index: destination packet-generator index
391
392 :returns: _PacketInfo object
393
394 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200395 info = _PacketInfo()
396 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200397 info.src = src_pg_index
398 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200399 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200400
Damjan Marionf56b77a2016-10-03 19:44:57 +0200401 @staticmethod
402 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200403 """
404 Convert _PacketInfo object to packet payload
405
406 :param info: _PacketInfo object
407
408 :returns: string containing serialized data from packet info
409 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200410 return "%d %d %d" % (info.index, info.src, info.dst)
411
Damjan Marionf56b77a2016-10-03 19:44:57 +0200412 @staticmethod
413 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200414 """
415 Convert packet payload to _PacketInfo object
416
417 :param payload: packet payload
418
419 :returns: _PacketInfo object containing de-serialized data from payload
420
421 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200422 numbers = payload.split()
423 info = _PacketInfo()
424 info.index = int(numbers[0])
425 info.src = int(numbers[1])
426 info.dst = int(numbers[2])
427 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200428
Damjan Marionf56b77a2016-10-03 19:44:57 +0200429 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200430 """
431 Iterate over the packet info list stored in the testcase
432 Start iteration with first element if info is None
433 Continue based on index in info if info is specified
434
435 :param info: info or None
436 :returns: next info in list or None if no more infos
437 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200438 if info is None:
439 next_index = 0
440 else:
441 next_index = info.index + 1
442 if next_index == len(self.packet_infos):
443 return None
444 else:
445 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200446
Klement Sekeraf62ae122016-10-11 11:47:09 +0200447 def get_next_packet_info_for_interface(self, src_index, info):
448 """
449 Search the packet info list for the next packet info with same source
450 interface index
451
452 :param src_index: source interface index to search for
453 :param info: packet info - where to start the search
454 :returns: packet info or None
455
456 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200457 while True:
458 info = self.get_next_packet_info(info)
459 if info is None:
460 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200461 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200462 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200463
Klement Sekeraf62ae122016-10-11 11:47:09 +0200464 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
465 """
466 Search the packet info list for the next packet info with same source
467 and destination interface indexes
468
469 :param src_index: source interface index to search for
470 :param dst_index: destination interface index to search for
471 :param info: packet info - where to start the search
472 :returns: packet info or None
473
474 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200475 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200476 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200477 if info is None:
478 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200479 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200480 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200481
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200482 def assert_equal(self, real_value, expected_value, name_or_class=None):
483 if name_or_class is None:
484 self.assertEqual(real_value, expected_value, msg)
485 return
486 try:
487 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
488 msg = msg % (getdoc(name_or_class).strip(),
489 real_value, str(name_or_class(real_value)),
490 expected_value, str(name_or_class(expected_value)))
491 except:
492 msg = "Invalid %s: %s does not match expected value %s" % (
493 name_or_class, real_value, expected_value)
494
495 self.assertEqual(real_value, expected_value, msg)
496
497 def assert_in_range(
498 self,
499 real_value,
500 expected_min,
501 expected_max,
502 name=None):
503 if name is None:
504 msg = None
505 else:
506 msg = "Invalid %s: %s out of range <%s,%s>" % (
507 name, real_value, expected_min, expected_max)
508 self.assertTrue(expected_min <= real_value <= expected_max, msg)
509
Damjan Marionf56b77a2016-10-03 19:44:57 +0200510
Damjan Marionf56b77a2016-10-03 19:44:57 +0200511class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200512 """
513 @property result_string
514 String variable to store the test case result string.
515 @property errors
516 List variable containing 2-tuples of TestCase instances and strings
517 holding formatted tracebacks. Each tuple represents a test which
518 raised an unexpected exception.
519 @property failures
520 List variable containing 2-tuples of TestCase instances and strings
521 holding formatted tracebacks. Each tuple represents a test where
522 a failure was explicitly signalled using the TestCase.assert*()
523 methods.
524 """
525
Damjan Marionf56b77a2016-10-03 19:44:57 +0200526 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200527 """
528 :param stream File descriptor to store where to report test results. Set
529 to the standard error stream by default.
530 :param descriptions Boolean variable to store information if to use test
531 case descriptions.
532 :param verbosity Integer variable to store required verbosity level.
533 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200534 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
535 self.stream = stream
536 self.descriptions = descriptions
537 self.verbosity = verbosity
538 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200539
Damjan Marionf56b77a2016-10-03 19:44:57 +0200540 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200541 """
542 Record a test succeeded result
543
544 :param test:
545
546 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200547 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200548 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200549
Klement Sekeraf62ae122016-10-11 11:47:09 +0200550 def addSkip(self, test, reason):
551 """
552 Record a test skipped.
553
554 :param test:
555 :param reason:
556
557 """
558 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200559 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200560
Damjan Marionf56b77a2016-10-03 19:44:57 +0200561 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200562 """
563 Record a test failed result
564
565 :param test:
566 :param err: error message
567
568 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200569 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200570 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200571 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200572 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
573 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200574 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200575
Damjan Marionf56b77a2016-10-03 19:44:57 +0200576 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200577 """
578 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200579
Klement Sekeraf62ae122016-10-11 11:47:09 +0200580 :param test:
581 :param err: error message
582
583 """
584 unittest.TestResult.addError(self, test, err)
585 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200586 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200587 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
588 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200589 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200590
Damjan Marionf56b77a2016-10-03 19:44:57 +0200591 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200592 """
593 Get test description
594
595 :param test:
596 :returns: test description
597
598 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200599 # TODO: if none print warning not raise exception
600 short_description = test.shortDescription()
601 if self.descriptions and short_description:
602 return short_description
603 else:
604 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200605
Damjan Marionf56b77a2016-10-03 19:44:57 +0200606 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200607 """
608 Start a test
609
610 :param test:
611
612 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200613 unittest.TestResult.startTest(self, test)
614 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200615 self.stream.writeln(
616 "Starting " + self.getDescription(test) + " ...")
617 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200618
Damjan Marionf56b77a2016-10-03 19:44:57 +0200619 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200620 """
621 Stop a test
622
623 :param test:
624
625 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200626 unittest.TestResult.stopTest(self, test)
627 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200628 self.stream.writeln(single_line_delim)
629 self.stream.writeln("%-60s%s" %
630 (self.getDescription(test), self.result_string))
631 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200632 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200633 self.stream.writeln("%-60s%s" %
634 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200635
Damjan Marionf56b77a2016-10-03 19:44:57 +0200636 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200637 """
638 Print errors from running the test case
639 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200640 self.stream.writeln()
641 self.printErrorList('ERROR', self.errors)
642 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200643
Damjan Marionf56b77a2016-10-03 19:44:57 +0200644 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200645 """
646 Print error list to the output stream together with error type
647 and test case description.
648
649 :param flavour: error type
650 :param errors: iterable errors
651
652 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200653 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200654 self.stream.writeln(double_line_delim)
655 self.stream.writeln("%s: %s" %
656 (flavour, self.getDescription(test)))
657 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200658 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200659
660
Damjan Marionf56b77a2016-10-03 19:44:57 +0200661class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200662 """
663 A basic test runner implementation which prints results on standard error.
664 """
665 @property
666 def resultclass(self):
667 """Class maintaining the results of the tests"""
668 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200669
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200671 """
672 Run the tests
673
674 :param test:
675
676 """
677 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200678 return super(VppTestRunner, self).run(test)