Skip to main content

perfgate_fake/
clock.rs

1//! Fake clock for deterministic time-based testing.
2
3use std::sync::{Arc, Mutex};
4use std::time::Duration;
5
6/// A fake clock that returns configurable timestamps.
7///
8/// This is useful for testing code that depends on timing without
9/// introducing non-determinism from real system clocks.
10///
11/// # Thread Safety
12///
13/// All methods are `&self` (not `&mut self`), making it safe
14/// to share a single instance across multiple threads in tests.
15///
16/// # Example
17///
18/// ```
19/// use perfgate_fake::FakeClock;
20/// use std::time::Duration;
21///
22/// let clock = FakeClock::new()
23///     .with_millis(1000);
24///
25/// assert_eq!(clock.now_millis(), 1000);
26///
27/// clock.advance(Duration::from_millis(500));
28/// assert_eq!(clock.now_millis(), 1500);
29/// ```
30#[derive(Debug, Clone)]
31pub struct FakeClock {
32    inner: Arc<Mutex<FakeClockInner>>,
33}
34
35#[derive(Debug, Clone)]
36struct FakeClockInner {
37    millis: u64,
38}
39
40impl Default for FakeClock {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl FakeClock {
47    /// Create a new `FakeClock` starting at time 0.
48    pub fn new() -> Self {
49        Self {
50            inner: Arc::new(Mutex::new(FakeClockInner { millis: 0 })),
51        }
52    }
53
54    /// Create a `FakeClock` starting at a specific time in milliseconds.
55    pub fn at_millis(millis: u64) -> Self {
56        Self {
57            inner: Arc::new(Mutex::new(FakeClockInner { millis })),
58        }
59    }
60
61    /// Create a `FakeClock` starting at a specific time.
62    pub fn at(duration: Duration) -> Self {
63        Self::at_millis(duration.as_millis() as u64)
64    }
65
66    /// Set the current time in milliseconds.
67    pub fn with_millis(self, millis: u64) -> Self {
68        self.inner.lock().expect("lock").millis = millis;
69        self
70    }
71
72    /// Set the current time.
73    pub fn with_duration(self, duration: Duration) -> Self {
74        self.with_millis(duration.as_millis() as u64)
75    }
76
77    /// Get the current time in milliseconds.
78    pub fn now_millis(&self) -> u64 {
79        self.inner.lock().expect("lock").millis
80    }
81
82    /// Get the current time as a `Duration`.
83    pub fn now(&self) -> Duration {
84        Duration::from_millis(self.now_millis())
85    }
86
87    /// Advance the clock by a duration.
88    pub fn advance(&self, duration: Duration) {
89        self.advance_millis(duration.as_millis() as u64);
90    }
91
92    /// Advance the clock by a number of milliseconds.
93    pub fn advance_millis(&self, millis: u64) {
94        let mut inner = self.inner.lock().expect("lock");
95        inner.millis = inner.millis.saturating_add(millis);
96    }
97
98    /// Reset the clock to time 0.
99    pub fn reset(&self) {
100        self.inner.lock().expect("lock").millis = 0;
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn new_clock_starts_at_zero() {
110        let clock = FakeClock::new();
111        assert_eq!(clock.now_millis(), 0);
112    }
113
114    #[test]
115    fn at_millis_sets_initial_time() {
116        let clock = FakeClock::at_millis(5000);
117        assert_eq!(clock.now_millis(), 5000);
118    }
119
120    #[test]
121    fn at_duration_sets_initial_time() {
122        let clock = FakeClock::at(Duration::from_secs(10));
123        assert_eq!(clock.now_millis(), 10000);
124    }
125
126    #[test]
127    fn with_millis_configures_time() {
128        let clock = FakeClock::new().with_millis(1234);
129        assert_eq!(clock.now_millis(), 1234);
130    }
131
132    #[test]
133    fn with_duration_configures_time() {
134        let clock = FakeClock::new().with_duration(Duration::from_secs(30));
135        assert_eq!(clock.now_millis(), 30000);
136    }
137
138    #[test]
139    fn advance_increments_time() {
140        let clock = FakeClock::new().with_millis(100);
141        clock.advance(Duration::from_millis(50));
142
143        assert_eq!(clock.now_millis(), 150);
144    }
145
146    #[test]
147    fn advance_millis_increments_time() {
148        let clock = FakeClock::new().with_millis(100);
149        clock.advance_millis(200);
150
151        assert_eq!(clock.now_millis(), 300);
152    }
153
154    #[test]
155    fn reset_returns_to_zero() {
156        let clock = FakeClock::new().with_millis(9999);
157        clock.reset();
158
159        assert_eq!(clock.now_millis(), 0);
160    }
161
162    #[test]
163    fn saturating_addition_on_overflow() {
164        let clock = FakeClock::new().with_millis(u64::MAX);
165        clock.advance_millis(1);
166
167        assert_eq!(clock.now_millis(), u64::MAX);
168    }
169
170    #[test]
171    fn thread_safe_sharing() {
172        use std::sync::Arc;
173        use std::thread;
174
175        let clock = Arc::new(FakeClock::new());
176
177        let handles: Vec<_> = (0..4)
178            .map(|_i| {
179                let c = clock.clone();
180                thread::spawn(move || {
181                    c.advance_millis(100);
182                    c.now_millis()
183                })
184            })
185            .collect();
186
187        let _results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
188
189        assert_eq!(clock.now_millis(), 400);
190    }
191}