from inspect import getdoc, isclass
from traceback import format_exception
from logging import FileHandler, DEBUG, Formatter
+from enum import Enum
import scapy.compat
from scapy.packet import Raw
from vpp_papi_provider import VppPapiProvider
import vpp_papi
from vpp_papi.vpp_stats import VPPStats
-from vpp_papi.vpp_transport_shmem import VppTransportShmemIOError
+from vpp_papi.vpp_transport_socket import VppTransportSocketIOError
from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
get_logger, colorize
from vpp_object import VppObjectRegistry
if testcase is None and method_name is None:
in_msg = ''
else:
- in_msg = 'running %s.%s ' % (testcase, method_name)
+ in_msg = ' while running %s.%s' % (testcase, method_name)
+
+ if self.rv:
+ msg = "VPP subprocess died unexpectedly%s with return code: %d%s."\
+ % (in_msg, self.rv, ' [%s]' %
+ (self.signal_name if
+ self.signal_name is not None else ''))
+ else:
+ msg = "VPP subprocess died unexpectedly%s." % in_msg
- msg = "VPP subprocess died %sunexpectedly with return code: %d%s." % (
- in_msg,
- self.rv,
- ' [%s]' % (self.signal_name if
- self.signal_name is not None else ''))
super(VppDiedError, self).__init__(msg)
self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
+class TestCaseTag(Enum):
+ # marks the suites that must run at the end
+ # using only a single test runner
+ RUN_SOLO = 1
+ # marks the suites broken on VPP multi-worker
+ FIXME_VPP_WORKERS = 2
+
+
+def create_tag_decorator(e):
+ def decorator(cls):
+ try:
+ cls.test_tags.append(e)
+ except AttributeError:
+ cls.test_tags = [e]
+ return cls
+ return decorator
+
+
+tag_run_solo = create_tag_decorator(TestCaseTag.RUN_SOLO)
+tag_fixme_vpp_workers = create_tag_decorator(TestCaseTag.FIXME_VPP_WORKERS)
+
+
class VppTestCase(unittest.TestCase):
"""This subclass is a base class for VPP test cases that are implemented as
classes. It provides methods to create and run test case.
return 0
@classmethod
- def force_solo(cls):
- """ if the test case class is timing-sensitive - return true """
+ def has_tag(cls, tag):
+ """ if the test case has a given tag - return true """
+ try:
+ return tag in cls.test_tags
+ except AttributeError:
+ pass
return False
+ @classmethod
+ def is_tagged_run_solo(cls):
+ """ if the test case class is timing-sensitive - return true """
+ return cls.has_tag(TestCaseTag.RUN_SOLO)
+
@classmethod
def instance(cls):
"""Return the instance of this testcase"""
cpu_core_number = cls.get_least_used_cpu()
if not hasattr(cls, "worker_config"):
- cls.worker_config = ""
+ cls.worker_config = os.getenv("VPP_WORKER_CONFIG", "")
+ if cls.worker_config != "":
+ if cls.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
+ cls.worker_config = ""
default_variant = os.getenv("VARIANT")
if default_variant is not None:
cls.logger.addHandler(cls.file_handler)
cls.logger.debug("--- setUpClass() for %s called ---" %
cls.__name__)
- cls.shm_prefix = os.path.basename(cls.tempdir)
+ cls.shm_prefix = os.path.basename(cls.tempdir) # Only used for VAPI
os.chdir(cls.tempdir)
- cls.logger.info("Temporary dir is %s, shm prefix is %s",
- cls.tempdir, cls.shm_prefix)
+ cls.logger.info("Temporary dir is %s, api socket is %s",
+ cls.tempdir, cls.api_sock)
cls.logger.debug("Random seed is %s" % seed)
cls.setUpConstants()
cls.reset_packet_infos()
cls.pump_thread.start()
if cls.debug_gdb or cls.debug_gdbserver:
cls.vapi_response_timeout = 0
- cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls,
+ cls.vapi = VppPapiProvider(cls.__name__, cls,
cls.vapi_response_timeout)
if cls.step:
hook = hookmodule.StepHook(cls)
raise
try:
cls.vapi.connect()
- except vpp_papi.VPPIOError as e:
+ except (vpp_papi.VPPIOError, Exception) as e:
cls.logger.debug("Exception connecting to vapi: %s" % e)
cls.vapi.disconnect()
print(colorize("You're running VPP inside gdbserver but "
"VPP-API connection failed, did you forget "
"to 'continue' VPP from within gdb?", RED))
- raise
+ raise e
except vpp_papi.VPPRuntimeError as e:
cls.logger.debug("%s" % e)
cls.quit()
- raise
+ raise e
except Exception as e:
cls.logger.debug("Exception connecting to VPP: %s" % e)
cls.quit()
- raise
+ raise e
@classmethod
def _debug_quit(cls):
cls.logger.debug("Sending TERM to vpp")
cls.vpp.terminate()
cls.logger.debug("Waiting for vpp to die")
- cls.vpp.communicate()
+ try:
+ outs, errs = cls.vpp.communicate(timeout=5)
+ except subprocess.TimeoutExpired:
+ cls.vpp.kill()
+ outs, errs = cls.vpp.communicate()
cls.logger.debug("Deleting class vpp attribute on %s",
cls.__name__)
+ cls.vpp.stdout.close()
+ cls.vpp.stderr.close()
del cls.vpp
if cls.vpp_startup_failed:
os.rename(tmp_api_trace, vpp_api_trace_log)
self.logger.info(self.vapi.ppcli("api trace custom-dump %s" %
vpp_api_trace_log))
- except VppTransportShmemIOError:
- self.logger.debug("VppTransportShmemIOError: Vpp dead. "
+ except VppTransportSocketIOError:
+ self.logger.debug("VppTransportSocketIOError: Vpp dead. "
"Cannot log show commands.")
self.vpp_dead = True
else:
cls.sleep(0.1)
@classmethod
- def pg_start(cls):
+ def pg_start(cls, trace=True):
""" Enable the PG, wait till it is done, then clean up """
- cls.vapi.cli("trace add pg-input 1000")
+ if trace:
+ cls.vapi.cli("clear trace")
+ cls.vapi.cli("trace add pg-input 1000")
cls.vapi.cli('packet-generator enable')
# PG, when starts, runs to completion -
# so let's avoid a race condition,
after - before, timeout)
cls.logger.debug(
- "Finished sleep (%s) - slept %es (wanted %es)",
- remark, after - before, timeout)
+ "Finished sleep (%s) - slept %es (wanted %es)",
+ remark, after - before, timeout)
- def pg_send(self, intf, pkts, worker=None):
- self.vapi.cli("clear trace")
+ def pg_send(self, intf, pkts, worker=None, trace=True):
intf.add_stream(pkts, worker=worker)
self.pg_enable_capture(self.pg_interfaces)
- self.pg_start()
+ self.pg_start(trace=trace)
def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
self.pg_send(intf, pkts)
i.assert_nothing_captured(remark=remark)
timeout = 0.1
- def send_and_expect(self, intf, pkts, output, n_rx=None, worker=None):
+ def send_and_expect(self, intf, pkts, output, n_rx=None, worker=None,
+ trace=True):
if not n_rx:
n_rx = len(pkts)
- self.pg_send(intf, pkts, worker=worker)
+ self.pg_send(intf, pkts, worker=worker, trace=trace)
rx = output.get_capture(n_rx)
return rx
os.path.basename(self.current_test_case_info.tempdir))
self.current_test_case_info.logger.debug(
- "creating a link to the failed test")
+ "creating a link to the failed test")
self.current_test_case_info.logger.debug(
- "os.symlink(%s, %s)" %
- (self.current_test_case_info.tempdir, link_path))
+ "os.symlink(%s, %s)" %
+ (self.current_test_case_info.tempdir, link_path))
if os.path.exists(link_path):
self.current_test_case_info.logger.debug(
- 'symlink already exists')
+ 'symlink already exists')
else:
os.symlink(self.current_test_case_info.tempdir, link_path)
raise Exception("No doc string for test '%s'" % test.id())
test_title = test_doc.splitlines()[0]
test_title_colored = colorize(test_title, GREEN)
- if test.force_solo():
+ if test.is_tagged_run_solo():
# long live PEP-8 and 80 char width limitation...
c = YELLOW
test_title_colored = colorize("SOLO RUN: " + test_title, c)
+ # This block may overwrite the colorized title above,
+ # but we want this to stand out and be fixed
+ if test.has_tag(TestCaseTag.FIXME_VPP_WORKERS):
+ c = RED
+ w = "FIXME with VPP workers: "
+ test_title_colored = colorize(w + test_title, c)
+
if not hasattr(test.__class__, '_header_printed'):
print(double_line_delim)
print(test_title_colored)