And removed zNEW_test now that everything's integrated back in.
This commit is contained in:
parent
1cf87d5c5e
commit
de35499976
|
@ -1,35 +0,0 @@
|
|||
# 23 march 2019
|
||||
|
||||
libui_test_sources += [
|
||||
'lib/testing.c',
|
||||
'lib/testingpriv.c',
|
||||
'lib/timer.c',
|
||||
]
|
||||
|
||||
if libui_OS == 'windows'
|
||||
libui_test_sources += [
|
||||
'lib/thread_windows.c',
|
||||
'lib/timer_windows.c',
|
||||
]
|
||||
else # including Haiku, since it's POSIX-compliant
|
||||
libui_test_sources += [
|
||||
'lib/thread_darwinunix.c',
|
||||
'lib/timer_darwinunix.c'
|
||||
]
|
||||
endif
|
||||
|
||||
libui_test_deps += [
|
||||
dependency('threads',
|
||||
required: true),
|
||||
]
|
||||
if libui_OS == 'windows'
|
||||
# static mode already gives us these dependencies
|
||||
if libui_mode != 'static'
|
||||
libui_test_deps += [
|
||||
meson.get_compiler('c').find_library('kernel32',
|
||||
required: true),
|
||||
meson.get_compiler('c').find_library('user32',
|
||||
required: true),
|
||||
]
|
||||
endif
|
||||
endif
|
|
@ -1,326 +0,0 @@
|
|||
// 27 february 2018
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <setjmp.h>
|
||||
#include <string.h>
|
||||
#include "timer.h"
|
||||
#include "testing.h"
|
||||
#include "testingpriv.h"
|
||||
|
||||
struct defer {
|
||||
void (*f)(testingT *, void *);
|
||||
void *data;
|
||||
struct defer *next;
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Microsoft defines jmp_buf with a __declspec(align()), and for whatever reason, they have a warning that triggers when you use that for any reason, and that warning is enabled with /W4
|
||||
// Silence the warning; it's harmless.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4324)
|
||||
#endif
|
||||
|
||||
struct testingT {
|
||||
// set at test creation time
|
||||
const char *name;
|
||||
void (*f)(testingT *, void *);
|
||||
void *data;
|
||||
|
||||
// for sorting tests in a set; not used by subtests
|
||||
const char *file;
|
||||
long line;
|
||||
|
||||
// test status
|
||||
bool failed;
|
||||
bool skipped;
|
||||
bool returned;
|
||||
jmp_buf returnNowBuf;
|
||||
|
||||
// deferred functions
|
||||
struct defer *defers;
|
||||
bool defersRun;
|
||||
|
||||
// execution options
|
||||
testingOptions opts;
|
||||
|
||||
// output
|
||||
testingprivOutbuf *outbuf;
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
static void initTest(testingT *t, const char *name, void (*f)(testingT *, void *), void *data, const char *file, long line)
|
||||
{
|
||||
memset(t, 0, sizeof (testingT));
|
||||
t->name = name;
|
||||
t->f = f;
|
||||
t->data = data;
|
||||
t->file = file;
|
||||
t->line = line;
|
||||
}
|
||||
|
||||
struct testingSet {
|
||||
testingprivArray tests;
|
||||
};
|
||||
|
||||
static testingSet mainTests = { testingprivArrayStaticInit(testingT, 32, "testingT[]") };
|
||||
|
||||
void testingprivSetRegisterTest(testingSet **pset, const char *name, void (*f)(testingT *, void *), void *data, const char *file, long line)
|
||||
{
|
||||
testingSet *set;
|
||||
testingT *t;
|
||||
|
||||
set = &mainTests;
|
||||
if (pset != NULL) {
|
||||
set = *pset;
|
||||
if (set == NULL) {
|
||||
set = testingprivNew(testingSet);
|
||||
testingprivArrayInit(set->tests, testingT, 32, "testingT[]");
|
||||
*pset = set;
|
||||
}
|
||||
}
|
||||
t = (testingT *) testingprivArrayAppend(&(set->tests), 1);
|
||||
initTest(t, name, f, data, file, line);
|
||||
}
|
||||
|
||||
static int testcmp(const void *a, const void *b)
|
||||
{
|
||||
const testingT *ta = (const testingT *) a;
|
||||
const testingT *tb = (const testingT *) b;
|
||||
int ret;
|
||||
|
||||
ret = strcmp(ta->file, tb->file);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
if (ta->line < tb->line)
|
||||
return -1;
|
||||
if (ta->line > tb->line)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void runDefers(testingT *t)
|
||||
{
|
||||
struct defer *d;
|
||||
|
||||
if (t->defersRun)
|
||||
return;
|
||||
t->defersRun = true;
|
||||
for (d = t->defers; d != NULL; d = d->next)
|
||||
(*(d->f))(t, d->data);
|
||||
// and now free the defers
|
||||
// we could use t->defers == NULL instead of t->defersRun but then recursive calls to runDefers() (for instance, if a deferred function calls testingTFatalf()) would explode
|
||||
while (t->defers != NULL) {
|
||||
d = t->defers;
|
||||
t->defers = t->defers->next;
|
||||
testingprivFree(d);
|
||||
}
|
||||
}
|
||||
|
||||
static const testingOptions defaultOptions = {
|
||||
.Verbose = 0,
|
||||
};
|
||||
|
||||
static bool testingprivTRun(testingT *t, testingprivOutbuf *parentbuf)
|
||||
{
|
||||
const char *status;
|
||||
timerTime start, end;
|
||||
char timerstr[timerDurationStringLen];
|
||||
bool printStatus;
|
||||
|
||||
if (t->opts.Verbose)
|
||||
testingprivOutbufPrintf(parentbuf, "=== RUN %s\n", t->name);
|
||||
t->outbuf = testingprivNewOutbuf();
|
||||
|
||||
start = timerMonotonicNow();
|
||||
if (setjmp(t->returnNowBuf) == 0)
|
||||
(*(t->f))(t, t->data);
|
||||
end = timerMonotonicNow();
|
||||
t->returned = true;
|
||||
runDefers(t);
|
||||
|
||||
printStatus = t->opts.Verbose;
|
||||
status = "PASS";
|
||||
if (t->failed) {
|
||||
status = "FAIL";
|
||||
printStatus = true; // always print status on failure
|
||||
} else if (t->skipped)
|
||||
// note that failed overrides skipped
|
||||
status = "SKIP";
|
||||
timerDurationString(timerTimeSub(end, start), timerstr);
|
||||
if (printStatus) {
|
||||
testingprivOutbufPrintf(parentbuf, "--- %s: %s (%s)\n", status, t->name, timerstr);
|
||||
testingprivOutbufAppendOutbuf(parentbuf, t->outbuf);
|
||||
}
|
||||
|
||||
testingprivOutbufFree(t->outbuf);
|
||||
t->outbuf = NULL;
|
||||
return !t->failed;
|
||||
}
|
||||
|
||||
// TODO make options and opts consistent throughout? what about format vs fmt in public-facing functions?
|
||||
static void testingprivSetRun(testingSet *set, const testingOptions *opts, testingprivOutbuf *outbuf, bool *anyFailed)
|
||||
{
|
||||
size_t i;
|
||||
testingT *t;
|
||||
|
||||
testingprivArrayQsort(&(set->tests), testcmp);
|
||||
t = (testingT *) (set->tests.buf);
|
||||
for (i = 0; i < set->tests.len; i++) {
|
||||
t->opts = *opts;
|
||||
if (!testingprivTRun(t, outbuf))
|
||||
*anyFailed = true;
|
||||
t++;
|
||||
}
|
||||
}
|
||||
|
||||
void testingSetRun(testingSet *set, const struct testingOptions *options, bool *anyRun, bool *anyFailed)
|
||||
{
|
||||
*anyRun = false;
|
||||
*anyFailed = false;
|
||||
if (set == NULL)
|
||||
set = &mainTests;
|
||||
if (options == NULL)
|
||||
options = &defaultOptions;
|
||||
if (set->tests.len == 0)
|
||||
return;
|
||||
testingprivSetRun(set, options, NULL, anyFailed);
|
||||
*anyRun = true;
|
||||
}
|
||||
|
||||
void testingprivTLogfFullThen(testingT *t, void (*then)(testingT *t), const char *file, long line, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
// run va_end() *before* then, just to be safe
|
||||
testingprivTLogvfFullThen(t, NULL, file, line, format, ap);
|
||||
va_end(ap);
|
||||
if (then != NULL)
|
||||
(*then)(t);
|
||||
}
|
||||
|
||||
static const char *basename(const char *file)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
for (;;) {
|
||||
p = strpbrk(file, "/\\");
|
||||
if (p == NULL)
|
||||
break;
|
||||
file = p + 1;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
void testingprivTLogvfFullThen(testingT *t, void (*then)(testingT *t), const char *file, long line, const char *format, va_list ap)
|
||||
{
|
||||
testingprivOutbufPrintf(t->outbuf, "%s:%ld: ", basename(file), line);
|
||||
testingprivOutbufVprintfIndented(t->outbuf, format, ap);
|
||||
testingprivOutbufPrintf(t->outbuf, "\n");
|
||||
if (then != NULL)
|
||||
(*then)(t);
|
||||
}
|
||||
|
||||
void testingTFail(testingT *t)
|
||||
{
|
||||
t->failed = true;
|
||||
}
|
||||
|
||||
static void returnNow(testingT *t)
|
||||
{
|
||||
if (!t->returned) {
|
||||
// set this now so a FailNow inside a Defer doesn't longjmp twice
|
||||
t->returned = true;
|
||||
// run defers before calling longjmp() just to be safe
|
||||
runDefers(t);
|
||||
longjmp(t->returnNowBuf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void testingTFailNow(testingT *t)
|
||||
{
|
||||
testingTFail(t);
|
||||
returnNow(t);
|
||||
}
|
||||
|
||||
void testingTSkipNow(testingT *t)
|
||||
{
|
||||
t->skipped = true;
|
||||
returnNow(t);
|
||||
}
|
||||
|
||||
void testingTDefer(testingT *t, void (*f)(testingT *t, void *data), void *data)
|
||||
{
|
||||
struct defer *d;
|
||||
|
||||
d = testingprivNew(struct defer);
|
||||
d->f = f;
|
||||
d->data = data;
|
||||
// add to the head of the list so defers are run in reverse order of how they were added
|
||||
d->next = t->defers;
|
||||
t->defers = d;
|
||||
}
|
||||
|
||||
void testingTRun(testingT *t, const char *subname, void (*subfunc)(testingT *t, void *data), void *data)
|
||||
{
|
||||
testingT *subt;
|
||||
testingprivOutbuf *rewrittenName;
|
||||
char *fullName;
|
||||
|
||||
rewrittenName = testingprivNewOutbuf();
|
||||
while (*subname != '\0') {
|
||||
const char *replaced;
|
||||
|
||||
replaced = NULL;
|
||||
switch (*subname) {
|
||||
case ' ':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\v':
|
||||
replaced = "_";
|
||||
break;
|
||||
case '\a':
|
||||
replaced = "\\a";
|
||||
break;
|
||||
case '\b':
|
||||
replaced = "\\b";
|
||||
break;
|
||||
}
|
||||
if (replaced != NULL)
|
||||
testingprivOutbufPrintf(rewrittenName, "%s", replaced);
|
||||
else if (isprint(*subname))
|
||||
testingprivOutbufPrintf(rewrittenName, "%c", *subname);
|
||||
else
|
||||
testingprivOutbufPrintf(rewrittenName, "\\x%x", (unsigned int) (*subname));
|
||||
subname++;
|
||||
}
|
||||
fullName = testingprivSmprintf("%s/%s", t->name, testingprivOutbufString(rewrittenName));
|
||||
testingprivOutbufFree(rewrittenName);
|
||||
|
||||
subt = testingprivNew(testingT);
|
||||
initTest(subt, fullName, subfunc, data, NULL, 0);
|
||||
subt->opts = t->opts;
|
||||
if (!testingprivTRun(subt, t->outbuf))
|
||||
t->failed = true;
|
||||
testingprivFree(subt);
|
||||
|
||||
testingprivFree(fullName);
|
||||
}
|
||||
|
||||
// Utility functions, provided here to avoid mucking up the sharedbits functions.
|
||||
|
||||
char *testingUtilStrdup(const char *s)
|
||||
{
|
||||
return testingprivStrdup(s);
|
||||
}
|
||||
|
||||
void testingUtilFreeStrdup(char *s)
|
||||
{
|
||||
testingprivFree(s);
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
// 27 february 2018
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define testingprivImplName(basename) testingprivImpl ## basename
|
||||
|
||||
#define testingprivScaffoldName(basename) testingprivScaffold ## basename
|
||||
#define testingprivMkScaffold(basename, argtype, argname) \
|
||||
static void testingprivScaffoldName(basename)(argtype *argname, void *data) { testingprivImplName(basename)(argname); }
|
||||
|
||||
// references:
|
||||
// - https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gconstructor.h
|
||||
// - https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glib-compile-resources.c
|
||||
// - https://msdn.microsoft.com/en-us/library/bb918180.aspx
|
||||
#define testingprivCtorName(basename) testingprivCtor ## basename
|
||||
#define testingprivCtorPtrName(basename) testingprivCtorPtr ## basename
|
||||
#if defined(__GNUC__)
|
||||
#define testingprivMkCtor(basename, pset) \
|
||||
__attribute__((constructor)) static void testingprivCtorName(basename)(void) { testingprivSetRegisterTest(pset, #basename, testingprivScaffoldName(basename), NULL, __FILE__, __LINE__); }
|
||||
#elif defined(_MSC_VER)
|
||||
#define testingprivMkCtor(basename, pset) \
|
||||
static int testingprivCtorName(basename)(void) { testingprivSetRegisterTest(pset, #basename, testingprivScaffoldName(basename), NULL, __FILE__, __LINE__); return 0; } \
|
||||
__pragma(section(".CRT$XCU",read)) \
|
||||
__declspec(allocate(".CRT$XCU")) static int (*testingprivCtorPtrName(basename))(void) = testingprivCtorName(basename);
|
||||
#else
|
||||
#error unknown compiler for making constructors in C; cannot continue
|
||||
#endif
|
||||
|
||||
#define testingprivMk(basename, argtype, argname, pset) \
|
||||
void testingprivImplName(basename)(argtype *argname); \
|
||||
testingprivMkScaffold(basename, argtype, argname) \
|
||||
testingprivMkCtor(basename, pset) \
|
||||
void testingprivImplName(basename)(argtype *argname)
|
||||
|
||||
#define testingTest(Name) \
|
||||
testingprivMk(Test ## Name, testingT, t, NULL)
|
||||
#define testingTestInSet(Set, Name) \
|
||||
testingprivMk(Test ## Name, testingT, t, &Set)
|
||||
|
||||
typedef struct testingSet testingSet;
|
||||
typedef struct testingOptions testingOptions;
|
||||
|
||||
struct testingOptions {
|
||||
bool Verbose;
|
||||
};
|
||||
|
||||
extern void testingSetRun(testingSet *set, const struct testingOptions *options, bool *anyRun, bool *anyFailed);
|
||||
|
||||
typedef struct testingT testingT;
|
||||
|
||||
#define testingTLogf(t, ...) \
|
||||
testingprivTLogfFullThen(t, NULL, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define testingTLogvf(t, format, ap) \
|
||||
testingprivTLogvfFullThen(t, NULL, __FILE__, __LINE__, format, ap)
|
||||
#define testingTLogfFull(t, file, line, ...) \
|
||||
testingprivTLogfFullThen(t, NULL, file, line, __VA_ARGS__)
|
||||
#define testingTLogvfFull(t, file, line, format, ap) \
|
||||
testingprivTLogvfFullThen(t, NULL, file, line, format, ap)
|
||||
|
||||
#define testingTErrorf(t, ...) \
|
||||
testingprivTLogfFullThen(t, testingTFail, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define testingTErrorvf(t, format, ap) \
|
||||
testingprivTLogvfFullThen(t, testingTFail, __FILE__, __LINE__, format, ap)
|
||||
#define testingTErrorfFull(t, file, line, ...) \
|
||||
testingprivTLogfFullThen(t, testingTFail, file, line, __VA_ARGS__)
|
||||
#define testingTErrorvfFull(t, file, line, format, ap) \
|
||||
testingprivTLogvfFullThen(t, testingTFail, file, line, format, ap)
|
||||
|
||||
#define testingTFatalf(t, ...) \
|
||||
testingprivTLogfFullThen(t, testingTFailNow, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define testingTFatalvf(t, format, ap) \
|
||||
testingprivTLogvfFullThen(t, testingTFailNow, __FILE__, __LINE__, format, ap)
|
||||
#define testingTFatalfFull(t, file, line, ...) \
|
||||
testingprivTLogfFullThen(t, testingTFailNow, file, line, __VA_ARGS__)
|
||||
#define testingTFatalvfFull(t, file, line, format, ap) \
|
||||
testingprivTLogvfFullThen(t, testingTFailNow, file, line, format, ap)
|
||||
|
||||
#define testingTSkipf(t, ...) \
|
||||
testingprivTLogfFullThen(t, testingTSkipNow, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define testingTSkipvf(t, format, ap) \
|
||||
testingprivTLogvfFullThen(t, testingTSkipNow, __FILE__, __LINE__, format, ap)
|
||||
#define testingTSkipfFull(t, file, line, ...) \
|
||||
testingprivTLogfFullThen(t, testingTSkipNow, file, line, __VA_ARGS__)
|
||||
#define testingTSkipvfFull(t, file, line, format, ap) \
|
||||
testingprivTLogvfFullThen(t, testingTSkipNow, file, line, format, ap)
|
||||
|
||||
extern void testingTFail(testingT *t);
|
||||
extern void testingTFailNow(testingT *t);
|
||||
extern void testingTSkipNow(testingT *t);
|
||||
|
||||
extern void testingTDefer(testingT *t, void (*f)(testingT *t, void *data), void *data);
|
||||
extern void testingTRun(testingT *t, const char *subname, void (*subfunc)(testingT *t, void *data), void *data);
|
||||
|
||||
extern void testingprivSetRegisterTest(testingSet **pset, const char *, void (*)(testingT *, void *), void *, const char *, long);
|
||||
#include "../../sharedbits/printfwarn_header.h"
|
||||
sharedbitsPrintfFunc(
|
||||
extern void testingprivTLogfFullThen(testingT *, void (*)(testingT *), const char *, long, const char *, ...),
|
||||
5, 6);
|
||||
#undef sharedbitsPrintfFunc
|
||||
extern void testingprivTLogvfFullThen(testingT *, void (*)(testingT *), const char *, long, const char *, va_list);
|
||||
|
||||
// Utility functions, provided here to avoid mucking up the sharedbits functions.
|
||||
extern char *testingUtilStrdup(const char *s);
|
||||
extern void testingUtilFreeStrdup(char *s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,218 +0,0 @@
|
|||
// 19 may 2019
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "testing.h"
|
||||
#include "testingpriv.h"
|
||||
|
||||
void testingprivInternalError(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "** testing internal error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "; aborting\n");
|
||||
va_end(ap);
|
||||
abort();
|
||||
}
|
||||
|
||||
#define sharedbitsPrefix testingpriv
|
||||
#include "../../sharedbits/alloc_impl.h"
|
||||
#include "../../sharedbits/array_impl.h"
|
||||
#undef sharedbitsPrefix
|
||||
|
||||
#define sharedbitsPrefix testingprivImpl
|
||||
#define sharedbitsStatic static
|
||||
#define sharedbitsInternalError testingprivInternalError
|
||||
#include "../../sharedbits/strsafe_impl.h"
|
||||
#undef sharedbitsInternalError
|
||||
#undef sharedbitsStatic
|
||||
#undef sharedbitsPrefix
|
||||
|
||||
#define sharedbitsPrefix testingpriv
|
||||
#define testingprivStrncpy testingprivImplStrncpy
|
||||
#include "../../sharedbits/strdup_impl.h"
|
||||
#undef testingprivStrncpy
|
||||
#undef sharedbitsPrefix
|
||||
|
||||
int testingprivVsnprintf(char *s, size_t n, const char *fmt, va_list ap)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = testingprivImplVsnprintf(s, n, fmt, ap);
|
||||
if (ret < 0)
|
||||
testingprivInternalError("encoding error in vsnprintf(); this likely means your call to testingTLogf() and the like is invalid");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int testingprivSnprintf(char *s, size_t n, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = testingprivVsnprintf(s, n, fmt, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *testingprivVsmprintf(const char *fmt, va_list ap)
|
||||
{
|
||||
char *s;
|
||||
va_list ap2;
|
||||
int n;
|
||||
|
||||
va_copy(ap2, ap);
|
||||
n = testingprivVsnprintf(NULL, 0, fmt, ap2);
|
||||
va_end(ap2);
|
||||
s = (char *) testingprivAlloc((n + 1) * sizeof (char), "char[]");
|
||||
testingprivVsnprintf(s, n + 1, fmt, ap);
|
||||
return s;
|
||||
}
|
||||
|
||||
char *testingprivSmprintf(const char *fmt, ...)
|
||||
{
|
||||
char *s;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
s = testingprivVsmprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
return s;
|
||||
}
|
||||
|
||||
struct testingprivOutbuf {
|
||||
testingprivArray buf;
|
||||
};
|
||||
|
||||
testingprivOutbuf *testingprivNewOutbuf(void)
|
||||
{
|
||||
testingprivOutbuf *o;
|
||||
|
||||
o = testingprivNew(testingprivOutbuf);
|
||||
testingprivArrayInit(o->buf, char, 32, "testing output buffer");
|
||||
return o;
|
||||
}
|
||||
|
||||
void testingprivOutbufFree(testingprivOutbuf *o)
|
||||
{
|
||||
testingprivArrayFree(o->buf);
|
||||
testingprivFree(o);
|
||||
}
|
||||
|
||||
void testingprivOutbufVprintf(testingprivOutbuf *o, const char *fmt, va_list ap)
|
||||
{
|
||||
char *dest;
|
||||
va_list ap2;
|
||||
int n;
|
||||
|
||||
if (o == NULL) {
|
||||
vprintf(fmt, ap);
|
||||
return;
|
||||
}
|
||||
va_copy(ap2, ap);
|
||||
n = testingprivVsnprintf(NULL, 0, fmt, ap2);
|
||||
va_end(ap2);
|
||||
// To conserve memory, we only allocate the terminating NUL once.
|
||||
if (o->buf.len == 0)
|
||||
dest = (char *) testingprivArrayAppend(&(o->buf), n + 1);
|
||||
else {
|
||||
dest = (char *) testingprivArrayAppend(&(o->buf), n);
|
||||
dest--;
|
||||
}
|
||||
testingprivVsnprintf(dest, n + 1, fmt, ap);
|
||||
}
|
||||
|
||||
void testingprivOutbufVprintfIndented(testingprivOutbuf *o, const char *fmt, va_list ap)
|
||||
{
|
||||
char *buf;
|
||||
char *lineStart, *lineEnd;
|
||||
const char *indent;
|
||||
|
||||
buf = testingprivVsmprintf(fmt, ap);
|
||||
|
||||
lineStart = buf;
|
||||
indent = "";
|
||||
for (;;) {
|
||||
lineEnd = strchr(lineStart, '\n');
|
||||
if (lineEnd == NULL)
|
||||
break;
|
||||
*lineEnd = '\0';
|
||||
testingprivOutbufPrintf(o, "%s%s\n", indent, lineStart);
|
||||
lineStart = lineEnd + 1;
|
||||
indent = " ";
|
||||
}
|
||||
// and print the last line fragment, if any
|
||||
if (*lineStart != '\0')
|
||||
testingprivOutbufPrintf(o, "%s%s", indent, lineStart);
|
||||
testingprivFree(buf);
|
||||
}
|
||||
|
||||
void testingprivOutbufPrintf(testingprivOutbuf *o, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
testingprivOutbufVprintf(o, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
// TODO right now this assumes the last character in o before calling this is a newline
|
||||
void testingprivOutbufAppendOutbuf(testingprivOutbuf *o, testingprivOutbuf *src)
|
||||
{
|
||||
char *buf;
|
||||
size_t n;
|
||||
bool hasTrailingBlankLine;
|
||||
size_t trailingBlankLinePos = 0; // silence incorrect MSVC warning
|
||||
char *lineStart, *lineEnd;
|
||||
|
||||
buf = src->buf.buf;
|
||||
n = src->buf.len;
|
||||
if (n == 0)
|
||||
// nothing to write
|
||||
return;
|
||||
|
||||
// strip trailing blank lines, if any
|
||||
hasTrailingBlankLine = false;
|
||||
if (buf[n - 1] == '\n') {
|
||||
hasTrailingBlankLine = true;
|
||||
while (n > 0 && buf[n - 1] == '\n')
|
||||
n--;
|
||||
if (n == 0) {
|
||||
// the buffer only has blank lines, so just add a single newline and be done with it
|
||||
// TODO verify that this is the correct behavior
|
||||
testingprivOutbufPrintf(o, "\n");
|
||||
return;
|
||||
}
|
||||
trailingBlankLinePos = n;
|
||||
buf[trailingBlankLinePos] = '\0';
|
||||
}
|
||||
|
||||
lineStart = buf;
|
||||
for (;;) {
|
||||
lineEnd = strchr(lineStart, '\n');
|
||||
if (lineEnd == NULL) // last line
|
||||
break;
|
||||
*lineEnd = '\0';
|
||||
testingprivOutbufPrintf(o, " %s\n", lineStart);
|
||||
// be sure to restore src to its original state
|
||||
*lineEnd = '\n';
|
||||
lineStart = lineEnd + 1;
|
||||
}
|
||||
// print the last line, if any
|
||||
if (*lineStart != '\0')
|
||||
testingprivOutbufPrintf(o, " %s\n", lineStart);
|
||||
|
||||
// restore src to its original state
|
||||
if (hasTrailingBlankLine)
|
||||
buf[trailingBlankLinePos] = '\n';
|
||||
}
|
||||
|
||||
const char *testingprivOutbufString(testingprivOutbuf *o)
|
||||
{
|
||||
if (o->buf.buf == NULL)
|
||||
return "";
|
||||
return o->buf.buf;
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// 19 may 2019
|
||||
|
||||
#include "../../sharedbits/printfwarn_header.h"
|
||||
|
||||
sharedbitsPrintfFunc(
|
||||
extern void testingprivInternalError(const char *fmt, ...),
|
||||
1, 2);
|
||||
|
||||
#define sharedbitsPrefix testingpriv
|
||||
|
||||
#include "../../sharedbits/alloc_header.h"
|
||||
#define testingprivNew(T) ((T *) testingprivAlloc(sizeof (T), #T))
|
||||
#define testingprivNewArray(T, n) ((T *) testingprivAlloc(n * sizeof (T), #T "[]"))
|
||||
#define testingprivResizeArray(x, T, old, new) ((T *) testingprivRealloc(x, old * sizeof (T), new * sizeof (T), #T "[]"))
|
||||
|
||||
#include "../../sharedbits/array_header.h"
|
||||
#define testingprivArrayStaticInit(T, nGrow, what) { NULL, 0, 0, sizeof (T), nGrow, what }
|
||||
#define testingprivArrayInit(arr, T, nGrow, what) testingprivArrayInitFull(&(arr), sizeof (T), nGrow, what)
|
||||
#define testingprivArrayFree(arr) testingprivArrayFreeFull(&(arr))
|
||||
#define testingprivArrayAt(arr, T, n) (((T *) (arr.buf)) + (n))
|
||||
|
||||
#include "../../sharedbits/strdup_header.h"
|
||||
|
||||
#undef sharedbitsPrefix
|
||||
|
||||
extern int testingprivVsnprintf(char *s, size_t n, const char *fmt, va_list ap);
|
||||
sharedbitsPrintfFunc(
|
||||
extern int testingprivSnprintf(char *s, size_t n, const char *fmt, ...),
|
||||
3, 4);
|
||||
extern char *testingprivVsmprintf(const char *fmt, va_list ap);
|
||||
sharedbitsPrintfFunc(
|
||||
extern char *testingprivSmprintf(const char *fmt, ...),
|
||||
1, 2);
|
||||
|
||||
// a testingprivOutbuf of NULL writes directly to stdout
|
||||
typedef struct testingprivOutbuf testingprivOutbuf;
|
||||
extern testingprivOutbuf *testingprivNewOutbuf(void);
|
||||
extern void testingprivOutbufFree(testingprivOutbuf *o);
|
||||
extern void testingprivOutbufVprintf(testingprivOutbuf *o, const char *fmt, va_list ap);
|
||||
extern void testingprivOutbufVprintfIndented(testingprivOutbuf *o, const char *fmt, va_list ap);
|
||||
sharedbitsPrintfFunc(
|
||||
extern void testingprivOutbufPrintf(testingprivOutbuf *o, const char *fmt, ...),
|
||||
2, 3);
|
||||
extern void testingprivOutbufAppendOutbuf(testingprivOutbuf *o, testingprivOutbuf *src);
|
||||
extern const char *testingprivOutbufString(testingprivOutbuf *o);
|
||||
|
||||
#undef sharedbitsPrintfFunc
|
|
@ -1,352 +0,0 @@
|
|||
// 2 may 2019
|
||||
#include <string.h>
|
||||
#include "timer.h"
|
||||
#include "timerpriv.h"
|
||||
|
||||
// This is based on the algorithm that Go uses for time.Duration.
|
||||
// Of course, we're not expressing it the same way...
|
||||
struct timerStringPart {
|
||||
char suffix;
|
||||
char suffix2;
|
||||
int mode;
|
||||
uint32_t maxOrMod;
|
||||
int precision;
|
||||
};
|
||||
|
||||
enum {
|
||||
modeMaxAndStop,
|
||||
modeFracModContinue,
|
||||
};
|
||||
|
||||
static const struct timerStringPart parts[] = {
|
||||
{ 'n', 's', modeMaxAndStop, 1000, 0 },
|
||||
{ 'u', 's', modeMaxAndStop, 1000000, 3 },
|
||||
{ 'm', 's', modeMaxAndStop, 1000000000, 6 },
|
||||
{ 's', 0, modeFracModContinue, 60, 9 },
|
||||
{ 'm', 0, modeFracModContinue, 60, 0 },
|
||||
{ 'h', 0, modeFracModContinue, 60, 0 },
|
||||
{ 0, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
static int fillFracPart(char *buf, int precision, int start, uint64_t *unsec)
|
||||
{
|
||||
int i;
|
||||
bool print;
|
||||
uint64_t digit;
|
||||
|
||||
print = false;
|
||||
for (i = 0; i < precision; i++) {
|
||||
digit = *unsec % 10;
|
||||
print = print || (digit != 0);
|
||||
if (print) {
|
||||
buf[start - 1] = "0123456789"[digit];
|
||||
start--;
|
||||
}
|
||||
*unsec /= 10;
|
||||
}
|
||||
if (print) {
|
||||
buf[start - 1] = '.';
|
||||
start--;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
static int fillIntPart(char *buf, int start, uint64_t unsec)
|
||||
{
|
||||
if (unsec == 0) {
|
||||
buf[start - 1] = '0';
|
||||
start--;
|
||||
return start;
|
||||
}
|
||||
while (unsec != 0) {
|
||||
buf[start - 1] = "0123456789"[unsec % 10];
|
||||
start--;
|
||||
unsec /= 10;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
void timerDurationString(timerDuration d, char buf[timerDurationStringLen])
|
||||
{
|
||||
uint64_t unsec;
|
||||
bool neg;
|
||||
int start;
|
||||
const struct timerStringPart *p;
|
||||
|
||||
memset(buf, 0, timerDurationStringLen * sizeof (char));
|
||||
start = 32;
|
||||
|
||||
if (d == 0) {
|
||||
buf[0] = '0';
|
||||
buf[1] = 's';
|
||||
return;
|
||||
}
|
||||
neg = d < 0;
|
||||
if (neg) {
|
||||
// C99 §6.2.6.2 resticts the possible signed integer representations in C to either sign-magnitude, 1's complement, or 2's complement.
|
||||
// Therefore, INT64_MIN will always be either -INT64_MAX or -INT64_MAX - 1, so we can safely do this to see if we need to special-case INT64_MIN as -INT64_MIN cannot be safely represented, or if we can just say -d as that can be safely represented.
|
||||
// See also https://stackoverflow.com/questions/29808397/how-to-portably-find-out-minint-max-absint-min
|
||||
if (d < -INT64_MAX) {
|
||||
// INT64_MIN is -INT64_MAX - 1.
|
||||
// This value comes directly from forcing such a value into Go's code; let's just use it.
|
||||
memmove(buf, "-2562047h47m16.854775808s", (25 + 1) * sizeof (char));
|
||||
return;
|
||||
}
|
||||
unsec = (uint64_t) (-d);
|
||||
} else
|
||||
unsec = (uint64_t) d;
|
||||
|
||||
for (p = parts; p->suffix != 0; p++) {
|
||||
if (p->mode == modeMaxAndStop && unsec < p->maxOrMod) {
|
||||
if (p->suffix2 != 0) {
|
||||
buf[start - 1] = p->suffix2;
|
||||
start--;
|
||||
}
|
||||
buf[start - 1] = p->suffix;
|
||||
start--;
|
||||
start = fillFracPart(buf, p->precision, start, &unsec);
|
||||
start = fillIntPart(buf, start, unsec);
|
||||
break;
|
||||
}
|
||||
if (p->mode == modeFracModContinue && unsec != 0) {
|
||||
if (p->suffix2 != 0) {
|
||||
buf[start - 1] = p->suffix2;
|
||||
start--;
|
||||
}
|
||||
buf[start - 1] = p->suffix;
|
||||
start--;
|
||||
start = fillFracPart(buf, p->precision, start, &unsec);
|
||||
start = fillIntPart(buf, start, unsec % p->maxOrMod);
|
||||
unsec /= p->maxOrMod;
|
||||
// and move on to the next one
|
||||
}
|
||||
}
|
||||
|
||||
if (neg) {
|
||||
buf[start - 1] = '-';
|
||||
start--;
|
||||
}
|
||||
memmove(buf, buf + start, 33 - start);
|
||||
}
|
||||
|
||||
// portable implementations of 64x64-bit MulDiv(), because:
|
||||
// - a division intrinsic was not added to Visual Studio until VS2015
|
||||
// - there does not seem to be a division intrinsic in GCC or clang as far as I can tell
|
||||
// - there are no 128-bit facilities in macOS as far as I can tell
|
||||
|
||||
static void int128FromUint64(uint64_t n, timerprivInt128 *out)
|
||||
{
|
||||
out->neg = false;
|
||||
out->high = 0;
|
||||
out->low = n;
|
||||
}
|
||||
|
||||
static void int128FromInt64(int64_t n, timerprivInt128 *out)
|
||||
{
|
||||
if (n >= 0) {
|
||||
int128FromUint64((uint64_t) n, out);
|
||||
return;
|
||||
}
|
||||
out->neg = true;
|
||||
out->high = 0;
|
||||
// And now we do the same INT64_MIN/INT64_MAX juggling as above.
|
||||
if (n < -INT64_MAX) {
|
||||
out->low = ((uint64_t) INT64_MAX) + 1;
|
||||
return;
|
||||
}
|
||||
out->low = (uint64_t) (-n);
|
||||
}
|
||||
|
||||
// references for this part:
|
||||
// - https://opensource.apple.com/source/Libc/Libc-1272.200.26/gen/nanosleep.c.auto.html
|
||||
// - https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder
|
||||
|
||||
static void int128UAdd(timerprivInt128 *x, const timerprivInt128 *y)
|
||||
{
|
||||
x->high += y->high;
|
||||
x->low += y->low;
|
||||
if (x->low < y->low)
|
||||
x->high++;
|
||||
}
|
||||
|
||||
static void int128USub(timerprivInt128 *x, const timerprivInt128 *y)
|
||||
{
|
||||
x->high -= y->high;
|
||||
if (x->low < y->low)
|
||||
x->high--;
|
||||
x->low -= y->low;
|
||||
}
|
||||
|
||||
static void int128Lsh1(timerprivInt128 *x)
|
||||
{
|
||||
x->high <<= 1;
|
||||
if ((x->low & 0x8000000000000000) != 0)
|
||||
x->high |= 1;
|
||||
x->low <<= 1;
|
||||
}
|
||||
|
||||
static uint64_t int128Bit(const timerprivInt128 *x, int i)
|
||||
{
|
||||
uint64_t which;
|
||||
|
||||
which = x->low;
|
||||
if (i >= 64) {
|
||||
i -= 64;
|
||||
which = x->high;
|
||||
}
|
||||
return (which >> i) & 1;
|
||||
}
|
||||
|
||||
static int int128UCmp(const timerprivInt128 *x, const timerprivInt128 *y)
|
||||
{
|
||||
if (x->high < y->high)
|
||||
return -1;
|
||||
if (x->high > y->high)
|
||||
return 1;
|
||||
if (x->low < y->low)
|
||||
return -1;
|
||||
if (x->low > y->low)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void int128BitSet(timerprivInt128 *x, int i)
|
||||
{
|
||||
uint64_t bit;
|
||||
|
||||
bit = 1;
|
||||
if (i >= 64) {
|
||||
i -= 64;
|
||||
bit <<= i;
|
||||
x->high |= bit;
|
||||
return;
|
||||
}
|
||||
bit <<= i;
|
||||
x->low |= bit;
|
||||
}
|
||||
|
||||
static void int128MulDiv64(timerprivInt128 *x, timerprivInt128 *y, timerprivInt128 *z, timerprivInt128 *quot)
|
||||
{
|
||||
bool finalNeg;
|
||||
uint64_t x64high, x64low;
|
||||
uint64_t y64high, y64low;
|
||||
timerprivInt128 add, numer, rem;
|
||||
int i;
|
||||
|
||||
finalNeg = false;
|
||||
if (x->neg)
|
||||
finalNeg = !finalNeg;
|
||||
if (y->neg)
|
||||
finalNeg = !finalNeg;
|
||||
if (z->neg)
|
||||
finalNeg = !finalNeg;
|
||||
quot->neg = finalNeg;
|
||||
// we now treat x, y, and z as unsigned
|
||||
|
||||
// first, multiply x and y into numer
|
||||
// this assumes x->high == y->high == 0
|
||||
numer.neg = false;
|
||||
// the idea is if x = (a * 2^32) + b and y = (c * 2^32) + d, we can express x * y as ((a * 2^32) + b) * ((c * 2^32) + d)...
|
||||
x64high = (x->low >> 32) & 0xFFFFFFFF;
|
||||
x64low = x->low & 0xFFFFFFFF;
|
||||
y64high = (y->low >> 32) & 0xFFFFFFFF;
|
||||
y64low = y->low & 0xFFFFFFFF;
|
||||
// and we can expand that out to get...
|
||||
numer.high = x64high * y64high; // a * c * 2^64 +
|
||||
numer.low = x64low * y64low; // b * d +
|
||||
add.neg = false;
|
||||
add.high = x64high * y64low; // a * d * 2^32 +
|
||||
add.low = (add.high & 0xFFFFFFFF) << 32;
|
||||
add.high >>= 32;
|
||||
int128UAdd(&numer, &add);
|
||||
add.high = x64low * y64high; // b * c * 2^32
|
||||
add.low = (add.high & 0xFFFFFFFF) << 32;
|
||||
add.high >>= 32;
|
||||
int128UAdd(&numer, &add);
|
||||
// I did type this all by hand, btw; the idea does come from Apple's implementation, though they explain it a bit more obtusely, and the odd behavior with anding high into low is to avoid looking like I directly copied their code which does the opposite
|
||||
|
||||
// and now long-divide
|
||||
// Apple's implementation uses Newton–Raphson division using doubles to store 1/z but I'd rather go with "slow but guaranteed to be accurate"
|
||||
// (Apple also rejects quotients > UINT64_MAX; we won't)
|
||||
quot->high = 0;
|
||||
quot->low = 0;
|
||||
rem.neg = false;
|
||||
rem.high = 0;
|
||||
rem.low = 0;
|
||||
for (i = 127; i >= 0; i--) {
|
||||
int128Lsh1(&rem);
|
||||
rem.low |= int128Bit(&numer, i);
|
||||
if (int128UCmp(&rem, z) >= 0) {
|
||||
int128USub(&rem, z);
|
||||
int128BitSet(quot, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void timerprivMulDivInt64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot)
|
||||
{
|
||||
timerprivInt128 a, b, c;
|
||||
|
||||
int128FromInt64(x, &a);
|
||||
int128FromInt64(y, &b);
|
||||
int128FromInt64(z, &c);
|
||||
int128MulDiv64(&a, &b, &c, quot);
|
||||
}
|
||||
|
||||
void timerprivMulDivUint64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot)
|
||||
{
|
||||
timerprivInt128 a, b, c;
|
||||
|
||||
int128FromUint64(x, &a);
|
||||
int128FromUint64(y, &b);
|
||||
int128FromUint64(z, &c);
|
||||
int128MulDiv64(&a, &b, &c, quot);
|
||||
}
|
||||
|
||||
int64_t timerprivInt128ToInt64(const timerprivInt128 *n, int64_t min, int64_t minCap, int64_t max, int64_t maxCap)
|
||||
{
|
||||
if (n->neg) {
|
||||
int64_t ret;
|
||||
|
||||
if (n->high > 0)
|
||||
return minCap;
|
||||
if (n->low > (uint64_t) INT64_MAX) {
|
||||
// we can't safely convert n->low to int64_t
|
||||
#if INT64_MIN == -INT64_MAX
|
||||
// in this case, we can't store -n->low in an int64_t at all!
|
||||
// therefore, it must be out of range
|
||||
return minCap;
|
||||
#else
|
||||
// in this case, INT64_MIN == -INT64_MAX - 1
|
||||
if (n->low > ((uint64_t) INT64_MAX) + 1)
|
||||
// we still can't store -n->low in an int64_t
|
||||
return minCap;
|
||||
// only one option left
|
||||
ret = INT64_MIN;
|
||||
#endif
|
||||
} else {
|
||||
// -n->low can safely be stored in an int64_t, so do so
|
||||
ret = (int64_t) (n->low);
|
||||
ret = -ret;
|
||||
}
|
||||
if (ret < min)
|
||||
return minCap;
|
||||
return ret;
|
||||
}
|
||||
if (n->high > 0)
|
||||
return maxCap;
|
||||
if (n->low > (uint64_t) max)
|
||||
return maxCap;
|
||||
return (int64_t) (n->low);
|
||||
}
|
||||
|
||||
uint64_t timerprivInt128ToUint64(const timerprivInt128 *n, uint64_t max, uint64_t maxCap)
|
||||
{
|
||||
if (n->neg)
|
||||
return 0;
|
||||
if (n->high != 0)
|
||||
return maxCap;
|
||||
if (n->low > maxCap)
|
||||
return maxCap;
|
||||
return n->low;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// 2 may 2019
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int64_t timerDuration;
|
||||
typedef int64_t timerTime;
|
||||
|
||||
#define timerTimeMax ((timerTime) INT64_MAX)
|
||||
#define timerDurationMin ((timerDuration) INT64_MIN)
|
||||
#define timerDurationMax ((timerDuration) INT64_MAX)
|
||||
|
||||
#define timerNanosecond ((timerDuration) 1)
|
||||
#define timerMicrosecond ((timerDuration) 1000)
|
||||
#define timerMillisecond ((timerDuration) 1000000)
|
||||
#define timerSecond ((timerDuration) 1000000000)
|
||||
|
||||
extern timerTime timerMonotonicNow(void);
|
||||
extern timerDuration timerTimeSub(timerTime end, timerTime start);
|
||||
|
||||
// The Go algorithm says 32 should be enough.
|
||||
// We use 33 to count the terminating NUL.
|
||||
#define timerDurationStringLen 33
|
||||
extern void timerDurationString(timerDuration d, char buf[timerDurationStringLen]);
|
||||
|
||||
typedef uint64_t timerSysError;
|
||||
#ifdef _WIN32
|
||||
#define timerSysErrorFmt "0x%08I32X"
|
||||
#define timerSysErrorFmtArg(x) ((uint32_t) x)
|
||||
#else
|
||||
#include <string.h>
|
||||
#define timerSysErrorFmt "%s (%d)"
|
||||
#define timerSysErrorFmtArg(x) strerror((int) x), ((int) x)
|
||||
#endif
|
||||
|
||||
extern timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, bool *timedOut);
|
||||
|
||||
extern timerSysError timerSleep(timerDuration d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,268 +0,0 @@
|
|||
// 3 may 2019
|
||||
// TODO pin down minimum POSIX versions (depends on what macOS 10.8 conforms to and what GLib/GTK+ require)
|
||||
// TODO also pin down which of these I should be defining, because apparently FreeBSD only checks for _XOPEN_SOURCE (and 2004 POSIX says that defining this as 600 *implies* _POSIX_C_SOURCE being 200112L so only _XOPEN_SOURCE is needed...)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
#include "timer.h"
|
||||
#include "timerpriv.h"
|
||||
|
||||
static void mustpthread_once(pthread_once_t *once, void (*init)(void))
|
||||
{
|
||||
int err;
|
||||
|
||||
err = pthread_once(once, init);
|
||||
if (err != 0) {
|
||||
fprintf(stderr, "*** internal error in timerMonotonicNow(): pthread_once() failed: %s (%d)\n", strerror(err), err);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
static uint64_t base;
|
||||
static mach_timebase_info_data_t mt;
|
||||
static pthread_once_t baseOnce = PTHREAD_ONCE_INIT;
|
||||
|
||||
static void baseInit(void)
|
||||
{
|
||||
kern_return_t err;
|
||||
|
||||
base = mach_absolute_time();
|
||||
err = mach_timebase_info(&mt);
|
||||
if (err != KERN_SUCCESS) {
|
||||
fprintf(stderr, "*** internal error in timerMonotonicNow(): mach_timebase_info() failed: kern_return_t %d\n", err);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
timerTime timerMonotonicNow(void)
|
||||
{
|
||||
uint64_t t;
|
||||
timerprivInt128 quot;
|
||||
|
||||
mustpthread_once(&baseOnce, baseInit);
|
||||
t = mach_absolute_time() - base;
|
||||
timerprivMulDivUint64(t, mt.numer, mt.denom, ");
|
||||
// on overflow, return the maximum possible timerTime; this is inspired by what Go does
|
||||
// the limit check will ensure we can safely cast the return value to a timerTime
|
||||
return (timerTime) timerprivInt128ToUint64(",
|
||||
(uint64_t) timerTimeMax, (uint64_t) timerTimeMax);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void mustclock_gettime(clockid_t id, struct timespec *ts)
|
||||
{
|
||||
int err;
|
||||
|
||||
errno = 0;
|
||||
if (clock_gettime(id, ts) != 0) {
|
||||
err = errno;
|
||||
fprintf(stderr, "*** internal error in timerMonotonicNow(): clock_gettime() failed: %s (%d)\n", strerror(err), err);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static struct timespec base;
|
||||
static pthread_once_t baseOnce = PTHREAD_ONCE_INIT;
|
||||
|
||||
static void baseInit(void)
|
||||
{
|
||||
mustclock_gettime(CLOCK_MONOTONIC, &base);
|
||||
}
|
||||
|
||||
timerTime timerMonotonicNow(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
timerTime ret;
|
||||
|
||||
mustpthread_once(&baseOnce, baseInit);
|
||||
mustclock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
ts.tv_sec -= base.tv_sec;
|
||||
ts.tv_nsec -= base.tv_nsec;
|
||||
if (ts.tv_nsec < 0) { // this is safe because POSIX requires this to be of type long
|
||||
ts.tv_sec--;
|
||||
ts.tv_nsec += timerSecond;
|
||||
}
|
||||
ret = ((timerTime) (ts.tv_sec)) * timerSecond;
|
||||
ret += (timerTime) (ts.tv_nsec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
timerDuration timerTimeSub(timerTime end, timerTime start)
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
|
||||
struct timeoutParams {
|
||||
jmp_buf retpos;
|
||||
struct itimerval prevDuration;
|
||||
struct sigaction prevSig;
|
||||
};
|
||||
|
||||
static struct timeoutParams p;
|
||||
|
||||
static void onTimeout(int sig, siginfo_t *info, void *ctx)
|
||||
{
|
||||
longjmp(p.retpos, 1);
|
||||
}
|
||||
|
||||
// POSIX doesn't have atomic operations :|
|
||||
// We could have special OS X-specific code that uses OSAtomic.h, but they have signedness type mismatches :| :|
|
||||
// There's also the GCC intrinsic atomic operations, but I cannot find a way to portably detect their presence :| :| :|
|
||||
// And pthread_mutex_lock() and pthread_mutex_unlock() CAN fail :| :| :| :|
|
||||
|
||||
static pthread_mutex_t nonReentranceMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static volatile uint32_t nonReentranceInCall = 0;
|
||||
|
||||
static void mustpthread_mutex_lock(pthread_mutex_t *mu)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = pthread_mutex_lock(mu);
|
||||
if (err != 0) {
|
||||
fprintf(stderr, "*** internal error in timerRunWithTimeout(): pthread_mutex_lock() failed: %s (%d)\n", strerror(err), err);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void mustpthread_mutex_unlock(pthread_mutex_t *mu)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = pthread_mutex_unlock(mu);
|
||||
if (err != 0) {
|
||||
fprintf(stderr, "*** internal error in timerRunWithTimeout(): pthread_mutex_unlock() failed: %s (%d)\n", strerror(err), err);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static int setupNonReentrance(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
mustpthread_mutex_lock(&nonReentranceMutex);
|
||||
err = 0;
|
||||
if (nonReentranceInCall)
|
||||
err = EALREADY;
|
||||
else
|
||||
nonReentranceInCall = 1;
|
||||
mustpthread_mutex_unlock(&nonReentranceMutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void teardownNonReentrance(void)
|
||||
{
|
||||
mustpthread_mutex_lock(&nonReentranceMutex);
|
||||
nonReentranceInCall = 0;
|
||||
mustpthread_mutex_unlock(&nonReentranceMutex);
|
||||
}
|
||||
|
||||
timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, bool *timedOut)
|
||||
{
|
||||
sigset_t sigalrm, allsigs;
|
||||
sigset_t prevMask;
|
||||
volatile bool restorePrevMask = false;
|
||||
struct sigaction sig;
|
||||
volatile bool restoreSignal = false;
|
||||
struct itimerval duration;
|
||||
volatile bool destroyTimer = false;
|
||||
int err = 0;
|
||||
|
||||
*timedOut = false;
|
||||
err = setupNonReentrance();
|
||||
if (err != 0)
|
||||
return (timerSysError) err;
|
||||
|
||||
memset(&p, 0, sizeof (struct timeoutParams));
|
||||
|
||||
errno = 0;
|
||||
if (sigemptyset(&sigalrm) != 0)
|
||||
return (timerSysError) errno;
|
||||
errno = 0;
|
||||
if (sigaddset(&sigalrm, SIGALRM) != 0)
|
||||
return (timerSysError) errno;
|
||||
errno = 0;
|
||||
if (sigfillset(&allsigs) != 0)
|
||||
return (timerSysError) errno;
|
||||
|
||||
err = pthread_sigmask(SIG_BLOCK, &sigalrm, &prevMask);
|
||||
if (err != 0)
|
||||
return (timerSysError) err;
|
||||
restorePrevMask = true;
|
||||
|
||||
if (setjmp(p.retpos) == 0) {
|
||||
sig.sa_mask = allsigs;
|
||||
sig.sa_flags = SA_SIGINFO;
|
||||
sig.sa_sigaction = onTimeout;
|
||||
errno = 0;
|
||||
if (sigaction(SIGALRM, &sig, &(p.prevSig)) != 0) {
|
||||
err = errno;
|
||||
goto out;
|
||||
}
|
||||
restoreSignal = true;
|
||||
|
||||
duration.it_interval.tv_sec = 0;
|
||||
duration.it_interval.tv_usec = 0;
|
||||
duration.it_value.tv_sec = d / timerSecond;
|
||||
duration.it_value.tv_usec = (d % timerSecond) / timerMicrosecond;
|
||||
errno = 0;
|
||||
if (setitimer(ITIMER_REAL, &duration, &(p.prevDuration)) != 0) {
|
||||
err = errno;
|
||||
goto out;
|
||||
}
|
||||
destroyTimer = true;
|
||||
|
||||
// and fire away
|
||||
err = pthread_sigmask(SIG_UNBLOCK, &sigalrm, NULL);
|
||||
if (err != 0)
|
||||
goto out;
|
||||
|
||||
(*f)(data);
|
||||
} else
|
||||
*timedOut = true;
|
||||
err = 0;
|
||||
|
||||
out:
|
||||
if (destroyTimer)
|
||||
setitimer(ITIMER_REAL, &(p.prevDuration), NULL);
|
||||
if (restoreSignal)
|
||||
sigaction(SIGALRM, &(p.prevSig), NULL);
|
||||
if (restorePrevMask)
|
||||
pthread_sigmask(SIG_SETMASK, &prevMask, NULL);
|
||||
teardownNonReentrance();
|
||||
return (timerSysError) err;
|
||||
}
|
||||
|
||||
timerSysError timerSleep(timerDuration d)
|
||||
{
|
||||
struct timespec duration, remaining;
|
||||
int err;
|
||||
|
||||
duration.tv_sec = d / timerSecond;
|
||||
duration.tv_nsec = d % timerSecond;
|
||||
for (;;) {
|
||||
errno = 0;
|
||||
if (nanosleep(&duration, &remaining) == 0)
|
||||
return 0;
|
||||
err = errno;
|
||||
if (err != EINTR)
|
||||
return (timerSysError) err;
|
||||
duration = remaining;
|
||||
}
|
||||
}
|
|
@ -1,432 +0,0 @@
|
|||
// 23 april 2019
|
||||
#define UNICODE
|
||||
#define _UNICODE
|
||||
#define STRICT
|
||||
#define STRICT_TYPED_ITEMIDS
|
||||
#define WINVER 0x0600
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#define _WIN32_WINDOWS 0x0600
|
||||
#define _WIN32_IE 0x0700
|
||||
#define NTDDI_VERSION 0x06000000
|
||||
#include <windows.h>
|
||||
#include <errno.h>
|
||||
#include <process.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "timer.h"
|
||||
#include "timerpriv.h"
|
||||
|
||||
// blah MinGW-w64
|
||||
#ifndef UI_E_ILLEGAL_REENTRANCY
|
||||
#define UI_E_ILLEGAL_REENTRANCY 0x802A0003
|
||||
#endif
|
||||
|
||||
static HRESULT lastErrorCodeToHRESULT(DWORD lastError)
|
||||
{
|
||||
if (lastError == 0)
|
||||
return E_FAIL;
|
||||
return HRESULT_FROM_WIN32(lastError);
|
||||
}
|
||||
|
||||
static HRESULT lastErrorToHRESULT(void)
|
||||
{
|
||||
return lastErrorCodeToHRESULT(GetLastError());
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrWaitForMultipleObjectsEx(DWORD n, const HANDLE *objects, BOOL waitAll, DWORD timeout, BOOL alertable, DWORD *result)
|
||||
{
|
||||
SetLastError(0);
|
||||
*result = WaitForMultipleObjectsEx(n, objects, waitAll, timeout, alertable);
|
||||
if (*result == WAIT_FAILED)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrSuspendThread(HANDLE thread)
|
||||
{
|
||||
DWORD ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = SuspendThread(thread);
|
||||
if (ret == (DWORD) (-1))
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrGetThreadContext(HANDLE thread, LPCONTEXT ctx)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = GetThreadContext(thread, ctx);
|
||||
if (ret == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrSetThreadContext(HANDLE thread, CONST CONTEXT *ctx)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = SetThreadContext(thread, ctx);
|
||||
if (ret == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrPostThreadMessageW(DWORD threadID, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = PostThreadMessageW(threadID, uMsg, wParam, lParam);
|
||||
if (ret == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrResumeThread(HANDLE thread)
|
||||
{
|
||||
DWORD ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = ResumeThread(thread);
|
||||
if (ret == (DWORD) (-1))
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrDuplicateHandle(HANDLE sourceProcess, HANDLE sourceHandle, HANDLE targetProcess, LPHANDLE targetHandle, DWORD access, BOOL inherit, DWORD options)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = DuplicateHandle(sourceProcess, sourceHandle,
|
||||
targetProcess, targetHandle,
|
||||
access, inherit, options);
|
||||
if (ret == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrCreateWaitableTimerW(LPSECURITY_ATTRIBUTES attributes, BOOL manualReset, LPCWSTR name, HANDLE *handle)
|
||||
{
|
||||
SetLastError(0);
|
||||
*handle = CreateWaitableTimerW(attributes, manualReset, name);
|
||||
if (*handle == NULL)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrCreateEventW(LPSECURITY_ATTRIBUTES attributes, BOOL manualReset, BOOL initialState, LPCWSTR name, HANDLE *handle)
|
||||
{
|
||||
SetLastError(0);
|
||||
*handle = CreateEventW(attributes, manualReset, initialState, name);
|
||||
if (*handle == NULL)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrSetWaitableTimer(HANDLE timer, const LARGE_INTEGER *duration, LONG period, PTIMERAPCROUTINE completionRoutine, LPVOID completionData, BOOL resume)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = SetWaitableTimer(timer, duration, period, completionRoutine, completionData, resume);
|
||||
if (ret == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT __cdecl hr_beginthreadex(void *security, unsigned stackSize, unsigned (__stdcall *threadProc)(void *arg), void *threadProcArg, unsigned flags, unsigned *thirdArg, uintptr_t *handle)
|
||||
{
|
||||
DWORD lastError;
|
||||
|
||||
// _doserrno is the equivalent of GetLastError(), or at least that's how _beginthreadex() uses it.
|
||||
_doserrno = 0;
|
||||
*handle = _beginthreadex(security, stackSize, threadProc, threadProcArg, flags, thirdArg);
|
||||
if (*handle == 0) {
|
||||
lastError = (DWORD) _doserrno;
|
||||
return lastErrorCodeToHRESULT(lastError);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrSetEvent(HANDLE event)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = SetEvent(event);
|
||||
if (ret == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI hrWaitForSingleObject(HANDLE handle, DWORD timeout)
|
||||
{
|
||||
DWORD ret;
|
||||
|
||||
SetLastError(0);
|
||||
ret = WaitForSingleObject(handle, timeout);
|
||||
if (ret == WAIT_FAILED)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
timerTime timerMonotonicNow(void)
|
||||
{
|
||||
LARGE_INTEGER qpc;
|
||||
|
||||
QueryPerformanceCounter(&qpc);
|
||||
return qpc.QuadPart;
|
||||
}
|
||||
|
||||
timerDuration timerTimeSub(timerTime end, timerTime start)
|
||||
{
|
||||
LARGE_INTEGER qpf;
|
||||
timerprivInt128 quot;
|
||||
|
||||
QueryPerformanceFrequency(&qpf);
|
||||
timerprivMulDivInt64(end - start, timerSecond, qpf.QuadPart, ");
|
||||
// on underflow/overflow, return the minimum/maximum possible timerDuration (respectively); this is based on what Go does
|
||||
return timerprivInt128ToInt64(",
|
||||
INT64_MIN, timerDurationMin,
|
||||
INT64_MAX, timerDurationMax);
|
||||
}
|
||||
|
||||
// note: the idea for the SetThreadContext() nuttery is from https://www.codeproject.com/Articles/71529/Exception-Injection-Throwing-an-Exception-in-Other
|
||||
|
||||
struct timeoutParams {
|
||||
jmp_buf retpos;
|
||||
HANDLE timer;
|
||||
HANDLE finished;
|
||||
HANDLE targetThread;
|
||||
DWORD targetThreadID;
|
||||
HRESULT hr;
|
||||
};
|
||||
|
||||
static DWORD timeoutParamsSlot;
|
||||
static HRESULT timeoutParamsHRESULT = S_OK;
|
||||
static INIT_ONCE timeoutParamsOnce = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
static void onTimeout(void)
|
||||
{
|
||||
struct timeoutParams *p;
|
||||
|
||||
p = (struct timeoutParams *) TlsGetValue(timeoutParamsSlot);
|
||||
longjmp(p->retpos, 1);
|
||||
}
|
||||
|
||||
static BOOL CALLBACK timeoutParamsSlotInit(PINIT_ONCE once, PVOID param, PVOID *ctx)
|
||||
{
|
||||
SetLastError(0);
|
||||
timeoutParamsSlot = TlsAlloc();
|
||||
if (timeoutParamsSlot == TLS_OUT_OF_INDEXES)
|
||||
timeoutParamsHRESULT = lastErrorToHRESULT();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static HRESULT setupNonReentrance(struct timeoutParams *p)
|
||||
{
|
||||
SetLastError(0);
|
||||
if (InitOnceExecuteOnce(&timeoutParamsOnce, timeoutParamsSlotInit, NULL, NULL) == 0)
|
||||
return lastErrorToHRESULT();
|
||||
if (timeoutParamsHRESULT != S_OK)
|
||||
return timeoutParamsHRESULT;
|
||||
if (TlsGetValue(timeoutParamsSlot) != NULL)
|
||||
return UI_E_ILLEGAL_REENTRANCY;
|
||||
SetLastError(0);
|
||||
if (TlsSetValue(timeoutParamsSlot, p) == 0)
|
||||
return lastErrorToHRESULT();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void teardownNonReentrance(void)
|
||||
{
|
||||
TlsSetValue(timeoutParamsSlot, NULL);
|
||||
}
|
||||
|
||||
static void redirectToOnTimeout(CONTEXT *ctx, struct timeoutParams *p)
|
||||
{
|
||||
#if defined(_AMD64_)
|
||||
ctx->Rip = (DWORD64) onTimeout;
|
||||
#elif defined(_ARM_)
|
||||
ctx->Pc = (DWORD) onTimeout;
|
||||
#elif defined(_ARM64_)
|
||||
ctx->Pc = (DWORD64) onTimeout;
|
||||
#elif defined(_X86_)
|
||||
ctx->Eip = (DWORD) onTimeout;
|
||||
#elif defined(_IA64_)
|
||||
// TODO verify that this is correct
|
||||
ctx->StIIP = (ULONGLONG) onTimeout;
|
||||
#else
|
||||
#error unknown CPU architecture; cannot create CONTEXT objects for CPU-specific Windows test code
|
||||
#endif
|
||||
}
|
||||
|
||||
static void criticalCallFailed(const char *func, HRESULT hr)
|
||||
{
|
||||
fprintf(stderr, "*** internal error in timerRunWithTimeout(): %s failed: 0x%08I32X\n", func, hr);
|
||||
abort();
|
||||
}
|
||||
|
||||
static unsigned __stdcall timerThreadProc(void *data)
|
||||
{
|
||||
struct timeoutParams *p = (struct timeoutParams *) data;
|
||||
HANDLE objects[2];
|
||||
CONTEXT ctx;
|
||||
DWORD which;
|
||||
HRESULT hr;
|
||||
|
||||
objects[0] = p->timer;
|
||||
objects[1] = p->finished;
|
||||
p->hr = hrWaitForMultipleObjectsEx(2, objects,
|
||||
FALSE, INFINITE, FALSE, &which);
|
||||
if (p->hr != S_OK)
|
||||
// act as if we timed out; the other thread will see the error
|
||||
which = WAIT_OBJECT_0;
|
||||
if (which == WAIT_OBJECT_0 + 1)
|
||||
// we succeeded; do nothing
|
||||
return 0;
|
||||
|
||||
// we timed out (or there was an error); signal it
|
||||
hr = hrSuspendThread(p->targetThread);
|
||||
if (hr != S_OK)
|
||||
criticalCallFailed("SuspendThread()", hr);
|
||||
ZeroMemory(&ctx, sizeof (CONTEXT));
|
||||
ctx.ContextFlags = CONTEXT_CONTROL;
|
||||
hr = hrGetThreadContext(p->targetThread, &ctx);
|
||||
if (hr != S_OK)
|
||||
criticalCallFailed("GetThreadContext()", hr);
|
||||
redirectToOnTimeout(&ctx, p);
|
||||
hr = hrSetThreadContext(p->targetThread, &ctx);
|
||||
if (hr != S_OK)
|
||||
criticalCallFailed("SetThreadContext()", hr);
|
||||
// and force the thread to return from GetMessage(), if we are indeed in that
|
||||
// and yes, this is the way to do it (https://devblogs.microsoft.com/oldnewthing/?p=16553, https://devblogs.microsoft.com/oldnewthing/20050405-46/?p=35973, https://devblogs.microsoft.com/oldnewthing/20080528-00/?p=22163)
|
||||
hr = hrPostThreadMessageW(p->targetThreadID, WM_NULL, 0, 0);
|
||||
if (hr != S_OK)
|
||||
criticalCallFailed("PostThreadMessageW()", hr);
|
||||
hr = hrResumeThread(p->targetThread);
|
||||
if (hr != S_OK)
|
||||
criticalCallFailed("ResumeThread()", hr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, bool *timedOut)
|
||||
{
|
||||
struct timeoutParams *p;
|
||||
bool doTeardownNonReentrance = false;
|
||||
MSG msg;
|
||||
volatile HANDLE timerThread = NULL;
|
||||
LARGE_INTEGER duration;
|
||||
HRESULT hr;
|
||||
|
||||
*timedOut = false;
|
||||
// we use a pointer to heap memory here to avoid volatile kludges
|
||||
p = (struct timeoutParams *) malloc(sizeof (struct timeoutParams));
|
||||
if (p == NULL)
|
||||
return (timerSysError) E_OUTOFMEMORY;
|
||||
ZeroMemory(p, sizeof (struct timeoutParams));
|
||||
|
||||
hr = setupNonReentrance(p);
|
||||
if (hr != S_OK)
|
||||
goto out;
|
||||
doTeardownNonReentrance = true;
|
||||
|
||||
// to ensure that the PostThreadMessage() above will not fail because the thread doesn't have a message queue
|
||||
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
|
||||
|
||||
hr = hrDuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
|
||||
GetCurrentProcess(), &(p->targetThread),
|
||||
0, FALSE, DUPLICATE_SAME_ACCESS);
|
||||
if (hr != S_OK) {
|
||||
p->targetThread = NULL;
|
||||
goto out;
|
||||
}
|
||||
p->targetThreadID = GetCurrentThreadId();
|
||||
|
||||
hr = hrCreateWaitableTimerW(NULL, TRUE, NULL, &(p->timer));
|
||||
if (hr != S_OK) {
|
||||
p->timer = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
hr = hrCreateEventW(NULL, TRUE, FALSE, NULL, &(p->finished));
|
||||
if (hr != S_OK) {
|
||||
p->finished = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (setjmp(p->retpos) == 0) {
|
||||
uintptr_t timerThreadValue = 0;
|
||||
|
||||
hr = hr_beginthreadex(NULL, 0, timerThreadProc, p, 0, NULL, &timerThreadValue);
|
||||
if (hr != S_OK) {
|
||||
timerThread = NULL;
|
||||
goto out;
|
||||
}
|
||||
timerThread = (HANDLE) timerThreadValue;
|
||||
|
||||
duration.QuadPart = d / 100;
|
||||
duration.QuadPart = -duration.QuadPart;
|
||||
hr = hrSetWaitableTimer(p->timer, &duration, 0, NULL, NULL, FALSE);
|
||||
if (hr != S_OK)
|
||||
goto out;
|
||||
|
||||
(*f)(data);
|
||||
} else if (p->hr != S_OK) {
|
||||
hr = p->hr;
|
||||
goto out;
|
||||
} else
|
||||
*timedOut = true;
|
||||
hr = S_OK;
|
||||
|
||||
out:
|
||||
if (timerThread != NULL) {
|
||||
HRESULT xhr; // don't overwrite hr below
|
||||
|
||||
// if either of these two fail, we cannot continue because the timer thread might interrupt us later, screwing everything up
|
||||
xhr = hrSetEvent(p->finished);
|
||||
if (xhr != S_OK)
|
||||
criticalCallFailed("SetEvent()", xhr);
|
||||
xhr = hrWaitForSingleObject(timerThread, INFINITE);
|
||||
if (xhr != S_OK)
|
||||
criticalCallFailed("WaitForSingleObject()", xhr);
|
||||
CloseHandle(timerThread);
|
||||
}
|
||||
if (p->finished != NULL)
|
||||
CloseHandle(p->finished);
|
||||
if (p->timer != NULL)
|
||||
CloseHandle(p->timer);
|
||||
if (p->targetThread != NULL)
|
||||
CloseHandle(p->targetThread);
|
||||
if (doTeardownNonReentrance)
|
||||
teardownNonReentrance();
|
||||
free(p);
|
||||
return (timerSysError) hr;
|
||||
}
|
||||
|
||||
timerSysError timerSleep(timerDuration d)
|
||||
{
|
||||
HANDLE timer;
|
||||
LARGE_INTEGER duration;
|
||||
HRESULT hr;
|
||||
|
||||
duration.QuadPart = d / 100;
|
||||
duration.QuadPart = -duration.QuadPart;
|
||||
hr = hrCreateWaitableTimerW(NULL, TRUE, NULL, &timer);
|
||||
if (hr != S_OK)
|
||||
return (timerSysError) hr;
|
||||
hr = hrSetWaitableTimer(timer, &duration, 0, NULL, NULL, FALSE);
|
||||
if (hr != S_OK) {
|
||||
CloseHandle(timer);
|
||||
return (timerSysError) hr;
|
||||
}
|
||||
hr = hrWaitForSingleObject(timer, INFINITE);
|
||||
CloseHandle(timer);
|
||||
return (timerSysError) hr;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// 5 may 2019
|
||||
|
||||
typedef struct timerprivInt128 timerprivInt128;
|
||||
|
||||
struct timerprivInt128 {
|
||||
bool neg;
|
||||
uint64_t high;
|
||||
uint64_t low;
|
||||
};
|
||||
|
||||
extern void timerprivMulDivInt64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot);
|
||||
extern void timerprivMulDivUint64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot);
|
||||
extern int64_t timerprivInt128ToInt64(const timerprivInt128 *n, int64_t min, int64_t minCap, int64_t max, int64_t maxCap);
|
||||
extern uint64_t timerprivInt128ToUint64(const timerprivInt128 *n, uint64_t max, uint64_t maxCap);
|
|
@ -1,66 +0,0 @@
|
|||
// 10 april 2019
|
||||
#include "test.h"
|
||||
|
||||
void timeoutMain(void *data)
|
||||
{
|
||||
uiMain();
|
||||
}
|
||||
|
||||
void deferFree(testingT *t, void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
void deferEventFree(testingT *t, void *data)
|
||||
{
|
||||
uiEventFree((uiEvent *) data);
|
||||
}
|
||||
|
||||
static void runSetORingResults(testingSet *set, const struct testingOptions *options, bool *anyRun, bool *anyFailed)
|
||||
{
|
||||
bool ar, af;
|
||||
|
||||
testingSetRun(set, options, &ar, &af);
|
||||
if (ar)
|
||||
*anyRun = true;
|
||||
if (af)
|
||||
*anyFailed = true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
testingOptions opts;
|
||||
bool anyRun = false, anyFailed = false;
|
||||
uiInitError err;
|
||||
|
||||
memset(&opts, 0, sizeof (testingOptions));
|
||||
if (argc == 2 && strcmp(argv[1], "-v") == 0)
|
||||
opts.Verbose = true;
|
||||
else if (argc != 1) {
|
||||
fprintf(stderr, "usage: %s [-v]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
runSetORingResults(beforeTests, &opts, &anyRun, &anyFailed);
|
||||
memset(&err, 0, sizeof (uiInitError));
|
||||
err.Size = sizeof (uiInitError);
|
||||
if (!uiInit(NULL, &err)) {
|
||||
fprintf(stderr, "uiInit() failed: %s; can't continue\n", err.Message);
|
||||
printf("FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
testControlType = uiRegisterControlType("TestControl", testVtable(), testOSVtable(), testImplDataSize());
|
||||
testControlType2 = uiRegisterControlType("TestControl2", testVtable(), testOSVtable(), testImplDataSize());
|
||||
|
||||
runSetORingResults(NULL, &opts, &anyRun, &anyFailed);
|
||||
|
||||
if (!anyRun)
|
||||
fprintf(stderr, "warning: no tests to run\n");
|
||||
if (anyFailed) {
|
||||
printf("FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
printf("PASS\n");
|
||||
fflush(stdout); // AddressSanitizer can chop the tail end of the output for whatever reason
|
||||
return 0;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
# 23 march 2019
|
||||
|
||||
libui_test_sources = [
|
||||
'controls.c',
|
||||
'controls_errors.cpp',
|
||||
'errors.c',
|
||||
'events.c',
|
||||
'events_errors.cpp',
|
||||
'initmain.c',
|
||||
'main.c',
|
||||
'noinitwrongthread.cpp',
|
||||
]
|
||||
|
||||
if libui_OS == 'windows'
|
||||
libui_test_sources += [
|
||||
'controls_windows.c',
|
||||
'controls_windows_errors.cpp',
|
||||
]
|
||||
elif libui_OS == 'darwin'
|
||||
libui_test_sources += [
|
||||
'controls_darwin.m',
|
||||
'controls_darwin_errors.m',
|
||||
]
|
||||
elif libui_OS == 'haiku'
|
||||
libui_test_sources += [
|
||||
'controls_haiku.c',
|
||||
'controls_haiku_errors.cpp',
|
||||
]
|
||||
else
|
||||
libui_test_sources += [
|
||||
'controls_unix.c',
|
||||
'controls_unix_errors.cpp',
|
||||
]
|
||||
endif
|
||||
|
||||
libui_test_cpp_extra_args = []
|
||||
if libui_OS == 'windows'
|
||||
libui_test_sources += [
|
||||
windows.compile_resources('resources_' + libui_mode + '.rc',
|
||||
args: libui_manifest_args,
|
||||
depend_files: ['test_' + libui_mode + '.manifest']),
|
||||
]
|
||||
elif libui_OS == 'darwin'
|
||||
# since we use a deployment target of 10.8, the non-C++11-compliant libstdc++ is chosen by default; we need C++11
|
||||
# see issue #302 for more details
|
||||
libui_test_cpp_extra_args += ['--stdlib=libc++']
|
||||
endif
|
||||
|
||||
libui_test_deps = []
|
||||
|
||||
subdir('lib')
|
||||
|
||||
# TODO once we upgrade to 0.49.0, add pie: true
|
||||
# TODO once we upgrade to 0.50.0, add protocol: 'exitcode'
|
||||
libui_tester = executable('tester', libui_test_sources,
|
||||
dependencies: libui_binary_deps + libui_test_deps,
|
||||
link_with: libui_libui,
|
||||
cpp_args: libui_test_cpp_extra_args,
|
||||
link_args: libui_test_cpp_extra_args,
|
||||
gui_app: false,
|
||||
install: false)
|
||||
test('test', libui_tester,
|
||||
is_parallel: false,
|
||||
should_fail: false)
|
|
@ -1,65 +0,0 @@
|
|||
// 28 april 2019
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../ui.h"
|
||||
#ifdef libuiOSHeader
|
||||
#include libuiOSHeader
|
||||
#endif
|
||||
#include "../common/testhooks.h"
|
||||
#include "lib/testing.h"
|
||||
#include "lib/thread.h"
|
||||
#include "lib/timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define diff(fmt) "\ngot " fmt "\nwant " fmt
|
||||
|
||||
// main.c
|
||||
extern void timeoutMain(void *data);
|
||||
#define timeout_uiMain(t, d) { \
|
||||
timerSysError err; \
|
||||
bool timedOut; \
|
||||
err = timerRunWithTimeout(d, timeoutMain, NULL, &timedOut); \
|
||||
if (err != 0) \
|
||||
testingTErrorf(t, "error running uiMain() in timeout: " timerSysErrorFmt, timerSysErrorFmtArg(err)); \
|
||||
if (timedOut) { \
|
||||
char timeoutstr[timerDurationStringLen]; \
|
||||
timerDurationString(d, timeoutstr); \
|
||||
testingTErrorf(t, "uiMain() timed out (%s)", timeoutstr); \
|
||||
} \
|
||||
}
|
||||
extern void deferFree(testingT *t, void *data);
|
||||
extern void deferEventFree(testingT *t, void *data);
|
||||
|
||||
// init.c
|
||||
extern testingSet *beforeTests;
|
||||
|
||||
// errors.c
|
||||
struct checkErrorCase {
|
||||
const char *name;
|
||||
void (*f)(void);
|
||||
const char *msgWant;
|
||||
};
|
||||
extern void checkProgrammerErrorsFull(testingT *t, const char *file, long line, const struct checkErrorCase *cases, bool inThread);
|
||||
#define checkProgrammerErrors(t, cases) checkProgrammerErrorsFull(t, __FILE__, __LINE__, cases, false)
|
||||
#define checkProgrammerErrorsInThread(t, cases) checkProgrammerErrorsFull(t, __FILE__, __LINE__, cases, true)
|
||||
|
||||
// controls.c
|
||||
extern void *testControlFailInit;
|
||||
extern const uiControlVtable *testVtable(void);
|
||||
extern const uiControlOSVtable *testOSVtable(void);
|
||||
extern size_t testImplDataSize(void);
|
||||
extern uint32_t testControlType;
|
||||
extern uint32_t testControlType2;
|
||||
|
||||
// controls_errors.cpp
|
||||
extern const struct checkErrorCase controlOSVtableCases[];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue