tests: update the test template and doc

- Update the template and documentation to use newer pytest fixutres for
setup and teardown, as well as skipping tests when the suite fails.

Signed-off-by: Christian Hopps <chopps@labn.net>
This commit is contained in:
Christian Hopps 2021-09-06 05:05:45 -04:00
parent 75ec7bdb5d
commit 9b6f04c07c
4 changed files with 125 additions and 69 deletions

View File

@ -983,22 +983,20 @@ Writing Tests
""""""""""""" """""""""""""
Test topologies should always be bootstrapped from Test topologies should always be bootstrapped from
:file:`tests/topotests/example-test/test_template.py` because it contains :file:`tests/topotests/example_test/test_template.py` because it contains
important boilerplate code that can't be avoided, like: important boilerplate code that can't be avoided, like:
Example: Example:
.. code:: py .. code:: py
# For all registered routers, load the zebra configuration file # For all routers arrange for:
CWD = os.path.dirname(os.path.realpath(__file__)) # - starting zebra using config file from <rtrname>/zebra.conf
# - starting ospfd using an empty config file.
for rname, router in router_list.items(): for rname, router in router_list.items():
router.load_config( router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
TopoRouter.RD_ZEBRA, router.load_config(TopoRouter.RD_OSPF)
os.path.join(CWD, '{}/zebra.conf'.format(rname))
)
# os.path.join() joins the CWD string with arguments adding the necessary
# slashes ('/'). Arguments must not begin with '/'.
- The topology definition or build function - The topology definition or build function
@ -1013,27 +1011,31 @@ Example:
# topology build code # topology build code
... ...
- pytest ``setup_module()`` and ``teardown_module()`` to start the topology - pytest setup/teardown fixture to start the topology and supply `tgen` argument
to tests.
.. code:: py .. code:: py
def setup_module(module):
@pytest.fixture(scope="module")
def tgen(request):
"Setup/Teardown the environment and provide tgen argument to tests"
tgen = Topogen(topodef, module.__name__) tgen = Topogen(topodef, module.__name__)
# or # or
tgen = Topogen(build_topo, module.__name__) tgen = Topogen(build_topo, module.__name__)
tgen.start_topology('debug') ...
def teardown_module(_m): # Start and configure the router daemons
tgen = get_topogen() tgen.start_router()
# Provide tgen as argument to each test function
yield tgen
# Teardown after last test runs
tgen.stop_topology() tgen.stop_topology()
- ``__main__`` initialization code (to support running the script directly)
.. code:: py
if __name__ == '__main__':
sys.exit(pytest.main(["-s"]))
Requirements: Requirements:

View File

@ -0,0 +1,8 @@
interface r1-eth0
ip address 192.168.1.1/24
interface r1-eth1
ip address 192.168.2.1/24
interface r1-eth2
ip address 192.168.3.1/24

View File

