From 8161b823d77f9d89ffabd47444a83d693f74c515 Mon Sep 17 00:00:00 2001 From: "Alexander V. Chernikov" Date: Sun, 1 Jan 2023 14:35:41 +0000 Subject: [PATCH] testing: add python test examples Simplify the adoption of python tests by proving some examples, utilising commonly-used patterns. Differential Revision: https://reviews.freebsd.org/D37902 Reviewed by: asomers MFC after: 2 weeks --- etc/mtree/BSD.tests.dist | 2 + tests/Makefile | 1 + tests/examples/Makefile | 10 ++ tests/examples/test_examples.py | 198 ++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 tests/examples/Makefile create mode 100644 tests/examples/test_examples.py diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index 7724138d3298..f473995400ae 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -265,6 +265,8 @@ rc.d .. .. + examples + .. games .. gnu diff --git a/tests/Makefile b/tests/Makefile index b406b8dc6c17..47fc9488f772 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -9,6 +9,7 @@ ${PACKAGE}FILES+= README __init__.py conftest.py KYUAFILE= yes SUBDIR+= etc +SUBDIR+= examples SUBDIR+= sys SUBDIR+= atf_python diff --git a/tests/examples/Makefile b/tests/examples/Makefile new file mode 100644 index 000000000000..7a5d84a98dfe --- /dev/null +++ b/tests/examples/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/examples + +ATF_TESTS_PYTEST += test_examples.py + +.include + diff --git a/tests/examples/test_examples.py b/tests/examples/test_examples.py new file mode 100644 index 000000000000..13fdcc420f0e --- /dev/null +++ b/tests/examples/test_examples.py @@ -0,0 +1,198 @@ +import pytest +from atf_python.utils import BaseTest +from atf_python.sys.net.tools import ToolsHelper +from atf_python.sys.net.vnet import SingleVnetTestTemplate +from atf_python.sys.net.vnet import VnetTestTemplate +from atf_python.sys.net.vnet import VnetInstance + +import errno +import socket +import subprocess +import json + +from typing import List + + +# Test classes should be inherited +# from the BaseTest + + +class TestExampleSimplest(BaseTest): + @pytest.mark.skip(reason="comment me to run the test") + def test_one(self): + assert ToolsHelper.get_output("uname -s").strip() == "FreeBSD" + + +class TestExampleSimple(BaseTest): + # List of required kernel modules (kldstat -v) + # that needs to be present for the tests to run + REQUIRED_MODULES = ["null"] + + @pytest.mark.skip(reason="comment me to run the test") + def test_one(self): + """Optional test description + This and the following lines are not propagated + to the ATF test description. + """ + pass + + @pytest.mark.skip(reason="comment me to run the test") + # List of all requirements supported by an atf runner + # See atf-test-case(4) for the detailed description + @pytest.mark.require_user("root") + @pytest.mark.require_arch(["amd64", "i386"]) + @pytest.mark.require_files(["/path/file1", "/path/file2"]) + @pytest.mark.require_machine(["amd64", "i386"]) + @pytest.mark.require_memory("200M") + @pytest.mark.require_progs(["prog1", "prog2"]) + @pytest.mark.timeout(300) + def test_two(self): + pass + + @pytest.mark.skip(reason="comment me to run the test") + @pytest.mark.require_user("unprivileged") + def test_syscall_failure(self): + s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + with pytest.raises(OSError) as exc_info: + s.bind(("::1", 42)) + assert exc_info.value.errno == errno.EACCES + + @pytest.mark.skip(reason="comment me to run the test") + @pytest.mark.parametrize( + "family_tuple", + [ + pytest.param([socket.AF_INET, None], id="AF_INET"), + pytest.param([socket.AF_INET6, None], id="AF_INET6"), + pytest.param([39, errno.EAFNOSUPPORT], id="FAMILY_39"), + ], + ) + def test_parametrize(self, family_tuple): + family, error = family_tuple + try: + s = socket.socket(family, socket.SOCK_STREAM) + s.close() + except OSError as e: + if error is None or error != e.errno: + raise + + # @pytest.mark.skip(reason="comment me to run the test") + def test_with_cleanup(self): + print("TEST BODY") + + def cleanup_test_with_cleanup(self, test_id): + print("CLEANUP HANDLER") + + +class TestVnetSimple(SingleVnetTestTemplate): + """ + SingleVnetTestTemplate creates a topology with a single + vnet and a single epair between this vnet and the host system. + Additionally, lo0 interface is created inside the vnet. + + Both vnets and interfaces are aliased as vnetX and ifY. + They can be accessed via maps: + vnet: VnetInstance = self.vnet_map["vnet1"] + iface: VnetInterface = vnet.iface_alias_map["if1"] + + All prefixes from IPV4_PREFIXES and IPV6_PREFIXES are + assigned to the single epair interface inside the jail. + + One can rely on the fact that there are no IPv6 prefixes + in the tentative state when the test method is called. + """ + + IPV6_PREFIXES: List[str] = ["2001:db8::1/64"] + IPV4_PREFIXES: List[str] = ["192.0.2.1/24"] + + def setup_method(self, method): + """ + Optional pre-setup for all of the tests inside the class + """ + # Code to run before vnet setup + # + super().setup_method(method) + # + # Code to run after vnet setup + # Executed inside the vnet + + @pytest.mark.skip(reason="comment me to run the test") + @pytest.mark.require_user("root") + def test_ping(self): + assert subprocess.run("ping -c1 192.0.2.1".split()).returncode == 0 + assert subprocess.run("ping -c1 2001:db8::1".split()).returncode == 0 + + @pytest.mark.skip(reason="comment me to run the test") + def test_topology(self): + vnet = self.vnet_map["vnet1"] + iface = vnet.iface_alias_map["if1"] + print("Iface {} inside vnet {}".format(iface.name, vnet.name)) + + +class TestVnetDual1(VnetTestTemplate): + """ + VnetTestTemplate creates topology described in the self.TOPOLOGY + + Each vnet (except vnet1) can have a handler function, named + vnetX_handler. This function will be run in a separate process + inside vnetX jail. The framework automatically creates a pipe + to allow communication between the main test and the vnet handler. + + This topology contains 2 VNETs connected with 2 epairs: + + [ VNET1 ] [ VNET2 ] + if1(epair) 2001:db8:a::1/64 <-> 2001:db8:a::2/64 if1(epair) + if2(epair) 2001:db8:b::1/64 <-> 2001:db8:b::2/64 if2(epair) + lo0 lo0 + + """ + + TOPOLOGY = { + "vnet1": {"ifaces": ["if1", "if2"]}, + "vnet2": {"ifaces": ["if1", "if2"]}, + "if1": {"prefixes6": [("2001:db8:a::1/64", "2001:db8:a::2/64")]}, + "if2": {"prefixes6": [("2001:db8:b::1/64", "2001:db8:b::2/64")]}, + } + + def _get_iface_stat(self, os_ifname: str): + out = ToolsHelper.get_output( + "{} -I {} --libxo json".format(ToolsHelper.NETSTAT_PATH, os_ifname) + ) + js = json.loads(out) + return js["statistics"]["interface"][0] + + def vnet2_handler(self, vnet: VnetInstance): + """ + Test handler that runs in the vnet2 as a separate process. + + This handler receives an interface name, fetches received/sent packets + and returns this data back to the parent process. + """ + while True: + # receives 'ifX' with an infinite timeout + iface_alias = self.wait_object(vnet.pipe, None) + # Translates topology interface name to the actual OS-assigned name + os_ifname = vnet.iface_alias_map[iface_alias].name + self.send_object(vnet.pipe, self._get_iface_stat(os_ifname)) + + @pytest.mark.skip(reason="comment me to run the test") + @pytest.mark.require_user("root") + def test_ifstat(self): + """Checks that RX interface packets are properly accounted for""" + second_vnet = self.vnet_map["vnet2"] + pipe = second_vnet.pipe + + # Ping neighbor IP on if1 and verify that the counter was incremented + self.send_object(pipe, "if1") + old_stat = self.wait_object(pipe) + assert subprocess.run("ping -c5 2001:db8:a::2".split()).returncode == 0 + self.send_object(pipe, "if1") + new_stat = self.wait_object(pipe) + assert new_stat["received-packets"] - old_stat["received-packets"] >= 5 + + # Ping neighbor IP on if2 and verify that the counter was incremented + self.send_object(pipe, "if2") + old_stat = self.wait_object(pipe) + assert subprocess.run("ping -c5 2001:db8:b::2".split()).returncode == 0 + self.send_object(pipe, "if2") + new_stat = self.wait_object(pipe) + assert new_stat["received-packets"] - old_stat["received-packets"] >= 5