Home > front end >  Makefile for UnitTests (universal file)
Makefile for UnitTests (universal file)

Time:09-17

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.

  • Related