harn_vm/triggers/test_util/
clock.rs1use std::cell::RefCell;
17use std::sync::{Arc, OnceLock};
18use std::time::{Duration as StdDuration, Instant};
19
20use async_trait::async_trait;
21use harn_clock::Clock;
22use time::OffsetDateTime;
23
24thread_local! {
25 static MOCK_CLOCK_STACK: RefCell<Vec<Arc<MockClock>>> = const { RefCell::new(Vec::new()) };
26}
27
28fn process_start() -> &'static Instant {
29 static PROCESS_START: OnceLock<Instant> = OnceLock::new();
30 PROCESS_START.get_or_init(Instant::now)
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
37pub struct ClockInstant(StdDuration);
38
39impl ClockInstant {
40 pub fn duration_since(self, earlier: Self) -> StdDuration {
41 self.0.saturating_sub(earlier.0)
42 }
43
44 pub fn as_millis(self) -> u128 {
45 self.0.as_millis()
46 }
47}
48
49pub struct ClockOverrideGuard;
50
51impl Drop for ClockOverrideGuard {
52 fn drop(&mut self) {
53 let _ = MOCK_CLOCK_STACK.try_with(|slot| {
56 slot.borrow_mut().pop();
57 });
58 }
59}
60
61#[derive(Debug)]
69pub struct MockClock {
70 inner: Arc<harn_clock::PausedClock>,
71}
72
73impl MockClock {
74 pub fn new(now: OffsetDateTime) -> Arc<Self> {
75 Arc::new(Self {
76 inner: harn_clock::PausedClock::new(now),
77 })
78 }
79
80 pub fn at_wall_ms(wall_ms: i64) -> Arc<Self> {
82 let nanos = (wall_ms as i128).saturating_mul(1_000_000);
83 let now =
84 OffsetDateTime::from_unix_timestamp_nanos(nanos).unwrap_or(OffsetDateTime::UNIX_EPOCH);
85 Self::new(now)
86 }
87
88 pub fn monotonic_now(&self) -> ClockInstant {
89 ClockInstant(StdDuration::from_millis(
90 self.inner.monotonic_ms().max(0) as u64
91 ))
92 }
93
94 pub fn now_wall_ms(&self) -> i64 {
96 self.now_utc().unix_timestamp_nanos() as i64 / 1_000_000
97 }
98
99 pub fn now_monotonic_ms(&self) -> i64 {
101 self.inner.monotonic_ms()
102 }
103
104 pub fn set_sync(&self, now: OffsetDateTime) {
107 self.inner.set(now);
108 }
109
110 pub fn advance_std_sync(&self, duration: StdDuration) {
112 self.inner.advance(duration);
113 }
114
115 pub async fn set(&self, now: OffsetDateTime) {
118 self.inner.set(now);
119 }
120
121 pub async fn advance(&self, duration: time::Duration) {
123 self.inner.advance_time(duration);
124 }
125
126 pub async fn advance_std(&self, duration: StdDuration) {
128 self.inner.advance(duration);
129 }
130
131 pub async fn advance_ticks(&self, ticks: u32, tick: StdDuration) {
133 self.inner.advance_ticks(ticks, tick);
134 }
135}
136
137#[async_trait]
138impl Clock for MockClock {
139 fn now_utc(&self) -> OffsetDateTime {
140 self.inner.now_utc()
141 }
142
143 fn monotonic_ms(&self) -> i64 {
144 self.inner.monotonic_ms()
145 }
146
147 async fn sleep(&self, duration: StdDuration) {
148 self.inner.sleep(duration).await;
149 }
150
151 async fn sleep_until_utc(&self, deadline: OffsetDateTime) {
152 self.inner.sleep_until_utc(deadline).await;
153 }
154}
155
156pub fn install_override(clock: Arc<MockClock>) -> ClockOverrideGuard {
157 MOCK_CLOCK_STACK.with(|slot| {
158 slot.borrow_mut().push(clock);
159 });
160 ClockOverrideGuard
161}
162
163pub fn active_mock_clock() -> Option<Arc<MockClock>> {
164 MOCK_CLOCK_STACK.with(|slot| slot.borrow().last().cloned())
165}
166
167pub fn is_mocked() -> bool {
168 MOCK_CLOCK_STACK.with(|slot| !slot.borrow().is_empty())
169}
170
171pub fn clear_overrides() {
175 MOCK_CLOCK_STACK.with(|slot| {
176 slot.borrow_mut().clear();
177 });
178}
179
180pub fn now_utc() -> OffsetDateTime {
181 active_mock_clock()
182 .map(|clock| clock.now_utc())
183 .unwrap_or_else(OffsetDateTime::now_utc)
184}
185
186pub fn now_ms() -> i64 {
187 now_utc().unix_timestamp_nanos() as i64 / 1_000_000
188}
189
190pub fn instant_now() -> ClockInstant {
191 active_mock_clock()
192 .map(|clock| clock.monotonic_now())
193 .unwrap_or_else(|| ClockInstant(process_start().elapsed()))
194}
195
196pub fn advance(duration: StdDuration) {
199 if let Some(clock) = active_mock_clock() {
200 clock.advance_std_sync(duration);
201 }
202}
203
204pub async fn sleep(duration: StdDuration) {
217 if duration.is_zero() {
218 return;
219 }
220 if let Some(mock) = active_mock_clock() {
221 mock.sleep(duration).await;
222 return;
223 }
224 tokio::time::sleep(duration).await;
225}