blob: 62b99e582ca3434026c5f2a6e42fce11f1267d91 [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
Paul Vinciguerra72f00042018-11-25 11:05:13 -08005import sys
Ole Trøan162989e2018-11-26 10:27:50 +00006import os
7import select
Paul Vinciguerra496b0de2019-06-20 12:24:12 -04008import signal
Ole Trøan162989e2018-11-26 10:27:50 +00009import unittest
Klement Sekeraf62ae122016-10-11 11:47:09 +020010import tempfile
Klement Sekera277b89c2016-10-28 13:20:27 +020011import time
Paul Vinciguerra72f00042018-11-25 11:05:13 -080012import faulthandler
Ole Trøan162989e2018-11-26 10:27:50 +000013import random
14import copy
Paul Vinciguerra72f00042018-11-25 11:05:13 -080015import psutil
juraj.linkes68ebc832018-11-29 09:37:08 +010016import platform
Ole Trøan162989e2018-11-26 10:27:50 +000017from collections import deque
18from threading import Thread, Event
19from inspect import getdoc, isclass
20from traceback import format_exception
21from logging import FileHandler, DEBUG, Formatter
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -070022
23import scapy.compat
Ole Trøan162989e2018-11-26 10:27:50 +000024from scapy.packet import Raw
Paul Vinciguerra496b0de2019-06-20 12:24:12 -040025import hook as hookmodule
Paul Vinciguerra919efad2018-12-17 21:43:43 -080026from vpp_pg_interface import VppPGInterface
Ole Troana45dc072018-12-21 16:04:22 +010027from vpp_sub_interface import VppSubInterface
Ole Trøan162989e2018-11-26 10:27:50 +000028from vpp_lo_interface import VppLoInterface
Neale Ranns192b13f2019-03-15 02:16:20 -070029from vpp_bvi_interface import VppBviInterface
Ole Trøan162989e2018-11-26 10:27:50 +000030from vpp_papi_provider import VppPapiProvider
31from vpp_papi.vpp_stats import VPPStats
Paul Vinciguerra499ea642019-03-15 09:39:19 -070032from vpp_papi.vpp_transport_shmem import VppTransportShmemIOError
Ole Trøan162989e2018-11-26 10:27:50 +000033from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
34 get_logger, colorize
35from vpp_object import VppObjectRegistry
36from util import ppp, is_core_present
Klement Sekerad81ae412018-05-16 10:52:54 +020037from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
38from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
39from scapy.layers.inet6 import ICMPv6EchoReply
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -080040
Klement Sekeraacb9b8e2017-02-14 02:55:31 +010041if os.name == 'posix' and sys.version_info[0] < 3:
42 # using subprocess32 is recommended by python official documentation
43 # @ https://docs.python.org/2/library/subprocess.html
44 import subprocess32 as subprocess
45else:
46 import subprocess
Klement Sekeraf62ae122016-10-11 11:47:09 +020047
Paul Vinciguerra852f5ef2018-12-15 10:16:35 -080048# Python2/3 compatible
49try:
50 input = raw_input
51except NameError:
52 pass
53
juraj.linkescae64f82018-09-19 15:01:47 +020054PASS = 0
55FAIL = 1
56ERROR = 2
57SKIP = 3
58TEST_RUN = 4
59
Paul Vinciguerra0cbc71d2019-07-03 08:38:38 -040060
61class BoolEnvironmentVariable(object):
62
63 def __init__(self, env_var_name, default='n', true_values=None):
64 self.name = env_var_name
65 self.default = default
66 self.true_values = true_values if true_values is not None else \
67 ("y", "yes", "1")
68
69 def __bool__(self):
70 return os.getenv(self.name, self.default).lower() in self.true_values
71
72 if sys.version_info[0] == 2:
73 __nonzero__ = __bool__
74
75 def __repr__(self):
76 return 'BoolEnvironmentVariable(%r, default=%r, true_values=%r)' % \
77 (self.name, self.default, self.true_values)
78
79
80debug_framework = BoolEnvironmentVariable('TEST_DEBUG')
81if debug_framework:
Klement Sekeraebbaf552018-02-17 13:41:33 +010082 import debug_internal
83
Klement Sekeraf62ae122016-10-11 11:47:09 +020084"""
85 Test framework module.
86
87 The module provides a set of tools for constructing and running tests and
88 representing the results.
89"""
90
Klement Sekeraf62ae122016-10-11 11:47:09 +020091
Paul Vinciguerra496b0de2019-06-20 12:24:12 -040092class VppDiedError(Exception):
93 """ exception for reporting that the subprocess has died."""
94
95 signals_by_value = {v: k for k, v in signal.__dict__.items() if
96 k.startswith('SIG') and not k.startswith('SIG_')}
97
Paul Vinciguerra5dd6a7b2019-06-19 10:29:24 -040098 def __init__(self, rv=None, testcase=None, method_name=None):
Paul Vinciguerra496b0de2019-06-20 12:24:12 -040099 self.rv = rv
100 self.signal_name = None
Paul Vinciguerra5dd6a7b2019-06-19 10:29:24 -0400101 self.testcase = testcase
102 self.method_name = method_name
103
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400104 try:
105 self.signal_name = VppDiedError.signals_by_value[-rv]
Paul Vinciguerrafea82602019-06-26 20:45:08 -0400106 except (KeyError, TypeError):
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400107 pass
108
Paul Vinciguerra5dd6a7b2019-06-19 10:29:24 -0400109 if testcase is None and method_name is None:
110 in_msg = ''
111 else:
112 in_msg = 'running %s.%s ' % (testcase, method_name)
113
114 msg = "VPP subprocess died %sunexpectedly with return code: %d%s." % (
115 in_msg,
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400116 self.rv,
Paul Vinciguerraf7457522019-07-13 09:35:38 -0400117 ' [%s]' % (self.signal_name if
118 self.signal_name is not None else ''))
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400119 super(VppDiedError, self).__init__(msg)
120
121
Damjan Marionf56b77a2016-10-03 19:44:57 +0200122class _PacketInfo(object):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200123 """Private class to create packet info object.
124
125 Help process information about the next packet.
126 Set variables to default values.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200127 """
Matej Klotton86d87c42016-11-11 11:38:55 +0100128 #: Store the index of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200129 index = -1
Matej Klotton86d87c42016-11-11 11:38:55 +0100130 #: Store the index of the source packet generator interface of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200131 src = -1
Matej Klotton86d87c42016-11-11 11:38:55 +0100132 #: Store the index of the destination packet generator interface
133 #: of the packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200134 dst = -1
Pavel Kotucek59dda062017-03-02 15:22:47 +0100135 #: Store expected ip version
136 ip = -1
137 #: Store expected upper protocol
138 proto = -1
Matej Klotton86d87c42016-11-11 11:38:55 +0100139 #: Store the copy of the former packet.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200140 data = None
Damjan Marionf56b77a2016-10-03 19:44:57 +0200141
Matej Klotton16a14cd2016-12-07 15:09:13 +0100142 def __eq__(self, other):
143 index = self.index == other.index
144 src = self.src == other.src
145 dst = self.dst == other.dst
146 data = self.data == other.data
147 return index and src and dst and data
148
Klement Sekeraf62ae122016-10-11 11:47:09 +0200149
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100150def pump_output(testclass):
151 """ pump output from vpp stdout/stderr to proper queues """
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100152 stdout_fragment = ""
153 stderr_fragment = ""
Neale Ranns16782362018-07-23 05:35:56 -0400154 while not testclass.pump_thread_stop_flag.is_set():
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100155 readable = select.select([testclass.vpp.stdout.fileno(),
156 testclass.vpp.stderr.fileno(),
157 testclass.pump_thread_wakeup_pipe[0]],
158 [], [])[0]
159 if testclass.vpp.stdout.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100160 read = os.read(testclass.vpp.stdout.fileno(), 102400)
161 if len(read) > 0:
Ole Troan45ec5702019-10-17 01:53:47 +0200162 split = read.decode('ascii',
163 errors='backslashreplace').splitlines(True)
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100164 if len(stdout_fragment) > 0:
165 split[0] = "%s%s" % (stdout_fragment, split[0])
166 if len(split) > 0 and split[-1].endswith("\n"):
167 limit = None
168 else:
169 limit = -1
170 stdout_fragment = split[-1]
171 testclass.vpp_stdout_deque.extend(split[:limit])
172 if not testclass.cache_vpp_output:
173 for line in split[:limit]:
174 testclass.logger.debug(
175 "VPP STDOUT: %s" % line.rstrip("\n"))
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100176 if testclass.vpp.stderr.fileno() in readable:
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100177 read = os.read(testclass.vpp.stderr.fileno(), 102400)
178 if len(read) > 0:
Ole Troan6ed154f2019-10-15 19:31:55 +0200179 split = read.decode('ascii',
180 errors='backslashreplace').splitlines(True)
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100181 if len(stderr_fragment) > 0:
182 split[0] = "%s%s" % (stderr_fragment, split[0])
Ole Troan6ed154f2019-10-15 19:31:55 +0200183 if len(split) > 0 and split[-1].endswith("\n"):
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100184 limit = None
185 else:
186 limit = -1
187 stderr_fragment = split[-1]
Ole Troan6ed154f2019-10-15 19:31:55 +0200188
Klement Sekera6a6f4f72017-11-09 09:16:39 +0100189 testclass.vpp_stderr_deque.extend(split[:limit])
190 if not testclass.cache_vpp_output:
191 for line in split[:limit]:
192 testclass.logger.debug(
193 "VPP STDERR: %s" % line.rstrip("\n"))
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -0800194 # ignoring the dummy pipe here intentionally - the
195 # flag will take care of properly terminating the loop
Klement Sekera277b89c2016-10-28 13:20:27 +0200196
197
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800198def _is_skip_aarch64_set():
Paul Vinciguerra0cbc71d2019-07-03 08:38:38 -0400199 return BoolEnvironmentVariable('SKIP_AARCH64')
juraj.linkes68ebc832018-11-29 09:37:08 +0100200
Klement Sekera6aa58b72019-05-16 14:34:55 +0200201
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800202is_skip_aarch64_set = _is_skip_aarch64_set()
juraj.linkes68ebc832018-11-29 09:37:08 +0100203
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800204
205def _is_platform_aarch64():
juraj.linkes68ebc832018-11-29 09:37:08 +0100206 return platform.machine() == 'aarch64'
207
Klement Sekera6aa58b72019-05-16 14:34:55 +0200208
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800209is_platform_aarch64 = _is_platform_aarch64()
juraj.linkes68ebc832018-11-29 09:37:08 +0100210
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800211
212def _running_extended_tests():
Paul Vinciguerra0cbc71d2019-07-03 08:38:38 -0400213 return BoolEnvironmentVariable("EXTENDED_TESTS")
Klement Sekera87134932017-03-07 11:39:27 +0100214
Klement Sekera6aa58b72019-05-16 14:34:55 +0200215
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800216running_extended_tests = _running_extended_tests()
Klement Sekera87134932017-03-07 11:39:27 +0100217
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800218
219def _running_on_centos():
Klement Sekera13a83ef2018-03-21 12:35:51 +0100220 os_id = os.getenv("OS_ID", "")
221 return True if "centos" in os_id.lower() else False
Klement Sekerad3e671e2017-09-29 12:36:37 +0200222
Klement Sekera6aa58b72019-05-16 14:34:55 +0200223
Klement Sekera3a350702019-09-02 14:26:26 +0000224running_on_centos = _running_on_centos()
Paul Vinciguerradefde0f2018-12-06 07:46:13 -0800225
Klement Sekerad3e671e2017-09-29 12:36:37 +0200226
Klement Sekera909a6a12017-08-08 04:33:53 +0200227class KeepAliveReporter(object):
228 """
229 Singleton object which reports test start to parent process
230 """
231 _shared_state = {}
232
233 def __init__(self):
234 self.__dict__ = self._shared_state
Paul Vinciguerrac7b03fe2018-11-18 08:17:34 -0800235 self._pipe = None
Klement Sekera909a6a12017-08-08 04:33:53 +0200236
237 @property
238 def pipe(self):
239 return self._pipe
240
241 @pipe.setter
242 def pipe(self, pipe):
Paul Vinciguerrac7b03fe2018-11-18 08:17:34 -0800243 if self._pipe is not None:
Klement Sekera909a6a12017-08-08 04:33:53 +0200244 raise Exception("Internal error - pipe should only be set once.")
245 self._pipe = pipe
246
juraj.linkes40dd73b2018-09-21 13:55:16 +0200247 def send_keep_alive(self, test, desc=None):
Klement Sekera909a6a12017-08-08 04:33:53 +0200248 """
249 Write current test tmpdir & desc to keep-alive pipe to signal liveness
250 """
Klement Sekera3f6ff192017-08-11 06:56:05 +0200251 if self.pipe is None:
252 # if not running forked..
253 return
254
Klement Sekera909a6a12017-08-08 04:33:53 +0200255 if isclass(test):
juraj.linkes40dd73b2018-09-21 13:55:16 +0200256 desc = '%s (%s)' % (desc, unittest.util.strclass(test))
Klement Sekera909a6a12017-08-08 04:33:53 +0200257 else:
juraj.linkes40dd73b2018-09-21 13:55:16 +0200258 desc = test.id()
Klement Sekera909a6a12017-08-08 04:33:53 +0200259
Dave Wallacee2efd122017-09-30 22:04:21 -0400260 self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
Klement Sekera909a6a12017-08-08 04:33:53 +0200261
262
Damjan Marionf56b77a2016-10-03 19:44:57 +0200263class VppTestCase(unittest.TestCase):
Matej Klotton86d87c42016-11-11 11:38:55 +0100264 """This subclass is a base class for VPP test cases that are implemented as
265 classes. It provides methods to create and run test case.
Klement Sekeraf62ae122016-10-11 11:47:09 +0200266 """
267
Ole Troana45dc072018-12-21 16:04:22 +0100268 extra_vpp_punt_config = []
269 extra_vpp_plugin_config = []
Pavel Kotuceke88865d2018-11-28 07:42:11 +0100270
Klement Sekeraf62ae122016-10-11 11:47:09 +0200271 @property
272 def packet_infos(self):
273 """List of packet infos"""
274 return self._packet_infos
275
Klement Sekeradab231a2016-12-21 08:50:14 +0100276 @classmethod
277 def get_packet_count_for_if_idx(cls, dst_if_index):
278 """Get the number of packet info for specified destination if index"""
279 if dst_if_index in cls._packet_count_for_dst_if_idx:
280 return cls._packet_count_for_dst_if_idx[dst_if_index]
281 else:
282 return 0
Klement Sekeraf62ae122016-10-11 11:47:09 +0200283
284 @classmethod
285 def instance(cls):
286 """Return the instance of this testcase"""
287 return cls.test_instance
288
Damjan Marionf56b77a2016-10-03 19:44:57 +0200289 @classmethod
Klement Sekera277b89c2016-10-28 13:20:27 +0200290 def set_debug_flags(cls, d):
Dave Wallace24564332019-10-21 02:53:14 +0000291 cls.gdbserver_port = 7777
Klement Sekera277b89c2016-10-28 13:20:27 +0200292 cls.debug_core = False
293 cls.debug_gdb = False
294 cls.debug_gdbserver = False
Dave Wallace24564332019-10-21 02:53:14 +0000295 cls.debug_all = False
Klement Sekera277b89c2016-10-28 13:20:27 +0200296 if d is None:
297 return
298 dl = d.lower()
299 if dl == "core":
Klement Sekera277b89c2016-10-28 13:20:27 +0200300 cls.debug_core = True
Dave Wallace24564332019-10-21 02:53:14 +0000301 elif dl == "gdb" or dl == "gdb-all":
Klement Sekera277b89c2016-10-28 13:20:27 +0200302 cls.debug_gdb = True
Dave Wallace24564332019-10-21 02:53:14 +0000303 elif dl == "gdbserver" or dl == "gdbserver-all":
Klement Sekera277b89c2016-10-28 13:20:27 +0200304 cls.debug_gdbserver = True
305 else:
306 raise Exception("Unrecognized DEBUG option: '%s'" % d)
Dave Wallace24564332019-10-21 02:53:14 +0000307 if dl == "gdb-all" or dl == "gdbserver-all":
308 cls.debug_all = True
Klement Sekera277b89c2016-10-28 13:20:27 +0200309
Paul Vinciguerra86ebba62018-11-21 09:28:32 -0800310 @staticmethod
311 def get_least_used_cpu():
juraj.linkes184870a2018-07-16 14:22:01 +0200312 cpu_usage_list = [set(range(psutil.cpu_count()))]
313 vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name'])
314 if 'vpp_main' == p.info['name']]
315 for vpp_process in vpp_processes:
316 for cpu_usage_set in cpu_usage_list:
317 try:
318 cpu_num = vpp_process.cpu_num()
319 if cpu_num in cpu_usage_set:
320 cpu_usage_set_index = cpu_usage_list.index(
321 cpu_usage_set)
322 if cpu_usage_set_index == len(cpu_usage_list) - 1:
323 cpu_usage_list.append({cpu_num})
324 else:
325 cpu_usage_list[cpu_usage_set_index + 1].add(
326 cpu_num)
327 cpu_usage_set.remove(cpu_num)
328 break
329 except psutil.NoSuchProcess:
330 pass
331
332 for cpu_usage_set in cpu_usage_list:
333 if len(cpu_usage_set) > 0:
334 min_usage_set = cpu_usage_set
335 break
336
337 return random.choice(tuple(min_usage_set))
338
Paul Vinciguerra86ebba62018-11-21 09:28:32 -0800339 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200340 def setUpConstants(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200341 """ Set-up the test case class based on environment variables """
Paul Vinciguerra0cbc71d2019-07-03 08:38:38 -0400342 cls.step = BoolEnvironmentVariable('STEP')
Klement Sekera13a83ef2018-03-21 12:35:51 +0100343 d = os.getenv("DEBUG", None)
Paul Vinciguerra0cbc71d2019-07-03 08:38:38 -0400344 # inverted case to handle '' == True
Klement Sekera13a83ef2018-03-21 12:35:51 +0100345 c = os.getenv("CACHE_OUTPUT", "1")
346 cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
Klement Sekera277b89c2016-10-28 13:20:27 +0200347 cls.set_debug_flags(d)
Klement Sekerab8c72a42018-11-08 11:21:39 +0100348 cls.vpp_bin = os.getenv('VPP_BIN', "vpp")
349 cls.plugin_path = os.getenv('VPP_PLUGIN_PATH')
Dave Barach7d31ab22019-05-08 19:18:18 -0400350 cls.test_plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
Klement Sekera47e275b2017-03-21 08:21:25 +0100351 cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
352 plugin_path = None
353 if cls.plugin_path is not None:
354 if cls.extern_plugin_path is not None:
355 plugin_path = "%s:%s" % (
356 cls.plugin_path, cls.extern_plugin_path)
Klement Sekera6abbc282017-03-24 05:47:15 +0100357 else:
358 plugin_path = cls.plugin_path
Klement Sekera47e275b2017-03-21 08:21:25 +0100359 elif cls.extern_plugin_path is not None:
360 plugin_path = cls.extern_plugin_path
Ole Troana45dc072018-12-21 16:04:22 +0100361 debug_cli = ""
Klement Sekera01bbbe92016-11-02 09:25:05 +0100362 if cls.step or cls.debug_gdb or cls.debug_gdbserver:
Ole Troana45dc072018-12-21 16:04:22 +0100363 debug_cli = "cli-listen localhost:5002"
Klement Sekera80a7f0a2017-03-02 11:27:11 +0100364 coredump_size = None
Klement Sekera13a83ef2018-03-21 12:35:51 +0100365 size = os.getenv("COREDUMP_SIZE")
Ole Troana45dc072018-12-21 16:04:22 +0100366 if size is not None:
367 coredump_size = "coredump-size %s" % size
368 if coredump_size is None:
369 coredump_size = "coredump-size unlimited"
juraj.linkes184870a2018-07-16 14:22:01 +0200370
Ole Troana45dc072018-12-21 16:04:22 +0100371 cpu_core_number = cls.get_least_used_cpu()
Klement Sekera630ab582019-07-19 09:14:19 +0000372 if not hasattr(cls, "worker_config"):
373 cls.worker_config = ""
juraj.linkes184870a2018-07-16 14:22:01 +0200374
Ole Troana45dc072018-12-21 16:04:22 +0100375 cls.vpp_cmdline = [cls.vpp_bin, "unix",
376 "{", "nodaemon", debug_cli, "full-coredump",
377 coredump_size, "runtime-dir", cls.tempdir, "}",
378 "api-trace", "{", "on", "}", "api-segment", "{",
379 "prefix", cls.shm_prefix, "}", "cpu", "{",
Klement Sekera630ab582019-07-19 09:14:19 +0000380 "main-core", str(cpu_core_number),
381 cls.worker_config, "}",
Ole Troan4ff09ae2019-04-15 11:27:22 +0200382 "statseg", "{", "socket-name", cls.stats_sock, "}",
383 "socksvr", "{", "socket-name", cls.api_sock, "}",
384 "plugins",
Ole Troana45dc072018-12-21 16:04:22 +0100385 "{", "plugin", "dpdk_plugin.so", "{", "disable",
Ole Troan2e1c8962019-04-10 09:44:23 +0200386 "}", "plugin", "rdma_plugin.so", "{", "disable",
Ole Troana45dc072018-12-21 16:04:22 +0100387 "}", "plugin", "unittest_plugin.so", "{", "enable",
388 "}"] + cls.extra_vpp_plugin_config + ["}", ]
389 if cls.extra_vpp_punt_config is not None:
390 cls.vpp_cmdline.extend(cls.extra_vpp_punt_config)
Klement Sekera47e275b2017-03-21 08:21:25 +0100391 if plugin_path is not None:
Ole Troana45dc072018-12-21 16:04:22 +0100392 cls.vpp_cmdline.extend(["plugin_path", plugin_path])
Dave Barach7d31ab22019-05-08 19:18:18 -0400393 if cls.test_plugin_path is not None:
394 cls.vpp_cmdline.extend(["test_plugin_path", cls.test_plugin_path])
395
Klement Sekeraf37c3ba2018-11-08 11:24:34 +0100396 cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline)
397 cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline))
Klement Sekera277b89c2016-10-28 13:20:27 +0200398
399 @classmethod
400 def wait_for_enter(cls):
401 if cls.debug_gdbserver:
402 print(double_line_delim)
403 print("Spawned GDB server with PID: %d" % cls.vpp.pid)
404 elif cls.debug_gdb:
405 print(double_line_delim)
406 print("Spawned VPP with PID: %d" % cls.vpp.pid)
407 else:
408 cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
409 return
410 print(single_line_delim)
Dave Wallace24564332019-10-21 02:53:14 +0000411 print("You can debug VPP using:")
Klement Sekera277b89c2016-10-28 13:20:27 +0200412 if cls.debug_gdbserver:
Paul Vinciguerra3a9f11e2019-06-18 22:59:55 -0400413 print("sudo gdb " + cls.vpp_bin +
Dave Wallace24564332019-10-21 02:53:14 +0000414 " -ex 'target remote localhost:{port}'"
415 .format(port=cls.gdbserver_port))
416 print("Now is the time to attach gdb by running the above "
417 "command, set up breakpoints etc., then resume VPP from "
Klement Sekera277b89c2016-10-28 13:20:27 +0200418 "within gdb by issuing the 'continue' command")
Dave Wallace24564332019-10-21 02:53:14 +0000419 cls.gdbserver_port += 1
Klement Sekera277b89c2016-10-28 13:20:27 +0200420 elif cls.debug_gdb:
Paul Vinciguerra3a9f11e2019-06-18 22:59:55 -0400421 print("sudo gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
Dave Wallace24564332019-10-21 02:53:14 +0000422 print("Now is the time to attach gdb by running the above "
423 "command and set up breakpoints etc., then resume VPP from"
424 " within gdb by issuing the 'continue' command")
Klement Sekera277b89c2016-10-28 13:20:27 +0200425 print(single_line_delim)
Paul Vinciguerra852f5ef2018-12-15 10:16:35 -0800426 input("Press ENTER to continue running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200427
428 @classmethod
429 def run_vpp(cls):
430 cmdline = cls.vpp_cmdline
431
432 if cls.debug_gdbserver:
Klement Sekera931be3a2016-11-03 05:36:01 +0100433 gdbserver = '/usr/bin/gdbserver'
434 if not os.path.isfile(gdbserver) or \
435 not os.access(gdbserver, os.X_OK):
436 raise Exception("gdbserver binary '%s' does not exist or is "
437 "not executable" % gdbserver)
438
Dave Wallace24564332019-10-21 02:53:14 +0000439 cmdline = [gdbserver, 'localhost:{port}'
440 .format(port=cls.gdbserver_port)] + cls.vpp_cmdline
Klement Sekera277b89c2016-10-28 13:20:27 +0200441 cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
442
Klement Sekera931be3a2016-11-03 05:36:01 +0100443 try:
444 cls.vpp = subprocess.Popen(cmdline,
445 stdout=subprocess.PIPE,
446 stderr=subprocess.PIPE,
447 bufsize=1)
Paul Vinciguerra61e63bf2018-11-24 21:19:38 -0800448 except subprocess.CalledProcessError as e:
Paul Vinciguerra38a4ec72018-11-28 11:34:21 -0800449 cls.logger.critical("Subprocess returned with non-0 return code: ("
450 "%s)", e.returncode)
451 raise
452 except OSError as e:
453 cls.logger.critical("Subprocess returned with OS error: "
454 "(%s) %s", e.errno, e.strerror)
455 raise
456 except Exception as e:
457 cls.logger.exception("Subprocess returned unexpected from "
458 "%s:", cmdline)
Klement Sekera931be3a2016-11-03 05:36:01 +0100459 raise
460
Klement Sekera277b89c2016-10-28 13:20:27 +0200461 cls.wait_for_enter()
Pierre Pfistercd8e3182016-10-07 16:30:03 +0100462
Damjan Marionf56b77a2016-10-03 19:44:57 +0200463 @classmethod
Klement Sekera611864f2018-09-26 11:19:00 +0200464 def wait_for_stats_socket(cls):
Andrew Yourtchenko4f05a8e2019-10-13 10:06:46 +0000465 deadline = time.time() + 300
Paul Vinciguerradd4b2bb2018-11-15 08:13:03 -0800466 ok = False
467 while time.time() < deadline or \
468 cls.debug_gdb or cls.debug_gdbserver:
Klement Sekera611864f2018-09-26 11:19:00 +0200469 if os.path.exists(cls.stats_sock):
Paul Vinciguerradd4b2bb2018-11-15 08:13:03 -0800470 ok = True
Klement Sekera611864f2018-09-26 11:19:00 +0200471 break
Paul Vinciguerra0f6602c2019-03-10 09:10:54 -0700472 cls.sleep(0.8)
Paul Vinciguerradd4b2bb2018-11-15 08:13:03 -0800473 if not ok:
474 cls.logger.critical("Couldn't stat : {}".format(cls.stats_sock))
Klement Sekera611864f2018-09-26 11:19:00 +0200475
476 @classmethod
Dave Wallace3e9b7a22019-04-09 20:19:51 -0400477 def wait_for_coredump(cls):
478 corefile = cls.tempdir + "/core"
479 if os.path.isfile(corefile):
480 cls.logger.error("Waiting for coredump to complete: %s", corefile)
481 curr_size = os.path.getsize(corefile)
482 deadline = time.time() + 60
483 ok = False
484 while time.time() < deadline:
485 cls.sleep(1)
486 size = curr_size
487 curr_size = os.path.getsize(corefile)
488 if size == curr_size:
489 ok = True
490 break
491 if not ok:
492 cls.logger.error("Timed out waiting for coredump to complete:"
493 " %s", corefile)
494 else:
495 cls.logger.error("Coredump complete: %s, size %d",
496 corefile, curr_size)
497
498 @classmethod
Damjan Marionf56b77a2016-10-03 19:44:57 +0200499 def setUpClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200500 """
501 Perform class setup before running the testcase
502 Remove shared memory files, start vpp and connect the vpp-api
503 """
Paul Vinciguerra8d991d92019-01-25 14:05:48 -0800504 super(VppTestCase, cls).setUpClass()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100505 gc.collect() # run garbage collection first
Klement Sekera96867ba2018-02-02 11:27:53 +0100506 random.seed()
juraj.linkesdfb5f2a2018-11-09 11:58:54 +0100507 cls.logger = get_logger(cls.__name__)
508 if hasattr(cls, 'parallel_handler'):
509 cls.logger.addHandler(cls.parallel_handler)
juraj.linkes3d9b92a2018-11-21 13:13:39 +0100510 cls.logger.propagate = False
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -0700511
Klement Sekeraf62ae122016-10-11 11:47:09 +0200512 cls.tempdir = tempfile.mkdtemp(
Klement Sekeraf413bef2017-08-15 07:09:02 +0200513 prefix='vpp-unittest-%s-' % cls.__name__)
Klement Sekera611864f2018-09-26 11:19:00 +0200514 cls.stats_sock = "%s/stats.sock" % cls.tempdir
Ole Troan4ff09ae2019-04-15 11:27:22 +0200515 cls.api_sock = "%s/api.sock" % cls.tempdir
Klement Sekera027dbd52017-04-11 06:01:53 +0200516 cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
517 cls.file_handler.setFormatter(
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100518 Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
519 datefmt="%H:%M:%S"))
Klement Sekera027dbd52017-04-11 06:01:53 +0200520 cls.file_handler.setLevel(DEBUG)
521 cls.logger.addHandler(cls.file_handler)
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -0700522 cls.logger.debug("--- setUpClass() for %s called ---" %
523 cls.__name__)
juraj.linkes184870a2018-07-16 14:22:01 +0200524 cls.shm_prefix = os.path.basename(cls.tempdir)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200525 os.chdir(cls.tempdir)
Klement Sekera277b89c2016-10-28 13:20:27 +0200526 cls.logger.info("Temporary dir is %s, shm prefix is %s",
527 cls.tempdir, cls.shm_prefix)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200528 cls.setUpConstants()
Klement Sekeradab231a2016-12-21 08:50:14 +0100529 cls.reset_packet_infos()
Klement Sekera9225dee2016-12-12 08:36:58 +0100530 cls._captures = []
Klement Sekeraf62ae122016-10-11 11:47:09 +0200531 cls.verbose = 0
Klement Sekera085f5c02016-11-24 01:59:16 +0100532 cls.vpp_dead = False
Klement Sekera10db26f2017-01-11 08:16:53 +0100533 cls.registry = VppObjectRegistry()
Klement Sekera3747c752017-04-10 06:30:17 +0200534 cls.vpp_startup_failed = False
Klement Sekera909a6a12017-08-08 04:33:53 +0200535 cls.reporter = KeepAliveReporter()
Klement Sekeraf62ae122016-10-11 11:47:09 +0200536 # need to catch exceptions here because if we raise, then the cleanup
537 # doesn't get called and we might end with a zombie vpp
538 try:
Klement Sekera277b89c2016-10-28 13:20:27 +0200539 cls.run_vpp()
juraj.linkes40dd73b2018-09-21 13:55:16 +0200540 cls.reporter.send_keep_alive(cls, 'setUpClass')
541 VppTestResult.current_test_case_info = TestCaseInfo(
542 cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin)
Klement Sekerae4504c62016-12-08 10:16:41 +0100543 cls.vpp_stdout_deque = deque()
Klement Sekerae4504c62016-12-08 10:16:41 +0100544 cls.vpp_stderr_deque = deque()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100545 cls.pump_thread_stop_flag = Event()
546 cls.pump_thread_wakeup_pipe = os.pipe()
547 cls.pump_thread = Thread(target=pump_output, args=(cls,))
Klement Sekeraaeeac3b2017-02-14 07:11:52 +0100548 cls.pump_thread.daemon = True
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100549 cls.pump_thread.start()
Klement Sekera611864f2018-09-26 11:19:00 +0200550 if cls.debug_gdb or cls.debug_gdbserver:
551 read_timeout = 0
552 else:
553 read_timeout = 5
554 cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls,
555 read_timeout)
Klement Sekera085f5c02016-11-24 01:59:16 +0100556 if cls.step:
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400557 hook = hookmodule.StepHook(cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100558 else:
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400559 hook = hookmodule.PollHook(cls)
Klement Sekera085f5c02016-11-24 01:59:16 +0100560 cls.vapi.register_hook(hook)
Klement Sekera611864f2018-09-26 11:19:00 +0200561 cls.wait_for_stats_socket()
562 cls.statistics = VPPStats(socketname=cls.stats_sock)
Klement Sekera3747c752017-04-10 06:30:17 +0200563 try:
564 hook.poll_vpp()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100565 except VppDiedError:
Klement Sekera3747c752017-04-10 06:30:17 +0200566 cls.vpp_startup_failed = True
567 cls.logger.critical(
568 "VPP died shortly after startup, check the"
569 " output to standard error for possible cause")
570 raise
Klement Sekera085f5c02016-11-24 01:59:16 +0100571 try:
572 cls.vapi.connect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100573 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100574 try:
575 cls.vapi.disconnect()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100576 except Exception:
Klement Sekera4c533132018-02-22 11:41:12 +0100577 pass
Klement Sekera085f5c02016-11-24 01:59:16 +0100578 if cls.debug_gdbserver:
579 print(colorize("You're running VPP inside gdbserver but "
580 "VPP-API connection failed, did you forget "
581 "to 'continue' VPP from within gdb?", RED))
582 raise
Andrew Yourtchenko4f05a8e2019-10-13 10:06:46 +0000583 except Exception as e:
584 cls.logger.debug("Exception connecting to VPP: %s" % e)
Paul Vinciguerra496b0de2019-06-20 12:24:12 -0400585
586 cls.quit()
Klement Sekera13a83ef2018-03-21 12:35:51 +0100587 raise
Damjan Marionf56b77a2016-10-03 19:44:57 +0200588
Damjan Marionf56b77a2016-10-03 19:44:57 +0200589 @classmethod
590 def quit(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200591 """
592 Disconnect vpp-api, kill vpp and cleanup shared memory files
593 """
Klement Sekera277b89c2016-10-28 13:20:27 +0200594 if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
595 cls.vpp.poll()
596 if cls.vpp.returncode is None:
Dave Wallace24564332019-10-21 02:53:14 +0000597 print()
Klement Sekera277b89c2016-10-28 13:20:27 +0200598 print(double_line_delim)
599 print("VPP or GDB server is still running")
600 print(single_line_delim)
Paul Vinciguerra852f5ef2018-12-15 10:16:35 -0800601 input("When done debugging, press ENTER to kill the "
602 "process and finish running the testcase...")
Klement Sekera277b89c2016-10-28 13:20:27 +0200603
juraj.linkes184870a2018-07-16 14:22:01 +0200604 # first signal that we want to stop the pump thread, then wake it up
605 if hasattr(cls, 'pump_thread_stop_flag'):
606 cls.pump_thread_stop_flag.set()
607 if hasattr(cls, 'pump_thread_wakeup_pipe'):
Ole Troan7f991832018-12-06 17:35:12 +0100608 os.write(cls.pump_thread_wakeup_pipe[1], b'ding dong wake up')
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100609 if hasattr(cls, 'pump_thread'):
610 cls.logger.debug("Waiting for pump thread to stop")
611 cls.pump_thread.join()
612 if hasattr(cls, 'vpp_stderr_reader_thread'):
613 cls.logger.debug("Waiting for stdderr pump to stop")
614 cls.vpp_stderr_reader_thread.join()
615
Klement Sekeraf62ae122016-10-11 11:47:09 +0200616 if hasattr(cls, 'vpp'):
Klement Sekera0529a742016-12-02 07:05:24 +0100617 if hasattr(cls, 'vapi'):
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700618 cls.logger.debug("Disconnecting class vapi client on %s",
619 cls.__name__)
Klement Sekera0529a742016-12-02 07:05:24 +0100620 cls.vapi.disconnect()
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700621 cls.logger.debug("Deleting class vapi attribute on %s",
622 cls.__name__)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100623 del cls.vapi
Klement Sekeraf62ae122016-10-11 11:47:09 +0200624 cls.vpp.poll()
625 if cls.vpp.returncode is None:
Dave Wallace3e9b7a22019-04-09 20:19:51 -0400626 cls.wait_for_coredump()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100627 cls.logger.debug("Sending TERM to vpp")
Dave Barachad646872019-05-06 10:49:41 -0400628 cls.vpp.terminate()
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100629 cls.logger.debug("Waiting for vpp to die")
630 cls.vpp.communicate()
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700631 cls.logger.debug("Deleting class vpp attribute on %s",
632 cls.__name__)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200633 del cls.vpp
Damjan Marionf56b77a2016-10-03 19:44:57 +0200634
Klement Sekera3747c752017-04-10 06:30:17 +0200635 if cls.vpp_startup_failed:
636 stdout_log = cls.logger.info
637 stderr_log = cls.logger.critical
638 else:
639 stdout_log = cls.logger.info
640 stderr_log = cls.logger.info
641
Klement Sekerae4504c62016-12-08 10:16:41 +0100642 if hasattr(cls, 'vpp_stdout_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200643 stdout_log(single_line_delim)
644 stdout_log('VPP output to stdout while running %s:', cls.__name__)
645 stdout_log(single_line_delim)
Klement Sekerae4504c62016-12-08 10:16:41 +0100646 vpp_output = "".join(cls.vpp_stdout_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200647 with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
648 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200649 stdout_log('\n%s', vpp_output)
650 stdout_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200651
Klement Sekerae4504c62016-12-08 10:16:41 +0100652 if hasattr(cls, 'vpp_stderr_deque'):
Klement Sekera3747c752017-04-10 06:30:17 +0200653 stderr_log(single_line_delim)
654 stderr_log('VPP output to stderr while running %s:', cls.__name__)
655 stderr_log(single_line_delim)
Klement Sekera0ede47a2019-01-29 11:49:09 +0100656 vpp_output = "".join(cls.vpp_stderr_deque)
Klement Sekera027dbd52017-04-11 06:01:53 +0200657 with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
658 f.write(vpp_output)
Klement Sekera3747c752017-04-10 06:30:17 +0200659 stderr_log('\n%s', vpp_output)
660 stderr_log(single_line_delim)
Klement Sekera277b89c2016-10-28 13:20:27 +0200661
Damjan Marionf56b77a2016-10-03 19:44:57 +0200662 @classmethod
663 def tearDownClass(cls):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200664 """ Perform final cleanup after running all tests in this test-case """
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -0700665 cls.logger.debug("--- tearDownClass() for %s called ---" %
666 cls.__name__)
juraj.linkes40dd73b2018-09-21 13:55:16 +0200667 cls.reporter.send_keep_alive(cls, 'tearDownClass')
Damjan Marionf56b77a2016-10-03 19:44:57 +0200668 cls.quit()
Klement Sekera027dbd52017-04-11 06:01:53 +0200669 cls.file_handler.close()
Klement Sekeraebbaf552018-02-17 13:41:33 +0100670 cls.reset_packet_infos()
671 if debug_framework:
672 debug_internal.on_tear_down_class(cls)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200673
Paul Vinciguerra90cf21b2019-03-13 09:23:05 -0700674 def show_commands_at_teardown(self):
675 """ Allow subclass specific teardown logging additions."""
676 self.logger.info("--- No test specific show commands provided. ---")
677
Damjan Marionf56b77a2016-10-03 19:44:57 +0200678 def tearDown(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200679 """ Show various debug prints after each test """
Klement Sekerab91017a2017-02-09 06:04:36 +0100680 self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
681 (self.__class__.__name__, self._testMethodName,
682 self._testMethodDoc))
Paul Vinciguerra499ea642019-03-15 09:39:19 -0700683
684 try:
685 if not self.vpp_dead:
686 self.logger.debug(self.vapi.cli("show trace max 1000"))
687 self.logger.info(self.vapi.ppcli("show interface"))
688 self.logger.info(self.vapi.ppcli("show hardware"))
689 self.logger.info(self.statistics.set_errors_str())
690 self.logger.info(self.vapi.ppcli("show run"))
691 self.logger.info(self.vapi.ppcli("show log"))
Dave Barach32dcd3b2019-07-08 12:25:38 -0400692 self.logger.info(self.vapi.ppcli("show bihash"))
Paul Vinciguerra499ea642019-03-15 09:39:19 -0700693 self.logger.info("Logging testcase specific show commands.")
694 self.show_commands_at_teardown()
695 self.registry.remove_vpp_config(self.logger)
Dave Wallace90c55722017-02-16 11:25:26 -0500696 # Save/Dump VPP api trace log
Andrew Yourtchenko586d3ed2019-10-21 12:55:48 +0000697 m = self._testMethodName
698 api_trace = "vpp_api_trace.%s.%d.log" % (m, self.vpp.pid)
Dave Wallace90c55722017-02-16 11:25:26 -0500699 tmp_api_trace = "/tmp/%s" % api_trace
700 vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
701 self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
702 self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
703 vpp_api_trace_log))
704 os.rename(tmp_api_trace, vpp_api_trace_log)
Dave Wallace5ba58372018-02-13 16:14:06 -0500705 self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
Dave Wallace90c55722017-02-16 11:25:26 -0500706 vpp_api_trace_log))
Paul Vinciguerra499ea642019-03-15 09:39:19 -0700707 except VppTransportShmemIOError:
708 self.logger.debug("VppTransportShmemIOError: Vpp dead. "
709 "Cannot log show commands.")
710 self.vpp_dead = True
Klement Sekera1b686402017-03-02 11:29:19 +0100711 else:
712 self.registry.unregister_all(self.logger)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200713
Damjan Marionf56b77a2016-10-03 19:44:57 +0200714 def setUp(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200715 """ Clear trace before running each test"""
Paul Vinciguerra8d991d92019-01-25 14:05:48 -0800716 super(VppTestCase, self).setUp()
Klement Sekera909a6a12017-08-08 04:33:53 +0200717 self.reporter.send_keep_alive(self)
Klement Sekera0c1519b2016-12-08 05:03:32 +0100718 if self.vpp_dead:
Paul Vinciguerrafea82602019-06-26 20:45:08 -0400719
720 raise VppDiedError(rv=None, testcase=self.__class__.__name__,
721 method_name=self._testMethodName)
Klement Sekeraacb9b8e2017-02-14 02:55:31 +0100722 self.sleep(.1, "during setUp")
Klement Sekerae4504c62016-12-08 10:16:41 +0100723 self.vpp_stdout_deque.append(
724 "--- test setUp() for %s.%s(%s) starts here ---\n" %
725 (self.__class__.__name__, self._testMethodName,
726 self._testMethodDoc))
727 self.vpp_stderr_deque.append(
728 "--- test setUp() for %s.%s(%s) starts here ---\n" %
729 (self.__class__.__name__, self._testMethodName,
730 self._testMethodDoc))
Klement Sekeraf62ae122016-10-11 11:47:09 +0200731 self.vapi.cli("clear trace")
732 # store the test instance inside the test class - so that objects
733 # holding the class can access instance methods (like assertEqual)
734 type(self).test_instance = self
Damjan Marionf56b77a2016-10-03 19:44:57 +0200735
Damjan Marionf56b77a2016-10-03 19:44:57 +0200736 @classmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200737 def pg_enable_capture(cls, interfaces=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200738 """
739 Enable capture on packet-generator interfaces
Damjan Marionf56b77a2016-10-03 19:44:57 +0200740
Klement Sekera75e7d132017-09-20 08:26:30 +0200741 :param interfaces: iterable interface indexes (if None,
742 use self.pg_interfaces)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200743
Klement Sekeraf62ae122016-10-11 11:47:09 +0200744 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200745 if interfaces is None:
746 interfaces = cls.pg_interfaces
Klement Sekeraf62ae122016-10-11 11:47:09 +0200747 for i in interfaces:
748 i.enable_capture()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200749
Damjan Marionf56b77a2016-10-03 19:44:57 +0200750 @classmethod
Klement Sekera9225dee2016-12-12 08:36:58 +0100751 def register_capture(cls, cap_name):
752 """ Register a capture in the testclass """
753 # add to the list of captures with current timestamp
754 cls._captures.append((time.time(), cap_name))
Klement Sekera9225dee2016-12-12 08:36:58 +0100755
756 @classmethod
757 def pg_start(cls):
Andrew Yourtchenko8d829f62019-10-13 10:09:50 +0000758 """ Enable the PG, wait till it is done, then clean up """
Klement Sekerad91fa612019-01-15 13:25:09 +0100759 cls.vapi.cli("trace add pg-input 1000")
Klement Sekeraf62ae122016-10-11 11:47:09 +0200760 cls.vapi.cli('packet-generator enable')
Andrew Yourtchenko8d829f62019-10-13 10:09:50 +0000761 # PG, when starts, runs to completion -
762 # so let's avoid a race condition,
763 # and wait a little till it's done.
764 # Then clean it up - and then be gone.
765 deadline = time.time() + 300
766 while cls.vapi.cli('show packet-generator').find("Yes") != -1:
767 cls.sleep(0.01) # yield
768 if time.time() > deadline:
769 cls.logger.error("Timeout waiting for pg to stop")
770 break
771 for stamp, cap_name in cls._captures:
772 cls.vapi.cli('packet-generator delete %s' % cap_name)
Klement Sekera9225dee2016-12-12 08:36:58 +0100773 cls._captures = []
Damjan Marionf56b77a2016-10-03 19:44:57 +0200774
Damjan Marionf56b77a2016-10-03 19:44:57 +0200775 @classmethod
Mohsin Kazmi22e9cfd2019-07-23 11:54:48 +0200776 def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200777 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100778 Create packet-generator interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200779
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100780 :param interfaces: iterable indexes of the interfaces.
781 :returns: List of created interfaces.
Damjan Marionf56b77a2016-10-03 19:44:57 +0200782
Klement Sekeraf62ae122016-10-11 11:47:09 +0200783 """
784 result = []
785 for i in interfaces:
Mohsin Kazmi22e9cfd2019-07-23 11:54:48 +0200786 intf = VppPGInterface(cls, i, gso, gso_size)
Klement Sekeraf62ae122016-10-11 11:47:09 +0200787 setattr(cls, intf.name, intf)
788 result.append(intf)
789 cls.pg_interfaces = result
790 return result
791
Matej Klotton0178d522016-11-04 11:11:44 +0100792 @classmethod
Klement Sekerab9ef2732018-06-24 22:49:33 +0200793 def create_loopback_interfaces(cls, count):
Matej Klotton0178d522016-11-04 11:11:44 +0100794 """
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100795 Create loopback interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100796
Klement Sekerab9ef2732018-06-24 22:49:33 +0200797 :param count: number of interfaces created.
Matej Klotton8d8a1da2016-12-22 11:06:56 +0100798 :returns: List of created interfaces.
Matej Klotton0178d522016-11-04 11:11:44 +0100799 """
Klement Sekerab9ef2732018-06-24 22:49:33 +0200800 result = [VppLoInterface(cls) for i in range(count)]
801 for intf in result:
Matej Klotton0178d522016-11-04 11:11:44 +0100802 setattr(cls, intf.name, intf)
Matej Klotton0178d522016-11-04 11:11:44 +0100803 cls.lo_interfaces = result
804 return result
805
Neale Ranns192b13f2019-03-15 02:16:20 -0700806 @classmethod
807 def create_bvi_interfaces(cls, count):
808 """
809 Create BVI interfaces.
810
811 :param count: number of interfaces created.
812 :returns: List of created interfaces.
813 """
814 result = [VppBviInterface(cls) for i in range(count)]
815 for intf in result:
816 setattr(cls, intf.name, intf)
817 cls.bvi_interfaces = result
818 return result
819
Damjan Marionf56b77a2016-10-03 19:44:57 +0200820 @staticmethod
Klement Sekera75e7d132017-09-20 08:26:30 +0200821 def extend_packet(packet, size, padding=' '):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200822 """
Klement Sekera75e7d132017-09-20 08:26:30 +0200823 Extend packet to given size by padding with spaces or custom padding
Klement Sekeraf62ae122016-10-11 11:47:09 +0200824 NOTE: Currently works only when Raw layer is present.
825
826 :param packet: packet
827 :param size: target size
Klement Sekera75e7d132017-09-20 08:26:30 +0200828 :param padding: padding used to extend the payload
Klement Sekeraf62ae122016-10-11 11:47:09 +0200829
830 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200831 packet_len = len(packet) + 4
832 extend = size - packet_len
833 if extend > 0:
Alexandre Poirriercc991492019-05-07 10:39:57 +0200834 num = (extend // len(padding)) + 1
835 packet[Raw].load += (padding * num)[:extend].encode("ascii")
Damjan Marionf56b77a2016-10-03 19:44:57 +0200836
Klement Sekeradab231a2016-12-21 08:50:14 +0100837 @classmethod
838 def reset_packet_infos(cls):
839 """ Reset the list of packet info objects and packet counts to zero """
840 cls._packet_infos = {}
841 cls._packet_count_for_dst_if_idx = {}
Klement Sekeraf62ae122016-10-11 11:47:09 +0200842
Klement Sekeradab231a2016-12-21 08:50:14 +0100843 @classmethod
844 def create_packet_info(cls, src_if, dst_if):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200845 """
846 Create packet info object containing the source and destination indexes
847 and add it to the testcase's packet info list
848
Klement Sekeradab231a2016-12-21 08:50:14 +0100849 :param VppInterface src_if: source interface
850 :param VppInterface dst_if: destination interface
Klement Sekeraf62ae122016-10-11 11:47:09 +0200851
852 :returns: _PacketInfo object
853
854 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200855 info = _PacketInfo()
Klement Sekeradab231a2016-12-21 08:50:14 +0100856 info.index = len(cls._packet_infos)
857 info.src = src_if.sw_if_index
858 info.dst = dst_if.sw_if_index
859 if isinstance(dst_if, VppSubInterface):
860 dst_idx = dst_if.parent.sw_if_index
861 else:
862 dst_idx = dst_if.sw_if_index
863 if dst_idx in cls._packet_count_for_dst_if_idx:
864 cls._packet_count_for_dst_if_idx[dst_idx] += 1
865 else:
866 cls._packet_count_for_dst_if_idx[dst_idx] = 1
867 cls._packet_infos[info.index] = info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200868 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200869
Damjan Marionf56b77a2016-10-03 19:44:57 +0200870 @staticmethod
871 def info_to_payload(info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200872 """
873 Convert _PacketInfo object to packet payload
874
875 :param info: _PacketInfo object
876
877 :returns: string containing serialized data from packet info
878 """
Pavel Kotucek59dda062017-03-02 15:22:47 +0100879 return "%d %d %d %d %d" % (info.index, info.src, info.dst,
880 info.ip, info.proto)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200881
Damjan Marionf56b77a2016-10-03 19:44:57 +0200882 @staticmethod
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800883 def payload_to_info(payload, payload_field='load'):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200884 """
885 Convert packet payload to _PacketInfo object
886
887 :param payload: packet payload
Paul Vinciguerra22364e42019-03-12 20:04:56 -0700888 :type payload: <class 'scapy.packet.Raw'>
889 :param payload_field: packet fieldname of payload "load" for
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800890 <class 'scapy.packet.Raw'>
Paul Vinciguerra22364e42019-03-12 20:04:56 -0700891 :type payload_field: str
Klement Sekeraf62ae122016-10-11 11:47:09 +0200892 :returns: _PacketInfo object containing de-serialized data from payload
893
894 """
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800895 numbers = getattr(payload, payload_field).split()
Damjan Marionf56b77a2016-10-03 19:44:57 +0200896 info = _PacketInfo()
897 info.index = int(numbers[0])
898 info.src = int(numbers[1])
899 info.dst = int(numbers[2])
Pavel Kotucek59dda062017-03-02 15:22:47 +0100900 info.ip = int(numbers[3])
901 info.proto = int(numbers[4])
Damjan Marionf56b77a2016-10-03 19:44:57 +0200902 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200903
Damjan Marionf56b77a2016-10-03 19:44:57 +0200904 def get_next_packet_info(self, info):
Klement Sekeraf62ae122016-10-11 11:47:09 +0200905 """
906 Iterate over the packet info list stored in the testcase
907 Start iteration with first element if info is None
908 Continue based on index in info if info is specified
909
910 :param info: info or None
911 :returns: next info in list or None if no more infos
912 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200913 if info is None:
914 next_index = 0
915 else:
916 next_index = info.index + 1
Klement Sekeradab231a2016-12-21 08:50:14 +0100917 if next_index == len(self._packet_infos):
Damjan Marionf56b77a2016-10-03 19:44:57 +0200918 return None
919 else:
Klement Sekeradab231a2016-12-21 08:50:14 +0100920 return self._packet_infos[next_index]
Damjan Marionf56b77a2016-10-03 19:44:57 +0200921
Klement Sekeraf62ae122016-10-11 11:47:09 +0200922 def get_next_packet_info_for_interface(self, src_index, info):
923 """
924 Search the packet info list for the next packet info with same source
925 interface index
926
927 :param src_index: source interface index to search for
928 :param info: packet info - where to start the search
929 :returns: packet info or None
930
931 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200932 while True:
933 info = self.get_next_packet_info(info)
934 if info is None:
935 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200936 if info.src == src_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200937 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200938
Klement Sekeraf62ae122016-10-11 11:47:09 +0200939 def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
940 """
941 Search the packet info list for the next packet info with same source
942 and destination interface indexes
943
944 :param src_index: source interface index to search for
945 :param dst_index: destination interface index to search for
946 :param info: packet info - where to start the search
947 :returns: packet info or None
948
949 """
Damjan Marionf56b77a2016-10-03 19:44:57 +0200950 while True:
Klement Sekeraf62ae122016-10-11 11:47:09 +0200951 info = self.get_next_packet_info_for_interface(src_index, info)
Damjan Marionf56b77a2016-10-03 19:44:57 +0200952 if info is None:
953 return None
Klement Sekeraf62ae122016-10-11 11:47:09 +0200954 if info.dst == dst_index:
Damjan Marionf56b77a2016-10-03 19:44:57 +0200955 return info
Damjan Marionf56b77a2016-10-03 19:44:57 +0200956
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200957 def assert_equal(self, real_value, expected_value, name_or_class=None):
958 if name_or_class is None:
Klement Sekera239790f2017-02-16 10:53:53 +0100959 self.assertEqual(real_value, expected_value)
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200960 return
961 try:
962 msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
963 msg = msg % (getdoc(name_or_class).strip(),
964 real_value, str(name_or_class(real_value)),
965 expected_value, str(name_or_class(expected_value)))
Klement Sekera13a83ef2018-03-21 12:35:51 +0100966 except Exception:
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200967 msg = "Invalid %s: %s does not match expected value %s" % (
968 name_or_class, real_value, expected_value)
969
970 self.assertEqual(real_value, expected_value, msg)
971
Klement Sekerab17dd962017-01-09 07:43:48 +0100972 def assert_in_range(self,
973 real_value,
974 expected_min,
975 expected_max,
976 name=None):
Klement Sekera0e3c0de2016-09-29 14:43:44 +0200977 if name is None:
978 msg = None
979 else:
980 msg = "Invalid %s: %s out of range <%s,%s>" % (
981 name, real_value, expected_min, expected_max)
982 self.assertTrue(expected_min <= real_value <= expected_max, msg)
983
Klement Sekerad81ae412018-05-16 10:52:54 +0200984 def assert_packet_checksums_valid(self, packet,
985 ignore_zero_udp_checksums=True):
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700986 received = packet.__class__(scapy.compat.raw(packet))
Klement Sekerad81ae412018-05-16 10:52:54 +0200987 udp_layers = ['UDP', 'UDPerror']
988 checksum_fields = ['cksum', 'chksum']
989 checksums = []
990 counter = 0
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -0700991 temp = received.__class__(scapy.compat.raw(received))
Klement Sekerad81ae412018-05-16 10:52:54 +0200992 while True:
993 layer = temp.getlayer(counter)
994 if layer:
995 for cf in checksum_fields:
996 if hasattr(layer, cf):
997 if ignore_zero_udp_checksums and \
Klement Sekera6aa58b72019-05-16 14:34:55 +0200998 0 == getattr(layer, cf) and \
999 layer.name in udp_layers:
Klement Sekerad81ae412018-05-16 10:52:54 +02001000 continue
1001 delattr(layer, cf)
1002 checksums.append((counter, cf))
1003 else:
1004 break
1005 counter = counter + 1
Klement Sekera31da2e32018-06-24 22:49:55 +02001006 if 0 == len(checksums):
1007 return
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -07001008 temp = temp.__class__(scapy.compat.raw(temp))
Klement Sekerad81ae412018-05-16 10:52:54 +02001009 for layer, cf in checksums:
Klement Sekera31da2e32018-06-24 22:49:55 +02001010 calc_sum = getattr(temp[layer], cf)
1011 self.assert_equal(
1012 getattr(received[layer], cf), calc_sum,
1013 "packet checksum on layer #%d: %s" % (layer, temp[layer].name))
1014 self.logger.debug(
1015 "Checksum field `%s` on `%s` layer has correct value `%s`" %
1016 (cf, temp[layer].name, calc_sum))
Klement Sekerad81ae412018-05-16 10:52:54 +02001017
1018 def assert_checksum_valid(self, received_packet, layer,
1019 field_name='chksum',
1020 ignore_zero_checksum=False):
1021 """ Check checksum of received packet on given layer """
1022 received_packet_checksum = getattr(received_packet[layer], field_name)
1023 if ignore_zero_checksum and 0 == received_packet_checksum:
1024 return
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -07001025 recalculated = received_packet.__class__(
1026 scapy.compat.raw(received_packet))
Klement Sekerad81ae412018-05-16 10:52:54 +02001027 delattr(recalculated[layer], field_name)
Paul Vinciguerraa7427ec2019-03-10 10:04:23 -07001028 recalculated = recalculated.__class__(scapy.compat.raw(recalculated))
Klement Sekerad81ae412018-05-16 10:52:54 +02001029 self.assert_equal(received_packet_checksum,
1030 getattr(recalculated[layer], field_name),
1031 "packet checksum on layer: %s" % layer)
1032
1033 def assert_ip_checksum_valid(self, received_packet,
1034 ignore_zero_checksum=False):
1035 self.assert_checksum_valid(received_packet, 'IP',
1036 ignore_zero_checksum=ignore_zero_checksum)
1037
1038 def assert_tcp_checksum_valid(self, received_packet,
1039 ignore_zero_checksum=False):
1040 self.assert_checksum_valid(received_packet, 'TCP',
1041 ignore_zero_checksum=ignore_zero_checksum)
1042
1043 def assert_udp_checksum_valid(self, received_packet,
1044 ignore_zero_checksum=True):
1045 self.assert_checksum_valid(received_packet, 'UDP',
1046 ignore_zero_checksum=ignore_zero_checksum)
1047
1048 def assert_embedded_icmp_checksum_valid(self, received_packet):
1049 if received_packet.haslayer(IPerror):
1050 self.assert_checksum_valid(received_packet, 'IPerror')
1051 if received_packet.haslayer(TCPerror):
1052 self.assert_checksum_valid(received_packet, 'TCPerror')
1053 if received_packet.haslayer(UDPerror):
1054 self.assert_checksum_valid(received_packet, 'UDPerror',
1055 ignore_zero_checksum=True)
1056 if received_packet.haslayer(ICMPerror):
1057 self.assert_checksum_valid(received_packet, 'ICMPerror')
1058
1059 def assert_icmp_checksum_valid(self, received_packet):
1060 self.assert_checksum_valid(received_packet, 'ICMP')
1061 self.assert_embedded_icmp_checksum_valid(received_packet)
1062
1063 def assert_icmpv6_checksum_valid(self, pkt):
1064 if pkt.haslayer(ICMPv6DestUnreach):
1065 self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
1066 self.assert_embedded_icmp_checksum_valid(pkt)
1067 if pkt.haslayer(ICMPv6EchoRequest):
1068 self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
1069 if pkt.haslayer(ICMPv6EchoReply):
1070 self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
1071
Klement Sekera3a343d42019-05-16 14:35:46 +02001072 def get_packet_counter(self, counter):
1073 if counter.startswith("/"):
1074 counter_value = self.statistics.get_counter(counter)
1075 else:
1076 counters = self.vapi.cli("sh errors").split('\n')
Klement Sekera6aa58b72019-05-16 14:34:55 +02001077 counter_value = 0
Klement Sekera3a343d42019-05-16 14:35:46 +02001078 for i in range(1, len(counters) - 1):
1079 results = counters[i].split()
1080 if results[1] == counter:
1081 counter_value = int(results[0])
1082 break
1083 return counter_value
1084
Klement Sekeraf37c3ba2018-11-08 11:24:34 +01001085 def assert_packet_counter_equal(self, counter, expected_value):
Klement Sekera6aa58b72019-05-16 14:34:55 +02001086 counter_value = self.get_packet_counter(counter)
1087 self.assert_equal(counter_value, expected_value,
1088 "packet counter `%s'" % counter)
Klement Sekeraf37c3ba2018-11-08 11:24:34 +01001089
Ole Troan233e4682019-05-16 15:01:34 +02001090 def assert_error_counter_equal(self, counter, expected_value):
1091 counter_value = self.statistics.get_err_counter(counter)
1092 self.assert_equal(counter_value, expected_value,
1093 "error counter `%s'" % counter)
1094
Klement Sekeraacb9b8e2017-02-14 02:55:31 +01001095 @classmethod
1096 def sleep(cls, timeout, remark=None):
Paul Vinciguerra0f6602c2019-03-10 09:10:54 -07001097
1098 # /* Allow sleep(0) to maintain win32 semantics, and as decreed
1099 # * by Guido, only the main thread can be interrupted.
1100 # */
1101 # https://github.com/python/cpython/blob/6673decfa0fb078f60587f5cb5e98460eea137c2/Modules/timemodule.c#L1892 # noqa
1102 if timeout == 0:
1103 # yield quantum
1104 if hasattr(os, 'sched_yield'):
1105 os.sched_yield()
1106 else:
1107 time.sleep(0)
1108 return
1109
Klement Sekeraacb9b8e2017-02-14 02:55:31 +01001110 if hasattr(cls, 'logger'):
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -08001111 cls.logger.debug("Starting sleep for %es (%s)", timeout, remark)
Klement Sekera3cfa5582017-04-19 07:10:58 +00001112 before = time.time()
Klement Sekeraa57a9702017-02-02 06:58:07 +01001113 time.sleep(timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +00001114 after = time.time()
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -08001115 if hasattr(cls, 'logger') and after - before > 2 * timeout:
Paul Vinciguerra0f6602c2019-03-10 09:10:54 -07001116 cls.logger.error("unexpected self.sleep() result - "
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -08001117 "slept for %es instead of ~%es!",
1118 after - before, timeout)
Klement Sekera3cfa5582017-04-19 07:10:58 +00001119 if hasattr(cls, 'logger'):
1120 cls.logger.debug(
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -08001121 "Finished sleep (%s) - slept %es (wanted %es)",
1122 remark, after - before, timeout)
Klement Sekeraa57a9702017-02-02 06:58:07 +01001123
Paul Vinciguerraeb414432019-02-20 09:01:14 -08001124 def pg_send(self, intf, pkts):
Neale Ranns52fae862018-01-08 04:41:42 -08001125 self.vapi.cli("clear trace")
1126 intf.add_stream(pkts)
1127 self.pg_enable_capture(self.pg_interfaces)
1128 self.pg_start()
Paul Vinciguerraeb414432019-02-20 09:01:14 -08001129
1130 def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
1131 self.pg_send(intf, pkts)
Neale Ranns947ea622018-06-07 23:48:20 -07001132 if not timeout:
1133 timeout = 1
Neale Ranns52fae862018-01-08 04:41:42 -08001134 for i in self.pg_interfaces:
1135 i.get_capture(0, timeout=timeout)
1136 i.assert_nothing_captured(remark=remark)
1137 timeout = 0.1
1138
Neale Rannsd7603d92019-03-28 08:56:10 +00001139 def send_and_expect(self, intf, pkts, output, n_rx=None):
1140 if not n_rx:
1141 n_rx = len(pkts)
Paul Vinciguerraeb414432019-02-20 09:01:14 -08001142 self.pg_send(intf, pkts)
Neale Rannsd7603d92019-03-28 08:56:10 +00001143 rx = output.get_capture(n_rx)
Neale Ranns93cc3ee2018-10-10 07:22:51 -07001144 return rx
1145
Paul Vinciguerraeb414432019-02-20 09:01:14 -08001146 def send_and_expect_only(self, intf, pkts, output, timeout=None):
1147 self.pg_send(intf, pkts)
Paul Vinciguerra8aeb2202019-01-07 16:29:26 -08001148 rx = output.get_capture(len(pkts))
1149 outputs = [output]
Neale Ranns93cc3ee2018-10-10 07:22:51 -07001150 if not timeout:
1151 timeout = 1
1152 for i in self.pg_interfaces:
1153 if i not in outputs:
1154 i.get_capture(0, timeout=timeout)
1155 i.assert_nothing_captured()
1156 timeout = 0.1
1157
Neale Ranns52fae862018-01-08 04:41:42 -08001158 return rx
1159
Paul Vinciguerra087c8112018-12-15 08:03:09 -08001160 def runTest(self):
1161 """ unittest calls runTest when TestCase is instantiated without a
1162 test case. Use case: Writing unittests against VppTestCase"""
1163 pass
1164
Damjan Marionf56b77a2016-10-03 19:44:57 +02001165
juraj.linkes184870a2018-07-16 14:22:01 +02001166def get_testcase_doc_name(test):
1167 return getdoc(test.__class__).splitlines()[0]
1168
1169
Ole Trøan5ba91592018-11-22 10:01:09 +00001170def get_test_description(descriptions, test):
1171 short_description = test.shortDescription()
1172 if descriptions and short_description:
1173 return short_description
1174 else:
1175 return str(test)
1176
1177
juraj.linkes40dd73b2018-09-21 13:55:16 +02001178class TestCaseInfo(object):
1179 def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
1180 self.logger = logger
1181 self.tempdir = tempdir
1182 self.vpp_pid = vpp_pid
1183 self.vpp_bin_path = vpp_bin_path
1184 self.core_crash_test = None
Klement Sekera87134932017-03-07 11:39:27 +01001185
1186
Damjan Marionf56b77a2016-10-03 19:44:57 +02001187class VppTestResult(unittest.TestResult):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001188 """
1189 @property result_string
1190 String variable to store the test case result string.
1191 @property errors
1192 List variable containing 2-tuples of TestCase instances and strings
1193 holding formatted tracebacks. Each tuple represents a test which
1194 raised an unexpected exception.
1195 @property failures
1196 List variable containing 2-tuples of TestCase instances and strings
1197 holding formatted tracebacks. Each tuple represents a test where
1198 a failure was explicitly signalled using the TestCase.assert*()
1199 methods.
1200 """
1201
juraj.linkes40dd73b2018-09-21 13:55:16 +02001202 failed_test_cases_info = set()
1203 core_crash_test_cases_info = set()
1204 current_test_case_info = None
1205
Paul Vinciguerra1ec06ff2019-01-16 11:12:50 -08001206 def __init__(self, stream=None, descriptions=None, verbosity=None,
1207 runner=None):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001208 """
Klement Sekerada505f62017-01-04 12:58:53 +01001209 :param stream File descriptor to store where to report test results.
1210 Set to the standard error stream by default.
1211 :param descriptions Boolean variable to store information if to use
1212 test case descriptions.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001213 :param verbosity Integer variable to store required verbosity level.
1214 """
Paul Vinciguerradd3c5d22019-01-13 16:09:10 -08001215 super(VppTestResult, self).__init__(stream, descriptions, verbosity)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001216 self.stream = stream
1217 self.descriptions = descriptions
1218 self.verbosity = verbosity
1219 self.result_string = None
juraj.linkesabec0122018-11-16 17:28:56 +01001220 self.runner = runner
Damjan Marionf56b77a2016-10-03 19:44:57 +02001221
Damjan Marionf56b77a2016-10-03 19:44:57 +02001222 def addSuccess(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001223 """
1224 Record a test succeeded result
1225
1226 :param test:
1227
1228 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001229 if self.current_test_case_info:
1230 self.current_test_case_info.logger.debug(
1231 "--- addSuccess() %s.%s(%s) called" % (test.__class__.__name__,
1232 test._testMethodName,
1233 test._testMethodDoc))
Damjan Marionf56b77a2016-10-03 19:44:57 +02001234 unittest.TestResult.addSuccess(self, test)
Klement Sekera277b89c2016-10-28 13:20:27 +02001235 self.result_string = colorize("OK", GREEN)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001236
juraj.linkescae64f82018-09-19 15:01:47 +02001237 self.send_result_through_pipe(test, PASS)
1238
Klement Sekeraf62ae122016-10-11 11:47:09 +02001239 def addSkip(self, test, reason):
1240 """
1241 Record a test skipped.
1242
1243 :param test:
1244 :param reason:
1245
1246 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001247 if self.current_test_case_info:
1248 self.current_test_case_info.logger.debug(
1249 "--- addSkip() %s.%s(%s) called, reason is %s" %
1250 (test.__class__.__name__, test._testMethodName,
1251 test._testMethodDoc, reason))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001252 unittest.TestResult.addSkip(self, test, reason)
Klement Sekera277b89c2016-10-28 13:20:27 +02001253 self.result_string = colorize("SKIP", YELLOW)
Klement Sekeraf62ae122016-10-11 11:47:09 +02001254
juraj.linkescae64f82018-09-19 15:01:47 +02001255 self.send_result_through_pipe(test, SKIP)
1256
juraj.linkes40dd73b2018-09-21 13:55:16 +02001257 def symlink_failed(self):
1258 if self.current_test_case_info:
Klement Sekeraf413bef2017-08-15 07:09:02 +02001259 try:
Klement Sekerab8c72a42018-11-08 11:21:39 +01001260 failed_dir = os.getenv('FAILED_DIR')
juraj.linkes40dd73b2018-09-21 13:55:16 +02001261 link_path = os.path.join(
1262 failed_dir,
1263 '%s-FAILED' %
1264 os.path.basename(self.current_test_case_info.tempdir))
1265 if self.current_test_case_info.logger:
1266 self.current_test_case_info.logger.debug(
1267 "creating a link to the failed test")
1268 self.current_test_case_info.logger.debug(
1269 "os.symlink(%s, %s)" %
1270 (self.current_test_case_info.tempdir, link_path))
juraj.linkes184870a2018-07-16 14:22:01 +02001271 if os.path.exists(link_path):
juraj.linkes40dd73b2018-09-21 13:55:16 +02001272 if self.current_test_case_info.logger:
1273 self.current_test_case_info.logger.debug(
1274 'symlink already exists')
juraj.linkes184870a2018-07-16 14:22:01 +02001275 else:
juraj.linkes40dd73b2018-09-21 13:55:16 +02001276 os.symlink(self.current_test_case_info.tempdir, link_path)
juraj.linkes184870a2018-07-16 14:22:01 +02001277
Klement Sekeraf413bef2017-08-15 07:09:02 +02001278 except Exception as e:
juraj.linkes40dd73b2018-09-21 13:55:16 +02001279 if self.current_test_case_info.logger:
1280 self.current_test_case_info.logger.error(e)
Klement Sekeraf413bef2017-08-15 07:09:02 +02001281
juraj.linkescae64f82018-09-19 15:01:47 +02001282 def send_result_through_pipe(self, test, result):
1283 if hasattr(self, 'test_framework_result_pipe'):
1284 pipe = self.test_framework_result_pipe
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001285 if pipe:
juraj.linkescae64f82018-09-19 15:01:47 +02001286 pipe.send((test.id(), result))
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001287
juraj.linkes40dd73b2018-09-21 13:55:16 +02001288 def log_error(self, test, err, fn_name):
1289 if self.current_test_case_info:
1290 if isinstance(test, unittest.suite._ErrorHolder):
1291 test_name = test.description
1292 else:
1293 test_name = '%s.%s(%s)' % (test.__class__.__name__,
1294 test._testMethodName,
1295 test._testMethodDoc)
1296 self.current_test_case_info.logger.debug(
1297 "--- %s() %s called, err is %s" %
1298 (fn_name, test_name, err))
1299 self.current_test_case_info.logger.debug(
1300 "formatted exception is:\n%s" %
1301 "".join(format_exception(*err)))
1302
1303 def add_error(self, test, err, unittest_fn, error_type):
1304 if error_type == FAIL:
1305 self.log_error(test, err, 'addFailure')
1306 error_type_str = colorize("FAIL", RED)
1307 elif error_type == ERROR:
1308 self.log_error(test, err, 'addError')
1309 error_type_str = colorize("ERROR", RED)
1310 else:
1311 raise Exception('Error type %s cannot be used to record an '
1312 'error or a failure' % error_type)
1313
1314 unittest_fn(self, test, err)
1315 if self.current_test_case_info:
1316 self.result_string = "%s [ temp dir used by test case: %s ]" % \
1317 (error_type_str,
1318 self.current_test_case_info.tempdir)
1319 self.symlink_failed()
1320 self.failed_test_cases_info.add(self.current_test_case_info)
1321 if is_core_present(self.current_test_case_info.tempdir):
1322 if not self.current_test_case_info.core_crash_test:
1323 if isinstance(test, unittest.suite._ErrorHolder):
1324 test_name = str(test)
1325 else:
Paul Vinciguerraea2450f2019-03-06 08:23:58 -08001326 test_name = "'{!s}' ({!s})".format(
juraj.linkes40dd73b2018-09-21 13:55:16 +02001327 get_testcase_doc_name(test), test.id())
1328 self.current_test_case_info.core_crash_test = test_name
1329 self.core_crash_test_cases_info.add(
1330 self.current_test_case_info)
1331 else:
1332 self.result_string = '%s [no temp dir]' % error_type_str
1333
1334 self.send_result_through_pipe(test, error_type)
1335
Damjan Marionf56b77a2016-10-03 19:44:57 +02001336 def addFailure(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001337 """
1338 Record a test failed result
1339
1340 :param test:
1341 :param err: error message
1342
1343 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001344 self.add_error(test, err, unittest.TestResult.addFailure, FAIL)
juraj.linkescae64f82018-09-19 15:01:47 +02001345
Damjan Marionf56b77a2016-10-03 19:44:57 +02001346 def addError(self, test, err):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001347 """
1348 Record a test error result
Damjan Marionf56b77a2016-10-03 19:44:57 +02001349
Klement Sekeraf62ae122016-10-11 11:47:09 +02001350 :param test:
1351 :param err: error message
1352
1353 """
juraj.linkes40dd73b2018-09-21 13:55:16 +02001354 self.add_error(test, err, unittest.TestResult.addError, ERROR)
juraj.linkescae64f82018-09-19 15:01:47 +02001355
Damjan Marionf56b77a2016-10-03 19:44:57 +02001356 def getDescription(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001357 """
1358 Get test description
1359
1360 :param test:
1361 :returns: test description
1362
1363 """
Ole Trøan5ba91592018-11-22 10:01:09 +00001364 return get_test_description(self.descriptions, test)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001365
Damjan Marionf56b77a2016-10-03 19:44:57 +02001366 def startTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001367 """
1368 Start a test
1369
1370 :param test:
1371
1372 """
Paul Vinciguerradd3c5d22019-01-13 16:09:10 -08001373
1374 def print_header(test):
1375 if not hasattr(test.__class__, '_header_printed'):
1376 print(double_line_delim)
1377 print(colorize(getdoc(test).splitlines()[0], GREEN))
1378 print(double_line_delim)
1379 test.__class__._header_printed = True
1380
1381 print_header(test)
juraj.linkes40dd73b2018-09-21 13:55:16 +02001382
Damjan Marionf56b77a2016-10-03 19:44:57 +02001383 unittest.TestResult.startTest(self, test)
1384 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001385 self.stream.writeln(
1386 "Starting " + self.getDescription(test) + " ...")
1387 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001388
Damjan Marionf56b77a2016-10-03 19:44:57 +02001389 def stopTest(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001390 """
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001391 Called when the given test has been run
Klement Sekeraf62ae122016-10-11 11:47:09 +02001392
1393 :param test:
1394
1395 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001396 unittest.TestResult.stopTest(self, test)
1397 if self.verbosity > 0:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001398 self.stream.writeln(single_line_delim)
Klement Sekera52e84f32017-01-13 07:25:25 +01001399 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001400 self.result_string))
Klement Sekeraf62ae122016-10-11 11:47:09 +02001401 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001402 else:
Klement Sekera52e84f32017-01-13 07:25:25 +01001403 self.stream.writeln("%-73s%s" % (self.getDescription(test),
Klement Sekerada505f62017-01-04 12:58:53 +01001404 self.result_string))
juraj.linkescae64f82018-09-19 15:01:47 +02001405
1406 self.send_result_through_pipe(test, TEST_RUN)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001407
Damjan Marionf56b77a2016-10-03 19:44:57 +02001408 def printErrors(self):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001409 """
1410 Print errors from running the test case
1411 """
juraj.linkesabec0122018-11-16 17:28:56 +01001412 if len(self.errors) > 0 or len(self.failures) > 0:
1413 self.stream.writeln()
1414 self.printErrorList('ERROR', self.errors)
1415 self.printErrorList('FAIL', self.failures)
1416
1417 # ^^ that is the last output from unittest before summary
1418 if not self.runner.print_summary:
1419 devnull = unittest.runner._WritelnDecorator(open(os.devnull, 'w'))
1420 self.stream = devnull
1421 self.runner.stream = devnull
Damjan Marionf56b77a2016-10-03 19:44:57 +02001422
Damjan Marionf56b77a2016-10-03 19:44:57 +02001423 def printErrorList(self, flavour, errors):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001424 """
1425 Print error list to the output stream together with error type
1426 and test case description.
1427
1428 :param flavour: error type
1429 :param errors: iterable errors
1430
1431 """
Damjan Marionf56b77a2016-10-03 19:44:57 +02001432 for test, err in errors:
Klement Sekeraf62ae122016-10-11 11:47:09 +02001433 self.stream.writeln(double_line_delim)
1434 self.stream.writeln("%s: %s" %
1435 (flavour, self.getDescription(test)))
1436 self.stream.writeln(single_line_delim)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001437 self.stream.writeln("%s" % err)
Damjan Marionf56b77a2016-10-03 19:44:57 +02001438
1439
Damjan Marionf56b77a2016-10-03 19:44:57 +02001440class VppTestRunner(unittest.TextTestRunner):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001441 """
Klement Sekera104543f2017-02-03 07:29:43 +01001442 A basic test runner implementation which prints results to standard error.
Klement Sekeraf62ae122016-10-11 11:47:09 +02001443 """
Paul Vinciguerra6919b0d2018-12-09 15:37:04 -08001444
Klement Sekeraf62ae122016-10-11 11:47:09 +02001445 @property
1446 def resultclass(self):
1447 """Class maintaining the results of the tests"""
1448 return VppTestResult
Damjan Marionf56b77a2016-10-03 19:44:57 +02001449
juraj.linkes184870a2018-07-16 14:22:01 +02001450 def __init__(self, keep_alive_pipe=None, descriptions=True, verbosity=1,
juraj.linkescae64f82018-09-19 15:01:47 +02001451 result_pipe=None, failfast=False, buffer=False,
Paul Vinciguerra98894022019-01-13 21:32:37 -08001452 resultclass=None, print_summary=True, **kwargs):
Klement Sekera7a161da2017-01-17 13:42:48 +01001453 # ignore stream setting here, use hard-coded stdout to be in sync
1454 # with prints from VppTestCase methods ...
1455 super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1456 verbosity, failfast, buffer,
Paul Vinciguerra98894022019-01-13 21:32:37 -08001457 resultclass, **kwargs)
juraj.linkesccfead62018-11-21 13:20:43 +01001458 KeepAliveReporter.pipe = keep_alive_pipe
Klement Sekera104543f2017-02-03 07:29:43 +01001459
juraj.linkesabec0122018-11-16 17:28:56 +01001460 self.orig_stream = self.stream
1461 self.resultclass.test_framework_result_pipe = result_pipe
1462
1463 self.print_summary = print_summary
1464
1465 def _makeResult(self):
1466 return self.resultclass(self.stream,
1467 self.descriptions,
1468 self.verbosity,
1469 self)
juraj.linkes5e2c54d2018-08-30 10:51:45 +02001470
Damjan Marionf56b77a2016-10-03 19:44:57 +02001471 def run(self, test):
Klement Sekeraf62ae122016-10-11 11:47:09 +02001472 """
1473 Run the tests
1474
1475 :param test:
1476
1477 """
Klement Sekera3658adc2017-06-07 08:19:47 +02001478 faulthandler.enable() # emit stack trace to stderr if killed by signal
juraj.linkes184870a2018-07-16 14:22:01 +02001479
1480 result = super(VppTestRunner, self).run(test)
juraj.linkesabec0122018-11-16 17:28:56 +01001481 if not self.print_summary:
1482 self.stream = self.orig_stream
1483 result.stream = self.orig_stream
juraj.linkes184870a2018-07-16 14:22:01 +02001484 return result
Neale Ranns812ed392017-10-16 04:20:13 -07001485
1486
1487class Worker(Thread):
Paul Vinciguerra063366e2019-06-30 15:38:55 -04001488 def __init__(self, args, logger, env=None):
Neale Ranns812ed392017-10-16 04:20:13 -07001489 self.logger = logger
1490 self.args = args
Dave Wallace24564332019-10-21 02:53:14 +00001491 if hasattr(self, 'testcase') and self.testcase.debug_all:
1492 if self.testcase.debug_gdbserver:
1493 self.args = ['/usr/bin/gdbserver', 'localhost:{port}'
1494 .format(port=self.testcase.gdbserver_port)] + args
1495 elif self.testcase.debug_gdb and hasattr(self, 'wait_for_gdb'):
1496 self.args.append(self.wait_for_gdb)
1497 self.app_bin = args[0]
1498 self.app_name = os.path.basename(self.app_bin)
1499 if hasattr(self, 'role'):
1500 self.app_name += ' {role}'.format(role=self.role)
Paul Vinciguerra063366e2019-06-30 15:38:55 -04001501 self.process = None
Neale Ranns812ed392017-10-16 04:20:13 -07001502 self.result = None
Paul Vinciguerra063366e2019-06-30 15:38:55 -04001503 env = {} if env is None else env
Dave Wallace42996c02018-02-26 14:40:13 -05001504 self.env = copy.deepcopy(env)
Neale Ranns812ed392017-10-16 04:20:13 -07001505 super(Worker, self).__init__()
1506
Dave Wallace24564332019-10-21 02:53:14 +00001507 def wait_for_enter(self):
1508 if not hasattr(self, 'testcase'):
1509 return
1510 if self.testcase.debug_all and self.testcase.debug_gdbserver:
1511 print()
1512 print(double_line_delim)
1513 print("Spawned GDB Server for '{app}' with PID: {pid}"
1514 .format(app=self.app_name, pid=self.process.pid))
1515 elif self.testcase.debug_all and self.testcase.debug_gdb:
1516 print()
1517 print(double_line_delim)
1518 print("Spawned '{app}' with PID: {pid}"
1519 .format(app=self.app_name, pid=self.process.pid))
1520 else:
1521 return
1522 print(single_line_delim)
1523 print("You can debug '{app}' using:".format(app=self.app_name))
1524 if self.testcase.debug_gdbserver:
1525 print("sudo gdb " + self.app_bin +
1526 " -ex 'target remote localhost:{port}'"
1527 .format(port=self.testcase.gdbserver_port))
1528 print("Now is the time to attach gdb by running the above "
1529 "command, set up breakpoints etc., then resume from "
1530 "within gdb by issuing the 'continue' command")
1531 self.testcase.gdbserver_port += 1
1532 elif self.testcase.debug_gdb:
1533 print("sudo gdb " + self.app_bin +
1534 " -ex 'attach {pid}'".format(pid=self.process.pid))
1535 print("Now is the time to attach gdb by running the above "
1536 "command and set up breakpoints etc., then resume from"
1537 " within gdb by issuing the 'continue' command")
1538 print(single_line_delim)
1539 input("Press ENTER to continue running the testcase...")
1540
Neale Ranns812ed392017-10-16 04:20:13 -07001541 def run(self):
1542 executable = self.args[0]
Paul Vinciguerra063366e2019-06-30 15:38:55 -04001543 if not os.path.exists(executable) or not os.access(
1544 executable, os.F_OK | os.X_OK):
1545 # Exit code that means some system file did not exist,
1546 # could not be opened, or had some other kind of error.
1547 self.result = os.EX_OSFILE
1548 raise EnvironmentError(
1549 "executable '%s' is not found or executable." % executable)
Dave Wallace24564332019-10-21 02:53:14 +00001550 self.logger.debug("Running executable: '{app}'"
1551 .format(app=' '.join(self.args)))
Neale Ranns812ed392017-10-16 04:20:13 -07001552 env = os.environ.copy()
Dave Wallacecfcf2f42018-02-16 18:31:56 -05001553 env.update(self.env)
Neale Ranns812ed392017-10-16 04:20:13 -07001554 env["CK_LOG_FILE_NAME"] = "-"
1555 self.process = subprocess.Popen(
1556 self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1557 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Dave Wallace24564332019-10-21 02:53:14 +00001558 self.wait_for_enter()
Neale Ranns812ed392017-10-16 04:20:13 -07001559 out, err = self.process.communicate()
Dave Wallace24564332019-10-21 02:53:14 +00001560 self.logger.debug("Finished running `{app}'".format(app=self.app_name))
Neale Ranns812ed392017-10-16 04:20:13 -07001561 self.logger.info("Return code is `%s'" % self.process.returncode)
1562 self.logger.info(single_line_delim)
Dave Wallace24564332019-10-21 02:53:14 +00001563 self.logger.info("Executable `{app}' wrote to stdout:"
1564 .format(app=self.app_name))
Neale Ranns812ed392017-10-16 04:20:13 -07001565 self.logger.info(single_line_delim)
1566 self.logger.info(out)
1567 self.logger.info(single_line_delim)
Dave Wallace24564332019-10-21 02:53:14 +00001568 self.logger.info("Executable `{app}' wrote to stderr:"
1569 .format(app=self.app_name))
Neale Ranns812ed392017-10-16 04:20:13 -07001570 self.logger.info(single_line_delim)
Klement Sekerade0203e2018-02-22 19:21:27 +01001571 self.logger.info(err)
Neale Ranns812ed392017-10-16 04:20:13 -07001572 self.logger.info(single_line_delim)
1573 self.result = self.process.returncode
Paul Vinciguerradd3c5d22019-01-13 16:09:10 -08001574
Klement Sekera6aa58b72019-05-16 14:34:55 +02001575
Paul Vinciguerradd3c5d22019-01-13 16:09:10 -08001576if __name__ == '__main__':
1577 pass