-- Leo's gemini proxy

-- Connecting to gemini.hitchhiker-linux.org:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini;lang=en-US

TDD in C (make is all you need)

2023-09-16

There are a few things I used to think I'd miss going back to C from a more "modern" programming language. Lately I've been writing a lot of C again and finding that not to be the case, either because I just don't actually miss the language feature or because it's nowhere near as hard to simulate or manually implement as I thought it would be. Take sum types (tagged unions) for instance. At it's heart all that's required to add some type safety to a union is to associate it with an enum tag. In C this can be as simple as stuffing an enum and a union into struct fields and providing functions to access them only after checking the tag field.


One thing that I think languages like Rust, Go and Zig have put to the fore is including a nice test framework right at language level. Going back to C, I thought it might be even more beneficial to do test driven development than in "safer" languages, but the tooling doesn't really exist out of the box. So on one of my recent scribbles I set out to see how hard it would be to do some TDD using just what you might have on any POSIX system - namely the compiler and POSIX make.


Aside: There's a bit of an art to writing Makefiles that will work on both BSD and GNU systems. Most people these days (if they're even writing Makefiles by hand at all) write them for one or the other, usually for GNU make. I find this highly annoying. It's not actually very hard to write a portable Makefile.


Basically my method boiled down to this:

write a Makefile which compiles the working code as a static library

in a subdirectory "test" add a second Makefile which compiles each test as an executable linked to the static lib

add to "test/Makefile" a target which loops through and runs all of the tests, keeping track of exit codes and pretty printing the results


How does that actually look? It's surprisingly concise.

include ../config.mk

CFLAGS     += -I../include
LDLIBS     += ../libhaggis.a
LDLIBS     += $(LIBS)

tests      += store_u16
tests      += load_u16
tests      += store_u32
# more tests omitted

total      != echo $(tests) | wc -w | awk '{ print $$1 }'

.PHONY: test
test: $(tests) output
	@echo -e "\n\t=== \e[0;33mRunning $(total) tests\e[0m ===\n"
	@idx=1 ; success=0 ; fail=0; for t in $(tests) ; \
		do printf "[%02i/$(total)] %-25s" $${idx} $${t} ; \
		idx=$$(expr $${idx} + 1) ; \
		./$${t} ; \
		if [ $$? -eq 0 ] ; \
		then echo -e '\e[0;32mSuccess\e[0m' ; \
		success=$$(expr $${success} + 1) ; \
		else echo -e '\e[0;31mFailure\e[0m' ; \
		fail=$$(expr $${fail} + 1) ; \
		fi ; done || true ; \
		if [ $${fail} == 0 ] ; \
		then echo -e '\nResults: \e[0;32mOk\e[0m.' "$${success} succeeded; $${fail} failed" ; \
		else echo -e '\nResults: \e[0;31mFAILED\e[0m.' "$${success} succeeded; $${fail} failed\n" ; \
		fi

output:
	@ [-d $@ ] 2>/dev/null || install -d $@

.PHONY: clean
clean:

The most annoying part is making sure that 'make' doesn't fire up a new shell for each line, which you have to do by terminating the lines with '; \'. Then there's the double sigil '$$' which is needed for proper variable expansion by the shell. I also had to figure out a least common denominator set of 'echo' and 'printf' that works on almost every possible combination of BSD, GNU or Solaris tools. There's some other idiosyncrasies as well, like figuring out how to stuff a variable with shell output in a way that works for both BSD and GNU make, but hey it's not bad in the end.


What you get is a nice pretty printed test run showing the number of passes and fails. Adding a new test incolves creating a test executable and adding it to the "tests" variable in the Makefile. Easy peasy, and allows me to do TDD without any extra frameworks or tooling.

screenshot


Tags for this page

C

programming

make


Home

All posts


All content for this site is licensed as CC BY-SA.

© 2023 by JeanG3nie

Finger

Contact

-- Response ended

-- Page fetched on Mon May 20 11:39:49 2024