use std::time::Duration;
use elara_core::{ChaosCategory, ChaosSuccessCriteria, DegradationLevel, PresenceVector};
use crate::chaos::ChaosConfig;
use crate::integration::{IntegrationTestConfig, IntegrationTestHarness, SimulatedNode};
#[derive(Debug, Clone)]
pub struct ChaosTestSpec {
pub name: String,
pub category: ChaosCategory,
pub duration: Duration,
pub intensity: f32,
pub criteria: ChaosSuccessCriteria,
pub node_count: usize,
pub message_count: usize,
}
impl ChaosTestSpec {
pub fn new(name: &str, category: ChaosCategory) -> Self {
Self {
name: name.to_string(),
category,
duration: Duration::from_secs(10),
intensity: 0.5,
criteria: ChaosSuccessCriteria::default_for(category),
node_count: 4,
message_count: 20,
}
}
pub fn with_duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn with_intensity(mut self, intensity: f32) -> Self {
self.intensity = intensity.clamp(0.0, 1.0);
self
}
pub fn with_nodes(mut self, count: usize) -> Self {
self.node_count = count;
self
}
pub fn with_messages(mut self, count: usize) -> Self {
self.message_count = count;
self
}
}
pub struct ChaosHarness {
specs: Vec<ChaosTestSpec>,
results: Vec<ChaosHarnessResult>,
}
#[derive(Debug, Clone)]
pub struct ChaosHarnessResult {
pub spec: ChaosTestSpec,
pub passed: bool,
pub presence_vectors: Vec<PresenceVector>,
pub degradation_levels: Vec<DegradationLevel>,
pub messages_delivered: usize,
pub messages_dropped: usize,
pub violations: Vec<String>,
}
impl ChaosHarness {
pub fn new() -> Self {
Self {
specs: Vec::new(),
results: Vec::new(),
}
}
pub fn add_test(&mut self, spec: ChaosTestSpec) {
self.specs.push(spec);
}
pub fn add_standard_tests(&mut self) {
self.add_test(
ChaosTestSpec::new("ontological_light", ChaosCategory::Ontological)
.with_intensity(0.3)
.with_nodes(3)
.with_messages(10),
);
self.add_test(
ChaosTestSpec::new("ontological_heavy", ChaosCategory::Ontological)
.with_intensity(0.7)
.with_nodes(5)
.with_messages(30),
);
self.add_test(
ChaosTestSpec::new("temporal_light", ChaosCategory::Temporal)
.with_intensity(0.3)
.with_nodes(3)
.with_messages(10),
);
self.add_test(
ChaosTestSpec::new("temporal_heavy", ChaosCategory::Temporal)
.with_intensity(0.7)
.with_nodes(5)
.with_messages(30),
);
self.add_test(
ChaosTestSpec::new("topological_light", ChaosCategory::Topological)
.with_intensity(0.3)
.with_nodes(4)
.with_messages(15),
);
self.add_test(
ChaosTestSpec::new("topological_heavy", ChaosCategory::Topological)
.with_intensity(0.7)
.with_nodes(6)
.with_messages(40),
);
self.add_test(
ChaosTestSpec::new("adversarial_light", ChaosCategory::Adversarial)
.with_intensity(0.3)
.with_nodes(3)
.with_messages(10),
);
self.add_test(
ChaosTestSpec::new("adversarial_heavy", ChaosCategory::Adversarial)
.with_intensity(0.7)
.with_nodes(5)
.with_messages(30),
);
self.add_test(
ChaosTestSpec::new("perceptual_light", ChaosCategory::Perceptual)
.with_intensity(0.3)
.with_nodes(3)
.with_messages(10),
);
self.add_test(
ChaosTestSpec::new("perceptual_heavy", ChaosCategory::Perceptual)
.with_intensity(0.7)
.with_nodes(5)
.with_messages(30),
);
}
pub fn run_all(&mut self) -> &[ChaosHarnessResult] {
self.results.clear();
for spec in self.specs.clone() {
let result = self.run_single(&spec);
self.results.push(result);
}
&self.results
}
fn run_single(&self, spec: &ChaosTestSpec) -> ChaosHarnessResult {
let chaos_config = self.create_chaos_config(spec.category, spec.intensity);
let config = IntegrationTestConfig {
node_count: spec.node_count,
message_count: spec.message_count,
chaos: Some(chaos_config),
};
let mut harness = IntegrationTestHarness::new(config);
let result = harness.run();
let (presence_vectors, degradation_levels) =
self.apply_category_effects(spec, harness.nodes_mut());
let passed = self.check_criteria(spec, &presence_vectors, °radation_levels);
ChaosHarnessResult {
spec: spec.clone(),
passed,
presence_vectors,
degradation_levels,
messages_delivered: result.messages_processed,
messages_dropped: result.messages_dropped,
violations: result.invariant_violations,
}
}
fn create_chaos_config(&self, category: ChaosCategory, intensity: f32) -> ChaosConfig {
match category {
ChaosCategory::Ontological => {
ChaosConfig {
base_latency: Duration::from_millis(50),
jitter: crate::chaos::JitterDistribution::Uniform {
min_ms: 0,
max_ms: (100.0 * intensity) as u32,
},
loss_rate: 0.05 * intensity as f64,
burst_loss_prob: 0.1 * intensity as f64,
burst_length: (2, (5.0 * intensity) as u32 + 2),
reorder_prob: 0.1 * intensity as f64,
reorder_depth: (5.0 * intensity) as u32 + 1,
duplicate_prob: 0.05 * intensity as f64,
}
}
ChaosCategory::Temporal => {
ChaosConfig {
base_latency: Duration::from_millis((200.0 * intensity) as u64 + 50),
jitter: crate::chaos::JitterDistribution::Pareto {
scale_ms: 50.0 * intensity as f64 + 20.0,
shape: 1.5 - 0.3 * intensity as f64,
},
loss_rate: 0.03 * intensity as f64,
burst_loss_prob: 0.05,
burst_length: (1, 3),
reorder_prob: 0.3 * intensity as f64, reorder_depth: (10.0 * intensity) as u32 + 2,
duplicate_prob: 0.02,
}
}
ChaosCategory::Topological => {
ChaosConfig {
base_latency: Duration::from_millis(100),
jitter: crate::chaos::JitterDistribution::Uniform {
min_ms: 0,
max_ms: 50,
},
loss_rate: 0.2 * intensity as f64, burst_loss_prob: 0.3 * intensity as f64, burst_length: ((5.0 * intensity) as u32 + 2, (20.0 * intensity) as u32 + 5),
reorder_prob: 0.05,
reorder_depth: 3,
duplicate_prob: 0.01,
}
}
ChaosCategory::Adversarial => {
ChaosConfig {
base_latency: Duration::from_millis(50),
jitter: crate::chaos::JitterDistribution::Uniform {
min_ms: 0,
max_ms: 30,
},
loss_rate: 0.02,
burst_loss_prob: 0.05,
burst_length: (1, 3),
reorder_prob: 0.1 * intensity as f64,
reorder_depth: 5,
duplicate_prob: 0.2 * intensity as f64, }
}
ChaosCategory::Perceptual => {
ChaosConfig {
base_latency: Duration::from_millis((100.0 * intensity) as u64 + 20),
jitter: crate::chaos::JitterDistribution::Pareto {
scale_ms: 100.0 * intensity as f64 + 10.0,
shape: 1.2,
},
loss_rate: 0.1 * intensity as f64,
burst_loss_prob: 0.15 * intensity as f64,
burst_length: (2, 6),
reorder_prob: 0.1,
reorder_depth: 4,
duplicate_prob: 0.02,
}
}
}
}
fn apply_category_effects(
&self,
spec: &ChaosTestSpec,
nodes: &mut [SimulatedNode],
) -> (Vec<PresenceVector>, Vec<DegradationLevel>) {
let intensity = spec.intensity;
for node in nodes.iter_mut() {
match spec.category {
ChaosCategory::Ontological => {
node.update_presence(1.0 - 0.3 * intensity);
if intensity > 0.5 {
node.degrade();
}
}
ChaosCategory::Temporal => {
let factor = 1.0 - 0.4 * intensity;
node.update_presence(factor);
if intensity > 0.6 {
node.degrade();
node.degrade();
}
}
ChaosCategory::Topological => {
let factor = 1.0 - 0.5 * intensity;
node.update_presence(factor);
let degrade_count = (intensity * 3.0) as usize;
for _ in 0..degrade_count {
node.degrade();
}
}
ChaosCategory::Adversarial => {
let factor = 1.0 - 0.2 * intensity;
node.update_presence(factor);
if intensity > 0.7 {
node.degrade();
}
}
ChaosCategory::Perceptual => {
let factor = 1.0 - 0.35 * intensity;
node.update_presence(factor);
if intensity > 0.4 {
node.degrade();
}
}
}
}
let presence_vectors: Vec<_> = nodes.iter().map(|n| *n.presence()).collect();
let degradation_levels: Vec<_> = nodes.iter().map(|n| n.degradation_level()).collect();
(presence_vectors, degradation_levels)
}
fn check_criteria(
&self,
spec: &ChaosTestSpec,
presence_vectors: &[PresenceVector],
degradation_levels: &[DegradationLevel],
) -> bool {
if !presence_vectors.iter().all(|p| p.is_alive()) {
return false;
}
let max_allowed = spec.criteria.max_degradation;
if degradation_levels.iter().any(|&d| d > max_allowed) {
return false;
}
true
}
pub fn results(&self) -> &[ChaosHarnessResult] {
&self.results
}
pub fn summary(&self) -> ChaosSummary {
let total = self.results.len();
let passed = self.results.iter().filter(|r| r.passed).count();
let failed = total - passed;
let by_category: Vec<_> = ChaosCategory::all()
.iter()
.map(|&cat| {
let cat_results: Vec<_> = self
.results
.iter()
.filter(|r| r.spec.category == cat)
.collect();
let cat_passed = cat_results.iter().filter(|r| r.passed).count();
(cat, cat_passed, cat_results.len())
})
.collect();
ChaosSummary {
total,
passed,
failed,
by_category,
}
}
}
impl Default for ChaosHarness {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ChaosSummary {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub by_category: Vec<(ChaosCategory, usize, usize)>,
}
impl ChaosSummary {
pub fn all_passed(&self) -> bool {
self.failed == 0
}
pub fn pass_rate(&self) -> f32 {
if self.total == 0 {
1.0
} else {
self.passed as f32 / self.total as f32
}
}
}
pub fn run_standard_chaos_tests() -> ChaosSummary {
let mut harness = ChaosHarness::new();
harness.add_standard_tests();
harness.run_all();
harness.summary()
}
pub fn run_category_tests(category: ChaosCategory) -> Vec<ChaosHarnessResult> {
let mut harness = ChaosHarness::new();
harness.add_test(
ChaosTestSpec::new("light", category)
.with_intensity(0.3)
.with_nodes(3)
.with_messages(10),
);
harness.add_test(
ChaosTestSpec::new("moderate", category)
.with_intensity(0.5)
.with_nodes(4)
.with_messages(20),
);
harness.add_test(
ChaosTestSpec::new("heavy", category)
.with_intensity(0.7)
.with_nodes(5)
.with_messages(30),
);
harness.run_all().to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chaos_harness_creation() {
let harness = ChaosHarness::new();
assert!(harness.specs.is_empty());
assert!(harness.results.is_empty());
}
#[test]
fn test_add_standard_tests() {
let mut harness = ChaosHarness::new();
harness.add_standard_tests();
assert_eq!(harness.specs.len(), 10); }
#[test]
fn test_chaos_spec_builder() {
let spec = ChaosTestSpec::new("test", ChaosCategory::Temporal)
.with_intensity(0.8)
.with_nodes(6)
.with_messages(50);
assert_eq!(spec.name, "test");
assert_eq!(spec.category, ChaosCategory::Temporal);
assert_eq!(spec.intensity, 0.8);
assert_eq!(spec.node_count, 6);
assert_eq!(spec.message_count, 50);
}
#[test]
fn test_run_single_ontological() {
let harness = ChaosHarness::new();
let spec = ChaosTestSpec::new("ontological_test", ChaosCategory::Ontological)
.with_intensity(0.3)
.with_nodes(2)
.with_messages(5);
let result = harness.run_single(&spec);
assert!(result.passed, "Light ontological chaos should pass");
assert!(result.presence_vectors.iter().all(|p| p.is_alive()));
}
#[test]
fn test_run_single_temporal() {
let harness = ChaosHarness::new();
let spec = ChaosTestSpec::new("temporal_test", ChaosCategory::Temporal)
.with_intensity(0.3)
.with_nodes(2)
.with_messages(5);
let result = harness.run_single(&spec);
assert!(result.passed, "Light temporal chaos should pass");
}
#[test]
fn test_run_single_topological() {
let harness = ChaosHarness::new();
let spec = ChaosTestSpec::new("topological_test", ChaosCategory::Topological)
.with_intensity(0.3)
.with_nodes(2)
.with_messages(5);
let result = harness.run_single(&spec);
assert!(result.passed, "Light topological chaos should pass");
}
#[test]
fn test_run_single_adversarial() {
let harness = ChaosHarness::new();
let spec = ChaosTestSpec::new("adversarial_test", ChaosCategory::Adversarial)
.with_intensity(0.3)
.with_nodes(2)
.with_messages(5);
let result = harness.run_single(&spec);
assert!(result.passed, "Light adversarial chaos should pass");
}
#[test]
fn test_run_single_perceptual() {
let harness = ChaosHarness::new();
let spec = ChaosTestSpec::new("perceptual_test", ChaosCategory::Perceptual)
.with_intensity(0.3)
.with_nodes(2)
.with_messages(5);
let result = harness.run_single(&spec);
assert!(result.passed, "Light perceptual chaos should pass");
}
#[test]
fn test_run_standard_chaos_tests() {
let summary = run_standard_chaos_tests();
assert!(
summary.pass_rate() >= 0.5,
"At least half of chaos tests should pass"
);
assert_eq!(summary.by_category.len(), 5);
}
#[test]
fn test_chaos_summary() {
let mut harness = ChaosHarness::new();
harness.add_test(
ChaosTestSpec::new("test1", ChaosCategory::Ontological)
.with_intensity(0.2)
.with_nodes(2)
.with_messages(3),
);
harness.add_test(
ChaosTestSpec::new("test2", ChaosCategory::Temporal)
.with_intensity(0.2)
.with_nodes(2)
.with_messages(3),
);
harness.run_all();
let summary = harness.summary();
assert_eq!(summary.total, 2);
assert!(summary.pass_rate() > 0.0);
}
}