qubit_clock/mock.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! Mock clock implementation for testing.
10//!
11//! This module provides [`MockClock`], a controllable clock implementation
12//! designed for testing scenarios where precise control over time is needed.
13//!
14//! # Author
15//!
16//! Haixing Hu
17
18use crate::{Clock, ControllableClock, MonotonicClock};
19use chrono::{DateTime, Duration, Utc};
20use parking_lot::Mutex;
21use std::sync::Arc;
22
23/// A controllable clock implementation for testing.
24///
25/// `MockClock` allows you to manually control the passage of time, making it
26/// ideal for testing time-dependent code. It uses [`MonotonicClock`] as its
27/// internal time base to ensure stability during tests.
28///
29/// # Features
30///
31/// - Set the clock to a specific time
32/// - Advance the clock by a duration
33/// - Automatically advance time on each call
34/// - Reset to initial state
35///
36/// # Thread Safety
37///
38/// This type is thread-safe, using `Arc<Mutex<>>` internally to protect its
39/// mutable state.
40///
41/// # Examples
42///
43/// ```
44/// use qubit_clock::{Clock, ControllableClock, MockClock};
45/// use chrono::{DateTime, Duration, Utc};
46///
47/// let clock = MockClock::new();
48///
49/// // Set to a specific time
50/// let fixed_time = DateTime::parse_from_rfc3339(
51/// "2024-01-01T00:00:00Z"
52/// ).unwrap().with_timezone(&Utc);
53/// clock.set_time(fixed_time);
54/// assert_eq!(clock.time(), fixed_time);
55///
56/// // Advance by 1 hour
57/// clock.add_duration(Duration::hours(1));
58/// assert_eq!(clock.time(), fixed_time + Duration::hours(1));
59///
60/// // Reset to initial state
61/// clock.reset();
62/// ```
63///
64/// # Author
65///
66/// Haixing Hu
67#[derive(Debug, Clone)]
68pub struct MockClock {
69 inner: Arc<Mutex<MockClockInner>>,
70}
71
72#[derive(Debug)]
73struct MockClockInner {
74 /// The monotonic clock used as the time base.
75 monotonic_clock: MonotonicClock,
76 /// The time when this clock was created (milliseconds since epoch).
77 create_time: i64,
78 /// The epoch time to use as the base (milliseconds since epoch).
79 epoch: i64,
80 /// Additional milliseconds to add to the current time.
81 millis_to_add: i64,
82 /// Milliseconds to add on each call to `millis()`.
83 millis_to_add_each_time: i64,
84 /// Whether to automatically add `millis_to_add_each_time` on each call.
85 add_every_time: bool,
86}
87
88impl MockClock {
89 /// Creates a new `MockClock`.
90 ///
91 /// The clock is initialized with the current system time and uses a
92 /// [`MonotonicClock`] as its internal time base.
93 ///
94 /// # Returns
95 ///
96 /// A new `MockClock` instance.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use qubit_clock::MockClock;
102 ///
103 /// let clock = MockClock::new();
104 /// ```
105 ///
106 pub fn new() -> Self {
107 let monotonic_clock = MonotonicClock::new();
108 let create_time = monotonic_clock.millis();
109 MockClock {
110 inner: Arc::new(Mutex::new(MockClockInner {
111 monotonic_clock,
112 create_time,
113 epoch: create_time,
114 millis_to_add: 0,
115 millis_to_add_each_time: 0,
116 add_every_time: false,
117 })),
118 }
119 }
120
121 /// Adds a fixed amount of milliseconds to the clock.
122 ///
123 /// # Arguments
124 ///
125 /// * `millis` - The number of milliseconds to add.
126 /// * `add_every_time` - If `true`, the specified milliseconds will be
127 /// added on every call to [`millis()`](Clock::millis). If `false`, the
128 /// milliseconds are added only once.
129 ///
130 /// # Examples
131 ///
132 /// ```
133 /// use qubit_clock::{Clock, MockClock};
134 ///
135 /// let clock = MockClock::new();
136 /// let before = clock.millis();
137 ///
138 /// // Add 1000ms once
139 /// clock.add_millis(1000, false);
140 /// assert_eq!(clock.millis(), before + 1000);
141 ///
142 /// // Add 100ms on every call
143 /// clock.add_millis(100, true);
144 /// let t1 = clock.millis();
145 /// let t2 = clock.millis();
146 /// assert_eq!(t2 - t1, 100);
147 /// ```
148 ///
149 pub fn add_millis(&self, millis: i64, add_every_time: bool) {
150 if add_every_time {
151 self.set_auto_advance_millis(millis);
152 } else {
153 self.advance_millis(millis);
154 }
155 }
156
157 /// Advances the clock by a fixed amount once.
158 ///
159 /// This method updates the offset used by [`millis()`](Clock::millis) and
160 /// [`time()`](Clock::time) without enabling auto-advance. If the
161 /// accumulated offset exceeds the `i64` range, it saturates at the nearest
162 /// boundary.
163 ///
164 /// # Arguments
165 ///
166 /// * `millis` - The milliseconds to add once.
167 ///
168 /// # Examples
169 ///
170 /// ```
171 /// use qubit_clock::{Clock, MockClock};
172 ///
173 /// let clock = MockClock::new();
174 /// let before = clock.millis();
175 /// clock.advance_millis(1000);
176 /// assert_eq!(clock.millis(), before + 1000);
177 /// ```
178 pub fn advance_millis(&self, millis: i64) {
179 let mut inner = self.inner.lock();
180 inner.millis_to_add = inner.millis_to_add.saturating_add(millis);
181 }
182
183 /// Enables auto-advance on each read operation.
184 ///
185 /// After calling this method, each call to [`millis()`](Clock::millis) or
186 /// [`time()`](Clock::time) will advance the clock by `millis`.
187 ///
188 /// # Arguments
189 ///
190 /// * `millis` - The milliseconds to advance on each read.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// use qubit_clock::{Clock, MockClock};
196 ///
197 /// let clock = MockClock::new();
198 /// clock.set_auto_advance_millis(100);
199 /// let t1 = clock.millis();
200 /// let t2 = clock.millis();
201 /// assert_eq!(t2 - t1, 100);
202 /// ```
203 pub fn set_auto_advance_millis(&self, millis: i64) {
204 let mut inner = self.inner.lock();
205 inner.millis_to_add_each_time = millis;
206 inner.add_every_time = true;
207 }
208
209 /// Disables auto-advance behavior.
210 ///
211 /// This method clears the per-read advance setting. Subsequent read
212 /// operations will no longer mutate the clock state.
213 ///
214 /// # Examples
215 ///
216 /// ```
217 /// use qubit_clock::{Clock, MockClock};
218 ///
219 /// let clock = MockClock::new();
220 /// clock.set_auto_advance_millis(100);
221 /// let _ = clock.millis();
222 /// clock.clear_auto_advance();
223 /// let t1 = clock.millis();
224 /// let t2 = clock.millis();
225 /// assert!((t2 - t1).abs() < 10);
226 /// ```
227 pub fn clear_auto_advance(&self) {
228 let mut inner = self.inner.lock();
229 inner.millis_to_add_each_time = 0;
230 inner.add_every_time = false;
231 }
232}
233
234impl Default for MockClock {
235 #[inline]
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl Clock for MockClock {
242 fn millis(&self) -> i64 {
243 let mut inner = self.inner.lock();
244 let elapsed = inner
245 .monotonic_clock
246 .millis()
247 .saturating_sub(inner.create_time);
248 let result = inner
249 .epoch
250 .saturating_add(elapsed)
251 .saturating_add(inner.millis_to_add);
252
253 if inner.add_every_time {
254 inner.millis_to_add = inner
255 .millis_to_add
256 .saturating_add(inner.millis_to_add_each_time);
257 }
258
259 result
260 }
261}
262
263impl ControllableClock for MockClock {
264 fn set_time(&self, instant: DateTime<Utc>) {
265 let mut inner = self.inner.lock();
266 let current_monotonic = inner.monotonic_clock.millis();
267 let elapsed = current_monotonic.saturating_sub(inner.create_time);
268 inner.epoch = instant.timestamp_millis().saturating_sub(elapsed);
269 inner.millis_to_add = 0;
270 inner.millis_to_add_each_time = 0;
271 inner.add_every_time = false;
272 }
273
274 #[inline]
275 fn add_duration(&self, duration: Duration) {
276 let millis = duration.num_milliseconds();
277 self.advance_millis(millis);
278 }
279
280 fn reset(&self) {
281 let mut inner = self.inner.lock();
282 inner.epoch = inner.create_time;
283 inner.millis_to_add = 0;
284 inner.millis_to_add_each_time = 0;
285 inner.add_every_time = false;
286 }
287}