blob: 889a30469ba490a7cfe2ece08f045ccc3ac428e1 [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 Sekera10db26f2017-01-11 08:16:53 +010019from vpp_object import VppObjectRegistry
Klement Sekeraf62ae122016-10-11 11:47:09 +020020
21"""
22 Test framework module.
23
24 The module provides a set of tools for constructing and running tests and
25 representing the results.
26"""
27
Klement Sekeraf62ae122016-10-11 11:47:09 +020028
Damjan Marionf56b77a2016-10-03 19:44:57 +020029class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020030 """Private class to create packet info object.
31
32 Help process information about the next packet.
33 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020034 """
Matej Klotton86d87c42016-11-11 11:38:55 +010035 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020036 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010037 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020038 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010039 #: Store the index of the destination packet generator interface
40 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020041 dst = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010042 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020043 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020044
Matej Klotton16a14cd2016-12-07 15:09:13 +010045 def __eq__(self, other):
46 index = self.index == other.index
47 src = self.src == other.src
48 dst = self.dst == other.dst
49 data = self.data == other.data
50 return index and src and dst and data
51
Klement Sekeraf62ae122016-10-11 11:47:09 +020052
Klement Sekerae4504c62016-12-08 10:16:41 +010053def pump_output(out, deque):
Klement Sekera277b89c2016-10-28 13:20:27 +020054 for line in iter(out.readline, b''):
Klement Sekerae4504c62016-12-08 10:16:41 +010055 deque.append(line)
Klement Sekera277b89c2016-10-28 13:20:27 +020056
57
Damjan Marionf56b77a2016-10-03 19:44:57 +020058class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010059 """This subclass is a base class for VPP test cases that are implemented as
60 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020061 """
62
63 @property
64 def packet_infos(self):
65 """List of packet infos"""
66 return self._packet_infos
67
Klement Sekeradab231a2016-12-21 08:50:14 +010068 @classmethod
69 def get_packet_count_for_if_idx(cls, dst_if_index):
70 """Get the number of packet info for specified destination if index"""
71 if dst_if_index in cls._packet_count_for_dst_if_idx:
72 return cls._packet_count_for_dst_if_idx[dst_if_index]
73 else:
74 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +020075
76 @classmethod
77 def instance(cls):
78 """Return the instance of this testcase"""
79 return cls.test_instance
80
Damjan Marionf56b77a2016-10-03 19:44:57 +020081 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +020082 def set_debug_flags(cls, d):
83 cls.debug_core = False
84 cls.debug_gdb = False
85 cls.debug_gdbserver = False
86 if d is None:
87 return
88 dl = d.lower()
89 if dl == "core":
90 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
91 # give a heads up if this is actually useless
Klement Sekerab17dd962017-01-09 07:43:48 +010092 print(colorize("WARNING: core size limit is set 0, core files "
93 "will NOT be created", RED))
Klement Sekera277b89c2016-10-28 13:20:27 +020094 cls.debug_core = True
95 elif dl == "gdb":
96 cls.debug_gdb = True
97 elif dl == "gdbserver":
98 cls.debug_gdbserver = True
99 else:
100 raise Exception("Unrecognized DEBUG option: '%s'" % d)
101
102 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200103 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200104 """ Set-up the test case class based on environment variables """
105 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200106 s = os.getenv("STEP")
107 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200108 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200109 cls.step = False
110 try:
111 d = os.getenv("DEBUG")
112 except:
113 d = None
114 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200115 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100116 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100117 debug_cli = ""
118 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
119 debug_cli = "cli-listen localhost:5002"
Klement Sekerada505f62017-01-04 12:58:53 +0100120 cls.vpp_cmdline = [cls.vpp_bin,
121 "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200122 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100123 if cls.plugin_path is not None:
124 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200125 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
126
127 @classmethod
128 def wait_for_enter(cls):
129 if cls.debug_gdbserver:
130 print(double_line_delim)
131 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
132 elif cls.debug_gdb:
133 print(double_line_delim)
134 print("Spawned VPP with PID: %d" % cls.vpp.pid)
135 else:
136 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
137 return
138 print(single_line_delim)
139 print("You can debug the VPP using e.g.:")
140 if cls.debug_gdbserver:
141 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
142 print("Now is the time to attach a gdb by running the above "
143 "command, set up breakpoints etc. and then resume VPP from "
144 "within gdb by issuing the 'continue' command")
145 elif cls.debug_gdb:
146 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
147 print("Now is the time to attach a gdb by running the above "
148 "command and set up breakpoints etc.")
149 print(single_line_delim)
150 raw_input("Press ENTER to continue running the testcase...")
151
152 @classmethod
153 def run_vpp(cls):
154 cmdline = cls.vpp_cmdline
155
156 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100157 gdbserver = '/usr/bin/gdbserver'
158 if not os.path.isfile(gdbserver) or \
159 not os.access(gdbserver, os.X_OK):
160 raise Exception("gdbserver binary '%s' does not exist or is "
161 "not executable" % gdbserver)
162
163 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200164 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
165
Klement Sekera931be3a2016-11-03 05:36:01 +0100166 try:
167 cls.vpp = subprocess.Popen(cmdline,
168 stdout=subprocess.PIPE,
169 stderr=subprocess.PIPE,
170 bufsize=1)
171 except Exception as e:
172 cls.logger.critical("Couldn't start vpp: %s" % e)
173 raise
174
Klement Sekera277b89c2016-10-28 13:20:27 +0200175 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100176
Damjan Marionf56b77a2016-10-03 19:44:57 +0200177 @classmethod
178 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200179 """
180 Perform class setup before running the testcase
181 Remove shared memory files, start vpp and connect the vpp-api
182 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200183 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200184 cls.tempdir = tempfile.mkdtemp(
185 prefix='vpp-unittest-' + cls.__name__ + '-')
Klement Sekera6c7440c2016-12-23 09:16:39 +0100186 file_handler = FileHandler("%s/log.txt" % cls.tempdir)
187 file_handler.setLevel(DEBUG)
188 cls.logger.addHandler(file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200189 cls.shm_prefix = cls.tempdir.split("/")[-1]
190 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200191 cls.logger.info("Temporary dir is %s, shm prefix is %s",
192 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200193 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100194 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100195 cls._captures = []
196 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200197 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100198 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100199 cls.registry = VppObjectRegistry()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200200 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100201 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200202 print(double_line_delim)
203 # need to catch exceptions here because if we raise, then the cleanup
204 # doesn't get called and we might end with a zombie vpp
205 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200206 cls.run_vpp()
Klement Sekerae4504c62016-12-08 10:16:41 +0100207 cls.vpp_stdout_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100208 cls.vpp_stdout_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100209 cls.vpp.stdout, cls.vpp_stdout_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200210 cls.vpp_stdout_reader_thread.start()
Klement Sekerae4504c62016-12-08 10:16:41 +0100211 cls.vpp_stderr_deque = deque()
Klement Sekera0529a742016-12-02 07:05:24 +0100212 cls.vpp_stderr_reader_thread = Thread(target=pump_output, args=(
Klement Sekerae4504c62016-12-08 10:16:41 +0100213 cls.vpp.stderr, cls.vpp_stderr_deque))
Klement Sekera277b89c2016-10-28 13:20:27 +0200214 cls.vpp_stderr_reader_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100215 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100216 if cls.step:
217 hook = StepHook(cls)
218 else:
219 hook = PollHook(cls)
220 cls.vapi.register_hook(hook)
221 time.sleep(0.1)
222 hook.poll_vpp()
223 try:
224 cls.vapi.connect()
225 except:
226 if cls.debug_gdbserver:
227 print(colorize("You're running VPP inside gdbserver but "
228 "VPP-API connection failed, did you forget "
229 "to 'continue' VPP from within gdb?", RED))
230 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200231 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100232 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100233 try:
234 cls.quit()
235 except:
236 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100237 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200238
Damjan Marionf56b77a2016-10-03 19:44:57 +0200239 @classmethod
240 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200241 """
242 Disconnect vpp-api, kill vpp and cleanup shared memory files
243 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200244 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
245 cls.vpp.poll()
246 if cls.vpp.returncode is None:
247 print(double_line_delim)
248 print("VPP or GDB server is still running")
249 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100250 raw_input("When done debugging, press ENTER to kill the "
251 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200252
Klement Sekeraf62ae122016-10-11 11:47:09 +0200253 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100254 if hasattr(cls, 'vapi'):
255 cls.vapi.disconnect()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200256 cls.vpp.poll()
257 if cls.vpp.returncode is None:
258 cls.vpp.terminate()
259 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200260
Klement Sekerae4504c62016-12-08 10:16:41 +0100261 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200262 cls.logger.info(single_line_delim)
263 cls.logger.info('VPP output to stdout while running %s:',
264 cls.__name__)
265 cls.logger.info(single_line_delim)
266 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100267 vpp_output = "".join(cls.vpp_stdout_deque)
268 f.write(vpp_output)
269 cls.logger.info('\n%s', vpp_output)
270 cls.logger.info(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200271
Klement Sekerae4504c62016-12-08 10:16:41 +0100272 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200273 cls.logger.info(single_line_delim)
274 cls.logger.info('VPP output to stderr while running %s:',
275 cls.__name__)
276 cls.logger.info(single_line_delim)
277 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100278 vpp_output = "".join(cls.vpp_stderr_deque)
279 f.write(vpp_output)
280 cls.logger.info('\n%s', vpp_output)
Klement Sekera277b89c2016-10-28 13:20:27 +0200281 cls.logger.info(single_line_delim)
282
Damjan Marionf56b77a2016-10-03 19:44:57 +0200283 @classmethod
284 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200285 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200286 cls.quit()
287
Damjan Marionf56b77a2016-10-03 19:44:57 +0200288 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200289 """ Show various debug prints after each test """
290 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200291 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000292 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200293 self.logger.info(self.vapi.ppcli("show hardware"))
294 self.logger.info(self.vapi.ppcli("show error"))
295 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100296 self.registry.remove_vpp_config(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200297
Damjan Marionf56b77a2016-10-03 19:44:57 +0200298 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200299 """ Clear trace before running each test"""
Klement Sekera0c1519b2016-12-08 05:03:32 +0100300 if self.vpp_dead:
301 raise Exception("VPP is dead when setting up the test")
Klement Sekerae4504c62016-12-08 10:16:41 +0100302 time.sleep(.1)
303 self.vpp_stdout_deque.append(
304 "--- test setUp() for %s.%s(%s) starts here ---\n" %
305 (self.__class__.__name__, self._testMethodName,
306 self._testMethodDoc))
307 self.vpp_stderr_deque.append(
308 "--- test setUp() for %s.%s(%s) starts here ---\n" %
309 (self.__class__.__name__, self._testMethodName,
310 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200311 self.vapi.cli("clear trace")
312 # store the test instance inside the test class - so that objects
313 # holding the class can access instance methods (like assertEqual)
314 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200315
Damjan Marionf56b77a2016-10-03 19:44:57 +0200316 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200317 def pg_enable_capture(cls, interfaces):
318 """
319 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200320
Klement Sekeraf62ae122016-10-11 11:47:09 +0200321 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200322
Klement Sekeraf62ae122016-10-11 11:47:09 +0200323 """
324 for i in interfaces:
325 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200326
Damjan Marionf56b77a2016-10-03 19:44:57 +0200327 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100328 def register_capture(cls, cap_name):
329 """ Register a capture in the testclass """
330 # add to the list of captures with current timestamp
331 cls._captures.append((time.time(), cap_name))
332 # filter out from zombies
333 cls._zombie_captures = [(stamp, name)
334 for (stamp, name) in cls._zombie_captures
335 if name != cap_name]
336
337 @classmethod
338 def pg_start(cls):
339 """ Remove any zombie captures and enable the packet generator """
340 # how long before capture is allowed to be deleted - otherwise vpp
341 # crashes - 100ms seems enough (this shouldn't be needed at all)
342 capture_ttl = 0.1
343 now = time.time()
344 for stamp, cap_name in cls._zombie_captures:
345 wait = stamp + capture_ttl - now
346 if wait > 0:
347 cls.logger.debug("Waiting for %ss before deleting capture %s",
348 wait, cap_name)
349 time.sleep(wait)
350 now = time.time()
351 cls.logger.debug("Removing zombie capture %s" % cap_name)
352 cls.vapi.cli('packet-generator delete %s' % cap_name)
353
Klement Sekeraf62ae122016-10-11 11:47:09 +0200354 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
355 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100356 cls._zombie_captures = cls._captures
357 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200358
Damjan Marionf56b77a2016-10-03 19:44:57 +0200359 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200360 def create_pg_interfaces(cls, interfaces):
361 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100362 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200363
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100364 :param interfaces: iterable indexes of the interfaces.
365 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200366
Klement Sekeraf62ae122016-10-11 11:47:09 +0200367 """
368 result = []
369 for i in interfaces:
370 intf = VppPGInterface(cls, i)
371 setattr(cls, intf.name, intf)
372 result.append(intf)
373 cls.pg_interfaces = result
374 return result
375
Matej Klotton0178d522016-11-04 11:11:44 +0100376 @classmethod
377 def create_loopback_interfaces(cls, interfaces):
378 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100379 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100380
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100381 :param interfaces: iterable indexes of the interfaces.
382 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100383 """
384 result = []
385 for i in interfaces:
386 intf = VppLoInterface(cls, i)
387 setattr(cls, intf.name, intf)
388 result.append(intf)
389 cls.lo_interfaces = result
390 return result
391
Damjan Marionf56b77a2016-10-03 19:44:57 +0200392 @staticmethod
393 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200394 """
395 Extend packet to given size by padding with spaces
396 NOTE: Currently works only when Raw layer is present.
397
398 :param packet: packet
399 :param size: target size
400
401 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200402 packet_len = len(packet) + 4
403 extend = size - packet_len
404 if extend > 0:
405 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200406
Klement Sekeradab231a2016-12-21 08:50:14 +0100407 @classmethod
408 def reset_packet_infos(cls):
409 """ Reset the list of packet info objects and packet counts to zero """
410 cls._packet_infos = {}
411 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200412
Klement Sekeradab231a2016-12-21 08:50:14 +0100413 @classmethod
414 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200415 """
416 Create packet info object containing the source and destination indexes
417 and add it to the testcase's packet info list
418
Klement Sekeradab231a2016-12-21 08:50:14 +0100419 :param VppInterface src_if: source interface
420 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200421
422 :returns: _PacketInfo object
423
424 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200425 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100426 info.index = len(cls._packet_infos)
427 info.src = src_if.sw_if_index
428 info.dst = dst_if.sw_if_index
429 if isinstance(dst_if, VppSubInterface):
430 dst_idx = dst_if.parent.sw_if_index
431 else:
432 dst_idx = dst_if.sw_if_index
433 if dst_idx in cls._packet_count_for_dst_if_idx:
434 cls._packet_count_for_dst_if_idx[dst_idx] += 1
435 else:
436 cls._packet_count_for_dst_if_idx[dst_idx] = 1
437 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200438 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200439
Damjan Marionf56b77a2016-10-03 19:44:57 +0200440 @staticmethod
441 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200442 """
443 Convert _PacketInfo object to packet payload
444
445 :param info: _PacketInfo object
446
447 :returns: string containing serialized data from packet info
448 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200449 return "%d %d %d" % (info.index, info.src, info.dst)
450
Damjan Marionf56b77a2016-10-03 19:44:57 +0200451 @staticmethod
452 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200453 """
454 Convert packet payload to _PacketInfo object
455
456 :param payload: packet payload
457
458 :returns: _PacketInfo object containing de-serialized data from payload
459
460 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200461 numbers = payload.split()
462 info = _PacketInfo()
463 info.index = int(numbers[0])
464 info.src = int(numbers[1])
465 info.dst = int(numbers[2])
466 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200467
Damjan Marionf56b77a2016-10-03 19:44:57 +0200468 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200469 """
470 Iterate over the packet info list stored in the testcase
471 Start iteration with first element if info is None
472 Continue based on index in info if info is specified
473
474 :param info: info or None
475 :returns: next info in list or None if no more infos
476 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200477 if info is None:
478 next_index = 0
479 else:
480 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100481 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200482 return None
483 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100484 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200485
Klement Sekeraf62ae122016-10-11 11:47:09 +0200486 def get_next_packet_info_for_interface(self, src_index, info):
487 """
488 Search the packet info list for the next packet info with same source
489 interface index
490
491 :param src_index: source interface index to search for
492 :param info: packet info - where to start the search
493 :returns: packet info or None
494
495 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200496 while True:
497 info = self.get_next_packet_info(info)
498 if info is None:
499 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200500 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200501 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200502
Klement Sekeraf62ae122016-10-11 11:47:09 +0200503 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
504 """
505 Search the packet info list for the next packet info with same source
506 and destination interface indexes
507
508 :param src_index: source interface index to search for
509 :param dst_index: destination interface index to search for
510 :param info: packet info - where to start the search
511 :returns: packet info or None
512
513 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200514 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200515 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200516 if info is None:
517 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200518 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200519 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200520
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200521 def assert_equal(self, real_value, expected_value, name_or_class=None):
522 if name_or_class is None:
523 self.assertEqual(real_value, expected_value, msg)
524 return
525 try:
526 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
527 msg = msg % (getdoc(name_or_class).strip(),
528 real_value, str(name_or_class(real_value)),
529 expected_value, str(name_or_class(expected_value)))
530 except:
531 msg = "Invalid %s: %s does not match expected value %s" % (
532 name_or_class, real_value, expected_value)
533
534 self.assertEqual(real_value, expected_value, msg)
535
Klement Sekerab17dd962017-01-09 07:43:48 +0100536 def assert_in_range(self,
537 real_value,
538 expected_min,
539 expected_max,
540 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200541 if name is None:
542 msg = None
543 else:
544 msg = "Invalid %s: %s out of range <%s,%s>" % (
545 name, real_value, expected_min, expected_max)
546 self.assertTrue(expected_min <= real_value <= expected_max, msg)
547
Damjan Marionf56b77a2016-10-03 19:44:57 +0200548
Damjan Marionf56b77a2016-10-03 19:44:57 +0200549class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200550 """
551 @property result_string
552 String variable to store the test case result string.
553 @property errors
554 List variable containing 2-tuples of TestCase instances and strings
555 holding formatted tracebacks. Each tuple represents a test which
556 raised an unexpected exception.
557 @property failures
558 List variable containing 2-tuples of TestCase instances and strings
559 holding formatted tracebacks. Each tuple represents a test where
560 a failure was explicitly signalled using the TestCase.assert*()
561 methods.
562 """
563
Damjan Marionf56b77a2016-10-03 19:44:57 +0200564 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200565 """
Klement Sekerada505f62017-01-04 12:58:53 +0100566 :param stream File descriptor to store where to report test results.
567 Set to the standard error stream by default.
568 :param descriptions Boolean variable to store information if to use
569 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200570 :param verbosity Integer variable to store required verbosity level.
571 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200572 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
573 self.stream = stream
574 self.descriptions = descriptions
575 self.verbosity = verbosity
576 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200577
Damjan Marionf56b77a2016-10-03 19:44:57 +0200578 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200579 """
580 Record a test succeeded result
581
582 :param test:
583
584 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200585 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200586 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200587
Klement Sekeraf62ae122016-10-11 11:47:09 +0200588 def addSkip(self, test, reason):
589 """
590 Record a test skipped.
591
592 :param test:
593 :param reason:
594
595 """
596 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200597 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200598
Damjan Marionf56b77a2016-10-03 19:44:57 +0200599 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200600 """
601 Record a test failed result
602
603 :param test:
604 :param err: error message
605
606 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200607 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200608 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200609 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200610 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
611 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200612 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200613
Damjan Marionf56b77a2016-10-03 19:44:57 +0200614 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200615 """
616 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200617
Klement Sekeraf62ae122016-10-11 11:47:09 +0200618 :param test:
619 :param err: error message
620
621 """
622 unittest.TestResult.addError(self, test, err)
623 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200624 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200625 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
626 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200627 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200628
Damjan Marionf56b77a2016-10-03 19:44:57 +0200629 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200630 """
631 Get test description
632
633 :param test:
634 :returns: test description
635
636 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200637 # TODO: if none print warning not raise exception
638 short_description = test.shortDescription()
639 if self.descriptions and short_description:
640 return short_description
641 else:
642 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200643
Damjan Marionf56b77a2016-10-03 19:44:57 +0200644 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200645 """
646 Start a test
647
648 :param test:
649
650 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200651 unittest.TestResult.startTest(self, test)
652 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200653 self.stream.writeln(
654 "Starting " + self.getDescription(test) + " ...")
655 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200656
Damjan Marionf56b77a2016-10-03 19:44:57 +0200657 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200658 """
659 Stop a test
660
661 :param test:
662
663 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200664 unittest.TestResult.stopTest(self, test)
665 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200666 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100667 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100668 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200669 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200670 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100671 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100672 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200673
Damjan Marionf56b77a2016-10-03 19:44:57 +0200674 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200675 """
676 Print errors from running the test case
677 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200678 self.stream.writeln()
679 self.printErrorList('ERROR', self.errors)
680 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200681
Damjan Marionf56b77a2016-10-03 19:44:57 +0200682 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200683 """
684 Print error list to the output stream together with error type
685 and test case description.
686
687 :param flavour: error type
688 :param errors: iterable errors
689
690 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200691 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200692 self.stream.writeln(double_line_delim)
693 self.stream.writeln("%s: %s" %
694 (flavour, self.getDescription(test)))
695 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200696 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200697
698
Damjan Marionf56b77a2016-10-03 19:44:57 +0200699class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200700 """
Klement Sekera104543f2017-02-03 07:29:43 +0100701 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200702 """
703 @property
704 def resultclass(self):
705 """Class maintaining the results of the tests"""
706 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200707
Klement Sekera7a161da2017-01-17 13:42:48 +0100708 def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
709 failfast=False, buffer=False, resultclass=None):
710 # ignore stream setting here, use hard-coded stdout to be in sync
711 # with prints from VppTestCase methods ...
712 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
713 verbosity, failfast, buffer,
714 resultclass)
715
Klement Sekera104543f2017-02-03 07:29:43 +0100716 test_option = "TEST"
717
718 def parse_test_option(self):
719 try:
720 f = os.getenv(self.test_option)
721 except:
722 f = None
723 filter_file_name = None
724 filter_class_name = None
725 filter_func_name = None
726 if f:
727 if '.' in f:
728 parts = f.split('.')
729 if len(parts) > 3:
730 raise Exception("Unrecognized %s option: %s" %
731 (self.test_option, f))
732 if len(parts) > 2:
733 if parts[2] not in ('*', ''):
734 filter_func_name = parts[2]
735 if parts[1] not in ('*', ''):
736 filter_class_name = parts[1]
737 if parts[0] not in ('*', ''):
738 if parts[0].startswith('test_'):
739 filter_file_name = parts[0]
740 else:
741 filter_file_name = 'test_%s' % parts[0]
742 else:
743 if f.startswith('test_'):
744 filter_file_name = f
745 else:
746 filter_file_name = 'test_%s' % f
747 return filter_file_name, filter_class_name, filter_func_name
748
749 def filter_tests(self, tests, filter_file, filter_class, filter_func):
750 result = unittest.suite.TestSuite()
751 for t in tests:
752 if isinstance(t, unittest.suite.TestSuite):
753 # this is a bunch of tests, recursively filter...
754 x = self.filter_tests(t, filter_file, filter_class,
755 filter_func)
756 if x.countTestCases() > 0:
757 result.addTest(x)
758 elif isinstance(t, unittest.TestCase):
759 # this is a single test
760 parts = t.id().split('.')
761 # t.id() for common cases like this:
762 # test_classifier.TestClassifier.test_acl_ip
763 # apply filtering only if it is so
764 if len(parts) == 3:
765 if filter_file and filter_file != parts[0]:
766 continue
767 if filter_class and filter_class != parts[1]:
768 continue
769 if filter_func and filter_func != parts[2]:
770 continue
771 result.addTest(t)
772 else:
773 # unexpected object, don't touch it
774 result.addTest(t)
775 return result
776
Damjan Marionf56b77a2016-10-03 19:44:57 +0200777 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200778 """
779 Run the tests
780
781 :param test:
782
783 """
784 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +0100785 filter_file, filter_class, filter_func = self.parse_test_option()
786 print("Active filters: file=%s, class=%s, function=%s" % (
787 filter_file, filter_class, filter_func))
788 filtered = self.filter_tests(test, filter_file, filter_class,
789 filter_func)
790 print("%s out of %s tests match specified filters" % (
791 filtered.countTestCases(), test.countTestCases()))
792 return super(VppTestRunner, self).run(filtered)