Python Tests: Make VppTestCase unit testable.
[vpp.git] / test / framework.py
1 #!/usr/bin/env python
2
3 from __future__ import print_function
4 import gc
5 import sys
6 import os
7 import select
8 import unittest
9 import tempfile
10 import time
11 import faulthandler
12 import random
13 import copy
14 import psutil
15 import platform
16 from collections import deque
17 from threading import Thread, Event
18 from inspect import getdoc, isclass
19 from traceback import format_exception
20 from logging import FileHandler, DEBUG, Formatter
21 from scapy.packet import Raw
22 from hook import StepHook, PollHook, VppDiedError
23 from vpp_config import VppTestCaseVppConfig
24 from vpp_interface import VppInterface
25 from vpp_sub_interface import VppSubInterface
26 from vpp_pg_interface import VppPGInterface
27 from vpp_lo_interface import VppLoInterface
28 from vpp_papi_provider import VppPapiProvider
29 from vpp_papi.vpp_stats import VPPStats
30 from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
31     get_logger, colorize
32 from vpp_object import VppObjectRegistry
33 from util import ppp, is_core_present
34 from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
35 from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
36 from scapy.layers.inet6 import ICMPv6EchoReply
37
38 if os.name == 'posix' and sys.version_info[0] < 3:
39     # using subprocess32 is recommended by python official documentation
40     # @ https://docs.python.org/2/library/subprocess.html
41     import subprocess32 as subprocess
42 else:
43     import subprocess
44
45 #  Python2/3 compatible
46 try:
47     input = raw_input
48 except NameError:
49     pass
50
51 PASS = 0
52 FAIL = 1
53 ERROR = 2
54 SKIP = 3
55 TEST_RUN = 4
56
57 debug_framework = False
58 if os.getenv('TEST_DEBUG', "0") == "1":
59     debug_framework = True
60     import debug_internal
61
62 """
63   Test framework module.
64
65   The module provides a set of tools for constructing and running tests and
66   representing the results.
67 """
68
69
70 class _PacketInfo(object):
71     """Private class to create packet info object.
72
73     Help process information about the next packet.
74     Set variables to default values.
75     """
76     #: Store the index of the packet.
77     index = -1
78     #: Store the index of the source packet generator interface of the packet.
79     src = -1
80     #: Store the index of the destination packet generator interface
81     #: of the packet.
82     dst = -1
83     #: Store expected ip version
84     ip = -1
85     #: Store expected upper protocol
86     proto = -1
87     #: Store the copy of the former packet.
88     data = None
89
90     def __eq__(self, other):
91         index = self.index == other.index
92         src = self.src == other.src
93         dst = self.dst == other.dst
94         data = self.data == other.data
95         return index and src and dst and data
96
97
98 def pump_output(testclass):
99     """ pump output from vpp stdout/stderr to proper queues """
100     stdout_fragment = ""
101     stderr_fragment = ""
102     while not testclass.pump_thread_stop_flag.is_set():
103         readable = select.select([testclass.vpp.stdout.fileno(),
104                                   testclass.vpp.stderr.fileno(),
105                                   testclass.pump_thread_wakeup_pipe[0]],
106                                  [], [])[0]
107         if testclass.vpp.stdout.fileno() in readable:
108             read = os.read(testclass.vpp.stdout.fileno(), 102400)
109             if len(read) > 0:
110                 split = read.splitlines(True)
111                 if len(stdout_fragment) > 0:
112                     split[0] = "%s%s" % (stdout_fragment, split[0])
113                 if len(split) > 0 and split[-1].endswith("\n"):
114                     limit = None
115                 else:
116                     limit = -1
117                     stdout_fragment = split[-1]
118                 testclass.vpp_stdout_deque.extend(split[:limit])
119                 if not testclass.cache_vpp_output:
120                     for line in split[:limit]:
121                         testclass.logger.debug(
122                             "VPP STDOUT: %s" % line.rstrip("\n"))
123         if testclass.vpp.stderr.fileno() in readable:
124             read = os.read(testclass.vpp.stderr.fileno(), 102400)
125             if len(read) > 0:
126                 split = read.splitlines(True)
127                 if len(stderr_fragment) > 0:
128                     split[0] = "%s%s" % (stderr_fragment, split[0])
129                 if len(split) > 0 and split[-1].endswith(b"\n"):
130                     limit = None
131                 else:
132                     limit = -1
133                     stderr_fragment = split[-1]
134                 testclass.vpp_stderr_deque.extend(split[:limit])
135                 if not testclass.cache_vpp_output:
136                     for line in split[:limit]:
137                         testclass.logger.debug(
138                             "VPP STDERR: %s" % line.rstrip("\n"))
139                         # ignoring the dummy pipe here intentionally - the
140                         # flag will take care of properly terminating the loop
141
142
143 def _is_skip_aarch64_set():
144     return os.getenv('SKIP_AARCH64', 'n').lower() in ('yes', 'y', '1')
145
146 is_skip_aarch64_set = _is_skip_aarch64_set()
147
148
149 def _is_platform_aarch64():
150     return platform.machine() == 'aarch64'
151
152 is_platform_aarch64 = _is_platform_aarch64()
153
154
155 def _running_extended_tests():
156     s = os.getenv("EXTENDED_TESTS", "n")
157     return True if s.lower() in ("y", "yes", "1") else False
158
159 running_extended_tests = _running_extended_tests()
160
161
162 def _running_on_centos():
163     os_id = os.getenv("OS_ID", "")
164     return True if "centos" in os_id.lower() else False
165
166 running_on_centos = _running_on_centos
167
168
169 class KeepAliveReporter(object):
170     """
171     Singleton object which reports test start to parent process
172     """
173     _shared_state = {}
174
175     def __init__(self):
176         self.__dict__ = self._shared_state
177         self._pipe = None
178
179     @property
180     def pipe(self):
181         return self._pipe
182
183     @pipe.setter
184     def pipe(self, pipe):
185         if self._pipe is not None:
186             raise Exception("Internal error - pipe should only be set once.")
187         self._pipe = pipe
188
189     def send_keep_alive(self, test, desc=None):
190         """
191         Write current test tmpdir & desc to keep-alive pipe to signal liveness
192         """
193         if self.pipe is None:
194             # if not running forked..
195             return
196
197         if isclass(test):
198             desc = '%s (%s)' % (desc, unittest.util.strclass(test))
199         else:
200             desc = test.id()
201
202         self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
203
204
205 class VppTestCase(unittest.TestCase):
206     """This subclass is a base class for VPP test cases that are implemented as
207     classes. It provides methods to create and run test case.
208     """
209
210     CLI_LISTEN_DEFAULT = 'localhost:5002'
211     config = VppTestCaseVppConfig()
212
213     @property
214     def packet_infos(self):
215         """List of packet infos"""
216         return self._packet_infos
217
218     @classmethod
219     def get_packet_count_for_if_idx(cls, dst_if_index):
220         """Get the number of packet info for specified destination if index"""
221         if dst_if_index in cls._packet_count_for_dst_if_idx:
222             return cls._packet_count_for_dst_if_idx[dst_if_index]
223         else:
224             return 0
225
226     @classmethod
227     def instance(cls):
228         """Return the instance of this testcase"""
229         return cls.test_instance
230
231     @classmethod
232     def set_debug_flags(cls, d):
233         cls.debug_core = False
234         cls.debug_gdb = False
235         cls.debug_gdbserver = False
236         if d is None:
237             return
238         dl = d.lower()
239         if dl == "core":
240             cls.debug_core = True
241         elif dl == "gdb":
242             cls.debug_gdb = True
243         elif dl == "gdbserver":
244             cls.debug_gdbserver = True
245         else:
246             raise Exception("Unrecognized DEBUG option: '%s'" % d)
247
248     @staticmethod
249     def get_least_used_cpu():
250         cpu_usage_list = [set(range(psutil.cpu_count()))]
251         vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name'])
252                          if 'vpp_main' == p.info['name']]
253         for vpp_process in vpp_processes:
254             for cpu_usage_set in cpu_usage_list:
255                 try:
256                     cpu_num = vpp_process.cpu_num()
257                     if cpu_num in cpu_usage_set:
258                         cpu_usage_set_index = cpu_usage_list.index(
259                             cpu_usage_set)
260                         if cpu_usage_set_index == len(cpu_usage_list) - 1:
261                             cpu_usage_list.append({cpu_num})
262                         else:
263                             cpu_usage_list[cpu_usage_set_index + 1].add(
264                                 cpu_num)
265                         cpu_usage_set.remove(cpu_num)
266                         break
267                 except psutil.NoSuchProcess:
268                     pass
269
270         for cpu_usage_set in cpu_usage_list:
271             if len(cpu_usage_set) > 0:
272                 min_usage_set = cpu_usage_set
273                 break
274
275         return random.choice(tuple(min_usage_set))
276
277     @classmethod
278     def print_header(cls):
279         if not hasattr(cls, '_header_printed'):
280             print(double_line_delim)
281             print(colorize(getdoc(cls).splitlines()[0], GREEN))
282             print(double_line_delim)
283             cls._header_printed = True
284
285     @classmethod
286     def setUpConstants(cls):
287         """ Set-up the test case class based on environment variables """
288         s = os.getenv("STEP", "n")
289         cls.step = True if s.lower() in ("y", "yes", "1") else False
290         d = os.getenv("DEBUG", None)
291         c = os.getenv("CACHE_OUTPUT", "1")
292         cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
293         cls.set_debug_flags(d)
294         cls.vpp_bin = os.getenv('VPP_BIN', "vpp")
295         cls.plugin_path = os.getenv('VPP_PLUGIN_PATH')
296         cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
297         plugin_path = None
298         if cls.plugin_path is not None:
299             if cls.extern_plugin_path is not None:
300                 plugin_path = "%s:%s" % (
301                     cls.plugin_path, cls.extern_plugin_path)
302             else:
303                 plugin_path = cls.plugin_path
304         elif cls.extern_plugin_path is not None:
305             plugin_path = cls.extern_plugin_path
306
307         if cls.step or cls.debug_gdb or cls.debug_gdbserver:
308             cls.config.add('unix', 'cli-listen', cls.CLI_LISTEN_DEFAULT)
309
310         coredump_size = None
311         size = os.getenv("COREDUMP_SIZE")
312         cls.config.add('unix', 'coredump-size',
313                        size if size is not None else 'unlimited')
314
315         cls.config.add('unix', 'runtime-dir', cls.tempdir)
316         cls.config.add('api-segment', 'prefix', cls.shm_prefix)
317         cls.config.add('cpu', 'main-core', str(cls.get_least_used_cpu()))
318         cls.config.add('statseg', 'socket-name', cls.stats_sock)
319
320         if plugin_path is not None:
321             cls.config.add('plugins', 'path', plugin_path)
322         cls.config.add_plugin('dpdk_plugin.so', 'disable')
323         cls.config.add_plugin('unittest_plugin.so', 'enable')
324
325         cls.vpp_cmdline = [cls.vpp_bin] + cls.config.shlex()
326         cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline)
327         cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline))
328
329     @classmethod
330     def wait_for_enter(cls):
331         if cls.debug_gdbserver:
332             print(double_line_delim)
333             print("Spawned GDB server with PID: %d" % cls.vpp.pid)
334         elif cls.debug_gdb:
335             print(double_line_delim)
336             print("Spawned VPP with PID: %d" % cls.vpp.pid)
337         else:
338             cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
339             return
340         print(single_line_delim)
341         print("You can debug the VPP using e.g.:")
342         if cls.debug_gdbserver:
343             print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
344             print("Now is the time to attach a gdb by running the above "
345                   "command, set up breakpoints etc. and then resume VPP from "
346                   "within gdb by issuing the 'continue' command")
347         elif cls.debug_gdb:
348             print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
349             print("Now is the time to attach a gdb by running the above "
350                   "command and set up breakpoints etc.")
351         print(single_line_delim)
352         input("Press ENTER to continue running the testcase...")
353
354     @classmethod
355     def run_vpp(cls):
356         cmdline = cls.vpp_cmdline
357
358         if cls.debug_gdbserver:
359             gdbserver = '/usr/bin/gdbserver'
360             if not os.path.isfile(gdbserver) or \
361                     not os.access(gdbserver, os.X_OK):
362                 raise Exception("gdbserver binary '%s' does not exist or is "
363                                 "not executable" % gdbserver)
364
365             cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
366             cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
367
368         try:
369             cls.vpp = subprocess.Popen(cmdline,
370                                        stdout=subprocess.PIPE,
371                                        stderr=subprocess.PIPE,
372                                        bufsize=1)
373         except subprocess.CalledProcessError as e:
374             cls.logger.critical("Couldn't start vpp: %s" % e)
375             raise
376
377         cls.wait_for_enter()
378
379     @classmethod
380     def wait_for_stats_socket(cls):
381         deadline = time.time() + 3
382         ok = False
383         while time.time() < deadline or \
384                 cls.debug_gdb or cls.debug_gdbserver:
385             if os.path.exists(cls.stats_sock):
386                 ok = True
387                 break
388             time.sleep(0.8)
389         if not ok:
390             cls.logger.critical("Couldn't stat : {}".format(cls.stats_sock))
391
392     @classmethod
393     def setUpClass(cls):
394         """
395         Perform class setup before running the testcase
396         Remove shared memory files, start vpp and connect the vpp-api
397         """
398         gc.collect()  # run garbage collection first
399         random.seed()
400         cls.print_header()
401         cls.logger = get_logger(cls.__name__)
402         if hasattr(cls, 'parallel_handler'):
403             cls.logger.addHandler(cls.parallel_handler)
404             cls.logger.propagate = False
405         cls.tempdir = tempfile.mkdtemp(
406             prefix='vpp-unittest-%s-' % cls.__name__)
407         cls.stats_sock = "%s/stats.sock" % cls.tempdir
408         cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
409         cls.file_handler.setFormatter(
410             Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
411                       datefmt="%H:%M:%S"))
412         cls.file_handler.setLevel(DEBUG)
413         cls.logger.addHandler(cls.file_handler)
414         cls.shm_prefix = os.path.basename(cls.tempdir)
415         os.chdir(cls.tempdir)
416         cls.logger.info("Temporary dir is %s, shm prefix is %s",
417                         cls.tempdir, cls.shm_prefix)
418         cls.setUpConstants()
419         cls.reset_packet_infos()
420         cls._captures = []
421         cls._zombie_captures = []
422         cls.verbose = 0
423         cls.vpp_dead = False
424         cls.registry = VppObjectRegistry()
425         cls.vpp_startup_failed = False
426         cls.reporter = KeepAliveReporter()
427         # need to catch exceptions here because if we raise, then the cleanup
428         # doesn't get called and we might end with a zombie vpp
429         try:
430             cls.run_vpp()
431             cls.reporter.send_keep_alive(cls, 'setUpClass')
432             VppTestResult.current_test_case_info = TestCaseInfo(
433                 cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin)
434             cls.vpp_stdout_deque = deque()
435             cls.vpp_stderr_deque = deque()
436             cls.pump_thread_stop_flag = Event()
437             cls.pump_thread_wakeup_pipe = os.pipe()
438             cls.pump_thread = Thread(target=pump_output, args=(cls,))
439             cls.pump_thread.daemon = True
440             cls.pump_thread.start()
441             if cls.debug_gdb or cls.debug_gdbserver:
442                 read_timeout = 0
443             else:
444                 read_timeout = 5
445             cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls,
446                                        read_timeout)
447             if cls.step:
448                 hook = StepHook(cls)
449             else:
450                 hook = PollHook(cls)
451             cls.vapi.register_hook(hook)
452             cls.wait_for_stats_socket()
453             cls.statistics = VPPStats(socketname=cls.stats_sock)
454             try:
455                 hook.poll_vpp()
456             except VppDiedError:
457                 cls.vpp_startup_failed = True
458                 cls.logger.critical(
459                     "VPP died shortly after startup, check the"
460                     " output to standard error for possible cause")
461                 raise
462             try:
463                 cls.vapi.connect()
464             except Exception:
465                 try:
466                     cls.vapi.disconnect()
467                 except Exception:
468                     pass
469                 if cls.debug_gdbserver:
470                     print(colorize("You're running VPP inside gdbserver but "
471                                    "VPP-API connection failed, did you forget "
472                                    "to 'continue' VPP from within gdb?", RED))
473                 raise
474         except Exception:
475             try:
476                 cls.quit()
477             except Exception:
478                 pass
479             raise
480
481     @classmethod
482     def quit(cls):
483         """
484         Disconnect vpp-api, kill vpp and cleanup shared memory files
485         """
486         if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
487             cls.vpp.poll()
488             if cls.vpp.returncode is None:
489                 print(double_line_delim)
490                 print("VPP or GDB server is still running")
491                 print(single_line_delim)
492                 input("When done debugging, press ENTER to kill the "
493                       "process and finish running the testcase...")
494
495         # first signal that we want to stop the pump thread, then wake it up
496         if hasattr(cls, 'pump_thread_stop_flag'):
497             cls.pump_thread_stop_flag.set()
498         if hasattr(cls, 'pump_thread_wakeup_pipe'):
499             os.write(cls.pump_thread_wakeup_pipe[1], b'ding dong wake up')
500         if hasattr(cls, 'pump_thread'):
501             cls.logger.debug("Waiting for pump thread to stop")
502             cls.pump_thread.join()
503         if hasattr(cls, 'vpp_stderr_reader_thread'):
504             cls.logger.debug("Waiting for stdderr pump to stop")
505             cls.vpp_stderr_reader_thread.join()
506
507         if hasattr(cls, 'vpp'):
508             if hasattr(cls, 'vapi'):
509                 cls.vapi.disconnect()
510                 del cls.vapi
511             cls.vpp.poll()
512             if cls.vpp.returncode is None:
513                 cls.logger.debug("Sending TERM to vpp")
514                 cls.vpp.kill()
515                 cls.logger.debug("Waiting for vpp to die")
516                 cls.vpp.communicate()
517             del cls.vpp
518
519         if cls.vpp_startup_failed:
520             stdout_log = cls.logger.info
521             stderr_log = cls.logger.critical
522         else:
523             stdout_log = cls.logger.info
524             stderr_log = cls.logger.info
525
526         if hasattr(cls, 'vpp_stdout_deque'):
527             stdout_log(single_line_delim)
528             stdout_log('VPP output to stdout while running %s:', cls.__name__)
529             stdout_log(single_line_delim)
530             vpp_output = "".join(cls.vpp_stdout_deque)
531             with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
532                 f.write(vpp_output)
533             stdout_log('\n%s', vpp_output)
534             stdout_log(single_line_delim)
535
536         if hasattr(cls, 'vpp_stderr_deque'):
537             stderr_log(single_line_delim)
538             stderr_log('VPP output to stderr while running %s:', cls.__name__)
539             stderr_log(single_line_delim)
540             vpp_output = "".join(str(cls.vpp_stderr_deque))
541             with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
542                 f.write(vpp_output)
543             stderr_log('\n%s', vpp_output)
544             stderr_log(single_line_delim)
545
546     @classmethod
547     def tearDownClass(cls):
548         """ Perform final cleanup after running all tests in this test-case """
549         cls.reporter.send_keep_alive(cls, 'tearDownClass')
550         cls.quit()
551         cls.file_handler.close()
552         cls.reset_packet_infos()
553         if debug_framework:
554             debug_internal.on_tear_down_class(cls)
555
556     def tearDown(self):
557         """ Show various debug prints after each test """
558         self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
559                           (self.__class__.__name__, self._testMethodName,
560                            self._testMethodDoc))
561         if not self.vpp_dead:
562             self.logger.debug(self.vapi.cli("show trace"))
563             self.logger.info(self.vapi.ppcli("show interface"))
564             self.logger.info(self.vapi.ppcli("show hardware"))
565             self.logger.info(self.statistics.set_errors_str())
566             self.logger.info(self.vapi.ppcli("show run"))
567             self.logger.info(self.vapi.ppcli("show log"))
568             self.registry.remove_vpp_config(self.logger)
569             # Save/Dump VPP api trace log
570             api_trace = "vpp_api_trace.%s.log" % self._testMethodName
571             tmp_api_trace = "/tmp/%s" % api_trace
572             vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
573             self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
574             self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
575                                                     vpp_api_trace_log))
576             os.rename(tmp_api_trace, vpp_api_trace_log)
577             self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
578                                              vpp_api_trace_log))
579         else:
580             self.registry.unregister_all(self.logger)
581
582     def setUp(self):
583         """ Clear trace before running each test"""
584         self.reporter.send_keep_alive(self)
585         self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
586                           (self.__class__.__name__, self._testMethodName,
587                            self._testMethodDoc))
588         if self.vpp_dead:
589             raise Exception("VPP is dead when setting up the test")
590         self.sleep(.1, "during setUp")
591         self.vpp_stdout_deque.append(
592             "--- test setUp() for %s.%s(%s) starts here ---\n" %
593             (self.__class__.__name__, self._testMethodName,
594              self._testMethodDoc))
595         self.vpp_stderr_deque.append(
596             "--- test setUp() for %s.%s(%s) starts here ---\n" %
597             (self.__class__.__name__, self._testMethodName,
598              self._testMethodDoc))
599         self.vapi.cli("clear trace")
600         # store the test instance inside the test class - so that objects
601         # holding the class can access instance methods (like assertEqual)
602         type(self).test_instance = self
603
604     @classmethod
605     def pg_enable_capture(cls, interfaces=None):
606         """
607         Enable capture on packet-generator interfaces
608
609         :param interfaces: iterable interface indexes (if None,
610                            use self.pg_interfaces)
611
612         """
613         if interfaces is None:
614             interfaces = cls.pg_interfaces
615         for i in interfaces:
616             i.enable_capture()
617
618     @classmethod
619     def register_capture(cls, cap_name):
620         """ Register a capture in the testclass """
621         # add to the list of captures with current timestamp
622         cls._captures.append((time.time(), cap_name))
623         # filter out from zombies
624         cls._zombie_captures = [(stamp, name)
625                                 for (stamp, name) in cls._zombie_captures
626                                 if name != cap_name]
627
628     @classmethod
629     def pg_start(cls):
630         """ Remove any zombie captures and enable the packet generator """
631         # how long before capture is allowed to be deleted - otherwise vpp
632         # crashes - 100ms seems enough (this shouldn't be needed at all)
633         capture_ttl = 0.1
634         now = time.time()
635         for stamp, cap_name in cls._zombie_captures:
636             wait = stamp + capture_ttl - now
637             if wait > 0:
638                 cls.sleep(wait, "before deleting capture %s" % cap_name)
639                 now = time.time()
640             cls.logger.debug("Removing zombie capture %s" % cap_name)
641             cls.vapi.cli('packet-generator delete %s' % cap_name)
642
643         cls.vapi.cli("trace add pg-input 50")  # 50 is maximum
644         cls.vapi.cli('packet-generator enable')
645         cls._zombie_captures = cls._captures
646         cls._captures = []
647
648     @classmethod
649     def create_pg_interfaces(cls, interfaces):
650         """
651         Create packet-generator interfaces.
652
653         :param interfaces: iterable indexes of the interfaces.
654         :returns: List of created interfaces.
655
656         """
657         result = []
658         for i in interfaces:
659             intf = VppPGInterface(cls, i)
660             setattr(cls, intf.name, intf)
661             result.append(intf)
662         cls.pg_interfaces = result
663         return result
664
665     @classmethod
666     def create_loopback_interfaces(cls, count):
667         """
668         Create loopback interfaces.
669
670         :param count: number of interfaces created.
671         :returns: List of created interfaces.
672         """
673         result = [VppLoInterface(cls) for i in range(count)]
674         for intf in result:
675             setattr(cls, intf.name, intf)
676         cls.lo_interfaces = result
677         return result
678
679     @staticmethod
680     def extend_packet(packet, size, padding=' '):
681         """
682         Extend packet to given size by padding with spaces or custom padding
683         NOTE: Currently works only when Raw layer is present.
684
685         :param packet: packet
686         :param size: target size
687         :param padding: padding used to extend the payload
688
689         """
690         packet_len = len(packet) + 4
691         extend = size - packet_len
692         if extend > 0:
693             num = (extend / len(padding)) + 1
694             packet[Raw].load += (padding * num)[:extend]
695
696     @classmethod
697     def reset_packet_infos(cls):
698         """ Reset the list of packet info objects and packet counts to zero """
699         cls._packet_infos = {}
700         cls._packet_count_for_dst_if_idx = {}
701
702     @classmethod
703     def create_packet_info(cls, src_if, dst_if):
704         """
705         Create packet info object containing the source and destination indexes
706         and add it to the testcase's packet info list
707
708         :param VppInterface src_if: source interface
709         :param VppInterface dst_if: destination interface
710
711         :returns: _PacketInfo object
712
713         """
714         info = _PacketInfo()
715         info.index = len(cls._packet_infos)
716         info.src = src_if.sw_if_index
717         info.dst = dst_if.sw_if_index
718         if isinstance(dst_if, VppSubInterface):
719             dst_idx = dst_if.parent.sw_if_index
720         else:
721             dst_idx = dst_if.sw_if_index
722         if dst_idx in cls._packet_count_for_dst_if_idx:
723             cls._packet_count_for_dst_if_idx[dst_idx] += 1
724         else:
725             cls._packet_count_for_dst_if_idx[dst_idx] = 1
726         cls._packet_infos[info.index] = info
727         return info
728
729     @staticmethod
730     def info_to_payload(info):
731         """
732         Convert _PacketInfo object to packet payload
733
734         :param info: _PacketInfo object
735
736         :returns: string containing serialized data from packet info
737         """
738         return "%d %d %d %d %d" % (info.index, info.src, info.dst,
739                                    info.ip, info.proto)
740
741     @staticmethod
742     def payload_to_info(payload):
743         """
744         Convert packet payload to _PacketInfo object
745
746         :param payload: packet payload
747
748         :returns: _PacketInfo object containing de-serialized data from payload
749
750         """
751         numbers = payload.split()
752         info = _PacketInfo()
753         info.index = int(numbers[0])
754         info.src = int(numbers[1])
755         info.dst = int(numbers[2])
756         info.ip = int(numbers[3])
757         info.proto = int(numbers[4])
758         return info
759
760     def get_next_packet_info(self, info):
761         """
762         Iterate over the packet info list stored in the testcase
763         Start iteration with first element if info is None
764         Continue based on index in info if info is specified
765
766         :param info: info or None
767         :returns: next info in list or None if no more infos
768         """
769         if info is None:
770             next_index = 0
771         else:
772             next_index = info.index + 1
773         if next_index == len(self._packet_infos):
774             return None
775         else:
776             return self._packet_infos[next_index]
777
778     def get_next_packet_info_for_interface(self, src_index, info):
779         """
780         Search the packet info list for the next packet info with same source
781         interface index
782
783         :param src_index: source interface index to search for
784         :param info: packet info - where to start the search
785         :returns: packet info or None
786
787         """
788         while True:
789             info = self.get_next_packet_info(info)
790             if info is None:
791                 return None
792             if info.src == src_index:
793                 return info
794
795     def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
796         """
797         Search the packet info list for the next packet info with same source
798         and destination interface indexes
799
800         :param src_index: source interface index to search for
801         :param dst_index: destination interface index to search for
802         :param info: packet info - where to start the search
803         :returns: packet info or None
804
805         """
806         while True:
807             info = self.get_next_packet_info_for_interface(src_index, info)
808             if info is None:
809                 return None
810             if info.dst == dst_index:
811                 return info
812
813     def assert_equal(self, real_value, expected_value, name_or_class=None):
814         if name_or_class is None:
815             self.assertEqual(real_value, expected_value)
816             return
817         try:
818             msg = "Invalid %s: %d('%s') does not match expected value %d('%s')"
819             msg = msg % (getdoc(name_or_class).strip(),
820                          real_value, str(name_or_class(real_value)),
821                          expected_value, str(name_or_class(expected_value)))
822         except Exception:
823             msg = "Invalid %s: %s does not match expected value %s" % (
824                 name_or_class, real_value, expected_value)
825
826         self.assertEqual(real_value, expected_value, msg)
827
828     def assert_in_range(self,
829                         real_value,
830                         expected_min,
831                         expected_max,
832                         name=None):
833         if name is None:
834             msg = None
835         else:
836             msg = "Invalid %s: %s out of range <%s,%s>" % (
837                 name, real_value, expected_min, expected_max)
838         self.assertTrue(expected_min <= real_value <= expected_max, msg)
839
840     def assert_packet_checksums_valid(self, packet,
841                                       ignore_zero_udp_checksums=True):
842         received = packet.__class__(str(packet))
843         self.logger.debug(
844             ppp("Verifying packet checksums for packet:", received))
845         udp_layers = ['UDP', 'UDPerror']
846         checksum_fields = ['cksum', 'chksum']
847         checksums = []
848         counter = 0
849         temp = received.__class__(str(received))
850         while True:
851             layer = temp.getlayer(counter)
852             if layer:
853                 for cf in checksum_fields:
854                     if hasattr(layer, cf):
855                         if ignore_zero_udp_checksums and \
856                                 0 == getattr(layer, cf) and \
857                                 layer.name in udp_layers:
858                             continue
859                         delattr(layer, cf)
860                         checksums.append((counter, cf))
861             else:
862                 break
863             counter = counter + 1
864         if 0 == len(checksums):
865             return
866         temp = temp.__class__(str(temp))
867         for layer, cf in checksums:
868             calc_sum = getattr(temp[layer], cf)
869             self.assert_equal(
870                 getattr(received[layer], cf), calc_sum,
871                 "packet checksum on layer #%d: %s" % (layer, temp[layer].name))
872             self.logger.debug(
873                 "Checksum field `%s` on `%s` layer has correct value `%s`" %
874                 (cf, temp[layer].name, calc_sum))
875
876     def assert_checksum_valid(self, received_packet, layer,
877                               field_name='chksum',
878                               ignore_zero_checksum=False):
879         """ Check checksum of received packet on given layer """
880         received_packet_checksum = getattr(received_packet[layer], field_name)
881         if ignore_zero_checksum and 0 == received_packet_checksum:
882             return
883         recalculated = received_packet.__class__(str(received_packet))
884         delattr(recalculated[layer], field_name)
885         recalculated = recalculated.__class__(str(recalculated))
886         self.assert_equal(received_packet_checksum,
887                           getattr(recalculated[layer], field_name),
888                           "packet checksum on layer: %s" % layer)
889
890     def assert_ip_checksum_valid(self, received_packet,
891                                  ignore_zero_checksum=False):
892         self.assert_checksum_valid(received_packet, 'IP',
893                                    ignore_zero_checksum=ignore_zero_checksum)
894
895     def assert_tcp_checksum_valid(self, received_packet,
896                                   ignore_zero_checksum=False):
897         self.assert_checksum_valid(received_packet, 'TCP',
898                                    ignore_zero_checksum=ignore_zero_checksum)
899
900     def assert_udp_checksum_valid(self, received_packet,
901                                   ignore_zero_checksum=True):
902         self.assert_checksum_valid(received_packet, 'UDP',
903                                    ignore_zero_checksum=ignore_zero_checksum)
904
905     def assert_embedded_icmp_checksum_valid(self, received_packet):
906         if received_packet.haslayer(IPerror):
907             self.assert_checksum_valid(received_packet, 'IPerror')
908         if received_packet.haslayer(TCPerror):
909             self.assert_checksum_valid(received_packet, 'TCPerror')
910         if received_packet.haslayer(UDPerror):
911             self.assert_checksum_valid(received_packet, 'UDPerror',
912                                        ignore_zero_checksum=True)
913         if received_packet.haslayer(ICMPerror):
914             self.assert_checksum_valid(received_packet, 'ICMPerror')
915
916     def assert_icmp_checksum_valid(self, received_packet):
917         self.assert_checksum_valid(received_packet, 'ICMP')
918         self.assert_embedded_icmp_checksum_valid(received_packet)
919
920     def assert_icmpv6_checksum_valid(self, pkt):
921         if pkt.haslayer(ICMPv6DestUnreach):
922             self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum')
923             self.assert_embedded_icmp_checksum_valid(pkt)
924         if pkt.haslayer(ICMPv6EchoRequest):
925             self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum')
926         if pkt.haslayer(ICMPv6EchoReply):
927             self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum')
928
929     def assert_packet_counter_equal(self, counter, expected_value):
930         if counter.startswith("/"):
931             counter_value = self.statistics.get_counter(counter)
932             self.assert_equal(counter_value, expected_value,
933                               "packet counter `%s'" % counter)
934         else:
935             counters = self.vapi.cli("sh errors").split('\n')
936             counter_value = -1
937             for i in range(1, len(counters) - 1):
938                 results = counters[i].split()
939                 if results[1] == counter:
940                     counter_value = int(results[0])
941                     break
942
943     @classmethod
944     def sleep(cls, timeout, remark=None):
945         if hasattr(cls, 'logger'):
946             cls.logger.debug("Starting sleep for %es (%s)", timeout, remark)
947         before = time.time()
948         time.sleep(timeout)
949         after = time.time()
950         if hasattr(cls, 'logger') and after - before > 2 * timeout:
951             cls.logger.error("unexpected time.sleep() result - "
952                              "slept for %es instead of ~%es!",
953                              after - before, timeout)
954         if hasattr(cls, 'logger'):
955             cls.logger.debug(
956                 "Finished sleep (%s) - slept %es (wanted %es)",
957                 remark, after - before, timeout)
958
959     def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
960         self.vapi.cli("clear trace")
961         intf.add_stream(pkts)
962         self.pg_enable_capture(self.pg_interfaces)
963         self.pg_start()
964         if not timeout:
965             timeout = 1
966         for i in self.pg_interfaces:
967             i.get_capture(0, timeout=timeout)
968             i.assert_nothing_captured(remark=remark)
969             timeout = 0.1
970
971     def send_and_expect(self, input, pkts, output):
972         self.vapi.cli("clear trace")
973         input.add_stream(pkts)
974         self.pg_enable_capture(self.pg_interfaces)
975         self.pg_start()
976         if isinstance(object, (list,)):
977             rx = []
978             for o in output:
979                 rx.append(output.get_capture(len(pkts)))
980         else:
981             rx = output.get_capture(len(pkts))
982         return rx
983
984     def send_and_expect_only(self, input, pkts, output, timeout=None):
985         self.vapi.cli("clear trace")
986         input.add_stream(pkts)
987         self.pg_enable_capture(self.pg_interfaces)
988         self.pg_start()
989         if isinstance(object, (list,)):
990             outputs = output
991             rx = []
992             for o in outputs:
993                 rx.append(output.get_capture(len(pkts)))
994         else:
995             rx = output.get_capture(len(pkts))
996             outputs = [output]
997         if not timeout:
998             timeout = 1
999         for i in self.pg_interfaces:
1000             if i not in outputs:
1001                 i.get_capture(0, timeout=timeout)
1002                 i.assert_nothing_captured()
1003                 timeout = 0.1
1004
1005         return rx
1006
1007     def runTest(self):
1008         """ unittest calls runTest when TestCase is instantiated without a
1009         test case.  Use case: Writing unittests against VppTestCase"""
1010         pass
1011
1012
1013 def get_testcase_doc_name(test):
1014     return getdoc(test.__class__).splitlines()[0]
1015
1016
1017 def get_test_description(descriptions, test):
1018     short_description = test.shortDescription()
1019     if descriptions and short_description:
1020         return short_description
1021     else:
1022         return str(test)
1023
1024
1025 class TestCaseInfo(object):
1026     def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path):
1027         self.logger = logger
1028         self.tempdir = tempdir
1029         self.vpp_pid = vpp_pid
1030         self.vpp_bin_path = vpp_bin_path
1031         self.core_crash_test = None
1032
1033
1034 class VppTestResult(unittest.TestResult):
1035     """
1036     @property result_string
1037      String variable to store the test case result string.
1038     @property errors
1039      List variable containing 2-tuples of TestCase instances and strings
1040      holding formatted tracebacks. Each tuple represents a test which
1041      raised an unexpected exception.
1042     @property failures
1043      List variable containing 2-tuples of TestCase instances and strings
1044      holding formatted tracebacks. Each tuple represents a test where
1045      a failure was explicitly signalled using the TestCase.assert*()
1046      methods.
1047     """
1048
1049     failed_test_cases_info = set()
1050     core_crash_test_cases_info = set()
1051     current_test_case_info = None
1052
1053     def __init__(self, stream, descriptions, verbosity, runner):
1054         """
1055         :param stream File descriptor to store where to report test results.
1056             Set to the standard error stream by default.
1057         :param descriptions Boolean variable to store information if to use
1058             test case descriptions.
1059         :param verbosity Integer variable to store required verbosity level.
1060         """
1061         unittest.TestResult.__init__(self, stream, descriptions, verbosity)
1062         self.stream = stream
1063         self.descriptions = descriptions
1064         self.verbosity = verbosity
1065         self.result_string = None
1066         self.runner = runner
1067
1068     def addSuccess(self, test):
1069         """
1070         Record a test succeeded result
1071
1072         :param test:
1073
1074         """
1075         if self.current_test_case_info:
1076             self.current_test_case_info.logger.debug(
1077                 "--- addSuccess() %s.%s(%s) called" % (test.__class__.__name__,
1078                                                        test._testMethodName,
1079                                                        test._testMethodDoc))
1080         unittest.TestResult.addSuccess(self, test)
1081         self.result_string = colorize("OK", GREEN)
1082
1083         self.send_result_through_pipe(test, PASS)
1084
1085     def addSkip(self, test, reason):
1086         """
1087         Record a test skipped.
1088
1089         :param test:
1090         :param reason:
1091
1092         """
1093         if self.current_test_case_info:
1094             self.current_test_case_info.logger.debug(
1095                 "--- addSkip() %s.%s(%s) called, reason is %s" %
1096                 (test.__class__.__name__, test._testMethodName,
1097                  test._testMethodDoc, reason))
1098         unittest.TestResult.addSkip(self, test, reason)
1099         self.result_string = colorize("SKIP", YELLOW)
1100
1101         self.send_result_through_pipe(test, SKIP)
1102
1103     def symlink_failed(self):
1104         if self.current_test_case_info:
1105             try:
1106                 failed_dir = os.getenv('FAILED_DIR')
1107                 link_path = os.path.join(
1108                     failed_dir,
1109                     '%s-FAILED' %
1110                     os.path.basename(self.current_test_case_info.tempdir))
1111                 if self.current_test_case_info.logger:
1112                     self.current_test_case_info.logger.debug(
1113                         "creating a link to the failed test")
1114                     self.current_test_case_info.logger.debug(
1115                         "os.symlink(%s, %s)" %
1116                         (self.current_test_case_info.tempdir, link_path))
1117                 if os.path.exists(link_path):
1118                     if self.current_test_case_info.logger:
1119                         self.current_test_case_info.logger.debug(
1120                             'symlink already exists')
1121                 else:
1122                     os.symlink(self.current_test_case_info.tempdir, link_path)
1123
1124             except Exception as e:
1125                 if self.current_test_case_info.logger:
1126                     self.current_test_case_info.logger.error(e)
1127
1128     def send_result_through_pipe(self, test, result):
1129         if hasattr(self, 'test_framework_result_pipe'):
1130             pipe = self.test_framework_result_pipe
1131             if pipe:
1132                 pipe.send((test.id(), result))
1133
1134     def log_error(self, test, err, fn_name):
1135         if self.current_test_case_info:
1136             if isinstance(test, unittest.suite._ErrorHolder):
1137                 test_name = test.description
1138             else:
1139                 test_name = '%s.%s(%s)' % (test.__class__.__name__,
1140                                            test._testMethodName,
1141                                            test._testMethodDoc)
1142             self.current_test_case_info.logger.debug(
1143                 "--- %s() %s called, err is %s" %
1144                 (fn_name, test_name, err))
1145             self.current_test_case_info.logger.debug(
1146                 "formatted exception is:\n%s" %
1147                 "".join(format_exception(*err)))
1148
1149     def add_error(self, test, err, unittest_fn, error_type):
1150         if error_type == FAIL:
1151             self.log_error(test, err, 'addFailure')
1152             error_type_str = colorize("FAIL", RED)
1153         elif error_type == ERROR:
1154             self.log_error(test, err, 'addError')
1155             error_type_str = colorize("ERROR", RED)
1156         else:
1157             raise Exception('Error type %s cannot be used to record an '
1158                             'error or a failure' % error_type)
1159
1160         unittest_fn(self, test, err)
1161         if self.current_test_case_info:
1162             self.result_string = "%s [ temp dir used by test case: %s ]" % \
1163                                  (error_type_str,
1164                                   self.current_test_case_info.tempdir)
1165             self.symlink_failed()
1166             self.failed_test_cases_info.add(self.current_test_case_info)
1167             if is_core_present(self.current_test_case_info.tempdir):
1168                 if not self.current_test_case_info.core_crash_test:
1169                     if isinstance(test, unittest.suite._ErrorHolder):
1170                         test_name = str(test)
1171                     else:
1172                         test_name = "'{}' ({})".format(
1173                             get_testcase_doc_name(test), test.id())
1174                     self.current_test_case_info.core_crash_test = test_name
1175                 self.core_crash_test_cases_info.add(
1176                     self.current_test_case_info)
1177         else:
1178             self.result_string = '%s [no temp dir]' % error_type_str
1179
1180         self.send_result_through_pipe(test, error_type)
1181
1182     def addFailure(self, test, err):
1183         """
1184         Record a test failed result
1185
1186         :param test:
1187         :param err: error message
1188
1189         """
1190         self.add_error(test, err, unittest.TestResult.addFailure, FAIL)
1191
1192     def addError(self, test, err):
1193         """
1194         Record a test error result
1195
1196         :param test:
1197         :param err: error message
1198
1199         """
1200         self.add_error(test, err, unittest.TestResult.addError, ERROR)
1201
1202     def getDescription(self, test):
1203         """
1204         Get test description
1205
1206         :param test:
1207         :returns: test description
1208
1209         """
1210         return get_test_description(self.descriptions, test)
1211
1212     def startTest(self, test):
1213         """
1214         Start a test
1215
1216         :param test:
1217
1218         """
1219         test.print_header()
1220
1221         unittest.TestResult.startTest(self, test)
1222         if self.verbosity > 0:
1223             self.stream.writeln(
1224                 "Starting " + self.getDescription(test) + " ...")
1225             self.stream.writeln(single_line_delim)
1226
1227     def stopTest(self, test):
1228         """
1229         Called when the given test has been run
1230
1231         :param test:
1232
1233         """
1234         unittest.TestResult.stopTest(self, test)
1235         if self.verbosity > 0:
1236             self.stream.writeln(single_line_delim)
1237             self.stream.writeln("%-73s%s" % (self.getDescription(test),
1238                                              self.result_string))
1239             self.stream.writeln(single_line_delim)
1240         else:
1241             self.stream.writeln("%-73s%s" % (self.getDescription(test),
1242                                              self.result_string))
1243
1244         self.send_result_through_pipe(test, TEST_RUN)
1245
1246     def printErrors(self):
1247         """
1248         Print errors from running the test case
1249         """
1250         if len(self.errors) > 0 or len(self.failures) > 0:
1251             self.stream.writeln()
1252             self.printErrorList('ERROR', self.errors)
1253             self.printErrorList('FAIL', self.failures)
1254
1255         # ^^ that is the last output from unittest before summary
1256         if not self.runner.print_summary:
1257             devnull = unittest.runner._WritelnDecorator(open(os.devnull, 'w'))
1258             self.stream = devnull
1259             self.runner.stream = devnull
1260
1261     def printErrorList(self, flavour, errors):
1262         """
1263         Print error list to the output stream together with error type
1264         and test case description.
1265
1266         :param flavour: error type
1267         :param errors: iterable errors
1268
1269         """
1270         for test, err in errors:
1271             self.stream.writeln(double_line_delim)
1272             self.stream.writeln("%s: %s" %
1273                                 (flavour, self.getDescription(test)))
1274             self.stream.writeln(single_line_delim)
1275             self.stream.writeln("%s" % err)
1276
1277
1278 class VppTestRunner(unittest.TextTestRunner):
1279     """
1280     A basic test runner implementation which prints results to standard error.
1281     """
1282
1283     @property
1284     def resultclass(self):
1285         """Class maintaining the results of the tests"""
1286         return VppTestResult
1287
1288     def __init__(self, keep_alive_pipe=None, descriptions=True, verbosity=1,
1289                  result_pipe=None, failfast=False, buffer=False,
1290                  resultclass=None, print_summary=True):
1291         # ignore stream setting here, use hard-coded stdout to be in sync
1292         # with prints from VppTestCase methods ...
1293         super(VppTestRunner, self).__init__(sys.stdout, descriptions,
1294                                             verbosity, failfast, buffer,
1295                                             resultclass)
1296         KeepAliveReporter.pipe = keep_alive_pipe
1297
1298         self.orig_stream = self.stream
1299         self.resultclass.test_framework_result_pipe = result_pipe
1300
1301         self.print_summary = print_summary
1302
1303     def _makeResult(self):
1304         return self.resultclass(self.stream,
1305                                 self.descriptions,
1306                                 self.verbosity,
1307                                 self)
1308
1309     def run(self, test):
1310         """
1311         Run the tests
1312
1313         :param test:
1314
1315         """
1316         faulthandler.enable()  # emit stack trace to stderr if killed by signal
1317
1318         result = super(VppTestRunner, self).run(test)
1319         if not self.print_summary:
1320             self.stream = self.orig_stream
1321             result.stream = self.orig_stream
1322         return result
1323
1324
1325 class Worker(Thread):
1326     def __init__(self, args, logger, env={}):
1327         self.logger = logger
1328         self.args = args
1329         self.result = None
1330         self.env = copy.deepcopy(env)
1331         super(Worker, self).__init__()
1332
1333     def run(self):
1334         executable = self.args[0]
1335         self.logger.debug("Running executable w/args `%s'" % self.args)
1336         env = os.environ.copy()
1337         env.update(self.env)
1338         env["CK_LOG_FILE_NAME"] = "-"
1339         self.process = subprocess.Popen(
1340             self.args, shell=False, env=env, preexec_fn=os.setpgrp,
1341             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1342         out, err = self.process.communicate()
1343         self.logger.debug("Finished running `%s'" % executable)
1344         self.logger.info("Return code is `%s'" % self.process.returncode)
1345         self.logger.info(single_line_delim)
1346         self.logger.info("Executable `%s' wrote to stdout:" % executable)
1347         self.logger.info(single_line_delim)
1348         self.logger.info(out)
1349         self.logger.info(single_line_delim)
1350         self.logger.info("Executable `%s' wrote to stderr:" % executable)
1351         self.logger.info(single_line_delim)
1352         self.logger.info(err)
1353         self.logger.info(single_line_delim)
1354         self.result = self.process.returncode