/home/noah/src/trueno/src/chaos.rs
Line | Count | Source |
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 | | |
27 | | use 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)] |
34 | | pub 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 | | |
45 | | impl Default for ChaosConfig { |
46 | 0 | fn default() -> Self { |
47 | 0 | Self { |
48 | 0 | memory_limit: 0, |
49 | 0 | cpu_limit: 0.0, |
50 | 0 | timeout: Duration::from_secs(60), |
51 | 0 | signal_injection: false, |
52 | 0 | } |
53 | 0 | } |
54 | | } |
55 | | |
56 | | impl ChaosConfig { |
57 | | /// Create a new chaos configuration with default values (no limits) |
58 | 0 | pub fn new() -> Self { |
59 | 0 | Self::default() |
60 | 0 | } |
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 | 0 | pub fn with_memory_limit(mut self, bytes: usize) -> Self { |
74 | 0 | self.memory_limit = bytes; |
75 | 0 | self |
76 | 0 | } |
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 | 0 | pub fn with_cpu_limit(mut self, fraction: f64) -> Self { |
95 | 0 | self.cpu_limit = fraction.clamp(0.0, 1.0); |
96 | 0 | self |
97 | 0 | } |
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 | 0 | pub fn with_timeout(mut self, timeout: Duration) -> Self { |
112 | 0 | self.timeout = timeout; |
113 | 0 | self |
114 | 0 | } |
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 | 0 | pub fn with_signal_injection(mut self, enabled: bool) -> Self { |
127 | 0 | self.signal_injection = enabled; |
128 | 0 | self |
129 | 0 | } |
130 | | |
131 | | /// Finalize configuration (no-op, for builder pattern consistency) |
132 | 0 | pub fn build(self) -> Self { |
133 | 0 | self |
134 | 0 | } |
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 | 0 | pub fn gentle() -> Self { |
158 | 0 | Self::new() |
159 | 0 | .with_memory_limit(512 * 1024 * 1024) |
160 | 0 | .with_cpu_limit(0.8) |
161 | 0 | .with_timeout(Duration::from_secs(120)) |
162 | 0 | } |
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 | 0 | pub fn aggressive() -> Self { |
186 | 0 | Self::new() |
187 | 0 | .with_memory_limit(64 * 1024 * 1024) |
188 | 0 | .with_cpu_limit(0.25) |
189 | 0 | .with_timeout(Duration::from_secs(10)) |
190 | 0 | .with_signal_injection(true) |
191 | 0 | } |
192 | | } |
193 | | |
194 | | /// Result type for chaos operations |
195 | | pub type ChaosResult<T> = Result<T, ChaosError>; |
196 | | |
197 | | /// Errors that can occur during chaos testing |
198 | | #[derive(Debug, Clone, PartialEq, Eq)] |
199 | | pub 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 | | |
223 | | impl std::fmt::Display for ChaosError { |
224 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
225 | 0 | match self { |
226 | 0 | ChaosError::MemoryLimitExceeded { limit, used } => { |
227 | 0 | write!(f, "Memory limit exceeded: {} > {} bytes", used, limit) |
228 | | } |
229 | 0 | ChaosError::Timeout { elapsed, limit } => { |
230 | 0 | write!(f, "Timeout: {:?} > {:?}", elapsed, limit) |
231 | | } |
232 | 0 | ChaosError::SignalInjectionFailed { signal, reason } => { |
233 | 0 | write!(f, "Signal injection failed ({}): {}", signal, reason) |
234 | | } |
235 | | } |
236 | 0 | } |
237 | | } |
238 | | |
239 | | impl std::error::Error for ChaosError {} |
240 | | |
241 | | #[cfg(test)] |
242 | | mod 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 { |
302 | | limit: 1000, |
303 | | used: 2000, |
304 | | }; |
305 | | assert_eq!( |
306 | | format!("{}", mem_err), |
307 | | "Memory limit exceeded: 2000 > 1000 bytes" |
308 | | ); |
309 | | |
310 | | let timeout_err = ChaosError::Timeout { |
311 | | elapsed: Duration::from_secs(5), |
312 | | limit: Duration::from_secs(3), |
313 | | }; |
314 | | assert!(format!("{}", timeout_err).contains("Timeout")); |
315 | | |
316 | | let signal_err = ChaosError::SignalInjectionFailed { |
317 | | signal: 9, |
318 | | reason: "test failure".to_string(), |
319 | | }; |
320 | | assert_eq!( |
321 | | format!("{}", signal_err), |
322 | | "Signal injection failed (9): test failure" |
323 | | ); |
324 | | } |
325 | | } |