Skip to main content

telltale_runtime/testing/
clock.rs

1//! Clock and RNG traits for deterministic simulation.
2//!
3//! This module re-exports the core effect traits from `telltale_types::effects`
4//! and provides async extensions for use in choreography testing.
5//!
6//! # Re-exports
7//!
8//! The following are re-exported from `telltale_types::effects`:
9//! - `Clock` - Monotonic time trait
10//! - `WallClock` - Wall-clock time trait
11//! - `MockClock` - Deterministic mock clock
12//! - `Rng` - Random number generation trait
13//! - `SeededRng` - Xorshift64-based seeded RNG
14//!
15//! # Async Extensions
16//!
17//! This module adds `AsyncClock` which extends `Clock` with async sleep.
18
19use std::time::Duration;
20
21// Re-export core traits and implementations from telltale-types
22pub use telltale_types::effects::{Clock, MockClock, Rng, SeededRng, WallClock};
23
24/// Extension trait adding async sleep to `Clock`.
25///
26/// This is separate from the base `Clock` trait because async methods
27/// cannot be in traits without additional machinery, and we want to keep
28/// `telltale-types` dependency-free.
29pub trait AsyncClock: Clock {
30    /// Sleep for a duration asynchronously.
31    ///
32    /// In simulation mode this may advance simulated time immediately.
33    fn sleep(&self, duration: Duration) -> impl std::future::Future<Output = ()> + Send;
34}
35
36/// Implement `AsyncClock` for `MockClock`.
37///
38/// Sleep immediately advances simulated time without blocking.
39impl AsyncClock for MockClock {
40    async fn sleep(&self, duration: Duration) {
41        self.advance(duration);
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_seeded_rng_reproducible() {
51        let mut rng1 = SeededRng::new(12345);
52        let mut rng2 = SeededRng::new(12345);
53
54        for _ in 0..100 {
55            assert_eq!(rng1.next_u64(), rng2.next_u64());
56        }
57    }
58
59    #[test]
60    fn test_seeded_rng_different_seeds() {
61        let mut rng1 = SeededRng::new(12345);
62        let mut rng2 = SeededRng::new(54321);
63        assert_ne!(rng1.next_u64(), rng2.next_u64());
64    }
65
66    #[test]
67    fn test_mock_clock_advance() {
68        let clock = MockClock::new();
69        let start = clock.now();
70
71        clock.advance(Duration::from_secs(1));
72        assert!(clock.elapsed(start) >= Duration::from_secs(1));
73
74        clock.advance(Duration::from_millis(500));
75        assert!(clock.elapsed(start) >= Duration::from_millis(1500));
76    }
77
78    #[test]
79    fn test_mock_wall_clock() {
80        let clock = MockClock::new();
81        assert_eq!(clock.now_unix_ns(), 0);
82        clock.advance(Duration::from_millis(2));
83        assert_eq!(clock.now_unix_ns(), 2_000_000);
84    }
85
86    #[test]
87    fn test_rng_choose() {
88        let mut rng = SeededRng::new(42);
89        let items = vec![1, 2, 3, 4, 5];
90        let chosen = rng.choose(&items);
91        assert!(chosen.is_some());
92        assert!(items.contains(chosen.expect("must choose an element")));
93    }
94
95    #[test]
96    fn test_rng_duration_between() {
97        let mut rng = SeededRng::new(42);
98        let min = Duration::from_millis(100);
99        let max = Duration::from_millis(200);
100
101        for _ in 0..100 {
102            let d = rng.duration_between(min, max);
103            assert!(d >= min);
104            assert!(d < max);
105        }
106    }
107}