Testsuite Primer

Over the past two major releases firewalld has seen vast improvements to its testsuite. This post will discuss how to run the testsuite, how to debug a failure, and finally we’ll go through an exercise of adding a new test case. The main target is for current and future contributors to firewalld. However, since firewalld’s testsuite utilizes autotest some of the knowledge gained here may also carry over to other projects.

Running the testsuite

Running the testsuite is very simple. To start build the code just as you would if installing from source.

$ git clone https://github.com/firewalld/firewalld
$ cd firewalld
$ ./autogen.sh
$ ./configure
$ make

Then run the testsuite using the check make target. This needs to be run as root.

# make check

These tests are non-destructive to the host running them and there is no need to stop the host’s running firewalld instance. They are run inside of network namespaces (containers) which allows numerous benefits; reliability, non-destructive to the host, and they can be run in parallel.

At the time of writing firewalld has 112 test groups - most of which include multiple tests. As such, the testsuite takes a long time to run. The good news is you can speed things up by running them in parallel.

To run test groups in parallel pass -j4 to autotest via the TESTSUITEFLAGS variable.

# make check TESTSUITEFLAGS="-j4"

Running the testsuite against the host installed firewalld

Firewalld’s testsuite also provides an installcheck make target. This is useful for running tests against the hosts installed version of firewalld. The check target runs against the executables built in the source tree. This is very important for development as it allows you to run a newer testsuite against an older version of firewalld. As we’ll find later on in this post, this can be used to leverage test driven development.

To run the testsuite against the installed firewalld

# make installcheck

Debugging a failed test case

Inevitably a test will fail. Whether it’s a bug in firewalld, a permission issue, or a compatibility issue, you’ll have to debug the problem. There are a couple of ways to inspect a failure. The most straight forward way is to view the testsuite log.

To view the testsuite log for failed test number 42

# vi ./src/tests/testsuite.dir/042/testsuite.log

Alternatively, you can enable the verbose flag to cause the testsuite to dump to standard output.

# make check TESTSUITEFLAGS="42 -v"

To enable firewalld’s debug output, you can use the -d flag.

# make check TESTSUITEFLAGS="42 -v -d"

The test numbers passed to TESTSUITEFLAGS are very flexible. It will accept individual numbers or ranges.

For example this is also valid

# make check TESTSUITEFLAGS="42 1-5 110 13-14 17"

Writing a new test case

In this example we’ll follow a test driven development style to fix a real world firewalld bug. We’ll be using Red Hat bugzilla 1404076 for this exercise. This bug occurs when a port range is opened using the --add-port option. --query-port fails to return the expected result if querying a single port within the range.

Basic test layout

All of the tests follow the same basic layout. Since this is a new test case in response to an existing bug we’ll add it to the set of regression tests in src/tests/regression.

At the absolute minimum your new test should look like this

FWD_START_TEST([test description])

...
FWD_CHECK([some command])
...

FWD_END_TEST

The testsuite makes heavy use of m4 macros (FWD_START_TEST, FWD_CHECK, etc.) to simplify test creation. m4 is a macro language which autotest uses to generate the testsuite script.

A consequence of the macro magic is most tests will be run multiple times. Once for each firewall backend; nftables and iptables. This ensures that both backends are tested equally.

The new test case

We start by creating a new test that reproduces the issue from the bugzilla report.

Here is our new test case
src/tests/regression/rhbz1404076.at:

FWD_START_TEST([query single port added with range])

dnl add a set of ports by range, then query a specific port inside that range.
FWD_CHECK([-q --add-port=8080-8090/tcp])
FWD_CHECK([-q --query-port=8085/tcp])
FWD_CHECK([-q --query-port=webcache/tcp]) dnl named port
FWD_CHECK([-q --query-port=8091/tcp], 1) dnl negative test
FWD_CHECK([-q --query-port=8085/udp], 1) dnl negative test

dnl same thing, but for permanent configuration.
FWD_CHECK([-q --permanent --add-port=8080-8090/tcp])
FWD_CHECK([-q --permanent --query-port=8085/tcp])
FWD_CHECK([-q --permanent --query-port=webcache/tcp]) dnl named port
FWD_CHECK([-q --permanent --query-port=8091/tcp], 1) dnl negative test
FWD_CHECK([-q --permanent --query-port=8085/udp], 1) dnl negative test

FWD_END_TEST

Note: dnl is m4’s way of starting a comment.

This test is fairly exhaustive by including negative tests and named ports. This gives us even more confidence in our code changes.

To attach the new test case to the testsuite we need to append the new test to src/tests/regression.at.

$ vi src/tests/regression.at
[...]
m4_include([regression/rhbz1404076.at])

This new test case can be found upstream on github. The commit is 3fb707228ced ("tests/regression: add coverage for rhbz 1404076").

Verifying the new test reproduces the issue

Now that the test has been added you can verify it reproduces the issue by running it against the host’s firewalld using the installcheck make target.

# make installcheck
[...]
regression (nftables)

[...]
 67: query single port added with range              FAILED (rhbz1404076.at:5)
[...]
regression (iptables)

[..]
110: query single port added with range              FAILED (rhbz1404076.at:5)

Here we see the test case failed as expected for both firewall backends.

Fix the bug

The next step is to make code changes to fix the bug. This is out of scope for this post, but for those interested the fix can be found on github. The commit is 2925de324443 ("ports: allow querying a single added by range").

Verify the fix against the in-tree source code

Now that the test case has been written and code changes have been made we can run the test case again, but this time against the source tree version. This will verify our code changes fix the bug. Be sure to use the check make target this time.

# make check TESTSUITEFLAGS="67 110"

Once your new test case passes you should run the whole testsuite again to verify you didn’t introduce a regression.

# make check

If that was successful, then you’re ready to submit your changes upstream with a pull request! Once your pull request is submitted the testsuite will automatically be run again by travis-ci against multiple versions of python.

Conclusions

This post has shown how easy it is to get started with the firewalld testsuite. We hope it will encourage contributions from others while simultaneously improving firewalld’s quality. Additionally we hope it turns some users into active contributors!