rate_guard/time_source/mock.rs
1//! Mock time source implementation for deterministic testing.
2//!
3//! This module provides MockTimeSource, a controllable time source that allows
4//! manual manipulation of time for testing rate limiting behavior. It maintains
5//! the elapsed time model while providing full control over time progression.
6//!
7//! # Design Features
8//!
9//! - **Deterministic**: Time only advances when explicitly requested
10//! - **Monotonic**: Time never goes backward, even with manual control
11//! - **Thread-local**: Each thread has independent mock time state
12//! - **Simple API**: Easy to use in tests and examples
13//!
14//! # Examples
15//!
16//! ## Basic Time Control
17//!
18//! ```rust
19//! use rate_guard::time_source::{TimeSource, MockTimeSource};
20//! use std::time::Duration;
21//!
22//! let time_source = MockTimeSource::new();
23//!
24//! // Initially at zero
25//! assert_eq!(time_source.now(), Duration::ZERO);
26//!
27//! // Advance time manually
28//! time_source.advance(Duration::from_millis(100));
29//! assert_eq!(time_source.now(), Duration::from_millis(100));
30//!
31//! // Set absolute time
32//! time_source.set_elapsed(Duration::from_secs(5));
33//! assert_eq!(time_source.now(), Duration::from_secs(5));
34//! ```
35//!
36//! ## Rate Limiter Testing
37//!
38//! ```rust
39//! use rate_guard::time_source::{TimeSource, MockTimeSource};
40//! use rate_guard::precision::{Precision, Millis};
41//! use std::time::Duration;
42//!
43//! let time_source = MockTimeSource::new();
44//!
45//! // Simulate rate limiter behavior
46//! let initial_ticks = Millis::to_ticks(time_source.now());
47//! assert_eq!(initial_ticks, 0);
48//!
49//! // Advance time to trigger refill
50//! time_source.advance(Duration::from_millis(100));
51//! let later_ticks = Millis::to_ticks(time_source.now());
52//! assert_eq!(later_ticks, 100);
53//! ```
54
55use std::time::Duration;
56use std::cell::Cell;
57use crate::time_source::TimeSource;
58
59thread_local! {
60 /// Thread-local mock time state.
61 ///
62 /// This thread-local variable holds the current elapsed time value that can
63 /// be controlled in tests. Each thread has its own independent mock time,
64 /// ensuring test isolation and avoiding synchronization overhead.
65 static MOCK_ELAPSED: Cell<Duration> = Cell::new(Duration::ZERO);
66}
67
68/// Mock time source for deterministic testing.
69///
70/// MockTimeSource provides a controllable time source where elapsed time can
71/// be manually set and advanced. This enables deterministic testing of rate
72/// limiting behavior without depending on real system time.
73///
74/// # Time Model
75///
76/// MockTimeSource maintains an elapsed Duration since an implicit starting point.
77/// Time starts at `Duration::ZERO` and can be advanced or set to specific values.
78/// Time is always monotonic - attempting to set time backward will be ignored.
79///
80/// # Thread Safety
81///
82/// MockTimeSource uses thread-local storage, so each thread has its own
83/// independent mock time. This is perfect for testing scenarios where you
84/// want isolated time control per test.
85///
86/// # Examples
87///
88/// ## Basic Usage
89///
90/// ```rust
91/// use rate_guard::time_source::{TimeSource, MockTimeSource};
92/// use std::time::Duration;
93///
94/// let time_source = MockTimeSource::new();
95///
96/// // Check initial state
97/// assert_eq!(time_source.now(), Duration::ZERO);
98///
99/// // Advance time
100/// time_source.advance(Duration::from_millis(50));
101/// assert_eq!(time_source.now(), Duration::from_millis(50));
102///
103/// // Advance more
104/// time_source.advance(Duration::from_millis(30));
105/// assert_eq!(time_source.now(), Duration::from_millis(80));
106/// ```
107///
108/// ## Absolute Time Setting
109///
110/// ```rust
111/// use rate_guard::time_source::{TimeSource, MockTimeSource};
112/// use std::time::Duration;
113///
114/// let time_source = MockTimeSource::new();
115///
116/// // Jump to specific time
117/// time_source.set_elapsed(Duration::from_secs(10));
118/// assert_eq!(time_source.now(), Duration::from_secs(10));
119///
120/// // Reset to zero
121/// time_source.reset();
122/// assert_eq!(time_source.now(), Duration::ZERO);
123/// ```
124///
125/// ## Rate Limiter Integration
126///
127/// ```rust
128/// use rate_guard::time_source::{TimeSource, MockTimeSource};
129/// use std::time::Duration;
130///
131/// let time_source = MockTimeSource::new();
132///
133/// // Simulate token bucket refill timing
134/// let refill_interval = Duration::from_millis(100);
135/// let initial_time = time_source.now();
136///
137/// // Advance past refill interval
138/// time_source.advance(refill_interval);
139/// let elapsed = time_source.now() - initial_time;
140/// assert!(elapsed >= refill_interval);
141/// ```
142#[derive(Debug, Clone)]
143pub struct MockTimeSource;
144
145impl MockTimeSource {
146 /// Creates a new MockTimeSource.
147 ///
148 /// The time source starts with elapsed time of `Duration::ZERO`.
149 /// Each instance shares the same thread-local time state within
150 /// the current thread.
151 ///
152 /// # Examples
153 ///
154 /// ```rust
155 /// use rate_guard::time_source::{TimeSource, MockTimeSource};
156 /// use std::time::Duration;
157 ///
158 /// let time_source = MockTimeSource::new();
159 /// assert_eq!(time_source.now(), Duration::ZERO);
160 /// ```
161 pub fn new() -> Self {
162 Self
163 }
164
165 /// Sets the elapsed time to a specific value.
166 ///
167 /// This method allows tests to jump to any point in time. The time
168 /// must be greater than or equal to the current elapsed time to
169 /// maintain monotonicity. Attempts to set time backward are ignored.
170 ///
171 /// # Arguments
172 /// * `elapsed` - The new elapsed time value
173 ///
174 /// # Examples
175 ///
176 /// ```rust
177 /// use rate_guard::time_source::{TimeSource, MockTimeSource};
178 /// use std::time::Duration;
179 ///
180 /// let time_source = MockTimeSource::new();
181 ///
182 /// time_source.set_elapsed(Duration::from_millis(1000));
183 /// assert_eq!(time_source.now(), Duration::from_millis(1000));
184 ///
185 /// // Attempting to go backward is ignored
186 /// time_source.set_elapsed(Duration::from_millis(500));
187 /// assert_eq!(time_source.now(), Duration::from_millis(1000));
188 /// ```
189 pub fn set_elapsed(&self, elapsed: Duration) {
190 MOCK_ELAPSED.with(|cell| {
191 let current = cell.get();
192 if elapsed >= current {
193 cell.set(elapsed);
194 }
195 });
196 }
197
198 /// Advances the elapsed time by the specified duration.
199 ///
200 /// This method adds the specified duration to the current elapsed time,
201 /// simulating the passage of time in tests. The advancement is always
202 /// forward, maintaining monotonicity.
203 ///
204 /// # Arguments
205 /// * `duration` - Duration to advance the time by
206 ///
207 /// # Examples
208 ///
209 /// ```rust
210 /// use rate_guard::time_source::{TimeSource, MockTimeSource};
211 /// use std::time::Duration;
212 ///
213 /// let time_source = MockTimeSource::new();
214 ///
215 /// time_source.advance(Duration::from_millis(50));
216 /// assert_eq!(time_source.now(), Duration::from_millis(50));
217 ///
218 /// time_source.advance(Duration::from_millis(25));
219 /// assert_eq!(time_source.now(), Duration::from_millis(75));
220 /// ```
221 pub fn advance(&self, duration: Duration) {
222 MOCK_ELAPSED.with(|cell| {
223 let current = cell.get();
224 cell.set(current + duration);
225 });
226 }
227
228 /// Resets the elapsed time to zero.
229 ///
230 /// This is a convenience method equivalent to `set_elapsed(Duration::ZERO)`.
231 /// Useful for resetting time state between tests.
232 ///
233 /// # Examples
234 ///
235 /// ```rust
236 /// use rate_guard::time_source::{TimeSource, MockTimeSource};
237 /// use std::time::Duration;
238 ///
239 /// let time_source = MockTimeSource::new();
240 /// time_source.advance(Duration::from_secs(5));
241 /// assert_eq!(time_source.now(), Duration::from_secs(5));
242 ///
243 /// time_source.reset();
244 /// assert_eq!(time_source.now(), Duration::ZERO);
245 /// ```
246 pub fn reset(&self) {
247 MOCK_ELAPSED.with(|cell| {
248 cell.set(Duration::ZERO);
249 });
250 }
251
252 /// Returns the current elapsed time.
253 ///
254 /// This is equivalent to calling the `now()` method from the TimeSource
255 /// trait, but provided as a convenience method with a more descriptive name.
256 ///
257 /// # Examples
258 ///
259 /// ```rust
260 /// use rate_guard::time_source::MockTimeSource;
261 /// use std::time::Duration;
262 ///
263 /// let time_source = MockTimeSource::new();
264 /// time_source.advance(Duration::from_millis(100));
265 ///
266 /// assert_eq!(time_source.elapsed(), Duration::from_millis(100));
267 /// assert_eq!(time_source.elapsed(), time_source.now());
268 /// ```
269 pub fn elapsed(&self) -> Duration {
270 self.now()
271 }
272
273 pub fn now(&self) -> Duration {
274 MOCK_ELAPSED.with(|cell| cell.get())
275 }
276}
277
278impl Default for MockTimeSource {
279 /// Creates a new MockTimeSource with default settings.
280 ///
281 /// Equivalent to `MockTimeSource::new()`.
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287impl TimeSource for MockTimeSource {
288 /// Returns the current elapsed time since the mock starting point.
289 ///
290 /// This method returns the elapsed Duration that has been set or advanced
291 /// through the mock time control methods.
292 ///
293 /// # Examples
294 ///
295 /// ```rust
296 /// use rate_guard::time_source::{TimeSource, MockTimeSource};
297 /// use std::time::Duration;
298 ///
299 /// let time_source = MockTimeSource::new();
300 /// assert_eq!(time_source.now(), Duration::ZERO);
301 ///
302 /// time_source.advance(Duration::from_millis(123));
303 /// assert_eq!(time_source.now(), Duration::from_millis(123));
304 /// ```
305 #[inline(always)]
306 fn now(&self) -> Duration {
307 MOCK_ELAPSED.with(|cell| cell.get())
308 }
309}