Implementing `make check` or `make test`
Make a test runner script that takes a test name and infers the input filename, output filename and smaple data from that:
#!/bin/sh
set -e
jscheme < $1.in > $1.out 2>&1
diff -q $1.out $1.cmp
Then, in your Makefile
:
TESTS := expr unrecognised
.PHONY: test
test:
for test in $(TESTS); do bash test-runner.sh $$test || exit 1; done
You could also try implementing something like automake
's simple 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):
SHELL := /bin/bash
all-tests := $(addsuffix .test, $(basename $(wildcard *.test-in)))
.PHONY : test all %.test
BC := /usr/bin/bc
test : $(all-tests)
%.test : %.test-in %.test-cmp $(BC)
@$(BC) <$< 2>&1 | diff -q $(word 2, $?) - >/dev/null || \
(echo "Test $@ failed" && exit 1)
all : test
@echo "Success, all tests passed."
The solution directly addresses your original questions:
- The placeholders you're looking for are
$<
and$(word 2, $?)
corresponding to the prerequisites%.test-in
and%.test-cmp
respectively. Contrary to the @reinierpost comment temp files are not needed. - The diff message is hidden and replaced using
echo
. - The makefile should be invoked with
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.
What I ended up with looks like this:
TESTS = whitespace list boolean character \
literal fixnum string symbol quote
.PHONY: clean test
test: $(JSCHEME)
for t in $(TESTS); do \
$(JSCHEME) < test/$$t.ss > test/$$t.out 2>&1; \
diff test/$$t.out test/$$t.cmp > /dev/null || \
echo Test $$t failed >&2; \
done
It’s based on Jack Kelly’s idea, with Jonathan Leffler’s tip included.