diff --git a/test/meson.build b/test/meson.build index 9d55e675..e2502dd2 100644 --- a/test/meson.build +++ b/test/meson.build @@ -6,6 +6,10 @@ libui_test_sources = [ 'testing.c', ] +if libui_OS == 'darwin' + libui_test_sources += ['testing_darwin.c'] +endif + if libui_OS == 'windows' libui_test_manifest = 'test.manifest' if libui_mode == 'static' diff --git a/test/testing.c b/test/testing.c index 5c5df531..7dc56f10 100644 --- a/test/testing.c +++ b/test/testing.c @@ -128,12 +128,17 @@ static void testsetRun(struct testset *set, int *anyFailed) size_t i; testingT *t; const char *status; + testingTimer *timer; + char *timerstr; t = set->tests; + timer = testingNewTimer(); for (i = 0; i < set->len; i++) { printf("=== RUN %s\n", t->name); + testingTimerStart(timer); if (setjmp(t->returnNowBuf) == 0) (*(t->f))(t); + testingTimerEnd(timer); t->returned = 1; runDefers(t); status = "PASS"; @@ -143,9 +148,12 @@ static void testsetRun(struct testset *set, int *anyFailed) } else if (t->skipped) // note that failed overrides skipped status = "SKIP"; - printf("--- %s: %s (%s)\n", status, t->name, "TODO"); + timerstr = testingTimerString(timer); + printf("--- %s: %s (%s)\n", status, t->name, timerstr); + testingFreeTimerString(timerstr); t++; } + testingFreeTimer(timer); } int testingMain(void) @@ -236,3 +244,131 @@ 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 *testingTimerString(testingTimer *t) +{ + 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 = (char *) malloc(33 * sizeof (char)); + // TODO handle failure + memset(s, 0, 33 * sizeof (char)); + start = 32; + + nsec = testingTimerNsec(t); + if (nsec == 0) { + s[0] = '0'; + s[1] = 's'; + return s; + } + unsec = (uint64_t) nsec; + neg = 0; + if (nsec < 0) { + unsec = -unsec; + 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 testingFreeTimerString(char *s) +{ + free(s); +} diff --git a/test/testing.h b/test/testing.h index df7bb77c..b5f7737d 100644 --- a/test/testing.h +++ b/test/testing.h @@ -1,6 +1,7 @@ // 27 february 2018 #include +#include #define testingprivImplName(basename) testingprivImpl ## basename @@ -63,6 +64,18 @@ extern void testingTFailNow(testingT *t); extern void testingTSkipNow(testingT *t); extern void testingTDefer(testingT *t, void (*f)(testingT *t, void *data), void *data); +typedef struct testingTimer testingTimer; + +#define testingTimerNsecPerSec ((int64_t) 1000000000) + +extern testingTimer *testingNewTimer(void); +extern void testingFreeTimer(testingTimer *t); +extern void testingTimerStart(testingTimer *t); +extern void testingTimerEnd(testingTimer *t); +extern int64_t testingTimerNsec(testingTimer *t); +extern char *testingTimerString(testingTimer *t); +extern void testingFreeTimerString(char *s); + extern void testingprivRegisterTest(const char *, void (*)(testingT *), const char *, long); extern void testingprivRegisterTestBefore(const char *, void (*)(testingT *), const char *, long); extern void testingprivRegisterTestAfter(const char *, void (*)(testingT *), const char *, long); diff --git a/test/testing_darwin.c b/test/testing_darwin.c new file mode 100644 index 00000000..462c5ea5 --- /dev/null +++ b/test/testing_darwin.c @@ -0,0 +1,47 @@ +// 22 april 2019 +#include +#include +#include +#include +#include "testing.h" + +struct testingTimer { + uint64_t start; + uint64_t end; +}; + +testingTimer *testingNewTimer(void) +{ + testingTimer *t; + + t = (testingTimer *) malloc(sizeof (testingTimer)); + // TODO handle failure + memset(t, 0, sizeof (testingTimer)); + return t; +} + +void testingFreeTimer(testingTimer *t) +{ + free(t); +} + +void testingTimerStart(testingTimer *t) +{ + t->start = mach_absolute_time(); +} + +void testingTimerEnd(testingTimer *t) +{ + t->end = mach_absolute_time(); +} + +int64_t testingTimerNsec(testingTimer *t) +{ + mach_timebase_info_data_t mt; + uint64_t c; + + mach_timebase_info(&mt); + c = t->end - t->start; + c = c * mt.numer / mt.denom; + return (int64_t) c; +}