Skip to main content

qubit_clock/mock/
mock_time.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//! Facade for a complete mock time runtime.
11
12use std::time::Duration as StdDuration;
13
14use chrono::{
15    DateTime,
16    Utc,
17};
18
19use crate::sleep::MockSleeper;
20use crate::{
21    ControllableClock,
22    MockClock,
23    MockTimeError,
24    MockTimeline,
25};
26
27/// A complete mock time runtime sharing one timeline across clocks and sleepers.
28///
29/// `MockTime` is the recommended entry point for tests that need both "what
30/// time is it?" and "how long should this operation sleep?" to use the same
31/// deterministic time source. It constructs one [`MockTimeline`], then creates
32/// a [`MockClock`] and [`MockSleeper`](crate::sleep::MockSleeper) over that
33/// timeline.
34///
35/// Advancing the runtime through [`advance`](Self::advance) advances every
36/// component derived from it. This avoids a common testing bug where a mock
37/// clock reports one logical time while a mock sleeper or monitor waits on a
38/// different logical time source.
39///
40/// Wall-clock anchoring is separate from elapsed mock time. Calling
41/// [`set_time`](Self::set_time) changes the UTC value read at the current
42/// timeline instant, but it does not advance or rewind the timeline. Calling
43/// [`reset`](Self::reset) restores both the elapsed timeline and the clock
44/// anchor, and fails if any timeline waiter is active.
45///
46/// # Example
47///
48/// ```
49/// use std::time::Duration;
50///
51/// use qubit_clock::{Clock, MockTime};
52///
53/// let mock = MockTime::unix_epoch();
54/// let clock = mock.clock();
55///
56/// assert_eq!(0, clock.millis());
57///
58/// mock.advance(Duration::from_millis(250));
59///
60/// assert_eq!(250, clock.millis());
61/// assert_eq!(Duration::from_millis(250), mock.elapsed());
62/// ```
63#[derive(Debug, Clone)]
64pub struct MockTime {
65    timeline: MockTimeline,
66    clock: MockClock,
67    sleeper: MockSleeper,
68}
69
70impl MockTime {
71    /// Creates a mock runtime anchored at a UTC time.
72    ///
73    /// # Parameters
74    /// - `start`: UTC reading returned before the timeline advances.
75    ///
76    /// # Returns
77    /// A mock runtime with a clock and sleeper sharing one timeline.
78    #[must_use]
79    pub fn at(start: DateTime<Utc>) -> Self {
80        let timeline = MockTimeline::new();
81        let clock = MockClock::with_timeline(start, timeline.clone());
82        let sleeper = MockSleeper::with_timeline(timeline.clone());
83        Self {
84            timeline,
85            clock,
86            sleeper,
87        }
88    }
89
90    /// Creates a mock runtime anchored at the Unix epoch.
91    ///
92    /// # Returns
93    /// A mock runtime starting at `1970-01-01T00:00:00Z`.
94    #[must_use]
95    pub fn unix_epoch() -> Self {
96        Self::at(DateTime::<Utc>::UNIX_EPOCH)
97    }
98
99    /// Returns the shared timeline.
100    ///
101    /// # Returns
102    /// The timeline that drives all components in this runtime.
103    #[inline]
104    pub fn timeline(&self) -> MockTimeline {
105        self.timeline.clone()
106    }
107
108    /// Returns a clock view over the shared timeline.
109    ///
110    /// # Returns
111    /// Cloneable mock clock.
112    #[inline]
113    pub fn clock(&self) -> MockClock {
114        self.clock.clone()
115    }
116
117    /// Returns a sleeper view over the shared timeline.
118    ///
119    /// # Returns
120    /// Cloneable mock sleeper.
121    #[inline]
122    pub fn sleeper(&self) -> MockSleeper {
123        self.sleeper.clone()
124    }
125
126    /// Returns elapsed time on the shared timeline.
127    ///
128    /// # Returns
129    /// Elapsed mock time.
130    #[inline]
131    pub fn elapsed(&self) -> StdDuration {
132        self.timeline.elapsed()
133    }
134
135    /// Advances the shared timeline.
136    ///
137    /// # Parameters
138    /// - `duration`: Duration to add to mock elapsed time.
139    #[inline]
140    pub fn advance(&self, duration: StdDuration) {
141        self.timeline.advance(duration);
142    }
143
144    /// Reanchors the runtime clock at the current timeline instant.
145    ///
146    /// # Parameters
147    /// - `instant`: New UTC time returned at the current timeline instant.
148    #[inline]
149    pub fn set_time(&self, instant: DateTime<Utc>) {
150        self.clock.set_time(instant);
151    }
152
153    /// Resets the timeline and clock anchor to the runtime's initial state.
154    ///
155    /// # Returns
156    /// `Ok(())` when reset succeeds.
157    ///
158    /// # Errors
159    /// Returns [`MockTimeError::ActiveWaiters`] when timeline waiters are active.
160    pub fn reset(&self) -> Result<(), MockTimeError> {
161        self.timeline.reset()?;
162        self.clock.reset_wall_anchor();
163        Ok(())
164    }
165}