From: Mike Lowis Date: Tue, 25 Apr 2023 16:58:00 +0000 (-0400) Subject: setup unit test framework X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=a8c25afb7fddc7332f9fe32f49c6568258502574;p=proto%2Faos.git setup unit test framework --- diff --git a/Makefile b/Makefile index 582845d..371b9e6 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ -.PHONY: all libs bins rules clean +.PHONY: all libs bins tests rules clean -all: libs bins +all: bins clean: rm -rf $(OUTDIR) rules.mk -bins: libs +bins: libs tests + +tests: + for f in build/test/*; do $$f; done rules: ./mkrules $(OUTDIR) @@ -16,3 +19,4 @@ rules.mk: include config.mk include rules.mk include bin/rules.mk +include test/rules.mk diff --git a/inc/atf.h b/inc/atf.h index 5ce11c3..af5d552 100644 --- a/inc/atf.h +++ b/inc/atf.h @@ -1,39 +1,164 @@ -#include +/** + 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 #include +#include +#include + +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 #include +#ifndef NO_SIGNALS +#include +#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 */ diff --git a/mkrules b/mkrules index 2fe8e69..c053b03 100755 --- a/mkrules +++ b/mkrules @@ -24,6 +24,7 @@ libs="" mkdir -p "$OUTDIR/bin/" mkdir -p "$OUTDIR/lib/" mkdir -p "$OUTDIR/obj/" + mkdir -p "$OUTDIR/test/" # Generate rules for all libraries for lib in lib/*; do @@ -55,4 +56,12 @@ libs="" printf "\t\$(BINARY)\n" puts "bins: \$(OUTDIR)/${bin%/}" done + + # Generate rules for test binaries + for test in test/*/ ; do + find "${test%/}" -name '*.c' | ObjectRules "\$(OUTDIR)/${test%/}" + puts "\$(OUTDIR)/${test%/}: | \$(libs)" + printf "\t\$(BINARY)\n" + puts "tests: \$(OUTDIR)/${test%/}" + done } >> rules.mk diff --git a/test/libui/window_show.c b/test/libui/window_show.c index 7df0e54..701f193 100644 --- a/test/libui/window_show.c +++ b/test/libui/window_show.c @@ -1,16 +1,25 @@ -#include -#include -#include -#include "libui_impl.h" -#include "mocks.h" +//#include +//#include +//#include +//#include "libui_impl.h" +//#include "mocks.h" + +#define INCLUDE_DEFS #include "atf.h" -char* ARGV0; +// +//char* ARGV0; +// +//UNITTEST(window_show_should_show_the_window) +//{ +// MockInit(); +// Expect_XMapWindow(NULL, 0, 0); +// Expect_XSync(NULL, False, 0); +// win_show(&(UIWin){0}); +//} -UNITTEST(window_show_should_show_the_window) +int main(int argc, char** argv) { - MockInit(); - Expect_XMapWindow(NULL, 0, 0); - Expect_XSync(NULL, False, 0); - win_show(&(UIWin){0}); + atf_init(argc, argv); + return atf_print_results(); } diff --git a/tools/atf b/tools/atf deleted file mode 100755 index aada39b..0000000 --- a/tools/atf +++ /dev/null @@ -1,353 +0,0 @@ -#!/usr/bin/env bash - -runner="runner.c" -declare -a sources -header=$(cat<<-EOF -#include -#include -#include - -#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 -#include -#include -#include -#include -#include -#include -#include - -#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"