]> git.mdlowis.com Git - proto/anvil.git/commitdiff
added atf test framework
authorMichael D. Lowis <mike@mdlowis.com>
Mon, 13 Apr 2020 02:55:10 +0000 (22:55 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Mon, 13 Apr 2020 02:55:10 +0000 (22:55 -0400)
anvil.h
atf [new file with mode: 0755]
build.sh
test/atf.h [new file with mode: 0644]
test/tile.c [new file with mode: 0644]

diff --git a/anvil.h b/anvil.h
index ca33033fb8ede88a4d386a687c371e72c87a13f5..fb2685d1c8856e4012353eed899500e61ecd4384 100644 (file)
--- a/anvil.h
+++ b/anvil.h
@@ -1,3 +1,6 @@
+#ifndef ANVIL_H
+#define ANVIL_H
+
 #include <X11/X.h>
 #include <X11/Xlib.h>
 #include <X11/keysym.h>
@@ -71,7 +74,7 @@ typedef struct Client {
 
 typedef struct Column {
     struct Column* next;
-    int mode, width, nclients;
+    int width;
     Client* focused;
     Client* clients;
 } Column;
@@ -200,3 +203,5 @@ void* ecalloc(size_t n, size_t sz);
 void xfree(void* p);
 Atom atom(char* str);
 void sendmsg(Window win, Atom proto, Atom type);
+
+#endif
\ No newline at end of file
diff --git a/atf b/atf
new file mode 100755 (executable)
index 0000000..bd37bd2
--- /dev/null
+++ b/atf
@@ -0,0 +1,354 @@
+#!/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(unsigned int seed)
+{
+    printf("%lu/%lu tests passed with seed %d\n" , Total - Failed, Total, seed);
+    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++;
+    printf("%lu/%lu tests passed\n" , Total - Failed, Total);
+    _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));
+    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(seed);
+}
+EOF
+
+cc="${CC:-cc}"
+"$cc" -DATF_TEST -g -o runner -O0 "$@" "$runner" && ./runner
+error="$?"
+rm -f runner "$runner"
+exit $error
index ab48db1b7b9c9a33809ee974227201c3894a5669..8e8f4a6c80b0f0393893546078cd5806c570222b 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -1,10 +1,13 @@
 #!/bin/sh
-cc -g -Wall -Wextra -Werror -I/usr/X11R6/include -L/usr/X11R6/lib -o anvil *.c -lX11 -lXinerama -lm
+cflags="-g -Wall -Wextra -Werror"
+incpath="-I/usr/X11R6/include -Itest/"
+libpath="-L/usr/X11R6/lib"
+libs="-lX11 -lXinerama -lm"
+
+cc $cflags $incpath $libpath -o anvil ./*.c $libs && ./atf $incpath test/*.c
 error="$?"
-ctags *
-grep -n 'TODO' *.[ch]| sed -e 's/: \+/: /' -e 's/\/\* *//g' -e 's/ *\*\/$//' -e 's/TODO: *//'
-printf "\a"
-if [ $error -ne 0 ]; then
-    printf "\a"
-fi
-exit "$error"
+
+grep -n 'TODO' ./*.[ch]| sed -e 's/: \+/: /' -e 's/\/\* *//g' -e 's/ *\*\/$//' -e 's/TODO: *//'
+ctags ./*
+
+exit $error
diff --git a/test/atf.h b/test/atf.h
new file mode 100644 (file)
index 0000000..5ce11c3
--- /dev/null
@@ -0,0 +1,39 @@
+#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);
diff --git a/test/tile.c b/test/tile.c
new file mode 100644 (file)
index 0000000..f3ea4f2
--- /dev/null
@@ -0,0 +1,53 @@
+#include "../tile.c"
+#include "../list.c"
+#include "atf.h"
+
+XConf X;
+
+/**** Stub Functions ****/
+void client_adjust(Client* c)
+{
+    (void)c;
+}
+
+void client_setshade(Client* c, int shade)
+{
+    (void)c, (void)shade;
+}
+
+void mons_layer(Monitor* mon)
+{
+    (void)mon;
+}
+
+void mouse_totitle(Client* c)
+{
+    (void)c;
+}
+
+/**** Stub Functions ****/
+
+Monitor TestMon = {
+    .x = 0, .y = 0,
+    .w = 100, .h = 100,
+    .midx = 50, .midy = 50
+};
+
+Location TestLoc = {
+    .monitor = &TestMon,
+    .workspace = &(Workspace){
+        .columns = &(Column){
+            .width = 50,
+            .next = &(Column){
+                .width = 50
+            }
+        }
+    },
+
+};
+
+/**** Stub Functions ****/
+UNITTEST(Foo)
+{
+    CHECK(1==9);
+}
\ No newline at end of file