Skip to main content

nautilus_common/
clock.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Real-time and static `Clock` implementations.
17//!
18//! Defines the [`Clock`] contract, the user-facing [`ClockApi`] facade, and the deterministic
19//! [`TestClock`] used for controlled time advancement. Shared validation and callback registration
20//! support test and live clock implementations.
21
22use std::{
23    any::Any,
24    cell::RefCell,
25    collections::{BTreeMap, BinaryHeap},
26    fmt::Debug,
27    ops::Deref,
28    time::Duration,
29};
30
31use ahash::AHashMap;
32use jiff::Timestamp;
33use nautilus_core::{
34    AtomicTime, UUID4, UnixNanos,
35    correctness::{check_positive_u64, check_predicate_true, check_valid_string_utf8},
36    datetime::{NANOSECONDS_IN_SECOND, try_datetime_to_unix_nanos},
37    string::formatting::Separable,
38};
39use ustr::Ustr;
40
41use crate::timer::{
42    ScheduledTimeEvent, TestTimer, TimeEvent, TimeEventCallback, TimeEventHandler, Timer,
43    create_valid_interval,
44};
45
46/// Provides time access, timer scheduling, and callback registration.
47///
48/// An active timer is one that has not expired.
49pub trait Clock: Debug + Any {
50    /// Returns the current UTC timestamp.
51    fn utc_now(&self) -> Timestamp {
52        self.timestamp_ns().to_datetime_utc()
53    }
54
55    /// Returns the current UNIX timestamp in nanoseconds (ns).
56    fn timestamp_ns(&self) -> UnixNanos;
57
58    /// Returns the current UNIX timestamp in microseconds (μs).
59    fn timestamp_us(&self) -> u64;
60
61    /// Returns the current UNIX timestamp in milliseconds (ms).
62    fn timestamp_ms(&self) -> u64;
63
64    /// Returns the current UNIX timestamp in seconds.
65    fn timestamp(&self) -> f64;
66
67    /// Returns the names of active timers in the clock.
68    fn timer_names(&self) -> Vec<&str>;
69
70    /// Returns the count of active timers in the clock.
71    fn timer_count(&self) -> usize;
72
73    /// Returns whether an active timer named `name` exists.
74    fn timer_exists(&self, name: &Ustr) -> bool;
75
76    /// Registers the callback used when a timer has no named callback.
77    fn register_default_handler(&mut self, callback: TimeEventCallback);
78
79    /// Cancels the registered default event handler, if any.
80    ///
81    /// Releases the held callback so any Python object owned by it can be dropped.
82    /// `Trader::release_component` calls this at component retirement to break the cycle
83    /// between a Python component and its clock: the clock holds the callback as a
84    /// `Py<PyAny>` that Python's cycle collector cannot reach through.
85    fn cancel_default_handler(&mut self);
86
87    /// Cancels all registered named event callbacks, preserving the default handler.
88    ///
89    /// Releases callbacks registered via [`Clock::set_time_alert_ns`] or
90    /// [`Clock::set_timer_ns`] with an explicit `callback` argument.
91    /// `Trader::release_component` calls this at component retirement, breaking the same
92    /// cycle as [`Clock::cancel_default_handler`].
93    fn cancel_callbacks(&mut self);
94
95    /// Sets a timer to alert at the specified time.
96    ///
97    /// See [`Clock::set_time_alert_ns`] for flag semantics.
98    ///
99    /// # Callback
100    ///
101    /// - `Some(callback)` registers and uses `callback` for the named alert.
102    /// - `None` uses a callback registered under `name`, falling back to the default callback.
103    ///
104    /// # Errors
105    ///
106    /// Returns an error if:
107    /// - `name` is invalid.
108    /// - `alert_time` is before the UNIX epoch or outside the [`UnixNanos`] range.
109    /// - The alert is in the past and `allow_past` is `Some(false)`.
110    /// - No explicit, named, or default callback is available.
111    fn set_time_alert(
112        &mut self,
113        name: &str,
114        alert_time: Timestamp,
115        callback: Option<TimeEventCallback>,
116        allow_past: Option<bool>,
117    ) -> anyhow::Result<()> {
118        self.set_time_alert_ns(
119            name,
120            try_datetime_to_unix_nanos(alert_time)?,
121            callback,
122            allow_past,
123        )
124    }
125
126    /// Sets a timer to alert at the specified time.
127    ///
128    /// Any active timer registered under the same `name` is canceled with a warning before the
129    /// new alert is scheduled. `allow_past` defaults to `true`.
130    ///
131    /// # Flags
132    ///
133    /// | `allow_past` | Behavior                                                               |
134    /// | ------------ | ---------------------------------------------------------------------- |
135    /// | `true`       | A past alert is moved to the current time and fires immediately.       |
136    /// | `false`      | An alert earlier than the current time returns an error.               |
137    ///
138    /// # Callback
139    ///
140    /// - `Some(callback)` registers and uses `callback` for the named alert.
141    /// - `None` uses a callback registered under `name`, falling back to the default callback.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if:
146    /// - `name` is invalid.
147    /// - `alert_time_ns` is earlier than now and `allow_past` is `Some(false)`.
148    /// - No explicit, named, or default callback is available.
149    fn set_time_alert_ns(
150        &mut self,
151        name: &str,
152        alert_time_ns: UnixNanos,
153        callback: Option<TimeEventCallback>,
154        allow_past: Option<bool>,
155    ) -> anyhow::Result<()>;
156
157    /// Sets a timer to fire time events at every interval between the start and stop times.
158    ///
159    /// Any active timer registered under the same `name` is canceled with a warning before the
160    /// new timer is scheduled.
161    ///
162    /// See [`Clock::set_timer_ns`] for flag semantics.
163    ///
164    /// # Callback
165    ///
166    /// - `Some(callback)` registers and uses `callback` for the named timer.
167    /// - `None` uses a callback registered under `name`, falling back to the default callback.
168    ///
169    /// # Errors
170    ///
171    /// Returns an error if:
172    /// - `name` is invalid.
173    /// - `interval` is zero or exceeds `u64::MAX` nanoseconds.
174    /// - `start_time` or `stop_time` is before the UNIX epoch or out of range for `UnixNanos`.
175    /// - The first event timestamp is out of range for `UnixNanos`.
176    /// - The first event is in the past when past times are disallowed.
177    /// - The stop time is not after the start time.
178    /// - The stop time is not after the current time when past times are disallowed.
179    /// - No explicit, named, or default callback is available.
180    #[expect(clippy::too_many_arguments)]
181    fn set_timer(
182        &mut self,
183        name: &str,
184        interval: Duration,
185        start_time: Option<Timestamp>,
186        stop_time: Option<Timestamp>,
187        callback: Option<TimeEventCallback>,
188        allow_past: Option<bool>,
189        fire_immediately: Option<bool>,
190    ) -> anyhow::Result<()> {
191        self.set_timer_ns(
192            name,
193            duration_to_nanos(interval)?,
194            start_time.map(try_datetime_to_unix_nanos).transpose()?,
195            stop_time.map(try_datetime_to_unix_nanos).transpose()?,
196            callback,
197            allow_past,
198            fire_immediately,
199        )
200    }
201
202    /// Sets a timer to fire time events at every interval between the start and stop times.
203    ///
204    /// Any active timer registered under the same `name` is canceled with a warning before the
205    /// new timer is scheduled. `allow_past` defaults to `true`, and `fire_immediately` defaults to
206    /// `false`.
207    ///
208    /// # Start Time
209    ///
210    /// - `None` or `Some(0)`: Uses the current time as start time.
211    /// - `Some(non_zero)`: Uses the specified timestamp as start time.
212    ///
213    /// # Flags
214    ///
215    /// | `allow_past` | `fire_immediately` | First event behavior                                |
216    /// | ------------ | ------------------ | --------------------------------------------------- |
217    /// | `true`       | `true`             | Fires at the start time, including a past start.    |
218    /// | `true`       | `false`            | Fires one interval after the start, including past. |
219    /// | `false`      | `true`             | A past start time returns an error.                 |
220    /// | `false`      | `false`            | A past first event returns an error.                |
221    ///
222    /// # Callback
223    ///
224    /// - `Some(callback)` registers and uses `callback` for the named timer.
225    /// - `None` uses a callback registered under `name`, falling back to the default callback.
226    ///
227    /// # Errors
228    ///
229    /// Returns an error if:
230    /// - `name` is invalid.
231    /// - `interval_ns` is zero.
232    /// - `start_time_ns + interval_ns` is out of range for `UnixNanos` when not firing immediately.
233    /// - The first event is in the past when past times are disallowed.
234    /// - The stop time is not after the start time.
235    /// - The stop time is not after the current time when past times are disallowed.
236    /// - No explicit, named, or default callback is available.
237    #[expect(clippy::too_many_arguments)]
238    fn set_timer_ns(
239        &mut self,
240        name: &str,
241        interval_ns: u64,
242        start_time_ns: Option<UnixNanos>,
243        stop_time_ns: Option<UnixNanos>,
244        callback: Option<TimeEventCallback>,
245        allow_past: Option<bool>,
246        fire_immediately: Option<bool>,
247    ) -> anyhow::Result<()>;
248
249    /// Returns the next trigger timestamp for the active timer named `name`.
250    ///
251    /// Returns `None` if no active timer with that name exists.
252    fn next_time_ns(&self, name: &str) -> Option<UnixNanos>;
253
254    /// Cancels the timer named `name`, if it exists.
255    fn cancel_timer(&mut self, name: &str);
256
257    /// Cancels all timers.
258    fn cancel_timers(&mut self);
259
260    /// Resets scheduling state while preserving the default callback.
261    ///
262    /// The reset clears all timers and named callbacks. Static clocks also reset their stored time.
263    fn reset(&mut self);
264}
265
266impl dyn Clock {
267    /// Returns a reference to this clock as `Any` for downcasting.
268    pub fn as_any(&self) -> &dyn std::any::Any {
269        self
270    }
271
272    /// Returns a mutable reference to this clock as `Any` for downcasting.
273    pub fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
274        self
275    }
276}
277
278/// Provides a user-facing facade over clock operations.
279///
280/// Calls delegate to either a borrowed [`Clock`] or a set of operation handlers.
281/// Panics from supplied operation handlers propagate to the caller.
282#[derive(Debug)]
283pub struct ClockApi<'a> {
284    backing: ClockApiBacking<'a>,
285}
286
287impl<'a> ClockApi<'a> {
288    pub(crate) fn new(clock: &'a RefCell<dyn Clock>) -> Self {
289        Self {
290            backing: ClockApiBacking::Native(clock),
291        }
292    }
293
294    /// Creates a clock API backed by the supplied operation handlers.
295    ///
296    /// The nanosecond timestamp handler also supplies the derived UTC, second, millisecond, and
297    /// microsecond values. Timestamp-based scheduling methods convert their inputs before invoking
298    /// the corresponding nanosecond handler.
299    #[doc(hidden)]
300    #[must_use]
301    #[expect(
302        clippy::too_many_arguments,
303        reason = "clock API backing mirrors the full ClockApi surface"
304    )]
305    pub fn from_handlers<
306        TimestampNs,
307        SetTimeAlertNs,
308        SetTimerNs,
309        TimerNames,
310        TimerCount,
311        TimerExists,
312        NextTimeNs,
313        CancelTimer,
314        CancelTimers,
315    >(
316        timestamp_ns: TimestampNs,
317        set_time_alert_ns: SetTimeAlertNs,
318        set_timer_ns: SetTimerNs,
319        timer_names: TimerNames,
320        timer_count: TimerCount,
321        timer_exists: TimerExists,
322        next_time_ns: NextTimeNs,
323        cancel_timer: CancelTimer,
324        cancel_timers: CancelTimers,
325    ) -> Self
326    where
327        TimestampNs: Fn() -> UnixNanos + 'a,
328        SetTimeAlertNs:
329            Fn(&str, UnixNanos, Option<TimeEventCallback>, Option<bool>) -> anyhow::Result<()> + 'a,
330        SetTimerNs: Fn(
331                &str,
332                u64,
333                Option<UnixNanos>,
334                Option<UnixNanos>,
335                Option<TimeEventCallback>,
336                Option<bool>,
337                Option<bool>,
338            ) -> anyhow::Result<()>
339            + 'a,
340        TimerNames: Fn() -> Vec<String> + 'a,
341        TimerCount: Fn() -> usize + 'a,
342        TimerExists: Fn(&str) -> bool + 'a,
343        NextTimeNs: Fn(&str) -> Option<UnixNanos> + 'a,
344        CancelTimer: Fn(&str) + 'a,
345        CancelTimers: Fn() + 'a,
346    {
347        Self {
348            backing: ClockApiBacking::Handlers(ClockApiHandlers {
349                timestamp_ns: Box::new(timestamp_ns),
350                set_time_alert_ns: Box::new(set_time_alert_ns),
351                set_timer_ns: Box::new(set_timer_ns),
352                timer_names: Box::new(timer_names),
353                timer_count: Box::new(timer_count),
354                timer_exists: Box::new(timer_exists),
355                next_time_ns: Box::new(next_time_ns),
356                cancel_timer: Box::new(cancel_timer),
357                cancel_timers: Box::new(cancel_timers),
358            }),
359        }
360    }
361
362    /// Returns the current UNIX timestamp in nanoseconds.
363    ///
364    /// # Panics
365    ///
366    /// With native backing, panics if the clock is already mutably borrowed.
367    #[must_use]
368    pub fn timestamp_ns(&self) -> UnixNanos {
369        match &self.backing {
370            ClockApiBacking::Native(clock) => clock.borrow().timestamp_ns(),
371            ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)(),
372        }
373    }
374
375    /// Returns the current UNIX timestamp in microseconds.
376    ///
377    /// # Panics
378    ///
379    /// With native backing, panics if the clock is already mutably borrowed.
380    #[must_use]
381    pub fn timestamp_us(&self) -> u64 {
382        match &self.backing {
383            ClockApiBacking::Native(clock) => clock.borrow().timestamp_us(),
384            ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().as_micros(),
385        }
386    }
387
388    /// Returns the current UNIX timestamp in milliseconds.
389    ///
390    /// # Panics
391    ///
392    /// With native backing, panics if the clock is already mutably borrowed.
393    #[must_use]
394    pub fn timestamp_ms(&self) -> u64 {
395        match &self.backing {
396            ClockApiBacking::Native(clock) => clock.borrow().timestamp_ms(),
397            ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().as_millis(),
398        }
399    }
400
401    /// Returns the current UNIX timestamp in seconds.
402    ///
403    /// # Panics
404    ///
405    /// With native backing, panics if the clock is already mutably borrowed.
406    #[must_use]
407    pub fn timestamp(&self) -> f64 {
408        match &self.backing {
409            ClockApiBacking::Native(clock) => clock.borrow().timestamp(),
410            ClockApiBacking::Handlers(handlers) => {
411                (handlers.timestamp_ns)().as_f64() / (NANOSECONDS_IN_SECOND as f64)
412            }
413        }
414    }
415
416    /// Returns the current UTC timestamp.
417    ///
418    /// # Panics
419    ///
420    /// With native backing, panics if the clock is already mutably borrowed.
421    #[must_use]
422    pub fn utc_now(&self) -> Timestamp {
423        match &self.backing {
424            ClockApiBacking::Native(clock) => clock.borrow().utc_now(),
425            ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().to_datetime_utc(),
426        }
427    }
428
429    // panics-doc-ok
430    /// Sets a time alert for the specified UTC timestamp.
431    ///
432    /// See [`Clock::set_time_alert`] for timing and callback selection semantics.
433    ///
434    /// # Errors
435    ///
436    /// Returns an error if the timestamp cannot be converted to [`UnixNanos`] or the backing clock
437    /// rejects the alert.
438    ///
439    /// # Panics
440    ///
441    /// With native backing, panics if the clock is already borrowed.
442    pub fn set_time_alert(
443        &self,
444        name: &str,
445        alert_time: Timestamp,
446        callback: Option<TimeEventCallback>,
447        allow_past: Option<bool>,
448    ) -> anyhow::Result<()> {
449        match &self.backing {
450            ClockApiBacking::Native(clock) => clock
451                .borrow_mut()
452                .set_time_alert(name, alert_time, callback, allow_past),
453            ClockApiBacking::Handlers(handlers) => (handlers.set_time_alert_ns)(
454                name,
455                try_datetime_to_unix_nanos(alert_time)?,
456                callback,
457                allow_past,
458            ),
459        }
460    }
461
462    // panics-doc-ok
463    /// Sets a time alert for the specified UNIX nanosecond timestamp.
464    ///
465    /// See [`Clock::set_time_alert_ns`] for timing and callback selection semantics.
466    ///
467    /// # Errors
468    ///
469    /// Returns an error if the backing clock rejects the alert.
470    ///
471    /// # Panics
472    ///
473    /// With native backing, panics if the clock is already borrowed.
474    pub fn set_time_alert_ns(
475        &self,
476        name: &str,
477        alert_time_ns: UnixNanos,
478        callback: Option<TimeEventCallback>,
479        allow_past: Option<bool>,
480    ) -> anyhow::Result<()> {
481        match &self.backing {
482            ClockApiBacking::Native(clock) => {
483                clock
484                    .borrow_mut()
485                    .set_time_alert_ns(name, alert_time_ns, callback, allow_past)
486            }
487            ClockApiBacking::Handlers(handlers) => {
488                (handlers.set_time_alert_ns)(name, alert_time_ns, callback, allow_past)
489            }
490        }
491    }
492
493    // panics-doc-ok
494    /// Sets an interval timer using UTC timestamps.
495    ///
496    /// See [`Clock::set_timer`] for scheduling and callback selection semantics.
497    ///
498    /// # Errors
499    ///
500    /// Returns an error if the interval exceeds `u64::MAX` nanoseconds, a timestamp cannot be
501    /// converted to [`UnixNanos`], or the backing clock rejects the timer.
502    ///
503    /// # Panics
504    ///
505    /// With native backing, panics if the clock is already borrowed.
506    #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
507    pub fn set_timer(
508        &self,
509        name: &str,
510        interval: Duration,
511        start_time: Option<Timestamp>,
512        stop_time: Option<Timestamp>,
513        callback: Option<TimeEventCallback>,
514        allow_past: Option<bool>,
515        fire_immediately: Option<bool>,
516    ) -> anyhow::Result<()> {
517        match &self.backing {
518            ClockApiBacking::Native(clock) => clock.borrow_mut().set_timer(
519                name,
520                interval,
521                start_time,
522                stop_time,
523                callback,
524                allow_past,
525                fire_immediately,
526            ),
527            ClockApiBacking::Handlers(handlers) => (handlers.set_timer_ns)(
528                name,
529                duration_to_nanos(interval)?,
530                start_time.map(try_datetime_to_unix_nanos).transpose()?,
531                stop_time.map(try_datetime_to_unix_nanos).transpose()?,
532                callback,
533                allow_past,
534                fire_immediately,
535            ),
536        }
537    }
538
539    // panics-doc-ok
540    /// Sets an interval timer using UNIX nanosecond timestamps.
541    ///
542    /// See [`Clock::set_timer_ns`] for scheduling and callback selection semantics.
543    ///
544    /// # Errors
545    ///
546    /// Returns an error if the backing clock rejects the timer.
547    ///
548    /// # Panics
549    ///
550    /// With native backing, panics if the clock is already borrowed.
551    #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
552    pub fn set_timer_ns(
553        &self,
554        name: &str,
555        interval_ns: u64,
556        start_time_ns: Option<UnixNanos>,
557        stop_time_ns: Option<UnixNanos>,
558        callback: Option<TimeEventCallback>,
559        allow_past: Option<bool>,
560        fire_immediately: Option<bool>,
561    ) -> anyhow::Result<()> {
562        match &self.backing {
563            ClockApiBacking::Native(clock) => clock.borrow_mut().set_timer_ns(
564                name,
565                interval_ns,
566                start_time_ns,
567                stop_time_ns,
568                callback,
569                allow_past,
570                fire_immediately,
571            ),
572            ClockApiBacking::Handlers(handlers) => (handlers.set_timer_ns)(
573                name,
574                interval_ns,
575                start_time_ns,
576                stop_time_ns,
577                callback,
578                allow_past,
579                fire_immediately,
580            ),
581        }
582    }
583
584    /// Returns the names of active timers.
585    ///
586    /// # Panics
587    ///
588    /// With native backing, panics if the clock is already mutably borrowed.
589    #[must_use]
590    pub fn timer_names(&self) -> Vec<String> {
591        match &self.backing {
592            ClockApiBacking::Native(clock) => clock
593                .borrow()
594                .timer_names()
595                .into_iter()
596                .map(str::to_string)
597                .collect(),
598            ClockApiBacking::Handlers(handlers) => (handlers.timer_names)(),
599        }
600    }
601
602    /// Returns the count of active timers.
603    ///
604    /// # Panics
605    ///
606    /// With native backing, panics if the clock is already mutably borrowed.
607    #[must_use]
608    pub fn timer_count(&self) -> usize {
609        match &self.backing {
610            ClockApiBacking::Native(clock) => clock.borrow().timer_count(),
611            ClockApiBacking::Handlers(handlers) => (handlers.timer_count)(),
612        }
613    }
614
615    /// Returns whether an active timer named `name` exists.
616    ///
617    /// # Panics
618    ///
619    /// With native backing, panics if the clock is already mutably borrowed.
620    #[must_use]
621    pub fn timer_exists(&self, name: &str) -> bool {
622        match &self.backing {
623            ClockApiBacking::Native(clock) => clock.borrow().timer_exists(&Ustr::from(name)),
624            ClockApiBacking::Handlers(handlers) => (handlers.timer_exists)(name),
625        }
626    }
627
628    /// Returns the next trigger timestamp for the active timer named `name`.
629    ///
630    /// Returns `None` if no active timer with that name exists.
631    ///
632    /// # Panics
633    ///
634    /// With native backing, panics if the clock is already mutably borrowed.
635    #[must_use]
636    pub fn next_time_ns(&self, name: &str) -> Option<UnixNanos> {
637        match &self.backing {
638            ClockApiBacking::Native(clock) => clock.borrow().next_time_ns(name),
639            ClockApiBacking::Handlers(handlers) => (handlers.next_time_ns)(name),
640        }
641    }
642
643    /// Cancels the timer named `name`, if it exists.
644    ///
645    /// # Panics
646    ///
647    /// With native backing, panics if the clock is already borrowed.
648    pub fn cancel_timer(&self, name: &str) {
649        match &self.backing {
650            ClockApiBacking::Native(clock) => clock.borrow_mut().cancel_timer(name),
651            ClockApiBacking::Handlers(handlers) => (handlers.cancel_timer)(name),
652        }
653    }
654
655    /// Cancels all timers.
656    ///
657    /// # Panics
658    ///
659    /// With native backing, panics if the clock is already borrowed.
660    pub fn cancel_timers(&self) {
661        match &self.backing {
662            ClockApiBacking::Native(clock) => clock.borrow_mut().cancel_timers(),
663            ClockApiBacking::Handlers(handlers) => (handlers.cancel_timers)(),
664        }
665    }
666}
667
668enum ClockApiBacking<'a> {
669    Native(&'a RefCell<dyn Clock>),
670    Handlers(ClockApiHandlers<'a>),
671}
672
673struct ClockApiHandlers<'a> {
674    timestamp_ns: Box<dyn Fn() -> UnixNanos + 'a>,
675    set_time_alert_ns: Box<SetTimeAlertNsHandler<'a>>,
676    set_timer_ns: Box<SetTimerNsHandler<'a>>,
677    timer_names: Box<dyn Fn() -> Vec<String> + 'a>,
678    timer_count: Box<dyn Fn() -> usize + 'a>,
679    timer_exists: Box<dyn Fn(&str) -> bool + 'a>,
680    next_time_ns: Box<NextTimeNsHandler<'a>>,
681    cancel_timer: Box<dyn Fn(&str) + 'a>,
682    cancel_timers: Box<dyn Fn() + 'a>,
683}
684
685impl Debug for ClockApiBacking<'_> {
686    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
687        match self {
688            Self::Native(_) => f.write_str("Native"),
689            Self::Handlers(_) => f.write_str("Handlers"),
690        }
691    }
692}
693
694type SetTimeAlertNsHandler<'a> =
695    dyn Fn(&str, UnixNanos, Option<TimeEventCallback>, Option<bool>) -> anyhow::Result<()> + 'a;
696type NextTimeNsHandler<'a> = dyn Fn(&str) -> Option<UnixNanos> + 'a;
697type SetTimerNsHandler<'a> = dyn Fn(
698        &str,
699        u64,
700        Option<UnixNanos>,
701        Option<UnixNanos>,
702        Option<TimeEventCallback>,
703        Option<bool>,
704        Option<bool>,
705    ) -> anyhow::Result<()>
706    + 'a;
707
708fn duration_to_nanos(duration: Duration) -> anyhow::Result<u64> {
709    u64::try_from(duration.as_nanos())
710        .map_err(|_| anyhow::anyhow!("Interval exceeds u64 nanoseconds"))
711}
712
713/// Registry for timer event callbacks.
714///
715/// Provides shared callback registration and retrieval logic used by both
716/// `TestClock` and `LiveClock`.
717#[derive(Debug, Default)]
718pub struct CallbackRegistry {
719    default_callback: Option<TimeEventCallback>,
720    callbacks: AHashMap<Ustr, TimeEventCallback>,
721}
722
723impl CallbackRegistry {
724    /// Creates an empty callback registry.
725    #[must_use]
726    pub fn new() -> Self {
727        Self::default()
728    }
729
730    /// Registers the callback used when no callback exists for a timer name.
731    pub fn register_default_handler(&mut self, callback: TimeEventCallback) {
732        self.default_callback = Some(callback);
733    }
734
735    /// Removes the default callback, preserving all named callbacks.
736    pub fn cancel_default_handler(&mut self) {
737        self.default_callback = None;
738    }
739
740    /// Registers a callback for `name`, replacing any existing callback for that name.
741    pub fn register_callback(&mut self, name: Ustr, callback: TimeEventCallback) {
742        self.callbacks.insert(name, callback);
743    }
744
745    /// Returns whether a named or default callback is available for `name`.
746    #[must_use]
747    pub fn has_any_callback(&self, name: &Ustr) -> bool {
748        self.callbacks.contains_key(name) || self.default_callback.is_some()
749    }
750
751    /// Returns the callback for `name`, falling back to the default callback.
752    #[must_use]
753    pub fn get_callback(&self, name: &Ustr) -> Option<TimeEventCallback> {
754        self.callbacks
755            .get(name)
756            .cloned()
757            .or_else(|| self.default_callback.clone())
758    }
759
760    /// Creates a handler for `event` using its named callback or the default callback.
761    ///
762    /// # Panics
763    ///
764    /// Panics if neither a named nor default callback exists for the event.
765    #[must_use]
766    pub fn get_handler(&self, event: TimeEvent) -> TimeEventHandler {
767        let callback = self
768            .get_callback(&event.name)
769            .unwrap_or_else(|| panic!("Event '{}' should have associated handler", event.name));
770
771        TimeEventHandler::new(event, callback)
772    }
773
774    /// Clears all named callbacks, preserving the default callback.
775    pub fn clear(&mut self) {
776        self.callbacks.clear();
777    }
778}
779
780/// Validates and normalizes parameters for a time alert.
781///
782/// `allow_past` defaults to `true`. When enabled, a past alert timestamp is replaced with
783/// `ts_now`. Returns the interned name and normalized alert timestamp.
784///
785/// # Errors
786///
787/// Returns an error if `name` is invalid or the alert is in the past when past alerts are
788/// disallowed.
789pub fn validate_and_prepare_time_alert(
790    name: &str,
791    mut alert_time_ns: UnixNanos,
792    allow_past: Option<bool>,
793    ts_now: UnixNanos,
794) -> anyhow::Result<(Ustr, UnixNanos)> {
795    check_valid_string_utf8(name, stringify!(name))?;
796
797    let name = Ustr::from(name);
798    let allow_past = allow_past.unwrap_or(true);
799
800    if alert_time_ns < ts_now {
801        if allow_past {
802            log::warn!(
803                "Timer '{name}' alert time {} was in the past, adjusted to current time for immediate firing",
804                alert_time_ns.to_rfc3339(),
805            );
806            alert_time_ns = ts_now;
807        } else {
808            anyhow::bail!(
809                "Timer '{name}' alert time {} was in the past (current time is {ts_now})",
810                alert_time_ns.to_rfc3339(),
811            );
812        }
813    }
814
815    Ok((name, alert_time_ns))
816}
817
818/// Validates and normalizes parameters for an interval timer.
819///
820/// A missing or zero `start_time_ns` resolves to `ts_now`. `allow_past` defaults to `true`, and
821/// `fire_immediately` defaults to `false`. Returns the interned name, normalized start and stop
822/// times, and resolved flag values.
823///
824/// # Errors
825///
826/// Returns an error if:
827/// - `name` is invalid.
828/// - `interval_ns` is zero.
829/// - `start_time_ns + interval_ns` is out of range for `UnixNanos` when not firing immediately.
830/// - The first event is in the past when past times are disallowed.
831/// - The stop time is not after the normalized start time.
832/// - The stop time is not after `ts_now` when past times are disallowed.
833pub fn validate_and_prepare_timer(
834    name: &str,
835    interval_ns: u64,
836    start_time_ns: Option<UnixNanos>,
837    stop_time_ns: Option<UnixNanos>,
838    allow_past: Option<bool>,
839    fire_immediately: Option<bool>,
840    ts_now: UnixNanos,
841) -> anyhow::Result<(Ustr, UnixNanos, Option<UnixNanos>, bool, bool)> {
842    check_valid_string_utf8(name, stringify!(name))?;
843    check_positive_u64(interval_ns, stringify!(interval_ns))?;
844
845    let name = Ustr::from(name);
846    let allow_past = allow_past.unwrap_or(true);
847    let fire_immediately = fire_immediately.unwrap_or(false);
848
849    let start_time_ns = start_time_ns
850        .filter(|start_time_ns| *start_time_ns != 0)
851        .unwrap_or(ts_now);
852
853    let next_event_time = if fire_immediately {
854        start_time_ns
855    } else {
856        start_time_ns.checked_add(interval_ns).ok_or_else(|| {
857            anyhow::anyhow!("Timer '{name}' first event time exceeds UnixNanos range")
858        })?
859    };
860
861    if !allow_past && next_event_time < ts_now {
862        anyhow::bail!(
863            "Timer '{name}' next event time {} would be in the past (current time is {ts_now})",
864            next_event_time.to_rfc3339(),
865        );
866    }
867
868    if let Some(stop_time) = stop_time_ns {
869        if stop_time <= start_time_ns {
870            anyhow::bail!(
871                "Timer '{name}' stop time {} must be after start time {}",
872                stop_time.to_rfc3339(),
873                start_time_ns.to_rfc3339(),
874            );
875        }
876
877        if !allow_past && stop_time <= ts_now {
878            anyhow::bail!(
879                "Timer '{name}' stop time {} is in the past (current time is {ts_now})",
880                stop_time.to_rfc3339(),
881            );
882        }
883    }
884
885    Ok((
886        name,
887        start_time_ns,
888        stop_time_ns,
889        allow_past,
890        fire_immediately,
891    ))
892}
893
894/// A deterministic clock for controlled time advancement.
895///
896/// The clock stores a manual timestamp, schedules [`TestTimer`] instances, and returns due events
897/// without waiting for wall-clock time.
898///
899/// # Threading
900///
901/// This clock is thread-affine; use it only from the thread that created it.
902#[derive(Debug)]
903pub struct TestClock {
904    time: AtomicTime,
905    timers: BTreeMap<Ustr, TestTimer>,
906    timer_queue: BinaryHeap<ScheduledTimeEvent>,
907    callbacks: CallbackRegistry,
908}
909
910impl TestClock {
911    /// Creates a test clock at the UNIX epoch with no timers or callbacks.
912    #[must_use]
913    pub fn new() -> Self {
914        Self {
915            time: AtomicTime::new(false, UnixNanos::default()),
916            timers: BTreeMap::new(),
917            timer_queue: BinaryHeap::new(),
918            callbacks: CallbackRegistry::new(),
919        }
920    }
921
922    /// Advances active timers through `to_time_ns` and returns their due events.
923    ///
924    /// Events at `to_time_ns` are included and returned in ascending order by event timestamp and
925    /// timer name. If `set_time` is `true`, the stored clock timestamp is also set to `to_time_ns`;
926    /// otherwise the stored timestamp remains unchanged.
927    ///
928    /// # Warnings
929    ///
930    /// Logs a warning if at least 1,000,000 time events are allocated during advancement.
931    ///
932    /// # Panics
933    ///
934    /// Panics if `to_time_ns` is less than the current internal clock time.
935    pub fn advance_time(&mut self, to_time_ns: UnixNanos, set_time: bool) -> Vec<TimeEvent> {
936        const WARN_TIME_EVENTS_THRESHOLD: usize = 1_000_000;
937
938        let from_time_ns = self.time.get_time_ns();
939
940        assert!(
941            to_time_ns >= from_time_ns,
942            "Invariant: time must be non-decreasing, `to_time_ns` {to_time_ns} < `from_time_ns` {from_time_ns}"
943        );
944
945        if set_time {
946            self.time.set_time(to_time_ns);
947        }
948
949        let mut events: Vec<TimeEvent> = Vec::new();
950
951        while self
952            .timer_queue
953            .peek()
954            .is_some_and(|entry| entry.0.ts_event <= to_time_ns)
955        {
956            let entry = self
957                .timer_queue
958                .pop()
959                .expect("timer queue peeked Some but pop returned None");
960
961            let Some((event, next_event)) = self.advance_timer_from_entry(&entry.0) else {
962                continue;
963            };
964
965            events.push(event);
966            if let Some(next_event) = next_event {
967                self.timer_queue.push(next_event);
968            }
969        }
970
971        self.compact_timer_queue_if_needed();
972
973        if events.len() >= WARN_TIME_EVENTS_THRESHOLD {
974            log::warn!(
975                "Allocated {} time events during clock advancement from {} to {}, \
976                 consider stopping the timer between large time ranges with no data points",
977                events.len().separate_with_commas(),
978                from_time_ns,
979                to_time_ns
980            );
981        }
982
983        events.sort_by(|a, b| {
984            a.ts_event
985                .cmp(&b.ts_event)
986                .then_with(|| a.name.cmp(&b.name))
987        });
988        events
989    }
990
991    /// Matches time events with their registered callbacks, preserving input order.
992    ///
993    /// A named callback takes precedence over the default callback for each event.
994    ///
995    /// # Panics
996    ///
997    /// Panics if an event has neither a named nor default callback.
998    #[must_use]
999    pub fn match_handlers(&self, events: Vec<TimeEvent>) -> Vec<TimeEventHandler> {
1000        events
1001            .into_iter()
1002            .map(|event| self.callbacks.get_handler(event))
1003            .collect()
1004    }
1005
1006    fn replace_existing_timer_if_needed(&mut self, name: &Ustr) {
1007        replace_existing_timer(&mut self.timers, name);
1008        self.compact_timer_queue_if_needed();
1009    }
1010
1011    fn insert_timer(&mut self, timer: TestTimer) {
1012        self.timer_queue.push(Self::scheduled_event(&timer));
1013        self.timers.insert(timer.name, timer);
1014        self.compact_timer_queue_if_needed();
1015    }
1016
1017    fn advance_timer_from_entry(
1018        &mut self,
1019        entry: &TimeEvent,
1020    ) -> Option<(TimeEvent, Option<ScheduledTimeEvent>)> {
1021        let timer = self.timers.get_mut(&entry.name)?;
1022        if timer.next_time_ns() != entry.ts_event {
1023            return None;
1024        }
1025
1026        let Some((event, _)) = timer.next() else {
1027            self.timers.remove(&entry.name);
1028            return None;
1029        };
1030
1031        let next_entry = if timer.is_expired() {
1032            self.timers.remove(&entry.name);
1033            None
1034        } else {
1035            Some(Self::scheduled_event(timer))
1036        };
1037
1038        Some((event, next_entry))
1039    }
1040
1041    fn compact_timer_queue_if_needed(&mut self) {
1042        if self.timer_queue.len() > self.timers.len().saturating_mul(2) {
1043            self.compact_timer_queue();
1044        }
1045    }
1046
1047    fn compact_timer_queue(&mut self) {
1048        self.timer_queue = self.timers.values().map(Self::scheduled_event).collect();
1049    }
1050
1051    fn scheduled_event(timer: &TestTimer) -> ScheduledTimeEvent {
1052        ScheduledTimeEvent::new(TimeEvent::new(
1053            timer.name,
1054            UUID4::new(),
1055            timer.next_time_ns(),
1056            timer.next_time_ns(),
1057        ))
1058    }
1059}
1060
1061impl Default for TestClock {
1062    /// Creates a new default [`TestClock`] instance.
1063    fn default() -> Self {
1064        Self::new()
1065    }
1066}
1067
1068impl Deref for TestClock {
1069    type Target = AtomicTime;
1070
1071    fn deref(&self) -> &Self::Target {
1072        &self.time
1073    }
1074}
1075
1076impl Clock for TestClock {
1077    fn timestamp_ns(&self) -> UnixNanos {
1078        self.time.get_time_ns()
1079    }
1080
1081    fn timestamp_us(&self) -> u64 {
1082        self.time.get_time_us()
1083    }
1084
1085    fn timestamp_ms(&self) -> u64 {
1086        self.time.get_time_ms()
1087    }
1088
1089    fn timestamp(&self) -> f64 {
1090        self.time.get_time()
1091    }
1092
1093    fn timer_names(&self) -> Vec<&str> {
1094        self.timers
1095            .iter()
1096            .filter(|(_, timer)| !timer.is_expired())
1097            .map(|(k, _)| k.as_str())
1098            .collect()
1099    }
1100
1101    fn timer_count(&self) -> usize {
1102        self.timers
1103            .iter()
1104            .filter(|(_, timer)| !timer.is_expired())
1105            .count()
1106    }
1107
1108    fn timer_exists(&self, name: &Ustr) -> bool {
1109        self.timers
1110            .get(name)
1111            .is_some_and(|timer| !timer.is_expired())
1112    }
1113
1114    fn register_default_handler(&mut self, callback: TimeEventCallback) {
1115        self.callbacks.register_default_handler(callback);
1116    }
1117
1118    fn cancel_default_handler(&mut self) {
1119        self.callbacks.cancel_default_handler();
1120    }
1121
1122    fn cancel_callbacks(&mut self) {
1123        self.callbacks.clear();
1124    }
1125
1126    fn set_time_alert_ns(
1127        &mut self,
1128        name: &str,
1129        alert_time_ns: UnixNanos,
1130        callback: Option<TimeEventCallback>,
1131        allow_past: Option<bool>,
1132    ) -> anyhow::Result<()> {
1133        let ts_now = self.get_time_ns();
1134        let (name, alert_time_ns) =
1135            validate_and_prepare_time_alert(name, alert_time_ns, allow_past, ts_now)?;
1136
1137        check_predicate_true(
1138            callback.is_some() | self.callbacks.has_any_callback(&name),
1139            "No callbacks provided",
1140        )?;
1141
1142        self.replace_existing_timer_if_needed(&name);
1143
1144        if let Some(callback) = callback {
1145            self.callbacks.register_callback(name, callback);
1146        }
1147
1148        // Safe to calculate interval now that we've ensured alert_time_ns >= ts_now
1149        let interval_ns = create_valid_interval((alert_time_ns - ts_now).into());
1150        let fire_immediately = alert_time_ns == ts_now;
1151
1152        let timer = TestTimer::new(
1153            name,
1154            interval_ns,
1155            ts_now,
1156            Some(alert_time_ns),
1157            fire_immediately,
1158        );
1159        self.insert_timer(timer);
1160
1161        Ok(())
1162    }
1163
1164    fn set_timer_ns(
1165        &mut self,
1166        name: &str,
1167        interval_ns: u64,
1168        start_time_ns: Option<UnixNanos>,
1169        stop_time_ns: Option<UnixNanos>,
1170        callback: Option<TimeEventCallback>,
1171        allow_past: Option<bool>,
1172        fire_immediately: Option<bool>,
1173    ) -> anyhow::Result<()> {
1174        let ts_now = self.get_time_ns();
1175        let (name, start_time_ns, stop_time_ns, _allow_past, fire_immediately) =
1176            validate_and_prepare_timer(
1177                name,
1178                interval_ns,
1179                start_time_ns,
1180                stop_time_ns,
1181                allow_past,
1182                fire_immediately,
1183                ts_now,
1184            )?;
1185
1186        check_predicate_true(
1187            callback.is_some() | self.callbacks.has_any_callback(&name),
1188            "No callbacks provided",
1189        )?;
1190
1191        self.replace_existing_timer_if_needed(&name);
1192
1193        if let Some(callback) = callback {
1194            self.callbacks.register_callback(name, callback);
1195        }
1196
1197        let interval_ns = create_valid_interval(interval_ns);
1198
1199        let timer = TestTimer::new(
1200            name,
1201            interval_ns,
1202            start_time_ns,
1203            stop_time_ns,
1204            fire_immediately,
1205        );
1206        self.insert_timer(timer);
1207
1208        Ok(())
1209    }
1210
1211    fn next_time_ns(&self, name: &str) -> Option<UnixNanos> {
1212        self.timers
1213            .get(&Ustr::from(name))
1214            .filter(|timer| !timer.is_expired())
1215            .map(TestTimer::next_time_ns)
1216    }
1217
1218    fn cancel_timer(&mut self, name: &str) {
1219        let timer = self.timers.remove(&Ustr::from(name));
1220        if let Some(mut timer) = timer {
1221            timer.cancel();
1222        }
1223        self.compact_timer_queue_if_needed();
1224    }
1225
1226    fn cancel_timers(&mut self) {
1227        for timer in &mut self.timers.values_mut() {
1228            timer.cancel();
1229        }
1230
1231        self.timers.clear();
1232        self.timer_queue.clear();
1233    }
1234
1235    fn reset(&mut self) {
1236        self.time = AtomicTime::new(false, UnixNanos::default());
1237        self.timers = BTreeMap::new();
1238        self.timer_queue = BinaryHeap::new();
1239        self.callbacks.clear();
1240    }
1241}
1242
1243pub(crate) fn replace_existing_timer<T: Timer>(timers: &mut BTreeMap<Ustr, T>, name: &Ustr) {
1244    let Some(mut timer) = timers.remove(name) else {
1245        return;
1246    };
1247
1248    if timer.is_expired() {
1249        return;
1250    }
1251
1252    timer.cancel();
1253    log::warn!("Timer '{name}' replaced");
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258    use std::{cell::RefCell, collections::BTreeMap, sync::Arc, time::Duration};
1259
1260    use nautilus_core::UnixNanos;
1261    use parking_lot::Mutex;
1262    use proptest::{prelude::*, test_runner::TestCaseResult};
1263    use rstest::{fixture, rstest};
1264    use ustr::Ustr;
1265
1266    use super::*;
1267    use crate::timer::{TimeEvent, TimeEventCallback};
1268
1269    #[derive(Debug, Default)]
1270    struct TestCallback {
1271        /// Shared flag updated from within the timer callback; Mutex keeps the closure `Send` for tests.
1272        called: Arc<Mutex<bool>>,
1273    }
1274
1275    impl TestCallback {
1276        fn new(called: Arc<Mutex<bool>>) -> Self {
1277            Self { called }
1278        }
1279    }
1280
1281    impl From<TestCallback> for TimeEventCallback {
1282        fn from(callback: TestCallback) -> Self {
1283            Self::from(move |_event: TimeEvent| {
1284                *callback.called.lock() = true;
1285            })
1286        }
1287    }
1288
1289    #[fixture]
1290    pub fn test_clock() -> TestClock {
1291        let mut clock = TestClock::new();
1292        clock.register_default_handler(TestCallback::default().into());
1293        clock
1294    }
1295
1296    #[rstest]
1297    fn test_time_monotonicity(mut test_clock: TestClock) {
1298        let initial_time = test_clock.timestamp_ns();
1299        test_clock.advance_time(UnixNanos::from(*initial_time + 1000), true);
1300        assert!(test_clock.timestamp_ns() > initial_time);
1301    }
1302
1303    #[rstest]
1304    fn test_timer_registration(mut test_clock: TestClock) {
1305        test_clock
1306            .set_time_alert_ns(
1307                "test_timer",
1308                (*test_clock.timestamp_ns() + 1000).into(),
1309                None,
1310                None,
1311            )
1312            .unwrap();
1313        assert_eq!(test_clock.timer_count(), 1);
1314        assert_eq!(test_clock.timer_names(), vec!["test_timer"]);
1315    }
1316
1317    #[rstest]
1318    fn test_timer_expiration(mut test_clock: TestClock) {
1319        let alert_time = (*test_clock.timestamp_ns() + 1000).into();
1320        test_clock
1321            .set_time_alert_ns("test_timer", alert_time, None, None)
1322            .unwrap();
1323        let events = test_clock.advance_time(alert_time, true);
1324        assert_eq!(events.len(), 1);
1325        assert_eq!(events[0].name.as_str(), "test_timer");
1326    }
1327
1328    #[rstest]
1329    fn test_timer_cancellation(mut test_clock: TestClock) {
1330        test_clock
1331            .set_time_alert_ns(
1332                "test_timer",
1333                (*test_clock.timestamp_ns() + 1000).into(),
1334                None,
1335                None,
1336            )
1337            .unwrap();
1338        assert_eq!(test_clock.timer_count(), 1);
1339        test_clock.cancel_timer("test_timer");
1340        assert_eq!(test_clock.timer_count(), 0);
1341    }
1342
1343    #[rstest]
1344    fn test_time_advancement(mut test_clock: TestClock) {
1345        let start_time = test_clock.timestamp_ns();
1346        test_clock
1347            .set_timer_ns("test_timer", 1000, Some(start_time), None, None, None, None)
1348            .unwrap();
1349        let events = test_clock.advance_time(UnixNanos::from(*start_time + 2500), true);
1350        assert_eq!(events.len(), 2);
1351        assert_eq!(*events[0].ts_event, *start_time + 1000);
1352        assert_eq!(*events[1].ts_event, *start_time + 2000);
1353    }
1354
1355    #[rstest]
1356    fn test_default_and_custom_callbacks() {
1357        let mut clock = TestClock::new();
1358        let default_called = Arc::new(Mutex::new(false));
1359        let custom_called = Arc::new(Mutex::new(false));
1360
1361        let default_callback = TestCallback::new(Arc::clone(&default_called));
1362        let custom_callback = TestCallback::new(Arc::clone(&custom_called));
1363
1364        clock.register_default_handler(TimeEventCallback::from(default_callback));
1365        clock
1366            .set_time_alert_ns(
1367                "default_timer",
1368                (*clock.timestamp_ns() + 1000).into(),
1369                None,
1370                None,
1371            )
1372            .unwrap();
1373        clock
1374            .set_time_alert_ns(
1375                "custom_timer",
1376                (*clock.timestamp_ns() + 1000).into(),
1377                Some(TimeEventCallback::from(custom_callback)),
1378                None,
1379            )
1380            .unwrap();
1381
1382        let events = clock.advance_time(UnixNanos::from(*clock.timestamp_ns() + 1000), true);
1383        let handlers = clock.match_handlers(events);
1384
1385        for handler in handlers {
1386            handler.callback.call(handler.event);
1387        }
1388
1389        assert!(*default_called.lock());
1390        assert!(*custom_called.lock());
1391    }
1392
1393    #[rstest]
1394    fn test_timer_with_rust_local_callback() {
1395        use std::{cell::RefCell, rc::Rc};
1396
1397        let mut clock = TestClock::new();
1398        let call_count = Rc::new(RefCell::new(0_u32));
1399        let call_count_clone = Rc::clone(&call_count);
1400
1401        // Create RustLocal callback using Rc (not Send/Sync)
1402        let callback: Rc<dyn Fn(TimeEvent)> = Rc::new(move |_event: TimeEvent| {
1403            *call_count_clone.borrow_mut() += 1;
1404        });
1405
1406        clock
1407            .set_time_alert_ns(
1408                "local_timer",
1409                (*clock.timestamp_ns() + 1000).into(),
1410                Some(TimeEventCallback::from(callback)),
1411                None,
1412            )
1413            .unwrap();
1414
1415        let events = clock.advance_time(UnixNanos::from(*clock.timestamp_ns() + 1000), true);
1416        let handlers = clock.match_handlers(events);
1417
1418        for handler in handlers {
1419            handler.callback.call(handler.event);
1420        }
1421
1422        assert_eq!(*call_count.borrow(), 1);
1423    }
1424
1425    #[rstest]
1426    fn test_multiple_timers(mut test_clock: TestClock) {
1427        let start_time = test_clock.timestamp_ns();
1428        test_clock
1429            .set_timer_ns("timer1", 1000, Some(start_time), None, None, None, None)
1430            .unwrap();
1431        test_clock
1432            .set_timer_ns("timer2", 2000, Some(start_time), None, None, None, None)
1433            .unwrap();
1434        let events = test_clock.advance_time(UnixNanos::from(*start_time + 2000), true);
1435        assert_eq!(events.len(), 3);
1436        assert_eq!(events[0].name.as_str(), "timer1");
1437        assert_eq!(events[1].name.as_str(), "timer1");
1438        assert_eq!(events[2].name.as_str(), "timer2");
1439    }
1440
1441    #[rstest]
1442    fn test_allow_past_parameter_true(mut test_clock: TestClock) {
1443        test_clock.set_time(UnixNanos::from(2000));
1444        let current_time = test_clock.timestamp_ns();
1445        let past_time = UnixNanos::from(current_time.as_u64() - 1000);
1446
1447        // With allow_past=true (default), should adjust to current time and succeed
1448        test_clock
1449            .set_time_alert_ns("past_timer", past_time, None, Some(true))
1450            .unwrap();
1451
1452        // Verify timer was created with adjusted time
1453        assert_eq!(test_clock.timer_count(), 1);
1454        assert_eq!(test_clock.timer_names(), vec!["past_timer"]);
1455
1456        // Next time should be at or after current time, not in the past
1457        let next_time = test_clock.next_time_ns("past_timer").unwrap();
1458        assert!(next_time >= current_time);
1459    }
1460
1461    #[rstest]
1462    fn test_allow_past_parameter_false(mut test_clock: TestClock) {
1463        test_clock.set_time(UnixNanos::from(2000));
1464        let current_time = test_clock.timestamp_ns();
1465        let past_time = current_time - 1000;
1466
1467        // With allow_past=false, should fail for past times
1468        let result = test_clock.set_time_alert_ns("past_timer", past_time, None, Some(false));
1469
1470        // Verify the operation failed with appropriate error
1471        assert!(result.is_err());
1472        assert!(format!("{}", result.unwrap_err()).contains("was in the past"));
1473
1474        // Verify no timer was created
1475        assert_eq!(test_clock.timer_count(), 0);
1476        assert!(test_clock.timer_names().is_empty());
1477    }
1478
1479    #[rstest]
1480    fn test_invalid_stop_time_validation(mut test_clock: TestClock) {
1481        test_clock.set_time(UnixNanos::from(2000));
1482        let current_time = test_clock.timestamp_ns();
1483        let start_time = current_time + 1000;
1484        let stop_time = current_time + 500; // Stop time before start time
1485
1486        // Should fail because stop_time < start_time
1487        let result = test_clock.set_timer_ns(
1488            "invalid_timer",
1489            100,
1490            Some(start_time),
1491            Some(stop_time),
1492            None,
1493            None,
1494            None,
1495        );
1496
1497        // Verify the operation failed with appropriate error
1498        assert!(result.is_err());
1499        assert!(format!("{}", result.unwrap_err()).contains("must be after start time"));
1500
1501        // Verify no timer was created
1502        assert_eq!(test_clock.timer_count(), 0);
1503    }
1504
1505    #[rstest]
1506    fn test_set_timer_ns_fire_immediately_true(mut test_clock: TestClock) {
1507        let start_time = test_clock.timestamp_ns();
1508        let interval_ns = 1000;
1509
1510        test_clock
1511            .set_timer_ns(
1512                "fire_immediately_timer",
1513                interval_ns,
1514                Some(start_time),
1515                None,
1516                None,
1517                None,
1518                Some(true),
1519            )
1520            .unwrap();
1521
1522        // Advance time to check immediate firing and subsequent intervals
1523        let events = test_clock.advance_time(start_time + 2500, true);
1524
1525        // Should fire immediately at start_time (0), then at start_time+1000, then at start_time+2000
1526        assert_eq!(events.len(), 3);
1527        assert_eq!(*events[0].ts_event, *start_time); // Fires immediately
1528        assert_eq!(*events[1].ts_event, *start_time + 1000); // Then after interval
1529        assert_eq!(*events[2].ts_event, *start_time + 2000); // Then after second interval
1530    }
1531
1532    #[rstest]
1533    fn test_set_timer_ns_fire_immediately_false(mut test_clock: TestClock) {
1534        let start_time = test_clock.timestamp_ns();
1535        let interval_ns = 1000;
1536
1537        test_clock
1538            .set_timer_ns(
1539                "normal_timer",
1540                interval_ns,
1541                Some(start_time),
1542                None,
1543                None,
1544                None,
1545                Some(false),
1546            )
1547            .unwrap();
1548
1549        // Advance time to check normal behavior
1550        let events = test_clock.advance_time(start_time + 2500, true);
1551
1552        // Should fire after first interval, not immediately
1553        assert_eq!(events.len(), 2);
1554        assert_eq!(*events[0].ts_event, *start_time + 1000); // Fires after first interval
1555        assert_eq!(*events[1].ts_event, *start_time + 2000); // Then after second interval
1556    }
1557
1558    #[rstest]
1559    fn test_set_timer_ns_fire_immediately_default_is_false(mut test_clock: TestClock) {
1560        let start_time = test_clock.timestamp_ns();
1561        let interval_ns = 1000;
1562
1563        // Don't specify fire_immediately (should default to false)
1564        test_clock
1565            .set_timer_ns(
1566                "default_timer",
1567                interval_ns,
1568                Some(start_time),
1569                None,
1570                None,
1571                None,
1572                None,
1573            )
1574            .unwrap();
1575
1576        let events = test_clock.advance_time(start_time + 1500, true);
1577
1578        // Should behave the same as fire_immediately=false
1579        assert_eq!(events.len(), 1);
1580        assert_eq!(*events[0].ts_event, *start_time + 1000); // Fires after first interval
1581    }
1582
1583    #[rstest]
1584    fn test_set_timer_ns_fire_immediately_with_zero_start_time(mut test_clock: TestClock) {
1585        test_clock.set_time(5000.into());
1586        let interval_ns = 1000;
1587
1588        test_clock
1589            .set_timer_ns(
1590                "zero_start_timer",
1591                interval_ns,
1592                None,
1593                None,
1594                None,
1595                None,
1596                Some(true),
1597            )
1598            .unwrap();
1599
1600        let events = test_clock.advance_time(UnixNanos::from(7000), true);
1601
1602        // With zero start time, should use current time as start
1603        // Fire immediately at current time (5000), then at 6000, 7000
1604        assert_eq!(events.len(), 3);
1605        assert_eq!(*events[0].ts_event, 5000); // Immediate fire at current time
1606        assert_eq!(*events[1].ts_event, 6000);
1607        assert_eq!(*events[2].ts_event, 7000);
1608    }
1609
1610    #[rstest]
1611    fn test_multiple_timers_different_fire_immediately_settings(mut test_clock: TestClock) {
1612        let start_time = test_clock.timestamp_ns();
1613        let interval_ns = 1000;
1614
1615        // One timer with fire_immediately=true
1616        test_clock
1617            .set_timer_ns(
1618                "immediate_timer",
1619                interval_ns,
1620                Some(start_time),
1621                None,
1622                None,
1623                None,
1624                Some(true),
1625            )
1626            .unwrap();
1627
1628        // One timer with fire_immediately=false
1629        test_clock
1630            .set_timer_ns(
1631                "normal_timer",
1632                interval_ns,
1633                Some(start_time),
1634                None,
1635                None,
1636                None,
1637                Some(false),
1638            )
1639            .unwrap();
1640
1641        let events = test_clock.advance_time(start_time + 1500, true);
1642
1643        // Should have 3 events total: immediate_timer fires at start & 1000, normal_timer fires at 1000
1644        assert_eq!(events.len(), 3);
1645
1646        // Sort events by timestamp to check order
1647        let mut event_times: Vec<u64> = events.iter().map(|e| e.ts_event.as_u64()).collect();
1648        event_times.sort_unstable();
1649
1650        assert_eq!(event_times[0], start_time.as_u64()); // immediate_timer fires immediately
1651        assert_eq!(event_times[1], start_time.as_u64() + 1000); // both timers fire at 1000
1652        assert_eq!(event_times[2], start_time.as_u64() + 1000); // both timers fire at 1000
1653    }
1654
1655    #[rstest]
1656    fn test_timer_name_collision_overwrites(mut test_clock: TestClock) {
1657        let start_time = test_clock.timestamp_ns();
1658
1659        // Set first timer
1660        test_clock
1661            .set_timer_ns(
1662                "collision_timer",
1663                1000,
1664                Some(start_time),
1665                None,
1666                None,
1667                None,
1668                None,
1669            )
1670            .unwrap();
1671
1672        // Setting timer with same name should overwrite the existing one
1673        let result = test_clock.set_timer_ns(
1674            "collision_timer",
1675            2000,
1676            Some(start_time),
1677            None,
1678            None,
1679            None,
1680            None,
1681        );
1682
1683        assert!(result.is_ok());
1684        // Should still only have one timer (overwritten)
1685        assert_eq!(test_clock.timer_count(), 1);
1686
1687        // The timer should have the new interval
1688        let next_time = test_clock.next_time_ns("collision_timer").unwrap();
1689        // With interval 2000 and start at start_time, next time should be start_time + 2000
1690        assert_eq!(next_time, start_time + 2000);
1691    }
1692
1693    #[rstest]
1694    fn test_timer_zero_interval_error(mut test_clock: TestClock) {
1695        let start_time = test_clock.timestamp_ns();
1696
1697        // Attempt to set timer with zero interval should fail
1698        let result =
1699            test_clock.set_timer_ns("zero_interval", 0, Some(start_time), None, None, None, None);
1700
1701        assert!(result.is_err());
1702        assert_eq!(test_clock.timer_count(), 0);
1703    }
1704
1705    #[rstest]
1706    fn test_timer_empty_name_error(mut test_clock: TestClock) {
1707        let start_time = test_clock.timestamp_ns();
1708
1709        // Attempt to set timer with empty name should fail
1710        let result = test_clock.set_timer_ns("", 1000, Some(start_time), None, None, None, None);
1711
1712        assert!(result.is_err());
1713        assert_eq!(test_clock.timer_count(), 0);
1714    }
1715
1716    #[rstest]
1717    fn test_timer_exists(mut test_clock: TestClock) {
1718        let name = Ustr::from("exists_timer");
1719        assert!(!test_clock.timer_exists(&name));
1720
1721        test_clock
1722            .set_time_alert_ns(
1723                name.as_str(),
1724                (*test_clock.timestamp_ns() + 1_000).into(),
1725                None,
1726                None,
1727            )
1728            .unwrap();
1729
1730        assert!(test_clock.timer_exists(&name));
1731    }
1732
1733    #[rstest]
1734    fn test_timer_exists_consistent_with_names_and_count_after_expiry(mut test_clock: TestClock) {
1735        let name = Ustr::from("expiring_timer");
1736        let start_time = test_clock.timestamp_ns();
1737
1738        test_clock
1739            .set_timer_ns(
1740                name.as_str(),
1741                1_000,
1742                Some(start_time),
1743                Some(start_time + 2_500),
1744                None,
1745                None,
1746                None,
1747            )
1748            .unwrap();
1749
1750        assert!(test_clock.timer_exists(&name));
1751        assert_eq!(test_clock.timer_count(), 1);
1752
1753        test_clock.advance_time(start_time + 10_000, true);
1754
1755        // All three introspection surfaces must agree the timer is gone
1756        assert!(!test_clock.timer_exists(&name));
1757        assert_eq!(test_clock.timer_count(), 0);
1758        assert!(test_clock.timer_names().is_empty());
1759    }
1760
1761    #[rstest]
1762    fn test_timer_rejects_past_stop_time_when_not_allowed(mut test_clock: TestClock) {
1763        test_clock.set_time(UnixNanos::from(10_000));
1764        let current = test_clock.timestamp_ns();
1765
1766        let result = test_clock.set_timer_ns(
1767            "past_stop",
1768            10_000,
1769            Some(current - 500),
1770            Some(current - 100),
1771            None,
1772            Some(false),
1773            None,
1774        );
1775
1776        let err = result.expect_err("expected stop time validation error");
1777        let err_msg = err.to_string();
1778        assert!(err_msg.contains("stop time"));
1779        assert!(err_msg.contains("in the past"));
1780    }
1781
1782    #[rstest]
1783    fn test_timer_accepts_future_stop_time(mut test_clock: TestClock) {
1784        let current = test_clock.timestamp_ns();
1785
1786        let result = test_clock.set_timer_ns(
1787            "future_stop",
1788            1_000,
1789            Some(current),
1790            Some(current + 10_000),
1791            None,
1792            Some(false),
1793            None,
1794        );
1795
1796        assert!(result.is_ok());
1797    }
1798
1799    #[rstest]
1800    fn test_timer_fire_immediately_at_exact_stop_time(mut test_clock: TestClock) {
1801        let start_time = test_clock.timestamp_ns();
1802        let interval_ns = 1000;
1803        let stop_time = start_time + interval_ns; // Stop exactly at first interval
1804
1805        test_clock
1806            .set_timer_ns(
1807                "exact_stop",
1808                interval_ns,
1809                Some(start_time),
1810                Some(stop_time),
1811                None,
1812                None,
1813                Some(true),
1814            )
1815            .unwrap();
1816
1817        let events = test_clock.advance_time(stop_time, true);
1818
1819        // Should fire immediately at start, then at stop time (which equals first interval)
1820        assert_eq!(events.len(), 2);
1821        assert_eq!(*events[0].ts_event, *start_time); // Immediate fire
1822        assert_eq!(*events[1].ts_event, *stop_time); // Fire at stop time
1823    }
1824
1825    #[rstest]
1826    fn test_timer_advance_to_exact_next_time(mut test_clock: TestClock) {
1827        let start_time = test_clock.timestamp_ns();
1828        let interval_ns = 1000;
1829
1830        test_clock
1831            .set_timer_ns(
1832                "exact_advance",
1833                interval_ns,
1834                Some(start_time),
1835                None,
1836                None,
1837                None,
1838                Some(false),
1839            )
1840            .unwrap();
1841
1842        // Advance to exactly the next fire time
1843        let next_time = test_clock.next_time_ns("exact_advance").unwrap();
1844        let events = test_clock.advance_time(next_time, true);
1845
1846        assert_eq!(events.len(), 1);
1847        assert_eq!(*events[0].ts_event, *next_time);
1848    }
1849
1850    #[rstest]
1851    fn test_allow_past_bar_aggregation_use_case(mut test_clock: TestClock) {
1852        // Simulate bar aggregation scenario: current time is in middle of a bar window
1853        test_clock.set_time(UnixNanos::from(100_500)); // 100.5 seconds
1854
1855        let bar_start_time = UnixNanos::from(100_000); // 100 seconds (0.5 sec ago)
1856        let interval_ns = 1000; // 1 second bars
1857
1858        // With allow_past=false and fire_immediately=false:
1859        // start_time is in past (100 sec) but next event (101 sec) is in future
1860        // This should be ALLOWED for bar aggregation
1861        let result = test_clock.set_timer_ns(
1862            "bar_timer",
1863            interval_ns,
1864            Some(bar_start_time),
1865            None,
1866            None,
1867            Some(false), // allow_past = false
1868            Some(false), // fire_immediately = false
1869        );
1870
1871        // Should succeed because next event time (100_000 + 1000 = 101_000) > current time (100_500)
1872        assert!(result.is_ok());
1873        assert_eq!(test_clock.timer_count(), 1);
1874
1875        // Next event should be at bar_start_time + interval = 101_000
1876        let next_time = test_clock.next_time_ns("bar_timer").unwrap();
1877        assert_eq!(*next_time, 101_000);
1878    }
1879
1880    #[rstest]
1881    fn test_allow_past_false_rejects_when_next_event_in_past(mut test_clock: TestClock) {
1882        test_clock.set_time(UnixNanos::from(102_000)); // 102 seconds
1883
1884        let past_start_time = UnixNanos::from(100_000); // 100 seconds (2 sec ago)
1885        let interval_ns = 1000; // 1 second interval
1886
1887        // With allow_past=false and fire_immediately=false:
1888        // Next event would be 100_000 + 1000 = 101_000, which is < current time (102_000)
1889        // This should be REJECTED
1890        let result = test_clock.set_timer_ns(
1891            "past_event_timer",
1892            interval_ns,
1893            Some(past_start_time),
1894            None,
1895            None,
1896            Some(false), // allow_past = false
1897            Some(false), // fire_immediately = false
1898        );
1899
1900        // Should fail because next event time (101_000) < current time (102_000)
1901        assert!(result.is_err());
1902        assert!(
1903            result
1904                .unwrap_err()
1905                .to_string()
1906                .contains("would be in the past")
1907        );
1908    }
1909
1910    #[rstest]
1911    fn test_allow_past_false_with_fire_immediately_true(mut test_clock: TestClock) {
1912        test_clock.set_time(UnixNanos::from(100_500)); // 100.5 seconds
1913
1914        let past_start_time = UnixNanos::from(100_000); // 100 seconds (0.5 sec ago)
1915        let interval_ns = 1000;
1916
1917        // With fire_immediately=true, next event = start_time (which is in past)
1918        // This should be REJECTED with allow_past=false
1919        let result = test_clock.set_timer_ns(
1920            "immediate_past_timer",
1921            interval_ns,
1922            Some(past_start_time),
1923            None,
1924            None,
1925            Some(false), // allow_past = false
1926            Some(true),  // fire_immediately = true
1927        );
1928
1929        // Should fail because next event time (100_000) < current time (100_500)
1930        assert!(result.is_err());
1931        assert!(
1932            result
1933                .unwrap_err()
1934                .to_string()
1935                .contains("would be in the past")
1936        );
1937    }
1938
1939    #[rstest]
1940    fn test_cancel_timer_during_execution(mut test_clock: TestClock) {
1941        let start_time = test_clock.timestamp_ns();
1942
1943        test_clock
1944            .set_timer_ns(
1945                "cancel_test",
1946                1000,
1947                Some(start_time),
1948                None,
1949                None,
1950                None,
1951                None,
1952            )
1953            .unwrap();
1954
1955        assert_eq!(test_clock.timer_count(), 1);
1956
1957        // Cancel the timer
1958        test_clock.cancel_timer("cancel_test");
1959
1960        assert_eq!(test_clock.timer_count(), 0);
1961
1962        // Advance time - should get no events from cancelled timer
1963        let events = test_clock.advance_time(start_time + 2000, true);
1964        assert_eq!(events.len(), 0);
1965    }
1966
1967    #[rstest]
1968    fn test_cancelled_timer_queue_entry_is_skipped(mut test_clock: TestClock) {
1969        let start_time = test_clock.timestamp_ns();
1970        test_clock
1971            .set_time_alert_ns("cancelled", start_time + 1000, None, None)
1972            .unwrap();
1973        test_clock
1974            .set_time_alert_ns("active", start_time + 2000, None, None)
1975            .unwrap();
1976
1977        test_clock.cancel_timer("cancelled");
1978        assert_eq!(test_clock.timer_count(), 1);
1979        assert_eq!(test_clock.timer_queue.len(), 2);
1980
1981        let events = test_clock.advance_time(start_time + 1000, true);
1982        assert!(events.is_empty());
1983        assert_eq!(test_clock.timer_names(), vec!["active"]);
1984
1985        let events = test_clock.advance_time(start_time + 2000, true);
1986        assert_eq!(events.len(), 1);
1987        assert_eq!(events[0].name.as_str(), "active");
1988    }
1989
1990    #[rstest]
1991    fn test_timer_queue_compacts_stale_entries(mut test_clock: TestClock) {
1992        let start_time = test_clock.timestamp_ns();
1993        test_clock
1994            .set_time_alert_ns("active", start_time + 1000, None, None)
1995            .unwrap();
1996        test_clock
1997            .set_time_alert_ns("cancelled-1", start_time + 2000, None, None)
1998            .unwrap();
1999        test_clock
2000            .set_time_alert_ns("cancelled-2", start_time + 3000, None, None)
2001            .unwrap();
2002
2003        test_clock.cancel_timer("cancelled-1");
2004        assert_eq!(test_clock.timer_queue.len(), 3);
2005
2006        test_clock.cancel_timer("cancelled-2");
2007        assert_eq!(test_clock.timer_count(), 1);
2008        assert_eq!(test_clock.timer_queue.len(), 1);
2009    }
2010
2011    #[rstest]
2012    fn test_cancel_all_timers(mut test_clock: TestClock) {
2013        // Create multiple timers
2014        test_clock
2015            .set_timer_ns("timer1", 1000, None, None, None, None, None)
2016            .unwrap();
2017        test_clock
2018            .set_timer_ns("timer2", 1500, None, None, None, None, None)
2019            .unwrap();
2020        test_clock
2021            .set_timer_ns("timer3", 2000, None, None, None, None, None)
2022            .unwrap();
2023
2024        assert_eq!(test_clock.timer_count(), 3);
2025
2026        // Cancel all timers
2027        test_clock.cancel_timers();
2028
2029        assert_eq!(test_clock.timer_count(), 0);
2030
2031        // Advance time - should get no events
2032        let events = test_clock.advance_time(UnixNanos::from(5000), true);
2033        assert_eq!(events.len(), 0);
2034    }
2035
2036    #[rstest]
2037    fn test_clock_reset_clears_timers(mut test_clock: TestClock) {
2038        test_clock
2039            .set_timer_ns("reset_test", 1000, None, None, None, None, None)
2040            .unwrap();
2041
2042        assert_eq!(test_clock.timer_count(), 1);
2043
2044        // Reset the clock
2045        test_clock.reset();
2046
2047        assert_eq!(test_clock.timer_count(), 0);
2048        assert_eq!(test_clock.timestamp_ns(), UnixNanos::default()); // Time reset to zero
2049    }
2050
2051    #[rstest]
2052    fn test_cancel_default_handler_clears_default(mut test_clock: TestClock) {
2053        // Default handler is registered by the fixture
2054        test_clock.cancel_default_handler();
2055
2056        // Without a default and without an explicit callback, scheduling fails
2057        let alert_time: UnixNanos = (*test_clock.timestamp_ns() + 1000).into();
2058        let err = test_clock
2059            .set_time_alert_ns("alert", alert_time, None, None)
2060            .unwrap_err();
2061        assert!(
2062            err.to_string().contains("No callbacks provided"),
2063            "unexpected error: {err}"
2064        );
2065    }
2066
2067    #[rstest]
2068    fn test_cancel_default_handler_is_idempotent_on_empty_registry() {
2069        // Fresh clock with no handler registered: cancel must not panic
2070        let mut clock = TestClock::new();
2071        clock.cancel_default_handler();
2072        clock.cancel_default_handler();
2073    }
2074
2075    #[rstest]
2076    fn test_cancel_callbacks_clears_named(mut test_clock: TestClock) {
2077        let alert_time: UnixNanos = (*test_clock.timestamp_ns() + 1000).into();
2078        let callback = TimeEventCallback::from(TestCallback::default());
2079        test_clock
2080            .set_time_alert_ns("named_alert", alert_time, Some(callback), None)
2081            .unwrap();
2082        test_clock.cancel_timer("named_alert");
2083
2084        // Cancel both default and named callbacks; rescheduling without a callback fails
2085        test_clock.cancel_default_handler();
2086        test_clock.cancel_callbacks();
2087
2088        let err = test_clock
2089            .set_time_alert_ns("named_alert", alert_time, None, None)
2090            .unwrap_err();
2091        assert!(
2092            err.to_string().contains("No callbacks provided"),
2093            "unexpected error: {err}"
2094        );
2095    }
2096
2097    #[rstest]
2098    fn test_failed_set_time_alert_ns_preserves_existing_timer() {
2099        // Fresh clock with no default handler
2100        let mut clock = TestClock::new();
2101        let alert_time: UnixNanos = (*clock.timestamp_ns() + 1000).into();
2102        let callback = TimeEventCallback::from(TestCallback::default());
2103        clock
2104            .set_time_alert_ns("alert", alert_time, Some(callback), None)
2105            .unwrap();
2106        assert_eq!(clock.next_time_ns("alert"), Some(alert_time));
2107
2108        // Callbacks released (e.g. partial component teardown) while the alert still lives
2109        clock.cancel_callbacks();
2110
2111        // Rescheduling without a callback fails the predicate check; the error
2112        // return must not have destroyed the previously scheduled alert
2113        let err = clock
2114            .set_time_alert_ns("alert", (*alert_time + 1000).into(), None, None)
2115            .unwrap_err();
2116        assert!(
2117            err.to_string().contains("No callbacks provided"),
2118            "unexpected error: {err}"
2119        );
2120        assert_eq!(clock.timer_count(), 1);
2121        assert_eq!(clock.next_time_ns("alert"), Some(alert_time));
2122    }
2123
2124    #[rstest]
2125    fn test_cancel_default_handler_preserves_named_callbacks(mut test_clock: TestClock) {
2126        let alert_time: UnixNanos = (*test_clock.timestamp_ns() + 1000).into();
2127        let callback = TimeEventCallback::from(TestCallback::default());
2128        test_clock
2129            .set_time_alert_ns("alert", alert_time, Some(callback), None)
2130            .unwrap();
2131        test_clock.cancel_timer("alert");
2132
2133        test_clock.cancel_default_handler();
2134
2135        // Named callback survives: rescheduling under the same name without a callback works
2136        test_clock
2137            .set_time_alert_ns("alert", alert_time, None, None)
2138            .unwrap();
2139    }
2140
2141    #[rstest]
2142    fn test_cancel_callbacks_preserves_default_handler(mut test_clock: TestClock) {
2143        // Default handler from fixture remains available
2144        test_clock.cancel_callbacks();
2145
2146        let alert_time: UnixNanos = (*test_clock.timestamp_ns() + 1000).into();
2147        test_clock
2148            .set_time_alert_ns("alert", alert_time, None, None)
2149            .unwrap();
2150    }
2151
2152    #[rstest]
2153    fn test_set_time_alert_default_impl(mut test_clock: TestClock) {
2154        let current_time = test_clock.utc_now();
2155        let alert_time = current_time + jiff::SignedDuration::from_secs(1);
2156
2157        // Test the default implementation that delegates to set_time_alert_ns
2158        test_clock
2159            .set_time_alert("alert_test", alert_time, None, None)
2160            .unwrap();
2161
2162        assert_eq!(test_clock.timer_count(), 1);
2163        assert_eq!(test_clock.timer_names(), vec!["alert_test"]);
2164
2165        // Verify the timer is set for the correct time
2166        let expected_ns = UnixNanos::from(alert_time);
2167        let next_time = test_clock.next_time_ns("alert_test").unwrap();
2168
2169        // Should be very close (within a few nanoseconds due to conversion)
2170        let diff = if next_time >= expected_ns {
2171            next_time.as_u64() - expected_ns.as_u64()
2172        } else {
2173            expected_ns.as_u64() - next_time.as_u64()
2174        };
2175        assert!(
2176            diff < 1000,
2177            "Timer should be set within 1 microsecond of expected time"
2178        );
2179    }
2180
2181    #[rstest]
2182    fn test_set_timer_default_impl(mut test_clock: TestClock) {
2183        let current_time = test_clock.utc_now();
2184        let start_time = current_time + jiff::SignedDuration::from_secs(1);
2185        let interval = Duration::from_millis(500);
2186
2187        // Test the default implementation that delegates to set_timer_ns
2188        test_clock
2189            .set_timer(
2190                "timer_test",
2191                interval,
2192                Some(start_time),
2193                None,
2194                None,
2195                None,
2196                None,
2197            )
2198            .unwrap();
2199
2200        assert_eq!(test_clock.timer_count(), 1);
2201        assert_eq!(test_clock.timer_names(), vec!["timer_test"]);
2202
2203        // Advance time and verify timer fires at correct intervals
2204        let start_ns = UnixNanos::from(start_time);
2205        let interval_ns = interval.as_nanos() as u64;
2206
2207        let events = test_clock.advance_time(start_ns + interval_ns * 3, true);
2208        assert_eq!(events.len(), 3); // Should fire 3 times
2209
2210        // Verify timing
2211        assert_eq!(*events[0].ts_event, *start_ns + interval_ns);
2212        assert_eq!(*events[1].ts_event, *start_ns + interval_ns * 2);
2213        assert_eq!(*events[2].ts_event, *start_ns + interval_ns * 3);
2214    }
2215
2216    #[rstest]
2217    fn test_set_timer_with_stop_time_default_impl(mut test_clock: TestClock) {
2218        let current_time = test_clock.utc_now();
2219        let start_time = current_time + jiff::SignedDuration::from_secs(1);
2220        let stop_time = current_time + jiff::SignedDuration::from_secs(3);
2221        let interval = Duration::from_secs(1);
2222
2223        // Test with stop time
2224        test_clock
2225            .set_timer(
2226                "timer_with_stop",
2227                interval,
2228                Some(start_time),
2229                Some(stop_time),
2230                None,
2231                None,
2232                None,
2233            )
2234            .unwrap();
2235
2236        assert_eq!(test_clock.timer_count(), 1);
2237
2238        // Advance beyond stop time
2239        let stop_ns = UnixNanos::from(stop_time);
2240        let events = test_clock.advance_time(stop_ns + 1000, true);
2241
2242        // Should fire twice: at start_time + 1s and start_time + 2s, but not at start_time + 3s since that would be at stop_time
2243        assert_eq!(events.len(), 2);
2244
2245        let start_ns = UnixNanos::from(start_time);
2246        let interval_ns = interval.as_nanos() as u64;
2247        assert_eq!(*events[0].ts_event, *start_ns + interval_ns);
2248        assert_eq!(*events[1].ts_event, *start_ns + interval_ns * 2);
2249    }
2250
2251    #[rstest]
2252    fn test_set_timer_fire_immediately_default_impl(mut test_clock: TestClock) {
2253        let current_time = test_clock.utc_now();
2254        let start_time = current_time + jiff::SignedDuration::from_secs(1);
2255        let interval = Duration::from_millis(500);
2256
2257        // Test with fire_immediately=true
2258        test_clock
2259            .set_timer(
2260                "immediate_timer",
2261                interval,
2262                Some(start_time),
2263                None,
2264                None,
2265                None,
2266                Some(true),
2267            )
2268            .unwrap();
2269
2270        let start_ns = UnixNanos::from(start_time);
2271        let interval_ns = interval.as_nanos() as u64;
2272
2273        // Advance to start time + 1 interval
2274        let events = test_clock.advance_time(start_ns + interval_ns, true);
2275
2276        // Should fire immediately at start_time, then again at start_time + interval
2277        assert_eq!(events.len(), 2);
2278        assert_eq!(*events[0].ts_event, *start_ns); // Immediate fire
2279        assert_eq!(*events[1].ts_event, *start_ns + interval_ns); // Regular interval
2280    }
2281
2282    #[rstest]
2283    fn test_set_time_alert_when_alert_time_equals_current_time(mut test_clock: TestClock) {
2284        let current_time = test_clock.timestamp_ns();
2285
2286        // Set time alert for exactly the current time
2287        test_clock
2288            .set_time_alert_ns("alert_at_current_time", current_time, None, None)
2289            .unwrap();
2290
2291        assert_eq!(test_clock.timer_count(), 1);
2292
2293        // Advance time by exactly 0 (to current time) - should fire immediately
2294        let events = test_clock.advance_time(current_time, true);
2295
2296        // Should fire immediately since alert_time_ns == ts_now
2297        assert_eq!(events.len(), 1);
2298        assert_eq!(events[0].name.as_str(), "alert_at_current_time");
2299        assert_eq!(*events[0].ts_event, *current_time);
2300    }
2301
2302    #[rstest]
2303    fn test_cancel_and_reschedule_same_name(mut test_clock: TestClock) {
2304        let start = test_clock.timestamp_ns();
2305
2306        test_clock
2307            .set_time_alert_ns("timer", UnixNanos::from(*start + 1000), None, None)
2308            .unwrap();
2309        assert_eq!(test_clock.timer_count(), 1);
2310
2311        test_clock.cancel_timer("timer");
2312        assert_eq!(test_clock.timer_count(), 0);
2313
2314        test_clock
2315            .set_time_alert_ns("timer", UnixNanos::from(*start + 2000), None, None)
2316            .unwrap();
2317        assert_eq!(test_clock.timer_count(), 1);
2318
2319        let events = test_clock.advance_time(UnixNanos::from(*start + 1500), true);
2320        assert!(events.is_empty());
2321
2322        let events = test_clock.advance_time(UnixNanos::from(*start + 2000), true);
2323        assert_eq!(events.len(), 1);
2324        assert_eq!(*events[0].ts_event, *start + 2000);
2325    }
2326
2327    #[rstest]
2328    fn test_multiple_timers_same_timestamp_all_fire(mut test_clock: TestClock) {
2329        let fire_time = UnixNanos::from(*test_clock.timestamp_ns() + 1000);
2330
2331        for i in 0..5 {
2332            test_clock
2333                .set_time_alert_ns(&format!("timer_{i}"), fire_time, None, None)
2334                .unwrap();
2335        }
2336        assert_eq!(test_clock.timer_count(), 5);
2337
2338        let events = test_clock.advance_time(fire_time, true);
2339        assert_eq!(events.len(), 5);
2340        for event in &events {
2341            assert_eq!(*event.ts_event, *fire_time);
2342        }
2343    }
2344
2345    #[rstest]
2346    fn test_events_ordered_by_timestamp_after_advance() {
2347        let mut clock = TestClock::new();
2348        clock.register_default_handler(TestCallback::default().into());
2349        let start = clock.timestamp_ns();
2350
2351        clock
2352            .set_time_alert_ns("third", UnixNanos::from(*start + 300), None, None)
2353            .unwrap();
2354        clock
2355            .set_time_alert_ns("first", UnixNanos::from(*start + 100), None, None)
2356            .unwrap();
2357        clock
2358            .set_time_alert_ns("second", UnixNanos::from(*start + 200), None, None)
2359            .unwrap();
2360
2361        let events = clock.advance_time(UnixNanos::from(*start + 400), true);
2362        assert_eq!(events.len(), 3);
2363        assert_eq!(events[0].name.as_str(), "first");
2364        assert_eq!(events[1].name.as_str(), "second");
2365        assert_eq!(events[2].name.as_str(), "third");
2366    }
2367
2368    #[rstest]
2369    fn test_large_interval_does_not_overflow(mut test_clock: TestClock) {
2370        let start = test_clock.timestamp_ns();
2371        let large_interval: u64 = 1_000_000_000 * 60 * 60 * 24 * 365; // ~1 year in ns
2372
2373        test_clock
2374            .set_timer_ns(
2375                "large_interval",
2376                large_interval,
2377                Some(start),
2378                None,
2379                None,
2380                None,
2381                None,
2382            )
2383            .unwrap();
2384
2385        let events = test_clock.advance_time(UnixNanos::from(*start + large_interval), true);
2386        assert_eq!(events.len(), 1);
2387        assert_eq!(*events[0].ts_event, *start + large_interval);
2388    }
2389
2390    #[rstest]
2391    fn test_near_zero_interval_fires_correctly(mut test_clock: TestClock) {
2392        let start = test_clock.timestamp_ns();
2393
2394        test_clock
2395            .set_timer_ns("tiny", 1, Some(start), None, None, None, None)
2396            .unwrap();
2397
2398        let events = test_clock.advance_time(UnixNanos::from(*start + 10), true);
2399        assert_eq!(events.len(), 10);
2400
2401        for i in 1..events.len() {
2402            assert!(events[i].ts_event >= events[i - 1].ts_event);
2403        }
2404    }
2405
2406    #[rstest]
2407    fn test_repeated_advance_to_same_time_no_double_fire(mut test_clock: TestClock) {
2408        let fire_time = UnixNanos::from(*test_clock.timestamp_ns() + 1000);
2409
2410        test_clock
2411            .set_time_alert_ns("once", fire_time, None, None)
2412            .unwrap();
2413
2414        let events1 = test_clock.advance_time(fire_time, true);
2415        assert_eq!(events1.len(), 1);
2416
2417        let events2 = test_clock.advance_time(fire_time, true);
2418        assert!(events2.is_empty());
2419    }
2420
2421    #[rstest]
2422    fn test_advance_with_no_timers(mut test_clock: TestClock) {
2423        let start = test_clock.timestamp_ns();
2424
2425        let events = test_clock.advance_time(UnixNanos::from(*start + 1000), true);
2426        assert!(events.is_empty());
2427        assert_eq!(*test_clock.timestamp_ns(), *start + 1000);
2428    }
2429
2430    #[rstest]
2431    fn test_set_time_alert_rejects_unconvertible_datetime(mut test_clock: TestClock) {
2432        let pre_epoch = Timestamp::from_nanosecond(-1).unwrap();
2433
2434        let err = test_clock
2435            .set_time_alert("pre_epoch_alert", pre_epoch, None, None)
2436            .unwrap_err();
2437        assert!(
2438            err.to_string().contains("cannot be negative"),
2439            "unexpected error: {err}"
2440        );
2441
2442        let err = test_clock
2443            .set_time_alert("out_of_range_alert", Timestamp::MAX, None, None)
2444            .unwrap_err();
2445        assert!(
2446            err.to_string().contains("out of range"),
2447            "unexpected error: {err}"
2448        );
2449
2450        assert_eq!(test_clock.timer_count(), 0);
2451    }
2452
2453    #[rstest]
2454    fn test_set_timer_rejects_unconvertible_datetime(mut test_clock: TestClock) {
2455        let pre_epoch = Timestamp::from_nanosecond(-1).unwrap();
2456        let valid_start = test_clock.utc_now() + jiff::SignedDuration::from_secs(1);
2457
2458        let err = test_clock
2459            .set_timer(
2460                "pre_epoch_start",
2461                Duration::from_secs(1),
2462                Some(pre_epoch),
2463                None,
2464                None,
2465                None,
2466                None,
2467            )
2468            .unwrap_err();
2469        assert!(
2470            err.to_string().contains("cannot be negative"),
2471            "unexpected error: {err}"
2472        );
2473
2474        let err = test_clock
2475            .set_timer(
2476                "pre_epoch_stop",
2477                Duration::from_secs(1),
2478                Some(valid_start),
2479                Some(pre_epoch),
2480                None,
2481                None,
2482                None,
2483            )
2484            .unwrap_err();
2485        assert!(
2486            err.to_string().contains("cannot be negative"),
2487            "unexpected error: {err}"
2488        );
2489
2490        assert_eq!(test_clock.timer_count(), 0);
2491    }
2492
2493    #[rstest]
2494    fn test_set_timer_rejects_interval_exceeding_u64_nanos(mut test_clock: TestClock) {
2495        let interval = Duration::from_secs(u64::MAX / NANOSECONDS_IN_SECOND + 1);
2496
2497        let err = test_clock
2498            .set_timer("overflow", interval, None, None, None, None, None)
2499            .unwrap_err();
2500
2501        assert_eq!(err.to_string(), "Interval exceeds u64 nanoseconds");
2502        assert_eq!(test_clock.timer_count(), 0);
2503    }
2504
2505    #[rstest]
2506    fn test_set_timer_ns_rejects_unrepresentable_first_event_without_replacing_timer(
2507        mut test_clock: TestClock,
2508    ) {
2509        test_clock.set_time(UnixNanos::from(1));
2510        test_clock
2511            .set_timer_ns("overflow", 1, None, None, None, None, None)
2512            .unwrap();
2513
2514        let err = test_clock
2515            .set_timer_ns("overflow", u64::MAX, None, None, None, None, None)
2516            .unwrap_err();
2517
2518        assert_eq!(
2519            err.to_string(),
2520            "Timer 'overflow' first event time exceeds UnixNanos range"
2521        );
2522        assert_eq!(test_clock.timer_count(), 1);
2523        assert_eq!(
2524            test_clock.next_time_ns("overflow"),
2525            Some(UnixNanos::from(2))
2526        );
2527    }
2528
2529    #[rstest]
2530    fn test_clock_api_handlers_reject_invalid_time_inputs() {
2531        let calls = Arc::new(Mutex::new(Vec::new()));
2532        let calls_for_alert = Arc::clone(&calls);
2533        let calls_for_timer = Arc::clone(&calls);
2534
2535        let clock = ClockApi::from_handlers(
2536            || UnixNanos::from(1_700_000_000_000_000_000),
2537            move |name, _, _, _| {
2538                calls_for_alert.lock().push(name.to_string());
2539                Ok(())
2540            },
2541            move |name, _, _, _, _, _, _| {
2542                calls_for_timer.lock().push(name.to_string());
2543                Ok(())
2544            },
2545            Vec::new,
2546            || 0,
2547            |_| false,
2548            |_| None,
2549            |_| {},
2550            || {},
2551        );
2552
2553        let pre_epoch = Timestamp::from_nanosecond(-1).unwrap();
2554        clock
2555            .set_time_alert("alert", pre_epoch, None, None)
2556            .unwrap_err();
2557        clock
2558            .set_timer(
2559                "timer",
2560                Duration::from_secs(1),
2561                Some(pre_epoch),
2562                None,
2563                None,
2564                None,
2565                None,
2566            )
2567            .unwrap_err();
2568        let interval = Duration::from_secs(u64::MAX / NANOSECONDS_IN_SECOND + 1);
2569        let err = clock
2570            .set_timer("overflow", interval, None, None, None, None, None)
2571            .unwrap_err();
2572
2573        assert_eq!(err.to_string(), "Interval exceeds u64 nanoseconds");
2574        assert!(calls.lock().is_empty());
2575    }
2576
2577    #[rstest]
2578    fn test_clock_api_new_uses_native_backing(test_clock: TestClock) {
2579        let clock = RefCell::new(test_clock);
2580        let api = ClockApi::new(&clock);
2581
2582        api.set_timer_ns(
2583            "native-timer",
2584            1_000,
2585            None,
2586            None,
2587            None,
2588            Some(true),
2589            Some(false),
2590        )
2591        .unwrap();
2592
2593        assert_eq!(api.timer_count(), 1);
2594        assert_eq!(api.timer_names(), vec!["native-timer".to_string()]);
2595        assert_eq!(
2596            api.next_time_ns("native-timer"),
2597            Some(UnixNanos::from(1_000))
2598        );
2599    }
2600
2601    #[rstest]
2602    fn test_clock_api_handlers_back_full_surface() {
2603        let alerts = Arc::new(Mutex::new(Vec::new()));
2604        let timers = Arc::new(Mutex::new(Vec::new()));
2605        let cancellations = Arc::new(Mutex::new(Vec::new()));
2606        let cancel_all = Arc::new(Mutex::new(false));
2607
2608        let alerts_for_handler = Arc::clone(&alerts);
2609        let timers_for_handler = Arc::clone(&timers);
2610        let cancellations_for_handler = Arc::clone(&cancellations);
2611        let cancel_all_for_handler = Arc::clone(&cancel_all);
2612
2613        let clock = ClockApi::from_handlers(
2614            || UnixNanos::from(1_700_000_000_123_456_789),
2615            move |name, alert_time_ns, _callback, allow_past| {
2616                alerts_for_handler
2617                    .lock()
2618                    .push((name.to_string(), alert_time_ns, allow_past));
2619                Ok(())
2620            },
2621            move |name,
2622                  interval_ns,
2623                  start_time_ns,
2624                  stop_time_ns,
2625                  _callback,
2626                  allow_past,
2627                  fire_immediately| {
2628                timers_for_handler.lock().push((
2629                    name.to_string(),
2630                    interval_ns,
2631                    start_time_ns,
2632                    stop_time_ns,
2633                    allow_past,
2634                    fire_immediately,
2635                ));
2636                Ok(())
2637            },
2638            || vec!["alpha".to_string(), "beta".to_string()],
2639            || 2,
2640            |name| name == "alpha",
2641            |name| (name == "alpha").then(|| UnixNanos::from(1_700_000_000_999_000_000)),
2642            move |name| {
2643                cancellations_for_handler.lock().push(name.to_string());
2644            },
2645            move || {
2646                *cancel_all_for_handler.lock() = true;
2647            },
2648        );
2649
2650        let alert_time = Timestamp::from_nanosecond(1_700_000_000_333_000_000).unwrap();
2651        let start_time = Timestamp::from_nanosecond(1_700_000_000_444_000_000).unwrap();
2652        let stop_time = Timestamp::from_nanosecond(1_700_000_001_444_000_000).unwrap();
2653        clock
2654            .set_time_alert("alert", alert_time, None, Some(false))
2655            .unwrap();
2656        clock
2657            .set_time_alert_ns(
2658                "alert-ns",
2659                UnixNanos::from(1_700_000_000_555_000_000),
2660                None,
2661                Some(true),
2662            )
2663            .unwrap();
2664        clock
2665            .set_timer(
2666                "timer",
2667                Duration::from_millis(250),
2668                Some(start_time),
2669                Some(stop_time),
2670                None,
2671                Some(true),
2672                Some(false),
2673            )
2674            .unwrap();
2675        clock
2676            .set_timer_ns(
2677                "timer-ns",
2678                500_000_000,
2679                Some(UnixNanos::from(1_700_000_000_666_000_000)),
2680                Some(UnixNanos::from(1_700_000_001_666_000_000)),
2681                None,
2682                Some(false),
2683                Some(true),
2684            )
2685            .unwrap();
2686        clock.cancel_timer("alpha");
2687        clock.cancel_timers();
2688
2689        assert_eq!(
2690            clock.timestamp_ns(),
2691            UnixNanos::from(1_700_000_000_123_456_789)
2692        );
2693        assert_eq!(clock.timestamp_us(), 1_700_000_000_123_456);
2694        assert_eq!(clock.timestamp_ms(), 1_700_000_000_123);
2695        assert_eq!(clock.timestamp(), 1_700_000_000.123_456_7);
2696        assert_eq!(
2697            clock.utc_now(),
2698            Timestamp::from_nanosecond(1_700_000_000_123_456_789).unwrap()
2699        );
2700        assert_eq!(clock.timer_names(), vec!["alpha", "beta"]);
2701        assert_eq!(clock.timer_count(), 2);
2702        assert!(clock.timer_exists("alpha"));
2703        assert!(!clock.timer_exists("gamma"));
2704        assert_eq!(
2705            clock.next_time_ns("alpha"),
2706            Some(UnixNanos::from(1_700_000_000_999_000_000))
2707        );
2708        assert_eq!(
2709            alerts.lock().as_slice(),
2710            &[
2711                (
2712                    "alert".to_string(),
2713                    UnixNanos::from(1_700_000_000_333_000_000),
2714                    Some(false)
2715                ),
2716                (
2717                    "alert-ns".to_string(),
2718                    UnixNanos::from(1_700_000_000_555_000_000),
2719                    Some(true)
2720                )
2721            ]
2722        );
2723        assert_eq!(
2724            timers.lock().as_slice(),
2725            &[
2726                (
2727                    "timer".to_string(),
2728                    250_000_000,
2729                    Some(UnixNanos::from(1_700_000_000_444_000_000)),
2730                    Some(UnixNanos::from(1_700_000_001_444_000_000)),
2731                    Some(true),
2732                    Some(false)
2733                ),
2734                (
2735                    "timer-ns".to_string(),
2736                    500_000_000,
2737                    Some(UnixNanos::from(1_700_000_000_666_000_000)),
2738                    Some(UnixNanos::from(1_700_000_001_666_000_000)),
2739                    Some(false),
2740                    Some(true)
2741                )
2742            ]
2743        );
2744        assert_eq!(cancellations.lock().as_slice(), &["alpha".to_string()]);
2745        assert!(*cancel_all.lock());
2746    }
2747
2748    proptest! {
2749        #[rstest]
2750        fn prop_test_clock_operations_match_reference(
2751            initial_time_ns in clock_time_strategy(),
2752            operations in prop::collection::vec(clock_operation_strategy(), 1..=50),
2753        ) {
2754            check_clock_operations(initial_time_ns, operations)?;
2755        }
2756
2757        #[rstest]
2758        fn prop_test_clock_max_time_alert(initial_time_ns in clock_time_strategy()) {
2759            check_clock_max_time_alert(initial_time_ns)?;
2760        }
2761    }
2762
2763    #[derive(Clone, Debug)]
2764    enum ClockOperation {
2765        Set {
2766            name_index: usize,
2767            interval_ns: u64,
2768            stop_after_ns: Option<u64>,
2769            fire_immediately: bool,
2770        },
2771        Cancel(usize),
2772        Advance {
2773            delta_ns: u64,
2774            set_time: bool,
2775        },
2776    }
2777
2778    #[derive(Clone, Debug)]
2779    struct TimerModel {
2780        interval: u64,
2781        next: u64,
2782        stop: Option<u64>,
2783    }
2784
2785    fn clock_operation_strategy() -> impl Strategy<Value = ClockOperation> {
2786        prop_oneof![
2787            5 => (
2788                0usize..CLOCK_TIMER_NAMES.len(),
2789                1u64..=15,
2790                prop::option::of(1u64..=60),
2791                prop::bool::ANY,
2792            )
2793                .prop_map(
2794                    |(name_index, interval_ns, stop_after_ns, fire_immediately)| {
2795                        ClockOperation::Set {
2796                            name_index,
2797                            interval_ns,
2798                            stop_after_ns,
2799                            fire_immediately,
2800                        }
2801                    },
2802                ),
2803            2 => (0usize..CLOCK_TIMER_NAMES.len()).prop_map(ClockOperation::Cancel),
2804            5 => (0u64..=30, prop::bool::ANY)
2805                .prop_map(|(delta_ns, set_time)| ClockOperation::Advance { delta_ns, set_time }),
2806        ]
2807    }
2808
2809    fn clock_time_strategy() -> impl Strategy<Value = u64> {
2810        prop_oneof![
2811            6 => 0u64..=u64::MAX - CLOCK_TIME_HEADROOM,
2812            2 => 0u64..=1_000_000,
2813            1 => Just(1_700_000_000_000_000_000),
2814            1 => Just(u64::MAX - CLOCK_TIME_HEADROOM),
2815        ]
2816    }
2817
2818    fn check_clock_operations(
2819        initial_time_ns: u64,
2820        operations: Vec<ClockOperation>,
2821    ) -> TestCaseResult {
2822        let mut clock = TestClock::new();
2823        clock.register_default_handler(TestCallback::default().into());
2824        clock.set_time(UnixNanos::from(initial_time_ns));
2825
2826        let mut time_ns = initial_time_ns;
2827        let mut timers = BTreeMap::new();
2828
2829        for operation in operations {
2830            match operation {
2831                ClockOperation::Set {
2832                    name_index,
2833                    interval_ns,
2834                    stop_after_ns,
2835                    fire_immediately,
2836                } => {
2837                    let name = clock_timer_name(name_index);
2838                    let stop_time_ns = stop_after_ns.map(|offset| time_ns + offset);
2839                    clock
2840                        .set_timer_ns(
2841                            name.as_str(),
2842                            interval_ns,
2843                            Some(UnixNanos::from(time_ns)),
2844                            stop_time_ns.map(UnixNanos::from),
2845                            None,
2846                            None,
2847                            Some(fire_immediately),
2848                        )
2849                        .expect("generated timer configuration should be valid");
2850                    timers.insert(
2851                        name,
2852                        TimerModel {
2853                            interval: interval_ns,
2854                            next: if fire_immediately {
2855                                time_ns
2856                            } else {
2857                                time_ns + interval_ns
2858                            },
2859                            stop: stop_time_ns,
2860                        },
2861                    );
2862                }
2863                ClockOperation::Cancel(name_index) => {
2864                    let name = clock_timer_name(name_index);
2865                    clock.cancel_timer(name.as_str());
2866                    timers.remove(&name);
2867                }
2868                ClockOperation::Advance { delta_ns, set_time } => {
2869                    let to_time_ns = time_ns + delta_ns;
2870                    let actual: Vec<(u64, Ustr, u64)> = clock
2871                        .advance_time(UnixNanos::from(to_time_ns), set_time)
2872                        .into_iter()
2873                        .map(|event| (event.ts_event.as_u64(), event.name, event.ts_init.as_u64()))
2874                        .collect();
2875                    let expected = advance_clock_timers(&mut timers, to_time_ns);
2876
2877                    prop_assert_eq!(actual, expected);
2878
2879                    if set_time {
2880                        time_ns = to_time_ns;
2881                    }
2882                }
2883            }
2884
2885            assert_clock_state(&clock, &timers, time_ns)?;
2886        }
2887
2888        Ok(())
2889    }
2890
2891    fn check_clock_max_time_alert(initial_time_ns: u64) -> TestCaseResult {
2892        let mut clock = TestClock::new();
2893        clock.register_default_handler(TestCallback::default().into());
2894        clock.set_time(UnixNanos::from(initial_time_ns));
2895        let name = Ustr::from("terminal-alert");
2896        clock
2897            .set_time_alert_ns(name.as_str(), UnixNanos::max(), None, None)
2898            .expect("maximum timestamp should be a valid time alert");
2899
2900        let events: Vec<(u64, Ustr, u64)> = clock
2901            .advance_time(UnixNanos::max(), true)
2902            .into_iter()
2903            .map(|event| (event.ts_event.as_u64(), event.name, event.ts_init.as_u64()))
2904            .collect();
2905
2906        prop_assert_eq!(events, vec![(u64::MAX, name, u64::MAX)]);
2907        prop_assert_eq!(clock.timestamp_ns(), UnixNanos::max());
2908        prop_assert_eq!(clock.timer_count(), 0);
2909        prop_assert!(clock.timer_names().is_empty());
2910        prop_assert!(!clock.timer_exists(&name));
2911        prop_assert_eq!(clock.next_time_ns(name.as_str()), None);
2912
2913        Ok(())
2914    }
2915
2916    fn advance_clock_timers(
2917        timers: &mut BTreeMap<Ustr, TimerModel>,
2918        to_time_ns: u64,
2919    ) -> Vec<(u64, Ustr, u64)> {
2920        let mut events = Vec::new();
2921
2922        timers.retain(|name, timer| {
2923            while timer.next <= to_time_ns {
2924                if timer
2925                    .stop
2926                    .is_some_and(|stop_time_ns| timer.next > stop_time_ns)
2927                {
2928                    return false;
2929                }
2930
2931                let event_time_ns = timer.next;
2932                events.push((event_time_ns, *name, event_time_ns));
2933                let Some(following_time_ns) = event_time_ns.checked_add(timer.interval) else {
2934                    return false;
2935                };
2936                timer.next = following_time_ns;
2937                if timer.stop == Some(event_time_ns) {
2938                    return false;
2939                }
2940            }
2941
2942            true
2943        });
2944
2945        events.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
2946        events
2947    }
2948
2949    fn assert_clock_state(
2950        clock: &TestClock,
2951        timers: &BTreeMap<Ustr, TimerModel>,
2952        time_ns: u64,
2953    ) -> TestCaseResult {
2954        let expected_names: Vec<&str> = timers.keys().map(Ustr::as_str).collect();
2955
2956        prop_assert_eq!(clock.timestamp_ns(), UnixNanos::from(time_ns));
2957        prop_assert_eq!(clock.timer_count(), timers.len());
2958        prop_assert_eq!(clock.timer_names(), expected_names);
2959
2960        for name in CLOCK_TIMER_NAMES.map(Ustr::from) {
2961            let expected = timers.get(&name);
2962            prop_assert_eq!(clock.timer_exists(&name), expected.is_some());
2963            prop_assert_eq!(
2964                clock
2965                    .next_time_ns(name.as_str())
2966                    .map(|next_time_ns| next_time_ns.as_u64()),
2967                expected.map(|timer| timer.next),
2968            );
2969        }
2970
2971        Ok(())
2972    }
2973
2974    const CLOCK_TIMER_NAMES: [&str; 4] = ["timer-0", "timer-1", "timer-2", "timer-3"];
2975    const CLOCK_TIME_HEADROOM: u64 = 100_000;
2976
2977    fn clock_timer_name(index: usize) -> Ustr {
2978        Ustr::from(CLOCK_TIMER_NAMES[index])
2979    }
2980}