aboutsummaryrefslogtreecommitdiff
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
parent09c631a793e46a1acc5848592f246fbb2b6c6f4e (diff)
downloadnng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.gz
nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.bz2
nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.zip
Updates to reflect new external convey framework.
-rw-r--r--tests/CMakeLists.txt5
-rw-r--r--tests/README.adoc13
-rw-r--r--tests/convey.c917
-rw-r--r--tests/convey.h349
-rw-r--r--tests/demo.c94
-rw-r--r--tests/list.c23
-rw-r--r--tests/test.c850
-rw-r--r--tests/test.h270
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 */