Created
November 20, 2010 20:33
-
-
Save pervognsen/708129 to your computer and use it in GitHub Desktop.
minimalistic unit testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __TEST_H__ | |
#define __TEST_H__ | |
#ifndef NO_TESTING | |
#define TESTING 1 | |
// Plumbing | |
void test_main(int argc, char const **argv); | |
void test_register(const char *name, void (*run)(), const char *file, int phase, int line); | |
void test_run(); | |
void test_is(int b, const char *expr, const char *file, int line); | |
#ifdef TEST_IMPL | |
#include <stdlib.h> | |
#include <setjmp.h> | |
#include <string.h> | |
typedef struct test_t | |
{ | |
const char *name; void (*run)(); | |
const char *file; int phase; int line; | |
struct test_t *link; | |
} test_t; | |
static test_t *test_link = NULL; | |
void test_register(const char *name, void (*run)(), const char *file, int phase, int line) | |
{ | |
test_t *t = (test_t*) malloc(sizeof(test_t)); // blatantly ignores clean-up | |
t->name = name; t->run = run; t->file = file; t->phase = phase; t->line = line; | |
t->link = test_link; test_link = t; | |
} | |
static jmp_buf test_fail_jmp; | |
void test_fail(const char *file, int line) | |
{ | |
printf(" (line %d)\n", line); | |
longjmp(test_fail_jmp, 1); | |
} | |
#define TEST_FAIL_BANNER " FAIL: " | |
static int test_compare(const void *p1, const void *p2) | |
{ | |
test_t *x = *(test_t**) p1, *y = *(test_t**) p2; | |
int file = x->file - y->file, phase = x->phase - y->phase, line = x->line - y->line; | |
return (file != 0) ? file : (phase != 0) ? phase : line; | |
} | |
void test_run() | |
{ | |
// sort tests | |
test_t **tests; int n; | |
{ | |
n = 0; | |
for (test_t *t = test_link; t; t = t->link) { n++; } | |
tests = (test_t**) calloc(n, sizeof(test_t*)); | |
int i = 0; | |
for (test_t *t = test_link; t; t = t->link) { tests[i++] = t; } | |
qsort(tests, n, sizeof(test_t*), test_compare); | |
} | |
// run tests in order | |
{ | |
int total = 0, passed = 0; | |
const char *file = NULL; | |
for (int i = 0; i < n; i++) { | |
test_t *t = tests[i]; | |
if (t->file != file) { | |
file = t->file; | |
printf("%sFILE: %s\n\n", i != 0 ? "\n" : "", file); | |
} | |
if (t->phase == 0) { | |
printf(" TEST: %s\n", t->name); | |
total++; | |
if (setjmp(test_fail_jmp) == 0) { | |
t->run(); | |
passed++; | |
} | |
} else { | |
t->run(); | |
} | |
} | |
printf("\nDONE: %d tests (%d passed, %d failed)\n", total, passed, total - passed); | |
} | |
free(tests); | |
} | |
void test_main(int argc, char const **argv) | |
{ | |
for (int i = 0; i < argc; i++) { | |
if (strcmp(argv[i], "--tests") == 0) { | |
test_run(); | |
getchar(); | |
exit(0); | |
} | |
} | |
} | |
void test_is(int b, const char *expr, const char *file, int line) | |
{ | |
if (!b) { | |
printf("%s%s", TEST_FAIL_BANNER, expr); | |
test_fail(file, line); | |
} | |
} | |
#endif // TEST_IMPL | |
#endif // NO_TESTING | |
#define TEST_TOKENPASTE(x, y) x ## y | |
#define TEST_TOKENPASTE2(x, y) TEST_TOKENPASTE(x, y) | |
// Porcelain | |
#if TESTING | |
#define PRE_TEST DO_TEST(pre, -1) | |
#define TEST(name) DO_TEST(name, 0) | |
#define POST_TEST DO_TEST(post, +1) | |
#define TEST_MAIN test_main(argc, argv) | |
#define DO_TEST(name, phase) \ | |
static void test_##name##(); \ | |
struct TEST_TOKENPASTE2(test, __LINE__) \ | |
{ \ | |
TEST_TOKENPASTE2(test, __LINE__)() \ | |
{ \ | |
test_register(#name, test_##name, __FILE__, phase, __LINE__); \ | |
} \ | |
} TEST_TOKENPASTE2(test_, __LINE__); \ | |
void test_##name##() | |
#define IS(b) test_is((b), #b, __FILE__, __LINE__) | |
#else | |
#define TEST_MAIN | |
#define DO_TEST(name, phase) static void test_##name##() | |
#define IS(b) do {} while(0) | |
#endif // TESTING | |
#endif // __TEST_H__ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment