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}