use crate::tui::app::StreamingTpsTracker;
use std::time::{Duration, Instant};
#[test]
fn fresh_tracker_has_zero_active_time() {
let t = StreamingTpsTracker::default();
let now = Instant::now();
assert_eq!(t.active_secs_now(now), 0.0);
assert_eq!(t.last_tps, None);
}
#[test]
fn first_advance_opens_window_with_zero_elapsed() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
assert_eq!(
t.active_secs_now(t0),
0.0,
"fresh window with no prior token has zero active time"
);
}
#[test]
fn consecutive_advances_within_threshold_extend_same_window() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + Duration::from_millis(50));
let t2 = t0 + Duration::from_millis(100);
t.advance(t2);
assert!(
(t.active_secs_now(t2) - 0.1).abs() < 1e-6,
"expected 100ms active window, got {:.6}s",
t.active_secs_now(t2)
);
}
#[test]
fn gap_longer_than_threshold_closes_window_and_opens_new_one() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + Duration::from_millis(50)); let after_gap = t0 + Duration::from_millis(50 + 1100); t.advance(after_gap);
let total = t.active_secs_now(after_gap);
assert!(
(total - 0.05).abs() < 1e-6,
"idle gap must be excluded; got total active {total:.6}s (would be ~1.15s if regression)"
);
}
#[test]
fn three_windows_separated_by_gaps_each_count() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + Duration::from_millis(30));
let w2_start = t0 + Duration::from_millis(1530);
t.advance(w2_start);
t.advance(w2_start + Duration::from_millis(50));
let w3_start = w2_start + Duration::from_millis(50 + 2000);
t.advance(w3_start);
let w3_end = w3_start + Duration::from_millis(70);
t.advance(w3_end);
let total = t.active_secs_now(w3_end);
let expected = 0.030 + 0.050 + 0.070; assert!(
(total - expected).abs() < 1e-6,
"three windows must sum: expected {expected:.6}s, got {total:.6}s"
);
}
#[test]
fn finalize_stashes_rate_and_resets_accumulator() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + Duration::from_millis(100)); t.finalize(100, None);
let tps = t.last_tps.expect("finalize must stash a rate");
assert!(
(tps - 1000.0).abs() < 1e-3,
"100 tokens / 0.1s = 1000 tok/s, got {tps:.3}"
);
assert_eq!(t.active_secs_now(t0 + Duration::from_millis(200)), 0.0);
}
#[test]
fn finalize_with_zero_tokens_does_not_clobber_previous_last_tps() {
let mut t = StreamingTpsTracker::default();
t.last_tps = Some(42.0);
let t0 = Instant::now();
t.advance(t0);
t.finalize(0, None);
assert_eq!(
t.last_tps,
Some(42.0),
"empty turn must not clobber prior persisted rate"
);
}
#[test]
fn finalize_with_zero_active_secs_does_not_clobber_previous_last_tps() {
let mut t = StreamingTpsTracker::default();
t.last_tps = Some(42.0);
let t0 = Instant::now();
t.advance(t0);
t.finalize(50, None);
assert_eq!(t.last_tps, Some(42.0));
}
#[test]
fn finalize_uses_authoritative_tps_when_provided() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + std::time::Duration::from_millis(100));
t.finalize(100, Some(73.0));
assert_eq!(t.last_tps, Some(73.0));
}
#[test]
fn finalize_falls_back_to_local_when_authoritative_is_none() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + std::time::Duration::from_millis(100));
t.finalize(100, None);
let tps = t
.last_tps
.expect("local estimate must apply when authoritative is None");
assert!((tps - 1000.0).abs() < 1e-3);
}
#[test]
fn finalize_ignores_non_finite_authoritative() {
let mut t = StreamingTpsTracker::default();
t.last_tps = Some(42.0);
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + std::time::Duration::from_millis(100));
t.finalize(100, Some(f64::NAN));
let tps = t
.last_tps
.expect("NaN authoritative must fall back to local, not blank");
assert!(
(tps - 1000.0).abs() < 1e-3,
"got {tps:?}, expected ~1000 from local fallback"
);
}
#[test]
fn idle_ticks_past_last_token_dont_inflate_in_flight_window() {
let mut t = StreamingTpsTracker::default();
let t0 = Instant::now();
t.advance(t0);
t.advance(t0 + Duration::from_millis(40));
let queried_at = t0 + Duration::from_millis(540);
assert!(
(t.active_secs_now(queried_at) - 0.040).abs() < 1e-6,
"active time must equal last-window span, not extend to now"
);
}