blob: 1c3e56cc827a8f160297308e7168e413a29ba543 [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
Klement Sekerae4504c62016-12-08 10:16:41 +01008from collections import deque
Klement Sekera277b89c2016-10-28 13:20:27 +02009from threading import Thread
Damjan Marionf56b77a2016-10-03 19:44:57 +020010from inspect import getdoc
Klement Sekera277b89c2016-10-28 13:20:27 +020011from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020012from vpp_pg_interface import VppPGInterface
Matej Klotton0178d522016-11-04 11:11:44 +010013from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020014from vpp_papi_provider import VppPapiProvider
Damjan Marionf56b77a2016-10-03 19:44:57 +020015from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020016from log import *
Klement Sekeraf62ae122016-10-11 11:47:09 +020017
18"""
19 Test framework module.
20
21 The module provides a set of tools for constructing and running tests and
22 representing the results.
23"""
24
Klement Sekeraf62ae122016-10-11 11:47:09 +020025
Damjan Marionf56b77a2016-10-03 19:44:57 +020026class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020027 """Private class to create packet info object.
28
29 Help process information about the next packet.
30 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020031 """
Matej Klotton86d87c42016-11-11 11:38:55 +010032 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020033 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010034 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020035 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010036 #: Store the index of the destination packet generator interface
37 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020038 dst = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010039 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020040 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020041
Matej Klotton16a14cd2016-12-07 15:09:13 +010042 def __eq__(self, other):
43 index = self.index == other.index
44 src = self.src == other.src
45 dst = self.dst == other.dst
46 data = self.data == other.data
47 return index and src and dst and data
48
Klement Sekeraf62ae122016-10-11 11:47:09 +020049
Klement Sekerae4504c62016-12-08 10:16:41 +010050def pump_output(out, deque):
Klement Sekera277b89c2016-10-28 13:20:27 +020051 for line in iter(out.readline, b''):
Klement Sekerae4504c62016-12-08 10:16:41 +010052 deque.append(line)
Klement Sekera277b89c2016-10-28 13:20:27 +020053
54
Damjan Marionf56b77a2016-10-03 19:44:57 +020055class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010056 """This subclass is a base class for VPP test cases that are implemented as
57 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020058 """
59
60 @property
61 def packet_infos(self):
62 """List of packet infos"""
63 return self._packet_infos
64
65 @packet_infos.setter
66 def packet_infos(self, value):
67 self._packet_infos = value
68
69 @classmethod
70 def instance(cls):
71 """Return the instance of this testcase"""
72 return cls.test_instance
73
Damjan Marionf56b77a2016-10-03 19:44:57 +020074 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +020075 def set_debug_flags(cls, d):
76 cls.debug_core = False
77 cls.debug_gdb = False
78 cls.debug_gdbserver = False
79 if d is None:
80 return
81 dl = d.lower()
82 if dl == "core":
83 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
84 # give a heads up if this is actually useless
85 cls.logger.critical("WARNING: core size limit is set 0, core "
86 "files will NOT be created")
87 cls.debug_core = True
88 elif dl == "gdb":
89 cls.debug_gdb = True
90 elif dl == "gdbserver":
91 cls.debug_gdbserver = True
92 else:
93 raise Exception("Unrecognized DEBUG option: '%s'" % d)
94
95 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +020096 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +020097 """ Set-up the test case class based on environment variables """
98 try:
Klement Sekera277b89c2016-10-28 13:20:27 +020099 s = os.getenv("STEP")
100 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200101 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200102 cls.step = False
103 try:
104 d = os.getenv("DEBUG")
105 except:
106 d = None
107 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200108 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100109 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100110 debug_cli = ""
111 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
112 debug_cli = "cli-listen localhost:5002"
113 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200114 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100115 if cls.plugin_path is not None:
116 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200117 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
118
119 @classmethod
120 def wait_for_enter(cls):
121 if cls.debug_gdbserver:
122 print(double_line_delim)
123 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
124 elif cls.debug_gdb:
125 print(double_line_delim)
126 print("Spawned VPP with PID: %d" % cls.vpp.pid)
127 else:
128 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
129 return
130 print(single_line_delim)
131 print("You can debug the VPP using e.g.:")
132 if cls.debug_gdbserver:
133 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
134 print("Now is the time to attach a gdb by running the above "
135 "command, set up breakpoints etc. and then resume VPP from "
136 "within gdb by issuing the 'continue' command")
137 elif cls.debug_gdb:
138 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
139 print("Now is the time to attach a gdb by running the above "
140 "command and set up breakpoints etc.")
141 print(single_line_delim)
142 raw_input("Press ENTER to continue running the testcase...")
143
144 @classmethod
145 def run_vpp(cls):
146 cmdline = cls.vpp_cmdline
147
148 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100149 gdbserver = '/usr/bin/gdbserver'
150 if not os.path.isfile(gdbserver) or \
151 not os.access(gdbserver, os.X_OK):
152 raise Exception("gdbserver binary '%s' does not exist or is "
153 "not executable" % gdbserver)
154
155 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200156 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
157
Klement Sekera931be3a2016-11-03 05:36:01 +0100158 try:
159 cls.vpp = subprocess.Popen(cmdline,
160 stdout=subprocess.PIPE,
161 stderr=subprocess.PIPE,
162 bufsize=1)
163 except Exception as e:
164 cls.logger.critical("Couldn't start vpp: %s" % e)
165 raise
166
Klement Sekera277b89c2016-10-28 13:20:27 +0200167 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100168
Damjan Marionf56b77a2016-10-03 19:44:57 +0200169 @classmethod
170 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200171 """
172 Perform class setup before running the testcase
173 Remove shared memory files, start vpp and connect the vpp-api
174 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200175 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200176 cls.tempdir = tempfile.mkdtemp(
177 prefix='vpp-unittest-' + cls.__name__ + '-')
178 cls.shm_prefix = cls.tempdir.split("/")[-1]
179 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200180 cls.logger.info("Temporary dir is %s, shm prefix is %s",
181 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200182 cls.setUpConstants()
Klement Sekera9225dee2016-12-12 08:36:58 +0100183 cls._captures = []
184 cls._zombie_captures = []
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 Sekera9225dee2016-12-12 08:36:58 +0100315 def register_capture(cls, cap_name):
316 """ Register a capture in the testclass """
317 # add to the list of captures with current timestamp
318 cls._captures.append((time.time(), cap_name))
319 # filter out from zombies
320 cls._zombie_captures = [(stamp, name)
321 for (stamp, name) in cls._zombie_captures
322 if name != cap_name]
323
324 @classmethod
325 def pg_start(cls):
326 """ Remove any zombie captures and enable the packet generator """
327 # how long before capture is allowed to be deleted - otherwise vpp
328 # crashes - 100ms seems enough (this shouldn't be needed at all)
329 capture_ttl = 0.1
330 now = time.time()
331 for stamp, cap_name in cls._zombie_captures:
332 wait = stamp + capture_ttl - now
333 if wait > 0:
334 cls.logger.debug("Waiting for %ss before deleting capture %s",
335 wait, cap_name)
336 time.sleep(wait)
337 now = time.time()
338 cls.logger.debug("Removing zombie capture %s" % cap_name)
339 cls.vapi.cli('packet-generator delete %s' % cap_name)
340
Klement Sekeraf62ae122016-10-11 11:47:09 +0200341 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
342 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100343 cls._zombie_captures = cls._captures
344 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200345
Damjan Marionf56b77a2016-10-03 19:44:57 +0200346 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200347 def create_pg_interfaces(cls, interfaces):
348 """
349 Create packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200350
Klement Sekeraf62ae122016-10-11 11:47:09 +0200351 :param interfaces: iterable indexes of the interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200352
Klement Sekeraf62ae122016-10-11 11:47:09 +0200353 """
354 result = []
355 for i in interfaces:
356 intf = VppPGInterface(cls, i)
357 setattr(cls, intf.name, intf)
358 result.append(intf)
359 cls.pg_interfaces = result
360 return result
361
Matej Klotton0178d522016-11-04 11:11:44 +0100362 @classmethod
363 def create_loopback_interfaces(cls, interfaces):
364 """
365 Create loopback interfaces
366
367 :param interfaces: iterable indexes of the interfaces
368
369 """
370 result = []
371 for i in interfaces:
372 intf = VppLoInterface(cls, i)
373 setattr(cls, intf.name, intf)
374 result.append(intf)
375 cls.lo_interfaces = result
376 return result
377
Damjan Marionf56b77a2016-10-03 19:44:57 +0200378 @staticmethod
379 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200380 """
381 Extend packet to given size by padding with spaces
382 NOTE: Currently works only when Raw layer is present.
383
384 :param packet: packet
385 :param size: target size
386
387 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200388 packet_len = len(packet) + 4
389 extend = size - packet_len
390 if extend > 0:
391 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200392
Damjan Marionf56b77a2016-10-03 19:44:57 +0200393 def add_packet_info_to_list(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200394 """
395 Add packet info to the testcase's packet info list
396
397 :param info: packet info
398
399 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200400 info.index = len(self.packet_infos)
401 self.packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200402
Klement Sekeraf62ae122016-10-11 11:47:09 +0200403 def create_packet_info(self, src_pg_index, dst_pg_index):
404 """
405 Create packet info object containing the source and destination indexes
406 and add it to the testcase's packet info list
407
408 :param src_pg_index: source packet-generator index
409 :param dst_pg_index: destination packet-generator index
410
411 :returns: _PacketInfo object
412
413 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200414 info = _PacketInfo()
415 self.add_packet_info_to_list(info)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200416 info.src = src_pg_index
417 info.dst = dst_pg_index
Damjan Marionf56b77a2016-10-03 19:44:57 +0200418 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200419
Damjan Marionf56b77a2016-10-03 19:44:57 +0200420 @staticmethod
421 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200422 """
423 Convert _PacketInfo object to packet payload
424
425 :param info: _PacketInfo object
426
427 :returns: string containing serialized data from packet info
428 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200429 return "%d %d %d" % (info.index, info.src, info.dst)
430
Damjan Marionf56b77a2016-10-03 19:44:57 +0200431 @staticmethod
432 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200433 """
434 Convert packet payload to _PacketInfo object
435
436 :param payload: packet payload
437
438 :returns: _PacketInfo object containing de-serialized data from payload
439
440 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200441 numbers = payload.split()
442 info = _PacketInfo()
443 info.index = int(numbers[0])
444 info.src = int(numbers[1])
445 info.dst = int(numbers[2])
446 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200447
Damjan Marionf56b77a2016-10-03 19:44:57 +0200448 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200449 """
450 Iterate over the packet info list stored in the testcase
451 Start iteration with first element if info is None
452 Continue based on index in info if info is specified
453
454 :param info: info or None
455 :returns: next info in list or None if no more infos
456 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200457 if info is None:
458 next_index = 0
459 else:
460 next_index = info.index + 1
461 if next_index == len(self.packet_infos):
462 return None
463 else:
464 return self.packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200465
Klement Sekeraf62ae122016-10-11 11:47:09 +0200466 def get_next_packet_info_for_interface(self, src_index, info):
467 """
468 Search the packet info list for the next packet info with same source
469 interface index
470
471 :param src_index: source interface index to search for
472 :param info: packet info - where to start the search
473 :returns: packet info or None
474
475 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200476 while True:
477 info = self.get_next_packet_info(info)
478 if info is None:
479 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200480 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200481 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200482
Klement Sekeraf62ae122016-10-11 11:47:09 +0200483 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
484 """
485 Search the packet info list for the next packet info with same source
486 and destination interface indexes
487
488 :param src_index: source interface index to search for
489 :param dst_index: destination interface index to search for
490 :param info: packet info - where to start the search
491 :returns: packet info or None
492
493 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200494 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200495 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200496 if info is None:
497 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200498 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200499 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200500
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200501 def assert_equal(self, real_value, expected_value, name_or_class=None):
502 if name_or_class is None:
503 self.assertEqual(real_value, expected_value, msg)
504 return
505 try:
506 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
507 msg = msg % (getdoc(name_or_class).strip(),
508 real_value, str(name_or_class(real_value)),
509 expected_value, str(name_or_class(expected_value)))
510 except:
511 msg = "Invalid %s: %s does not match expected value %s" % (
512 name_or_class, real_value, expected_value)
513
514 self.assertEqual(real_value, expected_value, msg)
515
516 def assert_in_range(
517 self,
518 real_value,
519 expected_min,
520 expected_max,
521 name=None):
522 if name is None:
523 msg = None
524 else:
525 msg = "Invalid %s: %s out of range <%s,%s>" % (
526 name, real_value, expected_min, expected_max)
527 self.assertTrue(expected_min <= real_value <= expected_max, msg)
528
Damjan Marionf56b77a2016-10-03 19:44:57 +0200529
Damjan Marionf56b77a2016-10-03 19:44:57 +0200530class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200531 """
532 @property result_string
533 String variable to store the test case result string.
534 @property errors
535 List variable containing 2-tuples of TestCase instances and strings
536 holding formatted tracebacks. Each tuple represents a test which
537 raised an unexpected exception.
538 @property failures
539 List variable containing 2-tuples of TestCase instances and strings
540 holding formatted tracebacks. Each tuple represents a test where
541 a failure was explicitly signalled using the TestCase.assert*()
542 methods.
543 """
544
Damjan Marionf56b77a2016-10-03 19:44:57 +0200545 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200546 """
547 :param stream File descriptor to store where to report test results. Set
548 to the standard error stream by default.
549 :param descriptions Boolean variable to store information if to use test
550 case descriptions.
551 :param verbosity Integer variable to store required verbosity level.
552 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200553 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
554 self.stream = stream
555 self.descriptions = descriptions
556 self.verbosity = verbosity
557 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200558
Damjan Marionf56b77a2016-10-03 19:44:57 +0200559 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200560 """
561 Record a test succeeded result
562
563 :param test:
564
565 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200566 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200567 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200568
Klement Sekeraf62ae122016-10-11 11:47:09 +0200569 def addSkip(self, test, reason):
570 """
571 Record a test skipped.
572
573 :param test:
574 :param reason:
575
576 """
577 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200578 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200579
Damjan Marionf56b77a2016-10-03 19:44:57 +0200580 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200581 """
582 Record a test failed result
583
584 :param test:
585 :param err: error message
586
587 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200588 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200589 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200590 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200591 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
592 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200593 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200594
Damjan Marionf56b77a2016-10-03 19:44:57 +0200595 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200596 """
597 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200598
Klement Sekeraf62ae122016-10-11 11:47:09 +0200599 :param test:
600 :param err: error message
601
602 """
603 unittest.TestResult.addError(self, test, err)
604 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200605 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200606 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
607 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200608 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200609
Damjan Marionf56b77a2016-10-03 19:44:57 +0200610 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200611 """
612 Get test description
613
614 :param test:
615 :returns: test description
616
617 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200618 # TODO: if none print warning not raise exception
619 short_description = test.shortDescription()
620 if self.descriptions and short_description:
621 return short_description
622 else:
623 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200624
Damjan Marionf56b77a2016-10-03 19:44:57 +0200625 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200626 """
627 Start a test
628
629 :param test:
630
631 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200632 unittest.TestResult.startTest(self, test)
633 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200634 self.stream.writeln(
635 "Starting " + self.getDescription(test) + " ...")
636 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200637
Damjan Marionf56b77a2016-10-03 19:44:57 +0200638 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200639 """
640 Stop a test
641
642 :param test:
643
644 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200645 unittest.TestResult.stopTest(self, test)
646 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200647 self.stream.writeln(single_line_delim)
648 self.stream.writeln("%-60s%s" %
649 (self.getDescription(test), self.result_string))
650 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200652 self.stream.writeln("%-60s%s" %
653 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200654
Damjan Marionf56b77a2016-10-03 19:44:57 +0200655 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200656 """
657 Print errors from running the test case
658 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200659 self.stream.writeln()
660 self.printErrorList('ERROR', self.errors)
661 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200662
Damjan Marionf56b77a2016-10-03 19:44:57 +0200663 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200664 """
665 Print error list to the output stream together with error type
666 and test case description.
667
668 :param flavour: error type
669 :param errors: iterable errors
670
671 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200672 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200673 self.stream.writeln(double_line_delim)
674 self.stream.writeln("%s: %s" %
675 (flavour, self.getDescription(test)))
676 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200677 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200678
679
Damjan Marionf56b77a2016-10-03 19:44:57 +0200680class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200681 """
682 A basic test runner implementation which prints results on standard error.
683 """
684 @property
685 def resultclass(self):
686 """Class maintaining the results of the tests"""
687 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200688
Damjan Marionf56b77a2016-10-03 19:44:57 +0200689 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200690 """
691 Run the tests
692
693 :param test:
694
695 """
696 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200697 return super(VppTestRunner, self).run(test)