X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=test%2Fframework.py;h=be8c209f4ea277c2b972f668d87463d8a2f37df6;hb=31da2e30317bc1fcb4586e1dc0d560600d9b29d3;hp=210eadbf4c56cc8826bc4a5fdad43b65fdd2eb1a;hpb=e178399942c655d0fea162611527ea57192669c0;p=vpp.git diff --git a/test/framework.py b/test/framework.py index 210eadbf4c5..be8c209f4ea 100644 --- a/test/framework.py +++ b/test/framework.py @@ -8,22 +8,27 @@ import select import unittest import tempfile import time -import resource import faulthandler import random +import copy from collections import deque from threading import Thread, Event from inspect import getdoc, isclass from traceback import format_exception from logging import FileHandler, DEBUG, Formatter from scapy.packet import Raw -from hook import StepHook, PollHook +from hook import StepHook, PollHook, VppDiedError from vpp_pg_interface import VppPGInterface from vpp_sub_interface import VppSubInterface from vpp_lo_interface import VppLoInterface from vpp_papi_provider import VppPapiProvider -from log import * +from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \ + getLogger, colorize from vpp_object import VppObjectRegistry +from util import ppp +from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror +from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest +from scapy.layers.inet6 import ICMPv6EchoReply if os.name == 'posix' and sys.version_info[0] < 3: # using subprocess32 is recommended by python official documentation # @ https://docs.python.org/2/library/subprocess.html @@ -31,6 +36,13 @@ if os.name == 'posix' and sys.version_info[0] < 3: else: import subprocess + +debug_framework = False +if os.getenv('TEST_DEBUG', "0") == "1": + debug_framework = True + import debug_internal + + """ Test framework module. @@ -113,21 +125,13 @@ def pump_output(testclass): def running_extended_tests(): - try: - s = os.getenv("EXTENDED_TESTS") - return True if s.lower() in ("y", "yes", "1") else False - except: - return False - return False + s = os.getenv("EXTENDED_TESTS", "n") + return True if s.lower() in ("y", "yes", "1") else False def running_on_centos(): - try: - os_id = os.getenv("OS_ID") - return True if "centos" in os_id.lower() else False - except: - return False - return False + os_id = os.getenv("OS_ID", "") + return True if "centos" in os_id.lower() else False class KeepAliveReporter(object): @@ -210,21 +214,11 @@ class VppTestCase(unittest.TestCase): @classmethod def setUpConstants(cls): """ Set-up the test case class based on environment variables """ - try: - s = os.getenv("STEP") - cls.step = True if s.lower() in ("y", "yes", "1") else False - except: - cls.step = False - try: - d = os.getenv("DEBUG") - except: - d = None - try: - c = os.getenv("CACHE_OUTPUT", "1") - cls.cache_vpp_output = \ - False if c.lower() in ("n", "no", "0") else True - except: - cls.cache_vpp_output = True + s = os.getenv("STEP", "n") + cls.step = True if s.lower() in ("y", "yes", "1") else False + d = os.getenv("DEBUG", None) + c = os.getenv("CACHE_OUTPUT", "1") + cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True cls.set_debug_flags(d) cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp") cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH') @@ -242,12 +236,9 @@ class VppTestCase(unittest.TestCase): if cls.step or cls.debug_gdb or cls.debug_gdbserver: debug_cli = "cli-listen localhost:5002" coredump_size = None - try: - size = os.getenv("COREDUMP_SIZE") - if size is not None: - coredump_size = "coredump-size %s" % size - except: - pass + size = os.getenv("COREDUMP_SIZE") + if size is not None: + coredump_size = "coredump-size %s" % size if coredump_size is None: coredump_size = "coredump-size unlimited" cls.vpp_cmdline = [cls.vpp_bin, "unix", @@ -255,7 +246,7 @@ class VppTestCase(unittest.TestCase): coredump_size, "}", "api-trace", "{", "on", "}", "api-segment", "{", "prefix", cls.shm_prefix, "}", "plugins", "{", "plugin", "dpdk_plugin.so", "{", - "disable", "}", "}"] + "disable", "}", "}", ] if plugin_path is not None: cls.vpp_cmdline.extend(["plugin_path", plugin_path]) cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline) @@ -361,7 +352,7 @@ class VppTestCase(unittest.TestCase): cls.sleep(0.1, "after vpp startup, before initial poll") try: hook.poll_vpp() - except: + except VppDiedError: cls.vpp_startup_failed = True cls.logger.critical( "VPP died shortly after startup, check the" @@ -369,19 +360,22 @@ class VppTestCase(unittest.TestCase): raise try: cls.vapi.connect() - except: + except Exception: + try: + cls.vapi.disconnect() + except Exception: + pass if cls.debug_gdbserver: print(colorize("You're running VPP inside gdbserver but " "VPP-API connection failed, did you forget " "to 'continue' VPP from within gdb?", RED)) raise - except: - t, v, tb = sys.exc_info() + except Exception: try: cls.quit() - except: + except Exception: pass - raise t, v, tb + raise @classmethod def quit(cls): @@ -450,6 +444,9 @@ class VppTestCase(unittest.TestCase): """ Perform final cleanup after running all tests in this test-case """ cls.quit() cls.file_handler.close() + cls.reset_packet_infos() + if debug_framework: + debug_internal.on_tear_down_class(cls) def tearDown(self): """ Show various debug prints after each test """ @@ -462,6 +459,7 @@ class VppTestCase(unittest.TestCase): self.logger.info(self.vapi.ppcli("show hardware")) self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show run")) + self.logger.info(self.vapi.ppcli("show log")) self.registry.remove_vpp_config(self.logger) # Save/Dump VPP api trace log api_trace = "vpp_api_trace.%s.log" % self._testMethodName @@ -471,7 +469,7 @@ class VppTestCase(unittest.TestCase): self.logger.info("Moving %s to %s\n" % (tmp_api_trace, vpp_api_trace_log)) os.rename(tmp_api_trace, vpp_api_trace_log) - self.logger.info(self.vapi.ppcli("api trace dump %s" % + self.logger.info(self.vapi.ppcli("api trace custom-dump %s" % vpp_api_trace_log)) else: self.registry.unregister_all(self.logger) @@ -499,13 +497,16 @@ class VppTestCase(unittest.TestCase): type(self).test_instance = self @classmethod - def pg_enable_capture(cls, interfaces): + def pg_enable_capture(cls, interfaces=None): """ Enable capture on packet-generator interfaces - :param interfaces: iterable interface indexes + :param interfaces: iterable interface indexes (if None, + use self.pg_interfaces) """ + if interfaces is None: + interfaces = cls.pg_interfaces for i in interfaces: i.enable_capture() @@ -573,19 +574,21 @@ class VppTestCase(unittest.TestCase): return result @staticmethod - def extend_packet(packet, size): + def extend_packet(packet, size, padding=' '): """ - Extend packet to given size by padding with spaces + Extend packet to given size by padding with spaces or custom padding NOTE: Currently works only when Raw layer is present. :param packet: packet :param size: target size + :param padding: padding used to extend the payload """ packet_len = len(packet) + 4 extend = size - packet_len if extend > 0: - packet[Raw].load += ' ' * extend + num = (extend / len(padding)) + 1 + packet[Raw].load += (padding * num)[:extend] @classmethod def reset_packet_infos(cls): @@ -713,7 +716,7 @@ class VppTestCase(unittest.TestCase): msg = msg % (getdoc(name_or_class).strip(), real_value, str(name_or_class(real_value)), expected_value, str(name_or_class(expected_value))) - except: + except Exception: msg = "Invalid %s: %s does not match expected value %s" % ( name_or_class, real_value, expected_value) @@ -731,6 +734,95 @@ class VppTestCase(unittest.TestCase): name, real_value, expected_min, expected_max) self.assertTrue(expected_min <= real_value <= expected_max, msg) + def assert_packet_checksums_valid(self, packet, + ignore_zero_udp_checksums=True): + received = packet.__class__(str(packet)) + self.logger.debug( + ppp("Verifying packet checksums for packet:", received)) + udp_layers = ['UDP', 'UDPerror'] + checksum_fields = ['cksum', 'chksum'] + checksums = [] + counter = 0 + temp = received.__class__(str(received)) + while True: + layer = temp.getlayer(counter) + if layer: + for cf in checksum_fields: + if hasattr(layer, cf): + if ignore_zero_udp_checksums and \ + 0 == getattr(layer, cf) and \ + layer.name in udp_layers: + continue + delattr(layer, cf) + checksums.append((counter, cf)) + else: + break + counter = counter + 1 + if 0 == len(checksums): + return + temp = temp.__class__(str(temp)) + for layer, cf in checksums: + calc_sum = getattr(temp[layer], cf) + self.assert_equal( + getattr(received[layer], cf), calc_sum, + "packet checksum on layer #%d: %s" % (layer, temp[layer].name)) + self.logger.debug( + "Checksum field `%s` on `%s` layer has correct value `%s`" % + (cf, temp[layer].name, calc_sum)) + + def assert_checksum_valid(self, received_packet, layer, + field_name='chksum', + ignore_zero_checksum=False): + """ Check checksum of received packet on given layer """ + received_packet_checksum = getattr(received_packet[layer], field_name) + if ignore_zero_checksum and 0 == received_packet_checksum: + return + recalculated = received_packet.__class__(str(received_packet)) + delattr(recalculated[layer], field_name) + recalculated = recalculated.__class__(str(recalculated)) + self.assert_equal(received_packet_checksum, + getattr(recalculated[layer], field_name), + "packet checksum on layer: %s" % layer) + + def assert_ip_checksum_valid(self, received_packet, + ignore_zero_checksum=False): + self.assert_checksum_valid(received_packet, 'IP', + ignore_zero_checksum=ignore_zero_checksum) + + def assert_tcp_checksum_valid(self, received_packet, + ignore_zero_checksum=False): + self.assert_checksum_valid(received_packet, 'TCP', + ignore_zero_checksum=ignore_zero_checksum) + + def assert_udp_checksum_valid(self, received_packet, + ignore_zero_checksum=True): + self.assert_checksum_valid(received_packet, 'UDP', + ignore_zero_checksum=ignore_zero_checksum) + + def assert_embedded_icmp_checksum_valid(self, received_packet): + if received_packet.haslayer(IPerror): + self.assert_checksum_valid(received_packet, 'IPerror') + if received_packet.haslayer(TCPerror): + self.assert_checksum_valid(received_packet, 'TCPerror') + if received_packet.haslayer(UDPerror): + self.assert_checksum_valid(received_packet, 'UDPerror', + ignore_zero_checksum=True) + if received_packet.haslayer(ICMPerror): + self.assert_checksum_valid(received_packet, 'ICMPerror') + + def assert_icmp_checksum_valid(self, received_packet): + self.assert_checksum_valid(received_packet, 'ICMP') + self.assert_embedded_icmp_checksum_valid(received_packet) + + def assert_icmpv6_checksum_valid(self, pkt): + if pkt.haslayer(ICMPv6DestUnreach): + self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum') + self.assert_embedded_icmp_checksum_valid(pkt) + if pkt.haslayer(ICMPv6EchoRequest): + self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum') + if pkt.haslayer(ICMPv6EchoReply): + self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum') + @classmethod def sleep(cls, timeout, remark=None): if hasattr(cls, 'logger'): @@ -747,6 +839,25 @@ class VppTestCase(unittest.TestCase): "Finished sleep (%s) - slept %ss (wanted %ss)" % ( remark, after - before, timeout)) + def send_and_assert_no_replies(self, intf, pkts, remark=""): + self.vapi.cli("clear trace") + intf.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + timeout = 1 + for i in self.pg_interfaces: + i.get_capture(0, timeout=timeout) + i.assert_nothing_captured(remark=remark) + timeout = 0.1 + + def send_and_expect(self, input, pkts, output): + self.vapi.cli("clear trace") + input.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = output.get_capture(len(pkts)) + return rx + class TestCasePrinter(object): _shared_state = {} @@ -1013,10 +1124,7 @@ class VppTestRunner(unittest.TextTestRunner): test_option = "TEST" def parse_test_option(self): - try: - f = os.getenv(self.test_option) - except: - f = None + f = os.getenv(self.test_option, None) filter_file_name = None filter_class_name = None filter_func_name = None @@ -1049,7 +1157,7 @@ class VppTestRunner(unittest.TextTestRunner): for t in tests: if isinstance(t, unittest.suite.TestSuite): # this is a bunch of tests, recursively filter... - x = filter_tests(t, filter_cb) + x = VppTestRunner.filter_tests(t, filter_cb) if x.countTestCases() > 0: result.addTest(x) elif isinstance(t, unittest.TestCase): @@ -1090,16 +1198,18 @@ class VppTestRunner(unittest.TextTestRunner): class Worker(Thread): - def __init__(self, args, logger): + def __init__(self, args, logger, env={}): self.logger = logger self.args = args self.result = None + self.env = copy.deepcopy(env) super(Worker, self).__init__() def run(self): executable = self.args[0] self.logger.debug("Running executable w/args `%s'" % self.args) env = os.environ.copy() + env.update(self.env) env["CK_LOG_FILE_NAME"] = "-" self.process = subprocess.Popen( self.args, shell=False, env=env, preexec_fn=os.setpgrp, @@ -1114,6 +1224,6 @@ class Worker(Thread): self.logger.info(single_line_delim) self.logger.info("Executable `%s' wrote to stderr:" % executable) self.logger.info(single_line_delim) - self.logger.error(err) + self.logger.info(err) self.logger.info(single_line_delim) self.result = self.process.returncode