neo4j/
time.rs

1// Copyright Rouven Bauer
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Internal module to enable mocking of time.
16
17use std::fmt::Debug;
18use std::ops::{Add, AddAssign};
19use std::time::{Duration, Instant as StdInstant};
20
21#[cfg(feature = "_internal_testkit_backend")]
22#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23pub struct Instant(StdInstant);
24#[cfg(not(feature = "_internal_testkit_backend"))]
25#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub(crate) struct Instant(StdInstant);
27
28impl Debug for Instant {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        self.0.fmt(f)
31    }
32}
33
34impl Add<Duration> for Instant {
35    type Output = Self;
36
37    fn add(self, other: Duration) -> Self::Output {
38        Self(self.0 + other)
39    }
40}
41
42impl AddAssign<Duration> for Instant {
43    fn add_assign(&mut self, other: Duration) {
44        self.0 += other;
45    }
46}
47
48#[allow(dead_code)] // make API more consistent between feature flags
49impl Instant {
50    #[inline]
51    pub fn new(inner: StdInstant) -> Self {
52        Self(inner)
53    }
54
55    #[inline]
56    pub fn unmockable_now() -> Self {
57        Self(StdInstant::now())
58    }
59
60    #[inline]
61    pub fn raw(&self) -> StdInstant {
62        self.0
63    }
64
65    #[inline]
66    pub fn saturating_duration_since(&self, earlier: Self) -> Duration {
67        self.0.saturating_duration_since(earlier.0)
68    }
69
70    #[inline]
71    pub fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
72        self.0.checked_duration_since(earlier.0)
73    }
74
75    #[inline]
76    pub fn checked_add(&self, duration: Duration) -> Option<Self> {
77        self.0.checked_add(duration).map(Self)
78    }
79
80    #[inline]
81    pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
82        self.0.checked_sub(duration).map(Self)
83    }
84}
85
86#[cfg(not(feature = "_internal_testkit_backend"))]
87impl Instant {
88    #[inline]
89    pub fn now() -> Self {
90        Self(StdInstant::now())
91    }
92
93    #[inline]
94    pub fn elapsed(&self) -> Duration {
95        self.0.elapsed()
96    }
97}
98
99#[cfg(feature = "_internal_testkit_backend")]
100mod mockable_time {
101    use super::*;
102    use std::sync::OnceLock;
103
104    use parking_lot::RwLock;
105
106    static MOCKED_TIME: OnceLock<RwLock<Option<StdInstant>>> = OnceLock::new();
107
108    #[inline]
109    fn mocked_time() -> &'static RwLock<Option<StdInstant>> {
110        MOCKED_TIME.get_or_init(Default::default)
111    }
112
113    impl Instant {
114        pub fn now() -> Self {
115            Self(mocked_time().read().unwrap_or_else(StdInstant::now))
116        }
117
118        #[inline]
119        pub fn elapsed(&self) -> Duration {
120            Self::now().0 - self.0
121        }
122    }
123
124    pub fn freeze_time() -> StdInstant {
125        let mut mocked_time = mocked_time().write();
126        mocked_time.replace(StdInstant::now());
127        mocked_time.unwrap()
128    }
129
130    pub fn tick(duration: Duration) -> Result<StdInstant, String> {
131        let mut mocked_time = mocked_time().write();
132        *mocked_time = mocked_time.map(|mocked_time| mocked_time + duration);
133        mocked_time
134            .as_ref()
135            .copied()
136            .ok_or_else(|| String::from("cannot tick time if not frozen"))
137    }
138
139    pub fn unfreeze_time() -> Result<(), String> {
140        let mut mocked_time = mocked_time().write();
141        mocked_time
142            .take()
143            .map(drop)
144            .ok_or_else(|| String::from("cannot unfreeze time if not frozen"))
145    }
146}
147
148#[cfg(feature = "_internal_testkit_backend")]
149pub use mockable_time::*;