// 27 february 2018 #include <stdio.h> #include <stdlib.h> #include <setjmp.h> #include "testing.h" #define testingprivNew(T) ((T *) malloc(sizeof (T))) struct defer { void (*f)(testingT *, void *); void *data; struct defer *next; }; struct testingT { const char *name; void (*f)(testingT *); int failed; int skipped; int returned; jmp_buf returnNowBuf; struct defer *defers; int defersRun; testingT *next; }; static testingT *tests = NULL; static testingT *testsTail = NULL; static testingT *testsBefore = NULL; static testingT *testsBeforeTail = NULL; static testingT *testsAfter = NULL; static testingT *testsAfterTail = NULL; static testingT *newTest(const char *name, void (*f)(testingT *), const char *file, long line, testingT *prev) { testingT *t; printf("%s %s %ld\n", name, file, line); t = testingprivNew(testingT); t->name = name; t->f = f; t->failed = 0; t->skipped = 0; t->returned = 0; t->defers = NULL; t->defersRun = 0; t->next = NULL; if (prev != NULL) prev->next = t; return t; } void testingprivRegisterTest(const char *name, void (*f)(testingT *), const char *file, long line) { testingT *t; t = newTest(name, f, file, line, testsTail); testsTail = t; if (tests == NULL) tests = t; } void testingprivRegisterTestBefore(const char *name, void (*f)(testingT *), const char *file, long line) { testingT *t; t = newTest(name, f, file, line, testsBeforeTail); testsBeforeTail = t; if (testsBefore == NULL) testsBefore = t; } void testingprivRegisterTestAfter(const char *name, void (*f)(testingT *), const char *file, long line) { testingT *t; t = newTest(name, f, file, line, testsAfterTail); testsAfterTail = t; if (testsAfter == NULL) testsAfter = t; } static void runDefers(testingT *t) { struct defer *d; if (t->defersRun) return; t->defersRun = 1; for (d = t->defers; d != NULL; d = d->next) (*(d->f))(t, d->data); } static void runTestSet(testingT *t, int *anyFailed) { const char *status; for (; t != NULL; t = t->next) { printf("=== RUN %s\n", t->name); if (setjmp(t->returnNowBuf) == 0) (*(t->f))(t); t->returned = 1; runDefers(t); status = "PASS"; if (t->failed) { status = "FAIL"; *anyFailed = 1; } else if (t->skipped) // note that failed overrides skipped status = "SKIP"; printf("--- %s: %s (%s)\n", status, t->name, "TODO"); } } int testingMain(void) { int anyFailed; // TODO see if this should run if all tests are skipped if (tests == NULL) { fprintf(stderr, "warning: no tests to run\n"); // imitate Go here (TODO confirm this) return 0; } anyFailed = 0; runTestSet(testsBefore, &anyFailed); runTestSet(tests, &anyFailed); runTestSet(testsAfter, &anyFailed); if (anyFailed) { printf("FAIL\n"); return 1; } printf("PASS\n"); return 0; } void testingprivTLogfFull(testingT *t, const char *file, long line, const char *format, ...) { va_list ap; va_start(ap, format); testingprivTLogvfFull(t, file, line, format, ap); va_end(ap); } void testingprivTLogvfFull(testingT *t, const char *file, long line, const char *format, va_list ap) { // TODO extract filename from file printf("\t%s:%ld: ", file, line); // TODO split into lines separated by \n\t\t and trimming trailing empty lines vprintf(format, ap); printf("\n"); } void testingTFail(testingT *t) { t->failed = 1; } static void returnNow(testingT *t) { if (!t->returned) { // set this now so a FailNow inside a Defer doesn't longjmp twice t->returned = 1; // run defers before calling longjmp() just to be safe runDefers(t); longjmp(t->returnNowBuf, 1); } } void testingprivTFailNow(testingT *t) { testingTFail(t); returnNow(t); } void testingprivTSkipNow(testingT *t) { t->skipped = 1; returnNow(t); } void testingTDefer(testingT *t, void (*f)(testingT *t, void *data), void *data) { struct defer *d; d = testingprivNew(struct defer); d->f = f; d->data = data; // add to the head of the list so defers are run in reverse order of how they were added d->next = t->defers; t->defers = d; }