Skip to content

Commit ca9d122

Browse files
nicejungleclaude
andcommitted
cli: make FPS test deterministic via tick_at(now)
The previous test used real wall-clock sleeps and asserted 40<fps<100, which was flaky on virtualized macOS GitHub runners (observed ~10 fps). Add a tick_at(Instant) entry point and rewrite the tests to drive the meter with synthetic timestamps. Also adds a window-slide test that the original sleep-based version could not reliably exercise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9cd2464 commit ca9d122

1 file changed

Lines changed: 28 additions & 11 deletions

File tree

crates/glyph8-cli/src/fps.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ impl FpsMeter {
1818
/// Record a tick (one rendered frame). Returns the current FPS
1919
/// estimate, or 0.0 if fewer than 2 ticks have been seen.
2020
pub fn tick(&mut self) -> f32 {
21-
let now = Instant::now();
21+
self.tick_at(Instant::now())
22+
}
23+
24+
/// Like [`tick`](Self::tick), but uses the supplied instant. Lets tests
25+
/// drive the meter with synthetic timestamps instead of wall-clock sleep.
26+
pub fn tick_at(&mut self, now: Instant) -> f32 {
2227
if self.window.len() == self.capacity {
2328
self.window.remove(0);
2429
}
@@ -37,7 +42,6 @@ impl FpsMeter {
3742
#[cfg(test)]
3843
mod tests {
3944
use super::*;
40-
use std::thread::sleep;
4145

4246
#[test]
4347
fn first_tick_returns_zero() {
@@ -46,16 +50,29 @@ mod tests {
4650
}
4751

4852
#[test]
49-
fn estimates_roughly_60fps_when_ticks_are_16ms_apart() {
53+
fn estimates_60fps_when_ticks_are_16_667ms_apart() {
5054
let mut m = FpsMeter::new(8);
51-
// 5 ticks at ~16 ms intervals should give ~60 fps.
52-
m.tick();
53-
for _ in 0..4 {
54-
sleep(Duration::from_millis(16));
55-
let _ = m.tick();
55+
let t0 = Instant::now();
56+
// 6 synthetic ticks exactly 1/60 s apart -> exactly 60 fps.
57+
let step = Duration::from_nanos(16_666_667);
58+
for i in 0..6 {
59+
let _ = m.tick_at(t0 + step * i);
60+
}
61+
let fps = m.tick_at(t0 + step * 6);
62+
assert!((fps - 60.0).abs() < 0.1, "expected ~60fps, got {fps}");
63+
}
64+
65+
#[test]
66+
fn window_slides_when_capacity_exceeded() {
67+
let mut m = FpsMeter::new(3);
68+
let t0 = Instant::now();
69+
let step = Duration::from_millis(10);
70+
// 5 ticks, capacity 3 -> only the last 3 timestamps survive,
71+
// so the FPS estimate reflects the recent rate (100 fps), not the average since t0.
72+
for i in 0..5 {
73+
let _ = m.tick_at(t0 + step * i);
5674
}
57-
let fps = m.tick();
58-
// Allow generous tolerance for sleep jitter, but it should be in the 50–80 range.
59-
assert!(fps > 40.0 && fps < 100.0, "expected ~60fps, got {}", fps);
75+
let fps = m.tick_at(t0 + step * 5);
76+
assert!((fps - 100.0).abs() < 0.5, "expected ~100fps, got {fps}");
6077
}
6178
}

0 commit comments

Comments
 (0)