Skip to main content

qubit_clock/
mock_clock.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Mock clock implementation for testing.
11//!
12//! This module provides [`MockClock`], a controllable clock implementation
13//! designed for testing scenarios where precise control over time is needed.
14//!
15
16use crate::{
17    Clock,
18    ControllableClock,
19    MockClockProgression,
20    MonotonicClock,
21    SystemClock,
22};
23use chrono::{
24    DateTime,
25    Duration,
26    Utc,
27};
28use std::sync::{
29    Arc,
30    Mutex,
31    MutexGuard,
32};
33
34/// A controllable clock implementation for testing.
35///
36/// `MockClock` allows you to adjust logical time, making it useful for testing
37/// time-dependent code. Readings are frozen after construction by default.
38/// [`set_time()`](ControllableClock::set_time) reanchors the logical time
39/// without changing the current progression mode or auto-advance settings.
40///
41/// # Features
42///
43/// - Align the logical current time to a specific time
44/// - Advance the clock by a duration
45/// - Automatically advance time on each call
46/// - Switch between frozen and monotonic progression
47/// - Reset to the initial creation state
48///
49/// # Thread Safety
50///
51/// This type is thread-safe, using `Arc<Mutex<>>` internally to protect its
52/// mutable state.
53///
54/// # Examples
55///
56/// ```
57/// use qubit_clock::{Clock, ControllableClock, MockClock};
58/// use chrono::{DateTime, Duration, Utc};
59///
60/// let clock = MockClock::new();
61///
62/// // Set to a specific time
63/// let fixed_time = DateTime::parse_from_rfc3339(
64///     "2024-01-01T00:00:00Z"
65/// ).unwrap().with_timezone(&Utc);
66/// clock.set_time(fixed_time);
67/// assert_eq!(clock.time(), fixed_time);
68///
69/// // Advance by 1 hour
70/// clock.add_duration(Duration::hours(1));
71/// assert_eq!(clock.time(), fixed_time + Duration::hours(1));
72///
73/// // Reset to initial state
74/// clock.reset();
75/// ```
76///
77#[derive(Debug, Clone)]
78pub struct MockClock {
79    inner: Arc<Mutex<MockClockInner>>,
80}
81
82#[derive(Debug)]
83struct MockClockInner {
84    /// The frozen time captured when this clock was created.
85    initial_time: i64,
86    /// The progression mode captured when this clock was created.
87    initial_progression: MockClockProgression,
88    /// The epoch time to use as the base (milliseconds since epoch).
89    epoch: i64,
90    /// The monotonic clock used when monotonic progression is enabled.
91    monotonic_clock: MonotonicClock,
92    /// The monotonic reading corresponding to `epoch`.
93    monotonic_base_millis: i64,
94    /// The current progression mode.
95    progression: MockClockProgression,
96    /// Additional milliseconds to add to the current time.
97    millis_to_add: i64,
98    /// Milliseconds to add on each call to `millis()`.
99    millis_to_add_each_time: i64,
100    /// Whether to automatically add `millis_to_add_each_time` on each call.
101    add_every_time: bool,
102}
103
104impl MockClock {
105    #[inline]
106    fn lock_inner(&self) -> MutexGuard<'_, MockClockInner> {
107        match self.inner.lock() {
108            Ok(guard) => guard,
109            Err(poisoned) => poisoned.into_inner(),
110        }
111    }
112
113    #[inline]
114    fn current_millis(inner: &MockClockInner) -> i64 {
115        let elapsed = if inner.progression.is_monotonic() {
116            inner
117                .monotonic_clock
118                .millis()
119                .saturating_sub(inner.monotonic_base_millis)
120        } else {
121            0
122        };
123        inner
124            .epoch
125            .saturating_add(elapsed)
126            .saturating_add(inner.millis_to_add)
127    }
128
129    #[inline]
130    fn rebase_at_current(inner: &mut MockClockInner) {
131        let current = Self::current_millis(inner);
132        inner.epoch = current;
133        inner.millis_to_add = 0;
134        inner.monotonic_base_millis = inner.monotonic_clock.millis();
135    }
136
137    /// Creates a new `MockClock`.
138    ///
139    /// The clock is initialized with the current system time and remains
140    /// frozen at that instant until adjusted by the control methods or switched
141    /// to monotonic progression.
142    ///
143    /// # Returns
144    ///
145    /// A new `MockClock` instance.
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// use qubit_clock::MockClock;
151    ///
152    /// let clock = MockClock::new();
153    /// ```
154    ///
155    pub fn new() -> Self {
156        Self::with_progression(MockClockProgression::Frozen)
157    }
158
159    /// Creates a new `MockClock` with the specified progression mode.
160    ///
161    /// The clock starts at the current system time. In
162    /// [`Frozen`](MockClockProgression::Frozen) mode, readings stay fixed until
163    /// explicitly advanced. In [`Monotonic`](MockClockProgression::Monotonic)
164    /// mode, readings progress naturally from the initial system time.
165    ///
166    /// # Arguments
167    ///
168    /// * `progression` - The initial progression mode.
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use qubit_clock::{MockClock, MockClockProgression};
174    ///
175    /// let clock = MockClock::with_progression(MockClockProgression::Monotonic);
176    /// assert_eq!(clock.progression(), MockClockProgression::Monotonic);
177    /// ```
178    ///
179    pub fn with_progression(progression: MockClockProgression) -> Self {
180        let initial_time = SystemClock::new().millis();
181        let monotonic_clock = MonotonicClock::new();
182        let monotonic_base_millis = monotonic_clock.millis();
183        MockClock {
184            inner: Arc::new(Mutex::new(MockClockInner {
185                initial_time,
186                initial_progression: progression,
187                epoch: initial_time,
188                monotonic_clock,
189                monotonic_base_millis,
190                progression,
191                millis_to_add: 0,
192                millis_to_add_each_time: 0,
193                add_every_time: false,
194            })),
195        }
196    }
197
198    /// Returns the current progression mode.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use qubit_clock::{MockClock, MockClockProgression};
204    ///
205    /// let clock = MockClock::new();
206    /// assert_eq!(clock.progression(), MockClockProgression::Frozen);
207    /// ```
208    pub fn progression(&self) -> MockClockProgression {
209        self.lock_inner().progression
210    }
211
212    /// Switches the clock progression mode without changing the current reading.
213    ///
214    /// The current logical reading is first folded into the clock's base state,
215    /// so changing between frozen and monotonic modes does not cause an
216    /// immediate time jump.
217    ///
218    /// # Arguments
219    ///
220    /// * `progression` - The new progression mode.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// use qubit_clock::{MockClock, MockClockProgression};
226    ///
227    /// let clock = MockClock::new();
228    /// clock.set_progression(MockClockProgression::Monotonic);
229    /// assert_eq!(clock.progression(), MockClockProgression::Monotonic);
230    /// ```
231    pub fn set_progression(&self, progression: MockClockProgression) {
232        let mut inner = self.lock_inner();
233        Self::rebase_at_current(&mut inner);
234        inner.progression = progression;
235    }
236
237    /// Returns whether monotonic progression is enabled.
238    ///
239    /// # Examples
240    ///
241    /// ```
242    /// use qubit_clock::MockClock;
243    ///
244    /// let clock = MockClock::new();
245    /// assert!(!clock.monotonic_progression_enabled());
246    /// ```
247    pub fn monotonic_progression_enabled(&self) -> bool {
248        self.progression().is_monotonic()
249    }
250
251    /// Enables or disables monotonic progression.
252    ///
253    /// This is a boolean convenience wrapper around
254    /// [`set_progression()`](MockClock::set_progression).
255    ///
256    /// # Arguments
257    ///
258    /// * `enabled` - `true` to use monotonic progression, `false` to freeze.
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use qubit_clock::MockClock;
264    ///
265    /// let clock = MockClock::new();
266    /// clock.set_monotonic_progression_enabled(true);
267    /// assert!(clock.monotonic_progression_enabled());
268    /// ```
269    pub fn set_monotonic_progression_enabled(&self, enabled: bool) {
270        let progression = if enabled {
271            MockClockProgression::Monotonic
272        } else {
273            MockClockProgression::Frozen
274        };
275        self.set_progression(progression);
276    }
277
278    /// Adds a fixed amount of milliseconds to the clock.
279    ///
280    /// # Arguments
281    ///
282    /// * `millis` - The number of milliseconds to add.
283    /// * `add_every_time` - If `true`, the specified milliseconds will be
284    ///   added on every call to [`millis()`](Clock::millis). If `false`, the
285    ///   milliseconds are added only once.
286    ///
287    /// # Examples
288    ///
289    /// ```
290    /// use qubit_clock::{Clock, MockClock};
291    ///
292    /// let clock = MockClock::new();
293    /// let before = clock.millis();
294    ///
295    /// // Add 1000ms once
296    /// clock.add_millis(1000, false);
297    /// assert_eq!(clock.millis(), before + 1000);
298    ///
299    /// // Add 100ms on every call
300    /// clock.add_millis(100, true);
301    /// let t1 = clock.millis();
302    /// let t2 = clock.millis();
303    /// assert_eq!(t2 - t1, 100);
304    /// ```
305    ///
306    pub fn add_millis(&self, millis: i64, add_every_time: bool) {
307        if add_every_time {
308            self.set_auto_advance_millis(millis);
309        } else {
310            self.advance_millis(millis);
311        }
312    }
313
314    /// Advances the clock by a fixed amount once.
315    ///
316    /// This method updates the offset used by [`millis()`](Clock::millis) and
317    /// [`time()`](Clock::time) without enabling auto-advance. If the
318    /// accumulated offset exceeds the `i64` range, it saturates at the nearest
319    /// boundary.
320    ///
321    /// # Arguments
322    ///
323    /// * `millis` - The milliseconds to add once.
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use qubit_clock::{Clock, MockClock};
329    ///
330    /// let clock = MockClock::new();
331    /// let before = clock.millis();
332    /// clock.advance_millis(1000);
333    /// assert_eq!(clock.millis(), before + 1000);
334    /// ```
335    pub fn advance_millis(&self, millis: i64) {
336        let mut inner = self.lock_inner();
337        inner.millis_to_add = inner.millis_to_add.saturating_add(millis);
338    }
339
340    /// Enables auto-advance on each read operation.
341    ///
342    /// After calling this method, each call to [`millis()`](Clock::millis) or
343    /// [`time()`](Clock::time) returns the current logical time and advances
344    /// the next read by `millis`.
345    ///
346    /// # Arguments
347    ///
348    /// * `millis` - The milliseconds to advance on each read.
349    ///
350    /// # Examples
351    ///
352    /// ```
353    /// use qubit_clock::{Clock, MockClock};
354    ///
355    /// let clock = MockClock::new();
356    /// clock.set_auto_advance_millis(100);
357    /// let t1 = clock.millis();
358    /// let t2 = clock.millis();
359    /// assert_eq!(t2 - t1, 100);
360    /// ```
361    pub fn set_auto_advance_millis(&self, millis: i64) {
362        let mut inner = self.lock_inner();
363        inner.millis_to_add_each_time = millis;
364        inner.add_every_time = true;
365    }
366
367    /// Disables auto-advance behavior.
368    ///
369    /// This method clears the per-read advance setting. Subsequent read
370    /// operations will no longer mutate the clock state.
371    ///
372    /// # Examples
373    ///
374    /// ```
375    /// use qubit_clock::{Clock, MockClock};
376    ///
377    /// let clock = MockClock::new();
378    /// clock.set_auto_advance_millis(100);
379    /// let _ = clock.millis();
380    /// clock.clear_auto_advance();
381    /// let t1 = clock.millis();
382    /// let t2 = clock.millis();
383    /// assert_eq!(t2, t1);
384    /// ```
385    pub fn clear_auto_advance(&self) {
386        let mut inner = self.lock_inner();
387        inner.millis_to_add_each_time = 0;
388        inner.add_every_time = false;
389    }
390}
391
392impl Default for MockClock {
393    #[inline]
394    fn default() -> Self {
395        Self::new()
396    }
397}
398
399impl Clock for MockClock {
400    fn millis(&self) -> i64 {
401        let mut inner = self.lock_inner();
402        let result = Self::current_millis(&inner);
403
404        if inner.add_every_time {
405            inner.millis_to_add = inner
406                .millis_to_add
407                .saturating_add(inner.millis_to_add_each_time);
408        }
409
410        result
411    }
412}
413
414impl ControllableClock for MockClock {
415    fn set_time(&self, instant: DateTime<Utc>) {
416        let mut inner = self.lock_inner();
417        inner.epoch = instant.timestamp_millis();
418        inner.monotonic_base_millis = inner.monotonic_clock.millis();
419        inner.millis_to_add = 0;
420    }
421
422    #[inline]
423    fn add_duration(&self, duration: Duration) {
424        let millis = duration.num_milliseconds();
425        self.advance_millis(millis);
426    }
427
428    fn reset(&self) {
429        let mut inner = self.lock_inner();
430        inner.epoch = inner.initial_time;
431        inner.progression = inner.initial_progression;
432        inner.monotonic_base_millis = inner.monotonic_clock.millis();
433        inner.millis_to_add = 0;
434        inner.millis_to_add_each_time = 0;
435        inner.add_every_time = false;
436    }
437}