Skip to main content

assay_sim/
lib.rs

1pub mod attacks;
2pub mod corpus;
3pub mod differential;
4pub mod mutators;
5pub mod report;
6pub mod subprocess;
7pub mod suite;
8
9pub use report::{AttackResult, AttackStatus, SimReport};
10pub use suite::{run_suite, tier_default_limits, SuiteConfig, SuiteTier, TimeBudget};
11
12#[cfg(test)]
13mod tests {
14    use super::*;
15    use std::path::PathBuf;
16
17    #[test]
18    fn test_quick_suite() {
19        let cfg = SuiteConfig {
20            tier: SuiteTier::Quick,
21            target_bundle: PathBuf::from("placeholder"),
22            seed: 42,
23            verify_limits: None,
24            time_budget_secs: 60,
25        };
26
27        let report = run_suite(cfg).expect("Suite failed to run");
28
29        // Print full report on failure for debugging
30        if report.summary.bypassed > 0 {
31            println!("{}", serde_json::to_string_pretty(&report).unwrap());
32        }
33
34        // Invariant assertions (stable across attack additions):
35        // - No attack may bypass verification (security contract)
36        assert_eq!(
37            report.summary.bypassed, 0,
38            "SECURITY: {} attacks bypassed verification",
39            report.summary.bypassed
40        );
41        // - At least 1 attack must be blocked (sanity: attacks actually ran)
42        assert!(
43            report.summary.blocked >= 1,
44            "SANITY: no attacks were blocked — suite may not have run"
45        );
46        // - At least 1 check must pass, or differential ran (sanity: differential tests ran; allow flaky fail on CI)
47        let differential_ran = report
48            .results
49            .iter()
50            .any(|r| r.name == "differential.invariants");
51        assert!(
52            report.summary.passed >= 1 || differential_ran,
53            "SANITY: no checks passed and differential did not run — suite may not have run"
54        );
55        // - Every result must have a valid status classification:
56        //   Blocked/Passed are normal outcomes.
57        //   Error is acceptable for chaos IO faults (WouldBlock, persistent EINTR)
58        //   but NOT for integrity/differential tests.
59        for r in &report.results {
60            let is_chaos_io = r.name.starts_with("chaos.io_fault.");
61            match r.status {
62                AttackStatus::Blocked | AttackStatus::Passed => {} // always ok
63                AttackStatus::Error if is_chaos_io => {}           // infra IO, acceptable
64                _ => panic!(
65                    "Unexpected status {:?} for '{}': {:?}",
66                    r.status, r.name, r.message
67                ),
68            }
69        }
70    }
71}