diff options
-rw-r--r-- | test/test_timer.c | 141 |
1 files changed, 100 insertions, 41 deletions
diff --git a/test/test_timer.c b/test/test_timer.c index d8cc5c3..262322f 100644 --- a/test/test_timer.c +++ b/test/test_timer.c @@ -16,10 +16,11 @@ #include <assert.h> #include <math.h> #include <stdbool.h> -#include <stddef.h> #include <stdint.h> #include <stdio.h> +#define NUM_TIMERS 4U // NOLINT(modernize-macro-to-enum) + #ifdef __APPLE__ static const double timeout = 1 / 60.0; #else @@ -29,37 +30,53 @@ static const double timeout = -1.0; // Windows SetTimer has a maximum resolution of 10ms static const double tolerance = 0.014; -static const uintptr_t timerId = 1U; -static const double timerPeriod = 1 / 60.0; - typedef enum { START, EXPOSED, } State; typedef struct { + size_t numAlarms; + double firstAlarmTime; + double lastAlarmTime; +} TimerStats; + +typedef struct { PuglWorld* world; PuglView* view; PuglTestOptions opts; - size_t numAlarms; - double firstAlarmTime; - double lastAlarmTime; + TimerStats stats[NUM_TIMERS]; State state; } PuglTest; +static double +timerPeriod(const uintptr_t timerId) +{ + return 1.0 / (60.0 / ((double)timerId + 1.0)); +} + +static void +resetStats(PuglTest* const test) +{ + for (unsigned i = 0U; i < NUM_TIMERS; ++i) { + test->stats[i].numAlarms = 0; + test->stats[i].firstAlarmTime = 0.0; + test->stats[i].lastAlarmTime = 0.0; + } +} + static void onTimer(PuglView* const view, const PuglTimerEvent* const event) { - PuglTest* const test = (PuglTest*)puglGetHandle(view); - const double time = puglGetTime(puglGetWorld(view)); + assert(event->id < NUM_TIMERS); - assert(event->id == timerId); + PuglTest* const test = (PuglTest*)puglGetHandle(view); + TimerStats* const stats = &test->stats[event->id]; - if (test->numAlarms++ == 0) { - test->firstAlarmTime = time; + stats->lastAlarmTime = puglGetTime(puglGetWorld(view)); + if (++stats->numAlarms == 1U) { + stats->firstAlarmTime = stats->lastAlarmTime; } - - test->lastAlarmTime = time; } static PuglStatus @@ -93,15 +110,43 @@ roundPeriod(const double period) return floor(period * 1000.0) / 1000.0; // Round down to milliseconds } +static double +averagePeriod(const TimerStats* const stats) +{ + if (stats->numAlarms < 2U) { + return (double)INFINITY; + } + + const double duration = stats->lastAlarmTime - stats->firstAlarmTime; + + return roundPeriod(duration / (double)(stats->numAlarms - 1U)); +} + +static void +checkTimerPeriod(const TimerStats* const stats, const double expectedPeriod) +{ + const double expected = roundPeriod(expectedPeriod); + const double actual = averagePeriod(stats); + const double difference = fabs(actual - expected); + + if (difference > tolerance) { + fprintf(stderr, "error: Period not within %f of %f\n", tolerance, expected); + fprintf(stderr, "note: Actual period %f\n", actual); + } + + assert(difference <= tolerance); +} + int main(int argc, char** argv) { + const double updateDuration = timerPeriod(NUM_TIMERS - 1U) * 10.0; + const double otherTimerPeriod = timerPeriod(0) * 3.0; + PuglTest test = {puglNewWorld(PUGL_PROGRAM, 0), NULL, puglParseTestOptions(&argc, &argv), - 0, - 0.0, - 0.0, + {{0, 0.0, 0.0}, {0, 0.0, 0.0}, {0, 0.0, 0.0}}, START}; // Set up view @@ -121,36 +166,50 @@ main(int argc, char** argv) assert(!puglUpdate(test.world, timeout)); } - // Register a timer with a longer period first - assert(!puglStartTimer(test.view, timerId, timerPeriod * 2.0)); - - // Replace it with the one we want (to ensure timers are replaced) - assert(!puglStartTimer(test.view, timerId, timerPeriod)); - - puglUpdate(test.world, timerPeriod * 30.0); - assert(test.numAlarms > 0); + // Register timers with different periods + for (unsigned i = 0U; i < NUM_TIMERS; ++i) { + assert(!puglStartTimer(test.view, i, timerPeriod(i))); + } - // Calculate the actual period of the timer - const double duration = test.lastAlarmTime - test.firstAlarmTime; - const double expected = roundPeriod(timerPeriod); - const double actual = roundPeriod(duration / (double)(test.numAlarms - 1)); - const double difference = fabs(actual - expected); + // Check that stopping an unknown timer fails gracefully + assert(!!puglStopTimer(test.view, NUM_TIMERS)); - if (difference > tolerance) { - fprintf(stderr, "error: Period not within %f of %f\n", tolerance, expected); - fprintf(stderr, "note: Actual period %f\n", actual); + // Run and check that each timer fired and has the expected period + assert(!puglUpdate(test.world, updateDuration)); + for (unsigned i = 0U; i < NUM_TIMERS; ++i) { + checkTimerPeriod(&test.stats[i], timerPeriod(i)); } - assert(difference <= tolerance); + // Stop the first timer and change the frequency of the last + resetStats(&test); + assert(!puglStopTimer(test.view, 0U)); + assert(!puglStartTimer(test.view, NUM_TIMERS - 1U, otherTimerPeriod)); + assert(!puglUpdate(test.world, updateDuration)); + assert(test.stats[0].numAlarms == 0); + checkTimerPeriod(&test.stats[NUM_TIMERS - 1U], otherTimerPeriod); + for (unsigned i = 1U; i < NUM_TIMERS - 1U; ++i) { + checkTimerPeriod(&test.stats[i], timerPeriod(i)); + } - // Deregister timer and tick once to synchronize - assert(!puglStopTimer(test.view, timerId)); - puglUpdate(test.world, 0.0); + // Stop the last timer + resetStats(&test); + assert(!puglStopTimer(test.view, NUM_TIMERS - 1U)); + assert(!puglUpdate(test.world, updateDuration)); + assert(test.stats[0].numAlarms == 0); + assert(test.stats[NUM_TIMERS - 1U].numAlarms == 0); + for (unsigned i = 1U; i < NUM_TIMERS - 1U; ++i) { + checkTimerPeriod(&test.stats[i], timerPeriod(i)); + } - // Update for a half second and check that we receive no more alarms - test.numAlarms = 0; - puglUpdate(test.world, 0.5); - assert(test.numAlarms == 0); + // Stop all other timers + resetStats(&test); + for (unsigned i = 1U; i < NUM_TIMERS - 1U; ++i) { + assert(!puglStopTimer(test.view, i)); + } + assert(!puglUpdate(test.world, timerPeriod(NUM_TIMERS - 1))); + for (unsigned i = 0U; i < NUM_TIMERS; ++i) { + assert(test.stats[0].numAlarms == 0); + } puglFreeView(test.view); puglFreeWorld(test.world); |