fake_instant/
lib.rs

1// Copyright 2018 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
4// http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
5// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
6// modified, or distributed except according to those terms. Please review the Licences for the
7// specific language governing permissions and limitations relating to use of the SAFE Network
8// Software.
9
10//! # fake_clock
11//!
12//! A crate providing a virtual clock mimicking `std::time::Instant`'s interface, enabling full
13//! control over the flow of time during testing.
14
15// For explanation of lint checks, run `rustc -W help`.
16#![forbid(
17    bad_style,
18    arithmetic_overflow,
19    mutable_transmutes,
20    no_mangle_const_items,
21    unknown_crate_types
22)]
23#![deny(
24    missing_docs,
25    overflowing_literals,
26    unsafe_code,
27    warnings,
28    trivial_casts,
29    trivial_numeric_casts,
30    unused_extern_crates,
31    unused_import_braces,
32    unused_qualifications,
33    unused_must_use
34)]
35
36use std::cell::Cell;
37use std::convert::TryInto;
38use std::ops::{Add, AddAssign, Sub, SubAssign};
39use std::time::Duration;
40
41thread_local! {
42    static FAKE_TIME: Cell<u64> = Default::default();
43}
44
45/// Struct representing a fake instant.
46#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
47pub struct FakeInstant {
48    time_created: u64,
49}
50
51impl FakeInstant {
52    /// Sets the thread-local fake time to the given value, returning the old
53    /// fake time.
54    pub fn set_time(time: u64) -> u64 {
55        FAKE_TIME.with(|c| c.replace(time))
56    }
57
58    /// Advances the thread-local fake time by the given amount of
59    /// milliseconds, returns the new fake time.
60    pub fn advance_time(millis: u64) -> u64 {
61        FAKE_TIME.with(|c| {
62            let new_time = c.get() + millis;
63            c.set(new_time);
64            new_time
65        })
66    }
67
68    /// Returns the current thread-local fake time.
69    pub fn time() -> u64 {
70        FAKE_TIME.with(|c| c.get())
71    }
72
73    /// Returns a `FakeInstant` instance representing the current thread-local
74    /// fake time.
75    pub fn now() -> Self {
76        let time = Self::time();
77        Self { time_created: time }
78    }
79
80    /// Returns the duration that passed between `self` and `earlier`.
81    ///
82    /// Previously this panicked when `earlier` was later than `self`.
83    /// Currently this method returns a `Duration` of zero in that case. Future
84    /// versions may reintroduce the panic in some circumstances.
85    pub fn duration_since(self, earlier: Self) -> Duration {
86        self.checked_duration_since(earlier).unwrap_or_default()
87    }
88
89    /// Returns the amount of fake time elapsed from another `FakeInstant` to
90    /// this one, or `None` if that `FakeInstant` is earlier than this one.
91    pub fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
92        self.time_created
93            .checked_sub(earlier.time_created)
94            .map(Duration::from_millis)
95    }
96
97    /// Returns the amount of fake time elapsed from another `FakeInstant` to
98    /// this one, or zero duration if that `FakeInstant` is earlier than this
99    /// one.
100    pub fn saturating_duration_since(&self, earlier: Self) -> Duration {
101        self.checked_duration_since(earlier).unwrap_or_default()
102    }
103
104    /// Returns the duration of time between the creation of `self` until
105    /// the thread-local fake time.
106    ///
107    /// Sending a `FakeInstant` across threads will result in this being
108    /// computed relative to the destination thread's fake time.
109    ///
110    /// Previously this panicked when the current fakse time was earlier than
111    /// `self`. Currently this method returns a `Duration` of zero in that
112    /// case. Future versions may reintroduce the panic in some circumstances.
113    pub fn elapsed(self) -> Duration {
114        Duration::from_millis(Self::time() - self.time_created)
115    }
116
117    /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be
118    /// represented as `FakeInstant`, `None` otherwise.
119    pub fn checked_add(&self, duration: Duration) -> Option<Self> {
120        duration
121            .as_millis()
122            .checked_add(self.time_created as u128)
123            .and_then(|time| time.try_into().ok())
124            .map(|time| Self { time_created: time })
125    }
126
127    /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be
128    /// represented as `FakeInstant`, `None` otherwise.
129    pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
130        duration
131            .as_millis()
132            .try_into()
133            .ok()
134            .and_then(|dur| self.time_created.checked_sub(dur))
135            .map(|time| Self { time_created: time })
136    }
137}
138
139impl Add<Duration> for FakeInstant {
140    type Output = Self;
141    fn add(self, other: Duration) -> Self {
142        self.checked_add(other)
143            .expect("overflow when adding duration to instant")
144    }
145}
146
147impl AddAssign<Duration> for FakeInstant {
148    fn add_assign(&mut self, rhs: Duration) {
149        *self = *self + rhs;
150    }
151}
152
153impl Sub<Duration> for FakeInstant {
154    type Output = Self;
155    fn sub(self, other: Duration) -> Self {
156        self.checked_sub(other)
157            .expect("overflow when subtracting duration from instant")
158    }
159}
160
161impl SubAssign<Duration> for FakeInstant {
162    fn sub_assign(&mut self, rhs: Duration) {
163        *self = *self - rhs;
164    }
165}
166
167impl Sub<Self> for FakeInstant {
168    type Output = Duration;
169    fn sub(self, other: Self) -> Duration {
170        self.duration_since(other)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_auto_traits() {
180        use std::any::Any;
181        use std::panic::{RefUnwindSafe, UnwindSafe};
182
183        fn check_traits<T: 'static + RefUnwindSafe + Send + Sync + Unpin + UnwindSafe + Any>() {}
184        check_traits::<FakeInstant>();
185    }
186
187    #[test]
188    fn test_advance_time() {
189        const DUR: u64 = 5300;
190        let clock = FakeInstant::now();
191        FakeInstant::advance_time(DUR);
192        assert_eq!(Duration::from_millis(DUR), clock.elapsed());
193    }
194
195    #[test]
196    fn test_checked_add_some() {
197        FakeInstant::set_time(0);
198
199        let inst = FakeInstant::now();
200        let dur = Duration::from_millis(std::u64::MAX);
201        FakeInstant::set_time(std::u64::MAX);
202
203        assert_eq!(Some(FakeInstant::now()), inst.checked_add(dur));
204    }
205
206    #[test]
207    fn test_checked_add_none() {
208        FakeInstant::set_time(1);
209
210        let inst = FakeInstant::now();
211        let dur = Duration::from_millis(std::u64::MAX);
212
213        assert_eq!(None, inst.checked_add(dur));
214    }
215
216    #[test]
217    fn test_checked_sub_some() {
218        FakeInstant::set_time(std::u64::MAX);
219
220        let inst = FakeInstant::now();
221        let dur = Duration::from_millis(std::u64::MAX);
222        FakeInstant::set_time(0);
223
224        assert_eq!(Some(FakeInstant::now()), inst.checked_sub(dur));
225    }
226
227    #[test]
228    fn test_checked_sub_none() {
229        FakeInstant::set_time(std::u64::MAX - 1);
230
231        let inst = FakeInstant::now();
232        let dur = Duration::from_millis(std::u64::MAX);
233
234        assert_eq!(None, inst.checked_sub(dur));
235    }
236
237    #[test]
238    fn checked_duration_since_some() {
239        FakeInstant::set_time(0);
240        let inst0 = FakeInstant::now();
241        FakeInstant::set_time(std::u64::MAX);
242        let inst_max = FakeInstant::now();
243
244        assert_eq!(
245            Some(Duration::from_millis(std::u64::MAX)),
246            inst_max.checked_duration_since(inst0)
247        );
248    }
249
250    #[test]
251    fn checked_duration_since_none() {
252        FakeInstant::set_time(1);
253        let inst1 = FakeInstant::now();
254        FakeInstant::set_time(0);
255        let inst0 = FakeInstant::now();
256
257        assert_eq!(None, inst0.checked_duration_since(inst1));
258    }
259
260    #[test]
261    fn saturating_duration_since_nonzero() {
262        FakeInstant::set_time(0);
263        let inst0 = FakeInstant::now();
264        FakeInstant::set_time(std::u64::MAX);
265        let inst_max = FakeInstant::now();
266
267        assert_eq!(
268            Duration::from_millis(std::u64::MAX),
269            inst_max.saturating_duration_since(inst0)
270        );
271    }
272
273    #[test]
274    fn saturating_duration_since_zero() {
275        FakeInstant::set_time(1);
276        let inst1 = FakeInstant::now();
277        FakeInstant::set_time(0);
278        let inst0 = FakeInstant::now();
279
280        assert_eq!(Duration::new(0, 0), inst0.saturating_duration_since(inst1));
281    }
282
283    #[test]
284    fn test_debug() {
285        let inst = FakeInstant::now();
286        assert_eq!("FakeInstant { time_created: 0 }", format!("{:?}", inst));
287    }
288
289    #[test]
290    fn test_threads() {
291        FakeInstant::set_time(200);
292        let inst1 = FakeInstant::now();
293        assert!(std::thread::spawn(move || {
294            FakeInstant::set_time(500);
295            let inst2 = FakeInstant::now();
296            assert_eq!(Duration::from_millis(300), inst1.elapsed());
297            assert_eq!(Duration::from_millis(0), inst2.elapsed());
298        })
299        .join()
300        .is_ok());
301    }
302}