/* 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 // data required by the context management functions typedef struct ContextData ContextData; // data required to run a single test with a label typedef struct SingleTestContext SingleTestContext; // data used by check_value() typedef struct CheckValueData CheckValueData; 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 }; struct SingleTestContext { void *ptr; // the state's previous ptr value TestResult (*test)(TestState *); // the test to run }; struct CheckValueData { void *ptr; // state's original ptr value 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 void build_new_context(TestState *, ContextData *); static void display_context(TestState *); static void restore_context(TestState *, ContextData *); static void run_single_test_context(TestState *); static void check_value_test(TestState *); // 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_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 single_test_context( TestState *s, const char *label, TestResult (*test)(TestState *) ) { if (!s) return; SingleTestContext stc; stc.ptr = s->ptr; stc.test = test; s->ptr = &stc; test_context(s, label, run_single_test_context); } void check_value( TestState *s, const char *context, void (*test)(TestState *, void *, void *), void *chk_val, void *ref_val ) { if (!(s && test)) return; CheckValueData d; d.ptr = s->ptr; d.chk_val = chk_val; d.ref_val = ref_val; d.test = test; s->ptr = &d; test_context(s, context, check_value_test); } // 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 void run_single_test_context(TestState *s) { SingleTestContext *stc = s->ptr; s->ptr = stc->ptr; run_test(s, stc->test); } static void check_value_test(TestState *s) { CheckValueData *d = s->ptr; s->ptr = d->ptr; (*d->test)(s, d->chk_val, d->ref_val); } //jl