tiny_counter/
clock.rs

1use std::sync::{Arc, Mutex};
2
3use chrono::{DateTime, Duration, Utc};
4
5use crate::Clock;
6
7/// Clock implementations for time abstraction.
8///
9/// System clock that returns the current real time.
10#[derive(Clone)]
11pub struct SystemClock;
12
13impl SystemClock {
14    /// Creates a new SystemClock wrapped in Arc for sharing.
15    #[allow(clippy::new_ret_no_self)]
16    pub fn new() -> Arc<dyn Clock> {
17        Arc::new(Self)
18    }
19}
20
21impl Default for SystemClock {
22    fn default() -> Self {
23        Self
24    }
25}
26
27impl Clock for SystemClock {
28    fn now(&self) -> DateTime<Utc> {
29        Utc::now()
30    }
31}
32
33/// Test clock that allows manual time control for testing.
34#[derive(Clone)]
35pub struct TestClock {
36    pub(crate) time: Arc<Mutex<DateTime<Utc>>>,
37}
38
39impl TestClock {
40    /// Creates a new TestClock at the current time.
41    #[allow(clippy::new_ret_no_self)]
42    pub fn new() -> Arc<dyn Clock> {
43        Arc::new(Self {
44            time: Arc::new(Mutex::new(Utc::now())),
45        })
46    }
47
48    /// Creates a new TestClock at a specific time.
49    pub fn new_at(time: DateTime<Utc>) -> Arc<dyn Clock> {
50        Arc::new(Self {
51            time: Arc::new(Mutex::new(time)),
52        })
53    }
54
55    /// Creates a concrete TestClock instance for direct use in tests.
56    pub fn build_for_testing() -> Self {
57        Self {
58            time: Arc::new(Mutex::new(Utc::now())),
59        }
60    }
61
62    /// Creates a concrete TestClock instance at a specific time for tests.
63    pub fn build_for_testing_at(time: DateTime<Utc>) -> Self {
64        Self {
65            time: Arc::new(Mutex::new(time)),
66        }
67    }
68
69    /// Advances the clock by the given duration.
70    pub fn advance(&self, duration: Duration) {
71        let mut time = self.time.lock().unwrap();
72        *time += duration;
73    }
74
75    /// Sets the clock to a specific time.
76    pub fn set(&self, new_time: DateTime<Utc>) {
77        let mut time = self.time.lock().unwrap();
78        *time = new_time;
79    }
80}
81
82impl Default for TestClock {
83    fn default() -> Self {
84        Self {
85            time: Arc::new(Mutex::new(Utc::now())),
86        }
87    }
88}
89
90impl Clock for TestClock {
91    fn now(&self) -> DateTime<Utc> {
92        *self.time.lock().unwrap()
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use std::{
99        sync::{Arc, Mutex},
100        thread,
101    };
102
103    use chrono::TimeZone;
104
105    use super::*;
106
107    #[test]
108    fn test_system_clock_new() {
109        let clock = SystemClock::new();
110        let _ = clock.now(); // Should not panic
111    }
112
113    #[test]
114    fn test_system_clock_returns_real_time() {
115        let clock = SystemClock::new();
116        let before = Utc::now();
117        let clock_time = clock.now();
118        let after = Utc::now();
119
120        // Clock time should be between before and after (within 1 second tolerance)
121        assert!(clock_time >= before - Duration::seconds(1));
122        assert!(clock_time <= after + Duration::seconds(1));
123    }
124
125    #[test]
126    fn test_system_clock_is_send_sync() {
127        fn assert_send<T: Send>() {}
128        fn assert_sync<T: Sync>() {}
129        assert_send::<SystemClock>();
130        assert_sync::<SystemClock>();
131    }
132
133    #[test]
134    fn test_test_clock_new() {
135        let clock = TestClock::new();
136        let _ = clock.now(); // Should not panic
137    }
138
139    #[test]
140    fn test_test_clock_new_at() {
141        let time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
142        let clock = TestClock::new_at(time);
143        assert_eq!(clock.now(), time);
144    }
145
146    #[test]
147    fn test_test_clock_advance() {
148        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
149
150        // Create TestClock directly to access advance method
151        let test_clock = TestClock {
152            time: Arc::new(Mutex::new(start_time)),
153        };
154
155        test_clock.advance(Duration::hours(5));
156        let expected = start_time + Duration::hours(5);
157        assert_eq!(test_clock.now(), expected);
158    }
159
160    #[test]
161    fn test_test_clock_advance_is_additive() {
162        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
163        let test_clock = TestClock {
164            time: Arc::new(Mutex::new(start_time)),
165        };
166
167        test_clock.advance(Duration::hours(2));
168        test_clock.advance(Duration::hours(3));
169
170        let expected = start_time + Duration::hours(5);
171        assert_eq!(test_clock.now(), expected);
172    }
173
174    #[test]
175    fn test_test_clock_set() {
176        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
177        let test_clock = TestClock {
178            time: Arc::new(Mutex::new(start_time)),
179        };
180
181        let new_time = Utc.with_ymd_and_hms(2025, 6, 15, 12, 30, 0).unwrap();
182        test_clock.set(new_time);
183
184        assert_eq!(test_clock.now(), new_time);
185    }
186
187    #[test]
188    fn test_test_clock_is_clone() {
189        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
190        let test_clock = TestClock {
191            time: Arc::new(Mutex::new(start_time)),
192        };
193
194        let clock2 = test_clock.clone();
195
196        // Both should share the same time
197        test_clock.advance(Duration::hours(1));
198        assert_eq!(clock2.now(), start_time + Duration::hours(1));
199    }
200
201    #[test]
202    fn test_test_clock_concurrent_access() {
203        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
204        let test_clock = TestClock {
205            time: Arc::new(Mutex::new(start_time)),
206        };
207
208        let clock1 = test_clock.clone();
209        let clock2 = test_clock.clone();
210
211        let handle1 = thread::spawn(move || {
212            clock1.advance(Duration::hours(1));
213        });
214
215        let handle2 = thread::spawn(move || {
216            clock2.advance(Duration::hours(2));
217        });
218
219        handle1.join().unwrap();
220        handle2.join().unwrap();
221
222        // Total advancement should be 3 hours
223        assert_eq!(test_clock.now(), start_time + Duration::hours(3));
224    }
225
226    #[test]
227    fn test_arc_dyn_clock_pattern() {
228        // Test that Arc<dyn Clock> works correctly
229        let clock: Arc<dyn Clock> = SystemClock::new();
230        let _ = clock.now();
231
232        let test_clock: Arc<dyn Clock> = TestClock::new();
233        let _ = test_clock.now();
234    }
235}