use std::sync::Mutex;
use std::time::{Instant, SystemTime};
use super::clock as clock_inner;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClockLeak {
pub capability_id: String,
pub count: u64,
}
struct LeakRegistry {
entries: Vec<ClockLeak>,
}
impl LeakRegistry {
const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
fn record(&mut self, capability_id: &str) {
if let Some(entry) = self
.entries
.iter_mut()
.find(|entry| entry.capability_id == capability_id)
{
entry.count = entry.count.saturating_add(1);
return;
}
self.entries.push(ClockLeak {
capability_id: capability_id.to_string(),
count: 1,
});
}
}
static REGISTRY: Mutex<LeakRegistry> = Mutex::new(LeakRegistry::new());
pub fn wall_now(capability_id: &str) -> SystemTime {
record_if_mocked(capability_id);
SystemTime::now()
}
pub fn instant_now(capability_id: &str) -> Instant {
record_if_mocked(capability_id);
Instant::now()
}
pub fn snapshot() -> Vec<ClockLeak> {
REGISTRY
.lock()
.expect("clock leak registry mutex poisoned")
.entries
.clone()
}
pub fn drain() -> Vec<ClockLeak> {
std::mem::take(
&mut REGISTRY
.lock()
.expect("clock leak registry mutex poisoned")
.entries,
)
}
pub fn reset() {
REGISTRY
.lock()
.expect("clock leak registry mutex poisoned")
.entries
.clear();
}
fn record_if_mocked(capability_id: &str) {
if !clock_inner::is_mocked() {
return;
}
REGISTRY
.lock()
.expect("clock leak registry mutex poisoned")
.record(capability_id);
}
#[cfg(test)]
pub static TEST_LOCK: Mutex<()> = Mutex::new(());
#[cfg(test)]
mod tests {
use super::*;
use crate::clock_mock::{install_override, MockClock};
fn isolated_registry<F: FnOnce()>(f: F) {
let _guard = TEST_LOCK.lock().unwrap_or_else(|p| p.into_inner());
reset();
f();
reset();
}
#[test]
fn no_mock_no_leak() {
isolated_registry(|| {
let _ = wall_now("test/cap");
let _ = instant_now("test/cap");
assert!(snapshot().is_empty());
});
}
#[test]
fn mock_present_records_leak() {
isolated_registry(|| {
let _guard = install_override(MockClock::at_wall_ms(1_000_000));
let _ = wall_now("test/cap");
let leaks = snapshot();
assert_eq!(leaks.len(), 1);
assert_eq!(leaks[0].capability_id, "test/cap");
assert_eq!(leaks[0].count, 1);
});
}
#[test]
fn duplicate_capability_increments_count() {
isolated_registry(|| {
let _guard = install_override(MockClock::at_wall_ms(1_000_000));
wall_now("test/cap");
wall_now("test/cap");
wall_now("test/cap");
let leaks = snapshot();
assert_eq!(leaks.len(), 1);
assert_eq!(leaks[0].count, 3);
});
}
#[test]
fn distinct_capabilities_kept_separate_in_insertion_order() {
isolated_registry(|| {
let _guard = install_override(MockClock::at_wall_ms(1_000_000));
wall_now("test/a");
wall_now("test/b");
wall_now("test/a");
let leaks = snapshot();
assert_eq!(leaks.len(), 2);
assert_eq!(leaks[0].capability_id, "test/a");
assert_eq!(leaks[0].count, 2);
assert_eq!(leaks[1].capability_id, "test/b");
assert_eq!(leaks[1].count, 1);
});
}
#[test]
fn drain_empties_registry() {
isolated_registry(|| {
let _guard = install_override(MockClock::at_wall_ms(1_000_000));
wall_now("test/cap");
let drained = drain();
assert_eq!(drained.len(), 1);
assert!(snapshot().is_empty());
});
}
}