hojicha_core/testing/
time_control.rs1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
7use std::sync::Arc;
8use std::time::Duration;
9
10#[derive(Clone)]
12pub struct TimeController {
13 paused: Arc<AtomicBool>,
15 current_ms: Arc<AtomicU64>,
17 time_scale: Arc<AtomicU64>, }
20
21impl TimeController {
22 pub fn new_paused() -> Self {
24 Self {
25 paused: Arc::new(AtomicBool::new(true)),
26 current_ms: Arc::new(AtomicU64::new(0)),
27 time_scale: Arc::new(AtomicU64::new(0)), }
29 }
30
31 pub fn new_real() -> Self {
33 Self {
34 paused: Arc::new(AtomicBool::new(false)),
35 current_ms: Arc::new(AtomicU64::new(0)),
36 time_scale: Arc::new(AtomicU64::new(1000)), }
38 }
39
40 pub fn pause(&self) {
42 self.paused.store(true, Ordering::SeqCst);
43 self.time_scale.store(0, Ordering::SeqCst);
44 }
45
46 pub fn resume(&self) {
48 self.paused.store(false, Ordering::SeqCst);
49 self.time_scale.store(1000, Ordering::SeqCst);
50 }
51
52 pub fn set_scale(&self, scale: f64) {
54 let scale_fixed = (scale * 1000.0) as u64;
55 self.time_scale.store(scale_fixed, Ordering::SeqCst);
56 self.paused.store(scale == 0.0, Ordering::SeqCst);
57 }
58
59 pub fn advance(&self, duration: Duration) -> Result<(), &'static str> {
61 if !self.paused.load(Ordering::SeqCst) {
62 return Err("Cannot advance time when not paused");
63 }
64
65 let ms = duration.as_millis() as u64;
66 self.current_ms.fetch_add(ms, Ordering::SeqCst);
67 Ok(())
68 }
69
70 pub fn now(&self) -> Duration {
72 let ms = self.current_ms.load(Ordering::SeqCst);
73 Duration::from_millis(ms)
74 }
75
76 pub async fn sleep(&self, duration: Duration) {
80 if self.paused.load(Ordering::SeqCst) {
81 let _ = self.advance(duration);
83 } else {
84 let scale = self.time_scale.load(Ordering::SeqCst) as f64 / 1000.0;
86 if scale > 0.0 {
87 let scaled_duration = Duration::from_secs_f64(duration.as_secs_f64() / scale);
88 tokio::time::sleep(scaled_duration).await;
89 }
90 }
91 }
92
93 pub fn sleep_blocking(&self, duration: Duration) {
97 if self.paused.load(Ordering::SeqCst) {
98 let _ = self.advance(duration);
100 } else {
101 let scale = self.time_scale.load(Ordering::SeqCst) as f64 / 1000.0;
103 if scale > 0.0 {
104 let scaled_duration = Duration::from_secs_f64(duration.as_secs_f64() / scale);
105 std::thread::sleep(scaled_duration);
106 }
107 }
108 }
109}
110
111use std::sync::LazyLock;
113
114static GLOBAL_TIME: LazyLock<TimeController> = LazyLock::new(TimeController::new_real);
115
116pub fn pause() {
118 GLOBAL_TIME.pause();
119}
120
121pub fn resume() {
123 GLOBAL_TIME.resume();
124}
125
126pub fn advance(duration: Duration) -> Result<(), &'static str> {
128 GLOBAL_TIME.advance(duration)
129}
130
131pub fn controller() -> TimeController {
133 GLOBAL_TIME.clone()
134}
135
136#[macro_export]
138macro_rules! test_with_paused_time {
139 ($name:ident, $body:block) => {
140 #[test]
141 fn $name() {
142 let _guard = $crate::testing::time_control::PausedTimeGuard::new();
143 $body
144 }
145 };
146}
147
148pub struct PausedTimeGuard {
150 was_paused: bool,
151}
152
153impl PausedTimeGuard {
154 pub fn new() -> Self {
156 let was_paused = GLOBAL_TIME.paused.load(Ordering::SeqCst);
157 pause();
158 Self { was_paused }
159 }
160}
161
162impl Default for PausedTimeGuard {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168impl Drop for PausedTimeGuard {
169 fn drop(&mut self) {
170 if !self.was_paused {
171 resume();
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_time_controller_pause_advance() {
182 let controller = TimeController::new_paused();
183
184 assert_eq!(controller.now(), Duration::ZERO);
185
186 controller.advance(Duration::from_secs(1)).unwrap();
187 assert_eq!(controller.now(), Duration::from_secs(1));
188
189 controller.advance(Duration::from_millis(500)).unwrap();
190 assert_eq!(controller.now(), Duration::from_millis(1500));
191 }
192
193 #[test]
194 fn test_time_controller_cannot_advance_when_running() {
195 let controller = TimeController::new_real();
196
197 let result = controller.advance(Duration::from_secs(1));
198 assert!(result.is_err());
199 }
200
201 #[test]
202 fn test_time_scale() {
203 let controller = TimeController::new_paused();
204
205 controller.set_scale(2.0); assert!(!controller.paused.load(Ordering::SeqCst));
207
208 controller.set_scale(0.0); assert!(controller.paused.load(Ordering::SeqCst));
210 }
211
212 #[tokio::test]
213 async fn test_virtual_sleep() {
214 let controller = TimeController::new_paused();
215
216 let start = controller.now();
217 controller.sleep(Duration::from_secs(10)).await;
218 let end = controller.now();
219
220 assert_eq!(end - start, Duration::from_secs(10));
222 }
223
224 test_with_paused_time!(test_macro_paused_time, {
225 advance(Duration::from_secs(1)).unwrap();
226 assert_eq!(controller().now(), Duration::from_secs(1));
227 });
228}