Skip to main content

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.
161    ///
162    /// # Arguments
163    ///
164    /// * `millis` - The milliseconds to add once.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use qubit_clock::{Clock, MockClock};
170    ///
171    /// let clock = MockClock::new();
172    /// let before = clock.millis();
173    /// clock.advance_millis(1000);
174    /// assert_eq!(clock.millis(), before + 1000);
175    /// ```
176    pub fn advance_millis(&self, millis: i64) {
177        let mut inner = self.inner.lock();
178        inner.millis_to_add += millis;
179    }
180
181    /// Enables auto-advance on each read operation.
182    ///
183    /// After calling this method, each call to [`millis()`](Clock::millis) or
184    /// [`time()`](Clock::time) will advance the clock by `millis`.
185    ///
186    /// # Arguments
187    ///
188    /// * `millis` - The milliseconds to advance on each read.
189    ///
190    /// # Examples
191    ///
192    /// ```
193    /// use qubit_clock::{Clock, MockClock};
194    ///
195    /// let clock = MockClock::new();
196    /// clock.set_auto_advance_millis(100);
197    /// let t1 = clock.millis();
198    /// let t2 = clock.millis();
199    /// assert_eq!(t2 - t1, 100);
200    /// ```
201    pub fn set_auto_advance_millis(&self, millis: i64) {
202        let mut inner = self.inner.lock();
203        inner.millis_to_add_each_time = millis;
204        inner.add_every_time = true;
205    }
206
207    /// Disables auto-advance behavior.
208    ///
209    /// This method clears the per-read advance setting. Subsequent read
210    /// operations will no longer mutate the clock state.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use qubit_clock::{Clock, MockClock};
216    ///
217    /// let clock = MockClock::new();
218    /// clock.set_auto_advance_millis(100);
219    /// let _ = clock.millis();
220    /// clock.clear_auto_advance();
221    /// let t1 = clock.millis();
222    /// let t2 = clock.millis();
223    /// assert!((t2 - t1).abs() < 10);
224    /// ```
225    pub fn clear_auto_advance(&self) {
226        let mut inner = self.inner.lock();
227        inner.millis_to_add_each_time = 0;
228        inner.add_every_time = false;
229    }
230}
231
232impl Default for MockClock {
233    #[inline]
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239impl Clock for MockClock {
240    fn millis(&self) -> i64 {
241        let mut inner = self.inner.lock();
242        let elapsed = inner.monotonic_clock.millis() - inner.create_time;
243        let result = inner.epoch + elapsed + inner.millis_to_add;
244
245        if inner.add_every_time {
246            inner.millis_to_add += inner.millis_to_add_each_time;
247        }
248
249        result
250    }
251}
252
253impl ControllableClock for MockClock {
254    fn set_time(&self, instant: DateTime<Utc>) {
255        let mut inner = self.inner.lock();
256        let current_monotonic = inner.monotonic_clock.millis();
257        let elapsed = current_monotonic - inner.create_time;
258        inner.epoch = instant.timestamp_millis() - elapsed;
259        inner.millis_to_add = 0;
260        inner.millis_to_add_each_time = 0;
261        inner.add_every_time = false;
262    }
263
264    #[inline]
265    fn add_duration(&self, duration: Duration) {
266        let millis = duration.num_milliseconds();
267        self.advance_millis(millis);
268    }
269
270    fn reset(&self) {
271        let mut inner = self.inner.lock();
272        inner.epoch = inner.create_time;
273        inner.millis_to_add = 0;
274        inner.millis_to_add_each_time = 0;
275        inner.add_every_time = false;
276    }
277}