diff --git a/.gitignore b/.gitignore index 41d513c..01fe5bd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build test/* !test/*.[ch] !test/Makefile +!test/Readme.md diff --git a/test/Makefile b/test/Makefile index b4aafa5..bf6fa74 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,8 +1,8 @@ CC = gcc CFLAGS := -Wall -g -I../src $(shell pkg-config --cflags glib-2.0) LFLAGS := $(shell pkg-config --libs glib-2.0) -DEPS = minunit.h ../build/lake.a TESTS = test_comment test_dlist test_env +DEPS = laketest.o ../build/lake.a all: $(TESTS) @clear @@ -11,6 +11,8 @@ all: $(TESTS) ./$$test; \ done +test: all + test_comment: $(DEPS) test_comment.o $(CC) $(CFLAGS) $(LFLAGS) $^ -o $@ diff --git a/test/Readme.md b/test/Readme.md new file mode 100644 index 0000000..61890d8 --- /dev/null +++ b/test/Readme.md @@ -0,0 +1,46 @@ +Running Tests +============= + +From the root or test directory, run all tests: + + $ make test + +From the test directory run individual tests: + + $ make test_eval && ./test_eval + +Test Suite +========== + +A custom, minimal test framework based on [MinUnit](http://www.jera.com/techinfo/jtns/jtn002.html). The entire framework is ~30 lines. Its use is very simple: + + // Shared state comes in handy + static this_t *this; + + // Tests are just like MinUnit except we use lt_assert + static char *test_this(void) + { + lt_assert("this should not be NULL", this != NULL); + ... + return 0; + } + + static char *test_that(void) + { + lt_assert("that should be greater than zero", this->that > 0); + return 0; + } + + int main(int argc, char const *argv[]) + { + // do your setup + this = create_this(); + + // run the tests exiting with 0 if all passed, non-zero if any failed + return !lt_run_tests("Description", (test_fn[]){ + test_this, + ... + test_that, + NULL // this terminator is important + }); + } diff --git a/test/laketest.c b/test/laketest.c new file mode 100644 index 0000000..9f7b9cd --- /dev/null +++ b/test/laketest.c @@ -0,0 +1,56 @@ +/** + * laketest.c + * Lake Scheme + * + * Copyright 2011 Sami Samhuri + * MIT License + * + * Based on MinUnit: http://www.jera.com/techinfo/jtns/jtn002.html + * + */ + +#include +#include "laketest.h" + +static void capture_output(void) +{ + int fd = fopen("./tmp", "w"); + close(2); + int newfd = dup(fd); + close(fd); + + fd = fopen("./tmp", "w"); + close(1); + newfd = dup(fd); + close(fd); +} + +void restore_output(void) +{ + freopen("/dev/tty", "a", stdout); + freopen("/dev/tty", "a", stderr); + unlink("./tmp"); +} + +int lt_run_tests(char *title, test_fn *tests) +{ + int pass = 0; + int n_tests = 0; + char *message = 0; + test_fn test; + printf("-- %s --\n", title); + capture_output(); + while ((test = *(tests++))) { + if ((message = test())) break; + n_tests++; + } + restore_output(); + pass = message == 0; + if (pass) { + fprintf(stderr, "PASS: %d test%s\n", n_tests, n_tests == 1 ? "" : "s"); + } + else { + fprintf(stderr, "FAIL: %s\n", message); + } + return pass; +} diff --git a/test/laketest.h b/test/laketest.h new file mode 100644 index 0000000..eceb2f8 --- /dev/null +++ b/test/laketest.h @@ -0,0 +1,28 @@ +/** + * laketest.h + * Lake Scheme + * + * Copyright 2011 Sami Samhuri + * MIT License + * + * Based on MinUnit: http://www.jera.com/techinfo/jtns/jtn002.html + * + */ + +#include + +void restore_output(void); + +#define lt_assert(message, test) do { \ + if (!(test)) { \ + restore_output(); \ + fprintf(stderr, "%s:%d assertion failed: " #test "\n", \ + __FILE__, __LINE__); \ + return message; \ + } \ + } while (0) + +typedef char *(*test_fn)(void); + +/* Returns non-zero if all passed, or zero if any failed */ +int lt_run_tests(char *title, test_fn *tests); diff --git a/test/minunit.h b/test/minunit.h deleted file mode 100644 index 9ae3aac..0000000 --- a/test/minunit.h +++ /dev/null @@ -1,18 +0,0 @@ -#include - -#define mu_assert(message, test) do { \ - if (!(test)) { \ - fprintf(stderr, "%s:%d assertion failed\n", __FILE__, __LINE__); \ - return "error: " message; \ - } \ - } while (0) - -#define mu_run_test(test) do { \ - char *message = test(); \ - tests_run++; \ - if (message) { \ - return message; \ - } \ - } while (0) - -extern int tests_run; diff --git a/test/test_comment.c b/test/test_comment.c index 785be23..b9d5b8e 100644 --- a/test/test_comment.c +++ b/test/test_comment.c @@ -8,23 +8,22 @@ */ #include -#include "minunit.h" +#include "laketest.h" #include "comment.h" #include "lake.h" #include "str.h" #define TEXT "you are not expected to understand this" -int tests_run; static LakeStr *text = NULL; /* LakeComment *comment_make(LakeStr *text) */ static char *test_comment_make(void) { LakeComment *comment = comment_make(text); - mu_assert("type is not TYPE_COMM", IS(TYPE_COMM, comment)); - mu_assert("value size is incorrect", VAL_SIZE(comment) == sizeof(LakeComment)); - mu_assert("comment text is incorrect", str_equal(text, COMM_TEXT(comment))); + lt_assert("type is not TYPE_COMM", IS(TYPE_COMM, comment)); + lt_assert("value size is incorrect", VAL_SIZE(comment) == sizeof(LakeComment)); + lt_assert("comment text is incorrect", str_equal(text, COMM_TEXT(comment))); return 0; } @@ -32,9 +31,9 @@ static char *test_comment_make(void) static char *test_comment_from_c(void) { LakeComment *comment = comment_from_c(TEXT); - mu_assert("type is not TYPE_COMM", IS(TYPE_COMM, comment)); - mu_assert("value size is incorrect", VAL_SIZE(comment) == sizeof(LakeComment)); - mu_assert("comment text is incorrect", str_equal(text, COMM_TEXT(comment))); + lt_assert("type is not TYPE_COMM", IS(TYPE_COMM, comment)); + lt_assert("value size is incorrect", VAL_SIZE(comment) == sizeof(LakeComment)); + lt_assert("comment text is incorrect", str_equal(text, COMM_TEXT(comment))); return 0; } @@ -42,7 +41,7 @@ static char *test_comment_from_c(void) static char *test_comment_repr(void) { LakeComment *comment = comment_make(text); - mu_assert("comment_repr is incorrect", strncmp(comment_repr(comment), TEXT, strlen(TEXT)) == 0); + lt_assert("comment_repr is incorrect", strncmp(comment_repr(comment), TEXT, strlen(TEXT)) == 0); return 0; } @@ -52,38 +51,26 @@ static char *test_comment_equal(void) LakeComment *a = comment_make(text); LakeComment *b = comment_from_c(TEXT); LakeComment *c = comment_from_c("and now for something completely different"); - mu_assert("comment a != a", comment_equal(a, a)); - mu_assert("comment a != b", comment_equal(a, b)); - mu_assert("comment a == c", !comment_equal(a, c)); - mu_assert("comment b == c", !comment_equal(b, c)); + lt_assert("comment a != a", comment_equal(a, a)); + lt_assert("comment a != b", comment_equal(a, b)); + lt_assert("comment a == c", !comment_equal(a, c)); + lt_assert("comment b == c", !comment_equal(b, c)); return 0; } -static void setup(void) +void setup(void) { text = str_from_c(TEXT); } -static char *all_tests() { +int main(int argc, char const *argv[]) +{ setup(); - mu_run_test(test_comment_make); - mu_run_test(test_comment_from_c); - mu_run_test(test_comment_repr); - mu_run_test(test_comment_equal); - return 0; -} - -int main(int argc, char const *argv[]) { - char *result = all_tests(); - int pass = result == 0; - printf("-- Comments --\n"); - if (pass) { - printf("PASS: %d test%s run\n", tests_run, tests_run == 1 ? "" : "s"); - } - else { - printf("%s\n", result); - printf("FAIL: %d test%s run\n", tests_run, tests_run == 1 ? "" : "s"); - } - - return !pass; + return !lt_run_tests("Comments", (test_fn[]){ + test_comment_make, + test_comment_from_c, + test_comment_repr, + test_comment_equal, + NULL + }); } diff --git a/test/test_dlist.c b/test/test_dlist.c index 530b31a..4fb8159 100644 --- a/test/test_dlist.c +++ b/test/test_dlist.c @@ -9,11 +9,10 @@ #include #include -#include "minunit.h" +#include "laketest.h" #include "lake.h" #include "list.h" -int tests_run; static LakeList *head; static LakeVal *tail; static LakeDottedList *dlist; @@ -22,18 +21,18 @@ static char *REPR = "(() . ())"; /* LakeDottedList *dlist_make(LakeList *head, LakeVal *tail) */ static char *test_dlist_make(void) { - mu_assert("type is not TYPE_DLIST", IS(TYPE_DLIST, dlist)); - mu_assert("value size is incorrect", VAL_SIZE(dlist) == sizeof(LakeDottedList)); - mu_assert("head value is incorrect", + lt_assert("type is not TYPE_DLIST", IS(TYPE_DLIST, dlist)); + lt_assert("value size is incorrect", VAL_SIZE(dlist) == sizeof(LakeDottedList)); + lt_assert("head value is incorrect", lake_equal(VAL(head), VAL(DLIST_HEAD(dlist)))); - mu_assert("tail value is incorrect", lake_equal(tail, DLIST_TAIL(dlist))); + lt_assert("tail value is incorrect", lake_equal(tail, DLIST_TAIL(dlist))); return 0; } /* char *dlist_repr(LakeDottedList *dlist) */ static char *test_dlist_repr(void) { - mu_assert("dlist_repr is incorrect", strncmp(dlist_repr(dlist), REPR, strlen(REPR)) == 0); + lt_assert("dlist_repr is incorrect", strncmp(dlist_repr(dlist), REPR, strlen(REPR)) == 0); char *REPR2 = "(spam eggs bacon spam eggs . spam)"; LakeCtx *lake = lake_init(); @@ -46,8 +45,8 @@ static char *test_dlist_repr(void) list_append(list, VAL(s_bacon)); list_append(list, VAL(s_spam)); list_append(list, VAL(s_eggs)); - LakeDottedList *dlist2 = dlist_make(list, s_spam); - mu_assert("", strncmp(dlist_repr(dlist2), REPR2, strlen(REPR2)) == 0); + LakeDottedList *dlist2 = dlist_make(list, VAL(s_spam)); + lt_assert("", strncmp(dlist_repr(dlist2), REPR2, strlen(REPR2)) == 0); return 0; } @@ -61,10 +60,10 @@ static char *test_dlist_equal(void) LakeList *null_pair = list_cons(null, null); LakeDottedList *diff_head = dlist_make(null_pair, tail); LakeDottedList *diff_tail = dlist_make(head, VAL(null_pair)); - mu_assert("dlist a != a", dlist_equal(a, a)); - mu_assert("dlist a != b", dlist_equal(a, b)); - mu_assert("dlist a == diff_head", !dlist_equal(a, diff_head)); - mu_assert("dlist a == diff_tail", !dlist_equal(a, diff_tail)); + lt_assert("dlist a != a", dlist_equal(a, a)); + lt_assert("dlist a != b", dlist_equal(a, b)); + lt_assert("dlist a == diff_head", !dlist_equal(a, diff_head)); + lt_assert("dlist a == diff_tail", !dlist_equal(a, diff_tail)); return 0; } @@ -75,26 +74,14 @@ static void setup(void) dlist = dlist_make(head, tail); } -static char *all_tests() { +int main(int argc, char const *argv[]) +{ setup(); - mu_run_test(test_dlist_make); - mu_run_test(test_dlist_repr); - mu_run_test(test_dlist_equal); - return 0; -} - -int main(int argc, char const *argv[]) { - char *result = all_tests(); - int pass = result == 0; - printf("-- Dotted Lists --\n"); - if (pass) { - printf("PASS: %d test%s run\n", tests_run, tests_run == 1 ? "" : "s"); - } - else { - printf("%s\n", result); - printf("FAIL: %d test%s run\n", tests_run, tests_run == 1 ? "" : "s"); - } - - return !pass; + return !lt_run_tests("Dotted Lists", (test_fn[]){ + test_dlist_make, + test_dlist_repr, + test_dlist_equal, + NULL + }); } diff --git a/test/test_env.c b/test/test_env.c index fd3ea52..512997e 100644 --- a/test/test_env.c +++ b/test/test_env.c @@ -8,7 +8,7 @@ */ #include -#include "minunit.h" +#include "laketest.h" #include "env.h" #include "lake.h" @@ -24,12 +24,12 @@ static LakeSym *s_undef; /* Env *env_make(Env *parent) */ static char *test_env_make(void) { - mu_assert("toplevel is NULL", toplevel != NULL); - mu_assert("toplevel->parent is not NULL", toplevel->parent == NULL); - mu_assert("toplevel->bindings is NULL", toplevel->bindings != NULL); + lt_assert("toplevel is NULL", toplevel != NULL); + lt_assert("toplevel->parent is not NULL", toplevel->parent == NULL); + lt_assert("toplevel->bindings is NULL", toplevel->bindings != NULL); - mu_assert("firstlevel is NULL", firstlevel != NULL); - mu_assert("firstlevel->parent is not toplevel", firstlevel->parent == toplevel); + lt_assert("firstlevel is NULL", firstlevel != NULL); + lt_assert("firstlevel->parent is not toplevel", firstlevel->parent == toplevel); return 0; } @@ -38,9 +38,9 @@ static char *test_env_make(void) static char *test_env_define(void) { env_define(toplevel, s_answer, answer); - mu_assert("env_define failed to define answer in toplevel", + lt_assert("env_define failed to define answer in toplevel", toplevel == env_is_defined(toplevel, s_answer)); - mu_assert("env_define set the value incorrectly", + lt_assert("env_define set the value incorrectly", answer == env_get(toplevel, s_answer)); return 0; } @@ -49,16 +49,16 @@ static char *test_env_define(void) static char *test_env_is_defined(void) { /* unbound symbol */ - mu_assert("unbound symbol is defined", + lt_assert("unbound symbol is defined", env_is_defined(toplevel, s_undef) == NULL); /* symbol bound in env itself */ env_define(toplevel, s_answer, answer); - mu_assert("failed to lookup symbol in defined env", + lt_assert("failed to lookup symbol in defined env", toplevel == env_is_defined(toplevel, s_answer)); /* symbol bound in parent */ - mu_assert("failed to lookup symbol in child env", + lt_assert("failed to lookup symbol in child env", toplevel == env_is_defined(firstlevel, s_answer)); return 0; @@ -69,20 +69,20 @@ static char *test_env_set(void) { /* unbound symbol */ LakeVal *ret = env_set(toplevel, s_undef, answer); - mu_assert("env_set returned non-NULL for an unbound symbol", ret == NULL); + lt_assert("env_set returned non-NULL for an unbound symbol", ret == NULL); /* symbol bound in env itself */ env_define(toplevel, s_answer, answer); LakeVal *zero = VAL(int_from_c(0)); ret = env_set(toplevel, s_answer, zero); - mu_assert("env_set failed to set bound symbol", ret != NULL); - mu_assert("env_set failed to set bound symbol", + lt_assert("env_set failed to set bound symbol", ret != NULL); + lt_assert("env_set failed to set bound symbol", zero == env_get(toplevel, s_answer)); /* symbol bound in parent */ ret = env_set(firstlevel, s_answer, answer); - mu_assert("env_set failed to set symbol bound in parent", ret != NULL); - mu_assert("env_set failed to set symbol bound in parent", + lt_assert("env_set failed to set symbol bound in parent", ret != NULL); + lt_assert("env_set failed to set symbol bound in parent", answer == env_get(toplevel, s_answer)); return 0; @@ -92,15 +92,15 @@ static char *test_env_set(void) static char *test_env_get(void) { /* unbound symbol */ - mu_assert("env_get returned non-NULL for an unbound symbol", + lt_assert("env_get returned non-NULL for an unbound symbol", NULL == env_get(toplevel, s_undef)); /* symbol bound in env itself */ env_define(toplevel, s_answer, answer); - mu_assert("failed to get value", answer == env_get(toplevel, s_answer)); + lt_assert("failed to get value", answer == env_get(toplevel, s_answer)); /* symbol bound in parent */ - mu_assert("failed to get value defined in parent", + lt_assert("failed to get value defined in parent", answer == env_get(firstlevel, s_answer)); return 0; @@ -116,28 +116,16 @@ static void setup(void) s_undef = sym_intern(lake, "undefined"); } -static char *all_tests() { +int main(int argc, char const *argv[]) +{ setup(); - mu_run_test(test_env_make); - mu_run_test(test_env_define); - mu_run_test(test_env_set); - mu_run_test(test_env_get); - mu_run_test(test_env_is_defined); - return 0; -} - -int main(int argc, char const *argv[]) { - char *result = all_tests(); - int pass = result == 0; - printf("-- Environment --\n"); - if (pass) { - printf("PASS: %d test%s run\n", tests_run, tests_run == 1 ? "" : "s"); - } - else { - printf("%s\n", result); - printf("FAIL: %d test%s run\n", tests_run, tests_run == 1 ? "" : "s"); - } - - return !pass; + return !lt_run_tests("Environment", (test_fn[]){ + test_env_make, + test_env_define, + test_env_set, + test_env_get, + test_env_is_defined, + NULL + }); }