blob: e364a8f58be423f5e8d339bf54964f18158e9d2e [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
Klement Sekeradab231a2016-12-21 08:50:14 +010013from vpp_sub_interface import VppSubInterface
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 Sekera6c7440c2016-12-23 09:16:39 +010017from logging import FileHandler, DEBUG
Klement Sekera277b89c2016-10-28 13:20:27 +020018from log import *
Klement Sekeraf62ae122016-10-11 11:47:09 +020019
20"""
21 Test framework module.
22
23 The module provides a set of tools for constructing and running tests and
24 representing the results.
25"""
26
Klement Sekeraf62ae122016-10-11 11:47:09 +020027
Damjan Marionf56b77a2016-10-03 19:44:57 +020028class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020029 """Private class to create packet info object.
30
31 Help process information about the next packet.
32 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020033 """
Matej Klotton86d87c42016-11-11 11:38:55 +010034 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020035 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010036 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020037 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010038 #: Store the index of the destination packet generator interface
39 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020040 dst = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010041 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020042 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020043
Matej Klotton16a14cd2016-12-07 15:09:13 +010044 def __eq__(self, other):
45 index = self.index == other.index
46 src = self.src == other.src
47 dst = self.dst == other.dst
48 data = self.data == other.data
49 return index and src and dst and data
50
Klement Sekeraf62ae122016-10-11 11:47:09 +020051
Klement Sekerae4504c62016-12-08 10:16:41 +010052def pump_output(out, deque):
Klement Sekera277b89c2016-10-28 13:20:27 +020053 for line in iter(out.readline, b''):
Klement Sekerae4504c62016-12-08 10:16:41 +010054 deque.append(line)
Klement Sekera277b89c2016-10-28 13:20:27 +020055
56
Damjan Marionf56b77a2016-10-03 19:44:57 +020057class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010058 """This subclass is a base class for VPP test cases that are implemented as
59 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020060 """
61
62 @property
63 def packet_infos(self):
64 """List of packet infos"""
65 return self._packet_infos
66
Klement Sekeradab231a2016-12-21 08:50:14 +010067 @classmethod
68 def get_packet_count_for_if_idx(cls, dst_if_index):
69 """Get the number of packet info for specified destination if index"""
70 if dst_if_index in cls._packet_count_for_dst_if_idx:
71 return cls._packet_count_for_dst_if_idx[dst_if_index]
72 else:
73 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +020074
75 @classmethod
76 def instance(cls):
77 """Return the instance of this testcase"""
78 return cls.test_instance
79
Damjan Marionf56b77a2016-10-03 19:44:57 +020080 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +020081 def set_debug_flags(cls, d):
82 cls.debug_core = False
83 cls.debug_gdb = False
84 cls.debug_gdbserver = False
85 if d is None:
86 return
87 dl = d.lower()
88 if dl == "core":
89 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
90 # give a heads up if this is actually useless
91 cls.logger.critical("WARNING: core size limit is set 0, core "
92 "files will NOT be created")
93 cls.debug_core = True
94 elif dl == "gdb":
95 cls.debug_gdb = True
96 elif dl == "gdbserver":
97 cls.debug_gdbserver = True
98 else:
99 raise Exception("Unrecognized DEBUG option: '%s'" % d)
100
101 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200102 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200103 """ Set-up the test case class based on environment variables """
104 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200105 s = os.getenv("STEP")
106 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200107 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200108 cls.step = False
109 try:
110 d = os.getenv("DEBUG")
111 except:
112 d = None
113 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200114 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100115 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100116 debug_cli = ""
117 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
118 debug_cli = "cli-listen localhost:5002"
119 cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200120 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100121 if cls.plugin_path is not None:
122 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200123 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
124
125 @classmethod
126 def wait_for_enter(cls):
127 if cls.debug_gdbserver:
128 print(double_line_delim)
129 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
130 elif cls.debug_gdb:
131 print(double_line_delim)
132 print("Spawned VPP with PID: %d" % cls.vpp.pid)
133 else:
134 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
135 return
136 print(single_line_delim)
137 print("You can debug the VPP using e.g.:")
138 if cls.debug_gdbserver:
139 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
140 print("Now is the time to attach a gdb by running the above "
141 "command, set up breakpoints etc. and then resume VPP from "
142 "within gdb by issuing the 'continue' command")
143 elif cls.debug_gdb:
144 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
145 print("Now is the time to attach a gdb by running the above "
146 "command and set up breakpoints etc.")
147 print(single_line_delim)
148 raw_input("Press ENTER to continue running the testcase...")
149
150 @classmethod
151 def run_vpp(cls):
152 cmdline = cls.vpp_cmdline
153
154 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100155 gdbserver = '/usr/bin/gdbserver'
156 if not os.path.isfile(gdbserver) or \
157 not os.access(gdbserver, os.X_OK):
158 raise Exception("gdbserver binary '%s' does not exist or is "
159 "not executable" % gdbserver)
160
161 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200162 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
163
Klement Sekera931be3a2016-11-03 05:36:01 +0100164 try:
165 cls.vpp = subprocess.Popen(cmdline,
166 stdout=subprocess.PIPE,
167 stderr=subprocess.PIPE,
168 bufsize=1)
169 except Exception as e:
170 cls.logger.critical("Couldn't start vpp: %s" % e)
171 raise
172
Klement Sekera277b89c2016-10-28 13:20:27 +0200173 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100174
Damjan Marionf56b77a2016-10-03 19:44:57 +0200175 @classmethod
176 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200177 """
178 Perform class setup before running the testcase
179 Remove shared memory files, start vpp and connect the vpp-api
180 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200181 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200182 cls.tempdir = tempfile.mkdtemp(
183 prefix='vpp-unittest-' + cls.__name__ + '-')
Klement Sekera6c7440c2016-12-23 09:16:39 +0100184 file_handler = FileHandler("%s/log.txt" % cls.tempdir)
185 file_handler.setLevel(DEBUG)
186 cls.logger.addHandler(file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200187 cls.shm_prefix = cls.tempdir.split("/")[-1]
188 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200189 cls.logger.info("Temporary dir is %s, shm prefix is %s",
190 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200191 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100192 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100193 cls._captures = []
194 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200195 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100196 cls.vpp_dead = False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200197 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100198 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200199 print(double_line_delim)
200 # need to catch exceptions here because if we raise, then the cleanup
201 # doesn't get called and we might end with a zombie vpp
202 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200203 cls.run_vpp()
Klement Sekerae4504c62016-12-08 10:16:41 +0100204 cls.vpp_stdout_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100205 cls.vpp_stdout_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100206 cls.vpp.stdout, cls.vpp_stdout_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200207 cls.vpp_stdout_reader_thread.start()
Klement Sekerae4504c62016-12-08 10:16:41 +0100208 cls.vpp_stderr_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100209 cls.vpp_stderr_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100210 cls.vpp.stderr, cls.vpp_stderr_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200211 cls.vpp_stderr_reader_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100212 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100213 if cls.step:
214 hook = StepHook(cls)
215 else:
216 hook = PollHook(cls)
217 cls.vapi.register_hook(hook)
218 time.sleep(0.1)
219 hook.poll_vpp()
220 try:
221 cls.vapi.connect()
222 except:
223 if cls.debug_gdbserver:
224 print(colorize("You're running VPP inside gdbserver but "
225 "VPP-API connection failed, did you forget "
226 "to 'continue' VPP from within gdb?", RED))
227 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200228 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100229 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100230 try:
231 cls.quit()
232 except:
233 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100234 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200235
Damjan Marionf56b77a2016-10-03 19:44:57 +0200236 @classmethod
237 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200238 """
239 Disconnect vpp-api, kill vpp and cleanup shared memory files
240 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200241 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
242 cls.vpp.poll()
243 if cls.vpp.returncode is None:
244 print(double_line_delim)
245 print("VPP or GDB server is still running")
246 print(single_line_delim)
247 raw_input("When done debugging, press ENTER to kill the process"
248 " and finish running the testcase...")
249
Klement Sekeraf62ae122016-10-11 11:47:09 +0200250 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100251 if hasattr(cls, 'vapi'):
252 cls.vapi.disconnect()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200253 cls.vpp.poll()
254 if cls.vpp.returncode is None:
255 cls.vpp.terminate()
256 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200257
Klement Sekerae4504c62016-12-08 10:16:41 +0100258 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200259 cls.logger.info(single_line_delim)
260 cls.logger.info('VPP output to stdout while running %s:',
261 cls.__name__)
262 cls.logger.info(single_line_delim)
263 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100264 vpp_output = "".join(cls.vpp_stdout_deque)
265 f.write(vpp_output)
266 cls.logger.info('\n%s', vpp_output)
267 cls.logger.info(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200268
Klement Sekerae4504c62016-12-08 10:16:41 +0100269 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200270 cls.logger.info(single_line_delim)
271 cls.logger.info('VPP output to stderr while running %s:',
272 cls.__name__)
273 cls.logger.info(single_line_delim)
274 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100275 vpp_output = "".join(cls.vpp_stderr_deque)
276 f.write(vpp_output)
277 cls.logger.info('\n%s', vpp_output)
Klement Sekera277b89c2016-10-28 13:20:27 +0200278 cls.logger.info(single_line_delim)
279
Damjan Marionf56b77a2016-10-03 19:44:57 +0200280 @classmethod
281 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200282 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200283 cls.quit()
284
Damjan Marionf56b77a2016-10-03 19:44:57 +0200285 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200286 """ Show various debug prints after each test """
287 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200288 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000289 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200290 self.logger.info(self.vapi.ppcli("show hardware"))
291 self.logger.info(self.vapi.ppcli("show error"))
292 self.logger.info(self.vapi.ppcli("show run"))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200293
Damjan Marionf56b77a2016-10-03 19:44:57 +0200294 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200295 """ Clear trace before running each test"""
Klement Sekera0c1519b2016-12-08 05:03:32 +0100296 if self.vpp_dead:
297 raise Exception("VPP is dead when setting up the test")
Klement Sekerae4504c62016-12-08 10:16:41 +0100298 time.sleep(.1)
299 self.vpp_stdout_deque.append(
300 "--- test setUp() for %s.%s(%s) starts here ---\n" %
301 (self.__class__.__name__, self._testMethodName,
302 self._testMethodDoc))
303 self.vpp_stderr_deque.append(
304 "--- test setUp() for %s.%s(%s) starts here ---\n" %
305 (self.__class__.__name__, self._testMethodName,
306 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200307 self.vapi.cli("clear trace")
308 # store the test instance inside the test class - so that objects
309 # holding the class can access instance methods (like assertEqual)
310 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200311
Damjan Marionf56b77a2016-10-03 19:44:57 +0200312 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200313 def pg_enable_capture(cls, interfaces):
314 """
315 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200316
Klement Sekeraf62ae122016-10-11 11:47:09 +0200317 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200318
Klement Sekeraf62ae122016-10-11 11:47:09 +0200319 """
320 for i in interfaces:
321 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200322
Damjan Marionf56b77a2016-10-03 19:44:57 +0200323 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100324 def register_capture(cls, cap_name):
325 """ Register a capture in the testclass """
326 # add to the list of captures with current timestamp
327 cls._captures.append((time.time(), cap_name))
328 # filter out from zombies
329 cls._zombie_captures = [(stamp, name)
330 for (stamp, name) in cls._zombie_captures
331 if name != cap_name]
332
333 @classmethod
334 def pg_start(cls):
335 """ Remove any zombie captures and enable the packet generator """
336 # how long before capture is allowed to be deleted - otherwise vpp
337 # crashes - 100ms seems enough (this shouldn't be needed at all)
338 capture_ttl = 0.1
339 now = time.time()
340 for stamp, cap_name in cls._zombie_captures:
341 wait = stamp + capture_ttl - now
342 if wait > 0:
343 cls.logger.debug("Waiting for %ss before deleting capture %s",
344 wait, cap_name)
345 time.sleep(wait)
346 now = time.time()
347 cls.logger.debug("Removing zombie capture %s" % cap_name)
348 cls.vapi.cli('packet-generator delete %s' % cap_name)
349
Klement Sekeraf62ae122016-10-11 11:47:09 +0200350 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
351 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100352 cls._zombie_captures = cls._captures
353 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200354
Damjan Marionf56b77a2016-10-03 19:44:57 +0200355 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200356 def create_pg_interfaces(cls, interfaces):
357 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100358 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200359
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100360 :param interfaces: iterable indexes of the interfaces.
361 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200362
Klement Sekeraf62ae122016-10-11 11:47:09 +0200363 """
364 result = []
365 for i in interfaces:
366 intf = VppPGInterface(cls, i)
367 setattr(cls, intf.name, intf)
368 result.append(intf)
369 cls.pg_interfaces = result
370 return result
371
Matej Klotton0178d522016-11-04 11:11:44 +0100372 @classmethod
373 def create_loopback_interfaces(cls, interfaces):
374 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100375 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100376
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100377 :param interfaces: iterable indexes of the interfaces.
378 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100379 """
380 result = []
381 for i in interfaces:
382 intf = VppLoInterface(cls, i)
383 setattr(cls, intf.name, intf)
384 result.append(intf)
385 cls.lo_interfaces = result
386 return result
387
Damjan Marionf56b77a2016-10-03 19:44:57 +0200388 @staticmethod
389 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200390 """
391 Extend packet to given size by padding with spaces
392 NOTE: Currently works only when Raw layer is present.
393
394 :param packet: packet
395 :param size: target size
396
397 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200398 packet_len = len(packet) + 4
399 extend = size - packet_len
400 if extend > 0:
401 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200402
Klement Sekeradab231a2016-12-21 08:50:14 +0100403 @classmethod
404 def reset_packet_infos(cls):
405 """ Reset the list of packet info objects and packet counts to zero """
406 cls._packet_infos = {}
407 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200408
Klement Sekeradab231a2016-12-21 08:50:14 +0100409 @classmethod
410 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200411 """
412 Create packet info object containing the source and destination indexes
413 and add it to the testcase's packet info list
414
Klement Sekeradab231a2016-12-21 08:50:14 +0100415 :param VppInterface src_if: source interface
416 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200417
418 :returns: _PacketInfo object
419
420 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200421 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100422 info.index = len(cls._packet_infos)
423 info.src = src_if.sw_if_index
424 info.dst = dst_if.sw_if_index
425 if isinstance(dst_if, VppSubInterface):
426 dst_idx = dst_if.parent.sw_if_index
427 else:
428 dst_idx = dst_if.sw_if_index
429 if dst_idx in cls._packet_count_for_dst_if_idx:
430 cls._packet_count_for_dst_if_idx[dst_idx] += 1
431 else:
432 cls._packet_count_for_dst_if_idx[dst_idx] = 1
433 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200434 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200435
Damjan Marionf56b77a2016-10-03 19:44:57 +0200436 @staticmethod
437 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200438 """
439 Convert _PacketInfo object to packet payload
440
441 :param info: _PacketInfo object
442
443 :returns: string containing serialized data from packet info
444 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200445 return "%d %d %d" % (info.index, info.src, info.dst)
446
Damjan Marionf56b77a2016-10-03 19:44:57 +0200447 @staticmethod
448 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200449 """
450 Convert packet payload to _PacketInfo object
451
452 :param payload: packet payload
453
454 :returns: _PacketInfo object containing de-serialized data from payload
455
456 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200457 numbers = payload.split()
458 info = _PacketInfo()
459 info.index = int(numbers[0])
460 info.src = int(numbers[1])
461 info.dst = int(numbers[2])
462 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200463
Damjan Marionf56b77a2016-10-03 19:44:57 +0200464 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200465 """
466 Iterate over the packet info list stored in the testcase
467 Start iteration with first element if info is None
468 Continue based on index in info if info is specified
469
470 :param info: info or None
471 :returns: next info in list or None if no more infos
472 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200473 if info is None:
474 next_index = 0
475 else:
476 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100477 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200478 return None
479 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100480 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200481
Klement Sekeraf62ae122016-10-11 11:47:09 +0200482 def get_next_packet_info_for_interface(self, src_index, info):
483 """
484 Search the packet info list for the next packet info with same source
485 interface index
486
487 :param src_index: source interface index to search for
488 :param info: packet info - where to start the search
489 :returns: packet info or None
490
491 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200492 while True:
493 info = self.get_next_packet_info(info)
494 if info is None:
495 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200496 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200497 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200498
Klement Sekeraf62ae122016-10-11 11:47:09 +0200499 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
500 """
501 Search the packet info list for the next packet info with same source
502 and destination interface indexes
503
504 :param src_index: source interface index to search for
505 :param dst_index: destination interface index to search for
506 :param info: packet info - where to start the search
507 :returns: packet info or None
508
509 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200510 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200511 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200512 if info is None:
513 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200514 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200515 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200516
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200517 def assert_equal(self, real_value, expected_value, name_or_class=None):
518 if name_or_class is None:
519 self.assertEqual(real_value, expected_value, msg)
520 return
521 try:
522 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
523 msg = msg % (getdoc(name_or_class).strip(),
524 real_value, str(name_or_class(real_value)),
525 expected_value, str(name_or_class(expected_value)))
526 except:
527 msg = "Invalid %s: %s does not match expected value %s" % (
528 name_or_class, real_value, expected_value)
529
530 self.assertEqual(real_value, expected_value, msg)
531
532 def assert_in_range(
533 self,
534 real_value,
535 expected_min,
536 expected_max,
537 name=None):
538 if name is None:
539 msg = None
540 else:
541 msg = "Invalid %s: %s out of range <%s,%s>" % (
542 name, real_value, expected_min, expected_max)
543 self.assertTrue(expected_min <= real_value <= expected_max, msg)
544
Damjan Marionf56b77a2016-10-03 19:44:57 +0200545
Damjan Marionf56b77a2016-10-03 19:44:57 +0200546class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200547 """
548 @property result_string
549 String variable to store the test case result string.
550 @property errors
551 List variable containing 2-tuples of TestCase instances and strings
552 holding formatted tracebacks. Each tuple represents a test which
553 raised an unexpected exception.
554 @property failures
555 List variable containing 2-tuples of TestCase instances and strings
556 holding formatted tracebacks. Each tuple represents a test where
557 a failure was explicitly signalled using the TestCase.assert*()
558 methods.
559 """
560
Damjan Marionf56b77a2016-10-03 19:44:57 +0200561 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200562 """
563 :param stream File descriptor to store where to report test results. Set
564 to the standard error stream by default.
565 :param descriptions Boolean variable to store information if to use test
566 case descriptions.
567 :param verbosity Integer variable to store required verbosity level.
568 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200569 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
570 self.stream = stream
571 self.descriptions = descriptions
572 self.verbosity = verbosity
573 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200574
Damjan Marionf56b77a2016-10-03 19:44:57 +0200575 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200576 """
577 Record a test succeeded result
578
579 :param test:
580
581 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200582 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200583 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200584
Klement Sekeraf62ae122016-10-11 11:47:09 +0200585 def addSkip(self, test, reason):
586 """
587 Record a test skipped.
588
589 :param test:
590 :param reason:
591
592 """
593 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200594 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200595
Damjan Marionf56b77a2016-10-03 19:44:57 +0200596 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200597 """
598 Record a test failed result
599
600 :param test:
601 :param err: error message
602
603 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200604 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200605 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200606 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200607 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
608 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200609 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200610
Damjan Marionf56b77a2016-10-03 19:44:57 +0200611 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200612 """
613 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200614
Klement Sekeraf62ae122016-10-11 11:47:09 +0200615 :param test:
616 :param err: error message
617
618 """
619 unittest.TestResult.addError(self, test, err)
620 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200621 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200622 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
623 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200624 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200625
Damjan Marionf56b77a2016-10-03 19:44:57 +0200626 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200627 """
628 Get test description
629
630 :param test:
631 :returns: test description
632
633 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200634 # TODO: if none print warning not raise exception
635 short_description = test.shortDescription()
636 if self.descriptions and short_description:
637 return short_description
638 else:
639 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200640
Damjan Marionf56b77a2016-10-03 19:44:57 +0200641 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200642 """
643 Start a test
644
645 :param test:
646
647 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200648 unittest.TestResult.startTest(self, test)
649 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200650 self.stream.writeln(
651 "Starting " + self.getDescription(test) + " ...")
652 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200653
Damjan Marionf56b77a2016-10-03 19:44:57 +0200654 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200655 """
656 Stop a test
657
658 :param test:
659
660 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200661 unittest.TestResult.stopTest(self, test)
662 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200663 self.stream.writeln(single_line_delim)
664 self.stream.writeln("%-60s%s" %
665 (self.getDescription(test), self.result_string))
666 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200667 else:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200668 self.stream.writeln("%-60s%s" %
669 (self.getDescription(test), self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670
Damjan Marionf56b77a2016-10-03 19:44:57 +0200671 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200672 """
673 Print errors from running the test case
674 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200675 self.stream.writeln()
676 self.printErrorList('ERROR', self.errors)
677 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200678
Damjan Marionf56b77a2016-10-03 19:44:57 +0200679 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200680 """
681 Print error list to the output stream together with error type
682 and test case description.
683
684 :param flavour: error type
685 :param errors: iterable errors
686
687 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200688 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200689 self.stream.writeln(double_line_delim)
690 self.stream.writeln("%s: %s" %
691 (flavour, self.getDescription(test)))
692 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200693 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200694
695
Damjan Marionf56b77a2016-10-03 19:44:57 +0200696class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200697 """
698 A basic test runner implementation which prints results on standard error.
699 """
700 @property
701 def resultclass(self):
702 """Class maintaining the results of the tests"""
703 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200704
Damjan Marionf56b77a2016-10-03 19:44:57 +0200705 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200706 """
707 Run the tests
708
709 :param test:
710
711 """
712 print("Running tests using custom test runner") # debug message
Damjan Marionf56b77a2016-10-03 19:44:57 +0200713 return super(VppTestRunner, self).run(test)