doompanning/tests/convey.c
oxmox 7063e2102e Squashed 'external/nng/' content from commit 29b73962
git-subtree-dir: external/nng
git-subtree-split: 29b73962b939a6fbbf6ea8d5d7680bb06d0eeb99
2024-12-18 18:29:29 +01:00

1081 lines
25 KiB
C

/*
* Copyright 2018 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 <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#if NNG_HAVE_LANGINFO
#include <langinfo.h>
#include <locale.h>
#endif
#include <sys/time.h>
#include <time.h>
#include <unistd.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_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 = "";
#if defined(_WIN32)
static WORD convey_defattr;
static HANDLE convey_console;
#endif
#define CONVEY_EXIT_OK 0
#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 **);
static void convey_emit_color(const char *);
/*
* convey_emit_color just changes the output text to the color
* requested. It is Windows console aware.
*/
static void
convey_emit_color(const char *color)
{
#if defined(_WIN32)
if (convey_console != INVALID_HANDLE_VALUE) {
WORD attr;
attr = convey_defattr &
~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED |
FOREGROUND_INTENSITY);
if (color == convey_nocolor) {
attr = convey_defattr;
} else if (color == convey_yellow) {
attr |= FOREGROUND_GREEN | FOREGROUND_RED |
FOREGROUND_INTENSITY;
} else if (color == convey_green) {
attr |= FOREGROUND_GREEN | FOREGROUND_INTENSITY;
} else if (color == convey_red) {
attr |= FOREGROUND_RED | FOREGROUND_INTENSITY;
} else {
return;
}
(void) fflush(stdout);
SetConsoleTextAttribute(convey_console, attr);
} else {
(void) fputs(color, stdout);
}
#else
(void) fputs(color, stdout);
#endif
}
/*
* 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) puts("");
}
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) puts("");
(void) puts("");
convey_emit_color(convey_assert_color);
(void) printf(
"%d assertions thus far", convey_nassert);
convey_emit_color(convey_nocolor);
if (convey_nskip) {
(void) fputs(" ", stdout);
convey_emit_color(convey_yellow);
(void) fputs(
"(one or more sections skipped)", stdout);
convey_emit_color(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. If unwind is true it indicates
* that setjmp returned true, and we are unwinding 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) puts("");
for (i = 0; i < t->ctx_level; i++) {
(void) fputs(" ", stdout);
}
(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) {
convey_emit_color(convey_yellow);
(void) fputs(convey_sym_skip, stdout);
convey_emit_color(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) {
convey_emit_color(convey_yellow);
(void) fputs(convey_sym_fail, stdout);
convey_emit_color(convey_nocolor);
(void) fflush(stdout);
}
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, "* Assertion Failed (%s)\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) {
convey_emit_color(convey_green);
(void) fputs(convey_sym_pass, stdout);
convey_emit_color(convey_nocolor);
(void) fflush(stdout);
}
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) {
convey_emit_color(convey_yellow);
(void) fputs(convey_sym_pass, stdout);
convey_emit_color(convey_nocolor);
(void) fflush(stdout);
}
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)
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
pc->timer_base = ts.tv_sec;
pc->timer_base *= 1000000000;
pc->timer_base += ts.tv_nsec;
pc->timer_rate = 1000000000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
pc->timer_base = tv.tv_sec;
pc->timer_base *= 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;
ns *= 1000000000;
ns += (uint64_t) ts.tv_nsec;
pc->timer_count += (ns - pc->timer_base);
#else
uint64_t us;
struct timeval tv;
gettimeofday(&tv, NULL);
us = tv.tv_sec;
us *= 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
static 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) {
size_t 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) fputs("\n\n", stdout);
convey_emit_color(color);
(void) fputs(header, stdout);
convey_emit_color(convey_nocolor);
(void) fputs("\n\n", stdout);
while ((s = convey_nextline(&last)) != NULL) {
(void) fputs(" ", stdout);
convey_emit_color(color);
(void) fputs(s, stdout);
convey_emit_color(convey_nocolor);
(void) fputs("\n", stdout);
}
}
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);
}
static void
convey_init_term(void)
{
const char *term;
#ifndef _WIN32
/* Windows console doesn't do Unicode (consistently). */
#if NNG_HAVE_LANGINFO
const char *codeset;
(void) setlocale(LC_ALL, "");
codeset = nl_langinfo(CODESET);
if ((codeset != NULL) && (strcmp(codeset, "UTF-8") == 0)) {
convey_sym_pass = "";
convey_sym_fail = "";
convey_sym_skip = "";
}
#endif
term = getenv("TERM");
if (!isatty(fileno(stdin))) {
term = NULL;
}
#else
CONSOLE_SCREEN_BUFFER_INFO info;
convey_console = GetStdHandle(STD_OUTPUT_HANDLE);
if (!GetConsoleScreenBufferInfo(convey_console, &info)) {
convey_console = INVALID_HANDLE_VALUE;
} else {
convey_defattr = info.wAttributes;
// Values probably don't matter, just need to be
// different!
convey_nocolor = "\033[0m";
convey_green = "\033[32m";
convey_yellow = "\033[33m";
convey_red = "\033[31m";
}
term = getenv("TERM");
#endif
if (term != NULL) {
if ((strstr(term, "xterm") != NULL) ||
(strstr(term, "ansi") != NULL) ||
(strstr(term, "color") != NULL)) {
convey_nocolor = "\033[0m";
convey_green = "\033[32m";
convey_yellow = "\033[33m";
convey_red = "\033[31m";
}
}
convey_assert_color = convey_green;
}
/*
* 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);
}
static struct convey_env {
struct convey_env *next;
const char * name;
char * value;
} * convey_environment;
static struct convey_env *
conveyFindEnv(const char *name)
{
struct convey_env *ev;
for (ev = convey_environment; ev != NULL; ev = ev->next) {
if (strcmp(name, ev->name) == 0) {
return (ev);
}
}
return (NULL);
}
char *
conveyGetEnv(const char *name)
{
struct convey_env *ev;
if ((ev = conveyFindEnv(name)) != NULL) {
return (ev->value);
}
return (getenv(name));
}
int
conveyPutEnv(const char *name, char *value)
{
struct convey_env *env;
if ((env = conveyFindEnv(name)) == NULL) {
env = malloc(sizeof(*env));
if (env == NULL) {
return (-1);
}
env->next = convey_environment;
convey_environment = env;
}
env->name = name;
env->value = value;
return (0);
}
int
conveyMain(int argc, char **argv)
{
int i;
const char * status;
const char * prog = "<unknown>";
struct convey_timer pc;
int secs, usecs;
struct convey_env * env;
if ((argc > 0) && (argv[0] != NULL)) {
prog = argv[0];
}
/*
* 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();
continue;
}
if (strcmp(argv[i], "-d") == 0) {
convey_debug++;
continue;
}
if ((strcmp(argv[i], "-p") == 0) && ((i + 1) < argc)) {
char *delim;
if ((delim = strchr(argv[i + 1], '=')) != NULL) {
*delim = '\0';
conveyPutEnv(argv[i + 1], delim + 1);
} else {
conveyPutEnv(argv[i + 1], "");
}
i++;
continue;
}
}
if (ConveyInit() != 0) {
(void) fputs("Cannot initialize test framework\n", stderr);
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) fputs("Cannot initialize root test context\n", stderr);
exit(CONVEY_EXIT_NOMEM);
case CONVEY_EXIT_OK:
if (convey_verbose) {
(void) puts("PASS");
}
status = "ok";
break;
case CONVEY_EXIT_FAIL:
status = "FAIL";
if (convey_verbose) {
(void) puts("FAIL");
}
break;
default:
status = "FATAL";
if (convey_verbose) {
(void) puts("FATAL");
}
break;
}
convey_read_timer(&pc, &secs, &usecs);
(void) printf(
"%-8s%-52s%4d.%03ds\n", status, prog, secs, usecs / 1000);
while ((env = convey_environment) != NULL) {
convey_environment = env->next;
free(env);
}
convey_read_timer(&pc, &secs, &usecs);
exit(i);
}