aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2023-05-22 14:55:41 -0400
committerDavid Robillard <d@drobilla.net>2023-05-22 15:12:36 -0400
commit0fac728dc65385af2c8e0b7a0fdc373e3c550ca8 (patch)
tree8f4eff5164bdde8e8859ee3e0c0eb8ebe04781db
parent6b31fb5a4bfd634f5af6ee30e637d81101d1d62b (diff)
downloadpugl-0fac728dc65385af2c8e0b7a0fdc373e3c550ca8.tar.gz
pugl-0fac728dc65385af2c8e0b7a0fdc373e3c550ca8.tar.bz2
pugl-0fac728dc65385af2c8e0b7a0fdc373e3c550ca8.zip
Test that multiple timers with different frequencies work correctly
-rw-r--r--test/test_timer.c141
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);