agent_tui/daemon/
sleeper.rs

1//! Sleeper trait for deterministic timing in tests.
2//!
3//! This module provides a `Sleeper` trait that abstracts over `thread::sleep`,
4//! allowing tests to replace real sleep with mock implementations that don't
5//! actually wait, making tests fast and deterministic.
6
7use std::sync::Mutex;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::thread;
10use std::time::Duration;
11
12/// Trait for abstracting sleep operations.
13///
14/// This trait allows production code to use `RealSleeper` which actually sleeps,
15/// while tests can use `MockSleeper` which records calls without sleeping.
16pub trait Sleeper: Send + Sync {
17    /// Sleep for the specified duration.
18    fn sleep(&self, duration: Duration);
19}
20
21/// Production sleeper that uses `thread::sleep`.
22#[derive(Debug, Clone, Copy, Default)]
23pub struct RealSleeper;
24
25impl Sleeper for RealSleeper {
26    fn sleep(&self, duration: Duration) {
27        thread::sleep(duration);
28    }
29}
30
31/// Mock sleeper for testing that records calls without sleeping.
32///
33/// This implementation tracks:
34/// - Total number of sleep calls
35/// - Total duration of all sleep calls
36/// - Individual durations of each sleep call
37#[derive(Debug, Default)]
38pub struct MockSleeper {
39    call_count: AtomicU64,
40    total_duration_ms: AtomicU64,
41    durations: Mutex<Vec<Duration>>,
42}
43
44impl MockSleeper {
45    /// Create a new mock sleeper.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Returns the number of times sleep was called.
51    pub fn call_count(&self) -> u64 {
52        self.call_count.load(Ordering::SeqCst)
53    }
54
55    /// Returns the total duration of all sleep calls.
56    pub fn total_duration(&self) -> Duration {
57        Duration::from_millis(self.total_duration_ms.load(Ordering::SeqCst))
58    }
59
60    /// Returns all individual sleep durations.
61    pub fn durations(&self) -> Vec<Duration> {
62        self.durations.lock().unwrap().clone()
63    }
64
65    /// Reset all tracking state.
66    pub fn reset(&self) {
67        self.call_count.store(0, Ordering::SeqCst);
68        self.total_duration_ms.store(0, Ordering::SeqCst);
69        self.durations.lock().unwrap().clear();
70    }
71}
72
73impl Sleeper for MockSleeper {
74    fn sleep(&self, duration: Duration) {
75        self.call_count.fetch_add(1, Ordering::SeqCst);
76        self.total_duration_ms
77            .fetch_add(duration.as_millis() as u64, Ordering::SeqCst);
78        self.durations.lock().unwrap().push(duration);
79        // Don't actually sleep - that's the whole point!
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_real_sleeper_sleeps() {
89        let sleeper = RealSleeper;
90        let start = std::time::Instant::now();
91        sleeper.sleep(Duration::from_millis(10));
92        let elapsed = start.elapsed();
93        // Should have slept for at least 10ms (allowing some margin)
94        assert!(elapsed >= Duration::from_millis(5));
95    }
96
97    #[test]
98    fn test_mock_sleeper_does_not_sleep() {
99        let sleeper = MockSleeper::new();
100        let start = std::time::Instant::now();
101        sleeper.sleep(Duration::from_millis(1000));
102        let elapsed = start.elapsed();
103        // Should complete nearly instantly (less than 5ms)
104        assert!(elapsed < Duration::from_millis(5));
105    }
106
107    #[test]
108    fn test_mock_sleeper_tracks_call_count() {
109        let sleeper = MockSleeper::new();
110
111        sleeper.sleep(Duration::from_millis(10));
112        sleeper.sleep(Duration::from_millis(20));
113        sleeper.sleep(Duration::from_millis(30));
114
115        assert_eq!(sleeper.call_count(), 3);
116    }
117
118    #[test]
119    fn test_mock_sleeper_tracks_total_duration() {
120        let sleeper = MockSleeper::new();
121
122        sleeper.sleep(Duration::from_millis(10));
123        sleeper.sleep(Duration::from_millis(20));
124        sleeper.sleep(Duration::from_millis(30));
125
126        assert_eq!(sleeper.total_duration(), Duration::from_millis(60));
127    }
128
129    #[test]
130    fn test_mock_sleeper_tracks_individual_durations() {
131        let sleeper = MockSleeper::new();
132
133        sleeper.sleep(Duration::from_millis(10));
134        sleeper.sleep(Duration::from_millis(20));
135        sleeper.sleep(Duration::from_millis(30));
136
137        let durations = sleeper.durations();
138        assert_eq!(durations.len(), 3);
139        assert_eq!(durations[0], Duration::from_millis(10));
140        assert_eq!(durations[1], Duration::from_millis(20));
141        assert_eq!(durations[2], Duration::from_millis(30));
142    }
143
144    #[test]
145    fn test_mock_sleeper_reset() {
146        let sleeper = MockSleeper::new();
147
148        sleeper.sleep(Duration::from_millis(100));
149        assert_eq!(sleeper.call_count(), 1);
150
151        sleeper.reset();
152
153        assert_eq!(sleeper.call_count(), 0);
154        assert_eq!(sleeper.total_duration(), Duration::ZERO);
155        assert!(sleeper.durations().is_empty());
156    }
157}