--- /dev/null
+#!/usr/bin/env bash
+
+runner="runner.c"
+declare -a sources
+header=$(cat<<-EOF
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#ifdef ATF_TEST
+ #define main MAIN
+ #define UNITTEST(name,...) void name(void)
+#else
+ #define UNITTEST(name,...) static inline void name(void)
+#endif
+
+#define CHECK(cond) Assert(cond, #cond, __FILE__, __LINE__)
+
+typedef struct Value {
+ struct Value* next;
+ void (*showfn)(struct Value* val);
+ long ndata;
+ long data[];
+} Value;
+
+void Assert(int cond, char* condstr, char* file, int line);
+void* ValueAlloc(void (*showfn)(Value* val), size_t num, size_t sz, void* data);
+long RandR(long from, long to);
+long Rand(void);
+
+void ShowLong(Value* val);
+void ShowBool(Value* val);
+void ShowChar(Value* val);
+void ShowString(Value* val);
+
+long MkLongR(long from, long to);
+long MkLong(void);
+uint8_t MkU8(void);
+uint16_t MkU16(void);
+uint32_t MkU32(void);
+bool MkBool(void);
+char MkAsciiChar(void);
+char* MkAsciiStringL(size_t len);
+char* MkAsciiString(void);
+EOF
+)
+
+# get the list of sources
+for i do
+ case "$i" in
+ *.c) sources+=("$i") ;;
+ --header)
+ printf "%s\n" "$header"
+ exit 0
+ ;;
+ esac
+done
+
+cat <<-EOF > "$runner"
+#include "atf.h"
+#undef main
+#include <setjmp.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+
+#define RUN(file, line, name, ntrials) \\
+ void name(void); \\
+ runtestn(file, line, #name, name, ntrials)
+
+static char* Curr_File = 0;
+static char* Curr_Test = 0;
+static char* Curr_Expr = 0;
+static unsigned int Curr_Line = 0;
+static Value* Curr_Values;
+static jmp_buf Jump_Buf;
+static unsigned long Total = 0;
+static unsigned long Failed = 0;
+static uintptr_t Heap_Buf[1024*1024];
+static uintptr_t* Heap_Top = Heap_Buf;
+static uintptr_t* Heap_Base = Heap_Buf;
+static uintptr_t* Heap_End = Heap_Buf + sizeof(Heap_Buf)/sizeof(uintptr_t) - 1;
+enum {
+ FAIL_ASSERT = 1,
+ FAIL_OOM = 2
+};
+
+/* Basic Runtime Functions
+ ****************************/
+static int print_results(void)
+{
+ printf("%lu/%lu tests passed\n" , Total - Failed, Total);
+ return Failed;
+}
+
+static void print_values(void)
+{
+ Value* values = 0;
+ while (Curr_Values)
+ {
+ Value* val = Curr_Values;
+ Curr_Values = val->next;
+ val->next = values;
+ values = val;
+ }
+
+ while (values)
+ {
+ printf(" -> arg: ");
+ values->showfn(values);
+ values = values->next;
+ }
+}
+
+void* malloc(size_t size)
+{
+ void* ptr = Heap_Top;
+ size_t num_words = ((size & ~0x7) + ((size & 7) ? 1 : 0)) / sizeof(uintptr_t) + 1;
+ *Heap_Top = size;
+ Heap_Top += num_words;
+ if (Heap_Top > Heap_End)
+ {
+ Heap_Top = Heap_Base; // Reset heap in case printf mallocs.
+ fprintf(stderr,"%s:%d: MEM %s out of memory\n", Curr_File, Curr_Line, Curr_Test);
+ longjmp(Jump_Buf, FAIL_OOM);
+ }
+ else
+ {
+ memset(ptr, 0, size);
+ }
+ return ptr;
+}
+
+void* calloc(size_t num, size_t size)
+{
+ return malloc(num * size);
+}
+
+void* realloc(void* ptr, size_t new_size)
+{
+ uintptr_t old_size = *((uintptr_t*)ptr - 1);
+ void* newptr = malloc(new_size);
+ memcpy(newptr, ptr, old_size);
+ return newptr;
+}
+
+void free(void* ptr)
+{
+ (void)ptr; /* simply reset the buffer after each test */
+}
+
+void Assert(int cond, char* condstr, char* file, int line)
+{
+ if (!cond)
+ {
+ Curr_Expr = condstr;
+ longjmp(Jump_Buf, 1);
+ }
+}
+
+static int runtest(void (*fn)(void))
+{
+ Curr_Values = 0;
+ Heap_Top = Heap_Base;
+ int code = setjmp(Jump_Buf);
+ if (code == 0)
+ {
+ fn();
+ }
+ return code;
+}
+
+static void runtestn(char* file, int line, char* fnname, void (*fn)(void), int ntrials)
+{
+ Curr_File = file;
+ Curr_Line = line;
+ Curr_Test = fnname;
+ Total++;
+ for (int i = 0; i < ntrials; i++)
+ {
+ int fail = runtest(fn);
+ if (fail != 0)
+ {
+ Failed++;
+ if (fail == FAIL_ASSERT)
+ {
+ if (ntrials == 1)
+ {
+ printf("%s:%d: FAIL for test %s \n -> CHECK( %s )\n", Curr_File, Curr_Line, Curr_Test, Curr_Expr);
+ }
+ else
+ {
+ printf("%s:%d: FAIL on trial %d/%d for test %s\n", Curr_File, Curr_Line, i, ntrials, Curr_Test);
+ print_values();
+ }
+ }
+ break;
+ }
+ }
+}
+
+static void handle_signal(int sig)
+{
+ /* Determine the signal name */
+ char* sig_name = NULL;
+ switch(sig)
+ {
+ case SIGABRT: sig_name = "SIGABRT"; break;
+ case SIGBUS: sig_name = "SIGBUS"; break;
+ case SIGFPE: sig_name = "SIGFPE"; break;
+ case SIGILL: sig_name = "SIGILL"; break;
+ case SIGSEGV: sig_name = "SIGSEGV"; break;
+ case SIGSYS: sig_name = "SIGSYS"; break;
+ /* If we don't recognize it then just return and let the default handler
+ catch it. */
+ default: return;
+ }
+ /* Error and exit. No summary will be printed but the user will know which
+ test has crashed. */
+ fprintf(stderr,"%s:%d: CRASH %s (signal: %d - %s)\n", Curr_File, Curr_Line, Curr_Test, sig, sig_name);
+ Failed++;
+ (void)print_results();
+ _Exit(1);
+}
+
+/* Property Testing Functions
+ ****************************/
+void* ValueAlloc(void (*showfn)(Value* val), size_t num, size_t sz, void* data)
+{
+ Value* value = calloc(num, sizeof(Value) + sz);
+ value->showfn = showfn;
+ value->ndata = num;
+ value->next = Curr_Values;
+ Curr_Values = value;
+ if (data)
+ {
+ memcpy(value->data, data, num * sz);
+ }
+ return &(value->data[0]);
+}
+
+long RandR(long from, long to)
+{
+ return ((rand() % (to - from + 1)) + from);
+}
+
+long Rand(void)
+{
+ return rand();
+}
+
+void ShowLong(Value* val)
+{
+ printf("%ld\n", (val->data[0]));
+}
+
+void ShowBool(Value* val)
+{
+ printf("%s\n", (val->data[0] ? "true" : "false"));
+}
+
+void ShowChar(Value* val)
+{
+ printf("'%c'\n", (char)(val->data[0]));
+}
+
+void ShowString(Value* val)
+{
+ printf("'%s'\n", (char*)(val->data));
+}
+
+long MkLongR(long from, long to)
+{
+ return *((long*)ValueAlloc(ShowLong, 1, sizeof(long), &(long){RandR(from, to)}));
+}
+
+long MkLong(void)
+{
+ return *((long*)ValueAlloc(ShowLong, 1, sizeof(long), &(long){Rand()}));
+}
+
+uint8_t MkU8(void)
+{
+ return MkLongR(0, UINT8_MAX);
+}
+
+uint16_t MkU16(void)
+{
+ return MkLongR(0, UINT16_MAX);
+}
+
+uint32_t MkU32(void)
+{
+ return MkLongR(0, UINT32_MAX);
+}
+
+bool MkBool(void)
+{
+ return *((bool*)ValueAlloc(ShowBool, 1, sizeof(bool), &(bool){RandR(0, 1)}));
+}
+
+char MkAsciiChar(void)
+{
+ return *((char*)ValueAlloc(ShowChar, 1, sizeof(char), &(char){RandR(32, 127)}));
+}
+
+char* MkAsciiStringL(size_t len) {
+ char* val = ValueAlloc(ShowString, len+1, sizeof(char), 0);
+ for (size_t i = 0; i < len; i++)
+ {
+ *(val++) = RandR(32, 127);
+ }
+ return val;
+}
+
+char* MkAsciiString(void) {
+ return MkAsciiStringL(RandR(1, 1024));
+}
+
+/* Main Routine
+ ****************************/
+int main(int argc, char** argv)
+{
+ unsigned int seed = (argc >= 2 ? strtoul(argv[1], 0, 0) : time(0));
+ printf("Seed: %u\n", seed);
+ srand(seed);
+ signal(SIGABRT, handle_signal);
+ signal(SIGBUS, handle_signal);
+ signal(SIGFPE, handle_signal);
+ signal(SIGILL, handle_signal);
+ signal(SIGSEGV, handle_signal);
+ signal(SIGSYS, handle_signal);
+ Heap_Base = Heap_Top;
+EOF
+grep -Hn '\(PROP\|UNIT\)TEST' "${sources[@]}" | sed '
+ s/^\(.*\):\(.*\):[\t ]*UNITTEST(\(.*\),\(.*\)).*$/ RUN("\1",\2,\3,\4);/
+ s/^\(.*\):\(.*\):[\t ]*UNITTEST(\(.*\)).*$/ RUN("\1",\2,\3,1);/
+' >> "$runner"
+cat <<-EOF >> "$runner"
+ return print_results();
+}
+EOF
+
+cc -D ATF_TEST -g -o runner -O0 "$@" "$runner" && ./runner
+rm -f runner "$runner"