Skip to main content

trueno/
chaos.rs

1//! Chaos Engineering Configuration
2//!
3//! This module provides chaos engineering infrastructure for testing Trueno under
4//! adverse conditions. Integrated from renacer v0.4.1 (https://github.com/paiml/renacer).
5//!
6//! # Examples
7//!
8//! ```
9//! use trueno::chaos::ChaosConfig;
10//! use std::time::Duration;
11//!
12//! // Use gentle preset for gradual stress testing
13//! let gentle = ChaosConfig::gentle();
14//!
15//! // Use aggressive preset for extreme conditions
16//! let aggressive = ChaosConfig::aggressive();
17//!
18//! // Custom configuration using builder pattern
19//! let custom = ChaosConfig::new()
20//!     .with_memory_limit(100 * 1024 * 1024)  // 100 MB
21//!     .with_cpu_limit(0.5)                    // 50% CPU
22//!     .with_timeout(Duration::from_secs(30))
23//!     .with_signal_injection(true)
24//!     .build();
25//! ```
26
27use std::time::Duration;
28
29/// Chaos engineering configuration for stress testing
30///
31/// Provides configurable limits and constraints for chaos testing scenarios.
32/// All limits are optional and disabled by default (value of 0 means no limit).
33#[derive(Debug, Clone, PartialEq)]
34pub struct ChaosConfig {
35    /// Memory limit in bytes (0 = no limit)
36    pub memory_limit: usize,
37    /// CPU usage limit as fraction 0.0-1.0 (0.0 = no limit)
38    pub cpu_limit: f64,
39    /// Maximum execution time
40    pub timeout: Duration,
41    /// Whether to inject random signals during execution
42    pub signal_injection: bool,
43}
44
45impl Default for ChaosConfig {
46    fn default() -> Self {
47        Self {
48            memory_limit: 0,
49            cpu_limit: 0.0,
50            timeout: Duration::from_secs(60),
51            signal_injection: false,
52        }
53    }
54}
55
56impl ChaosConfig {
57    /// Create a new chaos configuration with default values (no limits)
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Set memory limit in bytes
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use trueno::chaos::ChaosConfig;
68    ///
69    /// let config = ChaosConfig::new()
70    ///     .with_memory_limit(512 * 1024 * 1024); // 512 MB
71    /// assert_eq!(config.memory_limit, 512 * 1024 * 1024);
72    /// ```
73    pub fn with_memory_limit(mut self, bytes: usize) -> Self {
74        self.memory_limit = bytes;
75        self
76    }
77
78    /// Set CPU usage limit as fraction (0.0-1.0)
79    ///
80    /// Values are automatically clamped to valid range.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use trueno::chaos::ChaosConfig;
86    ///
87    /// let config = ChaosConfig::new().with_cpu_limit(0.75);
88    /// assert_eq!(config.cpu_limit, 0.75);
89    ///
90    /// // Values outside range are clamped
91    /// let clamped = ChaosConfig::new().with_cpu_limit(1.5);
92    /// assert_eq!(clamped.cpu_limit, 1.0);
93    /// ```
94    pub fn with_cpu_limit(mut self, fraction: f64) -> Self {
95        self.cpu_limit = fraction.clamp(0.0, 1.0);
96        self
97    }
98
99    /// Set maximum execution timeout
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use trueno::chaos::ChaosConfig;
105    /// use std::time::Duration;
106    ///
107    /// let config = ChaosConfig::new()
108    ///     .with_timeout(Duration::from_secs(30));
109    /// assert_eq!(config.timeout, Duration::from_secs(30));
110    /// ```
111    pub fn with_timeout(mut self, timeout: Duration) -> Self {
112        self.timeout = timeout;
113        self
114    }
115
116    /// Enable/disable random signal injection during execution
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use trueno::chaos::ChaosConfig;
122    ///
123    /// let config = ChaosConfig::new().with_signal_injection(true);
124    /// assert!(config.signal_injection);
125    /// ```
126    pub fn with_signal_injection(mut self, enabled: bool) -> Self {
127        self.signal_injection = enabled;
128        self
129    }
130
131    /// Finalize configuration (no-op, for builder pattern consistency)
132    pub fn build(self) -> Self {
133        self
134    }
135
136    /// Gentle chaos configuration preset
137    ///
138    /// - 512 MB memory limit
139    /// - 80% CPU limit
140    /// - 120 second timeout
141    /// - No signal injection
142    ///
143    /// Suitable for gradual stress testing and CI environments.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use trueno::chaos::ChaosConfig;
149    /// use std::time::Duration;
150    ///
151    /// let config = ChaosConfig::gentle();
152    /// assert_eq!(config.memory_limit, 512 * 1024 * 1024);
153    /// assert_eq!(config.cpu_limit, 0.8);
154    /// assert_eq!(config.timeout, Duration::from_secs(120));
155    /// assert!(!config.signal_injection);
156    /// ```
157    pub fn gentle() -> Self {
158        Self::new()
159            .with_memory_limit(512 * 1024 * 1024)
160            .with_cpu_limit(0.8)
161            .with_timeout(Duration::from_secs(120))
162    }
163
164    /// Aggressive chaos configuration preset
165    ///
166    /// - 64 MB memory limit
167    /// - 25% CPU limit
168    /// - 10 second timeout
169    /// - Signal injection enabled
170    ///
171    /// Suitable for extreme stress testing and finding edge cases.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use trueno::chaos::ChaosConfig;
177    /// use std::time::Duration;
178    ///
179    /// let config = ChaosConfig::aggressive();
180    /// assert_eq!(config.memory_limit, 64 * 1024 * 1024);
181    /// assert_eq!(config.cpu_limit, 0.25);
182    /// assert_eq!(config.timeout, Duration::from_secs(10));
183    /// assert!(config.signal_injection);
184    /// ```
185    pub fn aggressive() -> Self {
186        Self::new()
187            .with_memory_limit(64 * 1024 * 1024)
188            .with_cpu_limit(0.25)
189            .with_timeout(Duration::from_secs(10))
190            .with_signal_injection(true)
191    }
192}
193
194/// Result type for chaos operations
195pub type ChaosResult<T> = Result<T, ChaosError>;
196
197/// Errors that can occur during chaos testing
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub enum ChaosError {
200    /// Memory limit was exceeded during execution
201    MemoryLimitExceeded {
202        /// Configured memory limit in bytes
203        limit: usize,
204        /// Actual memory used in bytes
205        used: usize,
206    },
207    /// Execution exceeded timeout
208    Timeout {
209        /// Time elapsed before timeout
210        elapsed: Duration,
211        /// Configured timeout limit
212        limit: Duration,
213    },
214    /// Signal injection failed
215    SignalInjectionFailed {
216        /// Signal number that failed to inject
217        signal: i32,
218        /// Reason for failure
219        reason: String,
220    },
221}
222
223impl std::fmt::Display for ChaosError {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        match self {
226            ChaosError::MemoryLimitExceeded { limit, used } => {
227                write!(f, "Memory limit exceeded: {} > {} bytes", used, limit)
228            }
229            ChaosError::Timeout { elapsed, limit } => {
230                write!(f, "Timeout: {:?} > {:?}", elapsed, limit)
231            }
232            ChaosError::SignalInjectionFailed { signal, reason } => {
233                write!(f, "Signal injection failed ({}): {}", signal, reason)
234            }
235        }
236    }
237}
238
239impl std::error::Error for ChaosError {}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_default_config() {
247        let config = ChaosConfig::default();
248        assert_eq!(config.memory_limit, 0);
249        assert_eq!(config.cpu_limit, 0.0);
250        assert_eq!(config.timeout, Duration::from_secs(60));
251        assert!(!config.signal_injection);
252    }
253
254    #[test]
255    fn test_builder_pattern() {
256        let config = ChaosConfig::new()
257            .with_memory_limit(1024)
258            .with_cpu_limit(0.5)
259            .with_timeout(Duration::from_secs(30))
260            .with_signal_injection(true)
261            .build();
262
263        assert_eq!(config.memory_limit, 1024);
264        assert_eq!(config.cpu_limit, 0.5);
265        assert_eq!(config.timeout, Duration::from_secs(30));
266        assert!(config.signal_injection);
267    }
268
269    #[test]
270    fn test_cpu_limit_clamping() {
271        let too_high = ChaosConfig::new().with_cpu_limit(1.5);
272        assert_eq!(too_high.cpu_limit, 1.0);
273
274        let too_low = ChaosConfig::new().with_cpu_limit(-0.5);
275        assert_eq!(too_low.cpu_limit, 0.0);
276
277        let valid = ChaosConfig::new().with_cpu_limit(0.75);
278        assert_eq!(valid.cpu_limit, 0.75);
279    }
280
281    #[test]
282    fn test_gentle_preset() {
283        let gentle = ChaosConfig::gentle();
284        assert_eq!(gentle.memory_limit, 512 * 1024 * 1024);
285        assert_eq!(gentle.cpu_limit, 0.8);
286        assert_eq!(gentle.timeout, Duration::from_secs(120));
287        assert!(!gentle.signal_injection);
288    }
289
290    #[test]
291    fn test_aggressive_preset() {
292        let aggressive = ChaosConfig::aggressive();
293        assert_eq!(aggressive.memory_limit, 64 * 1024 * 1024);
294        assert_eq!(aggressive.cpu_limit, 0.25);
295        assert_eq!(aggressive.timeout, Duration::from_secs(10));
296        assert!(aggressive.signal_injection);
297    }
298
299    #[test]
300    fn test_chaos_error_display() {
301        let mem_err = ChaosError::MemoryLimitExceeded { limit: 1000, used: 2000 };
302        assert_eq!(format!("{}", mem_err), "Memory limit exceeded: 2000 > 1000 bytes");
303
304        let timeout_err =
305            ChaosError::Timeout { elapsed: Duration::from_secs(5), limit: Duration::from_secs(3) };
306        assert!(format!("{}", timeout_err).contains("Timeout"));
307
308        let signal_err =
309            ChaosError::SignalInjectionFailed { signal: 9, reason: "test failure".to_string() };
310        assert_eq!(format!("{}", signal_err), "Signal injection failed (9): test failure");
311    }
312}