And removed zNEW_test now that everything's integrated back in.

This commit is contained in:
Pietro Gagliardi 2020-05-12 01:47:14 -04:00
parent 1cf87d5c5e
commit de35499976
13 changed files with 0 additions and 2047 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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 NewtonRaphson 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;
}

View File

@ -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

View File

@ -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, &quot);
// 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(&quot,
(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;
}
}

View File

@ -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, &quot);
// on underflow/overflow, return the minimum/maximum possible timerDuration (respectively); this is based on what Go does
return timerprivInt128ToInt64(&quot,
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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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)

View File

@ -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