diff options
Diffstat (limited to 'tests/test.c')
| -rw-r--r-- | tests/test.c | 470 |
1 files changed, 357 insertions, 113 deletions
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 <langinfo.h> #include <unistd.h> #include <time.h> +#include <pthread.h> #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 |
