dev-chaos 0.9.2

Failure injection and recovery testing for Rust. Disk faults, network failures, panics. Part of the dev-* verification suite.
Documentation
use dev_chaos::{
    assert_recovered,
    crash::CrashPoint,
    io::{ChaosReader, ChaosWriter},
    latency::{LatencyInjector, LatencyProfile},
    ChaosProducer, FailureMode, FailureSchedule,
};
use dev_report::Producer;
use std::io::{Read, Write};
use std::time::Duration;

#[test]
fn schedule_on_attempts() {
    let s = FailureSchedule::on_attempts(&[1, 5], FailureMode::IoError);
    assert!(s.maybe_fail(1).is_err());
    assert!(s.maybe_fail(2).is_ok());
    assert!(s.maybe_fail(5).is_err());
}

#[test]
fn schedule_every_n() {
    let s = FailureSchedule::every_n(2, FailureMode::Timeout);
    assert!(s.maybe_fail(1).is_ok());
    assert!(s.maybe_fail(2).is_err());
    assert!(s.maybe_fail(4).is_err());
}

#[test]
fn schedule_seeded_random_is_reproducible() {
    let a = FailureSchedule::seeded_random(99, 0.3, FailureMode::IoError);
    let b = FailureSchedule::seeded_random(99, 0.3, FailureMode::IoError);
    let mut a_seq = Vec::new();
    let mut b_seq = Vec::new();
    for attempt in 1..=50 {
        a_seq.push(a.maybe_fail(attempt).is_err());
        b_seq.push(b.maybe_fail(attempt).is_err());
    }
    assert_eq!(a_seq, b_seq);
}

#[test]
fn recovery_pass() {
    let c = assert_recovered("op", 3, 3, true);
    assert!(matches!(c.verdict, dev_report::Verdict::Pass));
    assert!(c.has_tag("chaos"));
    assert!(c.has_tag("recovery"));
}

#[test]
fn recovery_fail_on_invalid_state() {
    let c = assert_recovered("op", 3, 3, false);
    assert!(matches!(c.verdict, dev_report::Verdict::Fail));
    assert!(c.has_tag("regression"));
}

#[test]
fn recovery_warn_on_fewer_failures() {
    let c = assert_recovered("op", 5, 2, true);
    assert!(matches!(c.verdict, dev_report::Verdict::Warn));
}

#[test]
fn recovery_check_carries_numeric_evidence() {
    let c = assert_recovered("op", 3, 3, true);
    let labels: Vec<&str> = c.evidence.iter().map(|e| e.label.as_str()).collect();
    assert!(labels.contains(&"expected_failures"));
    assert!(labels.contains(&"actual_failures"));
    assert!(labels.contains(&"final_state_ok"));
}

#[test]
fn chaos_reader_round_trip_with_failure() {
    let data: &[u8] = b"hello";
    let schedule = FailureSchedule::on_attempts(&[2], FailureMode::IoError);
    let mut r = ChaosReader::new(data, schedule);
    let mut buf = [0u8; 1];
    r.read_exact(&mut buf).unwrap();
    assert!(r.read_exact(&mut buf).is_err());
}

#[test]
fn chaos_writer_partial_write() {
    let sink: Vec<u8> = Vec::new();
    let schedule = FailureSchedule::on_attempts(&[1], FailureMode::PartialWrite);
    let mut w = ChaosWriter::new(sink, schedule);
    let _ = w.write(b"abcd");
    let inner = w.into_inner();
    assert_eq!(inner, b"a");
}

#[test]
fn latency_injector_constant_profile() {
    let inj = LatencyInjector::new(LatencyProfile::Constant(Duration::from_micros(2)));
    assert_eq!(inj.delay_for(1), Duration::from_micros(2));
    assert_eq!(inj.delay_for(50), Duration::from_micros(2));
}

#[test]
fn crash_point_truncates_at_offset() {
    let sink: Vec<u8> = Vec::new();
    let mut w = CrashPoint::after_byte(3).wrap(sink);
    let _ = w.write_all(b"abcde");
    let inner = w.into_inner();
    assert_eq!(inner, b"abc");
}

#[test]
fn chaos_producer_emits_report() {
    let producer = ChaosProducer::new(
        || vec![assert_recovered("a", 1, 1, true)],
        "my-crate",
        "0.1.0",
    );
    let report = producer.produce();
    assert_eq!(report.checks.len(), 1);
    assert_eq!(report.producer.as_deref(), Some("dev-chaos"));
}