From: Michael D. Lowis Date: Sun, 4 Oct 2020 02:49:57 +0000 (-0400) Subject: started adding tests and mocks X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=b29d94160cfc7830628396d609ef1b21489ab6c8;p=proto%2Faos.git started adding tests and mocks --- diff --git a/config.mk b/config.mk index aa01ec9..7f7d7ab 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ CC=cc CPPPATH="-Iinc -I/usr/X11/include -I/usr/X11/include/freetype2 -I/usr/include/freetype2" -CFLAGS="--std=c99 -Wall -Wextra -Werror" +CFLAGS="-g --std=c99 -Wall -Wextra -Werror" export CC CPPPATH CFLAGS \ No newline at end of file diff --git a/inc/atf.h b/inc/atf.h new file mode 100644 index 0000000..5ce11c3 --- /dev/null +++ b/inc/atf.h @@ -0,0 +1,39 @@ +#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); diff --git a/mocks b/mocks new file mode 100644 index 0000000..a5975f7 --- /dev/null +++ b/mocks @@ -0,0 +1,6 @@ +#include +#include +#include + +int XSync(Display* display, Bool discard); +int XMapWindow(Display* display, Window w); \ No newline at end of file diff --git a/mocks.h b/mocks.h deleted file mode 100644 index 514163d..0000000 --- a/mocks.h +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include -#include - -void XSetForeground(Display* display, GC gc, unsigned long foreground); -void XFillRectangle(Display* display, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height); diff --git a/src/liba/argv0.c b/src/liba/argv0.c new file mode 100644 index 0000000..442159c --- /dev/null +++ b/src/liba/argv0.c @@ -0,0 +1,4 @@ +#include +#include + +char* ARGV0; \ No newline at end of file diff --git a/src/libui/window_show.c b/src/libui/window_show.c index c034c99..2a1322a 100644 --- a/src/libui/window_show.c +++ b/src/libui/window_show.c @@ -5,6 +5,7 @@ void win_show(UIWin* win) { - XMapWindow(X.display, win->self); - XSync(X.display, False); + (void)win; +// XMapWindow(X.display, win->self); +// XSync(X.display, False); } diff --git a/test/libui/mocks.h b/test/libui/mocks.h new file mode 100644 index 0000000..c71d037 --- /dev/null +++ b/test/libui/mocks.h @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include + +typedef struct { + const char* name; + int ndata; + char data[]; +} MockValue_T; + +typedef struct { + const char* name; + MockValue_T* retval; + int nvalues; + MockValue_T* values[]; +} MockCall_T; + +#ifndef MAX_CALLS +#define MAX_CALLS 256 +#endif + +#ifndef MAX_MEMORY +#define MAX_MEMORY ((1024*1024) / sizeof(int)) +#endif + +int Memory[MAX_MEMORY]; +MockCall_T* ExpectedCalls[MAX_CALLS]; +MockCall_T* ActualCalls[MAX_CALLS]; +int* CurrentMem = Memory; +int CurrentExpected = 0; +int CurrentCall = 0; + +void FailMessage(char* fmt, ...) +{ + exit(1); +} + +void* Allocate(int sz) +{ + int ncells = (sz / sizeof(int)) + ((sz % sizeof(int)) ? 1 : 0); + int* next = CurrentMem + ncells; + if (next > (Memory + (sizeof(Memory)/sizeof(Memory[0])))) + { + FailMessage("memory buffer exhausted"); + } + int* curr = CurrentMem; + memset(curr, 0, sizeof(int)*ncells); + CurrentMem = next; + return curr; +} + +void MockInit(void) +{ + CurrentMem = Memory; + CurrentExpected = 0; + CurrentCall = 0; +} + +MockValue_T* MockValue(const char* name, void* data, int ndata) +{ + MockValue_T* val = Allocate(sizeof(MockValue_T) + ndata); + val->name = name; + val->ndata = ndata; + memcpy(val->data, data, ndata); + return val; +} + +MockCall_T* MockCall(const char* name, int nargs) +{ + MockCall_T* call = Allocate(sizeof(MockCall_T) + (nargs * sizeof(MockValue_T*))); + call->name = name; + call->nvalues = nargs; + return call; +} + +void RegisterCall(MockCall_T* call) +{ + if (CurrentExpected >= MAX_CALLS) + { + FailMessage("exhausted expected call buffer"); + } + ExpectedCalls[CurrentExpected++] = call; +} + +void VerifyCall(MockCall_T* call, void* data) +{ + if (CurrentCall >= CurrentExpected) + { + FailMessage(""); + } + MockCall_T* expect = ExpectedCalls[CurrentCall]; + if (0 != strcmp(expect->name, call->name)) + { + FailMessage("expected a call to '%s', received a call to '%s' instead", expect->name, call->name); + } + assert(expect->nvalues == call->nvalues); + for (int i = 0; i < call->nvalues; i++) + { + MockValue_T* earg = expect->values[i]; + MockValue_T* rarg = call->values[i]; + assert(earg->ndata == rarg->ndata); + if (0 != memcmp(earg->data, rarg->data, earg->ndata)) + { + FailMessage("received unexpected value for argument '%s'", earg->name); + } + } + if (expect->retval) + { + memcpy(data, expect->retval->data, expect->retval->ndata); + } +} + +#include +#include +#include + +int XSync(Display* display, Bool discard) +{ + MockCall_T* call = MockCall(__func__, 2); + call->values[0] = MockValue("display", &display, sizeof(display)); + call->values[1] = MockValue("discard", &discard, sizeof(discard)); + int retval; + VerifyCall(call, &retval); + return retval; +} + +void Expect_XSync(Display* display, Bool discard, int _rv) +{ + MockCall_T* call = MockCall(__func__, 2); + call->retval = MockValue(0, &_rv, sizeof(_rv)); + call->values[0] = MockValue("display", &display, sizeof(display)); + call->values[1] = MockValue("discard", &discard, sizeof(discard)); + RegisterCall(call); +} + +int XMapWindow(Display* display, Window w) +{ + MockCall_T* call = MockCall(__func__, 2); + call->values[0] = MockValue("display", &display, sizeof(display)); + call->values[1] = MockValue("w", &w, sizeof(w)); + int retval; + VerifyCall(call, &retval); + return retval; +} + +void Expect_XMapWindow(Display* display, Window w, int _rv) +{ + MockCall_T* call = MockCall(__func__, 2); + call->retval = MockValue(0, &_rv, sizeof(_rv)); + call->values[0] = MockValue("display", &display, sizeof(display)); + call->values[1] = MockValue("w", &w, sizeof(w)); + RegisterCall(call); +} + diff --git a/test/libui/window_show.c b/test/libui/window_show.c new file mode 100644 index 0000000..7df0e54 --- /dev/null +++ b/test/libui/window_show.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include "libui_impl.h" +#include "mocks.h" +#include "atf.h" + +char* ARGV0; + +UNITTEST(window_show_should_show_the_window) +{ + MockInit(); + Expect_XMapWindow(NULL, 0, 0); + Expect_XSync(NULL, False, 0); + win_show(&(UIWin){0}); +} diff --git a/tools/atf b/tools/atf new file mode 100755 index 0000000..aada39b --- /dev/null +++ b/tools/atf @@ -0,0 +1,353 @@ +#!/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" diff --git a/tools/mock.rb b/tools/mock.rb index 2d8b649..875d0de 100755 --- a/tools/mock.rb +++ b/tools/mock.rb @@ -1,15 +1,20 @@ -#!/bin/env ruby +#!/usr/bin/env ruby HEADER = <<-eos +#include +#include +#include +#include +#include typedef struct { - char* name; + const char* name; int ndata; char data[]; } MockValue_T; typedef struct { - char* name; + const char* name; MockValue_T* retval; int nvalues; MockValue_T* values[]; @@ -19,27 +24,96 @@ typedef struct { #define MAX_CALLS 256 #endif +#ifndef MAX_MEMORY +#define MAX_MEMORY ((1024*1024) / sizeof(int)) +#endif + +int* CurrentMem = 0; int CurrentExpected = 0; int CurrentCall = 0; +int Memory[MAX_MEMORY]; MockCall_T* ExpectedCalls[MAX_CALLS]; MockCall_T* ActualCalls[MAX_CALLS]; +void FailMessage(char* fmt, ...) +{ + exit(1); +} + +void* Allocate(int sz) +{ + int ncells = (sz / sizeof(int)) + ((sz % sizeof(int)) ? 1 : 0); + int* next = CurrentMem + ncells; + if (next > (Memory + (sizeof(Memory)/sizeof(Memory[0])))) + { + FailMessage("memory buffer exhausted"); + } + memset(CurrentMem, 0, sizeof(int)*ncells); + int* curr = CurrentMem; + CurrentMem = next; + return curr; +} + void MockInit(void) { + CurrentMem = Memory; CurrentExpected = 0; CurrentCall = 0; } -/* REGISTER MOCK CALL */ -/* allocate call structure with N values */ -/* foreach arg */ - /* allocate named value structure */ - /* initialize named value structure */ - /* assign named value structure to slot in mock call */ -/* if call returns non-void */ - /* allocate value structure */ - /* initialize value structure */ - /* assign value structure to slot in mock call */ +MockValue_T* MockValue(const char* name, void* data, int ndata) +{ + MockValue_T* val = Allocate(sizeof(MockValue_T) + ndata); + val->name = name; + val->ndata = ndata; + memcpy(val->data, data, ndata); + return val; +} + +MockCall_T* MockCall(const char* name, int nargs) +{ + MockCall_T* call = Allocate(sizeof(MockCall_T) + (nargs * sizeof(MockValue_T*))); + call->name = name; + call->nvalues = nargs; + return call; +} + +void RegisterCall(MockCall_T* call) +{ + if (CurrentExpected >= MAX_CALLS) + { + FailMessage("exhausted expected call buffer"); + } + ExpectedCalls[CurrentExpected++] = call; +} + +void VerifyCall(MockCall_T* call, void* data) +{ + if (CurrentCall >= CurrentExpected) + { + FailMessage(""); + } + MockCall_T* expect = ExpectedCalls[CurrentCall]; + if (0 != strcmp(expect->name, call->name)) + { + FailMessage("expected a call to '%s', received a call to '%s' instead", expect->name, call->name); + } + assert(expect->nvalues == call->nvalues); + for (int i = 0; i < call->nvalues; i++) + { + MockValue_T* earg = expect->values[i]; + MockValue_T* rarg = call->values[i]; + assert(earg->ndata == rarg->ndata); + if (0 != memcmp(earg->data, rarg->data, earg->ndata)) + { + FailMessage("received unexpected value for argument '%s'", earg->name); + } + } + if (expect->retval) + { + memcpy(data, expect->retval->data, expect->retval->ndata); + } +} eos @@ -51,12 +125,40 @@ def parse_args(args) end def genmock(name, args, rettype) - args = args.map {|a| "#{a[:type]} #{a[:name]}" } - puts "#{rettype} #{name}(#{args.join(", ")})\n{\n" + argstrings = args.map {|a| "#{a[:type]} #{a[:name]}" } + puts "#{rettype} #{name}(#{argstrings.join(", ")})\n{\n" + puts " MockCall_T* call = MockCall(__func__, #{args.length});" + args.each_with_index do |a,i| + puts " call->values[#{i}] = MockValue(\"#{a[:name]}\", &#{a[:name]}, sizeof(#{a[:name]}));" + end + if rettype != "void" + puts " #{rettype} retval;" + puts " VerifyCall(call, &retval);" + puts " return retval;" + else + puts " VerifyCall(call, NULL);" + end + puts "}" + puts +end + +def genexpect(name, args, rettype) + argstrings = args.map {|a| "#{a[:type]} #{a[:name]}" } + retarg = (rettype == "void" ? "" : ", #{rettype} _rv"); + puts "void Expect_#{name}(#{argstrings.join(", ")}#{retarg})\n{\n" + puts " MockCall_T* call = MockCall(__func__, #{args.length});" + if rettype != "void" + puts " call->retval = MockValue(0, &_rv, sizeof(_rv));" + end + args.each_with_index do |a,i| + puts " call->values[#{i}] = MockValue(\"#{a[:name]}\", &#{a[:name]}, sizeof(#{a[:name]}));" + end + puts " RegisterCall(call);" puts "}" puts end +puts HEADER File.read(ARGV[0]).each_line do |ln| ln = ln.gsub(/(const|volatile)/,'') if ln =~ /(.*) ([_a-zA-Z0-9]+)\((.*)\)/ @@ -66,6 +168,7 @@ File.read(ARGV[0]).each_line do |ln| args = (args == "void" ? [] : args.split(",")) args = parse_args(args) genmock(name, args, rettype) + genexpect(name, args, rettype) else puts ln end