Skip to content

Instantly share code, notes, and snippets.

@pervognsen
Created November 20, 2010 22:00
Show Gist options
  • Save pervognsen/708214 to your computer and use it in GitHub Desktop.
Save pervognsen/708214 to your computer and use it in GitHub Desktop.
minimalistic unit testing
// 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