use std::time::Duration;
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 {
Self::MemoryLimitExceeded { limit, used } => {
write!(f, "Memory limit exceeded: {used} > {limit} bytes")
}
Self::Timeout { elapsed, limit } => {
write!(f, "Timeout: {elapsed:?} > {limit:?}")
}
Self::SignalInjectionFailed { signal, reason } => {
write!(f, "Signal injection failed ({signal}): {reason}")
}
}
}
}
impl std::error::Error for ChaosError {}
#[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 {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_memory_limit(mut self, bytes: usize) -> Self {
self.memory_limit = bytes;
self
}
#[must_use]
pub const fn with_cpu_limit(mut self, fraction: f64) -> Self {
self.cpu_limit = fraction.clamp(0.0, 1.0);
self
}
#[must_use]
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub const fn with_signal_injection(mut self, enabled: bool) -> Self {
self.signal_injection = enabled;
self
}
#[must_use]
pub const fn build(self) -> Self {
self
}
#[must_use]
pub fn gentle() -> Self {
Self::new()
.with_memory_limit(512 * 1024 * 1024) .with_cpu_limit(0.8) .with_timeout(Duration::from_secs(120))
}
#[must_use]
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)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
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]
#[allow(clippy::float_cmp)]
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]
#[allow(clippy::float_cmp)]
fn test_cpu_limit_clamping() {
let too_high = ChaosConfig::new().with_cpu_limit(2.0);
assert_eq!(too_high.cpu_limit, 1.0);
let too_low = ChaosConfig::new().with_cpu_limit(-1.0);
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]
#[allow(clippy::float_cmp)]
fn test_gentle_preset() {
let config = ChaosConfig::gentle();
assert_eq!(config.memory_limit, 512 * 1024 * 1024);
assert_eq!(config.cpu_limit, 0.8);
assert_eq!(config.timeout, Duration::from_secs(120));
assert!(!config.signal_injection);
}
#[test]
#[allow(clippy::float_cmp)]
fn test_aggressive_preset() {
let config = ChaosConfig::aggressive();
assert_eq!(config.memory_limit, 64 * 1024 * 1024);
assert_eq!(config.cpu_limit, 0.25);
assert_eq!(config.timeout, Duration::from_secs(10));
assert!(config.signal_injection);
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_clone() {
let original = ChaosConfig::gentle();
let cloned = original.clone();
assert_eq!(&original, &cloned);
}
#[test]
fn test_debug_format() {
let config = ChaosConfig::new();
let debug_str = format!("{config:?}");
assert!(debug_str.contains("ChaosConfig"));
assert!(debug_str.contains("memory_limit"));
}
#[test]
fn test_memory_limit_exceeded_display() {
let error = ChaosError::MemoryLimitExceeded { limit: 1024, used: 2048 };
assert_eq!(format!("{error}"), "Memory limit exceeded: 2048 > 1024 bytes");
}
#[test]
fn test_timeout_display() {
let error =
ChaosError::Timeout { elapsed: Duration::from_secs(5), limit: Duration::from_secs(3) };
let display = format!("{error}");
assert!(display.contains("Timeout"));
assert!(display.contains("5s"));
assert!(display.contains("3s"));
}
#[test]
fn test_signal_injection_failed_display() {
let error = ChaosError::SignalInjectionFailed {
signal: 2,
reason: "Process not found".to_string(),
};
assert_eq!(format!("{error}"), "Signal injection failed (2): Process not found");
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_chaos_error_clone() {
let error = ChaosError::MemoryLimitExceeded { limit: 100, used: 200 };
let cloned = error.clone();
assert_eq!(&error, &cloned);
}
#[test]
fn test_chaos_error_debug() {
let error = ChaosError::Timeout {
elapsed: Duration::from_secs(1),
limit: Duration::from_millis(500),
};
let debug_str = format!("{error:?}");
assert!(debug_str.contains("Timeout"));
}
#[test]
fn test_chaos_result_ok() {
let result: ChaosResult<i32> = Ok(42);
if let Ok(value) = result {
assert_eq!(value, 42);
} else {
panic!("Expected Ok result");
}
}
#[test]
fn test_chaos_result_err() {
let result: ChaosResult<i32> =
Err(ChaosError::MemoryLimitExceeded { limit: 100, used: 200 });
assert!(result.is_err());
}
}