clnrm_core/template/
determinism.rs

1//! Determinism support for reproducible tests
2//!
3//! Provides configuration for deterministic test execution:
4//! - Fixed random seeds
5//! - Frozen timestamps
6//! - Reproducible test generation
7
8use serde::{Deserialize, Serialize};
9
10/// Configuration for deterministic test execution
11///
12/// Enables reproducible tests by controlling randomness and time:
13/// - `seed` - Fixed random seed for matrix expansion
14/// - `freeze_clock` - Fixed timestamp for `now_rfc3339()` function
15#[derive(Debug, Clone, Deserialize, Serialize, Default)]
16pub struct DeterminismConfig {
17    /// Random seed for deterministic matrix expansion
18    pub seed: Option<u64>,
19    /// Frozen timestamp in RFC3339 format
20    pub freeze_clock: Option<String>,
21}
22
23impl DeterminismConfig {
24    /// Create new determinism config
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Set random seed for matrix expansion
30    pub fn with_seed(mut self, seed: u64) -> Self {
31        self.seed = Some(seed);
32        self
33    }
34
35    /// Set frozen clock timestamp
36    pub fn with_freeze_clock(mut self, timestamp: String) -> Self {
37        self.freeze_clock = Some(timestamp);
38        self
39    }
40
41    /// Check if any determinism features are enabled
42    pub fn is_deterministic(&self) -> bool {
43        self.seed.is_some() || self.freeze_clock.is_some()
44    }
45
46    /// Check if random seed is set
47    pub fn has_seed(&self) -> bool {
48        self.seed.is_some()
49    }
50
51    /// Check if clock is frozen
52    pub fn has_frozen_clock(&self) -> bool {
53        self.freeze_clock.is_some()
54    }
55
56    /// Get the seed value if set
57    pub fn get_seed(&self) -> Option<u64> {
58        self.seed
59    }
60
61    /// Get the frozen clock timestamp if set
62    pub fn get_freeze_clock(&self) -> Option<&str> {
63        self.freeze_clock.as_deref()
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    #![allow(
70        clippy::unwrap_used,
71        clippy::expect_used,
72        clippy::indexing_slicing,
73        clippy::panic
74    )]
75
76    use super::*;
77
78    #[test]
79    fn test_determinism_config_default() {
80        let config = DeterminismConfig::new();
81        assert!(!config.is_deterministic());
82        assert!(!config.has_seed());
83        assert!(!config.has_frozen_clock());
84        assert_eq!(config.get_seed(), None);
85        assert_eq!(config.get_freeze_clock(), None);
86    }
87
88    #[test]
89    fn test_determinism_config_with_seed() {
90        let config = DeterminismConfig::new().with_seed(42);
91        assert!(config.is_deterministic());
92        assert!(config.has_seed());
93        assert!(!config.has_frozen_clock());
94        assert_eq!(config.get_seed(), Some(42));
95    }
96
97    #[test]
98    fn test_determinism_config_with_freeze_clock() {
99        let timestamp = "2024-01-01T00:00:00Z".to_string();
100        let config = DeterminismConfig::new().with_freeze_clock(timestamp.clone());
101        assert!(config.is_deterministic());
102        assert!(!config.has_seed());
103        assert!(config.has_frozen_clock());
104        assert_eq!(config.get_freeze_clock(), Some(timestamp.as_str()));
105    }
106
107    #[test]
108    fn test_determinism_config_with_both() {
109        let timestamp = "2024-01-01T00:00:00Z".to_string();
110        let config = DeterminismConfig::new()
111            .with_seed(42)
112            .with_freeze_clock(timestamp.clone());
113
114        assert!(config.is_deterministic());
115        assert!(config.has_seed());
116        assert!(config.has_frozen_clock());
117        assert_eq!(config.get_seed(), Some(42));
118        assert_eq!(config.get_freeze_clock(), Some(timestamp.as_str()));
119    }
120
121    #[test]
122    fn test_determinism_serialization() {
123        let config = DeterminismConfig::new()
124            .with_seed(42)
125            .with_freeze_clock("2024-01-01T00:00:00Z".to_string());
126
127        let json = serde_json::to_string(&config).unwrap();
128        let deserialized: DeterminismConfig = serde_json::from_str(&json).unwrap();
129
130        assert_eq!(deserialized.seed, config.seed);
131        assert_eq!(deserialized.freeze_clock, config.freeze_clock);
132    }
133
134    #[test]
135    fn test_determinism_deserialization_empty() {
136        let json = "{}";
137        let config: DeterminismConfig = serde_json::from_str(json).unwrap();
138
139        assert!(!config.is_deterministic());
140        assert_eq!(config.seed, None);
141        assert_eq!(config.freeze_clock, None);
142    }
143
144    #[test]
145    fn test_determinism_deserialization_with_seed() {
146        let json = r#"{"seed": 123}"#;
147        let config: DeterminismConfig = serde_json::from_str(json).unwrap();
148
149        assert!(config.is_deterministic());
150        assert_eq!(config.seed, Some(123));
151    }
152
153    #[test]
154    fn test_determinism_deserialization_with_freeze_clock() {
155        let json = r#"{"freeze_clock": "2024-12-31T23:59:59Z"}"#;
156        let config: DeterminismConfig = serde_json::from_str(json).unwrap();
157
158        assert!(config.is_deterministic());
159        assert_eq!(
160            config.freeze_clock,
161            Some("2024-12-31T23:59:59Z".to_string())
162        );
163    }
164
165    #[test]
166    fn test_chaining() {
167        let config = DeterminismConfig::default()
168            .with_seed(100)
169            .with_freeze_clock("2025-01-01T00:00:00Z".to_string());
170
171        assert_eq!(config.get_seed(), Some(100));
172        assert_eq!(config.get_freeze_clock(), Some("2025-01-01T00:00:00Z"));
173    }
174}