From 01b53ac4ce84aae2b2a1dcb1a68f748fd16f0ede Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Sat, 17 Dec 2016 20:52:30 -0800 Subject: Test framework is dev-complete. More functionality may be added later. Also, the current code is not Win32 safe. --- tests/demo.c | 3 +- tests/test.c | 470 +++++++++++++++++++++++++++++++++++++++++++++-------------- tests/test.h | 143 ++++++++++-------- 3 files changed, 438 insertions(+), 178 deletions(-) (limited to 'tests') diff --git a/tests/demo.c b/tests/demo.c index cfa879da..c38dd3b9 100644 --- a/tests/demo.c +++ b/tests/demo.c @@ -7,6 +7,7 @@ test_main("Things work", { x = 1; y = 2; test_convey("X is one", { + test_debugf("A logged message."); test_assert(x == 1); }); test_convey("Y is two", { @@ -38,7 +39,7 @@ test_main("Things work", { }); test_convey("Middle (Skip?)", { test_so(9 - 1 == 8); - test_skip(); + test_skip("forced skip"); test_so(0 == 1); }); test_convey("Ending", { diff --git a/tests/test.c b/tests/test.c index 6aa32efe..1868e409 100644 --- a/tests/test.c +++ b/tests/test.c @@ -31,11 +31,10 @@ #include #include #include +#include #include "test.h" -test_ctx_t *T_C = NULL; - static const char *sym_pass = "."; static const char *sym_skip = "?"; static const char *sym_fail = "X"; @@ -45,18 +44,21 @@ static const char *color_green = ""; static const char *color_red = ""; static const char *color_yellow = ""; +static int debug = 0; static int verbose = 0; static int nasserts = 0; static int nskips = 0; static const char *color_asserts = ""; typedef struct tperfcnt { + uint64_t pc_base; uint64_t pc_count; uint64_t pc_rate; + int pc_running; } tperfcnt_t; typedef struct tlog { - const char *l_buf; + char *l_buf; size_t l_size; size_t l_length; } tlog_t; @@ -79,8 +81,7 @@ typedef struct tctx { int t_fail; int t_skip; int t_printed; - tperfcnt_t t_starttime; - tperfcnt_t t_endtime; + tperfcnt_t t_perfcnt; tlog_t t_fatallog; tlog_t t_faillog; tlog_t t_debuglog; @@ -88,12 +89,36 @@ typedef struct tctx { #define PARENT(t) ((t_ctx_t *)(t->t_parent->t_data)) -static void test_print_result(tctx_t *); -static void test_getperfcnt(tperfcnt_t *); -static uint64_t test_perfdelta(tperfcnt_t *, tperfcnt_t *, int *, int *); +/* + * Symbol naming: + * + * functions exposed to users (public) are named test_xxx + * functions exposed in the ABI, but not part of the public API, are test_i_xxx + * functions local (static) to this file -- no prefix + */ + +static void print_result(tctx_t *); +static void init_perfcnt(tperfcnt_t *); +static void start_perfcnt(tperfcnt_t *); +static void stop_perfcnt(tperfcnt_t *); +static void read_perfcnt(tperfcnt_t *, int *, int *); +static void init_terminal(void); +static int init_specific(void); +static void *get_specific(void); +static int set_specific(void *); +static tctx_t *get_ctx(void); +static void log_vprintf(tlog_t *, const char *, va_list); +static void log_printf(tlog_t *, const char *, ...); +static void log_dump(tlog_t *, const char *, const char *); +/* + * test_i_print prints the test results. It prints more verbose information + * in verbose mode. Note that its possible for assertion checks done at + * a given block to be recorded in a deeper block, since we can't easily + * go back up to the old line and print it. + */ void -test_print_result(tctx_t *t) +print_result(tctx_t *t) { int secs, usecs; @@ -101,13 +126,18 @@ test_print_result(tctx_t *t) t->t_printed = 1; - test_getperfcnt(&t->t_endtime); - test_perfdelta(&t->t_starttime, &t->t_endtime, &secs, &usecs); + stop_perfcnt(&t->t_perfcnt); + read_perfcnt(&t->t_perfcnt, &secs, &usecs); + log_dump(&t->t_fatallog, "Errors:", color_red); + log_dump(&t->t_faillog, "Failures:", color_yellow); + if (debug) { + log_dump(&t->t_debuglog, "Log:", color_none); + } if (!verbose) { (void) printf("%-8s%-52s%4d.%03ds\n", - t->t_fatal ? "fatal" : - t->t_fail ? "fail" : "ok", + t->t_fatal ? "FATAL" : + t->t_fail ? "FAIL" : "ok", t->t_name, secs, usecs / 1000); } else { printf("\n\n%s%d assertions thus far%s", @@ -126,14 +156,23 @@ test_print_result(tctx_t *t) } } +/* + * test_i_start is called when the context starts, before any call to + * setjmp is made. If the context isn't initialized already, that is + * done. Note that this code gets called multiple times when the + * context is reentered, which is why the context used must be statically + * allocated -- a record that it has already done is checked. If + * the return value is zero, then this block has already been executed, + * and it should be skipped. Otherwise, it needs to be done. + */ int -test_ctx_init(test_ctx_t *ctx, test_ctx_t *parent, const char *name) +test_i_start(test_ctx_t *ctx, test_ctx_t *parent, const char *name) { tctx_t *t; if ((t = ctx->T_data) != NULL) { if (t->t_done) { - test_print_result(t); + print_result(t); return (1); /* all done, skip */ } return (0); /* continue onward */ @@ -159,17 +198,22 @@ test_ctx_init(test_ctx_t *ctx, test_ctx_t *parent, const char *name) /* * This is called right after setjmp. The jumped being true indicates - * that setjmp returned true, and we are popping the stack. + * that setjmp returned true, and we are popping the stack. In that case + * we perform a local cleanup and keep popping back up the stack. We + * always come through this, even if the test finishes successfully, so + * that we can do this stack unwind. If we are unwinding, and we are + * at the root context, then we pritn the results and return non-zero + * so that our caller knows to stop further processing. */ int -test_ctx_loop(test_ctx_t *ctx, int jumped) +test_i_loop(test_ctx_t *ctx, int unwind) { tctx_t *t; int i; if ((t = ctx->T_data) == NULL) { return (1); } - if (jumped != 0) { + if (unwind) { if (t->t_cleanup != NULL) { t->t_cleanup(t->t_cleanup_arg); } @@ -177,7 +221,7 @@ test_ctx_loop(test_ctx_t *ctx, int jumped) longjmp(*t->t_parent->t_jmp, 1); } if (t->t_done) { - test_print_result(t); + print_result(t); return (1); } } @@ -198,15 +242,16 @@ test_ctx_loop(test_ctx_t *ctx, int jumped) } } - test_getperfcnt(&t->t_starttime); + init_perfcnt(&t->t_perfcnt); + start_perfcnt(&t->t_perfcnt); } /* Reset TC for the following code. */ - T_C = ctx; + set_specific(ctx); return (0); } void -test_ctx_fini(test_ctx_t *ctx, int *rvp) +test_i_finish(test_ctx_t *ctx, int *rvp) { tctx_t *t; if ((t = ctx->T_data) == NULL) { @@ -227,25 +272,23 @@ test_ctx_fini(test_ctx_t *ctx, int *rvp) } void -test_ctx_skip(test_ctx_t *ctx) +test_i_skip(const char *file, int line, const char *reason) { - tctx_t *t = ctx->T_data; + tctx_t *t = get_ctx(); if (verbose) { - (void) printf("%s%s%s", color_none, sym_skip, color_none); + (void) printf("%s%s%s", color_yellow, sym_skip, color_none); } + log_printf(&t->t_root->t_debuglog, "* Skipping rest of %s: %s: %d: %s", + t->t_name, file, line, reason); t->t_done = 1; /* This forces an end */ nskips++; longjmp(*t->t_jmp, 1); } void -test_assert_fail(test_ctx_t *ctx, const char *cond, const char *file, int line) +test_i_assert_fail(const char *cond, const char *file, int line) { - tctx_t *t; - if ((t = ctx->T_data) == NULL) { - /* PANIC? */ - /* XXX: */ - } + tctx_t *t = get_ctx(); nasserts++; if (verbose) { (void) printf("%s%s%s", color_yellow, sym_fail, color_none); @@ -256,43 +299,44 @@ test_assert_fail(test_ctx_t *ctx, const char *cond, const char *file, int line) color_asserts = color_yellow; t->t_fail++; t->t_done = 1; /* This forces an end */ + log_printf(&t->t_root->t_faillog, "* %s (Assertion Failed)\n", + t->t_name); + log_printf(&t->t_root->t_faillog, "File: %s\n", file); + log_printf(&t->t_root->t_faillog, "Line: %d\n", line); + log_printf(&t->t_root->t_faillog, "Test: %s\n\n", cond); + log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (FAILED)\n", + t->t_name, file, line); longjmp(*t->t_jmp, 1); } void -test_assert_pass(test_ctx_t *ctx, const char *cond, const char *file, int line) +test_i_assert_pass(const char *cond, const char *file, int line) { - tctx_t *t; - if ((t = ctx->T_data) == NULL) { - /* PANIC? */ - } + tctx_t *t = get_ctx(); nasserts++; if (verbose) { (void) printf("%s%s%s", color_green, sym_pass, color_none); } + log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (Passed)\n", + t->t_name, file, line); } void -test_assert_skip(test_ctx_t *ctx, const char *cond, const char *file, int line) +test_i_assert_skip(const char *cond, const char *file, int line) { - tctx_t *t; - if ((t = ctx->T_data) == NULL) { - /* PANIC? */ - } + tctx_t *t = get_ctx(); nskips++; if (verbose) { - (void) printf("%s%s%s", color_none, sym_pass, color_none); + (void) printf("%s%s%s", color_yellow, sym_pass, color_none); } + log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (SKIPPED)\n", + t->t_name, file, line); } void -test_assert_fatal(test_ctx_t *ctx, const char *cond, const char *file, int line) +test_i_assert_fatal(const char *cond, const char *file, int line) { - tctx_t *t; - if ((t = ctx->T_data) == NULL) { - /* PANIC? */ - /* XXX: */ - } + tctx_t *t = get_ctx(); nasserts++; if (verbose) { (void) printf("%s%s%s", color_red, sym_fail, color_none); @@ -303,47 +347,97 @@ test_assert_fatal(test_ctx_t *ctx, const char *cond, const char *file, int line) color_asserts = color_red; t->t_fail++; t->t_done = 1; /* This forces an end */ + log_printf(&t->t_root->t_fatallog, "* %s (Fatal Assertion Failed)\n", + t->t_name); + log_printf(&t->t_root->t_fatallog, "File: %s\n", file); + log_printf(&t->t_root->t_fatallog, "Line: %d\n", line); + log_printf(&t->t_root->t_fatallog, "Test: %s\n\n", cond); + log_printf(&t->t_root->t_debuglog, "* %s (%s:%d) (FAILED)\n", + t->t_name, file, line); + longjmp(*t->t_jmp, 1); } +/* + * Performance counters. Really we just want to start and stop timers, to + * measure elapsed time in usec. + */ + static void -test_getperfcnt(tperfcnt_t *pc) +init_perfcnt(tperfcnt_t *pc) { + memset(pc, 0, sizeof (*pc)); +} + +static void +start_perfcnt(tperfcnt_t *pc) +{ + if (pc->pc_running) { + return; + } #if defined(_WIN32) LARGE_INTEGER pcnt, pfreq; QueryPerformanceCounter(&pcnt); QueryPerformanceFrequency(&pfreq); - pc->pc_count = pcnt.QuadPart; + pc->pc_base = pcnt.QuadPart; pc->pc_rate = pfreq.QuadPart; #elif defined(CLOCK_MONOTONIC) uint64_t usecs; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - pc->pc_count = ts.tv_sec * 1000000000; - pc->pc_count += ts.tv_nsec; + pc->pc_base = ts.tv_sec * 1000000000; + pc->pc_base += ts.tv_nsec; pc->pc_rate = 1000000000; #else struct timeval tv; gettimeofday(&tv, NULL); - pc->pc_count = tv.tv_secs * 1000000; - pc->pc_count += tv.tv_usec; + pc->pc_base = tv.tv_secs * 1000000; + pc->pc_base += tv.tv_usec; pc->pc_rate = 1000000; #endif + pc->pc_running = 1; } -/* - * Calculates the seconds and usecs between two values. Returns the - * entire usecs as a 64-bit integer. - */ -uint64_t -test_perfdelta(tperfcnt_t *start, tperfcnt_t *end, int *secp, int *usecp) +static void +stop_perfcnt(tperfcnt_t *pc) +{ + if (!pc->pc_running) { + return; + } + do { +#if defined(_WIN32) + LARGE_INTEGER pcnt; + QueryPerformanceCounter(&pcnt); + pc->pc_count += (pcnt.QuadPart - pc->pc_base); +#elif defined(CLOCK_MONOTONIC) + uint64_t ns; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + ns = (ts.tv_sec * 1000000000); + ns += ts.tv_nsec; + pc->pc_count += (ns - pc->pc_base); +#else + uint64_t us; + struct timeval tv; + + gettimeofday(&tv, NULL); + us = (ts.tv_sec * 1000000); + us += ts.tv_usec; + pc->pc_count += (us - pc->pc_base); +#endif + } while (0); +} + +static void +read_perfcnt(tperfcnt_t *pc, int *secp, int *usecp) { uint64_t delta, rate, sec, usec; - delta = end->pc_count - start->pc_count; - rate = start->pc_rate; + delta = pc->pc_count; + rate = pc->pc_rate; sec = delta / rate; delta -= (sec * rate); @@ -360,81 +454,223 @@ test_perfdelta(tperfcnt_t *start, tperfcnt_t *end, int *secp, int *usecp) if (usecp) { *usecp = (int)usec; } - return ((sec * 1000000) + usec); } -void -test_ctx_log(test_ctx_t *ctx, const char *fmt, ...) +/* + * Thread-specific data. Pthreads uses one way, Win32 another. If you + * lack threads, just #define NO_THREADS + */ + +#ifdef NO_THREADS +static void *specific_val; + +static int +init_specific(void) { + return (0); } -void -test_ctx_vlog(test_ctx_t *ctx, const char *fmt, va_list va) +static int +set_specific(void *v) { + specific_val = v; + return (0); } -void -test_ctx_fatal(test_ctx_t *ctx, const char *fmt, ...) +static void * +get_specific(void) +{ + return (specific_val); +} +#elif defined(_WIN32) + +#error "Win32 TLS API missing" + +#else + +pthread_key_t keyctx; + +static int +init_specific(void) +{ + if (pthread_key_create(&keyctx, NULL) != 0) { + return (-1); + } + return (0); +} + +static int +set_specific(void *v) +{ + if (pthread_setspecific(keyctx, v) != 0) { + return (-1); + } + return (0); +} + +static void * +get_specific(void) +{ + return (pthread_getspecific(keyctx)); +} +#endif + +test_ctx_t * +test_get_context(void) +{ + return (get_specific()); +} + +static tctx_t * +get_ctx(void) +{ + return (test_get_context()->T_data); +} + +/* + * Log stuff. + */ +#define LOG_MAXL 200 +#define LOG_CHUNK 2000 +static void +log_vprintf(tlog_t *log, const char *fmt, va_list va) +{ + while ((log->l_size - log->l_length) < LOG_MAXL) { + int newsz = log->l_size + LOG_CHUNK; + char *ptr = malloc(newsz); + if (ptr == NULL) { + return; + } + memcpy(ptr, log->l_buf, log->l_length); + memset(ptr + log->l_length, 0, newsz - log->l_length); + free(log->l_buf); + log->l_buf = ptr; + log->l_size = newsz; + } + (void) vsnprintf(log->l_buf + log->l_length, + log->l_size - (log->l_length + 3), fmt, va); + log->l_length += strlen(log->l_buf + log->l_length); + if (log->l_buf[log->l_length-1] != '\n') { + log->l_buf[log->l_length++] = '\n'; + } +} + +static void +log_printf(tlog_t *log, const char *fmt, ...) { va_list va; va_start(va, fmt); - printf("FATALITY: "); - vprintf(fmt, va); - printf("\n"); + log_vprintf(log, fmt, va); + va_end(va); } -#if 0 -void -test_ctx_emit_pass(test_ctx_t *ctx) +static void +log_dump(tlog_t *log, const char *header, const char *color) { - nasserts++; - if (verbose) { - (void) printf("%s%s%s", color_green, sym_pass, color_none); + char *s; +#ifdef NO_THREADS +#define STRTOK(base, sep) strtok(base, sep) +#else + char *last = NULL; +#define STRTOK(base, sep) strtok_r(base, sep, &last) +#endif + if (log->l_length == 0) { + return; + } + + printf("\n\n%s%s%s\n\n", color, header, color_none); + for (s = STRTOK(log->l_buf, "\n"); s != NULL; s = STRTOK(NULL, "\n")) { + printf(" %s%s%s\n", color, s, color_none); } } -void -test_ctx_emit_fail(test_ctx_t *ctx) +/* + * test_init initializes some common global stuff. Call it from main(), + * if you don't use the framework provided main. + */ +int +test_init(void) { - nasserts++; - if (verbose) { - (void) printf("%s%s%s", color_yellow, sym_fail, color_none); - } - while (!ctx->T_root) { - ctx = ctx->T_parent; + static int inited; + + if (!inited) { + if (init_specific() != 0) { + return (-1); + } + init_terminal(); + inited = 1; } - color_asserts = color_yellow; - ctx->T_fail++; + return (0); } void -test_ctx_emit_fatal(test_ctx_t *ctx) +test_set_verbose(void) { - if (verbose) { - (void) printf("%s%s%s", color_red, sym_fatal, color_none); - } - nasserts++; - while (!ctx->T_root) { - ctx = ctx->T_parent; + verbose = 1; +} + +void +test_debugf(const char *fmt, ...) +{ + va_list va; + tctx_t *ctx = get_ctx()->t_root; + + va_start(va, fmt); + log_vprintf(&ctx->t_debuglog, fmt, va); + va_end(va); +} + +void +test_i_fail(const char *file, int line, const char *reason) +{ + tctx_t *t = get_ctx()->t_root; + tlog_t *faillog = &t->t_root->t_faillog; + tlog_t *debuglog = &t->t_root->t_debuglog; + char buffer[1024]; + + log_printf(debuglog, "* %s (%s:%d) (Failed): %s\n", + t->t_name, file, line, reason); + log_printf(faillog, "* %s\n", t->t_name); + log_printf(faillog, "File: %s\n", file); + log_printf(faillog, "Line: %d\n", line); + log_printf(faillog, "Reason: %s\n", reason); + + if (t->t_root != t) { + t->t_root->t_fail++; } - ctx->T_fatal++; - color_asserts = color_red; + color_asserts = color_yellow; + t->t_fail++; + t->t_done = 1; /* This forces an end */ + longjmp(*t->t_jmp, 1); } void -test_ctx_emit_skipped(test_ctx_t *ctx) +test_i_fatal(const char *file, int line, const char *reason) { - if (verbose) { - (void) printf("%s%s%s", color_none, sym_skip, color_none); + tctx_t *t = get_ctx()->t_root; + tlog_t *faillog = &t->t_root->t_fatallog; + tlog_t *debuglog = &t->t_root->t_debuglog; + + log_printf(debuglog, "* %s (%s:%d) (Error): %s\n", + t->t_name, file, line, reason); + log_printf(faillog, "* %s\n", t->t_name); + log_printf(faillog, "File: %s\n", file); + log_printf(faillog, "Line: %d\n", line); + log_printf(faillog, "Reason: %s\n", reason); + + if (t->t_root != t) { + t->t_root->t_fail++; } - nskips++; + color_asserts = color_red; + t->t_fail++; + t->t_done = 1; /* This forces an end */ + longjmp(*t->t_jmp, 1); } -#endif extern int test_main_impl(void); static void -pretty(void) +init_terminal(void) { #ifndef _WIN32 /* Windows console doesn't do Unicode (consistently). */ @@ -443,13 +679,12 @@ pretty(void) (void) setlocale(LC_ALL, ""); codeset = nl_langinfo(CODESET); - if ((codeset == NULL) || (strcmp(codeset, "UTF-8") != 0)) { - return; + if ((codeset != NULL) && (strcmp(codeset, "UTF-8") == 0)) { + sym_pass = "✔"; + sym_fail = "✘"; + sym_fatal = "🔥"; + sym_skip = "⚠"; } - sym_pass = "✔"; - sym_fail = "✘"; - sym_fatal = "🔥"; - sym_skip = "⚠"; term = getenv("TERM"); if (isatty(1) && (term != NULL)) { @@ -467,11 +702,14 @@ pretty(void) } int -main(int argc, char **argv) +test_i_main(int argc, char **argv) { int i; - /* Poor man's getopt. Very poor. */ + /* + * Poor man's getopt. Very poor. We should add a way for tests + * to retrieve additional test specific options. + */ for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { break; @@ -479,7 +717,13 @@ main(int argc, char **argv) if (strcmp(argv[i], "-v") == 0) { verbose = 1; } + if (strcmp(argv[i], "-d") == 0) { + debug++; + } + } + if (test_init() != 0) { + fprintf(stderr, "Cannot initialize test framework\n"); + exit(1); } - pretty(); - test_main_impl(); + return (test_main_impl()); } \ No newline at end of file diff --git a/tests/test.h b/tests/test.h index c712cb3f..0ddf428f 100644 --- a/tests/test.h +++ b/tests/test.h @@ -80,84 +80,87 @@ typedef struct test_ctx { void *T_data; } test_ctx_t; -extern test_ctx_t *T_C; +#define T_C test_get_context() /* These functions are not for use by tests -- they are used internally. */ -extern int test_ctx_init(test_ctx_t *, test_ctx_t *, const char *); -extern int test_ctx_loop(test_ctx_t *, int); -extern void test_ctx_fini(test_ctx_t *, int *); -extern void test_assert_pass(test_ctx_t *, const char *, const char *, int); -extern void test_assert_skip(test_ctx_t *, const char *, const char *, int); -extern void test_assert_fail(test_ctx_t *, const char *, const char *, int); -extern void test_assert_fatal(test_ctx_t *, const char *, const char *, int); +extern int test_i_start(test_ctx_t *, test_ctx_t *, const char *); +extern int test_i_loop(test_ctx_t *, int); +extern void test_i_finish(test_ctx_t *, int *); +extern int test_i_main(int, char **); + +extern void test_i_assert_pass(const char *, const char *, int); +extern void test_i_assert_skip(const char *, const char *, int); +extern void test_i_assert_fail(const char *, const char *, int); +extern void test_i_assert_fatal(const char *, const char *, int); +extern void test_i_skip(const char *, int, const char *); +extern void test_i_fail(const char *, int, const char *); +extern void test_i_fatal(const char *, int, const char *); /* - * test_ctx_do is a helper function not to be called directly by user - * code. It has to be here exposed, in order for setjmp() to work. Do - * not call it directly, instead use one of the other macros. + * test_i_run is a helper function not to be called directly by user + * code. It has to be here exposed, in order for setjmp() to work. + * and for the code block to be inlined. */ -#define test_ctx_do(T_xparent, T_xname, T_xcode, T_rvp) \ +#define test_i_run(T_xparent, T_xname, T_code, T_rvp) \ do { \ static test_ctx_t T_ctx; \ - int T_jumped; \ - if (test_ctx_init(&T_ctx, T_xparent, T_xname) != 0) { \ + int T_unwind; \ + if (test_i_start(&T_ctx, T_xparent, T_xname) != 0) { \ break; \ } \ - T_jumped = setjmp(T_ctx.T_jmp); \ - if (test_ctx_loop(&T_ctx, T_jumped) != 0) { \ + T_unwind = setjmp(T_ctx.T_jmp); \ + if (test_i_loop(&T_ctx, T_unwind) != 0) { \ break; \ } \ do { \ - T_xcode \ + T_code \ } while (0); \ - test_ctx_fini(&T_ctx, T_rvp); \ + test_i_finish(&T_ctx, T_rvp); \ } while (0) -/* - * test_ctx_convey creates a "convey" using a custom parent context. - * The idea is that this new context creates a new "root" context, - * which can be useful for running tests in separate threads, or for - * passing context around explicitly. It should rarely be needed. - */ -#define test_ctx_convey(T_xparent, T_xname, T_xcode) \ - test_ctx_do(T_xparent, T_xname, T_xcode, NULL) - /* * test_main is used to wrap the top-level of your test suite, and is * used in lieu of a normal main() function. */ -#define test_main(name, code) \ +#define test_main(T_name, T_code) \ int test_main_impl(void) { \ - static test_ctx_t ctx; \ - int rv; \ - test_ctx_do(NULL, name, code, &rv); \ - return (rv); \ + int T_rv; \ + test_i_run(NULL, T_name, T_code, &T_rv); \ + return (T_rv); \ +} \ +int main(int argc, char **argv) { \ + return (test_i_main(argc, argv)); \ } /* - * test_ctx_assert and test_ctx_so allow you to run assertions against - * an explicit context. You shouldn't need these. + * test_block declares an enclosing test block. The block is inlined, + * and a new context is created. The purpose is to create a new block + * in a thread, or other function, instead of using main(). Note that + * tests that run concurrently may lead to confused output. A suitable + * parent context can be obtained with test_get_context(). If NULL is + * supplied instead, then a new root context is created. + */ +#define test_block(T_parent, T_name, T_code) \ + test_i_run(T_parent, T_name, T_code, NULL) + +/* + * test_assert and test_so allow you to run assertions. */ -#define test_ctx_assert(T_xctx, T_cond) \ +#define test_assert(T_cond) \ if (!(T_cond)) { \ - test_assert_fatal(T_xctx, #T_cond, __FILE__, __LINE__); \ + test_i_assert_fatal(#T_cond, __FILE__, __LINE__); \ } else { \ - test_assert_pass(T_xctx, #T_cond, __FILE__, __LINE__); \ + test_i_assert_pass(#T_cond, __FILE__, __LINE__); \ } -#define test_ctx_so(T_xctx, T_cond) \ +#define test_so(T_cond) \ if (!(T_cond)) { \ - test_assert_fail(T_xctx, #T_cond, __FILE__, __LINE__); \ + test_i_assert_fail(#T_cond, __FILE__, __LINE__); \ } else { \ - test_assert_pass(T_xctx, #T_cond, __FILE__, __LINE__); \ + test_i_assert_pass(#T_cond, __FILE__, __LINE__); \ } -/* - * test_ctx_skip can be used to skip further processing in the context. - */ -extern void test_ctx_skip(test_ctx_t *); - /* * These are convenience versions that use the "global" context. * Note that normally convey() will create its own contexts. These @@ -169,41 +172,53 @@ extern void test_ctx_skip(test_ctx_t *); * the body. The is its scope, and may be called repeatedly * within the body of a loop. */ -#define test_convey(T_xn, T_xc) test_ctx_convey(T_C, T_xn, T_xc) - -/* - * test_assert is just like assert(3), but applies to the context. - * Failures in these are treated as "fatal" failures; and get higher - * alerting than other other kinds of failures. The entire test run - * all the way to the root context is aborted. (Other root contexts - * can run, however.) - */ -#define test_assert(T_cond) test_ctx_assert(T_C, T_cond) - -/* - * test_so() is like test_assert, but failures here only abort the current - * test. - */ -#define test_so(T_cond) test_ctx_so(T_C, T_cond) +#define test_convey(T_name, T_code) test_i_run(T_C, T_name, T_code, NULL) /* * test_skip() just stops processing of the rest of the current context, * and records that processing was skipped. */ -#define test_skip() test_ctx_skip(T_C) +#define test_skip(reason) test_i_skip(__FILE__, __LINE__, reason) +#define test_fail(reason) test_i_fail(__FILE__, __LINE__, reason) +#define test_fatal(reason) test_i_fatal(__FILE__, __LINE__, reason) /* * test_skip_assert() is used to skip processing of a single assertion. * Further processing in the same context continues. */ -#define test_skip_assert(T_cnd) test_assert_skip(T_C, T_cnd) +#define test_skip_assert(T_cnd) test_i_assert_skip(T_cnd) /* * test_skip_convey() is used to skip a convey context. This is intended * to permit changing "test_convey", to "test_skip_convey". This is logged, * and the current convey context continues processing. */ -#define test_skip_convey(X_n, X_c) /* TBD */ +#define test_skip_convey(T_name, T_code) \ + test_convey(T_name, test_skip()) +/* + * test_get_context obtains the current context. It uses thread local + * storage in the event that threading is in use, allowing concurrent threads + * to "sort" of work. + */ +extern test_ctx_t *test_get_context(void); + +/* + * test_init sets up initial things required for testing. If you don't + * use test_main(), then you need to call this somewhere early in your + * main routine. If it returns non-zero, then you can't use the framework. + */ +extern int test_init(void); + +/* + * test_set_verbose sets verbose mode. You shouldn't set this normally, + * as the main() wrapper looks at argv, and does if -v is supplied. + */ +extern void test_set_verbose(void); + +/* + * test_debugf() is like printf, but it goes to a test-specific debug log. + */ +extern void test_debugf(const char *, ...); #endif /* TEST_TEST_H */ -- cgit v1.2.3-70-g09d2