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