109 lines
3.7 KiB
Rust
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);
|
|
}
|