unc_primitives/
static_clock.rs

1/// Provides structs used for getting time.
2///
3/// DEPRECATION WARNING: This module is DEPRECATED. Use `unc_primitives::time` instead.
4///
5/// WARNING WARNING WARNING
6/// WARNING WARNING WARNING
7/// Use at your own risk. The implementation is not complete, we have a places in code not mocked properly.
8/// For example, it's possible to call `::elapsed()` on `Instant` returned by `Instant::now()`.
9/// We use that that function, throughout the code, and the current mocking of `Instant` is not done properly.
10///
11/// Example:
12/// ```rust, ignore
13/// fn some_production_function() {
14///     let start = Clock::instant();
15///     // some computation
16///     let end = Clock::instant();
17///     assert!(end.duration_since(start) == Duration::from_secs(1));
18/// }
19///
20/// Test:
21/// fn test() {
22///     let mock_clock_guard = MockClockGuard::default();
23///     mock_clock_guard.add_instant(Instant::now());
24///     mock_clock_guard.add_instant(Instant::now() + Duration::from_secs(1));
25///
26///     // This won't crash anymore as long as mock works.
27///     some_production_function();
28/// }
29/// ```
30use chrono;
31use chrono::DateTime;
32use chrono::Utc;
33use std::cell::RefCell;
34use std::collections::VecDeque;
35use std::default::Default;
36use std::time::Instant;
37
38#[derive(Default)]
39struct MockClockPerState {
40    /// List of timestamps, we will return one timestamp for each call of `Clock::utc()`.
41    utc_list: VecDeque<DateTime<Utc>>,
42    /// List of timestamps, we will return one timestamp to each call of `Clock::instant()`.
43    instant_list: VecDeque<Instant>,
44    /// Number of times `Clock::utc()` method was called since we started mocking.
45    utc_call_count: u64,
46    /// Number of times `Clock::instant()` method was called since we started mocking.
47    instant_call_count: u64,
48}
49
50/// Stores the mocking state.
51#[derive(Default)]
52struct MockClockPerThread {
53    mock: Option<MockClockPerState>,
54}
55
56impl MockClockPerThread {
57    fn with<F, T>(f: F) -> T
58    where
59        F: FnOnce(&mut MockClockPerThread) -> T,
60    {
61        thread_local! {
62            static INSTANCE: RefCell<MockClockPerThread> = RefCell::default()
63        }
64        INSTANCE.with(|it| f(&mut *it.borrow_mut()))
65    }
66}
67
68pub struct MockClockGuard {}
69
70impl MockClockGuard {
71    /// Adds timestamp to queue, it will be returned in `Self::utc()`.
72    pub fn add_utc(&self, mock_date: DateTime<chrono::Utc>) {
73        MockClockPerThread::with(|clock| match &mut clock.mock {
74            Some(clock) => {
75                clock.utc_list.push_back(mock_date);
76            }
77            None => {
78                panic!("Use MockClockGuard in your test");
79            }
80        });
81    }
82
83    /// Adds timestamp to queue, it will be returned in `Self::utc()`.
84    pub fn add_instant(&self, mock_date: Instant) {
85        MockClockPerThread::with(|clock| match &mut clock.mock {
86            Some(clock) => {
87                clock.instant_list.push_back(mock_date);
88            }
89            None => {
90                panic!("Use MockClockGuard in your test");
91            }
92        });
93    }
94
95    /// Returns number of calls  to `Self::utc` since `Self::mock()` was called.
96    pub fn utc_call_count(&self) -> u64 {
97        MockClockPerThread::with(|clock| match &mut clock.mock {
98            Some(clock) => clock.utc_call_count,
99            None => {
100                panic!("Use MockClockGuard in your test");
101            }
102        })
103    }
104
105    /// Returns number of calls  to `Self::instant` since `Self::mock()` was called.
106    pub fn instant_call_count(&self) -> u64 {
107        MockClockPerThread::with(|clock| match &mut clock.mock {
108            Some(clock) => clock.instant_call_count,
109            None => {
110                panic!("Use MockClockGuard in your test");
111            }
112        })
113    }
114}
115
116impl Default for MockClockGuard {
117    fn default() -> Self {
118        StaticClock::set_mock();
119        Self {}
120    }
121}
122
123impl Drop for MockClockGuard {
124    fn drop(&mut self) {
125        StaticClock::reset();
126    }
127}
128
129/// Depreciated.
130pub struct StaticClock {}
131
132impl StaticClock {
133    /// Turns the mocking logic on.
134    fn set_mock() {
135        MockClockPerThread::with(|clock| {
136            assert!(clock.mock.is_none());
137            clock.mock = Some(MockClockPerState::default())
138        })
139    }
140
141    /// Resets mocks to default state.
142    fn reset() {
143        MockClockPerThread::with(|clock| clock.mock = None);
144    }
145
146    /// This methods gets current time as `std::Instant`
147    /// unless it's mocked, then returns time added by `Self::add_utc(...)`
148    pub fn instant() -> Instant {
149        MockClockPerThread::with(|clock| match &mut clock.mock {
150            Some(clock) => {
151                clock.instant_call_count += 1;
152                let x = clock.instant_list.pop_front();
153                match x {
154                    Some(t) => t,
155                    None => {
156                        panic!("Mock clock run out of samples");
157                    }
158                }
159            }
160            None => Instant::now(),
161        })
162    }
163
164    /// This methods gets current time as `std::Instant`
165    /// unless it's mocked, then returns time added by `Self::add_instant(...)`
166    pub fn utc() -> DateTime<chrono::Utc> {
167        MockClockPerThread::with(|clock| match &mut clock.mock {
168            Some(clock) => {
169                clock.utc_call_count += 1;
170                let x = clock.utc_list.pop_front();
171                match x {
172                    Some(t) => t,
173                    None => {
174                        panic!("Mock clock run out of samples");
175                    }
176                }
177            }
178            None => chrono::Utc::now(),
179        })
180    }
181}