xerv_core/testing/providers/
clock.rs1use parking_lot::Mutex;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
9
10pub trait ClockProvider: Send + Sync {
15 fn now(&self) -> u64;
17
18 fn system_time_millis(&self) -> u64;
20
21 fn sleep(
26 &self,
27 duration: Duration,
28 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>>;
29
30 fn advance(&self, duration: Duration);
34
35 fn is_mock(&self) -> bool;
37}
38
39#[derive(Debug, Clone)]
41pub struct RealClock {
42 start: Instant,
43}
44
45impl RealClock {
46 pub fn new() -> Self {
48 Self {
49 start: Instant::now(),
50 }
51 }
52}
53
54impl Default for RealClock {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl ClockProvider for RealClock {
61 fn now(&self) -> u64 {
62 self.start.elapsed().as_nanos() as u64
63 }
64
65 fn system_time_millis(&self) -> u64 {
66 SystemTime::now()
67 .duration_since(UNIX_EPOCH)
68 .expect("System time before UNIX epoch")
69 .as_millis() as u64
70 }
71
72 fn sleep(
73 &self,
74 duration: Duration,
75 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
76 Box::pin(async move {
77 tokio::time::sleep(duration).await;
78 })
79 }
80
81 fn advance(&self, _duration: Duration) {
82 }
84
85 fn is_mock(&self) -> bool {
86 false
87 }
88}
89
90pub struct MockClock {
95 current_nanos: AtomicU64,
97 system_time_millis: AtomicU64,
99 pending_sleeps: Mutex<Vec<PendingSleep>>,
101}
102
103struct PendingSleep {
104 wake_at_nanos: u64,
105 waker: Option<std::task::Waker>,
106}
107
108impl MockClock {
109 pub fn new() -> Self {
111 Self {
112 current_nanos: AtomicU64::new(0),
113 system_time_millis: AtomicU64::new(0),
114 pending_sleeps: Mutex::new(Vec::new()),
115 }
116 }
117
118 pub fn fixed(iso_time: &str) -> Self {
129 let dt = chrono::DateTime::parse_from_rfc3339(iso_time)
130 .expect("Invalid ISO 8601 datetime format");
131 let millis = dt.timestamp_millis() as u64;
132
133 Self {
134 current_nanos: AtomicU64::new(0),
135 system_time_millis: AtomicU64::new(millis),
136 pending_sleeps: Mutex::new(Vec::new()),
137 }
138 }
139
140 pub fn at_now() -> Self {
142 let millis = SystemTime::now()
143 .duration_since(UNIX_EPOCH)
144 .expect("System time before UNIX epoch")
145 .as_millis() as u64;
146
147 Self {
148 current_nanos: AtomicU64::new(0),
149 system_time_millis: AtomicU64::new(millis),
150 pending_sleeps: Mutex::new(Vec::new()),
151 }
152 }
153
154 pub fn current_nanos(&self) -> u64 {
156 self.current_nanos.load(Ordering::SeqCst)
157 }
158
159 fn wake_expired_sleeps(&self, current: u64) {
161 let mut sleeps = self.pending_sleeps.lock();
162 sleeps.retain_mut(|sleep| {
163 if sleep.wake_at_nanos <= current {
164 if let Some(waker) = sleep.waker.take() {
165 waker.wake();
166 }
167 false } else {
169 true }
171 });
172 }
173}
174
175impl Default for MockClock {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181impl ClockProvider for MockClock {
182 fn now(&self) -> u64 {
183 self.current_nanos.load(Ordering::SeqCst)
184 }
185
186 fn system_time_millis(&self) -> u64 {
187 self.system_time_millis.load(Ordering::SeqCst)
188 }
189
190 fn sleep(
191 &self,
192 duration: Duration,
193 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
194 let wake_at = self.current_nanos.load(Ordering::SeqCst) + duration.as_nanos() as u64;
195
196 if self.current_nanos.load(Ordering::SeqCst) >= wake_at {
198 return Box::pin(std::future::ready(()));
199 }
200
201 let clock_ref = self as *const Self;
203
204 Box::pin(MockSleepFuture {
205 wake_at,
206 clock: clock_ref,
207 registered: false,
208 })
209 }
210
211 fn advance(&self, duration: Duration) {
212 let nanos = duration.as_nanos() as u64;
213 let new_time = self.current_nanos.fetch_add(nanos, Ordering::SeqCst) + nanos;
214
215 let millis = duration.as_millis() as u64;
217 self.system_time_millis.fetch_add(millis, Ordering::SeqCst);
218
219 self.wake_expired_sleeps(new_time);
221 }
222
223 fn is_mock(&self) -> bool {
224 true
225 }
226}
227
228struct MockSleepFuture {
230 wake_at: u64,
231 clock: *const MockClock,
232 registered: bool,
233}
234
235unsafe impl Send for MockSleepFuture {}
237
238impl std::future::Future for MockSleepFuture {
239 type Output = ();
240
241 fn poll(
242 mut self: std::pin::Pin<&mut Self>,
243 cx: &mut std::task::Context<'_>,
244 ) -> std::task::Poll<Self::Output> {
245 let clock = unsafe { &*self.clock };
247
248 let current = clock.current_nanos.load(Ordering::SeqCst);
249
250 if current >= self.wake_at {
251 std::task::Poll::Ready(())
252 } else {
253 if !self.registered {
255 let mut sleeps = clock.pending_sleeps.lock();
256 sleeps.push(PendingSleep {
257 wake_at_nanos: self.wake_at,
258 waker: Some(cx.waker().clone()),
259 });
260 self.registered = true;
261 } else {
262 let mut sleeps = clock.pending_sleeps.lock();
264 for sleep in sleeps.iter_mut() {
265 if sleep.wake_at_nanos == self.wake_at {
266 sleep.waker = Some(cx.waker().clone());
267 break;
268 }
269 }
270 }
271 std::task::Poll::Pending
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use std::sync::Arc;
280
281 #[test]
282 fn real_clock_advances() {
283 let clock = RealClock::new();
284 let t1 = clock.now();
285 std::thread::sleep(Duration::from_millis(10));
286 let t2 = clock.now();
287 assert!(t2 > t1);
288 }
289
290 #[test]
291 fn mock_clock_fixed_time() {
292 let clock = MockClock::fixed("2024-01-15T10:30:00Z");
293 let millis = clock.system_time_millis();
294 assert_eq!(millis, 1705314600000);
296 }
297
298 #[test]
299 fn mock_clock_does_not_advance_automatically() {
300 let clock = MockClock::new();
301 let t1 = clock.now();
302 std::thread::sleep(Duration::from_millis(10));
303 let t2 = clock.now();
304 assert_eq!(t1, t2);
305 }
306
307 #[test]
308 fn mock_clock_advance() {
309 let clock = MockClock::new();
310 assert_eq!(clock.now(), 0);
311
312 clock.advance(Duration::from_secs(1));
313 assert_eq!(clock.now(), 1_000_000_000);
314
315 clock.advance(Duration::from_millis(500));
316 assert_eq!(clock.now(), 1_500_000_000);
317 }
318
319 #[test]
320 fn mock_clock_system_time_advances() {
321 let clock = MockClock::fixed("2024-01-15T10:30:00Z");
322 let t1 = clock.system_time_millis();
323
324 clock.advance(Duration::from_secs(60));
325 let t2 = clock.system_time_millis();
326
327 assert_eq!(t2 - t1, 60_000);
328 }
329
330 #[tokio::test]
331 async fn mock_clock_sleep_completes_on_advance() {
332 let clock = Arc::new(MockClock::new());
333 let clock_ref = Arc::clone(&clock);
334
335 let handle = tokio::spawn(async move {
337 clock_ref.sleep(Duration::from_secs(1)).await;
338 true
339 });
340
341 tokio::task::yield_now().await;
343
344 clock.advance(Duration::from_secs(2));
346
347 let result = tokio::time::timeout(Duration::from_millis(100), handle)
349 .await
350 .expect("Timed out waiting for sleep")
351 .expect("Task panicked");
352
353 assert!(result);
354 }
355}