blob: c91f34f973ef9da508537b7a68bfe089c5513a83 [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
Klement Sekeraf62ae122016-10-11 11:47:09 +020043
Klement Sekerae4504c62016-12-08 10:16:41 +010044def pump_output(out, deque):
Klement Sekera277b89c2016-10-28 13:20:27 +020045 for line in iter(out.readline, b''):
Klement Sekerae4504c62016-12-08 10:16:41 +010046 deque.append(line)
Klement Sekera277b89c2016-10-28 13:20:27 +020047
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 Sekerae4504c62016-12-08 10:16:41 +0100188 cls.vpp_stdout_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100189 cls.vpp_stdout_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100190 cls.vpp.stdout, cls.vpp_stdout_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200191 cls.vpp_stdout_reader_thread.start()
Klement Sekerae4504c62016-12-08 10:16:41 +0100192 cls.vpp_stderr_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100193 cls.vpp_stderr_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100194 cls.vpp.stderr, cls.vpp_stderr_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200195 cls.vpp_stderr_reader_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100196 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100197 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 Sekera0529a742016-12-02 07:05:24 +0100213 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100214 try:
215 cls.quit()
216 except:
217 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100218 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200219
Damjan Marionf56b77a2016-10-03 19:44:57 +0200220 @classmethod
221 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200222 """
223 Disconnect vpp-api, kill vpp and cleanup shared memory files
224 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200225 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
226 cls.vpp.poll()
227 if cls.vpp.returncode is None:
228 print(double_line_delim)
229 print("VPP or GDB server is still running")
230 print(single_line_delim)
231 raw_input("When done debugging, press ENTER to kill the process"
232 " and finish running the testcase...")
233
Klement Sekeraf62ae122016-10-11 11:47:09 +0200234 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100235 if hasattr(cls, 'vapi'):
236 cls.vapi.disconnect()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200237 cls.vpp.poll()
238 if cls.vpp.returncode is None:
239 cls.vpp.terminate()
240 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200241
Klement Sekerae4504c62016-12-08 10:16:41 +0100242 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200243 cls.logger.info(single_line_delim)
244 cls.logger.info('VPP output to stdout while running %s:',
245 cls.__name__)
246 cls.logger.info(single_line_delim)
247 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100248 vpp_output = "".join(cls.vpp_stdout_deque)
249 f.write(vpp_output)
250 cls.logger.info('\n%s', vpp_output)
251 cls.logger.info(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200252
Klement Sekerae4504c62016-12-08 10:16:41 +0100253 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200254 cls.logger.info(single_line_delim)
255 cls.logger.info('VPP output to stderr while running %s:',
256 cls.__name__)
257 cls.logger.info(single_line_delim)
258 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100259 vpp_output = "".join(cls.vpp_stderr_deque)
260 f.write(vpp_output)
261 cls.logger.info('\n%s', vpp_output)
Klement Sekera277b89c2016-10-28 13:20:27 +0200262 cls.logger.info(single_line_delim)
263
Damjan Marionf56b77a2016-10-03 19:44:57 +0200264 @classmethod
265 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200266 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200267 cls.quit()
268
Damjan Marionf56b77a2016-10-03 19:44:57 +0200269 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200270 """ Show various debug prints after each test """
271 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200272 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000273 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200274 self.logger.info(self.vapi.ppcli("show hardware"))
275 self.logger.info(self.vapi.ppcli("show error"))
276 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200277
Damjan Marionf56b77a2016-10-03 19:44:57 +0200278 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200279 """ Clear trace before running each test"""
Klement Sekera0c1519b2016-12-08 05:03:32 +0100280 if self.vpp_dead:
281 raise Exception("VPP is dead when setting up the test")
Klement Sekerae4504c62016-12-08 10:16:41 +0100282 time.sleep(.1)
283 self.vpp_stdout_deque.append(
284 "--- test setUp() for %s.%s(%s) starts here ---\n" %
285 (self.__class__.__name__, self._testMethodName,
286 self._testMethodDoc))
287 self.vpp_stderr_deque.append(
288 "--- test setUp() for %s.%s(%s) starts here ---\n" %
289 (self.__class__.__name__, self._testMethodName,
290 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200291 self.vapi.cli("clear trace")
292 # store the test instance inside the test class - so that objects
293 # holding the class can access instance methods (like assertEqual)
294 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200295
Damjan Marionf56b77a2016-10-03 19:44:57 +0200296 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200297 def pg_enable_capture(cls, interfaces):
298 """
299 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200300
Klement Sekeraf62ae122016-10-11 11:47:09 +0200301 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200302
Klement Sekeraf62ae122016-10-11 11:47:09 +0200303 """
304 for i in interfaces:
305 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200306
Damjan Marionf56b77a2016-10-03 19:44:57 +0200307 @classmethod
Klement Sekerae4504c62016-12-08 10:16:41 +0100308 def pg_start(cls, sleep_time=1):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200309 """
310 Enable the packet-generator and send all prepared packet streams
311 Remove the packet streams afterwards
312 """
313 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
314 cls.vapi.cli('packet-generator enable')
Klement Sekerae4504c62016-12-08 10:16:41 +0100315 sleep(sleep_time) # give VPP some time to process the packets
Damjan Marionf56b77a2016-10-03 19:44:57 +0200316 for stream in cls.pg_streams:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200317 cls.vapi.cli('packet-generator delete %s' % stream)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200318 cls.pg_streams = []
319
Damjan Marionf56b77a2016-10-03 19:44:57 +0200320 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200321 def create_pg_interfaces(cls, interfaces):
322 """
323 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200324
Klement Sekeraf62ae122016-10-11 11:47:09 +0200325 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200326
Klement Sekeraf62ae122016-10-11 11:47:09 +0200327 """
328 result = []
329 for i in interfaces:
330 intf = VppPGInterface(cls, i)
331 setattr(cls, intf.name, intf)
332 result.append(intf)
333 cls.pg_interfaces = result
334 return result
335
Matej Klotton0178d522016-11-04 11:11:44 +0100336 @classmethod
337 def create_loopback_interfaces(cls, interfaces):
338 """
339 Create loopback interfaces
340
341 :param interfaces: iterable indexes of the interfaces
342
343 """
344 result = []
345 for i in interfaces:
346 intf = VppLoInterface(cls, i)
347 setattr(cls, intf.name, intf)
348 result.append(intf)
349 cls.lo_interfaces = result
350 return result
351
Damjan Marionf56b77a2016-10-03 19:44:57 +0200352 @staticmethod
353 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200354 """
355 Extend packet to given size by padding with spaces
356 NOTE: Currently works only when Raw layer is present.
357
358 :param packet: packet
359 :param size: target size
360
361 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200362 packet_len = len(packet) + 4
363 extend = size - packet_len
364 if extend > 0:
365 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200366
Damjan Marionf56b77a2016-10-03 19:44:57 +0200367 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200368 """
369 Add packet info to the testcase's packet info list
370
371 :param info: packet info
372
373 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200374 info.index = len(self.packet_infos)
375 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200376
Klement Sekeraf62ae122016-10-11 11:47:09 +0200377 def create_packet_info(self, src_pg_index, dst_pg_index):
378 """
379 Create packet info object containing the source and destination indexes
380 and add it to the testcase's packet info list
381
382 :param src_pg_index: source packet-generator index
383 :param dst_pg_index: destination packet-generator index
384
385 :returns: _PacketInfo object
386
387 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200388 info = _PacketInfo()
389 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200390 info.src = src_pg_index
391 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200392 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200393
Damjan Marionf56b77a2016-10-03 19:44:57 +0200394 @staticmethod
395 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200396 """
397 Convert _PacketInfo object to packet payload
398
399 :param info: _PacketInfo object
400
401 :returns: string containing serialized data from packet info
402 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200403 return "%d %d %d" % (info.index, info.src, info.dst)
404
Damjan Marionf56b77a2016-10-03 19:44:57 +0200405 @staticmethod
406 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200407 """
408 Convert packet payload to _PacketInfo object
409
410 :param payload: packet payload
411
412 :returns: _PacketInfo object containing de-serialized data from payload
413
414 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200415 numbers = payload.split()
416 info = _PacketInfo()
417 info.index = int(numbers[0])
418 info.src = int(numbers[1])
419 info.dst = int(numbers[2])
420 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200421
Damjan Marionf56b77a2016-10-03 19:44:57 +0200422 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200423 """
424 Iterate over the packet info list stored in the testcase
425 Start iteration with first element if info is None
426 Continue based on index in info if info is specified
427
428 :param info: info or None
429 :returns: next info in list or None if no more infos
430 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200431 if info is None:
432 next_index = 0
433 else:
434 next_index = info.index + 1
435 if next_index == len(self.packet_infos):
436 return None
437 else:
438 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200439
Klement Sekeraf62ae122016-10-11 11:47:09 +0200440 def get_next_packet_info_for_interface(self, src_index, info):
441 """
442 Search the packet info list for the next packet info with same source
443 interface index
444
445 :param src_index: source interface index to search for
446 :param info: packet info - where to start the search
447 :returns: packet info or None
448
449 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200450 while True:
451 info = self.get_next_packet_info(info)
452 if info is None:
453 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200454 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200455 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200456
Klement Sekeraf62ae122016-10-11 11:47:09 +0200457 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
458 """
459 Search the packet info list for the next packet info with same source
460 and destination interface indexes
461
462 :param src_index: source interface index to search for
463 :param dst_index: destination interface index to search for
464 :param info: packet info - where to start the search
465 :returns: packet info or None
466
467 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200468 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200469 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200470 if info is None:
471 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200472 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200473 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200474
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200475 def assert_equal(self, real_value, expected_value, name_or_class=None):
476 if name_or_class is None:
477 self.assertEqual(real_value, expected_value, msg)
478 return
479 try:
480 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
481 msg = msg % (getdoc(name_or_class).strip(),
482 real_value, str(name_or_class(real_value)),
483 expected_value, str(name_or_class(expected_value)))
484 except:
485 msg = "Invalid %s: %s does not match expected value %s" % (
486 name_or_class, real_value, expected_value)
487
488 self.assertEqual(real_value, expected_value, msg)
489
490 def assert_in_range(
491 self,
492 real_value,
493 expected_min,
494 expected_max,
495 name=None):
496 if name is None:
497 msg = None
498 else:
499 msg = "Invalid %s: %s out of range <%s,%s>" % (
500 name, real_value, expected_min, expected_max)
501 self.assertTrue(expected_min <= real_value <= expected_max, msg)
502
Damjan Marionf56b77a2016-10-03 19:44:57 +0200503
Damjan Marionf56b77a2016-10-03 19:44:57 +0200504class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200505 """
506 @property result_string
507 String variable to store the test case result string.
508 @property errors
509 List variable containing 2-tuples of TestCase instances and strings
510 holding formatted tracebacks. Each tuple represents a test which
511 raised an unexpected exception.
512 @property failures
513 List variable containing 2-tuples of TestCase instances and strings
514 holding formatted tracebacks. Each tuple represents a test where
515 a failure was explicitly signalled using the TestCase.assert*()
516 methods.
517 """
518
Damjan Marionf56b77a2016-10-03 19:44:57 +0200519 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200520 """
521 :param stream File descriptor to store where to report test results. Set
522 to the standard error stream by default.
523 :param descriptions Boolean variable to store information if to use test
524 case descriptions.
525 :param verbosity Integer variable to store required verbosity level.
526 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200527 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
528 self.stream = stream
529 self.descriptions = descriptions
530 self.verbosity = verbosity
531 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200532
Damjan Marionf56b77a2016-10-03 19:44:57 +0200533 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200534 """
535 Record a test succeeded result
536
537 :param test:
538
539 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200540 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200541 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200542
Klement Sekeraf62ae122016-10-11 11:47:09 +0200543 def addSkip(self, test, reason):
544 """
545 Record a test skipped.
546
547 :param test:
548 :param reason:
549
550 """
551 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200552 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200553
Damjan Marionf56b77a2016-10-03 19:44:57 +0200554 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200555 """
556 Record a test failed result
557
558 :param test:
559 :param err: error message
560
561 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200562 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200563 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200564 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200565 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
566 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200567 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200568
Damjan Marionf56b77a2016-10-03 19:44:57 +0200569 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200570 """
571 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200572
Klement Sekeraf62ae122016-10-11 11:47:09 +0200573 :param test:
574 :param err: error message
575
576 """
577 unittest.TestResult.addError(self, test, err)
578 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200579 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200580 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
581 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200582 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200583
Damjan Marionf56b77a2016-10-03 19:44:57 +0200584 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200585 """
586 Get test description
587
588 :param test:
589 :returns: test description
590
591 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200592 # TODO: if none print warning not raise exception
593 short_description = test.shortDescription()
594 if self.descriptions and short_description:
595 return short_description
596 else:
597 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200598
Damjan Marionf56b77a2016-10-03 19:44:57 +0200599 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200600 """
601 Start a test
602
603 :param test:
604
605 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200606 unittest.TestResult.startTest(self, test)
607 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200608 self.stream.writeln(
609 "Starting " + self.getDescription(test) + " ...")
610 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200611
Damjan Marionf56b77a2016-10-03 19:44:57 +0200612 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200613 """
614 Stop a test
615
616 :param test:
617
618 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200619 unittest.TestResult.stopTest(self, test)
620 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200621 self.stream.writeln(single_line_delim)
622 self.stream.writeln("%-60s%s" %
623 (self.getDescription(test), self.result_string))
624 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200625 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200626 self.stream.writeln("%-60s%s" %
627 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200628
Damjan Marionf56b77a2016-10-03 19:44:57 +0200629 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200630 """
631 Print errors from running the test case
632 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200633 self.stream.writeln()
634 self.printErrorList('ERROR', self.errors)
635 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200636
Damjan Marionf56b77a2016-10-03 19:44:57 +0200637 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200638 """
639 Print error list to the output stream together with error type
640 and test case description.
641
642 :param flavour: error type
643 :param errors: iterable errors
644
645 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200646 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200647 self.stream.writeln(double_line_delim)
648 self.stream.writeln("%s: %s" %
649 (flavour, self.getDescription(test)))
650 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200652
653
Damjan Marionf56b77a2016-10-03 19:44:57 +0200654class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200655 """
656 A basic test runner implementation which prints results on standard error.
657 """
658 @property
659 def resultclass(self):
660 """Class maintaining the results of the tests"""
661 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200662
Damjan Marionf56b77a2016-10-03 19:44:57 +0200663 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200664 """
665 Run the tests
666
667 :param test:
668
669 """
670 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200671 return super(VppTestRunner, self).run(test)