And started breaking apart the timer functions.

This commit is contained in:
Pietro Gagliardi 2019-05-02 12:38:59 -04:00
parent 5537e823ef
commit 42623f92e9
5 changed files with 248 additions and 210 deletions

View File

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

View File

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

129
test/timer.c Normal file
View File

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

View File

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

110
test/timer_windows.c Normal file
View File

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