grok-build-upstream-mirror/crates/common/xai-circuit-breaker/src/window_tests.rs
grokkybara[bot] c68e39f604 Publish harness and TUI open-source
initial sync from the monorepo
2026-07-16 06:46:02 +01:00

109 lines
3.7 KiB
Rust

//! Tests for [`crate::window::SlidingWindow`].
use super::*;
#[test]
fn push_evict_and_error_rate() {
let mut w = SlidingWindow::new();
let base = Instant::now();
w.push(true, base);
w.push(false, base + Duration::from_millis(10));
w.push(true, base + Duration::from_millis(20));
assert_eq!(w.sample_count(), 3);
assert!((w.error_rate() - (2.0 / 3.0)).abs() < 1e-9);
// Evict everything older than 5ms relative to base + 20ms.
w.evict(Duration::from_millis(5), base + Duration::from_millis(20));
assert_eq!(w.sample_count(), 1);
}
#[test]
fn empty_error_rate_is_zero() {
let w = SlidingWindow::new();
assert_eq!(w.error_rate(), 0.0);
}
#[test]
fn push_respects_max_entries_cap() {
let mut w = SlidingWindow::new();
let base = Instant::now();
for i in 0..(MAX_WINDOW_ENTRIES + 5) {
w.push(true, base + Duration::from_nanos(i as u64));
}
assert_eq!(w.sample_count(), MAX_WINDOW_ENTRIES);
}
#[test]
fn failure_count_stays_consistent_under_cap_eviction() {
// Push enough failures to overflow the cap and confirm
// error_rate() (which is O(1) via the cached failures
// counter) still reads 1.0 after entries are dropped from
// the front.
let mut w = SlidingWindow::new();
let base = Instant::now();
for i in 0..(MAX_WINDOW_ENTRIES + 100) {
w.push(true, base + Duration::from_nanos(i as u64));
}
assert_eq!(w.sample_count(), MAX_WINDOW_ENTRIES);
assert!((w.error_rate() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn failure_count_decrements_on_time_eviction() {
let mut w = SlidingWindow::new();
let base = Instant::now();
w.push(true, base);
w.push(false, base + Duration::from_millis(10));
w.push(true, base + Duration::from_millis(20));
assert!((w.error_rate() - (2.0 / 3.0)).abs() < 1e-9);
// Evict the first two entries (the leading true and false).
// Remaining is one true → error_rate = 1.0.
w.evict(Duration::from_millis(5), base + Duration::from_millis(20));
assert_eq!(w.sample_count(), 1);
assert!((w.error_rate() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn clear_resets_failure_count() {
let mut w = SlidingWindow::new();
let base = Instant::now();
w.push(true, base);
w.push(true, base + Duration::from_millis(1));
w.clear();
// After clear, pushing one success must read error_rate 0.0;
// a stale failures counter would read 2/1 instead.
w.push(false, base + Duration::from_millis(2));
assert!(w.error_rate().abs() < f64::EPSILON);
}
/// Push past the cap, then advance time past the window duration and
/// push more — eviction must continue to read the correct cached
/// failures count even when the deque is at the cap.
#[test]
fn cap_then_time_eviction_keeps_failure_count_consistent() {
let mut w = SlidingWindow::new();
let base = Instant::now();
// Fill the deque to the cap with failures.
for i in 0..MAX_WINDOW_ENTRIES {
w.push(true, base + Duration::from_micros(i as u64));
}
assert_eq!(w.sample_count(), MAX_WINDOW_ENTRIES);
assert!((w.error_rate() - 1.0).abs() < f64::EPSILON);
// Move past the window and evict — every existing sample falls
// out, cached failures counter must reach zero.
let way_later = base + Duration::from_secs(3600);
w.evict(Duration::from_secs(1), way_later);
assert_eq!(w.sample_count(), 0);
assert!(w.error_rate().abs() < f64::EPSILON);
// New samples after a full eviction must continue to read
// consistently (regression on a stale `failures` field).
w.push(false, way_later);
w.push(true, way_later + Duration::from_micros(1));
assert_eq!(w.sample_count(), 2);
assert!((w.error_rate() - 0.5).abs() < f64::EPSILON);
}