1use std::sync::Mutex;
15use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16
17pub trait Clock: Send + Sync {
18 fn now_unix_secs(&self) -> u64;
20
21 fn now_unix_millis(&self) -> u64 {
23 self.now_unix_secs() * 1000
24 }
25
26 fn now_monotonic(&self) -> Instant;
29}
30
31#[derive(Debug, Default, Clone, Copy)]
33pub struct SystemClock;
34
35impl Clock for SystemClock {
36 fn now_unix_secs(&self) -> u64 {
37 SystemTime::now()
38 .duration_since(UNIX_EPOCH)
39 .map(|d| d.as_secs())
40 .unwrap_or(0)
41 }
42
43 fn now_unix_millis(&self) -> u64 {
44 SystemTime::now()
45 .duration_since(UNIX_EPOCH)
46 .map(|d| d.as_millis() as u64)
47 .unwrap_or(0)
48 }
49
50 fn now_monotonic(&self) -> Instant {
51 Instant::now()
52 }
53}
54
55pub struct MockClock {
60 inner: Mutex<MockState>,
61}
62
63struct MockState {
64 unix_millis: u64,
65 base_instant: Instant,
66 elapsed: Duration,
67}
68
69impl MockClock {
70 pub fn new(start_unix_secs: u64) -> Self {
71 Self {
72 inner: Mutex::new(MockState {
73 unix_millis: start_unix_secs * 1000,
74 base_instant: Instant::now(),
75 elapsed: Duration::ZERO,
76 }),
77 }
78 }
79
80 pub fn advance(&self, by: Duration) {
81 let mut s = self.inner.lock().expect("MockClock poisoned");
82 s.unix_millis += by.as_millis() as u64;
83 s.elapsed += by;
84 }
85}
86
87impl Clock for MockClock {
88 fn now_unix_secs(&self) -> u64 {
89 self.inner.lock().expect("MockClock poisoned").unix_millis / 1000
90 }
91
92 fn now_unix_millis(&self) -> u64 {
93 self.inner.lock().expect("MockClock poisoned").unix_millis
94 }
95
96 fn now_monotonic(&self) -> Instant {
97 let s = self.inner.lock().expect("MockClock poisoned");
98 s.base_instant + s.elapsed
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn system_clock_returns_nonzero() {
108 let c = SystemClock;
109 assert!(c.now_unix_secs() > 1_700_000_000);
110 }
111
112 #[test]
113 fn mock_clock_starts_at_given_time() {
114 let c = MockClock::new(1_000_000);
115 assert_eq!(c.now_unix_secs(), 1_000_000);
116 assert_eq!(c.now_unix_millis(), 1_000_000_000);
117 }
118
119 #[test]
120 fn mock_clock_advances_wall_and_monotonic_together() {
121 let c = MockClock::new(0);
122 let m0 = c.now_monotonic();
123 c.advance(Duration::from_secs(60));
124 assert_eq!(c.now_unix_secs(), 60);
125 let m1 = c.now_monotonic();
126 assert_eq!(m1.duration_since(m0), Duration::from_secs(60));
127 }
128
129 #[test]
130 fn dyn_clock_is_object_safe() {
131 fn use_it(c: &dyn Clock) -> u64 {
132 c.now_unix_secs()
133 }
134 assert_eq!(use_it(&MockClock::new(42)), 42);
135 }
136}