-#include <stdint.h>
+/**
+ Aardvark Test Framework - A minimalistic unit testing framework for C.
+
+ Copyright 2014 Michael D. Lowis
+
+ Permission to use, copy, modify, and/or distribute this software
+ for any purpose with or without fee is hereby granted, provided
+ that the above copyright notice and this permission notice appear
+ in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+ OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ PERFORMANCE OF THIS SOFTWARE.
+*/
+#ifndef ATF_H
+#define ATF_H
+
+#include <stddef.h>
#include <stdbool.h>
+#include <setjmp.h>
+#include <assert.h>
+
+extern char* Curr_Test;
+void atf_init(int argc, char** argv);
+void atf_test_start(char* file, unsigned int line, char* name);
+bool atf_test_assert(bool success, char* expr_str, char* file, int line);
+void atf_test_fail(char* expr, char* file, int line);
+int atf_print_results(void);
+
+#define IGNORE(msg) \
+ printf("%s:%d:%s:IGNORE:\n\t%s\n", __FILE__, __LINE__, Curr_Test, msg); break
+
+#define CHECK(expr) \
+ if(atf_test_assert((expr), #expr, __FILE__, __LINE__)) break
+
+#define CHECK_EXITCODE(code) \
+ CHECK(ExitCode == code)
+
+#define TEST_SUITE(name) \
+ void name(void)
+
+#define TEST(desc) \
+ for(atf_test_start(__FILE__,__LINE__,#desc); Curr_Test != NULL; Curr_Test = NULL)
+
+#define RUN_TEST_SUITE(name) \
+ name();
+
+#define RUN_EXTERN_TEST_SUITE(name) \
+ do { extern TEST_SUITE(name); RUN_TEST_SUITE(name); } while(0)
+
+#define EXPECT_EXIT \
+ if ((ExitExpected = true, 0 == setjmp(ExitPad)))
+
+/* Function Definitions
+ *****************************************************************************/
+#ifdef INCLUDE_DEFS
+#include <stdio.h>
#include <stdlib.h>
+#ifndef NO_SIGNALS
+#include <signal.h>
+#endif
+
+char* Curr_Test = NULL;
+char* Curr_File = NULL;
+char* Suite_Path = NULL;
+unsigned int Curr_Line = 0;
+static unsigned int Total = 0;
+static unsigned int Failed = 0;
+bool ExitExpected;
+int ExitCode;
+jmp_buf ExitPad;
+
+#ifndef NO_SIGNALS
+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:0:%s:CRASH (signal: %d - %s)\n", Curr_File, Curr_Line, Curr_Test, sig, sig_name);
+ Failed++;
+ (void)atf_print_results();
+ _Exit(1);
+}
+#endif
+
+void atf_init(int argc, char** argv) {
+ /* I reserve the right to use these later */
+ Suite_Path = argv[0];
+ (void)argc;
+
+#ifndef NO_SIGNALS
+ /* Init signal handler */
+ signal(SIGABRT, handle_signal);
+ signal(SIGBUS, handle_signal);
+ signal(SIGFPE, handle_signal);
+ signal(SIGILL, handle_signal);
+ signal(SIGSEGV, handle_signal);
+ signal(SIGSYS, handle_signal);
+#endif
+}
+
+void atf_test_start(char* file, unsigned int line, char* name) {
+ Curr_File = file;
+ Curr_Line = line;
+ Curr_Test = name;
+ Total++;
+}
+
+bool atf_test_assert(bool success, char* expr, char* file, int line) {
+ bool failed = !success;
+ if (failed) atf_test_fail(expr,file,line);
+ return failed;
+}
+
+void atf_test_fail(char* expr, char* file, int line) {
+ Failed++;
+ printf("%s:%d:0:%s:FAIL:( %s )\n", file, line, Curr_Test, expr); \
+}
+
+int atf_print_results(void) {
+ int count = printf("\n%s\n", Suite_Path);
+ for (int i = 1; i < count; i++)
+ {
+ putchar('-');
+ }
+ static const char* results_string =
+ "\nTotal: %d"
+ "\nPassed: %d"
+ "\nFailed: %d"
+ "\n\n";
+ printf(results_string, Total, Total - Failed, Failed);
+ return Failed;
+}
+
+void exit(int code) {
+ if (ExitExpected) {
+ ExitCode = code;
+ ExitExpected = false;
+ longjmp(ExitPad, 1);
+ } else {
+ assert(!"Unexpected exit. Something went wrong");
+ }
+}
-#ifdef ATF_TEST
- #define main MAIN
- #define UNITTEST(name,...) void name(void)
-#else
- #define UNITTEST(name,...) static inline void name(void)
+#undef INCLUDE_DEFS
#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);
+#endif /* ATF_H */
+++ /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_File = file;
- Curr_Line = line;
- 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)
-{
- (void)runtestn;
- unsigned int seed = (argc >= 2 ? strtoul(argv[1], 0, 0) : (unsigned int)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="${CC:-cc}"
-"$cc" -DATF_TEST -g -o runner -O0 "$@" "$runner" && ./runner
-#rm -f runner "$runner"