Test runner in C
Find a file
2024-04-17 14:06:57 -04:00
Makefile Initial commit 2024-04-17 13:50:22 -04:00
README.md Add subtitle to README 2024-04-17 14:06:57 -04:00
runner.c Initial commit 2024-04-17 13:50:22 -04:00

A brütally simple test harness

Contents

Rationale

The purpose of this package is both practical and as a demonstration for just how simple a test harness can be, in order to encourage both more testing in C code as well as the use of simple tools. As such, the Roadrunner test runner consists of a test runner written in a single file of highly portable C, totaling 43 lines, and a simple, POSIX compatible Makefile which works with both GNU and BSD make.

Most "modern" programming languages have a built in testing framework and this is often a selling point, as C just leaves it to the programmer to pick from the thousands of different ways to not only build but also test their code, or roll their own. In this guy's opinion, 99.99% of all modern build systems are overkill and the vast majority actually introduce corner cases and bugs into the process, making things like cross compilation a serious PITA. If you stick with make, without any wrappers around it, you can pretty easily build even complex software in a way that doesn't require 15k line configure scripts, keeping up to date with changes in Python (WTF, why use Python to build C?) and that will be friendly to cross compilation. This simplistic test harness is a natural outgrowth of that mental model of building software (at least for software written in C or Fortran).

The original version of this project was written entirely in POSIX make, but due to the variations in POSIX shell behavior, especially in regards to the variety of ways that both the built in echo command and /bin/echo screw up sending terminal escape sequences, something more robust and portable was desirable. There were simply too many corner cases to be bothered to find them all.

Requirements

  • a functional C compiler
  • a POSIX compatible make program (GNU and BSD make are both fine)
  • your code built as a static library

Usage

The best way to use this is to stuff it into a test subdirectory and call it via make -C test, or invoke it from your project's Makefile as a sub-make. If you are using something other than plain make to build your project then you probably don't want this, but also, why? That said, let's not get sidetracked.

The example Makefile also goes into the test subdirectory, and you will want to add the name of the library that you are testing on the appropriate line as well as the names of all of the tests that you wish to run. Add those to the tests array.

tests += my_test

For each member of tests there should be a corresponding file <testname>.c which will be compiled when you run make in this directory, along with the small helper program runner.c. Make will then invoke ./runner with the list of tests as it's arguments. The runner program will keep track of successes and failures, as well as skipped tests, giving colored output and a nice summary at the end. If there are no failures the program exits with a value of 0, otherwise the number of failures is returned.

Your library will have to be built as a static library and statically linked to each test program. All you have to do (after building the static library) is add it to the $(LDLIBS) array in the Makefile.

Cross compilation

It would be entirely possible to extend this harness to work for cross compilation, substituting $(HOSTCC) for $(CC) when building runner.c and calling qemu in user mode to run the binary tests. However, this is out of scope for Roadrunner.

Writing Tests

Return values

The return values for each test shouuld follow these conventions.

  • 0 for success
  • 255 or -1 indicates this test was skipped
  • any other value indicates failure

Skips

If your test requires elevated privileges, you can simply test for uid == 0 and then bail with code 255 if the runner is run by a different user. Similarly, if your test needs the network then you can test programmatically for a working connection and bail with 255 if it isn't available.

Don't use assert

The assert function from assert.h will not cause your test to fail, because it simply aborts the program after printing a message to stderr. Instead, check for proper results and return an exit code if the test fails. You can also print error messages in your tests, which runner will capture, assume it means failure, and print to stderr in the terminal session.