/* 9unit Copyright (C) Jonathan Lamothe This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include #include #include #include "9unit.h" // Internal Types typedef struct RunTestWith RunTestWith; typedef struct ContextData ContextData; typedef struct TestContextWith TestContextWith; typedef struct SingleTestContextWith SingleTestContextWith; typedef struct CheckValue CheckValue; // data required by run_test_with() struct RunTestWith { void *ptr; // the state's previous ptr value TestResult (*test)(TestState *, void *); // the test void *val; // the value passed in }; struct ContextData { const char *old_c; // previous context const char *old_fc; // previous full context const char *new_c; // new context char *new_fc; // new full context }; // data needed by test_context_with() struct TestContextWith { void *ptr; // state's original ptr value void (*test)(TestState *, void *); // the test function void *val; // the value being passed in }; struct SingleTestContextWith { void *ptr; TestResult (*test)(TestState *, void *); void *val; }; struct CheckValue { void *chk_val; // the value being checked void *ref_val; // the reference value void (*test)(TestState *, void *, void *); // the test }; // Internal Prototypes static void init_TestState(TestState *); static void print_log(TestState *); static void clear_log(TestState *); static void reindex(TestState *); static void report(const char *); static TestResult run_test_with_test(TestState *); static void build_new_context(TestState *, ContextData *); static void display_context(TestState *); static void restore_context(TestState *, ContextData *); static void single_test_context_test(TestState *, void *); static void test_context_with_test(TestState *); static TestResult single_test_context_with_test(TestState *); static void check_value_test(TestState *, void *); // Public Functions void run_test(TestState *s, TestResult (*t)(TestState *)) { if (!s) return; s->run++; // a null test function is a pending test if (!t) { s->pending++; return; } switch ((*t)(s)) { case test_success: s->passed++; break; case test_failure: s->failed++; break; case test_pending: s->pending++; break; default: exits("test returned an invalid response"); } } void run_test_with( TestState *s, // the state TestResult (*test)(TestState *, void *), // the test void *val // the value being passed in ) { if (!s) return; if (!test) { run_test(s, 0); return; } RunTestWith d; d.ptr = s->ptr; d.test = test; d.val = val; s->ptr = &d; run_test(s, run_test_with_test); } void run_tests(void (*tests)(TestState *)) { if (!tests) return; TestState s; memset(&s, 0, sizeof(TestState)); s.report = report; (*tests)(&s); print_log(&s); printf("Tests run: %d\n", s.run); printf("Tests passed: %d\n", s.passed); printf("Tests failed: %d\n", s.failed); printf("Tests pending: %d\n", s.pending); clear_log(&s); if (s.failed) exits("test(s) failed"); } void append_test_log(TestState *s, const char *msg) { if (!(s && msg)) return; // build a new entry: TestLogEntry *entry = malloc(sizeof(TestLogEntry)); entry->text = malloc(strlen(msg) + 1); strcpy(entry->text, msg); entry->next = 0; // add it to the list: if (!s->last_log) { if (s->first_log) // no last entry but we have a first? { reindex(s); s->last_log->next = entry; } else s->first_log = entry; } else // there's already a last entry { if (!s->first_log) // no first entry but we have a last? reindex(s); // do our best to fix that s->last_log->next = entry; } s->last_log = entry; } void test_context( TestState *s, const char *label, void (*test)(TestState *) ) { if (!(s && test)) return; if (label) { ContextData cd; cd.new_c = label; build_new_context(s, &cd); display_context(s); (*test)(s); restore_context(s, &cd); } else (*test)(s); } void test_context_with( TestState *s, const char *context, void (*test)(TestState *, void *), void *val ) { if (!s) return; TestContextWith d; d.ptr = s->ptr; d.test = test; d.val = val; s->ptr = &d; test_context(s, context, test_context_with_test); } void single_test_context( TestState *s, const char *context, TestResult (*test)(TestState *) ) { test_context_with(s, context, single_test_context_test, test); } void single_test_context_with( TestState *s, const char *context, TestResult (*test)(TestState *, void *), void *val ) { if (!s) return; if (!test) single_test_context(s, context, 0); SingleTestContextWith d; d.ptr = s->ptr; d.test = test; d.val = val; s->ptr = &d; single_test_context(s, context, single_test_context_with_test); } void check_value( TestState *s, const char *context, void (*test)(TestState *, void *, void *), void *chk_val, void *ref_val ) { if (!(s && test)) return; CheckValue d; d.chk_val = chk_val; d.ref_val = ref_val; d.test = test; test_context_with(s, context, check_value_test, &d); } // Internal Functions static void print_log(TestState *s) { if (!s) return; TestLogEntry *e = s->first_log; while(e) { if(e->text) printf("%s\n", e->text); else print("(empty message)\n"); e = e->next; } } static void clear_log(TestState *s) { if (!s) return; if(s->last_log && !s->first_log) reindex(s); // fix if broken TestLogEntry *e = s->first_log, *next; s->first_log = 0; s->last_log = 0; while (e) { next = e->next; free(e->text); free(e); e = next; } } static void reindex(TestState *s) { if (!s) return; if (s->first_log) { TestLogEntry *e = s->first_log; while (e) { s->last_log = e; e = e->next; } } else if (s->last_log) // we have a last log but no first? { s->first_log = s->last_log; fprint(2, "potential memory leak in test log\n"); } } static void report(const char *str) { print("%s", str); } static void build_new_context(TestState *s, ContextData *cd) { cd->old_c = s->context; cd->old_fc = s->full_context; if (s->full_context) { cd->new_fc = malloc(strlen(cd->old_fc) + strlen(cd->new_c) + 3); sprintf(cd->new_fc, "%s: %s", cd->old_fc, cd->new_c); } else { cd->new_fc = malloc(strlen(cd->new_c) + 1); strcpy(cd->new_fc, cd->new_c); } s->context = cd->new_c; s->full_context = cd->new_fc; s->depth++; } static void display_context(TestState *s) { for (int i = 1; i < s->depth; i++) s->report("\t"); s->report(s->context); s->report("\n"); } static void restore_context(TestState *s, ContextData *cd) { s->context = cd->old_c; s->full_context = cd->old_fc; s->depth--; free(cd->new_fc); } static TestResult run_test_with_test(TestState *s) { RunTestWith *d = s->ptr; s->ptr = d->ptr; return (*d->test)(s, d->val); } static void test_context_with_test(TestState *s) { TestContextWith *d = s->ptr; s->ptr = d->ptr; (*d->test)(s, d->val); } static void single_test_context_test(TestState *s, void *test) { run_test(s, test); } static TestResult single_test_context_with_test(TestState *s) { SingleTestContextWith *d = s->ptr; s->ptr = d->ptr; return (*d->test)(s, d->val); } static void check_value_test(TestState *s, void *ptr) { CheckValue *d = ptr; (*d->test)(s, d->chk_val, d->ref_val); } //jl