diff options
| author | Garrett D'Amore <garrett@damore.org> | 2016-12-20 20:59:33 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2016-12-20 20:59:33 -0800 |
| commit | 529c84d6a1bf2400170263c9e68d9433a70cc43d (patch) | |
| tree | ee97e857548a3cfe8dc4c7e2b0a179c14f9fb69c | |
| parent | 09c631a793e46a1acc5848592f246fbb2b6c6f4e (diff) | |
| download | nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.gz nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.bz2 nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.zip | |
Updates to reflect new external convey framework.
| -rw-r--r-- | tests/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | tests/README.adoc | 13 | ||||
| -rw-r--r-- | tests/convey.c | 917 | ||||
| -rw-r--r-- | tests/convey.h | 349 | ||||
| -rw-r--r-- | tests/demo.c | 94 | ||||
| -rw-r--r-- | tests/list.c | 23 | ||||
| -rw-r--r-- | tests/test.c | 850 | ||||
| -rw-r--r-- | tests/test.h | 270 |
8 files changed, 1189 insertions, 1332 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7a9f5e98..46cae66a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,7 +33,7 @@ if (NNG_TESTS) set (TEST_PORT 12100) macro (add_nng_test NAME TIMEOUT) list (APPEND all_tests ${NAME}) - add_executable (${NAME} ${NAME}.c test.c) + add_executable (${NAME} ${NAME}.c convey.c) target_link_libraries (${NAME} ${PROJECT_NAME}) add_test (NAME ${NAME} COMMAND ${NAME} ${TEST_PORT}) set_tests_properties (${NAME} PROPERTIES TIMEOUT ${TIMEOUT}) @@ -52,5 +52,4 @@ else () endmacro (add_nng_perf) endif () -add_nng_test(demo 5) -add_nng_test(list 5)
\ No newline at end of file +add_nng_test(list 5) diff --git a/tests/README.adoc b/tests/README.adoc new file mode 100644 index 00000000..d9cd71fd --- /dev/null +++ b/tests/README.adoc @@ -0,0 +1,13 @@ +About C-Convey +-------------- + +The convey framework in this directory was originally started for +this project, but has since been promoted to a project of it's own. +Please see it's home page on https://github.com/gdamore/c-convey[github] +for updates and further information. + +The convey framework is licensed under the same terms as the rest +of this project (MIT style license). + +If updating the code here, please also work with the upstream to +ensure that appropriate changes or fixes are made upstream. diff --git a/tests/convey.c b/tests/convey.c new file mode 100644 index 00000000..e74e150a --- /dev/null +++ b/tests/convey.c @@ -0,0 +1,917 @@ +/* + * Copyright 2016 Garrett D'Amore <garrett@damore.org> + * + * This software is supplied under the terms of the MIT License, a + * copy of which should be located in the distribution where this + * file was obtained (LICENSE.txt). A copy of the license may also be + * found online at https://opensource.org/licenses/MIT. + */ + +/* + * This contains some of the guts of the testing framework. It is in a single + * file in order to simplify use and minimize external dependencies. + * + * If you use this with threads, you need to either have pthreads (and link + * your test program against the threading library), or you need Windows. + * Support for C11 threading is not implemented yet. + * + * For timing, this code needs a decent timer. It will use clock_gettime + * if it appears to be present, or the Win32 QueryPerformanceCounter, or + * gettimeofday() if neither of those are available. + * + * This code is unlikely to function at all on anything that isn't a UNIX + * or Windows system. As we think its unlikely that you'd want to use this + * to run testing inside an embedded device or something, we think this is a + * reasonable limitation. + * + * Note that we expect that on Windows, you have a reasonably current + * version of MSVC. (Specifically we need a few C99-isms that Microsoft + * only added late -- like in 2010. Specifically uint32_t and uint64_t). + */ + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <setjmp.h> +#include <stdarg.h> +#include <string.h> + +#ifdef _WIN32 +#include <windows.h> + +#else + +#include <time.h> +#include <locale.h> +#include <langinfo.h> +#include <unistd.h> +#include <sys/time.h> + +#ifndef CONVEY_NO_THREADS +#include <pthread.h> +#endif + +#endif + +#include "convey.h" + +/* + * About symbol naming. We use Go-like conventions to help set expectations, + * even though we cannot necessarily count on the linker to prevent + * access. We have to accept that we may be inlined directly into the + * user's program, so we try not to pollute their namespace. (Convenience + * pollution may be enabled in convey.h.) + * + * Symbols exposed to users directly are named ConveyXXX using CamelCase + * (just like Go). + * + * Symbols used internally, but which must be exposed for external linkage + * will be named using conveyXXX (camelCase with the leading "c" lower.) + * + * Symbols used internally, and kept entirely within the the .c file, are + * named convey_xxx (using underscores). + * + * When symbols can reasonably be expected not to collide and are local to + * a scope not expressed to user code, these rules are relaxed. + */ + +static const char *convey_sym_pass = "."; +static const char *convey_sym_skip = "?"; +static const char *convey_sym_fail = "X"; +static const char *convey_sym_fatal = "!"; +static const char *convey_nocolor = ""; +static const char *convey_green = ""; +static const char *convey_red = ""; +static const char *convey_yellow = ""; + +static int convey_debug = 0; +static int convey_verbose = 0; +static int convey_nassert = 0; +static int convey_nskip = 0; +static const char *convey_assert_color = ""; + +#define CONVEY_EXIT_OK 0 +#define CONVEY_EXIT_USAGE 1 +#define CONVEY_EXIT_FAIL 2 +#define CONVEY_EXIT_FATAL 3 +#define CONVEY_EXIT_NOMEM 4 + +struct convey_timer { + uint64_t timer_base; + uint64_t timer_count; + uint64_t timer_rate; + int timer_running; +}; + +struct convey_log { + char *log_buf; + size_t log_size; + size_t log_length; +}; + +struct convey_ctx { + char ctx_name[256]; + struct convey_ctx *ctx_parent; + struct convey_ctx *ctx_root; /* the root node on the list */ + struct convey_ctx *ctx_next; /* root list only, cleanup */ + int ctx_level; + int ctx_done; + int ctx_started; + jmp_buf *ctx_jmp; + int ctx_fatal; + int ctx_fail; + int ctx_skip; + int ctx_printed; + struct convey_timer ctx_timer; + struct convey_log *ctx_errlog; + struct convey_log *ctx_faillog; + struct convey_log *ctx_dbglog; +}; + +static void convey_print_result(struct convey_ctx *); +static void convey_init_timer(struct convey_timer *); +static void convey_start_timer(struct convey_timer *); +static void convey_stop_timer(struct convey_timer *); +static void convey_read_timer(struct convey_timer *, int *, int *); +static void convey_init_term(void); +static int convey_tls_init(void); +static void *convey_tls_get(void); +static int convey_tls_set(void *); +static struct convey_ctx *convey_get_ctx(void); +static void convey_vlogf(struct convey_log *, const char *, va_list, int); +static void convey_logf(struct convey_log *, const char *, ...); +static void convey_log_emit(struct convey_log *, const char *, const char *); +static void convey_log_free(struct convey_log *); +static struct convey_log *convey_log_alloc(void); +static char *convey_nextline(char **); + +/* + * convey_print_result prints the test results. It prints more information + * in convey_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. + * + * We also leverage this point to detect completion of a root context, and + * deallocate the child contexts. The root context should never be reentered + * here. + */ +static void +convey_print_result(struct convey_ctx *t) +{ + int secs, usecs; + + if (t->ctx_root == t) { + convey_stop_timer(&t->ctx_timer); /* This is idempotent */ + + convey_read_timer(&t->ctx_timer, &secs, &usecs); + + (void) convey_logf(t->ctx_dbglog, "Test %s: %s (%d.%02ds)\n", + t->ctx_fatal ? "FATAL" : + t->ctx_fail ? "FAIL" : + t->ctx_skip ? "PASS (with SKIPs)" : + "PASS", t->ctx_name, secs, usecs / 10000); + + if (convey_verbose) { + (void) printf("\n"); + } + convey_log_emit(t->ctx_errlog, "Errors:", convey_red); + convey_log_emit(t->ctx_faillog, "Failures:", convey_yellow); + if (convey_debug) { + convey_log_emit(t->ctx_dbglog, "Log:", convey_nocolor); + } + if (convey_verbose) { + (void) printf("\n\n%s%d assertions thus far%s", + convey_assert_color, + convey_nassert, + convey_nocolor); + if (convey_nskip) { + (void) printf(" %s%s%s", + convey_yellow, + "(one or more sections skipped)", + convey_nocolor); + } + (void) printf("\n\n--- %s: %s (%d.%02ds)\n", + t->ctx_fatal ? "FATAL" : + t->ctx_fail ? "FAIL" : + "PASS", t->ctx_name, secs, usecs / 10000); + } + + /* Remove the context, because we cannot reenter here */ + convey_tls_set(NULL); + + while (t != NULL) { + struct convey_ctx *freeit = t; + if (t->ctx_root == t) { + convey_log_free(t->ctx_dbglog); + convey_log_free(t->ctx_faillog); + convey_log_free(t->ctx_errlog); + } + t = t->ctx_next; + memset(freeit, 0, sizeof (*freeit)); + free(freeit); + } + } +} + +/* + * conveyStart 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 +conveyStart(conveyScope *scope, const char *name) +{ + struct convey_ctx *t, *parent; + + parent = convey_get_ctx(); + + if ((t = scope->cs_data) != NULL) { + if (t->ctx_done) { + convey_print_result(t); + return (1); /* all done, skip */ + } + return (0); /* continue onward */ + } + scope->cs_data = (t = calloc(1, sizeof (struct convey_ctx))); + if (t == NULL) { + goto allocfail; + } + t->ctx_jmp = &scope->cs_jmp; + + (void) snprintf(t->ctx_name, sizeof (t->ctx_name)-1, "%s", name); + if (parent != NULL) { + t->ctx_parent = parent; + t->ctx_root = t->ctx_parent->ctx_root; + t->ctx_level = t->ctx_parent->ctx_level + 1; + /* unified logging against the root context */ + t->ctx_dbglog = t->ctx_root->ctx_dbglog; + t->ctx_faillog = t->ctx_root->ctx_faillog; + t->ctx_errlog = t->ctx_root->ctx_errlog; + t->ctx_next = t->ctx_root->ctx_next; + t->ctx_root->ctx_next = t; + } else { + t->ctx_parent = t; + t->ctx_root = t; + if (((t->ctx_errlog = convey_log_alloc()) == NULL) || + ((t->ctx_faillog = convey_log_alloc()) == NULL) || + ((t->ctx_dbglog = convey_log_alloc()) == NULL)) { + goto allocfail; + } + convey_logf(t->ctx_dbglog, + "Test Started: %s\n", t->ctx_name); + } + return (0); +allocfail: + if (t != NULL) { + convey_log_free(t->ctx_errlog); + convey_log_free(t->ctx_dbglog); + convey_log_free(t->ctx_faillog); + free(t); + scope->cs_data = NULL; + } + if (parent != NULL) { + ConveyError("Unable to allocate context"); + } + return (1); +} + +/* + * conveyLoop is called right after setjmp. The jumped being true indicates + * 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 +conveyLoop(conveyScope *scope, int unwind) +{ + struct convey_ctx *t; + int i; + if ((t = scope->cs_data) == NULL) { + return (1); + } + if (unwind) { + if ((t->ctx_parent != t) && (t->ctx_parent != NULL)) { + longjmp(*t->ctx_parent->ctx_jmp, 1); + } + if (t->ctx_done) { + convey_print_result(t); + return (1); + } + } + if (!t->ctx_started) { + t->ctx_started = 1; + + if (convey_verbose) { + if (t->ctx_root == t) { + (void) printf("=== RUN: %s\n", t->ctx_name); + } else { + (void) printf("\n"); + for (i = 0; i < t->ctx_level; i++) { + (void) printf(" "); + } + (void) printf("%s ", t->ctx_name); + (void) fflush(stdout); + } + } + + convey_init_timer(&t->ctx_timer); + convey_start_timer(&t->ctx_timer); + } + /* Reset TC for the following code. */ + convey_tls_set(t); + return (0); +} + +void +conveyFinish(conveyScope *scope, int *rvp) +{ + struct convey_ctx *t; + if ((t = scope->cs_data) == NULL) { + /* allocation failure */ + *rvp = CONVEY_EXIT_NOMEM; + return; + } + t->ctx_done = 1; + if (rvp != NULL) { + /* exit code 1 is reserved for usage errors */ + if (t->ctx_fatal) { + *rvp = CONVEY_EXIT_FATAL; + } else if (t->ctx_fail) { + *rvp = CONVEY_EXIT_FAIL; + } else { + *rvp = CONVEY_EXIT_OK; + } + } + longjmp(*t->ctx_jmp, 1); +} + +void +conveySkip(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + struct convey_ctx *t = convey_get_ctx(); + struct convey_log *dlog = t->ctx_dbglog; + if (convey_verbose) { + (void) printf("%s%s%s", + convey_yellow, convey_sym_skip, convey_nocolor); + } + convey_logf(dlog, "* %s (%s:%d) (Skip): ", + t->ctx_name, file, line); + va_start(ap, fmt); + convey_vlogf(dlog, fmt, ap, 1); + va_end(ap); + t->ctx_done = 1; /* This forces an end */ + convey_nskip++; + longjmp(*t->ctx_jmp, 1); +} + +void +conveyAssertFail(const char *cond, const char *file, int line) +{ + struct convey_ctx *t = convey_get_ctx(); + convey_nassert++; + if (convey_verbose) { + (void) printf("%s%s%s", + convey_yellow, convey_sym_fail, convey_nocolor); + } + if (t->ctx_root != t) { + t->ctx_root->ctx_fail++; + } + convey_assert_color = convey_yellow; + t->ctx_fail++; + t->ctx_done = 1; /* This forces an end */ + convey_logf(t->ctx_faillog, "* %s (Assertion Failed)\n", + t->ctx_name); + convey_logf(t->ctx_faillog, "File: %s\n", file); + convey_logf(t->ctx_faillog, "Line: %d\n", line); + convey_logf(t->ctx_faillog, "Test: %s\n\n", cond); + convey_logf(t->ctx_dbglog, "* %s (%s:%d) (FAILED): %s\n", + t->ctx_name, file, line, cond); + longjmp(*t->ctx_jmp, 1); +} + +void +conveyAssertPass(const char *cond, const char *file, int line) +{ + struct convey_ctx *t = convey_get_ctx(); + convey_nassert++; + if (convey_verbose) { + (void) printf("%s%s%s", + convey_green, convey_sym_pass, convey_nocolor); + } + convey_logf(t->ctx_dbglog, "* %s (%s:%d) (Passed): %s\n", + t->ctx_name, file, line, cond); +} + +void +conveyAssertSkip(const char *cond, const char *file, int line) +{ + struct convey_ctx *t = convey_get_ctx(); + convey_nskip++; + if (convey_verbose) { + (void) printf("%s%s%s", + convey_yellow, convey_sym_pass, convey_nocolor); + } + convey_logf(t->ctx_dbglog, "* %s (%s:%d) (Skip): %s\n", + t->ctx_name, file, line, cond); +} + +/* + * Performance counters. Really we just want to start and stop timers, to + * measure elapsed time in usec. + */ + +static void +convey_init_timer(struct convey_timer *pc) +{ + memset(pc, 0, sizeof (*pc)); +} + +static void +convey_start_timer(struct convey_timer *pc) +{ + if (pc->timer_running) { + return; + } +#if defined(_WIN32) + LARGE_INTEGER pcnt, pfreq; + QueryPerformanceCounter(&pcnt); + QueryPerformanceFrequency(&pfreq); + pc->timer_base = pcnt.QuadPart; + pc->timer_rate = pfreq.QuadPart; +#elif defined(CLOCK_MONOTONIC) && !defined(CONVEY_USE_GETTIMEOFDAY) + uint64_t usecs; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + pc->timer_base = ts.tv_sec * 1000000000; + pc->timer_base += ts.tv_nsec; + pc->timer_rate = 1000000000; +#else + struct timeval tv; + + gettimeofday(&tv, NULL); + pc->timer_base = tv.tv_sec * 1000000; + pc->timer_base += tv.tv_usec; + pc->timer_rate = 1000000; +#endif + pc->timer_running = 1; +} + +static void +convey_stop_timer(struct convey_timer *pc) +{ + if (!pc->timer_running) { + return; + } + do { +#if defined(_WIN32) + LARGE_INTEGER pcnt; + QueryPerformanceCounter(&pcnt); + pc->timer_count += (pcnt.QuadPart - pc->timer_base); +#elif defined(CLOCK_MONOTONIC) && !defined(CONVEY_USE_GETTIMEOFDAY) + uint64_t ns; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + ns = (ts.tv_sec * 1000000000); + ns += ts.tv_nsec; + pc->timer_count += (ns - pc->timer_base); +#else + uint64_t us; + struct timeval tv; + + gettimeofday(&tv, NULL); + us = (tv.tv_sec * 1000000); + us += tv.tv_usec; + pc->timer_count += (us - pc->timer_base); +#endif + } while (0); +} + +static void +convey_read_timer(struct convey_timer *pc, int *secp, int *usecp) +{ + uint64_t delta, rate, sec, usec; + + delta = pc->timer_count; + rate = pc->timer_rate; + + sec = delta / rate; + delta -= (sec * rate); + + /* + * done this way we avoid dividing rate by 1M -- and the above + * ensures we don't wrap. + */ + usec = (delta * 1000000) / rate; + + if (secp) { + *secp = (int)sec; + } + if (usecp) { + *usecp = (int)usec; + } +} + +/* + * Thread-specific data. Pthreads uses one way, Win32 another. If you + * lack threads, just #define CONVEY_NO_THREADS. C11 thread support is pending. + */ + +#ifdef CONVEY_NO_THREADS +static void *convey_tls_key; + +static int +convey_tls_init(void) +{ + return (0); +} + +static int +convey_tls_set(void *v) +{ + convey_tls_key = v; + return (0); +} + +static void * +convey_tls_get(void) +{ + return (convey_tls_key); +} +#elif defined(_WIN32) + +static DWORD convey_tls_key; + +static int +convey_tls_init(void) +{ + if ((convey_tls_key = TlsAlloc()) == TLS_OUT_OF_INDEXES) { + return (-1); + } + return (0); +} + +static int +convey_tls_set(void *v) +{ + if (!TlsSetValue(convey_tls_key, v)) { + return (-1); + } + return (0); +} + +static void * +convey_tls_get(void) +{ + return ((void *)TlsGetValue(convey_tls_key)); +} + +#else + +pthread_key_t convey_tls_key; + +static int +convey_tls_init(void) +{ + if (pthread_key_create(&convey_tls_key, NULL) != 0) { + return (-1); + } + return (0); +} + +static int +convey_tls_set(void *v) +{ + if (pthread_setspecific(convey_tls_key, v) != 0) { + return (-1); + } + return (0); +} + +static void * +convey_tls_get(void) +{ + return (pthread_getspecific(convey_tls_key)); +} +#endif + +static struct convey_ctx * +convey_get_ctx(void) +{ + return (convey_tls_get()); +} + +/* + * Log stuff. + */ +static void +convey_vlogf(struct convey_log *log, const char *fmt, va_list va, int addnl) +{ + /* Grow the log buffer if we need to */ + while ((log->log_size - log->log_length) < 256) { + int newsz = log->log_size + 2000; + char *ptr = malloc(newsz); + if (ptr == NULL) { + return; + } + memcpy(ptr, log->log_buf, log->log_length); + memset(ptr + log->log_length, 0, newsz - log->log_length); + free(log->log_buf); + log->log_buf = ptr; + log->log_size = newsz; + } + + /* 2 allows space for NULL, and newline */ + (void) vsnprintf(log->log_buf + log->log_length, + log->log_size - (log->log_length + 2), fmt, va); + log->log_length += strlen(log->log_buf + log->log_length); + if (addnl && log->log_buf[log->log_length-1] != '\n') { + log->log_buf[log->log_length++] = '\n'; + } +} + +static void +convey_logf(struct convey_log *log, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + convey_vlogf(log, fmt, va, 0); + va_end(va); +} + +static void +convey_log_emit(struct convey_log *log, const char *header, const char *color) +{ + char *s; + char *last = log->log_buf; + if (log->log_length == 0) { + return; + } + + (void) printf("\n\n%s%s%s\n\n", color, header, convey_nocolor); + while ((s = convey_nextline(&last)) != NULL) { + (void) printf(" %s%s%s\n", color, s, convey_nocolor); + } +} + +static void +convey_log_free(struct convey_log *log) +{ + if (log != NULL) { + if (log->log_size != 0) { + free(log->log_buf); + } + free(log); + } +} + +static struct convey_log * +convey_log_alloc(void) +{ + return (calloc(1, sizeof (struct convey_log))); +} + +/* + * ConveyInit initializes some common global stuff. Call it from main(), + * if you don't use the framework provided main. + */ +int +ConveyInit(void) +{ + static int inited; + + if (!inited) { + if (convey_tls_init() != 0) { + return (-1); + } + convey_init_term(); + inited = 1; + } + return (0); +} + +void +ConveySetVerbose(void) +{ + convey_verbose = 1; +} + +void +conveyFail(const char *file, int line, const char *fmt, ...) +{ + struct convey_ctx *t = convey_get_ctx(); + struct convey_log *flog = t->ctx_faillog; + struct convey_log *dlog = t->ctx_dbglog; + va_list ap; + + convey_logf(dlog, "* %s (%s:%d) (Failed): ", t->ctx_name, file, line); + va_start(ap, fmt); + convey_vlogf(dlog, fmt, ap, 1); + va_end(ap); + + convey_logf(flog, "* %s\n", t->ctx_root->ctx_name); + convey_logf(flog, "File: %s\n", file); + convey_logf(flog, "Line: %d\n", line); + convey_logf(flog, "Reason: "); + va_start(ap, fmt); + convey_vlogf(flog, fmt, ap, 1); + va_end(ap); + + if (t->ctx_root != t) { + t->ctx_root->ctx_fail++; + } + convey_assert_color = convey_yellow; + t->ctx_fail++; + t->ctx_done = 1; /* This forces an end */ + longjmp(*t->ctx_jmp, 1); +} + +void +conveyError(const char *file, int line, const char *fmt, ...) +{ + struct convey_ctx *t = convey_get_ctx(); + struct convey_log *flog = t->ctx_errlog; + struct convey_log *dlog = t->ctx_dbglog; + va_list ap; + + convey_logf(dlog, "* %s (%s:%d) (Error): ", t->ctx_name, file, line); + va_start(ap, fmt); + convey_vlogf(dlog, fmt, ap, 1); + va_end(ap); + + convey_logf(flog, "* %s\n", t->ctx_root->ctx_name); + convey_logf(flog, "File: %s\n", file); + convey_logf(flog, "Line: %d\n", line); + convey_logf(flog, "Reason: "); + va_start(ap, fmt); + convey_vlogf(flog, fmt, ap, 1); + va_end(ap); + + if (t->ctx_root != t) { + t->ctx_root->ctx_fail++; + } + convey_assert_color = convey_red; + t->ctx_fail++; + t->ctx_done = 1; /* This forces an end */ + longjmp(*t->ctx_jmp, 1); +} + +void +conveyPrintf(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + struct convey_ctx *t = convey_get_ctx(); + struct convey_log *dlog = t->ctx_dbglog; + + convey_logf(dlog, "* %s (%s:%d) (Debug): ", t->ctx_name, file, line); + va_start(ap, fmt); + convey_vlogf(dlog, fmt, ap, 1); + va_end(ap); +} + +extern int conveyMainImpl(void); + +static void +convey_init_term(void) +{ +#ifndef _WIN32 + /* Windows console doesn't do Unicode (consistently). */ + const char *codeset; + const char *term; + + (void) setlocale(LC_ALL, ""); + codeset = nl_langinfo(CODESET); + if ((codeset != NULL) && (strcmp(codeset, "UTF-8") == 0)) { + convey_sym_pass = "✔"; + convey_sym_fail = "✘"; + convey_sym_fatal = "🔥"; + convey_sym_skip = "⚠"; + } + + term = getenv("TERM"); + if (isatty(1) && (term != NULL)) { + if ((strstr(term, "xterm") != NULL) || + (strstr(term, "ansi") != NULL) || + (strstr(term, "color") != NULL)) { + convey_nocolor = "\e[0m"; + convey_green = "\e[32m"; + convey_yellow = "\e[33m"; + convey_red = "\e[31m"; + convey_assert_color = convey_green; + } + } +#endif +} + +/* + * This function exists because strtok isn't safe, and strtok_r and + * strsep are not universally available. Its like strsep, but only does + * newlines. Could be implemented using strpbrk, but this is probably + * faster since we are only looking for a single character. + */ +static char * +convey_nextline(char **next) +{ + char *line = *next; + char *nl; + char c; + + if (line == NULL) { + return (NULL); + } + for (nl = line; (c = (*nl)) != '\0'; nl++) { + if (c == '\n') { + *nl = '\0'; + *next = nl + 1; + return (line); + } + } + /* + * If the last character in the file is a newline, treat it as + * the end. (This will appear as a blank last line.) + */ + if (*line == '\0') { + line = NULL; + } + *next = NULL; + return (line); +} + +int +conveyMain(int argc, char **argv) +{ + int i; + const char *status; + const char *prog; + struct convey_timer pc; + int secs, usecs; + + if ((argc > 0) && (argv[0] != NULL)) { + prog = argv[0]; + } else { + prog = "<unknown>"; + } + + /* + * 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; + } + if (strcmp(argv[i], "-v") == 0) { + ConveySetVerbose(); + } + if (strcmp(argv[i], "-d") == 0) { + convey_debug++; + } + } + if (ConveyInit() != 0) { + (void) fprintf(stderr, "Cannot initialize test framework\n"); + exit(CONVEY_EXIT_NOMEM); + } + + convey_init_timer(&pc); + convey_start_timer(&pc); + i = conveyMainImpl(); + convey_stop_timer(&pc); + + switch (i) { + case CONVEY_EXIT_NOMEM: + (void) fprintf(stderr, "Cannot initialize root test context\n"); + exit(CONVEY_EXIT_NOMEM); + case CONVEY_EXIT_OK: + if (convey_verbose) { + (void) printf("PASS\n"); + } + status = "ok"; + break; + case CONVEY_EXIT_FAIL: + status = "FAIL"; + if (convey_verbose) { + (void) printf("FAIL\n"); + } + break; + default: + status = "FATAL"; + if (convey_verbose) { + (void) printf("FATAL\n"); + } + break; + } + + convey_read_timer(&pc, &secs, &usecs); + (void) printf("%-8s%-52s%4d.%03ds\n", status, prog, secs, usecs / 1000); + exit(i); +} diff --git a/tests/convey.h b/tests/convey.h index ea9d7a29..bb52ad7d 100644 --- a/tests/convey.h +++ b/tests/convey.h @@ -1,39 +1,21 @@ /* * Copyright 2016 Garrett D'Amore <garrett@damore.org> * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom - * the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. + * This software is supplied under the terms of the MIT License, a + * copy of which should be located in the distribution where this + * file was obtained (LICENSE.txt). A copy of the license may also be + * found online at https://opensource.org/licenses/MIT. */ -#ifndef TESTS_CONVEY_H - -#define TESTS_CONVEY_H - -#include "test.h" - -/* - * This header file provides a friendlier API to the test-convey framework. - * It basically provides some "friendly" names for symbols to use instead of - * the test_xxx symbols. Basically we pollute your namespace, for your - * benefit. Don't like the pollution? Use test.h instead. - */ +#ifndef CONVEY_H +#define CONVEY_H +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <setjmp.h> +#include <stdarg.h> /* * This test framework allows one to write tests as a form of assertion, @@ -41,7 +23,7 @@ * * The test framework provides a main() function. * - * To use this call the test_main() macro, and embed test_convey() references. + * To use this call the Main() macro, and embed Test() and Convey() blocks. * These can be nested, and after each convey the entire stack is popped so * that execution can continue from the beginning, giving each test section * the same environment. @@ -51,112 +33,285 @@ * * Here's a sample file: * - * TestMain("Integer Tests", { - * int x = 1; int y = 2; - * Convey("Addition works", func() { - * So(y == 2); - * So(y + x == 3); - * So(x + y == 3); - * Convey("Even big numbers", func() { - * y = 100; - * So(x + y == 101); - * }); - * Convey("Notice y is still 2 in this context", func() { + * Main({ + * Test({"Integer Tests", { + * int x = 1; int y = 2; + * Convey("Addition works", func() { * So(y == 2); - * }); - * }); + * So(y + x == 3); + * So(x + y == 3); + * Convey("Even big numbers", { + * y = 100; + * So(x + y == 101); + * }); + * Convey("Notice y is still 2 in this context", { + * So(y == 2); + * }); + * }); + * }); * }) - * - * There are other macros, but this is a work in progress. The inspiration - * for this is from GoConvey -- github.com/smartystreets/goconvey - but this - * is a version for C programs. * - * In addition to the names listed here, your test code should avoid using - * names beginning with "test_" or "T_" as we use those names internally - * in macros, which may collide or do other bad things with your names. + * This was inspired by GoConvey -- github.com/smartystreets/goconvey - but + * there are differences of course -- C is not Go! + * + * Pleaes note that we abuse the C preprocessor and setjmp fairly heavily, + * and as a result of the magic we have to do, a lot of these guts must be + * exposed in this header file. HOWEVER, only symbols beginning with a + * capital letter are intended for consumers. All others are for internal + * use only. Otherwise, welcome to the sausage factory. + * + * Please see the documentation at github.com/gdamore/c-convey for more + * details about how to use this. + */ + +/* + * This structure has to be exposed in order to expose the buffer used for + * setjmp. It's members should never be accessed directly. These should be + * allocated statically in the routine(s) that need custom contexts. The + * framework creates a context automatically for each convey scope. + */ +typedef struct { + jmp_buf cs_jmp; + void *cs_data; +} conveyScope; + +/* These functions are not for use by tests -- they are used internally. */ +extern int conveyStart(conveyScope *, const char *); +extern int conveyLoop(conveyScope *, int); +extern void conveyFinish(conveyScope *, int *); +extern int conveyMain(int, char **); + +extern void conveyAssertPass(const char *, const char *, int); +extern void conveyAssertSkip(const char *, const char *, int); +extern void conveyAssertFail(const char *, const char *, int); +extern void conveySkip(const char *, int, const char *, ...); +extern void conveyFail(const char *, int, const char *, ...); +extern void conveyError(const char *, int, const char *, ...); +extern void conveyPrintf(const char *, int, const char *, ...); + +/* + * conveyRun is a helper macro 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. Becuase this inlines user + * code, we have to be *very* careful with symbol names. + */ +#define conveyRun(convey_name, convey_code, convey_resultp) \ + do { \ + static conveyScope convey_scope; \ + int convey_unwind; \ + int convey_break = 0; \ + if (conveyStart(&convey_scope, convey_name) != 0) { \ + break; \ + } \ + convey_unwind = setjmp(convey_scope.cs_jmp); \ + if (conveyLoop(&convey_scope, convey_unwind) != 0) { \ + break; \ + } \ + do { \ + convey_code \ + } while (0); \ + if (convey_break) { \ + break; \ + } \ + conveyFinish(&convey_scope, convey_resultp); \ + } while (0); + +/* + * ConveyRset establishes a reset for the current scope. This code will + * be executed every time the current scope is unwinding. This means that + * the code will be executed each time a child convey exits. It is also + * going to be executed once more, for the final pass, which doesn't actually + * execute any convey blocks. (This final pass is required in order to + * learn that all convey's, as well as any code beyond them, are complete.) + * + * The way this works is by overriding the existing scope's jump buffer. + * + * Unlike with GoConvey, this must be registered before any children + * convey blocks; the logic only affects convey blocks that follow this + * one, within the same scope. + * + * This must be in a conveyRun scope (i.e. part of a Convey() or a + * top level Test() or it will not compile. + * + * It is possible to have a subsequent reset at the same convey scope + * override a prior reset. Normally you should avoid this, and just + * use lower level convey blocks. */ +#define ConveyReset(convey_reset_code) \ + convey_unwind = setjmp(convey_scope.cs_jmp); \ + if (convey_unwind) { \ + do { \ + convey_reset_code \ + } while (0); \ + } \ + if (conveyLoop(&convey_scope, convey_unwind) != 0) { \ + convey_break = 1; \ + break; \ + } /* - * TestMain is used to generate a main() function that runs your code, - * and is appropriate when your entire program consists of one test. - * This is equivalent to doing Main() with just a single Test(), but it - * may spare you a level of indentation. + * ConveyMain is the outer most scope that most test programs use, unless they + * use the short-cut ConveyTestMain. This creates a main() routine that + * sets up the program, parses options, and then executes the tests nested + * within it. */ -#define TestMain(name, code) test_main(name, code) +#define ConveyMain(code) \ + static int convey_main_rv; \ + int conveyMainImpl(void) { \ + do { \ + code \ + } while (0); \ + return (convey_main_rv); \ + } \ + int main(int argc, char **argv) { \ + return (conveyMain(argc, argv)); \ + } /* - * Main() wraps zero or more Tests, which will then contain Convey - * scopes. This emits a main function, and can only be used once. - * It also cannot be used with TestMain. + * ConveyTest creates a top-level test instance, which can contain multiple + * Convey blocks. */ -#define Main(code) test_main_group(code) +#define ConveyTest(name, code) \ + do { \ + int convey_rv; \ + conveyRun(name, code, &convey_rv); \ + if (convey_rv > convey_main_rv) { \ + convey_main_rv = convey_rv; \ + }; \ + } while (0); /* - * Test creates a top-level test scope. + * ConveyTestMain is used to wrap the top-level of your test suite, and is + * used in lieu of a normal main() function. This is the usual case where + * the executable only contains a single top level test group. It + * is the same as using Main with just a single Test embedded, but saves + * some typing and probably a level of indentation. */ -#define Test(name, code) test_group(name, code) +#define ConveyTestMain(name, code) \ + ConveyMain(ConveyTest(name, code)) /* - * Convey starts a new test scope. These can be nested. The code is - * executed, including new scopes, but each time a new scope is encountered, - * the stack is unwound to give the code a fresh start to work with. + * EXPERIMENTAL: + * If you don't want to use the test framework's main routine, but + * prefer (or need, because of threading for example) to have your + * test code driven separately, you can use inject ConveyBlock() in + * your function. It works like ConveyMain(). These must not be + * nested within other Conveys, Tests, or Blocks (or Main). The + * results are undefined if you try that. The final result pointer may + * be NULL, or a pointer to an integer to receive the an integer + * result from the test. (0 is success, 4 indicates a failure to allocate + * memory in the test framework, and anything else indicates a + * an error or failure in the code being tested. + * + * Blocks do not contain Tests, rather they contain Conveys only. The + * Block takes the place of both Main() and Test(). It is to be hoped + * that you will not need this. */ -#define Convey(name, code) test_convey(name, code) +#define ConveyBlock(name, code, resultp) conveyRun(name, code, resultp) /* - * So is to be used like assert(), except that it always is checked, - * and records results in the current scope. If the assertion fails, - * then no further processing in the same scope (other than possible - * reset logic) is performed. Additional tests at higher scopes, or - * in sibling scopes, may be executed. + * ConveyAssert and ConveySo allow you to run assertions. */ -#define So(condition) test_so(condition) +#define ConveyAssert(truth) \ + do { \ + if (!(truth)) { \ + conveyAssertFail(#truth, __FILE__, __LINE__); \ + } else { \ + conveyAssertPass(#truth, __FILE__, __LINE__); \ + } \ + } while (0) + +#define ConveySo(truth) ConveyAssert(truth) /* - * Skip ceases further processing the current scope (Convey). The - * reason is a string that will be emitted to the log. + * Convey(name, <code>) starts a convey context, with <code> as + * the body. The <code> is its scope, and may be called repeatedly + * within the body of a loop. */ -#define Skip(reason) test_skip(reason) +#define Convey(name, code) conveyRun(name, code, NULL) /* - * Fail records a test failure, and is much like So, except that - * no condition is recorded, and instead you may supply your own - * reason. + * ConveySkip() just stops processing of the rest of the current context, + * and records that processing was skipped. */ -#define Fail(reason) test_fail(reason) +/* + * If your preprocessor doesn't understand C99 variadics, indicate it + * with CONVEY_NO_VARIADICS. In that case you lose support for printf-style + * format specifiers. + */ +#ifdef CONVEY_NO_VARIADICS +#define ConveySkip(reason) conveySkip(__FILE__, __LINE__, reason) +#define ConveyFail(reason) conveyFail(__FILE__, __LINE__, reason) +#define ConveyError(reason) conveyError(__FILE__, __LINE__, reason) +#define ConveyPrintf(reason) conveyPrintf(__FILE__, __LINE__, reason) +#else +#define ConveySkip(...) conveySkip(__FILE__, __LINE__, __VA_ARGS__) +#define ConveyFail(...) conveyFail(__FILE__, __LINE__, __VA_ARGS__) +#define ConveyError(...) conveyError(__FILE__, __LINE__, __VA_ARGS__) +#define ConveyPrintf(...) conveyPrintf(__FILE__, __LINE__, __VA_ARGS__) +#endif /* - * SkipSo is a way to skip a check. The fact that it was skipped - * will be noted. + * ConveySkipSo() is used to skip processing of a single assertion. + * Further processing in the same context continues. */ -#define SkipSo(condition) test_skip_so(condition) +#define ConveySkipAssert(truth) \ + conveyAssertSkip(truth, __FILE__, __LINE__) +#define ConveySkipSo(truth) ConveySkipAssert(truth) /* - * SkipConvey is a way to skip an entire Convey scope. The fact - * will be noted. + * ConveySkipConvey() is used to skip a convey context. This is intended + * to permit changing "Convey", to "SkipConvey". This is logged, + * and the current convey context continues processing. */ -#define SkipConvey(name, code) test_skip_convey(name, code) +#define ConveySkipConvey(name, code) \ + Convey(name, ConveySkip("Skipped")) /* - * Reset is establishes a block of code to be reset when exiting from - * Convey blocks, or even when finishing the current scope. It only - * affects the following code, and it is possible to override a prior - * Reset block with a new one in the same scope. Unlike with GoConvey, - * you must put this *before* other Convey blocks you wish to cover. + * ConveyInit sets up initial things required for testing. If you don't + * use ConveyMain(), then you need to call this somewhere early in your + * main routine. If it returns non-zero, then you can't use the framework. */ -#define Reset(code) test_reset(code) +extern int ConveyInit(void); /* - * Printf is like printf, but it sends its output to the test debug - * log, which is emitted only after the test is finished. The system - * injects events in the debug log as well, which makes this useful for - * debugging flow of execution. + * ConveySetVerbose sets verbose mode. You shouldn't set this normally, + * as the main() wrapper looks at argv, and does if -v is supplied. + */ +extern void ConveySetVerbose(void); + +/* + * These are some public macros intended to make the API more friendly. + * The user is welcome to #undefine any of these he wishes not to + * use, or he can simply avoid the pollution altogether by defining + * CONVEY_NAMESPACE_CLEAN before including this header file. Any + * of these names are already defined using the Convey prefix, with + * the sole exception of Convey() itself, which you cannot undefine. + * (We don't define a ConveyConvey()... that's just silly.) Most of the + * time you won't need this, because its test code that you control, and + * you're writing to Convey(), so you can trivially avoid the conflicts and + * benefit from the friendlier names. This is why this is the default. * - * NB: We avoid variadic macros since some systems don't support them. + * There are some other less often used functions that we haven't aliased, + * like ConveyBlock() and ConveySetVerbose(). Aliases for those offer + * little benefit for the extra pollution they would create. */ -#define Printf test_debugf +#ifndef CONVEY_NAMESPACE_CLEAN + +#define TestMain ConveyTestMain +#define Test ConveyTest +#define Main ConveyMain +#define So ConveySo +#define Skip ConveySkip +#define Fail ConveyFail +#define Error ConveyError +#define SkipConvey ConveySkipConvey +#define SkipSo ConveySkipSo +#define Reset ConveyReset +#define Printf ConveyPrintf +#endif /* CONVEY_NAMESPACE_CLEAN */ -#endif /* TEST_CONVEY_H */ +#endif /* CONVEY_H */ diff --git a/tests/demo.c b/tests/demo.c deleted file mode 100644 index e831109a..00000000 --- a/tests/demo.c +++ /dev/null @@ -1,94 +0,0 @@ -#include <stdio.h> -#include <unistd.h> -#include <stdlib.h> -#include "test.h" - -test_main_group({ - test_group("Things work", { - int x; - int y; - x = 1; - y = 2; - test_convey("X is one", { - test_debugf("A logged message."); - test_assert(x == 1); - }); - test_convey("Y is two", { - test_so(y == 2); - y = 3; - test_so(y == 3); - }); - - test_convey("Operations (Outer)", { - test_convey("Arithmetic", { - test_so(y == 2); - test_convey("Addition", { - test_so(x + y == 3); - test_so(x + y + y == 5); - test_so(x == 9); - y = 5; - test_so(x + y == 6); - }); - test_convey("Subtraction", { - test_so(x - y == -1); - test_so(y - x == 1); - }); - }); - }); - - test_convey("Middle test is skipped", { - test_convey("Start", { - test_so(1 == 1); - }); - test_convey("Middle (Skip?)", { - test_so(9 - 1 == 8); - test_skip("forced skip"); - test_so(0 == 1); - }); - test_convey("Ending", { - test_so(2 == 2); - }); - }); - - }); - - test_group("Second group", { - int x = 1; - static int y =1; - test_convey("x is 1", { -#ifndef _WIN32 - sleep(1); -#endif - test_so(x == 1); - }); - }); - - test_group("Reset group", { - static int x = 0; - static int y = 0; - test_reset({ - x = 20; - }); - test_convey("Add one to both y and x", { - x++; - y++; - test_so(x == 1); /* no reset yet */ - test_so(y == 1); - }); - test_convey("Again", { - x++; - y++; - test_so(x == 21); - test_so(y == 2); - }); - test_convey("Third time", { - x++; - y++; - test_so(x == 21); - test_so(y == 3); - }); - - test_so(x == 20); - test_so(y == 3); - }); -}) diff --git a/tests/list.c b/tests/list.c index 79200931..8c3c6675 100644 --- a/tests/list.c +++ b/tests/list.c @@ -1,23 +1,10 @@ /* * Copyright 2016 Garrett D'Amore <garrett@damore.org> * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom - * the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. + * This software is supplied under the terms of the MIT License, a + * copy of which should be located in the distribution where this + * file was obtained (LICENSE.txt). A copy of the license may also be + * found online at https://opensource.org/licenses/MIT. */ #include "core/list.c" @@ -102,4 +89,4 @@ TestMain("Linked Lists", { }); }); }); -})
\ No newline at end of file +}) diff --git a/tests/test.c b/tests/test.c deleted file mode 100644 index 26c4eb8c..00000000 --- a/tests/test.c +++ /dev/null @@ -1,850 +0,0 @@ -/* - * Copyright 2016 Garrett D'Amore <garrett@damore.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom - * the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -/* - * This contains some of the guts of the testing framework. It is in a single - * file in order to simplify use and minimize external dependencies. - * - * If you use this with threads, you need to either have pthreads (and link - * your test program against the threading library), or you need Windows. - * Support for C11 threading is not implemented yet. - * - * For timing, this code needs a decent timer. It will use clock_gettime - * if it appears to be present, or the Win32 QueryPerformanceCounter, or - * gettimeofday() if neither of those are available. - * - * This code is unlikely to function at all on anything that isn't a UNIX - * or Windows system. As we think its unlikely that you'd want to use this - * to run testing inside an embedded device or something, we think this is a - * reasonable limitation. - * - * Note that we expect that on Windows, you have a reasonably current - * version of MSVC. (Specifically we need a few C99-isms that Microsoft - * only added late -- like in 2010. Specifically uint32_t and uint64_t). - */ - -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> -#include <stdint.h> -#include <setjmp.h> -#include <stdarg.h> -#include <string.h> - -#ifdef _WIN32 -#include <windows.h> - -#else - -#include <time.h> -#include <locale.h> -#include <langinfo.h> -#include <unistd.h> - -#ifndef NO_THREADS -#include <pthread.h> -#endif - -#endif - -#include "test.h" - -static const char *sym_pass = "."; -static const char *sym_skip = "?"; -static const char *sym_fail = "X"; -static const char *sym_fatal = "!"; -static const char *color_none = ""; -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 = ""; - -#define TEXIT_OK 0 -#define TEXIT_USAGE 1 -#define TEXIT_FAIL 2 -#define TEXIT_FATAL 3 -#define TEXIT_NOMEM 4 - -typedef struct tperfcnt { - uint64_t pc_base; - uint64_t pc_count; - uint64_t pc_rate; - int pc_running; -} tperfcnt_t; - -typedef struct tlog { - char *l_buf; - size_t l_size; - size_t l_length; -} tlog_t; - -typedef struct tctx { - char t_name[256]; - struct tctx *t_parent; - struct tctx *t_root; /* the root node on the list */ - struct tctx *t_next; /* root list only, for cleanup */ - int t_level; - int t_done; - int t_started; - jmp_buf *t_jmp; - int t_fatal; - int t_fail; - int t_skip; - int t_printed; - tperfcnt_t t_perfcnt; - tlog_t *t_fatallog; - tlog_t *t_faillog; - tlog_t *t_debuglog; -} tctx_t; - -#define PARENT(t) ((t_ctx_t *)(t->t_parent->t_data)) - -/* - * 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 *); -static void log_free(tlog_t *); -static tlog_t *log_alloc(void); - -/* - * print_result 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. - * - * We also leverage this point to detect completion of a root context, and - * deallocate the child contexts. The root context should never be reentered - * here. - */ -static void -print_result(tctx_t *t) -{ - int secs, usecs; - - if (t->t_root == t) { - stop_perfcnt(&t->t_perfcnt); /* This is idempotent */ - - 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("\n\n%s%d assertions thus far%s", - color_asserts, nasserts, color_none); - if (nskips) { - (void) printf(" %s%s%s", - color_yellow, - "(one or more sections skipped)", - color_none); - } - (void) printf("\n\n--- %s: %s (%d.%02d)\n", - t->t_fatal ? "FATAL" : - t->t_fail ? "FAIL" : - "PASS", t->t_name, secs, usecs / 10000); - } - - /* Remove the context, because we cannot reenter here */ - set_specific(NULL); - - while (t != NULL) { - tctx_t *freeit = t; - if (t->t_root == t) { - log_free(t->t_debuglog); - log_free(t->t_faillog); - log_free(t->t_fatallog); - } - t = t->t_next; - memset(freeit, 0, sizeof (*freeit)); - free(freeit); - } - } -} - -/* - * 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_i_start(test_ctx_t *ctx, const char *name) -{ - tctx_t *t, *parent; - - parent = get_ctx(); - - if ((t = ctx->T_data) != NULL) { - if (t->t_done) { - print_result(t); - return (1); /* all done, skip */ - } - return (0); /* continue onward */ - } - ctx->T_data = (t = calloc(1, sizeof (tctx_t))); - if (t == NULL) { - goto allocfail; - } - t->t_jmp = &ctx->T_jmp; - - (void) snprintf(t->t_name, sizeof(t->t_name)-1, "%s", name); - if (parent != NULL) { - t->t_parent = parent; - t->t_root = t->t_parent->t_root; - t->t_level = t->t_parent->t_level + 1; - /* unified logging against the root context */ - t->t_debuglog = t->t_root->t_debuglog; - t->t_faillog = t->t_root->t_faillog; - t->t_fatallog = t->t_root->t_fatallog; - t->t_next = t->t_root->t_next; - t->t_root->t_next = t; - } else { - t->t_parent = t; - t->t_root = t; - if (((t->t_fatallog = log_alloc()) == NULL) || - ((t->t_faillog = log_alloc()) == NULL) || - ((t->t_debuglog = log_alloc()) == NULL)) { - goto allocfail; - } - } - return (0); -allocfail: - if (t != NULL) { - log_free(t->t_fatallog); - log_free(t->t_debuglog); - log_free(t->t_faillog); - free(t); - ctx->T_data = NULL; - return (1); - } - if (parent != NULL) { - test_fatal("Unable to allocate context"); - } - return (1); -} - -/* - * This is called right after setjmp. The jumped being true indicates - * 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_i_loop(test_ctx_t *ctx, int unwind) -{ - tctx_t *t; - int i; - if ((t = ctx->T_data) == NULL) { - return (1); - } - if (unwind) { - if ((t->t_parent != t) && (t->t_parent != NULL)) { - longjmp(*t->t_parent->t_jmp, 1); - } - if (t->t_done) { - print_result(t); - return (1); - } - } - if (!t->t_started) { - t->t_started = 1; - - if (verbose) { - if (t->t_root == t) { - (void) printf("=== RUN: %s\n", t->t_name); - } else { - (void) printf("\n"); - for (i = 0; i < t->t_level; i++) { - (void) printf(" "); - } - (void) printf("%s ", t->t_name); - (void) fflush(stdout); - } - } - - init_perfcnt(&t->t_perfcnt); - start_perfcnt(&t->t_perfcnt); - } - /* Reset TC for the following code. */ - set_specific(t); - return (0); -} - -void -test_i_finish(test_ctx_t *ctx, int *rvp) -{ - tctx_t *t; - if ((t = ctx->T_data) == NULL) { - /* allocation failure */ - *rvp = 4; - return; - } - t->t_done = 1; - if (rvp != NULL) { - /* exit code 1 is reserved for usage errors */ - if (t->t_fatal) { - *rvp = 3; - } else if (t->t_fail) { - *rvp = 2; - } else { - *rvp = 0; - } - } - longjmp(*t->t_jmp, 1); -} - -void -test_i_skip(const char *file, int line, const char *reason) -{ - tctx_t *t = get_ctx(); - if (verbose) { - (void) printf("%s%s%s", color_yellow, sym_skip, color_none); - } - log_printf(t->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_i_assert_fail(const char *cond, const char *file, int line) -{ - tctx_t *t = get_ctx(); - nasserts++; - if (verbose) { - (void) printf("%s%s%s", color_yellow, sym_fail, color_none); - } - if (t->t_root != t) { - t->t_root->t_fail++; - } - color_asserts = color_yellow; - t->t_fail++; - t->t_done = 1; /* This forces an end */ - log_printf(t->t_faillog, "* %s (Assertion Failed)\n", t->t_name); - log_printf(t->t_faillog, "File: %s\n", file); - log_printf(t->t_faillog, "Line: %d\n", line); - log_printf(t->t_faillog, "Test: %s\n\n", cond); - log_printf(t->t_debuglog, "* %s (%s:%d) (FAILED)\n", - t->t_name, file, line); - longjmp(*t->t_jmp, 1); -} - -void -test_i_assert_pass(const char *cond, const char *file, int line) -{ - tctx_t *t = get_ctx(); - nasserts++; - if (verbose) { - (void) printf("%s%s%s", color_green, sym_pass, color_none); - } - log_printf(t->t_debuglog, "* %s (%s:%d) (Passed)\n", - t->t_name, file, line); -} - -void -test_i_assert_skip(const char *cond, const char *file, int line) -{ - tctx_t *t = get_ctx(); - nskips++; - if (verbose) { - (void) printf("%s%s%s", color_yellow, sym_pass, color_none); - } - log_printf(t->t_debuglog, "* %s (%s:%d) (SKIPPED)\n", - t->t_name, file, line); -} - -/* - * Performance counters. Really we just want to start and stop timers, to - * measure elapsed time in usec. - */ - -static void -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_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_base = ts.tv_sec * 1000000000; - pc->pc_base += ts.tv_nsec; - pc->pc_rate = 1000000000; -#else - struct timeval tv; - - gettimeofday(&tv, NULL); - pc->pc_base = tv.tv_secs * 1000000; - pc->pc_base += tv.tv_usec; - pc->pc_rate = 1000000; -#endif - pc->pc_running = 1; -} - -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 = pc->pc_count; - rate = pc->pc_rate; - - sec = delta / rate; - delta -= (sec * rate); - - /* - * done this way we avoid dividing rate by 1M -- and the above - * ensures we don't wrap. - */ - usec = (delta * 1000000) / rate; - - if (secp) { - *secp = (int)sec; - } - if (usecp) { - *usecp = (int)usec; - } -} - -/* - * Thread-specific data. Pthreads uses one way, Win32 another. If you - * lack threads, just #define NO_THREADS. C11 thread support is pending. - */ - -#ifdef NO_THREADS -static void *specific_val; - -static int -init_specific(void) -{ - return (0); -} - -static int -set_specific(void *v) -{ - specific_val = v; - return (0); -} - -static void * -get_specific(void) -{ - return (specific_val); -} -#elif defined(_WIN32) - -static DWORD keyctx; - -static int -init_specific(void) -{ - if ((keyctx = TlsAlloc()) == TLS_OUT_OF_INDEXES) { - return (-1); - } - return (0); -} - -static int -set_specific(void *v) -{ - if (!TlsSetValue(keyctx, v)) { - return (-1); - } - return (0); -} - -static void * -get_specific(void) -{ - return ((void *)TlsGetValue(keyctx)); -} - -#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 - -static tctx_t * -get_ctx(void) -{ - return (get_specific()); -} - -/* - * 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); - log_vprintf(log, fmt, va); - va_end(va); -} - -static void -log_dump(tlog_t *log, const char *header, const char *color) -{ - 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; - } - - (void) 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")) { - (void) printf(" %s%s%s\n", color, s, color_none); - } -} - -static void -log_free(tlog_t *log) -{ - if (log != NULL) { - if (log->l_size != 0) { - free(log->l_buf); - } - free(log); - } -} - -static tlog_t * -log_alloc(void) -{ - return (calloc(1, sizeof (tlog_t))); -} - -/* - * test_init initializes some common global stuff. Call it from main(), - * if you don't use the framework provided main. - */ -int -test_init(void) -{ - static int inited; - - if (!inited) { - if (init_specific() != 0) { - return (-1); - } - init_terminal(); - inited = 1; - } - return (0); -} - -void -test_set_verbose(void) -{ - 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_faillog; - tlog_t *debuglog = t->t_debuglog; - - 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++; - } - color_asserts = color_yellow; - t->t_fail++; - t->t_done = 1; /* This forces an end */ - longjmp(*t->t_jmp, 1); -} - -void -test_i_fatal(const char *file, int line, const char *reason) -{ - tctx_t *t = get_ctx()->t_root; - tlog_t *faillog = t->t_fatallog; - tlog_t *debuglog = t->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++; - } - color_asserts = color_red; - t->t_fail++; - t->t_done = 1; /* This forces an end */ - longjmp(*t->t_jmp, 1); -} - -extern int test_main_impl(void); - -static void -init_terminal(void) -{ -#ifndef _WIN32 - /* Windows console doesn't do Unicode (consistently). */ - const char *codeset; - const char *term; - - (void) setlocale(LC_ALL, ""); - codeset = nl_langinfo(CODESET); - if ((codeset != NULL) && (strcmp(codeset, "UTF-8") == 0)) { - sym_pass = "✔"; - sym_fail = "✘"; - sym_fatal = "🔥"; - sym_skip = "⚠"; - } - - term = getenv("TERM"); - if (isatty(1) && (term != NULL)) { - if ((strstr(term, "xterm") != NULL) || - (strstr(term, "ansi") != NULL) || - (strstr(term, "color") != NULL)) { - color_none = "\e[0m"; - color_green = "\e[32m"; - color_yellow = "\e[33m"; - color_red = "\e[31m"; - color_asserts = color_green; - } - } -#endif -} - -int -test_i_main(int argc, char **argv) -{ - int i; - const char *status; - const char *prog; - tperfcnt_t pc; - int secs, usecs; - - if ((argc > 0) && (argv[0] != NULL)) { - prog = argv[0]; - } else { - prog = "<unknown>"; - } - - /* - * 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; - } - if (strcmp(argv[i], "-v") == 0) { - verbose = 1; - } - if (strcmp(argv[i], "-d") == 0) { - debug++; - } - } - if (test_init() != 0) { - (void) fprintf(stderr, "Cannot initialize test framework\n"); - exit(TEXIT_NOMEM); - } - - init_perfcnt(&pc); - start_perfcnt(&pc); - i = test_main_impl(); - stop_perfcnt(&pc); - - switch (i) { - case TEXIT_NOMEM: - (void) fprintf(stderr, "Cannot initialize root test context\n"); - exit(TEXIT_NOMEM); - case TEXIT_OK: - if (verbose) { - (void) printf("PASS\n"); - } - status = "ok"; - break; - case TEXIT_FAIL: - status = "FAIL"; - if (verbose) { - (void) printf("FAIL\n"); - } - break; - default: - status = "FATAL"; - if (verbose) { - (void) printf("FATAL\n"); - } - break; - } - - read_perfcnt(&pc, &secs, &usecs); - (void) printf("%-8s%-52s%4d.%03ds\n", status, prog, secs, usecs / 1000); - exit(i); -}
\ No newline at end of file diff --git a/tests/test.h b/tests/test.h deleted file mode 100644 index 06cd1e29..00000000 --- a/tests/test.h +++ /dev/null @@ -1,270 +0,0 @@ - -/* - * Copyright 2016 Garrett D'Amore <garrett@damore.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom - * the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef TESTS_TEST_H - -#define TESTS_TEST_H - -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> -#include <setjmp.h> -#include <stdarg.h> -#include <sys/time.h> - -/* - * This test framework allows one to write tests as a form of assertion, - * giving simpler and more readable test logic. - * - * The test framework provides a main() function. - * - * To use this call the test_main() macro, and embed test_convey() references. - * These can be nested, and after each convey the entire stack is popped so - * that execution can continue from the beginning, giving each test section - * the same environment. - * - * There are assertion macros too, which don't roll back the stack, but which - * do update the test state. - * - * Here's a sample file: - * - * test_main("Integer Tests", { - * int x = 1; int y = 2; - * test_convey("Addition works", func() { - * test_so(y == 2); - * test_so(y + x == 3); - * test_so(x + y == 3); - * test_convey("Even big numbers", func() { - * y = 100; - * test_so(x + y == 101); - * }); - * test_convey("Notice y is still 2 in this context", func() { - * test_so(y == 2); - * }); - * }); - * }) - * - * There are other macros, but this is a work in progress. The inspiration - * for this is from GoConvey -- github.com/smartystreets/goconvey - but this - * is a version for C programs. - */ - -/* - * This structure has to be exposed in order to expose the buffer used for - * setjmp. It's members should never be accessed directly. These should be - * allocated statically in the routine(s) that need custom contexts. The - * framework creates a context automatically for each convey scope. - */ -typedef struct test_ctx { - jmp_buf T_jmp; - void *T_data; -} test_ctx_t; - -/* These functions are not for use by tests -- they are used internally. */ -extern int test_i_start(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_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_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_i_run(T_name, T_code, T_rvp) \ - do { \ - static test_ctx_t T_ctx; \ - int T_unwind; \ - int T_break = 0; \ - if (test_i_start(&T_ctx, T_name) != 0) { \ - break; \ - } \ - T_unwind = setjmp(T_ctx.T_jmp); \ - if (test_i_loop(&T_ctx, T_unwind) != 0) { \ - break; \ - } \ - do { \ - T_code \ - } while (0); \ - if (T_break) { \ - break; \ - } \ - test_i_finish(&T_ctx, T_rvp); \ - } while (0) - -/* - * If you want multiple top-level tests in your test suite, the test - * code should create a test_main_group(), with multiple calls to - * test_group() in the intervening section. This will cause a new main - * to be emitted that runs all the main groups. - */ -#define test_main_group(T_code) \ - static int test_main_rv; \ - int test_main_impl(void) { \ - do { \ - T_code \ - } while (0); \ - return (test_main_rv); \ - } \ - int main(int argc, char **argv) { \ - return (test_i_main(argc, argv)); \ - } - -#define test_group(T_name, T_code) \ - do { \ - int T_rv; \ - test_i_run(T_name, T_code, &T_rv); \ - if (T_rv > test_main_rv) { \ - test_main_rv = T_rv; \ - }; \ - } while (0) - -/* - * test_main is used to wrap the top-level of your test suite, and is - * used in lieu of a normal main() function. This is the usual case where - * the executable only contains a single top level test group. - */ -#define test_main(T_name, T_code) \ - test_main_group({ \ - test_group(T_name, T_code); \ - }) - - -/* - * If you don't want to use the test framework's main routine, but - * prefer (or need, because of threading for example) to have your - * test code driven separately, you can use inject test_block() in - * your function. It works like test_main(). These must not be - * nested within test_main, test_main_group, or test_block itself: - * results are undefined if you try that. The final T_rvp pointer may - * be NULL, or is a pointer to an integer to receive the an integer - * result from the test. (0 is success, 4 indicates a failure to allocate - * memory in the test framework, and anything else indicates a - * an error or failure in the code being tested. - */ -#define test_block(T_name, T_code, T_rvp) \ - test_i_run(T_name, T_code, T_rvp) - -/* - * test_assert and test_so allow you to run assertions. - */ -#define test_assert(T_cond) \ - do { \ - if (!(T_cond)) { \ - test_i_assert_fail(#T_cond, __FILE__, __LINE__);\ - } else { \ - test_i_assert_pass(#T_cond, __FILE__, __LINE__);\ - } \ - } while (0) - -#define test_so(T_cond) test_assert(T_cond) - -/* - * test_convey(name, <code>) starts a convey context, with <code> as - * the body. The <code> is its scope, and may be called repeatedly - * within the body of a loop. - */ -#define test_convey(T_name, T_code) test_i_run(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(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_so() is used to skip processing of a single assertion. - * Further processing in the same context continues. - */ -#define test_skip_so(T_cnd) \ - test_i_assert_skip(T_cnd, __FILE__, __LINE__) - -/* - * 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(T_name, T_code) \ - test_convey(T_name, test_skip("Skipped")) - -/* - * test_reset establishes a reset for the current block. This code will - * be executed every time the current block is unwinding. This means that - * the code will be executed each time a child convey exits. It is also - * going to be executed once more, for the final pass, which doesn't actually - * execute any convey blocks. (This final pass is required in order to - * learn that all convey's, as well as any code beyond them, are complete.) - * - * The way this works is by overriding the existing block's jump buffer. - * - * Unlike with GoConvey, this must be registered before any children - * convey blocks; the logic only affects convey blocks that follow this - * one, within the same scope. - * - * It is possible to have a subsequent reset at the same convey scope - * override a prior reset. Normally you should avoid this, and just - * use lower level convey blocks. - */ -#define test_reset(T_reset_code) \ - T_unwind = setjmp(T_ctx.T_jmp); \ - if (T_unwind) { \ - do { \ - T_reset_code \ - } while (0); \ - } \ - if (test_i_loop(&T_ctx, T_unwind) != 0) { \ - T_break = 1; \ - break; \ - } - - -/* - * 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 */ |
