commit - fcb7466a5ce845e0ca67043a78ba4dd6df61fbac
commit + b0aa2229dfdce21f3b35ca48fcdb99bcd9bc069b
blob - /dev/null
blob + 23e8cfaad8632bb2ad9adb72ec702222e957483d (mode 644)
--- /dev/null
+++ lang/c/test.h
+/* 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 <stdio.h> /* printf, fprintf */
+#include <string.h> /* strncmp */
+#include <stdlib.h> /* 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 */