mirror of
https://github.com/winfsp/winfsp.git
synced 2025-06-14 15:52:47 -05:00
add winfsp-tests and ext/tlib
This commit is contained in:
2
ext/tlib/Commit.txt
Normal file
2
ext/tlib/Commit.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Taked from secfs.core/src/tlib codebase:
|
||||
commit 16296d2ac64a7d532b1c486dd3c44af5865d4851
|
75
ext/tlib/callstack.c
Normal file
75
ext/tlib/callstack.c
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file tlib/callstack.c
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#include <tlib/callstack.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
#elif defined(__APPLE__) || defined(__linux__)
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#else
|
||||
#error tlib/callstack.c not implemented for this platform
|
||||
#endif
|
||||
|
||||
void tlib_callstack(size_t skip, size_t count, struct tlib_callstack_s *stack)
|
||||
{
|
||||
if (count > TLIB_MAX_SYMRET)
|
||||
count = TLIB_MAX_SYMRET;
|
||||
size_t naddrs = skip + count;
|
||||
if (naddrs > TLIB_MAX_SYMCAP)
|
||||
naddrs = TLIB_MAX_SYMCAP;
|
||||
memset((void *)stack->syms, 0, sizeof stack->syms);
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* SymInitialize()/SymFromAddr() are not thread-safe. Furthermore SymInitialize() should
|
||||
* not be called more than once per process.
|
||||
*/
|
||||
HANDLE hproc = GetCurrentProcess();
|
||||
static SYMBOL_INFO *syminfo = 0;
|
||||
void *addrs[TLIB_MAX_SYMCAP];
|
||||
size_t i = 0;
|
||||
if (0 == syminfo)
|
||||
{
|
||||
syminfo = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO) + TLIB_MAX_SYMLEN);
|
||||
if (0 == syminfo)
|
||||
return;
|
||||
SymInitialize(hproc, 0, TRUE);
|
||||
}
|
||||
memset(syminfo, 0, sizeof(SYMBOL_INFO));
|
||||
syminfo->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
syminfo->MaxNameLen = TLIB_MAX_SYMLEN;
|
||||
naddrs = CaptureStackBackTrace(0, naddrs, addrs, 0);
|
||||
for (void **p = addrs + skip, **endp = addrs + naddrs; endp > p; p++)
|
||||
{
|
||||
DWORD64 displacement = 0;
|
||||
if (SymFromAddr(hproc, (DWORD64)*p, &displacement, syminfo))
|
||||
{
|
||||
stack->syms[i] = stack->symbuf[i];
|
||||
strncpy(stack->symbuf[i], syminfo->Name, TLIB_MAX_SYMLEN);
|
||||
stack->symbuf[i][TLIB_MAX_SYMLEN] = '\0';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__) || defined(__linux__)
|
||||
void *addrs[TLIB_MAX_SYMCAP];
|
||||
naddrs = backtrace(addrs, naddrs);
|
||||
size_t i = 0;
|
||||
Dl_info syminfo;
|
||||
for (void **p = addrs + skip, **endp = addrs + naddrs; endp > p; p++)
|
||||
{
|
||||
if (dladdr(*p, &syminfo) && 0 != syminfo.dli_sname)
|
||||
{
|
||||
stack->syms[i] = stack->symbuf[i];
|
||||
strncpy(stack->symbuf[i], syminfo.dli_sname, TLIB_MAX_SYMLEN);
|
||||
stack->symbuf[i][TLIB_MAX_SYMLEN] = '\0';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
26
ext/tlib/callstack.h
Normal file
26
ext/tlib/callstack.h
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @file tlib/callstack.h
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#ifndef TLIB_CALLSTACK_H_INCLUDED
|
||||
#define TLIB_CALLSTACK_H_INCLUDED
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
enum
|
||||
{
|
||||
TLIB_MAX_SYMLEN = 63,
|
||||
TLIB_MAX_SYMRET = 8,
|
||||
TLIB_MAX_SYMCAP = 62, /* max number of frames to capture (Windows restriction) */
|
||||
};
|
||||
|
||||
struct tlib_callstack_s
|
||||
{
|
||||
const char *syms[TLIB_MAX_SYMRET + 1];
|
||||
char symbuf[TLIB_MAX_SYMRET][TLIB_MAX_SYMLEN + 1];
|
||||
};
|
||||
void tlib_callstack(size_t skip, size_t count, struct tlib_callstack_s *stack);
|
||||
|
||||
#endif
|
13
ext/tlib/injected/allfunc.h
Normal file
13
ext/tlib/injected/allfunc.h
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @file tlib/injected/allfunc.h
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#ifndef TLIB_INJECTED_ALLFUNC_H_INCLUDED
|
||||
#define TLIB_INJECTED_ALLFUNC_H_INCLUDED
|
||||
|
||||
#include <tlib/injected/curlfunc.h>
|
||||
#include <tlib/injected/stdfunc.h>
|
||||
|
||||
#endif
|
35
ext/tlib/injected/curlfunc.c
Normal file
35
ext/tlib/injected/curlfunc.c
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file tlib/injected/curlfunc.c
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#include <tlib/injected/curlfunc.h>
|
||||
#define TLIB_INJECTIONS_ENABLED
|
||||
#include <tlib/injection.h>
|
||||
|
||||
#undef curl_easy_init
|
||||
#undef curl_multi_init
|
||||
#undef curl_multi_add_handle
|
||||
#undef curl_multi_perform
|
||||
|
||||
CURL *tlib_curl_easy_init(void)
|
||||
{
|
||||
TLIB_INJECT("curl_easy_init", return 0);
|
||||
return curl_easy_init();
|
||||
}
|
||||
CURLM *tlib_curl_multi_init(void)
|
||||
{
|
||||
TLIB_INJECT("curl_multi_init", return 0);
|
||||
return curl_multi_init();
|
||||
}
|
||||
CURLMcode tlib_curl_multi_add_handle(CURLM *mh, CURL *eh)
|
||||
{
|
||||
TLIB_INJECT("curl_multi_add_handle", return CURLM_INTERNAL_ERROR);
|
||||
return curl_multi_add_handle(mh, eh);
|
||||
}
|
||||
CURLMcode tlib_curl_multi_perform(CURLM *mh, int *running_handles)
|
||||
{
|
||||
TLIB_INJECT("curl_multi_perform", return CURLM_INTERNAL_ERROR);
|
||||
return curl_multi_perform(mh, running_handles);
|
||||
}
|
24
ext/tlib/injected/curlfunc.h
Normal file
24
ext/tlib/injected/curlfunc.h
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file tlib/injected/curlfunc.h
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#ifndef TLIB_INJECTED_CURLFUNC_H_INCLUDED
|
||||
#define TLIB_INJECTED_CURLFUNC_H_INCLUDED
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#if defined(TLIB_INJECTIONS_ENABLED)
|
||||
#define curl_easy_init() tlib_curl_easy_init()
|
||||
#define curl_multi_init() tlib_curl_multi_init()
|
||||
#define curl_multi_add_handle(mh, eh) tlib_curl_multi_add_handle(mh, eh)
|
||||
#define curl_multi_perform(mh, rh) tlib_curl_multi_perform(mh, rh)
|
||||
#endif
|
||||
|
||||
CURL *tlib_curl_easy_init(void);
|
||||
CURLM *tlib_curl_multi_init(void);
|
||||
CURLMcode tlib_curl_multi_add_handle(CURLM *mh, CURL *eh);
|
||||
CURLMcode tlib_curl_multi_perform(CURLM *mh, int *running_handles);
|
||||
|
||||
#endif
|
29
ext/tlib/injected/stdfunc.c
Normal file
29
ext/tlib/injected/stdfunc.c
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file tlib/injected/stdfunc.c
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#include <tlib/injected/stdfunc.h>
|
||||
#define TLIB_INJECTIONS_ENABLED
|
||||
#include <tlib/injection.h>
|
||||
|
||||
#undef calloc
|
||||
#undef malloc
|
||||
#undef realloc
|
||||
|
||||
void *tlib_calloc(size_t count, size_t size)
|
||||
{
|
||||
TLIB_INJECT("calloc", return 0);
|
||||
return calloc(count, size);
|
||||
}
|
||||
void *tlib_malloc(size_t size)
|
||||
{
|
||||
TLIB_INJECT("malloc", return 0);
|
||||
return malloc(size);
|
||||
}
|
||||
void *tlib_realloc(void *ptr, size_t size)
|
||||
{
|
||||
TLIB_INJECT("realloc", return 0);
|
||||
return realloc(ptr, size);
|
||||
}
|
23
ext/tlib/injected/stdfunc.h
Normal file
23
ext/tlib/injected/stdfunc.h
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file tlib/injected/stdfunc.h
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#ifndef TLIB_INJECTED_STDFUNC_H_INCLUDED
|
||||
#define TLIB_INJECTED_STDFUNC_H_INCLUDED
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if defined(TLIB_INJECTIONS_ENABLED)
|
||||
#define calloc(count, size) tlib_calloc(count, size)
|
||||
#define malloc(size) tlib_malloc(size)
|
||||
#define realloc(ptr, size) tlib_realloc(ptr, size)
|
||||
#endif
|
||||
|
||||
void *tlib_calloc(size_t count, size_t size);
|
||||
void *tlib_malloc(size_t size);
|
||||
void *tlib_realloc(void *ptr, size_t size);
|
||||
|
||||
#endif
|
144
ext/tlib/injection.c
Normal file
144
ext/tlib/injection.c
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @file tlib/injection.c
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#include <tlib/injection.h>
|
||||
#include <tlib/callstack.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
|
||||
#define NBUCKETS 256
|
||||
struct injection_cond_s
|
||||
{
|
||||
struct injection_cond_s *cnext;
|
||||
char *sym;
|
||||
unsigned trigger, count;
|
||||
};
|
||||
struct injection_entry_s
|
||||
{
|
||||
struct injection_entry_s *hnext;
|
||||
char *name;
|
||||
struct injection_cond_s *clist;
|
||||
};
|
||||
struct injection_htab_s
|
||||
{
|
||||
struct injection_entry_s **buckets;
|
||||
};
|
||||
|
||||
static inline size_t hash_chars(const char *s, size_t length)
|
||||
{
|
||||
/* djb2: see http://www.cse.yorku.ca/~oz/hash.html */
|
||||
size_t h = 5381;
|
||||
for (const char *t = s + length; t > s; ++s)
|
||||
h = 33 * h + *s;
|
||||
return h;
|
||||
}
|
||||
static struct injection_htab_s *injection_htab()
|
||||
{
|
||||
static struct injection_htab_s *htab;
|
||||
if (0 == htab)
|
||||
{
|
||||
htab = calloc(1, sizeof *htab);
|
||||
assert(0 != htab);
|
||||
htab->buckets = calloc(NBUCKETS, sizeof(struct injection_entry_s *));
|
||||
}
|
||||
return htab;
|
||||
}
|
||||
static struct injection_entry_s *injection_lookup(const char *name)
|
||||
{
|
||||
struct injection_htab_s *htab = injection_htab();
|
||||
size_t i = hash_chars(name, strlen(name)) & (NBUCKETS - 1);
|
||||
for (struct injection_entry_s *entry = htab->buckets[i]; entry; entry = entry->hnext)
|
||||
if (0 == strcmp(entry->name, name))
|
||||
return entry;
|
||||
return 0;
|
||||
}
|
||||
static struct injection_entry_s *injection_insert(const char *name)
|
||||
{
|
||||
struct injection_htab_s *htab = injection_htab();
|
||||
size_t i = hash_chars(name, strlen(name)) & (NBUCKETS - 1);
|
||||
struct injection_entry_s *entry = calloc(1, sizeof *entry);
|
||||
assert(0 != entry);
|
||||
entry->name = strdup(name);
|
||||
entry->hnext = htab->buckets[i];
|
||||
htab->buckets[i] = entry;
|
||||
return entry;
|
||||
}
|
||||
static struct injection_cond_s *injection_cond_get(struct injection_entry_s *entry, const char **syms)
|
||||
{
|
||||
struct injection_cond_s *deinjection_centry = 0;
|
||||
for (struct injection_cond_s *centry = entry->clist; centry; centry = centry->cnext)
|
||||
if ('*' == centry->sym[0] && '\0' == centry->sym[1])
|
||||
deinjection_centry = centry;
|
||||
else
|
||||
{
|
||||
for (const char *sym; 0 != (sym = *syms); syms++)
|
||||
if (0 == strcmp(centry->sym, sym))
|
||||
return centry;
|
||||
}
|
||||
return deinjection_centry;
|
||||
}
|
||||
static void injection_cond_set(struct injection_entry_s *entry, const char *sym, unsigned trigger)
|
||||
{
|
||||
for (struct injection_cond_s *centry = entry->clist; centry; centry = centry->cnext)
|
||||
if (0 == strcmp(centry->sym, sym))
|
||||
{
|
||||
centry->trigger = trigger;
|
||||
return;
|
||||
}
|
||||
struct injection_cond_s *centry = calloc(1, sizeof *centry);
|
||||
assert(0 != centry);
|
||||
centry->sym = strdup(sym);
|
||||
centry->trigger = trigger;
|
||||
centry->cnext = entry->clist;
|
||||
entry->clist = centry;
|
||||
}
|
||||
static void injection_cond_remove(struct injection_entry_s *entry, const char *sym)
|
||||
{
|
||||
struct injection_cond_s **p = &entry->clist;
|
||||
for (; *p; p = &(*p)->cnext)
|
||||
if (0 == strcmp((*p)->sym, sym))
|
||||
break;
|
||||
if (*p) /* did we find the condition? */
|
||||
{
|
||||
struct injection_cond_s *q = *p;
|
||||
*p = q->cnext;
|
||||
free(q->sym);
|
||||
free(q);
|
||||
}
|
||||
}
|
||||
|
||||
void *tlib_injection(const char *name)
|
||||
{
|
||||
struct injection_entry_s *entry = injection_lookup(name);
|
||||
if (0 == entry)
|
||||
entry = injection_insert(name);
|
||||
return entry;
|
||||
}
|
||||
int tlib_injection_trace(void *injection)
|
||||
{
|
||||
if (0 == ((struct injection_entry_s *)injection)->clist)
|
||||
return 0;
|
||||
struct tlib_callstack_s stack;
|
||||
tlib_callstack(2, TLIB_MAX_SYMRET, &stack);
|
||||
struct injection_cond_s *centry = injection_cond_get(injection, stack.syms);
|
||||
if (0 == centry)
|
||||
return 0;
|
||||
return centry->count++ == centry->trigger || ~0 == centry->trigger;
|
||||
}
|
||||
void tlib_injection_enable(const char *name, const char *sym, unsigned trigger)
|
||||
{
|
||||
struct injection_entry_s *entry = tlib_injection(name);
|
||||
injection_cond_set(entry, sym, trigger);
|
||||
}
|
||||
void tlib_injection_disable(const char *name, const char *sym)
|
||||
{
|
||||
struct injection_entry_s *entry = tlib_injection(name);
|
||||
injection_cond_remove(entry, sym);
|
||||
}
|
30
ext/tlib/injection.h
Normal file
30
ext/tlib/injection.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @file tlib/injection.h
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
/* NOTE: This header may usefully be included multiple times.
|
||||
* The TLIB_INJECT() macro will be redefined based on whether
|
||||
* TLIB_INJECTIONS_ENABLED is defined.
|
||||
*/
|
||||
|
||||
#undef TLIB_INJECT
|
||||
#if defined(TLIB_INJECTIONS_ENABLED)
|
||||
#define TLIB_INJECT(name, stmt) \
|
||||
do\
|
||||
{\
|
||||
static void *injection = 0;\
|
||||
if (0 == injection)\
|
||||
injection = tlib_injection(name);\
|
||||
if (tlib_injection_trace(injection))\
|
||||
stmt;\
|
||||
} while (0)
|
||||
#else
|
||||
#define TLIB_INJECT(name, stmt) do {} while (0)
|
||||
#endif
|
||||
|
||||
void *tlib_injection(const char *name);
|
||||
int tlib_injection_trace(void *injection);
|
||||
void tlib_injection_enable(const char *name, const char *sym, unsigned trigger);
|
||||
void tlib_injection_disable(const char *name, const char *sym);
|
202
ext/tlib/testsuite.c
Normal file
202
ext/tlib/testsuite.c
Normal file
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @file tlib/testsuite.c
|
||||
*
|
||||
* @copyright 2014-2015 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
time_t t0 = time(0);
|
||||
test->fn();
|
||||
time_t t1 = time(0);
|
||||
return difftime(t1, t0);
|
||||
}
|
||||
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 %.0fs\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
|
||||
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)
|
||||
{
|
||||
#if defined(_WIN64) || defined(_WIN32)
|
||||
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\n", assert_buf, expr, file, line);
|
||||
else
|
||||
test_printf("%sASSERT(%s) failed at %s:%d:%s\n", assert_buf, expr, file, line, func);
|
||||
if (0 != test_jmp)
|
||||
longjmp(*test_jmp, 1);
|
||||
}
|
112
ext/tlib/testsuite.h
Normal file
112
ext/tlib/testsuite.h
Normal file
@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @file tlib/testsuite.h
|
||||
*
|
||||
* @copyright 2014-2015 Bill Zissimopoulos
|
||||
*/
|
||||
|
||||
#ifndef TLIB_TESTSUITE_H_INCLUDED
|
||||
#define TLIB_TESTSUITE_H_INCLUDED
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Assert macro
|
||||
*
|
||||
* This macro works similarly to the Standard C assert macro, except for the following differences:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The macro always checks the specified expression regardless of the NDEBUG macro.</li>
|
||||
* <li>The macro aborts the execution of the current test, but not necessarily the execution of the
|
||||
* whole testsuite.</li>
|
||||
* </ul>
|
||||
*/
|
||||
#define ASSERT(expr)\
|
||||
(!(expr) ? (tlib__assert(__func__, __FILE__, __LINE__, #expr), abort()) : (void)0)
|
||||
|
||||
/**
|
||||
* Register a test suite for execution.
|
||||
*
|
||||
* Test suites are simple functions with prototype <code>void testsuite()</code>. When executed
|
||||
* they register individual test cases for execution.
|
||||
*/
|
||||
#define TESTSUITE(fn)\
|
||||
do\
|
||||
{\
|
||||
void fn(void);\
|
||||
tlib_add_test_suite(#fn, fn);\
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Register a test case for execution.
|
||||
*
|
||||
* Test cases are simple functions with prototype <code>void testcase()</code>.
|
||||
*/
|
||||
#define TEST(fn)\
|
||||
do\
|
||||
{\
|
||||
void fn(void);\
|
||||
tlib_add_test(#fn, fn);\
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Register an optional test case for execution.
|
||||
*
|
||||
* Test cases are simple functions with prototype <code>void testcase()</code>.
|
||||
* Optional tests are not executed by default.
|
||||
*/
|
||||
#define TEST_OPT(fn)\
|
||||
do\
|
||||
{\
|
||||
void fn(void);\
|
||||
tlib_add_test_opt(#fn, fn);\
|
||||
} while (0)
|
||||
|
||||
void tlib_add_test_suite(const char *name, void (*fn)(void));
|
||||
void tlib_add_test(const char *name, void (*fn)(void));
|
||||
void tlib_add_test_opt(const char *name, void (*fn)(void));
|
||||
|
||||
/**
|
||||
* Printf function.
|
||||
*
|
||||
* Use this to produce output in the appropriate tlib file stream. This function uses the local
|
||||
* printf facilities and understands the same format strings.
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
void tlib_printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
#else
|
||||
void tlib_printf(const char *fmt, ...);
|
||||
#endif
|
||||
/**
|
||||
* Run tests.
|
||||
*
|
||||
* This function will first execute all registered test suites, thus giving them the chance to
|
||||
* register any test cases. It will then execute all registered test cases according to the
|
||||
* command line arguments passed in argv. The command line syntax is a follows:
|
||||
*
|
||||
* Usage: testprog [--list][[--tap][--no-abort][--repeat-forever] [[+-]TESTNAME...]
|
||||
*
|
||||
* <ul>
|
||||
* <li>--list - list tests only</li>
|
||||
* <li>--tap - produce output in TAP format</li>
|
||||
* <li>--no-abort - do not abort all tests when an ASSERT fails
|
||||
* (only the current test is aborted)</li>
|
||||
* <li>--repeat-forever - repeat tests forever</li>
|
||||
* </ul>
|
||||
*
|
||||
* By default all test cases are executed unless specific test cases are named. By default optional
|
||||
* test cases are not executed. To execute a specific test case specify its TESTNAME; if it is an
|
||||
* optional test case specify +TESTNAME. To excluse a test case specify -TESTNAME.
|
||||
*
|
||||
* TESTNAME may also contain a single asterisk at the end; for example, mytest* will match all test
|
||||
* cases that have names starting with "mytest".
|
||||
*
|
||||
* @param argc
|
||||
* Argument count.
|
||||
* @param argv
|
||||
* Argument vector.
|
||||
*/
|
||||
void tlib_run_tests(int argc, char *argv[]);
|
||||
|
||||
void tlib__assert(const char *func, const char *file, int line, const char *expr);
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user