diff --git a/test/testing.c b/test/testing.c index 803ff145..bd168b4e 100644 --- a/test/testing.c +++ b/test/testing.c @@ -3,6 +3,7 @@ #include #include #include +#include "timer.h" #include "testing.h" #include "testingpriv.h" @@ -161,17 +162,17 @@ static void testsetRun(struct testset *set, int *anyFailed) size_t i; testingT *t; const char *status; - testingTimer *timer; - char *timerstr; + timerTime start, end; + char timerstr[timeDurationStringLen]; t = set->tests; timer = testingNewTimer(); for (i = 0; i < set->len; i++) { printf("=== RUN %s\n", t->name); - testingTimerStart(timer); + start = timerMonotonicNow(); if (setjmp(t->returnNowBuf) == 0) (*(t->f))(t); - testingTimerEnd(timer); + end = timerMonotonicNow(); t->returned = 1; runDefers(t); status = "PASS"; @@ -181,9 +182,8 @@ static void testsetRun(struct testset *set, int *anyFailed) } else if (t->skipped) // note that failed overrides skipped status = "SKIP"; - timerstr = testingNsecString(testingTimerNsec(timer)); + timerDurationString(timerTimeSub(end, start), timerstr); printf("--- %s: %s (%s)\n", status, t->name, timerstr); - testingFreeNsecString(timerstr); t++; } testingFreeTimer(timer); @@ -277,135 +277,3 @@ void testingTDefer(testingT *t, void (*f)(testingT *t, void *data), void *data) d->next = t->defers; t->defers = d; } - -// 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 void fillFracPart(char *s, int precision, int *start, uint64_t *unsec) -{ - int i; - int print; - uint64_t digit; - - print = 0; - for (i = 0; i < precision; i++) { - digit = *unsec % 10; - print = print || (digit != 0); - if (print) { - s[*start - 1] = "0123456789"[digit]; - (*start)--; - } - *unsec /= 10; - } - if (print) { - s[*start - 1] = '.'; - (*start)--; - } -} - -static void fillIntPart(char *s, int *start, uint64_t unsec) -{ - if (unsec == 0) { - s[*start - 1] = '0'; - (*start)--; - return; - } - while (unsec != 0) { - s[*start - 1] = "0123456789"[unsec % 10]; - (*start)--; - unsec /= 10; - } -} - -char *testingNsecString(int64_t nsec) -{ - uint64_t unsec; - int neg; - char *s; - int start; - const struct timerStringPart *p; - - // The Go algorithm says 32 should be enough. - s = testingprivNewArray(char, 33); - start = 32; - - if (nsec == 0) { - s[0] = '0'; - s[1] = 's'; - return s; - } - unsec = (uint64_t) nsec; - neg = 0; - if (nsec < 0) { -#ifdef _MSC_VER -// TODO figure out a more explicit way to do this; until then, just go with what the standard says should happen, because it's what we want (TODO verify this) -#pragma warning(push) -#pragma warning(disable: 4146) -#endif - unsec = -unsec; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - neg = 1; - } - - for (p = parts; p->suffix != 0; p++) { - if (p->mode == modeMaxAndStop && unsec < p->maxOrMod) { - if (p->suffix2 != 0) { - s[start - 1] = p->suffix2; - start--; - } - s[start - 1] = p->suffix; - start--; - fillFracPart(s, p->precision, &start, &unsec); - fillIntPart(s, &start, unsec); - break; - } - if (p->mode == modeFracModContinue && unsec != 0) { - if (p->suffix2 != 0) { - s[start - 1] = p->suffix2; - start--; - } - s[start - 1] = p->suffix; - start--; - fillFracPart(s, p->precision, &start, &unsec); - fillIntPart(s, &start, unsec % p->maxOrMod); - unsec /= p->maxOrMod; - // and move on to the next one - } - } - - if (neg) { - s[start - 1] = '-'; - start--; - } - memmove(s, s + start, 33 - start); - return s; -} - -void testingFreeNsecString(char *s) -{ - testingprivFree(s); -} diff --git a/test/testing_windows.c b/test/testing_windows.c index d29a45d3..70e9f326 100644 --- a/test/testing_windows.c +++ b/test/testing_windows.c @@ -172,48 +172,6 @@ static HRESULT WINAPI hrWaitForSingleObject(HANDLE handle, DWORD timeout) return S_OK; } -struct testingTimer { - LARGE_INTEGER start; - LARGE_INTEGER end; -}; - -testingTimer *testingNewTimer(void) -{ - return testingprivNew(testingTimer); -} - -void testingFreeTimer(testingTimer *t) -{ - testingprivFree(t); -} - -void testingTimerStart(testingTimer *t) -{ - QueryPerformanceCounter(&(t->start)); -} - -void testingTimerEnd(testingTimer *t) -{ - QueryPerformanceCounter(&(t->end)); -} - -int64_t testingTimerNsec(testingTimer *t) -{ - LARGE_INTEGER qpf; - int64_t qpnsQuot, qpnsRem; - int64_t c; - int64_t ret; - - QueryPerformanceFrequency(&qpf); - qpnsQuot = testingNsecPerSec / qpf.QuadPart; - qpnsRem = testingNsecPerSec % qpf.QuadPart; - c = t->end.QuadPart - t->start.QuadPart; - - ret = c * qpnsQuot; - ret += (c * qpnsRem) / qpf.QuadPart; - return ret; -} - // note: the idea for the SetThreadContext() nuttery is from https://www.codeproject.com/Articles/71529/Exception-Injection-Throwing-an-Exception-in-Other static jmp_buf timeout_ret; @@ -398,32 +356,6 @@ out: testingTFailNow(t); } -void testingSleep(int64_t nsec) -{ - HANDLE timer; - LARGE_INTEGER duration; - HRESULT hr; - - duration.QuadPart = nsec / 100; - duration.QuadPart = -duration.QuadPart; - hr = hrCreateWaitableTimerW(NULL, TRUE, NULL, &timer); - if (hr != S_OK) - goto fallback; - hr = hrSetWaitableTimer(timer, &duration, 0, NULL, NULL, FALSE); - if (hr != S_OK) { - CloseHandle(timer); - goto fallback; - } - hr = hrWaitForSingleObject(timer, INFINITE); - CloseHandle(timer); - if (hr == S_OK) - return; - -fallback: - // this has lower resolution, but we can't detect a failure, so use it as a fallback - Sleep((DWORD) (nsec / testingNsecPerMsec)); -} - struct testingThread { uintptr_t handle; void (*f)(void *data); diff --git a/test/timer.c b/test/timer.c new file mode 100644 index 00000000..d9dc32bf --- /dev/null +++ b/test/timer.c @@ -0,0 +1,129 @@ +// 2 may 2019 +#include +#include "timer.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; + int print; + uint64_t digit; + + print = 0; + 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; + int neg; + int start; + const struct timerStringPart *p; + + memset(buf, 0, timerTimeStringLen * sizeof (char)); + start = 32; + + if (d == 0) { + buf[0] = '0'; + buf[1] = 's'; + return; + } + unsec = (uint64_t) d; + neg = 0; + if (d < 0) { +#ifdef _MSC_VER +// TODO figure out a more explicit way to do this; until then, just go with what the standard says should happen, because it's what we want (TODO verify this) +#pragma warning(push) +#pragma warning(disable: 4146) +#endif + unsec = -unsec; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + neg = 1; + } + + 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); +} diff --git a/test/timer.h b/test/timer.h index 2aa03e2a..fe387b52 100644 --- a/test/timer.h +++ b/test/timer.h @@ -11,13 +11,12 @@ typedef int64_t timerTime; #define timerSecond ((Duration) 1000000000) extern timerTime timerMonotonicNow(void); -extern timerDuration timerTimeSub(timerTime start, timerTime end); +extern timerDuration timerTimeSub(timerTime end, timerTime start); // The Go algorithm says 32 should be enough. // We use 33 to count the terminating NUL. -#define timerTimeStringLen 33 - -extern void timerDurationString(timerDuration d, char buf[timerTimeStringLen]); +#define timerDurationStringLen 33 +extern void timerDurationString(timerDuration d, char buf[timerDurationStringLen]); typedef uint64_t timerSysError; #ifdef _WIN32 diff --git a/test/timer_windows.c b/test/timer_windows.c new file mode 100644 index 00000000..d9c0e92c --- /dev/null +++ b/test/timer_windows.c @@ -0,0 +1,110 @@ +// 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 +#include "timer.h" + +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 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 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 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; + timerDuration qpnsQuot, qpnsRem; + timerTime c; + timerDuration ret; + + QueryPerformanceFrequency(&qpf); + qpnsQuot = timerSecond / qpf.QuadPart; + qpnsRem = timerSecond % qpf.QuadPart; + c = end - start; + + ret = ((timerDuration) c) * qpnsQuot; + ret += (c * qpnsRem) / qpf.QuadPart; + return ret; +} + +timerSysError timerSleep(timerDuration nsec) +{ + HANDLE timer; + LARGE_INTEGER duration; + HRESULT hr; + + duration.QuadPart = nsec / 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; +}