use std::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub struct ChaosConfig {
pub memory_limit: usize,
pub cpu_limit: f64,
pub timeout: Duration,
pub signal_injection: bool,
}
impl Default for ChaosConfig {
fn default() -> Self {
Self {
memory_limit: 0,
cpu_limit: 0.0,
timeout: Duration::from_secs(60),
signal_injection: false,
}
}
}
impl ChaosConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_memory_limit(mut self, bytes: usize) -> Self {
self.memory_limit = bytes;
self
}
pub fn with_cpu_limit(mut self, fraction: f64) -> Self {
self.cpu_limit = fraction.clamp(0.0, 1.0);
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn with_signal_injection(mut self, enabled: bool) -> Self {
self.signal_injection = enabled;
self
}
pub fn build(self) -> Self {
self
}
pub fn gentle() -> Self {
Self::new()
.with_memory_limit(512 * 1024 * 1024)
.with_cpu_limit(0.8)
.with_timeout(Duration::from_secs(120))
}
pub fn aggressive() -> Self {
Self::new()
.with_memory_limit(64 * 1024 * 1024)
.with_cpu_limit(0.25)
.with_timeout(Duration::from_secs(10))
.with_signal_injection(true)
}
}
pub type ChaosResult<T> = Result<T, ChaosError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChaosError {
MemoryLimitExceeded {
limit: usize,
used: usize,
},
Timeout {
elapsed: Duration,
limit: Duration,
},
SignalInjectionFailed {
signal: i32,
reason: String,
},
}
impl std::fmt::Display for ChaosError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChaosError::MemoryLimitExceeded { limit, used } => {
write!(f, "Memory limit exceeded: {} > {} bytes", used, limit)
}
ChaosError::Timeout { elapsed, limit } => {
write!(f, "Timeout: {:?} > {:?}", elapsed, limit)
}
ChaosError::SignalInjectionFailed { signal, reason } => {
write!(f, "Signal injection failed ({}): {}", signal, reason)
}
}
}
}
impl std::error::Error for ChaosError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = ChaosConfig::default();
assert_eq!(config.memory_limit, 0);
assert_eq!(config.cpu_limit, 0.0);
assert_eq!(config.timeout, Duration::from_secs(60));
assert!(!config.signal_injection);
}
#[test]
fn test_builder_pattern() {
let config = ChaosConfig::new()
.with_memory_limit(1024)
.with_cpu_limit(0.5)
.with_timeout(Duration::from_secs(30))
.with_signal_injection(true)
.build();
assert_eq!(config.memory_limit, 1024);
assert_eq!(config.cpu_limit, 0.5);
assert_eq!(config.timeout, Duration::from_secs(30));
assert!(config.signal_injection);
}
#[test]
fn test_cpu_limit_clamping() {
let too_high = ChaosConfig::new().with_cpu_limit(1.5);
assert_eq!(too_high.cpu_limit, 1.0);
let too_low = ChaosConfig::new().with_cpu_limit(-0.5);
assert_eq!(too_low.cpu_limit, 0.0);
let valid = ChaosConfig::new().with_cpu_limit(0.75);
assert_eq!(valid.cpu_limit, 0.75);
}
#[test]
fn test_gentle_preset() {
let gentle = ChaosConfig::gentle();
assert_eq!(gentle.memory_limit, 512 * 1024 * 1024);
assert_eq!(gentle.cpu_limit, 0.8);
assert_eq!(gentle.timeout, Duration::from_secs(120));
assert!(!gentle.signal_injection);
}
#[test]
fn test_aggressive_preset() {
let aggressive = ChaosConfig::aggressive();
assert_eq!(aggressive.memory_limit, 64 * 1024 * 1024);
assert_eq!(aggressive.cpu_limit, 0.25);
assert_eq!(aggressive.timeout, Duration::from_secs(10));
assert!(aggressive.signal_injection);
}
#[test]
fn test_chaos_error_display() {
let mem_err = ChaosError::MemoryLimitExceeded { limit: 1000, used: 2000 };
assert_eq!(format!("{}", mem_err), "Memory limit exceeded: 2000 > 1000 bytes");
let timeout_err =
ChaosError::Timeout { elapsed: Duration::from_secs(5), limit: Duration::from_secs(3) };
assert!(format!("{}", timeout_err).contains("Timeout"));
let signal_err =
ChaosError::SignalInjectionFailed { signal: 9, reason: "test failure".to_string() };
assert_eq!(format!("{}", signal_err), "Signal injection failed (9): test failure");
}
}