Created
August 16, 2018 16:05
-
-
Save SeijiEmery/cbcd98205a9787d42005877243ea8505 to your computer and use it in GitHub Desktop.
A minimalistic C unittesting library
This file contains hidden or 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
// | |
// A minimalistic unittesting framework that uses signal handling + | |
// longjmp to detect + test assertions + segfaults. | |
// | |
// Will add to this if / as needed | |
// | |
#ifndef __SEMERY_C_MINTEST_FRAMEWORK__ | |
#define __SEMERY_C_MINTEST_FRAMEWORK__ | |
static int g_lastCaughtSignal = 0; | |
static jmp_buf g_exceptionHandler; | |
static void handleSignal (int sig) { | |
g_lastCaughtSignal = sig; | |
longjmp(g_exceptionHandler, sig); | |
} | |
static void catchSignal (int sig) { | |
if (signal(sig, handleSignal) == SIG_ERR) { | |
fprintf(stderr, "An error occured while setting up signal handler %d\n", sig); | |
exit(-1); | |
} | |
} | |
static void setupSignalHandlers () { | |
catchSignal(SIGABRT); | |
catchSignal(SIGSEGV); | |
} | |
typedef struct Test Test; | |
struct Test { | |
int passed; | |
int failed; | |
}; | |
#define SET_COLOR(color) "\033[" #color "m" | |
#define SET_RED SET_COLOR(31) | |
#define SET_GREEN SET_COLOR(32) | |
#define SET_YELLOW SET_COLOR(33) | |
#define SET_CYAN SET_COLOR(36) | |
#define SET_CLEAR "\033[0m" | |
#define CLEAR_EOL SET_CLEAR "\n" | |
#define LOG_ASSERT_FAIL(args...) do { \ | |
fprintf(stderr, SET_RED "Test failed: %s:%d:\t", __FILE__, __LINE__); \ | |
fprintf(stderr, args); \ | |
fprintf(stderr, CLEAR_EOL); \ | |
} while (0) | |
#define LOG_ASSERT_INFO(args...) do { \ | |
fprintf(stderr, SET_CYAN); \ | |
fprintf(stderr, args); \ | |
fprintf(stderr, CLEAR_EOL); \ | |
} while (0) | |
#define ASSERT_EQ(a,b) \ | |
if ((a) == (b)) { \ | |
++test->passed; \ | |
} else { \ | |
++test->failed; \ | |
LOG_ASSERT_FAIL(#a " == " #b); \ | |
} \ | |
#define ASSERT_NE(a,b) \ | |
if ((a) != (b)) { \ | |
++test->passed; \ | |
} else { \ | |
++test->failed; \ | |
LOG_ASSERT_FAIL(#a " != " #b); \ | |
} \ | |
#define ASSERT_NOTHROW(expr) \ | |
if (setjmp(g_exceptionHandler)) { \ | |
++test->failed; \ | |
LOG_ASSERT_FAIL("Unexpected error (signal): %d", g_lastCaughtSignal); \ | |
} else { \ | |
(expr); \ | |
++test->passed; \ | |
} | |
#define ASSERT_THROWS(sig, expr) \ | |
if (setjmp(g_exceptionHandler)) { \ | |
if (sig == g_lastCaughtSignal) { \ | |
++test->passed; \ | |
} else { \ | |
++test->failed; \ | |
LOG_ASSERT_FAIL("Expected signal %d, got %d", sig, g_lastCaughtSignal); \ | |
} \ | |
} else { \ | |
(expr); \ | |
++test->failed; \ | |
LOG_ASSERT_FAIL("Expected error, got none"); \ | |
} | |
#define ASSERT_THROWN(expr) ASSERT_THROWS(SIGABRT, (expr)) | |
#define ASSERT_SEGV(expr) ASSERT_THROWS(SIGSEGV, (expr)) | |
void runTest (Test* outer, void (*testFcn)(Test*), const char* testName) { | |
printf(SET_YELLOW "Running test '%s'" CLEAR_EOL, testName); | |
Test inner; inner.passed = inner.failed = 0; | |
testFcn(&inner); | |
printf(inner.failed ? SET_RED : SET_GREEN); | |
printf("Ran test '%s': %d / %d test(s) passed" CLEAR_EOL, | |
testName, inner.passed, inner.passed + inner.failed); | |
inner.failed ? ++outer->failed : ++outer->passed; | |
} | |
#define RUN_TEST(testFcn) \ | |
runTest(test, &unittest__##testFcn, #testFcn) | |
#define UNITTEST(name) \ | |
static void unittest__##name (Test* test) | |
#define ASSERT_SHOULD_PASS LOG_ASSERT_INFO(SET_GREEN "This should pass"); | |
#define ASSERT_SHOULD_FAIL LOG_ASSERT_INFO(SET_RED "This should fail"); | |
// | |
// Unittest the testing framework | |
// | |
UNITTEST(sanity) { | |
ASSERT_SHOULD_PASS ASSERT_EQ(2 + 2, 4); | |
ASSERT_SHOULD_FAIL ASSERT_EQ(2 + 2, 5); | |
ASSERT_SHOULD_PASS ASSERT_NE(2 + 2, 5); | |
ASSERT_SHOULD_FAIL ASSERT_NE(2 + 2, 4); | |
ASSERT_SHOULD_PASS ASSERT_NOTHROW(assert(2 + 2 == 4)); | |
ASSERT_SHOULD_FAIL ASSERT_NOTHROW(assert(2 + 2 == 5)); | |
ASSERT_SHOULD_PASS ASSERT_THROWN(assert(2 + 2 != 4)); | |
ASSERT_SHOULD_FAIL ASSERT_THROWN(assert(2 + 2 != 5)); | |
int value = 0; | |
int* valid = &value; | |
int* invalid = NULL; | |
ASSERT_SHOULD_PASS ASSERT_SEGV(*invalid = 10); | |
ASSERT_SHOULD_PASS ASSERT_SEGV((*invalid == 10) ? value = 1 : 0); | |
ASSERT_SHOULD_FAIL ASSERT_SEGV(*valid = 10); | |
ASSERT_SHOULD_FAIL ASSERT_SEGV((*valid == 10) ? value = 1 : 0); | |
} | |
static void setupUnittestLibrary () { | |
LOG_ASSERT_INFO("Setting up unittest framework -- ignore this output --"); | |
setupSignalHandlers(); | |
// run initial tests - don't include in checked test results | |
Test _test; _test.passed = _test.failed = 0; | |
Test* test = &_test; | |
RUN_TEST(sanity); | |
LOG_ASSERT_INFO("Finished setting up unittest framework -- ignore above output --\n\n"); | |
} | |
#define RUN_ROOT_TEST(testFcn) \ | |
do { \ | |
setupUnittestLibrary(); \ | |
Test _test; _test.passed = _test.failed = 0; \ | |
Test* test = &_test; \ | |
RUN_TEST(testFcn); \ | |
exit(test->failed ? -1 : 0); \ | |
} while (0) | |
#endif //__SEMERY_C_MINTEST_FRAMEWORK__ | |
// Usage: | |
/* | |
UNITTEST(main) { | |
RUN_TEST(testSuite1); | |
RUN_TEST(testSuite2); | |
... | |
} | |
int main (int argc, const char** argv) { | |
RUN_ROOT_TEST(main); | |
return 0; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment