Created
November 20, 2010 22:00
-
-
Save pervognsen/708214 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
// test.h - minimalistic unit testing | |
// | |
// AUTHOR: | |
// Per Vognsen | |
// | |
// LICENSE: | |
// Public domain | |
// | |
// EXAMPLE: | |
// | |
// #define TEST_IMPL | |
// #include "test.h" | |
// | |
// TEST(add1) { IS(1 + 2 == 3); } // pass | |
// TEST(add2) { IS(42 + 0 == 0); } // fail | |
// TEST(add3) { IS(-42 + 42 == 0); } // pass | |
// | |
// int main(int argc, char **argv) | |
// { | |
// TEST_MAIN; | |
// return 0; | |
// } | |
// | |
// USAGE: | |
// | |
// This header file is self-contained. Any file that wants tests can simply #include it. | |
// One file (usually the main() file) must precede the #include with #define TEST_IMPL | |
// to additionally include the function definitions. | |
// | |
// TEST(x) { ... } a test named x | |
// IS(b); ensure that b is logically true | |
// PRE_TEST { ... } test setup (per file, optional) | |
// POST_TEST { ... } test clean-up (per file, optional) | |
// TEST_MAIN; run tests if --test is a command line option (call in main()) | |
#ifndef NO_TESTING | |
#define TESTING 1 | |
// plumbing | |
void test_register(const char *name, void (*run)(), const char *file, int phase, int line); | |
void test_assert(int cond, const char *expr, int line); | |
void test_main(int argc, char const **argv); | |
void test_run(); | |
#ifdef TEST_IMPL | |
#include <stdlib.h> | |
#include <setjmp.h> | |
#include <string.h> | |
#include <time.h> | |
#include <assert.h> | |
#ifdef _WIN32 | |
#include <windows.h> | |
#endif | |
typedef struct test_t | |
{ | |
const char *name; void (*run)(); | |
const char *file; int phase; int line; | |
} test_t; | |
#ifndef MAX_TESTS | |
#define MAX_TESTS 1024 | |
#endif | |
static test_t tests[MAX_TESTS]; | |
static int num_tests = 0; | |
void test_register(const char *name, void (*run)(), const char *file, int phase, int line) | |
{ | |
assert(num_tests < MAX_TESTS && "pre-#define MAX_TESTS to a larger number"); | |
test_t *t = &tests[num_tests++]; | |
t->name = name; t->run = run; t->file = file; t->phase = phase; t->line = line; | |
} | |
static void test_status(int success, const char *str) | |
{ | |
#ifdef _WIN32 | |
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); | |
CONSOLE_SCREEN_BUFFER_INFO info; | |
GetConsoleScreenBufferInfo(console, &info); | |
WORD color = success ? FOREGROUND_GREEN : FOREGROUND_RED; | |
SetConsoleTextAttribute(console, color | FOREGROUND_INTENSITY); | |
#endif | |
printf("%s", str); | |
#ifdef _WIN32 | |
SetConsoleTextAttribute(console, info.wAttributes); | |
#endif | |
} | |
static test_t *test_running; | |
static jmp_buf test_fail_jmp; | |
void test_assert(int cond, const char *expr, int line) | |
{ | |
if (!cond) { | |
test_status(0, " FAIL: "); | |
printf("%s\n LINE %d: %s\n", test_running->name, line, expr); | |
longjmp(test_fail_jmp, 1); | |
} | |
} | |
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 ? file : phase ? phase : line; | |
} | |
void test_run() | |
{ | |
int total = 0, passed = 0; | |
const char *file = NULL; | |
time_t start = time(NULL); | |
qsort(tests, num_tests, sizeof(test_t), test_compare); | |
for (int i = 0; i < num_tests; i++) { | |
test_t *t = &tests[i]; | |
if (t->file != file) { | |
file = t->file; | |
printf("%sFILE: %s\n\n", i ? "\n" : "", file); | |
} | |
if (t->phase == 0) { | |
test_running = t; | |
total++; | |
if (setjmp(test_fail_jmp) == 0) { | |
t->run(); | |
test_status(1, " PASS: "); | |
printf("%s\n", t->name); | |
passed++; | |
} | |
} else { | |
t->run(); | |
} | |
} | |
test_status(total == passed, "\nDONE: "); | |
printf("%d tests (%d passed, %d failed) in %.2f seconds\n", | |
total, passed, total - passed, difftime(time(NULL), start)); | |
} | |
void test_main(int argc, char const **argv) | |
{ | |
for (int i = 0; i < argc; i++) { | |
if (strcmp(argv[i], "--test") == 0) { | |
test_run(); | |
getchar(); | |
exit(0); | |
} | |
} | |
} | |
#endif // TEST_IMPL | |
#endif // NO_TESTING | |
// porcelain | |
#define PRE_TEST TEST_AUTOREGISTER(pre, -1) | |
#define TEST(name) TEST_AUTOREGISTER(name, 0) | |
#define POST_TEST TEST_AUTOREGISTER(post, +1) | |
#if TESTING | |
#define IS(b) test_assert((b), #b, __LINE__) | |
#define TEST_MAIN test_main(argc, argv) | |
#define TEST_AUTOREGISTER(name, phase) \ | |
static void test_##name##(); \ | |
namespace { struct test_##name##_proxy_t \ | |
{ \ | |
test_##name##_proxy_t() \ | |
{ \ | |
test_register(#name, test_##name, __FILE__, phase, __LINE__); \ | |
} \ | |
} test_##name##_proxy; } \ | |
void test_##name##() | |
#else | |
#define IS(b) do {} while(0) | |
#define TEST_MAIN do {} while(0) | |
#define TEST_AUTOREGISTER(name, phase) static void test_##name##() | |
#endif // TESTING |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment