I've been working on a Makefile to automate Unit Tests in C. The main goal is that for any given project that follows the structure of a source and a test directory, make
compiles the function and make test
compiles and runs the tests.
While the make
command works just fine, make test
doesn't. In the configuration down under, the dependency of test calls the %.exe
target, and not the test_%.exe
.
How do I make a succesfull call for any test target, as long as it begins with "test_"?
I've tried to make a wildcard for it but it didn't work. This can be of still lack of knowledge but I hope to find some help here :)
CC := gcc
CFLAGS := -Wall -Werror
CSYNTAX := -fsyntax-only -Wall
SRC = $(wildcard ./src/*.c)
DEP = $(SRC:.c=.exe)
SRCTEST = ./test/UnitTests
PATHI = ./test/UnitTests/includes
PATHU = ./test/UnitTests/Unity
INC := ./src/includes
TEST = test_$.exe
# Compilation of main program
all: $(DEP)
syntax: *.c $(SRC) $(SRCTEST)/test_*.c
$(CC) $(CSYNTAX) $^ $(CFLAGS) -I$(INC) -I $(PATHI)
%.exe: *.c $(SRC)
$(CC) $^ $(CFLAGS) -I$(INC) -o $@
./$@
# Compilation and execution of tests
test: $(SRCTEST)/test_*.exe
test_%.exe: $(SRCTEST)/test_%.c $(SRC) $(PATHU)/unity.c
$(CC) $(CFLAGS) $^ -I $(INC) -I $(PATHI) -I $(PATHU) -o $@
./$@
clean:
del *.exe
I'm using GNU gcc on MinGW 8.1.0 but also have access to a gcc Ubunutu 7.5.0 (replit.com)
CodePudding user response:
There is a bunch of subtle issues here.
First, when your test target depends on a wildcard, its resolution is not intuitive as this is not directly expanded by make
. If a matching file does not exist, make
will attempt to find the rule with literal *
in it:
$ cat Makefile
SRC = $(wildcard ./src/*.c)
SRCTEST = ./test/UnitTests
%.exe: $(SRC)
echo Making $@ from $^
test: $(SRCTEST)/test_*.exe
test_%.exe: $(SRCTEST)/test_%.c $(SRC)
echo Making $@ from $^
$ make test -dr
...
Considering target file 'test'.
Looking for an implicit rule for 'test'.
No implicit rule found for 'test'.
Considering target file 'test/UnitTests/test_*.exe'.
File 'test/UnitTests/test_*.exe' does not exist.
Looking for an implicit rule for 'test/UnitTests/test_*.exe'.
Trying pattern rule with stem '*'.
Trying implicit prerequisite 'test/UnitTests/test/UnitTests/test_*.c'.
Trying pattern rule with stem 'test_*'.
Trying rule prerequisite 'src/foo.c'.
Found an implicit rule for 'test/UnitTests/test_*.exe'.
Considering target file 'src/foo.c'.
Looking for an implicit rule for 'src/foo.c'.
No implicit rule found for 'src/foo.c'.
Finished prerequisites of target file 'src/foo.c'.
No need to remake target 'src/foo.c'.
Finished prerequisites of target file 'test/UnitTests/test_*.exe'.
Must remake target 'test/UnitTests/test_*.exe'.
echo Making test/UnitTests/test_*.exe from src/foo.c
...
Assuming you don't want to create a file literally named test_*.exe
, test binaries should have expected names, i.e.:
$ cat Makefile
SRC = $(wildcard ./src/*.c)
SRCTEST = ./test/UnitTests
%.exe: $(SRC)
echo Making $@ from $^
.PHONY: test
test: $(patsubst %.c,%.exe,$(wildcard $(SRCTEST)/test_*.c))
test_%.exe: $(SRCTEST)/test_%.c $(SRC)
echo Making $@ from $^
This will derive test binary names from test source files by converting their suffix from .c
to .exe
. (As a side note, the target itself should be phony since it does not create a real file).
With given file structure:
$ ls -R
.:
Makefile src test
./src:
foo.c
./test:
UnitTests
./test/UnitTests:
test_foo.c
this results in an attempt to create test_foo.exe
instead of test_*.exe
:
$ make test -dr
...
Considering target file 'test'.
File 'test' does not exist.
Considering target file 'test/UnitTests/test_foo.exe'.
File 'test/UnitTests/test_foo.exe' does not exist.
Looking for an implicit rule for 'test/UnitTests/test_foo.exe'.
Trying pattern rule with stem 'foo'.
Trying implicit prerequisite 'test/UnitTests/test/UnitTests/test_foo.c'.
Trying pattern rule with stem 'test_foo'.
Trying rule prerequisite 'src/foo.c'.
Found an implicit rule for 'test/UnitTests/test_foo.exe'.
Considering target file 'src/foo.c'.
Looking for an implicit rule for 'src/foo.c'.
No implicit rule found for 'src/foo.c'.
Finished prerequisites of target file 'src/foo.c'.
No need to remake target 'src/foo.c'.
Finished prerequisites of target file 'test/UnitTests/test_foo.exe'.
Must remake target 'test/UnitTests/test_foo.exe'.
echo Making test/UnitTests/test_foo.exe from src/foo.c
...
Still, it did not follow the test rule, but the general source rule. Why is that? Well, that's because when matching the stem (the %
part) the directory is prepended to the searched targets. You can see in the output above that when make
is Considering target file 'test/UnitTests/test_foo.exe'
, it will look for a file test/UnitTests/test/UnitTests/test_foo.c
, which comes from $(SRCTEST)/test_%.c
explicitly mentioning $(SRCTEST)
while it is also prepended automatically from the stem. Of course such file does not exist, hence the rule is rejected.
The proper fix is easy:
$ cat Makefile
SRC = $(wildcard ./src/*.c)
SRCTEST = ./test/UnitTests
%.exe: $(SRC)
echo Making $@ from $^
.PHONY: test
test: $(patsubst %.c,%.exe,$(wildcard $(SRCTEST)/test_*.c))
test_%.exe: test_%.c $(SRC)
echo Making $@ from $^
$ make -s test
Making test/UnitTests/test_foo.exe from test/UnitTests/test_foo.c src/foo.c
Note that it has now built the test binary from test source as expected.