blob: 8dd61aa14baa3ad3aa8b3e3cdc071ca0e240a977 [file] [log] [blame]
Damjan Marionf56b77a2016-10-03 19:44:57 +02001#!/usr/bin/env python
Damjan Marionf56b77a2016-10-03 19:44:57 +02002
Klement Sekeraacb9b8e2017-02-14 02:55:31 +01003from __future__ import print_function
4import gc
5import sys
6import os
7import select
Damjan Marionf56b77a2016-10-03 19:44:57 +02008import unittest
Klement Sekeraf62ae122016-10-11 11:47:09 +02009import tempfile
Klement Sekera277b89c2016-10-28 13:20:27 +020010import time
Klement Sekeraf62ae122016-10-11 11:47:09 +020011import resource
Klement Sekerae4504c62016-12-08 10:16:41 +010012from collections import deque
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010013from threading import Thread, Event
Damjan Marionf56b77a2016-10-03 19:44:57 +020014from inspect import getdoc
Klement Sekerab91017a2017-02-09 06:04:36 +010015from traceback import format_exception
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010016from logging import FileHandler, DEBUG, Formatter
17from scapy.packet import Raw
Klement Sekera277b89c2016-10-28 13:20:27 +020018from hook import StepHook, PollHook
Klement Sekeraf62ae122016-10-11 11:47:09 +020019from vpp_pg_interface import VppPGInterface
Klement Sekeradab231a2016-12-21 08:50:14 +010020from vpp_sub_interface import VppSubInterface
Matej Klotton0178d522016-11-04 11:11:44 +010021from vpp_lo_interface import VppLoInterface
Klement Sekeraf62ae122016-10-11 11:47:09 +020022from vpp_papi_provider import VppPapiProvider
Klement Sekera277b89c2016-10-28 13:20:27 +020023from log import *
Klement Sekera10db26f2017-01-11 08:16:53 +010024from vpp_object import VppObjectRegistry
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010025if os.name == 'posix' and sys.version_info[0] < 3:
26 # using subprocess32 is recommended by python official documentation
27 # @ https://docs.python.org/2/library/subprocess.html
28 import subprocess32 as subprocess
29else:
30 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020031
32"""
33 Test framework module.
34
35 The module provides a set of tools for constructing and running tests and
36 representing the results.
37"""
38
Klement Sekeraf62ae122016-10-11 11:47:09 +020039
Damjan Marionf56b77a2016-10-03 19:44:57 +020040class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +020041 """Private class to create packet info object.
42
43 Help process information about the next packet.
44 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +020045 """
Matej Klotton86d87c42016-11-11 11:38:55 +010046 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020047 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010048 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020049 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010050 #: Store the index of the destination packet generator interface
51 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020052 dst = -1
Matej Klotton86d87c42016-11-11 11:38:55 +010053 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +020054 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +020055
Matej Klotton16a14cd2016-12-07 15:09:13 +010056 def __eq__(self, other):
57 index = self.index == other.index
58 src = self.src == other.src
59 dst = self.dst == other.dst
60 data = self.data == other.data
61 return index and src and dst and data
62
Klement Sekeraf62ae122016-10-11 11:47:09 +020063
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010064def pump_output(testclass):
65 """ pump output from vpp stdout/stderr to proper queues """
66 while not testclass.pump_thread_stop_flag.wait(0):
67 readable = select.select([testclass.vpp.stdout.fileno(),
68 testclass.vpp.stderr.fileno(),
69 testclass.pump_thread_wakeup_pipe[0]],
70 [], [])[0]
71 if testclass.vpp.stdout.fileno() in readable:
72 read = os.read(testclass.vpp.stdout.fileno(), 1024)
73 testclass.vpp_stdout_deque.append(read)
74 if testclass.vpp.stderr.fileno() in readable:
75 read = os.read(testclass.vpp.stderr.fileno(), 1024)
76 testclass.vpp_stderr_deque.append(read)
77 # ignoring the dummy pipe here intentionally - the flag will take care
78 # of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +020079
80
Damjan Marionf56b77a2016-10-03 19:44:57 +020081class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +010082 """This subclass is a base class for VPP test cases that are implemented as
83 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +020084 """
85
86 @property
87 def packet_infos(self):
88 """List of packet infos"""
89 return self._packet_infos
90
Klement Sekeradab231a2016-12-21 08:50:14 +010091 @classmethod
92 def get_packet_count_for_if_idx(cls, dst_if_index):
93 """Get the number of packet info for specified destination if index"""
94 if dst_if_index in cls._packet_count_for_dst_if_idx:
95 return cls._packet_count_for_dst_if_idx[dst_if_index]
96 else:
97 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +020098
99 @classmethod
100 def instance(cls):
101 """Return the instance of this testcase"""
102 return cls.test_instance
103
Damjan Marionf56b77a2016-10-03 19:44:57 +0200104 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200105 def set_debug_flags(cls, d):
106 cls.debug_core = False
107 cls.debug_gdb = False
108 cls.debug_gdbserver = False
109 if d is None:
110 return
111 dl = d.lower()
112 if dl == "core":
113 if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
114 # give a heads up if this is actually useless
Klement Sekerab17dd962017-01-09 07:43:48 +0100115 print(colorize("WARNING: core size limit is set 0, core files "
116 "will NOT be created", RED))
Klement Sekera277b89c2016-10-28 13:20:27 +0200117 cls.debug_core = True
118 elif dl == "gdb":
119 cls.debug_gdb = True
120 elif dl == "gdbserver":
121 cls.debug_gdbserver = True
122 else:
123 raise Exception("Unrecognized DEBUG option: '%s'" % d)
124
125 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200126 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200127 """ Set-up the test case class based on environment variables """
128 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200129 s = os.getenv("STEP")
130 cls.step = True if s.lower() in ("y", "yes", "1") else False
Klement Sekeraf62ae122016-10-11 11:47:09 +0200131 except:
Klement Sekera277b89c2016-10-28 13:20:27 +0200132 cls.step = False
133 try:
134 d = os.getenv("DEBUG")
135 except:
136 d = None
137 cls.set_debug_flags(d)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200138 cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100139 cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera01bbbe92016-11-02 09:25:05 +0100140 debug_cli = ""
141 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
142 debug_cli = "cli-listen localhost:5002"
Klement Sekerada505f62017-01-04 12:58:53 +0100143 cls.vpp_cmdline = [cls.vpp_bin,
144 "unix", "{", "nodaemon", debug_cli, "}",
Klement Sekeraf62ae122016-10-11 11:47:09 +0200145 "api-segment", "{", "prefix", cls.shm_prefix, "}"]
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100146 if cls.plugin_path is not None:
147 cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
Klement Sekera277b89c2016-10-28 13:20:27 +0200148 cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
149
150 @classmethod
151 def wait_for_enter(cls):
152 if cls.debug_gdbserver:
153 print(double_line_delim)
154 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
155 elif cls.debug_gdb:
156 print(double_line_delim)
157 print("Spawned VPP with PID: %d" % cls.vpp.pid)
158 else:
159 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
160 return
161 print(single_line_delim)
162 print("You can debug the VPP using e.g.:")
163 if cls.debug_gdbserver:
164 print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
165 print("Now is the time to attach a gdb by running the above "
166 "command, set up breakpoints etc. and then resume VPP from "
167 "within gdb by issuing the 'continue' command")
168 elif cls.debug_gdb:
169 print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
170 print("Now is the time to attach a gdb by running the above "
171 "command and set up breakpoints etc.")
172 print(single_line_delim)
173 raw_input("Press ENTER to continue running the testcase...")
174
175 @classmethod
176 def run_vpp(cls):
177 cmdline = cls.vpp_cmdline
178
179 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100180 gdbserver = '/usr/bin/gdbserver'
181 if not os.path.isfile(gdbserver) or \
182 not os.access(gdbserver, os.X_OK):
183 raise Exception("gdbserver binary '%s' does not exist or is "
184 "not executable" % gdbserver)
185
186 cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200187 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
188
Klement Sekera931be3a2016-11-03 05:36:01 +0100189 try:
190 cls.vpp = subprocess.Popen(cmdline,
191 stdout=subprocess.PIPE,
192 stderr=subprocess.PIPE,
193 bufsize=1)
194 except Exception as e:
195 cls.logger.critical("Couldn't start vpp: %s" % e)
196 raise
197
Klement Sekera277b89c2016-10-28 13:20:27 +0200198 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100199
Damjan Marionf56b77a2016-10-03 19:44:57 +0200200 @classmethod
201 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200202 """
203 Perform class setup before running the testcase
204 Remove shared memory files, start vpp and connect the vpp-api
205 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100206 gc.collect() # run garbage collection first
Klement Sekera277b89c2016-10-28 13:20:27 +0200207 cls.logger = getLogger(cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200208 cls.tempdir = tempfile.mkdtemp(
209 prefix='vpp-unittest-' + cls.__name__ + '-')
Klement Sekera6c7440c2016-12-23 09:16:39 +0100210 file_handler = FileHandler("%s/log.txt" % cls.tempdir)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100211 file_handler.setFormatter(
212 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
213 datefmt="%H:%M:%S"))
Klement Sekera6c7440c2016-12-23 09:16:39 +0100214 file_handler.setLevel(DEBUG)
215 cls.logger.addHandler(file_handler)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200216 cls.shm_prefix = cls.tempdir.split("/")[-1]
217 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200218 cls.logger.info("Temporary dir is %s, shm prefix is %s",
219 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200220 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100221 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100222 cls._captures = []
223 cls._zombie_captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200224 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100225 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100226 cls.registry = VppObjectRegistry()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200227 print(double_line_delim)
Matej Klotton86d87c42016-11-11 11:38:55 +0100228 print(colorize(getdoc(cls).splitlines()[0], YELLOW))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200229 print(double_line_delim)
230 # need to catch exceptions here because if we raise, then the cleanup
231 # doesn't get called and we might end with a zombie vpp
232 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200233 cls.run_vpp()
Klement Sekerae4504c62016-12-08 10:16:41 +0100234 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100235 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100236 cls.pump_thread_stop_flag = Event()
237 cls.pump_thread_wakeup_pipe = os.pipe()
238 cls.pump_thread = Thread(target=pump_output, args=(cls,))
239 cls.pump_thread.start()
Klement Sekera7bb873a2016-11-18 07:38:42 +0100240 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100241 if cls.step:
242 hook = StepHook(cls)
243 else:
244 hook = PollHook(cls)
245 cls.vapi.register_hook(hook)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100246 cls.sleep(0.1, "after vpp startup, before initial poll")
Klement Sekera085f5c02016-11-24 01:59:16 +0100247 hook.poll_vpp()
248 try:
249 cls.vapi.connect()
250 except:
251 if cls.debug_gdbserver:
252 print(colorize("You're running VPP inside gdbserver but "
253 "VPP-API connection failed, did you forget "
254 "to 'continue' VPP from within gdb?", RED))
255 raise
Klement Sekeraf62ae122016-10-11 11:47:09 +0200256 except:
Klement Sekera0529a742016-12-02 07:05:24 +0100257 t, v, tb = sys.exc_info()
Klement Sekera085f5c02016-11-24 01:59:16 +0100258 try:
259 cls.quit()
260 except:
261 pass
Klement Sekera0529a742016-12-02 07:05:24 +0100262 raise t, v, tb
Damjan Marionf56b77a2016-10-03 19:44:57 +0200263
Damjan Marionf56b77a2016-10-03 19:44:57 +0200264 @classmethod
265 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200266 """
267 Disconnect vpp-api, kill vpp and cleanup shared memory files
268 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200269 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
270 cls.vpp.poll()
271 if cls.vpp.returncode is None:
272 print(double_line_delim)
273 print("VPP or GDB server is still running")
274 print(single_line_delim)
Klement Sekerada505f62017-01-04 12:58:53 +0100275 raw_input("When done debugging, press ENTER to kill the "
276 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200277
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100278 os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
279 cls.pump_thread_stop_flag.set()
280 if hasattr(cls, 'pump_thread'):
281 cls.logger.debug("Waiting for pump thread to stop")
282 cls.pump_thread.join()
283 if hasattr(cls, 'vpp_stderr_reader_thread'):
284 cls.logger.debug("Waiting for stdderr pump to stop")
285 cls.vpp_stderr_reader_thread.join()
286
Klement Sekeraf62ae122016-10-11 11:47:09 +0200287 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100288 if hasattr(cls, 'vapi'):
289 cls.vapi.disconnect()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100290 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200291 cls.vpp.poll()
292 if cls.vpp.returncode is None:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100293 cls.logger.debug("Sending TERM to vpp")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200294 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100295 cls.logger.debug("Waiting for vpp to die")
296 cls.vpp.communicate()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200297 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200298
Klement Sekerae4504c62016-12-08 10:16:41 +0100299 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200300 cls.logger.info(single_line_delim)
301 cls.logger.info('VPP output to stdout while running %s:',
302 cls.__name__)
303 cls.logger.info(single_line_delim)
304 f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100305 vpp_output = "".join(cls.vpp_stdout_deque)
306 f.write(vpp_output)
307 cls.logger.info('\n%s', vpp_output)
308 cls.logger.info(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200309
Klement Sekerae4504c62016-12-08 10:16:41 +0100310 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200311 cls.logger.info(single_line_delim)
312 cls.logger.info('VPP output to stderr while running %s:',
313 cls.__name__)
314 cls.logger.info(single_line_delim)
315 f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
Klement Sekerae4504c62016-12-08 10:16:41 +0100316 vpp_output = "".join(cls.vpp_stderr_deque)
317 f.write(vpp_output)
318 cls.logger.info('\n%s', vpp_output)
Klement Sekera277b89c2016-10-28 13:20:27 +0200319 cls.logger.info(single_line_delim)
320
Damjan Marionf56b77a2016-10-03 19:44:57 +0200321 @classmethod
322 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200323 """ Perform final cleanup after running all tests in this test-case """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200324 cls.quit()
325
Damjan Marionf56b77a2016-10-03 19:44:57 +0200326 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200327 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100328 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
329 (self.__class__.__name__, self._testMethodName,
330 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200331 if not self.vpp_dead:
Jan49c0fca2016-10-26 15:44:27 +0200332 self.logger.debug(self.vapi.cli("show trace"))
Neale Ranns177bbdc2016-11-15 09:46:51 +0000333 self.logger.info(self.vapi.ppcli("show int"))
Jan49c0fca2016-10-26 15:44:27 +0200334 self.logger.info(self.vapi.ppcli("show hardware"))
335 self.logger.info(self.vapi.ppcli("show error"))
336 self.logger.info(self.vapi.ppcli("show run"))
Klement Sekera10db26f2017-01-11 08:16:53 +0100337 self.registry.remove_vpp_config(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200338
Damjan Marionf56b77a2016-10-03 19:44:57 +0200339 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200340 """ Clear trace before running each test"""
Klement Sekerab91017a2017-02-09 06:04:36 +0100341 self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
342 (self.__class__.__name__, self._testMethodName,
343 self._testMethodDoc))
Klement Sekera0c1519b2016-12-08 05:03:32 +0100344 if self.vpp_dead:
345 raise Exception("VPP is dead when setting up the test")
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100346 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100347 self.vpp_stdout_deque.append(
348 "--- test setUp() for %s.%s(%s) starts here ---\n" %
349 (self.__class__.__name__, self._testMethodName,
350 self._testMethodDoc))
351 self.vpp_stderr_deque.append(
352 "--- test setUp() for %s.%s(%s) starts here ---\n" %
353 (self.__class__.__name__, self._testMethodName,
354 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200355 self.vapi.cli("clear trace")
356 # store the test instance inside the test class - so that objects
357 # holding the class can access instance methods (like assertEqual)
358 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200359
Damjan Marionf56b77a2016-10-03 19:44:57 +0200360 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200361 def pg_enable_capture(cls, interfaces):
362 """
363 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200364
Klement Sekeraf62ae122016-10-11 11:47:09 +0200365 :param interfaces: iterable interface indexes
Damjan Marionf56b77a2016-10-03 19:44:57 +0200366
Klement Sekeraf62ae122016-10-11 11:47:09 +0200367 """
368 for i in interfaces:
369 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200370
Damjan Marionf56b77a2016-10-03 19:44:57 +0200371 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100372 def register_capture(cls, cap_name):
373 """ Register a capture in the testclass """
374 # add to the list of captures with current timestamp
375 cls._captures.append((time.time(), cap_name))
376 # filter out from zombies
377 cls._zombie_captures = [(stamp, name)
378 for (stamp, name) in cls._zombie_captures
379 if name != cap_name]
380
381 @classmethod
382 def pg_start(cls):
383 """ Remove any zombie captures and enable the packet generator """
384 # how long before capture is allowed to be deleted - otherwise vpp
385 # crashes - 100ms seems enough (this shouldn't be needed at all)
386 capture_ttl = 0.1
387 now = time.time()
388 for stamp, cap_name in cls._zombie_captures:
389 wait = stamp + capture_ttl - now
390 if wait > 0:
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100391 cls.sleep(wait, "before deleting capture %s" % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100392 now = time.time()
393 cls.logger.debug("Removing zombie capture %s" % cap_name)
394 cls.vapi.cli('packet-generator delete %s' % cap_name)
395
Klement Sekeraf62ae122016-10-11 11:47:09 +0200396 cls.vapi.cli("trace add pg-input 50") # 50 is maximum
397 cls.vapi.cli('packet-generator enable')
Klement Sekera9225dee2016-12-12 08:36:58 +0100398 cls._zombie_captures = cls._captures
399 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200400
Damjan Marionf56b77a2016-10-03 19:44:57 +0200401 @classmethod
Klement Sekeraf62ae122016-10-11 11:47:09 +0200402 def create_pg_interfaces(cls, interfaces):
403 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100404 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200405
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100406 :param interfaces: iterable indexes of the interfaces.
407 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200408
Klement Sekeraf62ae122016-10-11 11:47:09 +0200409 """
410 result = []
411 for i in interfaces:
412 intf = VppPGInterface(cls, i)
413 setattr(cls, intf.name, intf)
414 result.append(intf)
415 cls.pg_interfaces = result
416 return result
417
Matej Klotton0178d522016-11-04 11:11:44 +0100418 @classmethod
419 def create_loopback_interfaces(cls, interfaces):
420 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100421 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100422
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100423 :param interfaces: iterable indexes of the interfaces.
424 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100425 """
426 result = []
427 for i in interfaces:
428 intf = VppLoInterface(cls, i)
429 setattr(cls, intf.name, intf)
430 result.append(intf)
431 cls.lo_interfaces = result
432 return result
433
Damjan Marionf56b77a2016-10-03 19:44:57 +0200434 @staticmethod
435 def extend_packet(packet, size):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200436 """
437 Extend packet to given size by padding with spaces
438 NOTE: Currently works only when Raw layer is present.
439
440 :param packet: packet
441 :param size: target size
442
443 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200444 packet_len = len(packet) + 4
445 extend = size - packet_len
446 if extend > 0:
447 packet[Raw].load += ' ' * extend
Damjan Marionf56b77a2016-10-03 19:44:57 +0200448
Klement Sekeradab231a2016-12-21 08:50:14 +0100449 @classmethod
450 def reset_packet_infos(cls):
451 """ Reset the list of packet info objects and packet counts to zero """
452 cls._packet_infos = {}
453 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200454
Klement Sekeradab231a2016-12-21 08:50:14 +0100455 @classmethod
456 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200457 """
458 Create packet info object containing the source and destination indexes
459 and add it to the testcase's packet info list
460
Klement Sekeradab231a2016-12-21 08:50:14 +0100461 :param VppInterface src_if: source interface
462 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200463
464 :returns: _PacketInfo object
465
466 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200467 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100468 info.index = len(cls._packet_infos)
469 info.src = src_if.sw_if_index
470 info.dst = dst_if.sw_if_index
471 if isinstance(dst_if, VppSubInterface):
472 dst_idx = dst_if.parent.sw_if_index
473 else:
474 dst_idx = dst_if.sw_if_index
475 if dst_idx in cls._packet_count_for_dst_if_idx:
476 cls._packet_count_for_dst_if_idx[dst_idx] += 1
477 else:
478 cls._packet_count_for_dst_if_idx[dst_idx] = 1
479 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200480 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200481
Damjan Marionf56b77a2016-10-03 19:44:57 +0200482 @staticmethod
483 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200484 """
485 Convert _PacketInfo object to packet payload
486
487 :param info: _PacketInfo object
488
489 :returns: string containing serialized data from packet info
490 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200491 return "%d %d %d" % (info.index, info.src, info.dst)
492
Damjan Marionf56b77a2016-10-03 19:44:57 +0200493 @staticmethod
494 def payload_to_info(payload):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200495 """
496 Convert packet payload to _PacketInfo object
497
498 :param payload: packet payload
499
500 :returns: _PacketInfo object containing de-serialized data from payload
501
502 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200503 numbers = payload.split()
504 info = _PacketInfo()
505 info.index = int(numbers[0])
506 info.src = int(numbers[1])
507 info.dst = int(numbers[2])
508 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200509
Damjan Marionf56b77a2016-10-03 19:44:57 +0200510 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200511 """
512 Iterate over the packet info list stored in the testcase
513 Start iteration with first element if info is None
514 Continue based on index in info if info is specified
515
516 :param info: info or None
517 :returns: next info in list or None if no more infos
518 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200519 if info is None:
520 next_index = 0
521 else:
522 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100523 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200524 return None
525 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100526 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200527
Klement Sekeraf62ae122016-10-11 11:47:09 +0200528 def get_next_packet_info_for_interface(self, src_index, info):
529 """
530 Search the packet info list for the next packet info with same source
531 interface index
532
533 :param src_index: source interface index to search for
534 :param info: packet info - where to start the search
535 :returns: packet info or None
536
537 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200538 while True:
539 info = self.get_next_packet_info(info)
540 if info is None:
541 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200542 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200543 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200544
Klement Sekeraf62ae122016-10-11 11:47:09 +0200545 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
546 """
547 Search the packet info list for the next packet info with same source
548 and destination interface indexes
549
550 :param src_index: source interface index to search for
551 :param dst_index: destination interface index to search for
552 :param info: packet info - where to start the search
553 :returns: packet info or None
554
555 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200556 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200557 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200558 if info is None:
559 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200560 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200561 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200562
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200563 def assert_equal(self, real_value, expected_value, name_or_class=None):
564 if name_or_class is None:
565 self.assertEqual(real_value, expected_value, msg)
566 return
567 try:
568 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
569 msg = msg % (getdoc(name_or_class).strip(),
570 real_value, str(name_or_class(real_value)),
571 expected_value, str(name_or_class(expected_value)))
572 except:
573 msg = "Invalid %s: %s does not match expected value %s" % (
574 name_or_class, real_value, expected_value)
575
576 self.assertEqual(real_value, expected_value, msg)
577
Klement Sekerab17dd962017-01-09 07:43:48 +0100578 def assert_in_range(self,
579 real_value,
580 expected_min,
581 expected_max,
582 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200583 if name is None:
584 msg = None
585 else:
586 msg = "Invalid %s: %s out of range <%s,%s>" % (
587 name, real_value, expected_min, expected_max)
588 self.assertTrue(expected_min <= real_value <= expected_max, msg)
589
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100590 @classmethod
591 def sleep(cls, timeout, remark=None):
592 if hasattr(cls, 'logger'):
593 cls.logger.debug("Sleeping for %ss (%s)" % (timeout, remark))
Klement Sekeraa57a9702017-02-02 06:58:07 +0100594 time.sleep(timeout)
595
Damjan Marionf56b77a2016-10-03 19:44:57 +0200596
Damjan Marionf56b77a2016-10-03 19:44:57 +0200597class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200598 """
599 @property result_string
600 String variable to store the test case result string.
601 @property errors
602 List variable containing 2-tuples of TestCase instances and strings
603 holding formatted tracebacks. Each tuple represents a test which
604 raised an unexpected exception.
605 @property failures
606 List variable containing 2-tuples of TestCase instances and strings
607 holding formatted tracebacks. Each tuple represents a test where
608 a failure was explicitly signalled using the TestCase.assert*()
609 methods.
610 """
611
Damjan Marionf56b77a2016-10-03 19:44:57 +0200612 def __init__(self, stream, descriptions, verbosity):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200613 """
Klement Sekerada505f62017-01-04 12:58:53 +0100614 :param stream File descriptor to store where to report test results.
615 Set to the standard error stream by default.
616 :param descriptions Boolean variable to store information if to use
617 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200618 :param verbosity Integer variable to store required verbosity level.
619 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200620 unittest.TestResult.__init__(self, stream, descriptions, verbosity)
621 self.stream = stream
622 self.descriptions = descriptions
623 self.verbosity = verbosity
624 self.result_string = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200625
Damjan Marionf56b77a2016-10-03 19:44:57 +0200626 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200627 """
628 Record a test succeeded result
629
630 :param test:
631
632 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100633 if hasattr(test, 'logger'):
634 test.logger.debug("--- addSuccess() %s.%s(%s) called"
635 % (test.__class__.__name__,
636 test._testMethodName,
637 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200638 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +0200639 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200640
Klement Sekeraf62ae122016-10-11 11:47:09 +0200641 def addSkip(self, test, reason):
642 """
643 Record a test skipped.
644
645 :param test:
646 :param reason:
647
648 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100649 if hasattr(test, 'logger'):
650 test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
651 % (test.__class__.__name__,
652 test._testMethodName,
653 test._testMethodDoc,
654 reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200655 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +0200656 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200657
Damjan Marionf56b77a2016-10-03 19:44:57 +0200658 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200659 """
660 Record a test failed result
661
662 :param test:
663 :param err: error message
664
665 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100666 if hasattr(test, 'logger'):
667 test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
668 % (test.__class__.__name__,
669 test._testMethodName,
670 test._testMethodDoc, err))
671 test.logger.debug("formatted exception is:\n%s" %
672 "".join(format_exception(*err)))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200673 unittest.TestResult.addFailure(self, test, err)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200674 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200675 self.result_string = colorize("FAIL", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200676 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
677 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200678 self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
Damjan Marionf56b77a2016-10-03 19:44:57 +0200679
Damjan Marionf56b77a2016-10-03 19:44:57 +0200680 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200681 """
682 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +0200683
Klement Sekeraf62ae122016-10-11 11:47:09 +0200684 :param test:
685 :param err: error message
686
687 """
Klement Sekerab91017a2017-02-09 06:04:36 +0100688 if hasattr(test, 'logger'):
689 test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
690 % (test.__class__.__name__,
691 test._testMethodName,
692 test._testMethodDoc, err))
693 test.logger.debug("formatted exception is:\n%s" %
694 "".join(format_exception(*err)))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200695 unittest.TestResult.addError(self, test, err)
696 if hasattr(test, 'tempdir'):
Klement Sekera277b89c2016-10-28 13:20:27 +0200697 self.result_string = colorize("ERROR", RED) + \
Klement Sekeraf62ae122016-10-11 11:47:09 +0200698 ' [ temp dir used by test case: ' + test.tempdir + ' ]'
699 else:
Klement Sekera277b89c2016-10-28 13:20:27 +0200700 self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
Klement Sekeraf62ae122016-10-11 11:47:09 +0200701
Damjan Marionf56b77a2016-10-03 19:44:57 +0200702 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200703 """
704 Get test description
705
706 :param test:
707 :returns: test description
708
709 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200710 # TODO: if none print warning not raise exception
711 short_description = test.shortDescription()
712 if self.descriptions and short_description:
713 return short_description
714 else:
715 return str(test)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200716
Damjan Marionf56b77a2016-10-03 19:44:57 +0200717 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200718 """
719 Start a test
720
721 :param test:
722
723 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200724 unittest.TestResult.startTest(self, test)
725 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200726 self.stream.writeln(
727 "Starting " + self.getDescription(test) + " ...")
728 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200729
Damjan Marionf56b77a2016-10-03 19:44:57 +0200730 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200731 """
732 Stop a test
733
734 :param test:
735
736 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200737 unittest.TestResult.stopTest(self, test)
738 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200739 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +0100740 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100741 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200742 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200743 else:
Klement Sekera52e84f32017-01-13 07:25:25 +0100744 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +0100745 self.result_string))
Damjan Marionf56b77a2016-10-03 19:44:57 +0200746
Damjan Marionf56b77a2016-10-03 19:44:57 +0200747 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200748 """
749 Print errors from running the test case
750 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200751 self.stream.writeln()
752 self.printErrorList('ERROR', self.errors)
753 self.printErrorList('FAIL', self.failures)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200754
Damjan Marionf56b77a2016-10-03 19:44:57 +0200755 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200756 """
757 Print error list to the output stream together with error type
758 and test case description.
759
760 :param flavour: error type
761 :param errors: iterable errors
762
763 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200764 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200765 self.stream.writeln(double_line_delim)
766 self.stream.writeln("%s: %s" %
767 (flavour, self.getDescription(test)))
768 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200769 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200770
771
Damjan Marionf56b77a2016-10-03 19:44:57 +0200772class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200773 """
Klement Sekera104543f2017-02-03 07:29:43 +0100774 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200775 """
776 @property
777 def resultclass(self):
778 """Class maintaining the results of the tests"""
779 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +0200780
Klement Sekera7a161da2017-01-17 13:42:48 +0100781 def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
782 failfast=False, buffer=False, resultclass=None):
783 # ignore stream setting here, use hard-coded stdout to be in sync
784 # with prints from VppTestCase methods ...
785 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
786 verbosity, failfast, buffer,
787 resultclass)
788
Klement Sekera104543f2017-02-03 07:29:43 +0100789 test_option = "TEST"
790
791 def parse_test_option(self):
792 try:
793 f = os.getenv(self.test_option)
794 except:
795 f = None
796 filter_file_name = None
797 filter_class_name = None
798 filter_func_name = None
799 if f:
800 if '.' in f:
801 parts = f.split('.')
802 if len(parts) > 3:
803 raise Exception("Unrecognized %s option: %s" %
804 (self.test_option, f))
805 if len(parts) > 2:
806 if parts[2] not in ('*', ''):
807 filter_func_name = parts[2]
808 if parts[1] not in ('*', ''):
809 filter_class_name = parts[1]
810 if parts[0] not in ('*', ''):
811 if parts[0].startswith('test_'):
812 filter_file_name = parts[0]
813 else:
814 filter_file_name = 'test_%s' % parts[0]
815 else:
816 if f.startswith('test_'):
817 filter_file_name = f
818 else:
819 filter_file_name = 'test_%s' % f
820 return filter_file_name, filter_class_name, filter_func_name
821
822 def filter_tests(self, tests, filter_file, filter_class, filter_func):
823 result = unittest.suite.TestSuite()
824 for t in tests:
825 if isinstance(t, unittest.suite.TestSuite):
826 # this is a bunch of tests, recursively filter...
827 x = self.filter_tests(t, filter_file, filter_class,
828 filter_func)
829 if x.countTestCases() > 0:
830 result.addTest(x)
831 elif isinstance(t, unittest.TestCase):
832 # this is a single test
833 parts = t.id().split('.')
834 # t.id() for common cases like this:
835 # test_classifier.TestClassifier.test_acl_ip
836 # apply filtering only if it is so
837 if len(parts) == 3:
838 if filter_file and filter_file != parts[0]:
839 continue
840 if filter_class and filter_class != parts[1]:
841 continue
842 if filter_func and filter_func != parts[2]:
843 continue
844 result.addTest(t)
845 else:
846 # unexpected object, don't touch it
847 result.addTest(t)
848 return result
849
Damjan Marionf56b77a2016-10-03 19:44:57 +0200850 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200851 """
852 Run the tests
853
854 :param test:
855
856 """
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100857 gc.disable() # disable garbage collection, we'll do that manually
Klement Sekeraf62ae122016-10-11 11:47:09 +0200858 print("Running tests using custom test runner") # debug message
Klement Sekera104543f2017-02-03 07:29:43 +0100859 filter_file, filter_class, filter_func = self.parse_test_option()
860 print("Active filters: file=%s, class=%s, function=%s" % (
861 filter_file, filter_class, filter_func))
862 filtered = self.filter_tests(test, filter_file, filter_class,
863 filter_func)
864 print("%s out of %s tests match specified filters" % (
865 filtered.countTestCases(), test.countTestCases()))
866 return super(VppTestRunner, self).run(filtered)