@ -0,0 +1,4 @@
interface r2-eth0
ip address 192.168.1.2/24
interface r2-eth1
ip address 192.168.3.2/24

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# #
# <template>.py # <template>.py
# Part of NetDEF Topology Tests # Part of NetDEF Topology Tests
@ -29,54 +29,69 @@
import sys import sys
import pytest import pytest
# Import topogen and topotest helpers from lib.topogen import Topogen, TopoRouter
from lib.topogen import Topogen, TopoRouter, get_topogen from lib.topolog import logger
# TODO: select markers based on daemons used during test # TODO: select markers based on daemons used during test
# pytest module level markers # pytest module level markers
"""
pytestmark = pytest.mark.bfdd # single marker
pytestmark = [ pytestmark = [
pytest.mark.bgpd, # pytest.mark.babeld,
# pytest.mark.bfdd,
# pytest.mark.bgpd,
# pytest.mark.eigrpd,
# pytest.mark.isisd,
# pytest.mark.ldpd,
# pytest.mark.nhrpd,
# pytest.mark.ospf6d,
pytest.mark.ospfd, pytest.mark.ospfd,
pytest.mark.ospf6d # pytest.mark.pathd,
] # multiple markers # pytest.mark.pbrd,
""" # pytest.mark.pimd,
# pytest.mark.ripd,
# pytest.mark.ripngd,
# pytest.mark.sharpd,
# pytest.mark.staticd,
# pytest.mark.vrrpd,
]
# Function we pass to Topogen to create the topology
def build_topo(tgen): def build_topo(tgen):
"Build function" "Build function"
# Create 2 routers # Create 2 routers
for routern in range(1, 3): r1 = tgen.add_router("r1")
tgen.add_router("r{}".format(routern)) r2 = tgen.add_router("r2")
# Create a switch with just one router connected to it to simulate a # Create a p2p connection between r1 and r2
# empty network. tgen.add_link(r1, r2)
# Create a switch with one router connected to it to simulate a empty network.
switch = tgen.add_switch("s1") switch = tgen.add_switch("s1")
switch.add_link(tgen.gears["r1"]) switch.add_link(r1)
# Create a connection between r1 and r2 # Create a p2p connection between r1 and r2
switch = tgen.add_switch("s2") switch = tgen.add_switch("s2")
switch.add_link(tgen.gears["r1"]) switch.add_link(r1)
switch.add_link(tgen.gears["r2"]) switch.add_link(r2)
def setup_module(mod): # New form of setup/teardown using pytest fixture
"Sets up the pytest environment" @pytest.fixture(scope="module")
def tgen(request):
"Setup/Teardown the environment and provide tgen argument to tests"
# This function initiates the topology build with Topogen... # This function initiates the topology build with Topogen...
tgen = Topogen(build_topo, mod.__name__) tgen = Topogen(build_topo, request.module.__name__)
# The basic topology above could also have be more easily specified using a # A basic topology similar to the above could also have be more easily specified
# dictionary, remove the build_topo function and use the following instead: # using a # dictionary, remove the build_topo function and use the following
# instead:
# #
# topodef = { # topodef = {
# "s1": "r1" # "s1": "r1"
# "s2": ("r1", "r2") # "s2": ("r1", "r2")
# } # }
# tgen = Topogen(topodef, mod.__name__) # tgen = Topogen(topodef, request.module.__name__)
# ... and here it calls initialization functions. # ... and here it calls initialization functions.
tgen.start_topology() tgen.start_topology()
@ -84,42 +99,69 @@ def setup_module(mod):
# This is a sample of configuration loading. # This is a sample of configuration loading.
router_list = tgen.routers() router_list = tgen.routers()
# For all registred routers, load the zebra configuration file # For all routers arrange for:
# CWD = os.path.dirname(os.path.realpath(__file__)) # - starting zebra using config file from <rtrname>/zebra.conf
# - starting ospfd using an empty config file.
for rname, router in router_list.items(): for rname, router in router_list.items():
router.load_config( router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
TopoRouter.RD_ZEBRA, router.load_config(TopoRouter.RD_OSPF)
# Uncomment next line to load configuration from ./router/zebra.conf
# os.path.join(CWD, '{}/zebra.conf'.format(rname))
)
# After loading the configurations, this function loads configured daemons. # Start and configure the router daemons
tgen.start_router() tgen.start_router()
# Provide tgen as argument to each test function
yield tgen
def teardown_module(mod): # Teardown after last test runs
"Teardown the pytest environment"
tgen = get_topogen()
# This function tears down the whole topology.
tgen.stop_topology() tgen.stop_topology()
def test_call_cli(): # Fixture that executes before each test
"Dummy test that just calls tgen.cli() so we can interact with the build." @pytest.fixture(autouse=True)
tgen = get_topogen() def skip_on_failure(tgen):
# Don't run this test if we have any failure.
if tgen.routers_have_failure(): if tgen.routers_have_failure():
pytest.skip(tgen.errors) pytest.skip("skipped because of previous test failure")
# logger.info("calling CLI")
# tgen.cli() # ===================
# The tests functions
# ===================
def test_get_version(tgen):
"Test the logs the FRR version"
r1 = tgen.gears["r1"]
version = r1.vtysh_cmd("show version")
logger.info("FRR version is: " + version)
def test_connectivity(tgen):
"Test the logs the FRR version"
r1 = tgen.gears["r1"]
r2 = tgen.gears["r2"]
output = r1.cmd_raises("ping -c1 192.168.1.2")
output = r2.cmd_raises("ping -c1 192.168.3.1")
@pytest.mark.xfail
def test_expect_failure(tgen):
"A test that is current expected to fail but should be fixed"
assert False, "Example of temporary expected failure that will eventually be fixed"
@pytest.mark.skip
def test_will_be_skipped(tgen):
"A test that will be skipped"
assert False
# Memory leak test template # Memory leak test template
def test_memory_leak(): def test_memory_leak(tgen):
"Run the memory leak test and report results." "Run the memory leak test and report results."
tgen = get_topogen()
if not tgen.is_memleak_enabled(): if not tgen.is_memleak_enabled():
pytest.skip("Memory leak test/report is disabled") pytest.skip("Memory leak test/report is disabled")