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