]> git.mdlowis.com Git - proto/aos.git/commitdiff
started adding tests and mocks
authorMichael D. Lowis <mike@mdlowis.com>
Sun, 4 Oct 2020 02:49:57 +0000 (22:49 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Sun, 4 Oct 2020 02:49:57 +0000 (22:49 -0400)
config.mk
inc/atf.h [new file with mode: 0644]
mocks [new file with mode: 0644]
mocks.h [deleted file]
src/liba/argv0.c [new file with mode: 0644]
src/libui/window_show.c
test/libui/mocks.h [new file with mode: 0644]
test/libui/window_show.c [new file with mode: 0644]
tools/atf [new file with mode: 0755]
tools/mock.rb

index aa01ec985b42e2c7262ab53aa2b786dafe68d82f..7f7d7ab62c351a390ceb823d7772bc684a3af80b 100644 (file)
--- 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 (file)
index 0000000..5ce11c3
--- /dev/null
+++ b/inc/atf.h
@@ -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/mocks b/mocks
new file mode 100644 (file)
index 0000000..a5975f7
--- /dev/null
+++ b/mocks
@@ -0,0 +1,6 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xft/Xft.h>
+
+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 (file)
index 514163d..0000000
--- a/mocks.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xft/Xft.h>
-
-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 (file)
index 0000000..442159c
--- /dev/null
@@ -0,0 +1,4 @@
+#include <stdc.h>
+#include <liba.h>
+
+char* ARGV0;
\ No newline at end of file
index c034c998cf321f07ad653beacb7afce40f951abc..2a1322a9293f08cbc799a976a0fc90c9f4ef9d93 100644 (file)
@@ -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 (file)
index 0000000..c71d037
--- /dev/null
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+
+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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xft/Xft.h>
+
+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 (file)
index 0000000..7df0e54
--- /dev/null
@@ -0,0 +1,16 @@
+#include <stdc.h>
+#include <liba.h>
+#include <libui.h>
+#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 (executable)
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 <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"
index 2d8b6498205fd7314ae772538bce5bfdb6fe5445..875d0de57016b678cb8a6bdbe61287021045887c 100755 (executable)
@@ -1,15 +1,20 @@
-#!/bin/env ruby
+#!/usr/bin/env ruby
 
 HEADER = <<-eos
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
 
 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