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}