commit b0aa2229dfdce21f3b35ca48fcdb99bcd9bc069b from: Sven M. Hallberg date: Mon May 26 10:22:53 2025 UTC add lang/c/test.h This is the first (2020) version, from project rx (darcs): patch e23a5d20646a06606d11f4ab711e123b4cd98ba6 Author: pesco@khjk.org Date: Mon Apr 12 15:47:51 CEST 2021 * prototype commit - fcb7466a5ce845e0ca67043a78ba4dd6df61fbac commit + b0aa2229dfdce21f3b35ca48fcdb99bcd9bc069b blob - /dev/null blob + 23e8cfaad8632bb2ad9adb72ec702222e957483d (mode 644) --- /dev/null +++ lang/c/test.h @@ -0,0 +1,149 @@ +/* test.h - unit tests + * pesco 2020 + * + * SYNOPSIS + * #include "test.h" + * + * TEST(function, ...); + * RUNT(function, ...); + * + * fail(fmt, ...); + * + * check(expression); + * + * DESCRIPTION + * The TEST() macro calls the given function as a unit test. Any + * additional arguments will be passed to function, allowing parameterized + * families of tests. + * + * TEST() is meant to be called from main() and inspects argc/argv. The + * command line should consist (only) of string prefixes that select the + * corresponding tests. An empty command line selects all tests. Any + * selected tests are passed to RUNT(). The RUNT() macro executes a test + * unconditionally and prints the test result to standard output. It does + * not depend on argc and argv being in scope. + * + * Test functions may be defined with any return type, but the return + * value is ignored. To signal failure of a test, the fail() macro should + * be called with an appropriate error message, using printf(3)-style + * arguments. Any other diagnostics should be printed to stderr as usual. + * + * The check() macro tests a boolean condition. It calls fail() if the + * given expression evaluates to false. The user should define custom + * variants of check() as suitable. + * + * The fail() macro does not abort the current test unless the X + * environment variable is set (see below). Checks that always form a + * precondition for the continuation of the test should use assert(3). + * Meanwhile, RUNT() calls exit(3) after a failed test returns; see also + * the K environment variable. + * + * ENVIRONMENT + * The following variables serve as flags. They enable the stated behavior + * whenever set, regardless of value. + * + * V Print tests that are skipped. + * + * K Do not exit after a failed test. + * + * X Call abort(3) immediately when fail() is called. + * + * EXAMPLES + * The following shows a test program: + * + * #include "test.h" + * + * void foo(void) + * { + * check(1 == 1); + * } + * + * void bar(unsigned int mask) + * { + * check(0x01 & mask != 0); + * check(0x10 & mask != 0); + * } + * + * int main(int argc, char **argv) + * { + * TEST(foo); + * TEST(bar, 0x0f); + * TEST(bar, 0xff); + * + * return 0; + * } + * + * To run only the 'bar' family of tests: + * + * $ ./test bar + * bar(0xff): OK + * test.c:11: condition failed: (0x10 & mask) != 0 + * bar(0x0f): FAIL + * + * Running 'foo' and 'bar(0xff)': + * + * $ ./test foo bar\(0xf + * foo(): OK + * bar(0xff): OK + * + * A list or count of available tests can be produced by combining verbose + * mode with an impossible selection: + * + * $ V=1 ./test - + * foo() skipped + * bar(0xff) skipped + * bar(0x0f) skipped + * + * $ V=1 ./test - | wc -l + * 3 + */ + +#ifndef TEST_H +#define TEST_H + +#include /* printf, fprintf */ +#include /* strncmp */ +#include /* exit, abort, getenv */ + +static int failed_; + +#define TEST(X, ...) do { \ + const char *name = #X "(" #__VA_ARGS__ ")"; \ + int i; \ + \ + /* does name match any prefix given on the command line? */ \ + for (i = 1; i < argc; i++) \ + if (strncmp(argv[i], name, strlen(argv[i])) == 0) \ + break; \ + /* run test if it matches an arg or if there are no args */ \ + if (i < argc || argc == 1) \ + RUNT(X, __VA_ARGS__); \ + else if (getenv("V") != NULL) /* V = verbose */ \ + printf("%s skipped\n", name); \ +} while (0) + +#define RUNT(X, ...) do { \ + const char *name = #X "(" #__VA_ARGS__ ")"; \ + \ + failed_ = 0; \ + (X)(__VA_ARGS__); \ + printf("%s:\t%s\n", name, failed_ ? "FAIL" : "OK"); \ + if (failed_ && getenv("K") == NULL) /* K = keep going */ \ + exit(1); \ +} while (0) + +#define check(EXP) do { \ + if (!(EXP)) \ + fail("condition failed: %s", #EXP); \ +} while (0) + +#define fail(...) do { \ + fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + if (getenv("X") != NULL) /* X = abort */ \ + abort(); \ + failed_ = 1; \ +} while (0) + +#endif /* TEST_H */