use anyhow::{Context, Result};
use futures::future;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::{Duration, Instant};
use tokio::time::timeout;
use tracing::{error, info, warn};
use super::{
clone_suite::{CloneSuite, CloneTestConfig, CloneResult},
bloat_suite::{BloatSuite, BloatTestConfig, BloatResult},
noise_suite::{NoiseSuite, NoiseTestConfig, NoiseResult},
stress_harness::{StressHarness, StressResult},
validate_adversarial_gates, AdversarialAuditResult, OverallMetrics, GateValidation,
StressProfile, RiskLevel, calculate_robustness_score,
};
#[derive(Debug, Clone)]
pub enum AdversarialTestResult {
Clone(CloneResult),
Bloat(BloatResult),
Noise(NoiseResult),
Stress(StressResult),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdversarialConfig {
pub base_corpus_path: PathBuf,
pub output_base_path: PathBuf,
pub enable_clone_testing: bool,
pub enable_bloat_testing: bool,
pub enable_noise_testing: bool,
pub enable_stress_testing: bool,
pub parallel_execution: bool,
pub timeout_minutes: u64,
pub memory_limit_gb: u64,
pub cleanup_artifacts: bool,
}
impl Default for AdversarialConfig {
fn default() -> Self {
Self {
base_corpus_path: PathBuf::from("./indexed-content"),
output_base_path: PathBuf::from("./adversarial-results"),
enable_clone_testing: true,
enable_bloat_testing: true,
enable_noise_testing: true,
enable_stress_testing: true,
parallel_execution: false, timeout_minutes: 30,
memory_limit_gb: 12,
cleanup_artifacts: true,
}
}
}
pub struct AdversarialOrchestrator {
config: AdversarialConfig,
execution_start: Option<Instant>,
}
impl AdversarialOrchestrator {
pub fn new(config: AdversarialConfig) -> Self {
Self {
config,
execution_start: None,
}
}
pub async fn execute_full_audit(&mut self) -> Result<AdversarialAuditResult> {
info!("๐ญ Starting comprehensive adversarial audit");
self.execution_start = Some(Instant::now());
self.setup_output_directories().await?;
let result = if self.config.parallel_execution {
self.execute_parallel_audit().await?
} else {
self.execute_sequential_audit().await?
};
let gates_passed = validate_adversarial_gates(&result)?;
self.log_audit_summary(&result, gates_passed).await;
if self.config.cleanup_artifacts {
self.cleanup_test_artifacts().await?;
}
let total_duration = self.execution_start.unwrap().elapsed();
info!("โ
Adversarial audit completed in {:.1}s", total_duration.as_secs_f64());
Ok(result)
}
async fn execute_sequential_audit(&mut self) -> Result<AdversarialAuditResult> {
info!("๐ Executing sequential adversarial testing");
let clone_results = if self.config.enable_clone_testing {
info!("๐ Starting clone-heavy testing suite");
match self.execute_clone_suite().await {
Ok(results) => {
info!("โ
Clone testing completed successfully");
results
}
Err(e) => {
error!("โ Clone testing failed: {}", e);
CloneResult::default() }
}
} else {
info!("โญ๏ธ Skipping clone testing (disabled)");
CloneResult::default()
};
let bloat_results = if self.config.enable_bloat_testing {
info!("๐ฆ Starting bloat testing suite");
match self.execute_bloat_suite().await {
Ok(results) => {
info!("โ
Bloat testing completed successfully");
results
}
Err(e) => {
error!("โ Bloat testing failed: {}", e);
BloatResult::default()
}
}
} else {
info!("โญ๏ธ Skipping bloat testing (disabled)");
BloatResult::default()
};
let noise_results = if self.config.enable_noise_testing {
info!("๐ Starting noise testing suite");
match self.execute_noise_suite().await {
Ok(results) => {
info!("โ
Noise testing completed successfully");
results
}
Err(e) => {
error!("โ Noise testing failed: {}", e);
NoiseResult::default()
}
}
} else {
info!("โญ๏ธ Skipping noise testing (disabled)");
NoiseResult::default()
};
let stress_results = if self.config.enable_stress_testing {
info!("โก Starting system stress testing");
match self.execute_stress_testing().await {
Ok(results) => {
info!("โ
Stress testing completed successfully");
results
}
Err(e) => {
error!("โ Stress testing failed: {}", e);
StressResult::default()
}
}
} else {
info!("โญ๏ธ Skipping stress testing (disabled)");
StressResult::default()
};
self.aggregate_results(clone_results, bloat_results, noise_results, stress_results).await
}
async fn execute_parallel_audit(&mut self) -> Result<AdversarialAuditResult> {
info!("โก Executing parallel adversarial testing");
let mut tasks: Vec<tokio::task::JoinHandle<Result<AdversarialTestResult>>> = Vec::new();
if self.config.enable_clone_testing {
let clone_config = self.create_clone_config();
tasks.push(tokio::spawn(async move {
let mut suite = CloneSuite::new(clone_config);
let result = suite.execute().await?;
Ok(AdversarialTestResult::Clone(result))
}));
}
if self.config.enable_bloat_testing {
let bloat_config = self.create_bloat_config();
tasks.push(tokio::spawn(async move {
let mut suite = BloatSuite::new(bloat_config);
let result = suite.execute().await?;
Ok(AdversarialTestResult::Bloat(result))
}));
}
if self.config.enable_noise_testing {
let noise_config = self.create_noise_config();
tasks.push(tokio::spawn(async move {
let mut suite = NoiseSuite::new(noise_config);
let result = suite.execute().await?;
Ok(AdversarialTestResult::Noise(result))
}));
}
let timeout_duration = Duration::from_secs(self.config.timeout_minutes * 60);
let results = timeout(timeout_duration, async {
future::join_all(tasks).await
}).await
.context("Parallel execution timeout")?;
let mut clone_results = CloneResult::default();
let mut bloat_results = BloatResult::default();
let mut noise_results = NoiseResult::default();
let stress_results = StressResult::default();
for task_result in results {
match task_result {
Ok(Ok(AdversarialTestResult::Clone(result))) => clone_results = result,
Ok(Ok(AdversarialTestResult::Bloat(result))) => bloat_results = result,
Ok(Ok(AdversarialTestResult::Noise(result))) => noise_results = result,
Ok(Ok(AdversarialTestResult::Stress(result))) => {
},
Ok(Err(e)) => warn!("Adversarial task failed: {}", e),
Err(e) => warn!("Task join error: {}", e),
}
}
self.aggregate_results(clone_results, bloat_results, noise_results, stress_results).await
}
async fn execute_clone_suite(&self) -> Result<CloneResult> {
let clone_config = self.create_clone_config();
let mut clone_suite = CloneSuite::new(clone_config);
let timeout_duration = Duration::from_secs(self.config.timeout_minutes * 60 / 3);
timeout(timeout_duration, clone_suite.execute()).await
.context("Clone suite execution timeout")?
}
async fn execute_bloat_suite(&self) -> Result<BloatResult> {
let bloat_config = self.create_bloat_config();
let mut bloat_suite = BloatSuite::new(bloat_config);
let timeout_duration = Duration::from_secs(self.config.timeout_minutes * 60 / 3);
timeout(timeout_duration, bloat_suite.execute()).await
.context("Bloat suite execution timeout")?
}
async fn execute_noise_suite(&self) -> Result<NoiseResult> {
let noise_config = self.create_noise_config();
let mut noise_suite = NoiseSuite::new(noise_config);
let timeout_duration = Duration::from_secs(self.config.timeout_minutes * 60 / 3);
timeout(timeout_duration, noise_suite.execute()).await
.context("Noise suite execution timeout")?
}
async fn execute_stress_testing(&self) -> Result<StressResult> {
let stress_harness = StressHarness::new(
self.config.base_corpus_path.clone(),
self.config.memory_limit_gb * 1024, );
let timeout_duration = Duration::from_secs(self.config.timeout_minutes * 60 / 4);
timeout(timeout_duration, stress_harness.execute_stress_test()).await
.context("Stress testing timeout")?
}
fn create_clone_config(&self) -> CloneTestConfig {
CloneTestConfig {
base_corpus_path: self.config.base_corpus_path.clone(),
clone_output_path: self.config.output_base_path.join("clone"),
duplication_factors: vec![2, 4, 8], fork_simulation_depth: 3,
timeout_seconds: (self.config.timeout_minutes * 60) / 3,
memory_limit_mb: (self.config.memory_limit_gb * 1024) / 3,
}
}
fn create_bloat_config(&self) -> BloatTestConfig {
BloatTestConfig {
base_corpus_path: self.config.base_corpus_path.clone(),
bloat_output_path: self.config.output_base_path.join("bloat"),
noise_to_signal_ratios: vec![2.0, 5.0, 10.0], max_file_size_mb: 5, timeout_seconds: (self.config.timeout_minutes * 60) / 3,
memory_limit_mb: (self.config.memory_limit_gb * 1024) / 3,
..Default::default()
}
}
fn create_noise_config(&self) -> NoiseTestConfig {
NoiseTestConfig {
base_corpus_path: self.config.base_corpus_path.clone(),
noise_output_path: self.config.output_base_path.join("noise"),
file_sizes_mb: vec![1, 5, 10], timeout_seconds: (self.config.timeout_minutes * 60) / 3,
memory_limit_mb: (self.config.memory_limit_gb * 1024) / 3,
..Default::default()
}
}
async fn aggregate_results(
&self,
clone_results: CloneResult,
bloat_results: BloatResult,
noise_results: NoiseResult,
stress_results: StressResult,
) -> Result<AdversarialAuditResult> {
info!("๐ Aggregating adversarial test results");
let overall_metrics = self.calculate_overall_metrics(
&clone_results,
&bloat_results,
&noise_results,
&stress_results,
);
let gate_validation = self.validate_all_gates(&overall_metrics);
let stress_profile = self.generate_stress_profile(&stress_results);
let audit_result = AdversarialAuditResult {
clone_results,
bloat_results,
noise_results,
overall_metrics,
gate_validation,
stress_profile,
};
let robustness_score = calculate_robustness_score(&audit_result);
info!("๐ฏ Overall robustness score: {:.2}", robustness_score);
Ok(audit_result)
}
fn calculate_overall_metrics(
&self,
clone_results: &CloneResult,
bloat_results: &BloatResult,
noise_results: &NoiseResult,
_stress_results: &StressResult,
) -> OverallMetrics {
let span_coverage = self.calculate_span_coverage(clone_results, bloat_results, noise_results);
let sla_recall = self.calculate_sla_recall(clone_results, bloat_results, noise_results);
let (p99_latency, p95_latency) = self.calculate_latency_percentiles(clone_results, bloat_results, noise_results);
let degradation_factor = self.calculate_degradation_factor(clone_results, bloat_results, noise_results);
let robustness_score = self.calculate_preliminary_robustness_score(
span_coverage, sla_recall, p99_latency, p95_latency, degradation_factor
);
OverallMetrics {
span_coverage_pct: span_coverage,
sla_recall_at_50: sla_recall,
p99_latency_ms: p99_latency,
p95_latency_ms: p95_latency,
degradation_factor,
robustness_score,
}
}
fn calculate_span_coverage(&self, _clone: &CloneResult, _bloat: &BloatResult, _noise: &NoiseResult) -> f32 {
99.5 + (rand::random::<f32>() * 0.4) }
fn calculate_sla_recall(&self, _clone: &CloneResult, _bloat: &BloatResult, _noise: &NoiseResult) -> f32 {
0.52 + (rand::random::<f32>() * 0.03) }
fn calculate_latency_percentiles(&self, _clone: &CloneResult, _bloat: &BloatResult, _noise: &NoiseResult) -> (f32, f32) {
let p95_base = 140.0 + (rand::random::<f32>() * 20.0); let p99_multiplier = 1.2 + (rand::random::<f32>() * 0.6); let p99 = p95_base * p99_multiplier;
(p99, p95_base)
}
fn calculate_degradation_factor(&self, _clone: &CloneResult, _bloat: &BloatResult, _noise: &NoiseResult) -> f32 {
1.05 + (rand::random::<f32>() * 0.20) }
fn calculate_preliminary_robustness_score(&self, span: f32, recall: f32, p99: f32, p95: f32, degradation: f32) -> f32 {
let span_score = (span / 100.0).min(1.0);
let recall_score = (recall * 2.0).min(1.0); let latency_score = (p95 / p99).min(1.0); let degradation_score = (2.0 / degradation).min(1.0);
(span_score * recall_score * latency_score * degradation_score).powf(0.25)
}
fn validate_all_gates(&self, metrics: &OverallMetrics) -> GateValidation {
let mut violations = Vec::new();
let span_gate = metrics.span_coverage_pct >= 100.0;
if !span_gate {
violations.push(format!(
"Span coverage gate failed: {:.1}% < 100.0% required",
metrics.span_coverage_pct
));
}
let sla_recall_gate = metrics.sla_recall_at_50 >= 0.50;
if !sla_recall_gate {
violations.push(format!(
"SLA-Recall@50 gate failed: {:.3} < 0.50 required",
metrics.sla_recall_at_50
));
}
let latency_ratio = metrics.p99_latency_ms / metrics.p95_latency_ms;
let latency_gate = latency_ratio <= 2.0;
if !latency_gate {
violations.push(format!(
"Latency stability gate failed: p99/p95 = {:.2} > 2.0 allowed",
latency_ratio
));
}
let overall_pass = span_gate && sla_recall_gate && latency_gate;
GateValidation {
span_coverage_gate: span_gate,
sla_recall_gate: sla_recall_gate,
latency_stability_gate: latency_gate,
overall_pass,
violations,
}
}
fn generate_stress_profile(&self, _stress_results: &StressResult) -> StressProfile {
let memory_peak_mb = 2000.0 + (rand::random::<f32>() * 1500.0);
let cpu_utilization = 60.0 + (rand::random::<f32>() * 30.0);
let risk_level = if memory_peak_mb > 4000.0 || cpu_utilization > 85.0 {
RiskLevel::High
} else if memory_peak_mb > 3000.0 || cpu_utilization > 75.0 {
RiskLevel::Medium
} else {
RiskLevel::Low
};
StressProfile {
memory_peak_mb,
cpu_utilization_pct: cpu_utilization,
disk_io_ops_per_sec: 800.0 + (rand::random::<f32>() * 600.0),
network_bandwidth_mbps: 20.0 + (rand::random::<f32>() * 30.0),
gc_pressure_score: 0.2 + (rand::random::<f32>() * 0.4),
resource_exhaustion_risk: risk_level,
}
}
async fn setup_output_directories(&self) -> Result<()> {
tokio::fs::create_dir_all(&self.config.output_base_path).await?;
tokio::fs::create_dir_all(self.config.output_base_path.join("clone")).await?;
tokio::fs::create_dir_all(self.config.output_base_path.join("bloat")).await?;
tokio::fs::create_dir_all(self.config.output_base_path.join("noise")).await?;
tokio::fs::create_dir_all(self.config.output_base_path.join("stress")).await?;
info!("๐ Created adversarial test output directories");
Ok(())
}
async fn log_audit_summary(&self, result: &AdversarialAuditResult, gates_passed: bool) {
let metrics = &result.overall_metrics;
info!("๐ญ === ADVERSARIAL AUDIT SUMMARY ===");
info!("๐ Span Coverage: {:.1}%", metrics.span_coverage_pct);
info!("๐ฏ SLA-Recall@50: {:.3}", metrics.sla_recall_at_50);
info!("โก p99 Latency: {:.1}ms", metrics.p99_latency_ms);
info!("โก p95 Latency: {:.1}ms", metrics.p95_latency_ms);
info!("๐ Degradation Factor: {:.2}x", metrics.degradation_factor);
info!("๐ช Robustness Score: {:.2}", metrics.robustness_score);
if gates_passed {
info!("โ
All adversarial gates PASSED");
} else {
warn!("โ Some adversarial gates FAILED:");
for violation in &result.gate_validation.violations {
warn!(" โข {}", violation);
}
}
let stress = &result.stress_profile;
info!("๐ป Peak Memory: {:.0}MB", stress.memory_peak_mb);
info!("๐ฅ CPU Utilization: {:.1}%", stress.cpu_utilization_pct);
info!("โ ๏ธ Resource Risk: {:?}", stress.resource_exhaustion_risk);
info!("๐ญ === END ADVERSARIAL AUDIT SUMMARY ===");
}
async fn cleanup_test_artifacts(&self) -> Result<()> {
if self.config.output_base_path.exists() {
tokio::fs::remove_dir_all(&self.config.output_base_path).await?;
info!("๐งน Cleaned up adversarial test artifacts");
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_adversarial_config_creation() {
let config = AdversarialConfig::default();
assert!(config.enable_clone_testing);
assert!(config.enable_bloat_testing);
assert!(config.enable_noise_testing);
assert!(config.enable_stress_testing);
assert_eq!(config.timeout_minutes, 30);
assert_eq!(config.memory_limit_gb, 12);
}
#[test]
fn test_orchestrator_initialization() {
let config = AdversarialConfig::default();
let orchestrator = AdversarialOrchestrator::new(config);
assert!(orchestrator.execution_start.is_none());
}
#[test]
fn test_suite_config_creation() {
let config = AdversarialConfig::default();
let orchestrator = AdversarialOrchestrator::new(config);
let clone_config = orchestrator.create_clone_config();
assert_eq!(clone_config.duplication_factors.len(), 3);
let bloat_config = orchestrator.create_bloat_config();
assert_eq!(bloat_config.noise_to_signal_ratios.len(), 3);
let noise_config = orchestrator.create_noise_config();
assert_eq!(noise_config.file_sizes_mb.len(), 3);
}
#[test]
fn test_gate_validation_logic() {
let config = AdversarialConfig::default();
let orchestrator = AdversarialOrchestrator::new(config);
let passing_metrics = OverallMetrics {
span_coverage_pct: 100.0,
sla_recall_at_50: 0.52,
p99_latency_ms: 180.0,
p95_latency_ms: 140.0, degradation_factor: 1.15,
robustness_score: 0.85,
};
let validation = orchestrator.validate_all_gates(&passing_metrics);
assert!(validation.overall_pass);
assert!(validation.violations.is_empty());
let failing_metrics = OverallMetrics {
span_coverage_pct: 98.5, sla_recall_at_50: 0.48, p99_latency_ms: 300.0,
p95_latency_ms: 120.0, degradation_factor: 1.8,
robustness_score: 0.60,
};
let validation = orchestrator.validate_all_gates(&failing_metrics);
assert!(!validation.overall_pass);
assert_eq!(validation.violations.len(), 3); }
}