Commit Diff


commit - 5befd76049fbcb96bef8ed5bb4d58b128098f470
commit + 6bb0b7c891ae7d389b952633dfbc8809ff21a4cf
blob - ed698719d8f3a01b6f046d3b6e78dd389d8b1db2 (mode 644)
blob + /dev/null
--- exercise.1
+++ /dev/null
@@ -1,292 +0,0 @@
-.Dd July 23, 2025
-.Dt EXERCISE 1
-.Os
-.
-.Sh NAME
-.Nm exercise
-.Nd simple but flexible test runner
-.
-.Sh SYNOPSIS
-.Nm exercise
-.Op Fl knqvx
-.Op Fl B Ar dir
-.Op Fl C Ar dir
-.Op Fl D Ar dir
-.Op Fl j Ar n
-.Op Fl p Ar k Ns Sy = Ns Ar pattern
-.Op Ar path ...
-.
-.Sh DESCRIPTION
-The
-.Nm
-utility searches for and executes software (unit) tests.
-It displays a running status and passes any error messages to standard error.
-.Pp
-Any
-.Ar path
-argument that refers to a directory
-is recursively searched for tests.
-Other arguments are executed as individual files.
-Executables should match one of the patterns
-.Pa *.t
-or
-.Pa *.ts
-(for test suites).
-.Pp
-If called without arguments,
-.Nm
-searches the current directory.
-.Pp
-Arguments can be passed to a test or test suite
-by embedding them in
-.Ar path
-after a colon
-.Sq ":" .
-Anything after the (first) colon is passed to the test command,
-split into arguments by white space.
-To avoid splitting an argument that contains spaces,
-surrounding double quotes must be embedded with it.
-.Pp
-The options are as follows:
-.Bl -tag -width Ds
-.It Fl B Ar dir
-Find test files relative to the given base directory.
-If
-.Ar dir
-is a relative path,
-it refers to the working directory in effect after any
-.Fl C
-option.
-.It Fl C Ar dir
-Change the working directory to
-.Ar dir
-before doing anything else.
-.It Fl D Ar dir
-Execute tests from the given working directory.
-This is useful, for example, when
-.Ar dir
-contains common helper or data files for the tests to find.
-.It Fl j Ar n
-Execute up to
-.Ar n
-test jobs concurrently.
-.It Fl k
-Keep going after a failed test.
-This is the default,
-but the option is passed on to test suites
-which may have different behavior.
-Negates any previous
-.Fl x
-option.
-.It Fl n
-Dry run.
-Do not execute the tests.
-Test suite executables
-.Em are
-called, with the
-.Fl n
-option passed on to them.
-.It Fl p Sy d= Ns Ar pattern
-When searching for tests,
-only descend into directories that match the given pattern.
-Defaults to
-.Sq *
-(all directories).
-.It Fl p Sy s= Ns Ar pattern
-Process files matching the given pattern as test suites.
-Defaults to
-.Sq *.ts .
-.It Fl p Sy t= Ns Ar pattern
-When searching for tests,
-process files that match the given pattern
-as test executables.
-Defaults to
-.Sq *.t .
-.It Fl q
-Be quiet.
-Do not print status counters to standard output.
-.It Fl v
-Be verbose.
-Print the name of each test to standard output preceded by
-.Dq ". " .
-If given twice, also print any test standard output
-with an additional level of quoting using
-.Dq "> " .
-This flag is passed on to test suites.
-.It Fl x
-Exit immediately after a test failure or error.
-This flag is passed on to test suites and
-negates any previous
-.Fl k
-option.
-.El
-.
-.Ss Test Files
-Tests are represented simply as executable files that are expected to exit 0 on
-success.
-Non-zero exits as well as any output to standard error
-are interpreted as negative results (failure).
-Standard output is ignored.
-.Pp
-As a special case, exit status 127 is interpreted as an error
-that precluded the proper execution of the test,
-implying an inconclusive result.
-.
-.Ss Test Suites
-Multiple tests can be combined into one executable.
-A test suite should pass error messages from individual tests unchanged to its
-standard error.
-Its exit status should indicate whether all tests passed.
-.Pp
-A test suite should only exit 127 if there was a fatal issue in executing
-the suite itself.
-Errors among the individual tests should be reported through the
-status counters as detailed below.
-.Pp
-The standard output from a
-test suite executable should consist of lines of the form
-.Sq run Va n No ok Va m No fail Va l No error Va k ,
-representing running counts as printed by
-.Nm
-itself.
-Consequently, the
-.Nm
-utility can be used as a (nested) test suite runner if desired.
-.Pp
-A test suite must accept the
-.Fl n ,
-.Fl v
-and 
-.Fl x
-options.
-It must honor
-.Fl n
-by performing a dry run, reporting status as usual
-without actually executing any tests.
-In addition, it may honor the
-.Fl v
-option by printing test names to its standard output,
-prefixed with
-.Dq ". "
-to distinguish them from regular status lines.
-If
-.Fl vv
-is given, more diagnostics may be printed which,
-by convention, should be prefixed with
-.Dq ". >"
-(period, space, greater-than).
-The
-.Fl x
-option should cause the test suite to exit after a test failure
-if it would normally continue.
-Given
-.Fl xx ,
-it may exit even earlier,
-i.e. on the first failed condition within a test.
-.Pp
-After running a test suite, its final report is checked for consistency.
-If any of the conditions (listed below) are not satisfied,
-the entire test suite is discarded and counted as a single
-.Sy error ,
-as if it had exited with status 127.
-.
-.Ss Status Counters
-Unless given the
-.Fl q
-option,
-.Nm
-prints the following running counters to its standard output:
-.Bl -tag -width Ds
-.It Sy run
-The number of tests that are planned to execute.
-This is first set to the number of test files found,
-including test suites.
-The latter are initially counted as single tests
-because the number of tests they contain is not known beforehand.
-The
-.Sy run
-counter will increase dynamically as test suites report their totals.
-.It Sy ok
-The number of tests that have passed successfully,
-i.e. exited 0 without any output to standard error.
-.It Sy fail
-The number of tests that have yielded a negative result,
-i.e. exited with a non-zero status (except 127)
-or printed anything to standard error.
-.It Sy error
-The number of tests that could not be executed despite the attempt
-or that exited with the special status 127.
-.El
-.Pp
-The following should hold at the end of a run:
-.Bd -filled -offset Ds
-.Sy run
-=
-.Sy ok
-+
-.Sy fail
-+
-.Sy error
-.Ed
-.Pp
-In practice, the condition is relaxed to an inequality
-to account for the fact that
-the sum may remain short of
-.Sy run
-if
-test suites or
-.Nm
-itself terminate early.
-.
-.Sh DEPENDENCIES
-The
-.Nm
-utility requires Tcl 8.4 or above.
-.
-.Sh ENVIRONMENT
-.Bl -tag -width EXERCISEDIR
-.It Ev EXERCISEDIR
-The working directory from which to execute tests.
-Overriden by the
-.Fl D
-option.
-If a relative path is provided,
-it is interpreted after any
-.Fl C
-option takes effect.
-.El
-.
-.Sh EXIT STATUS
-The
-.Nm
-utility exits 0 if all tests passed, 1 if some tests failed or encountered
-errors, and 127 if an error occured outside of the tests.
-.
-.Sh EXAMPLES
-See the
-.Pa tests/
-directory distributed with the program.
-It shows a useful idiom where a number of test (shell) scripts make use of
-a helper,
-.Pa assert-exercise .
-The helper runs
-.Nm
-with some test (shell) script to execute and compares
-the outcome to expectations.
-.Pp
-Calling a test suite with three arguments,
-the last of which contains a space character:
-.Bd -literal -offset Ds
-.Sy $ \c
-exercise foo/bar.ts:'a b "x y"'
-.Ed
-.Pp
-Note the use of (shell) single quotes to embed the necessary spaces and
-double quotes.
-.
-.Sh SEE ALSO
-.Xr sh 1 ,
-.Xr test.h 3
-.
-.Sh AUTHORS
-.An Sven M. Hallberg Aq Mt pesco@khjk.org
blob - /dev/null
blob + ed698719d8f3a01b6f046d3b6e78dd389d8b1db2 (mode 644)
--- /dev/null
+++ man/exercise.1
@@ -0,0 +1,292 @@
+.Dd July 23, 2025
+.Dt EXERCISE 1
+.Os
+.
+.Sh NAME
+.Nm exercise
+.Nd simple but flexible test runner
+.
+.Sh SYNOPSIS
+.Nm exercise
+.Op Fl knqvx
+.Op Fl B Ar dir
+.Op Fl C Ar dir
+.Op Fl D Ar dir
+.Op Fl j Ar n
+.Op Fl p Ar k Ns Sy = Ns Ar pattern
+.Op Ar path ...
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility searches for and executes software (unit) tests.
+It displays a running status and passes any error messages to standard error.
+.Pp
+Any
+.Ar path
+argument that refers to a directory
+is recursively searched for tests.
+Other arguments are executed as individual files.
+Executables should match one of the patterns
+.Pa *.t
+or
+.Pa *.ts
+(for test suites).
+.Pp
+If called without arguments,
+.Nm
+searches the current directory.
+.Pp
+Arguments can be passed to a test or test suite
+by embedding them in
+.Ar path
+after a colon
+.Sq ":" .
+Anything after the (first) colon is passed to the test command,
+split into arguments by white space.
+To avoid splitting an argument that contains spaces,
+surrounding double quotes must be embedded with it.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B Ar dir
+Find test files relative to the given base directory.
+If
+.Ar dir
+is a relative path,
+it refers to the working directory in effect after any
+.Fl C
+option.
+.It Fl C Ar dir
+Change the working directory to
+.Ar dir
+before doing anything else.
+.It Fl D Ar dir
+Execute tests from the given working directory.
+This is useful, for example, when
+.Ar dir
+contains common helper or data files for the tests to find.
+.It Fl j Ar n
+Execute up to
+.Ar n
+test jobs concurrently.
+.It Fl k
+Keep going after a failed test.
+This is the default,
+but the option is passed on to test suites
+which may have different behavior.
+Negates any previous
+.Fl x
+option.
+.It Fl n
+Dry run.
+Do not execute the tests.
+Test suite executables
+.Em are
+called, with the
+.Fl n
+option passed on to them.
+.It Fl p Sy d= Ns Ar pattern
+When searching for tests,
+only descend into directories that match the given pattern.
+Defaults to
+.Sq *
+(all directories).
+.It Fl p Sy s= Ns Ar pattern
+Process files matching the given pattern as test suites.
+Defaults to
+.Sq *.ts .
+.It Fl p Sy t= Ns Ar pattern
+When searching for tests,
+process files that match the given pattern
+as test executables.
+Defaults to
+.Sq *.t .
+.It Fl q
+Be quiet.
+Do not print status counters to standard output.
+.It Fl v
+Be verbose.
+Print the name of each test to standard output preceded by
+.Dq ". " .
+If given twice, also print any test standard output
+with an additional level of quoting using
+.Dq "> " .
+This flag is passed on to test suites.
+.It Fl x
+Exit immediately after a test failure or error.
+This flag is passed on to test suites and
+negates any previous
+.Fl k
+option.
+.El
+.
+.Ss Test Files
+Tests are represented simply as executable files that are expected to exit 0 on
+success.
+Non-zero exits as well as any output to standard error
+are interpreted as negative results (failure).
+Standard output is ignored.
+.Pp
+As a special case, exit status 127 is interpreted as an error
+that precluded the proper execution of the test,
+implying an inconclusive result.
+.
+.Ss Test Suites
+Multiple tests can be combined into one executable.
+A test suite should pass error messages from individual tests unchanged to its
+standard error.
+Its exit status should indicate whether all tests passed.
+.Pp
+A test suite should only exit 127 if there was a fatal issue in executing
+the suite itself.
+Errors among the individual tests should be reported through the
+status counters as detailed below.
+.Pp
+The standard output from a
+test suite executable should consist of lines of the form
+.Sq run Va n No ok Va m No fail Va l No error Va k ,
+representing running counts as printed by
+.Nm
+itself.
+Consequently, the
+.Nm
+utility can be used as a (nested) test suite runner if desired.
+.Pp
+A test suite must accept the
+.Fl n ,
+.Fl v
+and 
+.Fl x
+options.
+It must honor
+.Fl n
+by performing a dry run, reporting status as usual
+without actually executing any tests.
+In addition, it may honor the
+.Fl v
+option by printing test names to its standard output,
+prefixed with
+.Dq ". "
+to distinguish them from regular status lines.
+If
+.Fl vv
+is given, more diagnostics may be printed which,
+by convention, should be prefixed with
+.Dq ". >"
+(period, space, greater-than).
+The
+.Fl x
+option should cause the test suite to exit after a test failure
+if it would normally continue.
+Given
+.Fl xx ,
+it may exit even earlier,
+i.e. on the first failed condition within a test.
+.Pp
+After running a test suite, its final report is checked for consistency.
+If any of the conditions (listed below) are not satisfied,
+the entire test suite is discarded and counted as a single
+.Sy error ,
+as if it had exited with status 127.
+.
+.Ss Status Counters
+Unless given the
+.Fl q
+option,
+.Nm
+prints the following running counters to its standard output:
+.Bl -tag -width Ds
+.It Sy run
+The number of tests that are planned to execute.
+This is first set to the number of test files found,
+including test suites.
+The latter are initially counted as single tests
+because the number of tests they contain is not known beforehand.
+The
+.Sy run
+counter will increase dynamically as test suites report their totals.
+.It Sy ok
+The number of tests that have passed successfully,
+i.e. exited 0 without any output to standard error.
+.It Sy fail
+The number of tests that have yielded a negative result,
+i.e. exited with a non-zero status (except 127)
+or printed anything to standard error.
+.It Sy error
+The number of tests that could not be executed despite the attempt
+or that exited with the special status 127.
+.El
+.Pp
+The following should hold at the end of a run:
+.Bd -filled -offset Ds
+.Sy run
+=
+.Sy ok
++
+.Sy fail
++
+.Sy error
+.Ed
+.Pp
+In practice, the condition is relaxed to an inequality
+to account for the fact that
+the sum may remain short of
+.Sy run
+if
+test suites or
+.Nm
+itself terminate early.
+.
+.Sh DEPENDENCIES
+The
+.Nm
+utility requires Tcl 8.4 or above.
+.
+.Sh ENVIRONMENT
+.Bl -tag -width EXERCISEDIR
+.It Ev EXERCISEDIR
+The working directory from which to execute tests.
+Overriden by the
+.Fl D
+option.
+If a relative path is provided,
+it is interpreted after any
+.Fl C
+option takes effect.
+.El
+.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 if all tests passed, 1 if some tests failed or encountered
+errors, and 127 if an error occured outside of the tests.
+.
+.Sh EXAMPLES
+See the
+.Pa tests/
+directory distributed with the program.
+It shows a useful idiom where a number of test (shell) scripts make use of
+a helper,
+.Pa assert-exercise .
+The helper runs
+.Nm
+with some test (shell) script to execute and compares
+the outcome to expectations.
+.Pp
+Calling a test suite with three arguments,
+the last of which contains a space character:
+.Bd -literal -offset Ds
+.Sy $ \c
+exercise foo/bar.ts:'a b "x y"'
+.Ed
+.Pp
+Note the use of (shell) single quotes to embed the necessary spaces and
+double quotes.
+.
+.Sh SEE ALSO
+.Xr sh 1 ,
+.Xr test.h 3
+.
+.Sh AUTHORS
+.An Sven M. Hallberg Aq Mt pesco@khjk.org
blob - /dev/null
blob + d82e4bbd1a740743908c9626690691c259402964 (mode 644)
--- /dev/null
+++ man/test.h.3
@@ -0,0 +1,203 @@
+.Dd July 23, 2025
+.Dt TEST.H 3
+.Os
+.
+.Sh NAME
+.Nm test.h
+.Nd terribly simple tests in plain C
+.
+.Sh SYNOPSIS
+.In test.h
+.Vt static int status;
+.Fn test function ...
+.Fn runtest function ...
+.Fn expect expression
+.Fn fail fmt ...
+.Fn rem fmt ...
+.
+.Sh DESCRIPTION
+The
+.Fn test
+macro calls the given function as a test.
+Any additional arguments are be passed through to
+.Ar function ,
+allowing parameterized families of tests.
+.Pp
+.Fn test
+is meant to be called from
+.Fn main
+and inspects
+.Va argc
+and
+.Va argv .
+The command line should consist (only) of string prefixes
+that select the corresponding tests.
+An empty command line selects all tests.
+Each selected test is passed to the
+.Fn runtest
+macro.
+.Pp
+.Fn runtest
+unconditionally executes the given test
+and prints its result to standard output.
+It does not depend on
+.Va argc
+and
+.Va argv
+being in scope.
+.Pp
+Test functions may be defined with any return type;
+their return value is ignored.
+To signal failure of a test, the
+.Fn fail
+macro should be called with an appropriate error message, using
+.Xr printf 3 Ns
+-style arguments.
+Diagnostics relating to the failure may be printed to
+.Va stderr .
+.Pp
+The
+.Fn expect
+macro tests a boolean condition.
+It calls
+.Fn fail
+if the given expression evaluates to false.
+The user should define custom variants of
+.Fn expect
+as suitable.
+.Pp
+The
+.Fn rem
+macro can be used to produce miscellaneous messages;
+it is ignored unless
+.Ev V
+is set.
+Messages should consist of a single line,
+without a trailing newline,
+and will be printed to
+.\"Va stdout
+standard output
+prefixed by a TAB character to maintain a simple TSV format.
+No other output should be made to
+.Va stdout .
+.Pp
+By default, calling
+.Fn fail
+does not immediately abort the running test.
+Checks that are a strict precondition for the continuation of
+the test should use
+.Xr assert 3
+\(em
+.Pa test.h
+includes
+.In assert.h for convenience.
+After a failed test returns,
+.Fn runtest
+normally calls
+.Xr exit 3 ,
+forgoing any remaining tests.
+See the
+.Ev K
+and
+.Ev X
+environment variables for changing the default behavior.
+.Pp
+The global variable
+.Va status
+is set to 1 upon test failure and should be
+used to return the correct value from main (when
+.Ev K
+is set).
+.
+.Sh ENVIRONMENT
+The following variables serve as flags.
+They enable the stated behavior whenever set, regardless of value.
+.Bl -tag -width 3n
+.It Ev D
+Dry run.
+Print tests without executing them.
+.It Ev V
+Verbose.
+Print any tests that are skipped and enable informational output from
+.Fn rem .
+.It Ev K
+Keep going after a failed test instead of exiting.
+.It Ev X
+Exit with a call to
+.Xr abort 3
+as soon as
+.Fn fail
+is called.
+.El
+.
+.Sh EXAMPLES
+The following shows a test program consisting of two test functions,
+one parameterized over an
+.Vt unsigned int ,
+for a total of three test cases.
+.Bd -literal -offset Ds
+#include "test.h"
+
+void
+foo(void)
+{
+	expect(1 == 1);
+}
+
+void
+bar(unsigned int mask)
+{
+	expect(0x01 & mask != 0);
+	expect(0x10 & mask != 0);
+}
+
+int
+main(int argc, char *argv[])
+{
+	test(foo);
+	test(bar, 0x0f);
+	test(bar, 0xff);
+
+	return status;
+}
+.Ed
+.Pp
+To run only the
+.Sq bar
+family of tests:
+.Bd -literal -offset Ds
+$ ./test bar
+bar(0xff)	OK
+test.c:11: condition failed: (0x10 & mask) != 0
+bar(0x0f)	FAIL
+.Ed
+.Pp
+Running
+.Sq foo
+and
+.Sq bar(0xff) :
+.Bd -literal -offset Ds
+$ ./test foo bar\\(0xf
+foo()	OK
+bar(0xff)	OK
+.Ed
+.Pp
+A list or count of tests matching a given selection can be produced by
+issuing a dry run:
+.Bd -literal -offset Ds
+$ D=1 ./test
+foo()
+bar(0xff)
+bar(0x0f)
+
+$ D=1 ./test bar | wc -l
+2
+.Ed
+.
+.Sh SEE ALSO
+.Xr abort 3 ,
+.Xr assert 3 ,
+.Xr printf 3
+.
+.Sh AUTHORS
+.An Sven M. Hallberg Aq Mt pesco@khjk.org
blob - 857a903b38ec20f0097cc25a24e392d5f92e81e1 (mode 644)
blob + /dev/null
--- lang/c/test.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * test.h - terribly simple tests in plain C
- * pesco 2020, 2024, 2025
- * ISC license
- */
-
-#ifndef TEST_H
-#define TEST_H
-
-#include <assert.h>	/* expose assert() for the user */
-#include <stdio.h>	/* printf, fprintf */
-#include <stdlib.h>	/* exit, abort, getenv */
-#include <string.h>	/* strncmp */
-
-static int status;
-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)					\
-		runtest(X, __VA_ARGS__);				\
-	else if (getenv("V") != NULL)		/* V = verbose */	\
-		printf("%s\t-\n", name);				\
-} while (0)
-
-#define runtest(X, ...) do {						\
-	const char *name = #X "(" #__VA_ARGS__ ")";			\
-									\
-	failed_ = 0;							\
-	if (getenv("D") == NULL) {		/* D = dry run */	\
-		(X)(__VA_ARGS__);					\
-		printf("%s\t%s\n", name, failed_ ? "FAIL" : "OK");	\
-	} else								\
-		printf("%s\n", name);					\
-	if (failed_ && getenv("K") == NULL)	/* K = keep going */	\
-		exit(1);						\
-	status = status || failed_;					\
-} while (0)
-
-#define expect(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)
-
-#define rem(...) do {							\
-	if (getenv("V") == NULL)		/* V = verbose */	\
-		break;							\
-	printf("\t");							\
-	printf(__VA_ARGS__);						\
-	printf("\n");							\
-} while (0)
-
-#endif /* TEST_H */
blob - /dev/null
blob + 857a903b38ec20f0097cc25a24e392d5f92e81e1 (mode 644)
--- /dev/null
+++ support/c/test.h
@@ -0,0 +1,69 @@
+/*
+ * test.h - terribly simple tests in plain C
+ * pesco 2020, 2024, 2025
+ * ISC license
+ */
+
+#ifndef TEST_H
+#define TEST_H
+
+#include <assert.h>	/* expose assert() for the user */
+#include <stdio.h>	/* printf, fprintf */
+#include <stdlib.h>	/* exit, abort, getenv */
+#include <string.h>	/* strncmp */
+
+static int status;
+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)					\
+		runtest(X, __VA_ARGS__);				\
+	else if (getenv("V") != NULL)		/* V = verbose */	\
+		printf("%s\t-\n", name);				\
+} while (0)
+
+#define runtest(X, ...) do {						\
+	const char *name = #X "(" #__VA_ARGS__ ")";			\
+									\
+	failed_ = 0;							\
+	if (getenv("D") == NULL) {		/* D = dry run */	\
+		(X)(__VA_ARGS__);					\
+		printf("%s\t%s\n", name, failed_ ? "FAIL" : "OK");	\
+	} else								\
+		printf("%s\n", name);					\
+	if (failed_ && getenv("K") == NULL)	/* K = keep going */	\
+		exit(1);						\
+	status = status || failed_;					\
+} while (0)
+
+#define expect(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)
+
+#define rem(...) do {							\
+	if (getenv("V") == NULL)		/* V = verbose */	\
+		break;							\
+	printf("\t");							\
+	printf(__VA_ARGS__);						\
+	printf("\n");							\
+} while (0)
+
+#endif /* TEST_H */
blob - d82e4bbd1a740743908c9626690691c259402964 (mode 644)
blob + /dev/null
--- test.h.3
+++ /dev/null
@@ -1,203 +0,0 @@
-.Dd July 23, 2025
-.Dt TEST.H 3
-.Os
-.
-.Sh NAME
-.Nm test.h
-.Nd terribly simple tests in plain C
-.
-.Sh SYNOPSIS
-.In test.h
-.Vt static int status;
-.Fn test function ...
-.Fn runtest function ...
-.Fn expect expression
-.Fn fail fmt ...
-.Fn rem fmt ...
-.
-.Sh DESCRIPTION
-The
-.Fn test
-macro calls the given function as a test.
-Any additional arguments are be passed through to
-.Ar function ,
-allowing parameterized families of tests.
-.Pp
-.Fn test
-is meant to be called from
-.Fn main
-and inspects
-.Va argc
-and
-.Va argv .
-The command line should consist (only) of string prefixes
-that select the corresponding tests.
-An empty command line selects all tests.
-Each selected test is passed to the
-.Fn runtest
-macro.
-.Pp
-.Fn runtest
-unconditionally executes the given test
-and prints its result to standard output.
-It does not depend on
-.Va argc
-and
-.Va argv
-being in scope.
-.Pp
-Test functions may be defined with any return type;
-their return value is ignored.
-To signal failure of a test, the
-.Fn fail
-macro should be called with an appropriate error message, using
-.Xr printf 3 Ns
--style arguments.
-Diagnostics relating to the failure may be printed to
-.Va stderr .
-.Pp
-The
-.Fn expect
-macro tests a boolean condition.
-It calls
-.Fn fail
-if the given expression evaluates to false.
-The user should define custom variants of
-.Fn expect
-as suitable.
-.Pp
-The
-.Fn rem
-macro can be used to produce miscellaneous messages;
-it is ignored unless
-.Ev V
-is set.
-Messages should consist of a single line,
-without a trailing newline,
-and will be printed to
-.\"Va stdout
-standard output
-prefixed by a TAB character to maintain a simple TSV format.
-No other output should be made to
-.Va stdout .
-.Pp
-By default, calling
-.Fn fail
-does not immediately abort the running test.
-Checks that are a strict precondition for the continuation of
-the test should use
-.Xr assert 3
-\(em
-.Pa test.h
-includes
-.In assert.h for convenience.
-After a failed test returns,
-.Fn runtest
-normally calls
-.Xr exit 3 ,
-forgoing any remaining tests.
-See the
-.Ev K
-and
-.Ev X
-environment variables for changing the default behavior.
-.Pp
-The global variable
-.Va status
-is set to 1 upon test failure and should be
-used to return the correct value from main (when
-.Ev K
-is set).
-.
-.Sh ENVIRONMENT
-The following variables serve as flags.
-They enable the stated behavior whenever set, regardless of value.
-.Bl -tag -width 3n
-.It Ev D
-Dry run.
-Print tests without executing them.
-.It Ev V
-Verbose.
-Print any tests that are skipped and enable informational output from
-.Fn rem .
-.It Ev K
-Keep going after a failed test instead of exiting.
-.It Ev X
-Exit with a call to
-.Xr abort 3
-as soon as
-.Fn fail
-is called.
-.El
-.
-.Sh EXAMPLES
-The following shows a test program consisting of two test functions,
-one parameterized over an
-.Vt unsigned int ,
-for a total of three test cases.
-.Bd -literal -offset Ds
-#include "test.h"
-
-void
-foo(void)
-{
-	expect(1 == 1);
-}
-
-void
-bar(unsigned int mask)
-{
-	expect(0x01 & mask != 0);
-	expect(0x10 & mask != 0);
-}
-
-int
-main(int argc, char *argv[])
-{
-	test(foo);
-	test(bar, 0x0f);
-	test(bar, 0xff);
-
-	return status;
-}
-.Ed
-.Pp
-To run only the
-.Sq bar
-family of tests:
-.Bd -literal -offset Ds
-$ ./test bar
-bar(0xff)	OK
-test.c:11: condition failed: (0x10 & mask) != 0
-bar(0x0f)	FAIL
-.Ed
-.Pp
-Running
-.Sq foo
-and
-.Sq bar(0xff) :
-.Bd -literal -offset Ds
-$ ./test foo bar\\(0xf
-foo()	OK
-bar(0xff)	OK
-.Ed
-.Pp
-A list or count of tests matching a given selection can be produced by
-issuing a dry run:
-.Bd -literal -offset Ds
-$ D=1 ./test
-foo()
-bar(0xff)
-bar(0x0f)
-
-$ D=1 ./test bar | wc -l
-2
-.Ed
-.
-.Sh SEE ALSO
-.Xr abort 3 ,
-.Xr assert 3 ,
-.Xr printf 3
-.
-.Sh AUTHORS
-.An Sven M. Hallberg Aq Mt pesco@khjk.org
blob - 428fb5546d467591a176a2ef4421aeaba5a96f83 (mode 755)
blob + /dev/null
--- tests/lang/c/expect/abort.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/$(basename "$0").$$
-export CFLAGS=-I../../lang/c
-export X=1
-exec ./assert-c-abort \
-	"" \
-	"" \
-	"$tst.c:7: condition failed: 2 == 3\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		expect(2 == 3);			/* aborts with X set */
-		fputs("foo running\n", stderr);	/* should not print */
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail and abort */
-		test(bar);	/* will not execute */
-		return 0;
-	}
-EOF
blob - 3475d4d2d750f92577f759a7a0bb9f5193c31002 (mode 755)
blob + /dev/null
--- tests/lang/c/expect/false.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/fail.t.$$
-export CFLAGS=-I../../lang/c
-exec ./assert-c 1 \
-	"" \
-	"foo()\tFAIL\n" \
-	"$tst.c:7: condition failed: 2 == 3\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		expect(2 == 3);
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail */
-		test(bar);	/* will not execute without K set */
-		return 0;
-	}
-EOF
blob - 6aca62d9a33356436ee8d947759e7229225db13c (mode 755)
blob + /dev/null
--- tests/lang/c/expect/noabort.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/fail.t.$$
-export CFLAGS=-I../../lang/c
-exec ./assert-c 1 \
-	"" \
-	"foo()\tFAIL\n" \
-	"$tst.c:7: condition failed: 2 == 3\n$tst.c:9: condition failed: 0\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		expect(2 == 3);		/* will print an error */
-		expect(3 == 3);
-		expect(0);		/* will print an error */
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail */
-		test(bar);	/* will not execute without K set */
-		return 0;
-	}
-EOF
blob - 71b9e60d06bf85cb51ce8802b15a285d5790af55 (mode 755)
blob + /dev/null
--- tests/lang/c/expect/true.t
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		expect(2 == 2);
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - 576e48b05d1c9349a14b98c0bd2b3553ee3df7e6 (mode 755)
blob + /dev/null
--- tests/lang/c/fail/abort.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/$(basename "$0").$$
-export CFLAGS=-I../../lang/c
-export X=1
-exec ./assert-c-abort \
-	"" \
-	"" \
-	"$tst.c:7: foo failed\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fail("foo failed");		/* aborts with X set */
-		fputs("foo running\n", stderr);	/* should not print */
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail and abort */
-		test(bar);	/* will not execute */
-		return 0;
-	}
-EOF
blob - 3e7fbc3be6e8486b40aa34fa1d71bf2cf3d40caf (mode 755)
blob + /dev/null
--- tests/lang/c/fail/fmt.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/fail.t.$$
-export CFLAGS=-I../../lang/c
-exec ./assert-c 1 \
-	"" \
-	"foo(23)\tFAIL\n" \
-	"$tst.c:7: foo(23) failed\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(int i)
-	{
-		fail("foo(%d) failed", i);
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo, 23);	/* expected to fail */
-		test(bar);	/* will not execute without K set */
-		return 0;
-	}
-EOF
blob - 19f6f0102ab91e54d73e71494cce5ab1f424f596 (mode 755)
blob + /dev/null
--- tests/lang/c/fail/keepgoing.t
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/fail.t.$$
-export CFLAGS=-I../../lang/c
-export K=1
-exec ./assert-c 0 \
-	"" \
-	"foo()\tFAIL\nbar()\tOK\n" \
-	"$tst.c:7: foo failed\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fail("foo failed");
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail */
-		test(bar);	/* will still execute with K set */
-		return 0;	/* with K, we will exit 0! */
-	}
-EOF
blob - 50ca78ea98c9aec233e8a4fb83040ddee12e8179 (mode 755)
blob + /dev/null
--- tests/lang/c/fail/noabort.t
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/fail.t.$$
-export CFLAGS=-I../../lang/c
-exec ./assert-c 1 \
-	"" \
-	"foo()\tFAIL\n" \
-	"$tst.c:7: foo failed\nfoo running\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fail("foo failed");		/* does not abort */
-		fputs("foo running\n", stderr);	/* this should still print */
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail */
-		test(bar);	/* will not execute without K set */
-		return 0;
-	}
-EOF
blob - 31d01aeb9a83fe0a3edb6951b8655840e43c054a (mode 755)
blob + /dev/null
--- tests/lang/c/fail.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export tst=/tmp/fail.t.$$
-export CFLAGS=-I../../lang/c
-exec ./assert-c 1 \
-	"" \
-	"foo()\tFAIL\n" \
-	"$tst.c:7: foo failed\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fail("foo failed");
-	}
-
-	void
-	bar(void)
-	{
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);	/* expected to fail */
-		test(bar);	/* will not execute without K set */
-		return 0;
-	}
-EOF
blob - 66237947fda22c066b00bec4c050f704f99602c8 (mode 755)
blob + /dev/null
--- tests/lang/c/rem.t
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-
-set -e
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-
-# rem() ignored when not V
-./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"main\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		rem("foo called");
-	}
-
-	void
-	bar(void)
-	{
-		rem("bar called");
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		fputs("main\n", stderr);
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
-
-# rem() prints when V set
-V=1 \
-./assert-c 0 \
-	"" \
-	"\tfoo called\nfoo()\tOK\n\tbar called\nbar()\tOK\n" \
-	"main\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		rem("foo called");
-	}
-
-	void
-	bar(void)
-	{
-		rem("bar called");
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		fputs("main\n", stderr);
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - f1bb5d296fb1df3c6aa61a833f6120a07f129e5d (mode 755)
blob + /dev/null
--- tests/lang/c/runtest/argcv.t
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"foo called\nbar called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	void
-	runtests(void)
-	{
-		/* argc, argv not needed */
-		runtest(foo);
-		runtest(bar);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		runtests();
-		return 0;
-	}
-EOF
blob - 5c5fcefdb44713ba8cbf752ba778415f7352651d (mode 755)
blob + /dev/null
--- tests/lang/c/runtest/args.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo(0x01)\tOK\nbar(\"x y z\")\tOK\n" \
-	"foo(1) called\nbar(\"x y z\") called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(int i)
-	{
-		fprintf(stderr, "foo(%d) called\n", i);
-	}
-
-	void
-	bar(const char *s)
-	{
-		fprintf(stderr, "bar(\"%s\") called\n", s);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		runtest(foo, 0x01);
-		runtest(bar, "x y z");
-		return 0;
-	}
-EOF
blob - 834f069d340a58e85ed4cb565010586f52f40086 (mode 755)
blob + /dev/null
--- tests/lang/c/runtest/dryrun.t
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-export D=1
-exec ./assert-c 0 \
-	"" \
-	"foo()\nbar()\n" \
-	"" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		runtest(foo);
-		runtest(bar);
-		return 0;
-	}
-EOF
blob - 13212104deb606f59fe30c9429c6c919bb5a91ba (mode 755)
blob + /dev/null
--- tests/lang/c/runtest.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"foo called\nbar called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		runtest(foo);
-		runtest(bar);
-		return 0;
-	}
-EOF
blob - 4a85d7f1a96266036448822a33184622eae78e98 (mode 755)
blob + /dev/null
--- tests/lang/c/test/args.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo(0x01)\tOK\nbar(\"x y z\")\tOK\n" \
-	"foo(1) called\nbar(\"x y z\") called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(int i)
-	{
-		fprintf(stderr, "foo(%d) called\n", i);
-	}
-
-	void
-	bar(const char *s)
-	{
-		fprintf(stderr, "bar(\"%s\") called\n", s);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo, 0x01);
-		test(bar, "x y z");
-		return 0;
-	}
-EOF
blob - 8e102dc9a4f1d5ee853c089921a07605f793f94c (mode 755)
blob + /dev/null
--- tests/lang/c/test/dryrun.t
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-export D=1
-exec ./assert-c 0 \
-	"" \
-	"foo()\nbar()\n" \
-	"" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - 43c67f011d758bb9aa984b3471f1dbcdce4f83f2 (mode 755)
blob + /dev/null
--- tests/lang/c/test/select.t
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\n" \
-	"foo called\n" \
-	fo \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - 6d4102cfd6a99f5311df6a570c749f6b2ee1e5d4 (mode 755)
blob + /dev/null
--- tests/lang/c/test/stdin.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"xxxxxxxx\n" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"foo called\nbar called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - c3591da7d358ebfa3af2fa5b458222f0ee40a2b4 (mode 755)
blob + /dev/null
--- tests/lang/c/test/type.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"foo called\nbar called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	int
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-		return 23;
-	}
-
-	const char *
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-		return "hallo";
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - 94b76a90cd403a5b63dbf7d453faf9a7c00f872b (mode 755)
blob + /dev/null
--- tests/lang/c/test/verbose.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-export V=1
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\t-\n" \
-	"foo called\n" \
-	fo \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - ba7109402845e75ec1ace79b8b297b5fc0a21655 (mode 755)
blob + /dev/null
--- tests/lang/c/test.t
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-unset D V K X
-export CFLAGS=-I../../lang/c
-exec ./assert-c 0 \
-	"" \
-	"foo()\tOK\nbar()\tOK\n" \
-	"foo called\nbar called\n" \
-<<-EOF
-	#include <stdio.h>
-	#include "test.h"
-
-	void
-	foo(void)
-	{
-		fputs("foo called\n", stderr);
-	}
-
-	void
-	bar(void)
-	{
-		fputs("bar called\n", stderr);
-	}
-
-	int
-	main(int argc, char *argv[])
-	{
-		test(foo);
-		test(bar);
-		return 0;
-	}
-EOF
blob - /dev/null
blob + 1ed81a40db6bb4258c9362587a36f278948c4a47 (mode 755)
--- /dev/null
+++ tests/support/c/expect/abort.t
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/$(basename "$0").$$
+export CFLAGS=-I../../support/c
+export X=1
+exec ./assert-c-abort \
+	"" \
+	"" \
+	"$tst.c:7: condition failed: 2 == 3\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		expect(2 == 3);			/* aborts with X set */
+		fputs("foo running\n", stderr);	/* should not print */
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail and abort */
+		test(bar);	/* will not execute */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + fa21651e46cb438ada850eba3e9570ab298995ef (mode 755)
--- /dev/null
+++ tests/support/c/expect/false.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/fail.t.$$
+export CFLAGS=-I../../support/c
+exec ./assert-c 1 \
+	"" \
+	"foo()\tFAIL\n" \
+	"$tst.c:7: condition failed: 2 == 3\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		expect(2 == 3);
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail */
+		test(bar);	/* will not execute without K set */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + cc1ce0e840e0a5b316ab2a9c408edb349a7c7585 (mode 755)
--- /dev/null
+++ tests/support/c/expect/noabort.t
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/fail.t.$$
+export CFLAGS=-I../../support/c
+exec ./assert-c 1 \
+	"" \
+	"foo()\tFAIL\n" \
+	"$tst.c:7: condition failed: 2 == 3\n$tst.c:9: condition failed: 0\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		expect(2 == 3);		/* will print an error */
+		expect(3 == 3);
+		expect(0);		/* will print an error */
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail */
+		test(bar);	/* will not execute without K set */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + e7ee2dc43cec13ca541722ede68a85407263810c (mode 755)
--- /dev/null
+++ tests/support/c/expect/true.t
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		expect(2 == 2);
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + cd23b0d2d3332761446ea374f632e6993da5a704 (mode 755)
--- /dev/null
+++ tests/support/c/fail/abort.t
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/$(basename "$0").$$
+export CFLAGS=-I../../support/c
+export X=1
+exec ./assert-c-abort \
+	"" \
+	"" \
+	"$tst.c:7: foo failed\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fail("foo failed");		/* aborts with X set */
+		fputs("foo running\n", stderr);	/* should not print */
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail and abort */
+		test(bar);	/* will not execute */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 888cc6f392db9f5d836e4826b3dc5f5dcb4e43f6 (mode 755)
--- /dev/null
+++ tests/support/c/fail/fmt.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/fail.t.$$
+export CFLAGS=-I../../support/c
+exec ./assert-c 1 \
+	"" \
+	"foo(23)\tFAIL\n" \
+	"$tst.c:7: foo(23) failed\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(int i)
+	{
+		fail("foo(%d) failed", i);
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo, 23);	/* expected to fail */
+		test(bar);	/* will not execute without K set */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + c2439900fca3fe951a478841ac369a6c71c79d58 (mode 755)
--- /dev/null
+++ tests/support/c/fail/keepgoing.t
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/fail.t.$$
+export CFLAGS=-I../../support/c
+export K=1
+exec ./assert-c 0 \
+	"" \
+	"foo()\tFAIL\nbar()\tOK\n" \
+	"$tst.c:7: foo failed\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fail("foo failed");
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail */
+		test(bar);	/* will still execute with K set */
+		return 0;	/* with K, we will exit 0! */
+	}
+EOF
blob - /dev/null
blob + b859cd9901ee4ab31df751aa9dc576a8286bdd33 (mode 755)
--- /dev/null
+++ tests/support/c/fail/noabort.t
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/fail.t.$$
+export CFLAGS=-I../../support/c
+exec ./assert-c 1 \
+	"" \
+	"foo()\tFAIL\n" \
+	"$tst.c:7: foo failed\nfoo running\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fail("foo failed");		/* does not abort */
+		fputs("foo running\n", stderr);	/* this should still print */
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail */
+		test(bar);	/* will not execute without K set */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + ecdce8d658efcd2db99f20676a2d87af18fb2919 (mode 755)
--- /dev/null
+++ tests/support/c/fail.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export tst=/tmp/fail.t.$$
+export CFLAGS=-I../../support/c
+exec ./assert-c 1 \
+	"" \
+	"foo()\tFAIL\n" \
+	"$tst.c:7: foo failed\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fail("foo failed");
+	}
+
+	void
+	bar(void)
+	{
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);	/* expected to fail */
+		test(bar);	/* will not execute without K set */
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 7cfdd8b173b2cf40b3d8e0b3ca6a520688bf427b (mode 755)
--- /dev/null
+++ tests/support/c/rem.t
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+set -e
+
+unset D V K X
+export CFLAGS=-I../../support/c
+
+# rem() ignored when not V
+./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"main\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		rem("foo called");
+	}
+
+	void
+	bar(void)
+	{
+		rem("bar called");
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		fputs("main\n", stderr);
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
+
+# rem() prints when V set
+V=1 \
+./assert-c 0 \
+	"" \
+	"\tfoo called\nfoo()\tOK\n\tbar called\nbar()\tOK\n" \
+	"main\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		rem("foo called");
+	}
+
+	void
+	bar(void)
+	{
+		rem("bar called");
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		fputs("main\n", stderr);
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 03c8e5d8614c0c07825bf14726b56f7154a1a2ad (mode 755)
--- /dev/null
+++ tests/support/c/runtest/argcv.t
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"foo called\nbar called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	void
+	runtests(void)
+	{
+		/* argc, argv not needed */
+		runtest(foo);
+		runtest(bar);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		runtests();
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 61beccb4c883dcc77a432e757b455e804acbf06b (mode 755)
--- /dev/null
+++ tests/support/c/runtest/args.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo(0x01)\tOK\nbar(\"x y z\")\tOK\n" \
+	"foo(1) called\nbar(\"x y z\") called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(int i)
+	{
+		fprintf(stderr, "foo(%d) called\n", i);
+	}
+
+	void
+	bar(const char *s)
+	{
+		fprintf(stderr, "bar(\"%s\") called\n", s);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		runtest(foo, 0x01);
+		runtest(bar, "x y z");
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 0f6e59ebe2b6ae70ff493a585841cc3eeb75946d (mode 755)
--- /dev/null
+++ tests/support/c/runtest/dryrun.t
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+export D=1
+exec ./assert-c 0 \
+	"" \
+	"foo()\nbar()\n" \
+	"" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		runtest(foo);
+		runtest(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + bb5b03a7a35befc8e5c8b82429405f01bb236e9c (mode 755)
--- /dev/null
+++ tests/support/c/runtest.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"foo called\nbar called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		runtest(foo);
+		runtest(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 6f25e8aac399af966b06a5349c3aef4d0158a4cd (mode 755)
--- /dev/null
+++ tests/support/c/test/args.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo(0x01)\tOK\nbar(\"x y z\")\tOK\n" \
+	"foo(1) called\nbar(\"x y z\") called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(int i)
+	{
+		fprintf(stderr, "foo(%d) called\n", i);
+	}
+
+	void
+	bar(const char *s)
+	{
+		fprintf(stderr, "bar(\"%s\") called\n", s);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo, 0x01);
+		test(bar, "x y z");
+		return 0;
+	}
+EOF
blob - /dev/null
blob + c02d736abc51c20a8e1cebcbfb69dc16a4d090c2 (mode 755)
--- /dev/null
+++ tests/support/c/test/dryrun.t
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+export D=1
+exec ./assert-c 0 \
+	"" \
+	"foo()\nbar()\n" \
+	"" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 40992bdba3e6c7e0777f8f23f15487d2bc8bd0bd (mode 755)
--- /dev/null
+++ tests/support/c/test/select.t
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\n" \
+	"foo called\n" \
+	fo \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + faa99f5343dec03a45a18aec575fc13bd5e332a6 (mode 755)
--- /dev/null
+++ tests/support/c/test/stdin.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"xxxxxxxx\n" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"foo called\nbar called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + bd7ef14e517f6e9a8248be92bc22c60933b0202a (mode 755)
--- /dev/null
+++ tests/support/c/test/type.t
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"foo called\nbar called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	int
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+		return 23;
+	}
+
+	const char *
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+		return "hallo";
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 259fec1985dbb7d0ed4a9cc906fc8ec7a16ed4c7 (mode 755)
--- /dev/null
+++ tests/support/c/test/verbose.t
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+export V=1
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\t-\n" \
+	"foo called\n" \
+	fo \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF
blob - /dev/null
blob + 817644abedae4bb908ac4dbb2d90272ddfee5b66 (mode 755)
--- /dev/null
+++ tests/support/c/test.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+unset D V K X
+export CFLAGS=-I../../support/c
+exec ./assert-c 0 \
+	"" \
+	"foo()\tOK\nbar()\tOK\n" \
+	"foo called\nbar called\n" \
+<<-EOF
+	#include <stdio.h>
+	#include "test.h"
+
+	void
+	foo(void)
+	{
+		fputs("foo called\n", stderr);
+	}
+
+	void
+	bar(void)
+	{
+		fputs("bar called\n", stderr);
+	}
+
+	int
+	main(int argc, char *argv[])
+	{
+		test(foo);
+		test(bar);
+		return 0;
+	}
+EOF