winfsp/ext/tlib/testsuite.c
2024-01-08 12:38:44 +00:00

250 lines
7.3 KiB
C

/**
* @file tlib/testsuite.c
*
* @copyright 2014-2024 Bill Zissimopoulos
*/
#include <tlib/testsuite.h>
#include <limits.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct test
{
char name[64];
void (*fn)(void);
int optional;
struct test *next;
};
static struct test test_suite_sentinel = { .next = &test_suite_sentinel };
static struct test *test_suite_tail = &test_suite_sentinel;
static struct test test_sentinel = { .next = &test_sentinel };
static struct test *test_tail = &test_sentinel;
static void add_test_to_list(const char *name, void (*fn)(void), int optional, struct test **tail)
{
struct test *test = calloc(1, sizeof *test);
strncpy(test->name, name, sizeof test->name - 1);
test->name[sizeof test->name - 1] = '\0';
test->fn = fn;
test->optional = optional;
test->next = (*tail)->next;
(*tail)->next = test;
(*tail) = test;
}
void tlib_add_test_suite(const char *name, void (*fn)(void))
{
add_test_to_list(name, fn, 0, &test_suite_tail);
}
void tlib_add_test(const char *name, void (*fn)(void))
{
add_test_to_list(name, fn, 0, &test_tail);
}
void tlib_add_test_opt(const char *name, void (*fn)(void))
{
add_test_to_list(name, fn, 1, &test_tail);
}
struct hook
{
void (*fn)(const char *name, void (*fn)(void), int v);
struct hook *next;
};
static struct hook hook_sentinel = { .next = &hook_sentinel };
static struct hook *hook_tail = &hook_sentinel;
static void add_hook_to_list(void (*fn)(void), struct hook **tail)
{
struct hook *hook = calloc(1, sizeof *hook);
hook->fn = fn;
hook->next = (*tail)->next;
(*tail)->next = hook;
(*tail) = hook;
}
void tlib_add_hook(void (*fn)(const char *name, void (*fn)(void), int v))
{
add_hook_to_list(fn, &hook_tail);
}
static FILE *tlib_out, *tlib_err;
static jmp_buf test_jmp_buf, *test_jmp;
static char assert_buf[256];
static void test_printf(const char *fmt, ...);
static double run_test(struct test *test)
{
double res;
for (struct hook *hook = hook_tail->next->next; 0 != hook->fn; hook = hook->next)
hook->fn(test->name, test->fn, +1);
#if defined(_WIN64) || defined(_WIN32)
#pragma comment(lib, "winmm.lib")
unsigned long __stdcall timeGetTime(void);
unsigned long t0 = timeGetTime();
test->fn();
unsigned long t1 = timeGetTime();
res = (t1 - t0) / 1000.0;
#else
time_t t0 = time(0);
test->fn();
time_t t1 = time(0);
res = difftime(t1, t0);
#endif
for (struct hook *hook = hook_tail->next->next; 0 != hook->fn; hook = hook->next)
hook->fn(test->name, test->fn, -1);
return res;
}
static void do_test_default(struct test *test, int testno)
{
if (0 != test)
{
snprintf(assert_buf, sizeof assert_buf, "KO\n ");
char dispname[39 + 1];
size_t displen = strlen(test->name);
if (displen > sizeof dispname - 1)
displen = sizeof dispname - 1;
memcpy(dispname, test->name, displen);
memset(dispname + displen, '.', sizeof dispname - 1 - displen);
dispname[sizeof dispname - 1] = '\0';
test_printf("%s ", dispname);
double d = run_test(test);
test_printf("OK %.2fs\n", d);
}
else
test_printf("--- COMPLETE ---\n");
}
static void do_test_list(struct test *test, int testno)
{
if (0 != test)
test_printf("%s\n", test->name);
}
static void do_test_tap(struct test *test, int testno)
{
if (0 != test)
{
snprintf(assert_buf, sizeof assert_buf, "not ok %d %s\n# ", testno + 1, test->name);
run_test(test);
test_printf("ok %d %s\n", testno + 1, test->name);
}
else
test_printf("1..%d\n", testno);
}
static void test_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
FILE *f = tlib_out ? tlib_out : stdout;
vfprintf(f, fmt, ap);
fflush(f);
va_end(ap);
}
void tlib_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
FILE *f = tlib_err ? tlib_err : stderr;
vfprintf(f, fmt, ap);
fflush(f);
va_end(ap);
}
void tlib_run_tests(int argc, char *argv[])
{
argc--; argv++;
void (*do_test)(struct test *, int) = do_test_default;
int match_any = 1, no_abort = 0;
unsigned long repeat = 1;
for (char **ap = argv, **aendp = ap + argc; aendp > ap; ap++)
{
const char *a = *ap;
if ('-' == a[0])
{
if (0 == strcmp("--list", a))
do_test = do_test_list;
else if (0 == strcmp("--tap", a))
do_test = do_test_tap;
else if (0 == strcmp("--no-abort", a))
no_abort = 1;
else if (0 == strcmp("--repeat-forever", a))
repeat = ULONG_MAX;
else if ('-' == a[1])
{
fprintf(stderr, "tlib_run_tests: unknown option %s\n", a);
exit(2);
}
}
else
match_any = 0;
}
for (struct test *test = test_suite_tail->next->next; 0 != test->fn; test = test->next)
test->fn();
while (repeat--)
{
int testno = 0;
for (struct test *test = test_tail->next->next; 0 != test->fn; test = test->next)
{
int match_arg = match_any && !test->optional;
for (char **ap = argv, **aendp = ap + argc; aendp > ap; aendp--)
{
const char *a = aendp[-1];
int sign = a[0];
if ('+' == sign)
a++;
else if ('-' == sign)
{
if ('-' == a[1])
continue;
a++;
}
size_t l = strlen(a);
if (0 == (0 < l && '*' == a[l - 1] ?
strncmp(test->name, a, l - 1) : strcmp(test->name, a)))
{
if ('+' == sign)
match_arg = 1;
else if ('-' == sign)
match_arg = 0;
else
match_arg = !test->optional;
break;
}
}
if (!match_arg)
continue;
assert_buf[0] = '\0';
if (no_abort)
{
test_jmp = &test_jmp_buf;
if (0 == setjmp(*test_jmp))
do_test(test, testno);
test_jmp = 0;
}
else
do_test(test, testno);
testno++;
}
do_test(0, testno);
}
}
void tlib__assert(const char *func, const char *file, int line, const char *expr)
{
char extra_buf[256] = "";
#if defined(_WIN64) || defined(_WIN32)
long __stdcall RtlGetLastNtStatus(void);
unsigned long __stdcall RtlGetLastWin32Error(void);
snprintf(extra_buf, sizeof extra_buf,
" (err=%lx:%lu)",
RtlGetLastNtStatus(),
RtlGetLastWin32Error());
const char *p = strrchr(file, '\\');
#else
const char *p = strrchr(file, '/');
#endif
file = 0 != p ? p + 1 : file;
if (0 == func)
test_printf("%sASSERT(%s) failed at: %s:%d%s\n", assert_buf, expr, file, line, extra_buf);
else
test_printf("%sASSERT(%s) failed at %s:%d:%s%s\n", assert_buf, expr, file, line, func, extra_buf);
if (0 != test_jmp)
longjmp(*test_jmp, 1);
}