Skip to main content

torrust_clock/clock/stopped/
mod.rs

1/// Trait for types that can be used as a timestamp clock stopped
2/// at a given time.
3#[allow(clippy::module_name_repetitions)]
4pub struct StoppedClock {}
5
6#[allow(clippy::module_name_repetitions)]
7pub trait Stopped: clock::Time {
8    /// It sets the clock to a given time.
9    fn local_set(unix_time: &DurationSinceUnixEpoch);
10
11    /// It sets the clock to the Unix Epoch.
12    fn local_set_to_unix_epoch() {
13        Self::local_set(&DurationSinceUnixEpoch::ZERO);
14    }
15
16    /// It sets the clock to the time the application started.
17    fn local_set_to_app_start_time();
18
19    /// It sets the clock to the current system time.
20    fn local_set_to_system_time_now();
21
22    /// It adds a `Duration` to the clock.
23    ///
24    /// # Errors
25    ///
26    /// Will return `IntErrorKind` if `duration` would overflow the internal `Duration`.
27    fn local_add(duration: &Duration) -> Result<(), IntErrorKind>;
28
29    /// It subtracts a `Duration` from the clock.
30    /// # Errors
31    ///
32    /// Will return `IntErrorKind` if `duration` would underflow the internal `Duration`.
33    fn local_sub(duration: &Duration) -> Result<(), IntErrorKind>;
34
35    /// It resets the clock to default fixed time that is application start time (or the unix epoch when testing).
36    fn local_reset();
37}
38
39use std::num::IntErrorKind;
40use std::time::Duration;
41
42use super::{DurationSinceUnixEpoch, Time};
43use crate::clock;
44
45impl Time for clock::Stopped {
46    fn now() -> DurationSinceUnixEpoch {
47        detail::FIXED_TIME.with(|time| {
48            return *time.borrow();
49        })
50    }
51
52    fn dbg_clock_type() -> String {
53        "Stopped".to_owned()
54    }
55}
56
57impl Stopped for clock::Stopped {
58    fn local_set(unix_time: &DurationSinceUnixEpoch) {
59        detail::FIXED_TIME.with(|time| {
60            *time.borrow_mut() = *unix_time;
61        });
62    }
63
64    fn local_set_to_app_start_time() {
65        Self::local_set(&detail::get_app_start_time());
66    }
67
68    fn local_set_to_system_time_now() {
69        Self::local_set(&detail::get_app_start_time());
70    }
71
72    fn local_add(duration: &Duration) -> Result<(), IntErrorKind> {
73        detail::FIXED_TIME.with(|time| {
74            let time_borrowed = *time.borrow();
75            *time.borrow_mut() = match time_borrowed.checked_add(*duration) {
76                Some(time) => time,
77                None => {
78                    return Err(IntErrorKind::PosOverflow);
79                }
80            };
81            Ok(())
82        })
83    }
84
85    fn local_sub(duration: &Duration) -> Result<(), IntErrorKind> {
86        detail::FIXED_TIME.with(|time| {
87            let time_borrowed = *time.borrow();
88            *time.borrow_mut() = match time_borrowed.checked_sub(*duration) {
89                Some(time) => time,
90                None => {
91                    return Err(IntErrorKind::NegOverflow);
92                }
93            };
94            Ok(())
95        })
96    }
97
98    fn local_reset() {
99        Self::local_set(&detail::get_default_fixed_time());
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use std::thread;
106    use std::time::Duration;
107
108    use crate::DurationSinceUnixEpoch;
109    use crate::clock::stopped::Stopped as _;
110    use crate::clock::{Stopped, Time, Working};
111
112    #[test]
113    fn it_should_default_to_zero_when_testing() {
114        assert_eq!(Stopped::now(), DurationSinceUnixEpoch::ZERO);
115    }
116
117    #[test]
118    fn it_should_possible_to_set_the_time() {
119        // Check we start with ZERO.
120        assert_eq!(Stopped::now(), Duration::ZERO);
121
122        // Set to Current Time and Check
123        let timestamp = Working::now();
124        Stopped::local_set(&timestamp);
125        assert_eq!(Stopped::now(), timestamp);
126
127        // Elapse the Current Time and Check
128        Stopped::local_add(&timestamp).unwrap();
129        assert_eq!(Stopped::now(), timestamp + timestamp);
130
131        // Reset to ZERO and Check
132        Stopped::local_reset();
133        assert_eq!(Stopped::now(), Duration::ZERO);
134    }
135
136    #[test]
137    fn it_should_default_to_zero_on_thread_exit() {
138        assert_eq!(Stopped::now(), Duration::ZERO);
139        let after5 = Working::now_add(&Duration::from_secs(5)).unwrap();
140        Stopped::local_set(&after5);
141        assert_eq!(Stopped::now(), after5);
142
143        let t = thread::spawn(move || {
144            // each thread starts out with the initial value of ZERO
145            assert_eq!(Stopped::now(), Duration::ZERO);
146
147            // and gets set to the current time.
148            let timestamp = Working::now();
149            Stopped::local_set(&timestamp);
150            assert_eq!(Stopped::now(), timestamp);
151        });
152
153        // wait for the thread to complete and bail out on panic
154        t.join().unwrap();
155
156        // we retain our original value of current time + 5sec despite the child thread
157        assert_eq!(Stopped::now(), after5);
158
159        // Reset to ZERO and Check
160        Stopped::local_reset();
161        assert_eq!(Stopped::now(), Duration::ZERO);
162    }
163}
164
165mod detail {
166    use std::cell::RefCell;
167    use std::time::SystemTime;
168
169    use crate::{DurationSinceUnixEpoch, static_time};
170
171    thread_local!(pub static FIXED_TIME: RefCell<DurationSinceUnixEpoch>   = RefCell::new(get_default_fixed_time()));
172
173    pub fn get_app_start_time() -> DurationSinceUnixEpoch {
174        (*static_time::TIME_AT_APP_START)
175            .duration_since(SystemTime::UNIX_EPOCH)
176            .unwrap()
177    }
178
179    #[cfg(not(test))]
180    pub fn get_default_fixed_time() -> DurationSinceUnixEpoch {
181        get_app_start_time()
182    }
183
184    #[cfg(test)]
185    pub fn get_default_fixed_time() -> DurationSinceUnixEpoch {
186        DurationSinceUnixEpoch::ZERO
187    }
188
189    #[cfg(test)]
190    mod tests {
191        use std::time::Duration;
192
193        use crate::clock::stopped::detail::{get_app_start_time, get_default_fixed_time};
194
195        #[test]
196        fn it_should_get_the_zero_start_time_when_testing() {
197            assert_eq!(get_default_fixed_time(), Duration::ZERO);
198        }
199
200        #[test]
201        fn it_should_get_app_start_time() {
202            const TIME_AT_WRITING_THIS_TEST: Duration = Duration::new(1_662_983_731, 22312);
203            assert!(get_app_start_time() > TIME_AT_WRITING_THIS_TEST);
204        }
205    }
206}