use super::*;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::thread;
#[test]
fn empty_drain_is_zero() {
let rec = DualRecorder::new(3);
let snap = rec.get_interval_histogram();
assert_eq!(snap.count(), 0);
assert_eq!(snap.value_at_percentile(0.99), 0);
}
#[test]
fn drain_returns_records_since_last_rotate() {
let rec = DualRecorder::new(3);
for i in 1..=100 {
rec.record(i);
}
let snap = rec.get_interval_histogram();
assert_eq!(snap.count(), 100);
let empty = rec.get_interval_histogram();
assert_eq!(empty.count(), 0);
}
#[test]
fn rotation_swaps_active_side() {
let rec = DualRecorder::new(3);
let first = rec.active_index();
rec.record(10);
let _ = rec.get_interval_histogram();
let second = rec.active_index();
assert_ne!(first, second, "active side flipped");
}
#[test]
fn records_after_rotate_go_to_new_side() {
let rec = DualRecorder::new(3);
for i in 1..=50 {
rec.record(i);
}
let first = rec.get_interval_histogram();
for i in 1..=10 {
rec.record(i * 100);
}
let second = rec.get_interval_histogram();
assert_eq!(first.count(), 50);
assert_eq!(second.count(), 10);
assert!(second.max() >= 1000);
}
#[test]
fn concurrent_writers_with_periodic_drain() {
let rec = Arc::new(DualRecorder::new(3));
let stop = Arc::new(AtomicBool::new(false));
let producers = 6;
let per_producer = 20_000;
let mut handles = vec![];
for t in 0..producers {
let rec = rec.clone();
let stop = stop.clone();
handles.push(thread::spawn(move || {
let mut i = 0u64;
while !stop.load(Ordering::Acquire) && i < per_producer {
rec.record(((t as u64 * per_producer) + i) % 1000 + 1);
i += 1;
}
i
}));
}
let drainer_rec = rec.clone();
let drainer_stop = stop.clone();
let drainer = thread::spawn(move || {
let mut total = 0u64;
while !drainer_stop.load(Ordering::Acquire) {
let s = drainer_rec.get_interval_histogram();
total += s.count();
thread::yield_now();
}
let s = drainer_rec.get_interval_histogram();
total += s.count();
total
});
let mut produced = 0u64;
for h in handles {
produced += h.join().unwrap();
}
stop.store(true, Ordering::Release);
let drained = drainer.join().unwrap();
assert_eq!(
drained, produced,
"every record() must show up in some snapshot: produced={produced} drained={drained}"
);
}
#[test]
fn back_to_back_drain_alternates_sides() {
let rec = DualRecorder::new(3);
let i0 = rec.active_index();
let _ = rec.get_interval_histogram();
let i1 = rec.active_index();
let _ = rec.get_interval_histogram();
let i2 = rec.active_index();
assert_ne!(i0, i1);
assert_eq!(i0, i2);
}