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