summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/Makefile50
-rw-r--r--test/doc/Makefile188
-rw-r--r--test/doc/conf.py1
-rw-r--r--test/framework.py214
-rw-r--r--test/hook.py112
-rw-r--r--test/log.py71
-rw-r--r--test/test_l2bd.py2
-rw-r--r--test/test_l2xc.py12
-rw-r--r--test/vpp_papi_provider.py19
9 files changed, 490 insertions, 179 deletions
diff --git a/test/Makefile b/test/Makefile
index c90679e8e20..63cee2036b4 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,22 +1,58 @@
-PYTHON_VENV_PATH=$(PYTHON_PATH)/virtualenv
+.PHONY: verify-python-path
-test: clean
+verify-python-path:
+ifndef VPP_PYTHON_PREFIX
+ $(error VPP_PYTHON_PREFIX is not set)
+endif
+
+PYTHON_VENV_PATH=$(VPP_PYTHON_PREFIX)/virtualenv
+
+test: wipe verify-python-path
@virtualenv $(PYTHON_VENV_PATH)
@bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install scapy"
@bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install pexpect"
@bash -c "source $(PYTHON_VENV_PATH)/bin/activate && cd $(WS_ROOT)/vpp-api/python && python setup.py install"
@bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python run_tests.py discover -p test_$(TEST)\"*.py\""
-retest: clean
+retest: wipe verify-python-path
@bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python run_tests.py discover -p test_$(TEST)\"*.py\""
-.PHONY: clean doc
+.PHONY: wipe doc
-clean:
+wipe: verify-python-path
@rm -f /dev/shm/vpp-unittest-*
@rm -rf /tmp/vpp-unittest-*
-doc:
+doc: verify-python-path
@virtualenv $(PYTHON_VENV_PATH)
@bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install sphinx"
- @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && make -C doc html"
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && make -C doc WS_ROOT=$(WS_ROOT) BR=$(BR) NO_VPP_PAPI=1 html"
+
+wipe-doc:
+ @make -C doc wipe BR=$(BR)
+
+help:
+ @echo "Running tests:"
+ @echo ""
+ @echo " test - build and run functional tests"
+ @echo " test-debug - build and run functional tests (debug build)"
+ @echo " retest - run functional tests"
+ @echo " retest-debug - run functional tests (debug build)"
+ @echo " wipe-test - wipe (temporary) files generated by unit tests"
+ @echo ""
+ @echo "Arguments controlling test runs:"
+ @echo " V=[0|1|2] - set test verbosity level"
+ @echo " DEBUG=<type> - set VPP debugging kind"
+ @echo " DEBUG=core - detect coredump and load it in gdb on crash"
+ @echo " DEBUG=gdb - allow easy debugging by printing VPP PID "
+ @echo " and waiting for user input before running "
+ @echo " and tearing down a testcase"
+ @echo " DEBUG=gdbserver - run gdb inside a gdb server, otherwise "
+ @echo " same as above"
+ @echo " STEP=[yes|no] - ease debugging by stepping through a testcase "
+ @echo " TEST=<name> - only run specific test"
+ @echo ""
+ @echo "Creating test documentation"
+ @echo " test-doc - generate documentation for test framework"
+ @echo " wipe-test-doc - wipe documentation for test framework"
+ @echo ""
diff --git a/test/doc/Makefile b/test/doc/Makefile
index 00655389e94..809abef846e 100644
--- a/test/doc/Makefile
+++ b/test/doc/Makefile
@@ -3,20 +3,34 @@
# You can set these variables from the command line.
SPHINXOPTS =
-SPHINXBUILD = sphinx-build
+SRC_DOC_DIR = $(WS_ROOT)/test/doc
+SPHINXBUILD = sphinx-build
PAPER =
-BUILDDIR = _build
+BUILD_DOC_ROOT = $(BR)/test-doc
+BUILD_DOC_DIR = $(BUILD_DOC_ROOT)/build
+API_DOC_GEN_DIR = $(BUILD_DOC_ROOT)/apidoc
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+ALLSPHINXOPTS = -d $(BUILD_DOC_DIR)/.sphinx-cache $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(API_DOC_GEN_DIR) -c $(SRC_DOC_DIR)
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+INDEX_REL_PATH:=$(shell realpath --relative-to=$(API_DOC_GEN_DIR) $(SRC_DOC_DIR)/index.rst)
+IN_VENV:=$(shell if pip -V | grep "virtualenv" 2>&1 > /dev/null; then echo 1; else echo 0; fi)
+
+.PHONY: verify-virtualenv
+verify-virtualenv:
+ifeq ($(IN_VENV),0)
+ $(error "Not running inside virtualenv (are you running 'make test-doc' from root?)")
+endif
.PHONY: regen-api-doc
-regen-api-doc:
- sphinx-apidoc -o . ..
+regen-api-doc: verify-virtualenv
+ @mkdir -p $(API_DOC_GEN_DIR)
+ #@echo ".. include:: $(INDEX_REL_PATH)" > $(API_DOC_GEN_DIR)/index.rst
+ @cp $(SRC_DOC_DIR)/index.rst $(API_DOC_GEN_DIR)
+ sphinx-apidoc -o $(API_DOC_GEN_DIR) ..
.PHONY: help
help:
@@ -48,182 +62,182 @@ help:
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
-.PHONY: clean
-clean:
- rm -rf $(BUILDDIR)/*
+.PHONY: wipe
+wipe:
+ rm -rf $(BUILD_DOC_ROOT)
.PHONY: html
-html: regen-api-doc
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+html: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/html
@echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+ @echo "Build finished. The HTML pages are in $(BUILD_DOC_DIR)/html."
.PHONY: dirhtml
-dirhtml: regen-api-doc
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+dirhtml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/dirhtml
@echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+ @echo "Build finished. The HTML pages are in $(BUILD_DOC_DIR)/dirhtml."
.PHONY: singlehtml
-singlehtml: regen-api-doc
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+singlehtml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/singlehtml
@echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+ @echo "Build finished. The HTML page is in $(BUILD_DOC_DIR)/singlehtml."
.PHONY: pickle
-pickle: regen-api-doc
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+pickle: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
-json: regen-api-doc
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+json: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
-htmlhelp: regen-api-doc
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+htmlhelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
+ ".hhp project file in $(BUILD_DOC_DIR)/htmlhelp."
.PHONY: qthelp
-qthelp: regen-api-doc
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+qthelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/VPPtestframework.qhcp"
+ ".qhcp project file in $(BUILD_DOC_DIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILD_DOC_DIR)/qthelp/VPPtestframework.qhcp"
@echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/VPPtestframework.qhc"
+ @echo "# assistant -collectionFile $(BUILD_DOC_DIR)/qthelp/VPPtestframework.qhc"
.PHONY: applehelp
-applehelp: regen-api-doc
- $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+applehelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/applehelp
@echo
- @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+ @echo "Build finished. The help book is in $(BUILD_DOC_DIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
-devhelp: regen-api-doc
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+devhelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/VPPtestframework"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/VPPtestframework"
+ @echo "# ln -s $(BUILD_DOC_DIR)/devhelp $$HOME/.local/share/devhelp/VPPtestframework"
@echo "# devhelp"
.PHONY: epub
-epub: regen-api-doc
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+epub: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/epub
@echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+ @echo "Build finished. The epub file is in $(BUILD_DOC_DIR)/epub."
.PHONY: epub3
epub3:
- $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
+ $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/epub3
@echo
- @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
+ @echo "Build finished. The epub3 file is in $(BUILD_DOC_DIR)/epub3."
.PHONY: latex
-latex: regen-api-doc
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+latex: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex
@echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Build finished; the LaTeX files are in $(BUILD_DOC_DIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
-latexpdf: regen-api-doc
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+latexpdf: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex
@echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+ $(MAKE) -C $(BUILD_DOC_DIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILD_DOC_DIR)/latex."
.PHONY: latexpdfja
-latexpdfja: regen-api-doc
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+latexpdfja: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+ $(MAKE) -C $(BUILD_DOC_DIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILD_DOC_DIR)/latex."
.PHONY: text
-text: regen-api-doc
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+text: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/text
@echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
+ @echo "Build finished. The text files are in $(BUILD_DOC_DIR)/text."
.PHONY: man
-man: regen-api-doc
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+man: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/man
@echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+ @echo "Build finished. The manual pages are in $(BUILD_DOC_DIR)/man."
.PHONY: texinfo
-texinfo: regen-api-doc
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+texinfo: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/texinfo
@echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Build finished. The Texinfo files are in $(BUILD_DOC_DIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
-info: regen-api-doc
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+info: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+ make -C $(BUILD_DOC_DIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILD_DOC_DIR)/texinfo."
.PHONY: gettext
-gettext: regen-api-doc
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+gettext: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILD_DOC_DIR)/locale
@echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+ @echo "Build finished. The message catalogs are in $(BUILD_DOC_DIR)/locale."
.PHONY: changes
-changes: regen-api-doc
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+changes: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/changes
@echo
- @echo "The overview file is in $(BUILDDIR)/changes."
+ @echo "The overview file is in $(BUILD_DOC_DIR)/changes."
.PHONY: linkcheck
-linkcheck: regen-api-doc
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+linkcheck: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
+ "or in $(BUILD_DOC_DIR)/linkcheck/output.txt."
.PHONY: doctest
-doctest: regen-api-doc
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+doctest: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
+ "results in $(BUILD_DOC_DIR)/doctest/output.txt."
.PHONY: coverage
-coverage: regen-api-doc
- $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+coverage: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
- "results in $(BUILDDIR)/coverage/python.txt."
+ "results in $(BUILD_DOC_DIR)/coverage/python.txt."
.PHONY: xml
-xml: regen-api-doc
- $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+xml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/xml
@echo
- @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+ @echo "Build finished. The XML files are in $(BUILD_DOC_DIR)/xml."
.PHONY: pseudoxml
-pseudoxml: regen-api-doc
- $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+pseudoxml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/pseudoxml
@echo
- @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+ @echo "Build finished. The pseudo-XML files are in $(BUILD_DOC_DIR)/pseudoxml."
.PHONY: dummy
-dummy: regen-api-doc
- $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
+dummy: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
diff --git a/test/doc/conf.py b/test/doc/conf.py
index aa8417149df..cec9ddee4b4 100644
--- a/test/doc/conf.py
+++ b/test/doc/conf.py
@@ -20,6 +20,7 @@ import os
import sys
sys.path.insert(0, os.path.abspath('..'))
+
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
diff --git a/test/framework.py b/test/framework.py
index 02ffb7add4f..5f75e0107e7 100644
--- a/test/framework.py
+++ b/test/framework.py
@@ -2,20 +2,20 @@
from abc import *
import os
-import sys
import subprocess
import unittest
import tempfile
+import time
import resource
from time import sleep
+from Queue import Queue
+from threading import Thread
from inspect import getdoc
-from hook import PollHook
+from hook import StepHook, PollHook
from vpp_pg_interface import VppPGInterface
from vpp_papi_provider import VppPapiProvider
-
from scapy.packet import Raw
-
-from logging import *
+from log import *
"""
Test framework module.
@@ -24,41 +24,6 @@ from logging import *
representing the results.
"""
-handler = StreamHandler(sys.stdout)
-getLogger().addHandler(handler)
-try:
- verbose = int(os.getenv("V", 0))
-except:
- verbose = 0
-# 40 = ERROR, 30 = WARNING, 20 = INFO, 10 = DEBUG, 0 = NOTSET (all messages)
-getLogger().setLevel(40 - 10 * verbose)
-getLogger("scapy.runtime").addHandler(handler)
-getLogger("scapy.runtime").setLevel(ERROR)
-
-# Static variables to store color formatting strings.
-#
-# These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
-# the color of the text to be printed in the terminal. Variable COLOR_RESET
-# is used to revert the text color to the default one.
-if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
- RED = '\033[91m'
- GREEN = '\033[92m'
- YELLOW = '\033[93m'
- LPURPLE = '\033[94m'
- COLOR_RESET = '\033[0m'
-else:
- RED = ''
- GREEN = ''
- YELLOW = ''
- LPURPLE = ''
- COLOR_RESET = ''
-
-
-""" @var formatting delimiter consisting of '=' characters """
-double_line_delim = '=' * 70
-""" @var formatting delimiter consisting of '-' characters """
-single_line_delim = '-' * 70
-
class _PacketInfo(object):
"""Private class to create packet info object.
@@ -84,6 +49,11 @@ class _PacketInfo(object):
data = None
+def pump_output(out, queue):
+ for line in iter(out.readline, b''):
+ queue.put(line)
+
+
class VppTestCase(unittest.TestCase):
"""
Subclass of the python unittest.TestCase class.
@@ -108,23 +78,85 @@ class VppTestCase(unittest.TestCase):
return cls.test_instance
@classmethod
+ def set_debug_flags(cls, d):
+ cls.debug_core = False
+ cls.debug_gdb = False
+ cls.debug_gdbserver = False
+ if d is None:
+ return
+ dl = d.lower()
+ if dl == "core":
+ if resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
+ # give a heads up if this is actually useless
+ cls.logger.critical("WARNING: core size limit is set 0, core "
+ "files will NOT be created")
+ cls.debug_core = True
+ elif dl == "gdb":
+ cls.debug_gdb = True
+ elif dl == "gdbserver":
+ cls.debug_gdbserver = True
+ else:
+ raise Exception("Unrecognized DEBUG option: '%s'" % d)
+
+ @classmethod
def setUpConstants(cls):
""" Set-up the test case class based on environment variables """
try:
- cls.interactive = True if int(os.getenv("I")) > 0 else False
+ s = os.getenv("STEP")
+ cls.step = True if s.lower() in ("y", "yes", "1") else False
except:
- cls.interactive = False
- if cls.interactive and resource.getrlimit(resource.RLIMIT_CORE)[0] <= 0:
- # give a heads up if this is actually useless
- critical("WARNING: core size limit is set 0, core files will NOT "
- "be created")
+ cls.step = False
+ try:
+ d = os.getenv("DEBUG")
+ except:
+ d = None
+ cls.set_debug_flags(d)
cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon",
"api-segment", "{", "prefix", cls.shm_prefix, "}"]
if cls.plugin_path is not None:
cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path])
- info("vpp_cmdline: %s" % cls.vpp_cmdline)
+ cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
+
+ @classmethod
+ def wait_for_enter(cls):
+ if cls.debug_gdbserver:
+ print(double_line_delim)
+ print("Spawned GDB server with PID: %d" % cls.vpp.pid)
+ elif cls.debug_gdb:
+ print(double_line_delim)
+ print("Spawned VPP with PID: %d" % cls.vpp.pid)
+ else:
+ cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
+ return
+ print(single_line_delim)
+ print("You can debug the VPP using e.g.:")
+ if cls.debug_gdbserver:
+ print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
+ print("Now is the time to attach a gdb by running the above "
+ "command, set up breakpoints etc. and then resume VPP from "
+ "within gdb by issuing the 'continue' command")
+ elif cls.debug_gdb:
+ print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
+ print("Now is the time to attach a gdb by running the above "
+ "command and set up breakpoints etc.")
+ print(single_line_delim)
+ raw_input("Press ENTER to continue running the testcase...")
+
+ @classmethod
+ def run_vpp(cls):
+ cmdline = cls.vpp_cmdline
+
+ if cls.debug_gdbserver:
+ cmdline = ['gdbserver', 'localhost:7777'] + cls.vpp_cmdline
+ cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
+
+ cls.vpp = subprocess.Popen(cmdline,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=1)
+ cls.wait_for_enter()
@classmethod
def setUpClass(cls):
@@ -132,37 +164,66 @@ class VppTestCase(unittest.TestCase):
Perform class setup before running the testcase
Remove shared memory files, start vpp and connect the vpp-api
"""
+ cls.logger = getLogger(cls.__name__)
cls.tempdir = tempfile.mkdtemp(
prefix='vpp-unittest-' + cls.__name__ + '-')
cls.shm_prefix = cls.tempdir.split("/")[-1]
os.chdir(cls.tempdir)
- info("Temporary dir is %s, shm prefix is %s",
- cls.tempdir, cls.shm_prefix)
+ cls.logger.info("Temporary dir is %s, shm prefix is %s",
+ cls.tempdir, cls.shm_prefix)
cls.setUpConstants()
cls.pg_streams = []
cls.packet_infos = {}
cls.verbose = 0
print(double_line_delim)
- print(YELLOW + getdoc(cls) + COLOR_RESET)
+ print(colorize(getdoc(cls), YELLOW))
print(double_line_delim)
# need to catch exceptions here because if we raise, then the cleanup
# doesn't get called and we might end with a zombie vpp
try:
- cls.vpp = subprocess.Popen(cls.vpp_cmdline, stderr=subprocess.PIPE)
- debug("Spawned VPP with PID: %d" % cls.vpp.pid)
+ cls.run_vpp()
cls.vpp_dead = False
cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix)
- cls.vapi.register_hook(PollHook(cls))
- cls.vapi.connect()
+ if cls.step:
+ cls.vapi.register_hook(StepHook(cls))
+ else:
+ cls.vapi.register_hook(PollHook(cls))
+ time.sleep(0.1)
+ try:
+ cls.vapi.connect()
+ except:
+ 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
+ cls.vpp_stdout_queue = Queue()
+ cls.vpp_stdout_reader_thread = Thread(
+ target=pump_output, args=(cls.vpp.stdout, cls.vpp_stdout_queue))
+ cls.vpp_stdout_reader_thread.start()
+ cls.vpp_stderr_queue = Queue()
+ cls.vpp_stderr_reader_thread = Thread(
+ target=pump_output, args=(cls.vpp.stderr, cls.vpp_stderr_queue))
+ cls.vpp_stderr_reader_thread.start()
except:
cls.vpp.terminate()
del cls.vpp
+ raise
@classmethod
def quit(cls):
"""
Disconnect vpp-api, kill vpp and cleanup shared memory files
"""
+ if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
+ cls.vpp.poll()
+ if cls.vpp.returncode is None:
+ print(double_line_delim)
+ print("VPP or GDB server is still running")
+ print(single_line_delim)
+ raw_input("When done debugging, press ENTER to kill the process"
+ " and finish running the testcase...")
+
if hasattr(cls, 'vpp'):
cls.vapi.disconnect()
cls.vpp.poll()
@@ -170,6 +231,29 @@ class VppTestCase(unittest.TestCase):
cls.vpp.terminate()
del cls.vpp
+ if hasattr(cls, 'vpp_stdout_queue'):
+ cls.logger.info(single_line_delim)
+ cls.logger.info('VPP output to stdout while running %s:',
+ cls.__name__)
+ cls.logger.info(single_line_delim)
+ f = open(cls.tempdir + '/vpp_stdout.txt', 'w')
+ while not cls.vpp_stdout_queue.empty():
+ line = cls.vpp_stdout_queue.get_nowait()
+ f.write(line)
+ cls.logger.info('VPP stdout: %s' % line.rstrip('\n'))
+
+ if hasattr(cls, 'vpp_stderr_queue'):
+ cls.logger.info(single_line_delim)
+ cls.logger.info('VPP output to stderr while running %s:',
+ cls.__name__)
+ cls.logger.info(single_line_delim)
+ f = open(cls.tempdir + '/vpp_stderr.txt', 'w')
+ while not cls.vpp_stderr_queue.empty():
+ line = cls.vpp_stderr_queue.get_nowait()
+ f.write(line)
+ cls.logger.info('VPP stderr: %s' % line.rstrip('\n'))
+ cls.logger.info(single_line_delim)
+
@classmethod
def tearDownClass(cls):
""" Perform final cleanup after running all tests in this test-case """
@@ -178,11 +262,11 @@ class VppTestCase(unittest.TestCase):
def tearDown(self):
""" Show various debug prints after each test """
if not self.vpp_dead:
- info(self.vapi.cli("show int"))
- info(self.vapi.cli("show trace"))
- info(self.vapi.cli("show hardware"))
- info(self.vapi.cli("show error"))
- info(self.vapi.cli("show run"))
+ self.logger.info(self.vapi.cli("show int"))
+ self.logger.info(self.vapi.cli("show trace"))
+ self.logger.info(self.vapi.cli("show hardware"))
+ self.logger.info(self.vapi.cli("show error"))
+ self.logger.info(self.vapi.cli("show run"))
def setUp(self):
""" Clear trace before running each test"""
@@ -392,7 +476,7 @@ class VppTestResult(unittest.TestResult):
"""
unittest.TestResult.addSuccess(self, test)
- self.result_string = GREEN + "OK" + COLOR_RESET
+ self.result_string = colorize("OK", GREEN)
def addSkip(self, test, reason):
"""
@@ -403,7 +487,7 @@ class VppTestResult(unittest.TestResult):
"""
unittest.TestResult.addSkip(self, test, reason)
- self.result_string = YELLOW + "SKIP" + COLOR_RESET
+ self.result_string = colorize("SKIP", YELLOW)
def addFailure(self, test, err):
"""
@@ -415,10 +499,10 @@ class VppTestResult(unittest.TestResult):
"""
unittest.TestResult.addFailure(self, test, err)
if hasattr(test, 'tempdir'):
- self.result_string = RED + "FAIL" + COLOR_RESET + \
+ self.result_string = colorize("FAIL", RED) + \
' [ temp dir used by test case: ' + test.tempdir + ' ]'
else:
- self.result_string = RED + "FAIL" + COLOR_RESET + ' [no temp dir]'
+ self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
def addError(self, test, err):
"""
@@ -430,10 +514,10 @@ class VppTestResult(unittest.TestResult):
"""
unittest.TestResult.addError(self, test, err)
if hasattr(test, 'tempdir'):
- self.result_string = RED + "ERROR" + COLOR_RESET + \
+ self.result_string = colorize("ERROR", RED) + \
' [ temp dir used by test case: ' + test.tempdir + ' ]'
else:
- self.result_string = RED + "ERROR" + COLOR_RESET + ' [no temp dir]'
+ self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
def getDescription(self, test):
"""
diff --git a/test/hook.py b/test/hook.py
index 9489aa9d8bb..3ae14737caf 100644
--- a/test/hook.py
+++ b/test/hook.py
@@ -1,7 +1,8 @@
import signal
import os
import pexpect
-from logging import *
+import traceback
+from log import *
class Hook(object):
@@ -9,6 +10,9 @@ class Hook(object):
Generic hooks before/after API/CLI calls
"""
+ def __init__(self, logger):
+ self.logger = logger
+
def before_api(self, api_name, api_args):
"""
Function called before API call
@@ -17,7 +21,8 @@ class Hook(object):
@param api_name: name of the API
@param api_args: tuple containing the API arguments
"""
- debug("API: %s (%s)" % (api_name, api_args))
+ self.logger.debug("API: %s (%s)" %
+ (api_name, api_args), extra={'color': RED})
def after_api(self, api_name, api_args):
"""
@@ -35,7 +40,7 @@ class Hook(object):
@param cli: CLI string
"""
- debug("CLI: %s" % (cli))
+ self.logger.debug("CLI: %s" % (cli), extra={'color': RED})
def after_cli(self, cli):
"""
@@ -54,6 +59,7 @@ class PollHook(Hook):
def __init__(self, testcase):
self.vpp_dead = False
self.testcase = testcase
+ self.logger = testcase.logger
def spawn_gdb(self, gdb_path, core_path):
gdb_cmdline = gdb_path + ' ' + self.testcase.vpp_bin + ' ' + core_path
@@ -74,11 +80,12 @@ class PollHook(Hook):
self.spawn_gdb(gdb_path, core_path)
return
else:
- error("Debugger '%s' does not exist or is not an executable.." %
- gdb_path)
+ self.logger.error(
+ "Debugger '%s' does not exist or is not an executable.." %
+ gdb_path)
- critical('core file present, debug with: gdb ' +
- self.testcase.vpp_bin + ' ' + core_path)
+ self.logger.critical('core file present, debug with: gdb ' +
+ self.testcase.vpp_bin + ' ' + core_path)
def poll_vpp(self):
"""
@@ -97,7 +104,7 @@ class PollHook(Hook):
msg = "VPP subprocess died unexpectedly with returncode %d [%s]" % (
self.testcase.vpp.returncode,
signaldict[abs(self.testcase.vpp.returncode)])
- critical(msg)
+ self.logger.critical(msg)
core_path = self.testcase.tempdir + '/core'
if os.path.isfile(core_path):
self.on_crash(core_path)
@@ -126,3 +133,92 @@ class PollHook(Hook):
"""
super(PollHook, self).after_cli(cli)
self.poll_vpp()
+
+
+class StepHook(PollHook):
+ """ Hook which requires user to press ENTER before doing any API/CLI """
+
+ def __init__(self, testcase):
+ self.skip_stack = None
+ self.skip_num = None
+ self.skip_count = 0
+ super(StepHook, self).__init__(testcase)
+
+ def skip(self):
+ if self.skip_stack is None:
+ return False
+ stack = traceback.extract_stack()
+ counter = 0
+ skip = True
+ for e in stack:
+ if counter > self.skip_num:
+ break
+ if e[0] != self.skip_stack[counter][0]:
+ skip = False
+ if e[1] != self.skip_stack[counter][1]:
+ skip = False
+ counter += 1
+ if skip:
+ self.skip_count += 1
+ return True
+ else:
+ print("%d API/CLI calls skipped in specified stack "
+ "frame" % self.skip_count)
+ self.skip_count = 0
+ self.skip_stack = None
+ self.skip_num = None
+ return False
+
+ def user_input(self):
+ print('number\tfunction\tfile\tcode')
+ counter = 0
+ stack = traceback.extract_stack()
+ for e in stack:
+ print('%02d.\t%s\t%s:%d\t[%s]' % (counter, e[2], e[0], e[1], e[3]))
+ counter += 1
+ print(single_line_delim)
+ print("You can enter a number of stack frame chosen from above")
+ print("Calls in/below that stack frame will be not be stepped anymore")
+ print(single_line_delim)
+ while True:
+ choice = raw_input("Enter your choice, if any, and press ENTER to "
+ "continue running the testcase...")
+ if choice == "":
+ choice = None
+ try:
+ if choice is not None:
+ num = int(choice)
+ except:
+ print("Invalid input")
+ continue
+ if choice is not None and (num < 0 or num >= len(stack)):
+ print("Invalid choice")
+ continue
+ break
+ if choice is not None:
+ self.skip_stack = stack
+ self.skip_num = num
+
+ def before_cli(self, cli):
+ """ Wait for ENTER before executing CLI """
+ if self.skip():
+ print("Skip pause before executing CLI: %s" % cli)
+ else:
+ print(double_line_delim)
+ print("Test paused before executing CLI: %s" % cli)
+ print(single_line_delim)
+ self.user_input()
+ super(StepHook, self).before_cli(cli)
+
+ def before_api(self, api_name, api_args):
+ """ Wait for ENTER before executing API """
+ if self.skip():
+ print("Skip pause before executing API: %s (%s)"
+ % (api_name, api_args))
+ else:
+ print(double_line_delim)
+ print("Test paused before executing API: %s (%s)"
+ % (api_name, api_args))
+ print(single_line_delim)
+ self.user_input()
+ super(StepHook, self).before_api(api_name, api_args)
diff --git a/test/log.py b/test/log.py
new file mode 100644
index 00000000000..38610b7ee8b
--- /dev/null
+++ b/test/log.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import logging
+
+""" @var formatting delimiter consisting of '=' characters """
+double_line_delim = '=' * 70
+""" @var formatting delimiter consisting of '-' characters """
+single_line_delim = '-' * 70
+
+
+def colorize(msg, color):
+ return color + msg + COLOR_RESET
+
+
+class ColorFormatter(logging.Formatter):
+
+ def init(self, fmt=None, datefmt=None):
+ super(ColorFormatter, self).__init__(fmt, datefmt)
+
+ def format(self, record):
+ message = super(ColorFormatter, self).format(record)
+ if hasattr(record, 'color'):
+ message = colorize(message, record.color)
+ return message
+
+handler = logging.StreamHandler(sys.stdout)
+handler.setFormatter(ColorFormatter())
+
+global_logger = logging.getLogger()
+global_logger.addHandler(handler)
+try:
+ verbose = int(os.getenv("V", 0))
+except:
+ verbose = 0
+
+# 40 = ERROR, 30 = WARNING, 20 = INFO, 10 = DEBUG, 0 = NOTSET (all messages)
+if verbose >= 2:
+ log_level = 10
+elif verbose == 1:
+ log_level = 20
+else:
+ log_level = 40
+
+scapy_logger = logging.getLogger("scapy.runtime")
+scapy_logger.setLevel(logging.ERROR)
+
+
+def getLogger(name):
+ logger = logging.getLogger(name)
+ logger.setLevel(log_level)
+ return logger
+
+# Static variables to store color formatting strings.
+#
+# These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
+# the color of the text to be printed in the terminal. Variable COLOR_RESET
+# is used to revert the text color to the default one.
+if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
+ RED = '\033[91m'
+ GREEN = '\033[92m'
+ YELLOW = '\033[93m'
+ LPURPLE = '\033[94m'
+ COLOR_RESET = '\033[0m'
+else:
+ RED = ''
+ GREEN = ''
+ YELLOW = ''
+ LPURPLE = ''
+ COLOR_RESET = ''
diff --git a/test/test_l2bd.py b/test/test_l2bd.py
index 0fced5d95ab..53e1ee053ab 100644
--- a/test/test_l2bd.py
+++ b/test/test_l2bd.py
@@ -189,7 +189,7 @@ class TestL2bd(VppTestCase):
MAC learning enabled
learn 100 MAC enries
3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of dot1ad
- in the first version)
+ in the first version)
2.sending l2 eth pkts between 3 interface
64B, 512B, 1518B, 9200B (ether_size)
diff --git a/test/test_l2xc.py b/test/test_l2xc.py
index d448a04d13c..35c7a818414 100644
--- a/test/test_l2xc.py
+++ b/test/test_l2xc.py
@@ -78,7 +78,6 @@ class TestL2xc(VppTestCase):
interfaces. Create host IPv4 address for every host MAC address too.
:param count: Number of hosts to create MAC and IPv4 addresses for.
- Type: int
"""
for pg_if in self.pg_interfaces:
# self.MY_MACS[i.sw_if_index] = []
@@ -151,12 +150,11 @@ class TestL2xc(VppTestCase):
""" L2XC test
Test scenario:
- 1.config
- 2 pairs of 2 interfaces, l2xconnected
-
- 2.sending l2 eth packets between 4 interfaces
- 64B, 512B, 1518B, 9018B (ether_size)
- burst of packets per interface
+ 1. config
+ 2 pairs of 2 interfaces, l2xconnected
+ 2. sending l2 eth packets between 4 interfaces
+ 64B, 512B, 1518B, 9018B (ether_size)
+ burst of packets per interface
"""
# Create incoming packet streams for packet-generator interfaces
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index 454f3edce3f..261a0f4aa4b 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -1,7 +1,19 @@
-import vpp_papi
+import os
from logging import error
from hook import Hook
+do_import = True
+try:
+ no_vpp_papi = os.getenv("NO_VPP_PAPI")
+ if no_vpp_papi == "1":
+ do_import = False
+except:
+ pass
+
+if do_import:
+ import vpp_papi
+
+
# from vnet/vnet/mpls/mpls_types.h
MPLS_IETF_MAX_LABEL = 0xfffff
MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
@@ -16,7 +28,7 @@ class VppPapiProvider(object):
"""
def __init__(self, name, shm_prefix):
- self.hook = Hook()
+ self.hook = Hook("vpp-papi-provider")
self.name = name
self.shm_prefix = shm_prefix
@@ -130,7 +142,6 @@ class VppPapiProvider(object):
default_router, max_interval, min_interval,
lifetime, initial_count, initial_interval, async))
-
def vxlan_add_del_tunnel(
self,
src_addr,
@@ -177,7 +188,7 @@ class VppPapiProvider(object):
:param rx_sw_if_index: Software interface index of Rx interface.
:param tx_sw_if_index: Software interface index of Tx interface.
:param enable: Create cross-connect if equal to 1, delete cross-connect
- if equal to 0.
+ if equal to 0.
:type rx_sw_if_index: str or int
:type rx_sw_if_index: str or int
:type enable: int