se, expected", [ (1, "Generic Endpoint (Class I)"), (2, "Media Endpoint (Class II)"), (3, "Communication Device Endpoint (Class III)"), (4, "Network Connectivity Device")]) def test_med_devicetype(lldpd, lldpcli, namespaces, links, classe, expected): links(namespaces(1), namespaces(2)) with namespaces(1): lldpd("-r") with namespaces(2): lldpd("-M", str(classe)) with namespaces(1): out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") assert out['lldp.eth0.lldp-med.device-type'] == expected
First, the test will be executed only if
lldpd was compiled with
LLDP-MED support. Second, the test is parametrized. We will execute
four distinct tests, one for each role that
lldpd should be able to
take as an LLDP-MED-enabled endpoint.
The signature of the test has four parameters that are not covered by
links. They are fixtures. A lot of magic happen in those to keep
the actual tests short:
lldpdis a factory to spawn an instance of
lldpd. When called, it will setup the current namespace (setting up the chroot, creating the user and group for privilege separation, replacing some files to be distribution-agnostic, …), then call
lldpdwith the additional parameters provided. The output is recorded and added to the test report in case of failure. The module also contains the creation of the
pytest.config.lldpdobject that is used to record the features supported by
lldpdand skip non-matching tests. You can read
fixtures/programs.pyfor more details.
lldpcliis also a factory, but it spawns instances of
lldpcli, the client to query
lldpd. Moreover, it will parse the output in a dictionary to reduce boilerplate.
namespacesis one of the most interesting pieces. It is a factory for Linux namespaces. It will spawn a new namespace or refer to an existing one. It is possible to switch from one namespace to another (with
with) as they are contexts. Behind the scene, the factory maintains the appropriate file descriptors for each namespace and switch to them with
setns(). Once the test is done, everything is wipped out as the file descriptors are garbage collected. You can read
fixtures/namespaces.pyfor more details. It is quite reusable in other projects2.
linkscontains helpers to handle network interfaces: creation of virtual ethernet link between namespaces, creation of bridges, bonds and VLAN, etc. It relies on the pyroute2 module. You can read
fixtures/network.pyfor more details.
You can see an example of a test run on the
Travis build for 0.9.2. Since each test is correctly isolated,
it’s possible to run parallel tests with
pytest -n 10 --boxed. To
catch even more bugs, both the address sanitizer (ASAN) and the
undefined behavior sanitizer (UBSAN) are enabled. In case of a
problem, notably a memory leak, the faulty program will exit with a
non-zero exit code and the associated test will fail.
There are three main limitations in the use of namespaces with this fixture. First, when creating a user namespace, only root is mapped to the current user. With lldpd, we have two users (
_lldpd). Therefore, the tests have to run as root. The second limitation is with the PID namespace. It’s not possible for a process to switch from one PID namespace to another. When you call
setns()on a PID namespace, only children of the current process will be in the new PID namespace. The PID namespace is convenient to ensure everyone gets killed once the tests are terminated but you must keep in mind that
/procmust be mounted in children only. The third limitation is that, for some namespaces (PID and user), all threads of a process must be part of the same namespace. Therefore, don’t use threads in tests. Use
multiprocessingmodule instead. ↩