1pub mod tokio;
8
9use std::time::Instant;
10#[cfg(any(test, feature = "testing"))]
11use std::{
12 sync::{
13 Arc,
14 atomic::{AtomicU64, Ordering},
15 },
16 time::Duration,
17};
18
19pub trait Clock {
21 fn now(&self) -> Instant;
23}
24
25#[derive(Clone, Copy, Debug, Default)]
27pub struct SystemClock;
28
29impl Clock for SystemClock {
30 #[inline]
31 fn now(&self) -> Instant {
32 Instant::now()
33 }
34}
35
36#[cfg(any(test, feature = "testing"))]
38#[doc(hidden)]
39#[derive(Debug)]
40pub struct TestClock {
41 base: Instant,
42 offset_nanos: AtomicU64,
43}
44
45#[cfg(any(test, feature = "testing"))]
46impl TestClock {
47 #[must_use]
49 pub fn frozen() -> Arc<TestClock> {
50 Arc::new(TestClock {
51 base: Instant::now(),
52 offset_nanos: AtomicU64::new(0),
53 })
54 }
55
56 pub fn advance(&self, by: Duration) {
58 let nanos = u64::try_from(by.as_nanos()).expect("test clock advance fits in u64 nanos");
59 self.offset_nanos.fetch_add(nanos, Ordering::Relaxed);
60 }
61
62 pub fn advance_stepped(&self, total: Duration, step: Duration, mut between: impl FnMut()) {
68 assert!(!step.is_zero(), "advance_stepped needs a non-zero step");
69 let mut moved = Duration::ZERO;
70 while moved < total {
71 let chunk = step.min(total - moved);
72 self.advance(chunk);
73 moved += chunk;
74 between();
75 }
76 }
77}
78
79#[cfg(any(test, feature = "testing"))]
80impl Clock for TestClock {
81 fn now(&self) -> Instant {
82 self.base + Duration::from_nanos(self.offset_nanos.load(Ordering::Relaxed))
83 }
84}
85
86#[cfg(any(test, feature = "testing"))]
87impl Clock for &TestClock {
88 fn now(&self) -> Instant {
89 (**self).now()
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
100 fn advance_stepped_reaches_total_in_steps() {
101 let clock = TestClock::frozen();
102 let start = clock.now();
103 let mut seen = Vec::new();
104 clock.advance_stepped(Duration::from_millis(25), Duration::from_millis(10), || {
105 seen.push(clock.now() - start);
106 });
107 assert_eq!(
108 seen,
109 [10, 20, 25].map(Duration::from_millis),
110 "one reading per step, the last one short"
111 );
112 }
113
114 #[test]
115 #[should_panic(expected = "non-zero step")]
116 fn advance_stepped_rejects_a_zero_step() {
117 TestClock::frozen().advance_stepped(Duration::from_secs(1), Duration::ZERO, || {});
118 }
119}