quanta/mock.rs
1use crossbeam_utils::atomic::AtomicCell;
2use std::{sync::Arc, time::Duration};
3
4/// Type which can be converted into a nanosecond representation.
5///
6/// This allows users of [`Mock`] to increment/decrement the time both with raw
7/// integer values and the more convenient [`Duration`] type.
8pub trait IntoNanoseconds {
9 /// Consumes this value, converting it to a nanosecond representation.
10 fn into_nanos(self) -> u64;
11}
12
13impl IntoNanoseconds for u64 {
14 fn into_nanos(self) -> u64 {
15 self
16 }
17}
18
19impl IntoNanoseconds for Duration {
20 fn into_nanos(self) -> u64 {
21 self.as_nanos() as u64
22 }
23}
24
25/// Controllable time source for use in tests.
26///
27/// A mocked clock allows the caller to adjust the given time backwards and forwards by whatever
28/// amount they choose. While [`Clock`](crate::Clock) promises monotonic values for normal readings,
29/// when running in mocked mode, these guarantees do not apply: the given `Clock`/`Mock` pair are
30/// directly coupled.
31///
32/// This can be useful for not only testing code that depends on the passage of time, but also for
33/// testing that code can handle large shifts in time.
34#[derive(Debug, Clone)]
35pub struct Mock {
36 offset: Arc<AtomicCell<u64>>,
37}
38
39impl Mock {
40 pub(crate) fn new() -> Self {
41 Self {
42 offset: Arc::new(AtomicCell::new(0)),
43 }
44 }
45
46 /// Increments the time by the given amount.
47 pub fn increment<N: IntoNanoseconds>(&self, amount: N) {
48 let amount = amount.into_nanos();
49 self.offset
50 .fetch_update(|current| Some(current + amount))
51 .expect("should never return an error");
52 }
53
54 /// Decrements the time by the given amount.
55 pub fn decrement<N: IntoNanoseconds>(&self, amount: N) {
56 let amount = amount.into_nanos();
57 self.offset
58 .fetch_update(|current| Some(current - amount))
59 .expect("should never return an error");
60 }
61
62 /// Gets the current value of this `Mock`.
63 pub fn value(&self) -> u64 {
64 self.offset.load()
65 }
66}