aboutsummaryrefslogtreecommitdiff
path: root/tests/convey.c
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2016-12-20 20:59:33 -0800
committerGarrett D'Amore <garrett@damore.org>2016-12-20 20:59:33 -0800
commit529c84d6a1bf2400170263c9e68d9433a70cc43d (patch)
treeee97e857548a3cfe8dc4c7e2b0a179c14f9fb69c /tests/convey.c
parent09c631a793e46a1acc5848592f246fbb2b6c6f4e (diff)
downloadnng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.gz
nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.bz2
nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.zip
Updates to reflect new external convey framework.
Diffstat (limited to 'tests/convey.c')
-rw-r--r--tests/convey.c917
1 files changed, 917 insertions, 0 deletions
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);
+}