How can I implement a simple regression test framework with Make? (I’m using GNU Make, if that matters.)
My current makefile looks something like this (edited for simplicity):
OBJS = jscheme.o utility.o model.o read.o eval.o print.o
%.o : %.c jscheme.h
gcc -c -o $@ $<
jscheme : $(OBJS)
gcc -o $@ $(OBJS)
.PHONY : clean
clean :
-rm -f jscheme $(OBJS)
I’d like to have a set of regression tests, e.g., expr.in
testing a “good” expression & unrecognized.in
testing a “bad” one, with expr.cmp
& unrecognized.cmp
being the expected output for each. Manual testing would look like this:
$ jscheme < expr.in > expr.out 2>&1
$ jscheme < unrecognized.in > unrecognized.out 2>&1
$ diff -q expr.out expr.cmp # identical
$ diff -q unrecognized.out unrecognized.cmp
Files unrecognized.out and unrecognized.cmp differ
I thought to add a set of rules to the makefile looking something like this:
TESTS = expr.test unrecognized.test
.PHONY test $(TESTS)
test : $(TESTS)
%.test : jscheme %.in %.cmp
jscheme < [something.in] > [something.out] 2>&1
diff -q [something.out] [something.cmp]
My questions:
• What do I put in the [something] placeholders?
• Is there a way to replace the message from diff
with a message saying, “Test expr
failed”?
Make a test runner script that takes a test name and infers the input filename, output filename and smaple data from that:
Then, in your
Makefile
:You could also try implementing something like
automake
's simple test framework.What I ended up with looks like this:
It’s based on Jack Kelly’s idea, with Jonathan Leffler’s tip included.
I'll address just your question about diff. You can do:
although you might want to use cmp instead of diff.
On another note, you might find it helpful to go ahead and take the plunge and use automake. Your Makefile.am (in its entirety) will look like:
and you will get a whole lot of really nice targets for free, including a pretty full-featured test framework.
Your original approach, as stated in the question, is best. Each of your tests is in the form of a pair of expected inputs and outputs. Make is quite capable of iterating through these and running the tests; there is no need to use a shell
for
loop. In fact, by doing this you are losing the opportunity to run your tests in parallel, and are creating extra work for yourself in order to clean up temp files (which are not needed).Here's a solution (using bc as an example):
The solution directly addresses your original questions:
$<
and$(word 2, $?)
corresponding to the prerequisites%.test-in
and%.test-cmp
respectively. Contrary to the @reinierpost comment temp files are not needed.echo
.make -k
to run all the tests regardless of whether an individual test fails or succeeds.make -k all
will only run if all the tests succeed.We avoid enumerating each test manually when defining the
all-tests
variable by leveraging the file naming convention (*.test-in
) and the GNU make functions for file names. As a bonus this means the solution scales to tens of thousands of tests out of the box, as the length of variables is unlimited in GNU make. This is better than the shell based solution which will fall over once you hit the operating system command line limit.