And started breaking apart the timer functions.
This commit is contained in:
parent
5537e823ef
commit
42623f92e9
144
test/testing.c
144
test/testing.c
|
@ -3,6 +3,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <setjmp.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
// 2 may 2019
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 <windows.h>
|
||||
#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;
|
||||
}
|
Loading…
Reference in New Issue