#[cfg(test)]
mod atomic_fault;
pub mod clock;
pub(crate) mod corpus;
pub(crate) mod fault_model;
pub(crate) mod fork_hostile;
pub(crate) mod fork_recovery;
pub(crate) mod fs;
pub(crate) mod import_recovery;
pub(crate) mod invariants;
#[cfg(test)]
mod read_fault;
pub(crate) mod recovery;
pub(crate) mod recovery_matrix;
pub(crate) mod scheduler;
pub(crate) mod workload;
use std::sync::Arc;
pub use clock::SimClock;
pub(crate) use fault_model::InMemFaultFs;
pub(crate) use scheduler::SimScheduler;
pub(crate) fn seed_from_env(default: u64) -> u64 {
match std::env::var("BATPAK_SEED") {
Ok(raw) => raw.trim().parse::<u64>().unwrap_or(default),
Err(_) => default,
}
}
pub(crate) struct Sim {
pub(crate) seed: u64,
pub(crate) clock: Arc<SimClock>,
pub(crate) scheduler: Arc<SimScheduler>,
pub(crate) fs: Arc<InMemFaultFs>,
}
impl Sim {
pub(crate) fn new(seed: u64) -> Self {
Self {
seed,
clock: Arc::new(SimClock::new()),
scheduler: Arc::new(SimScheduler::new()),
fs: Arc::new(InMemFaultFs::new(seed ^ 0x5F5F_5F5F_5F5F_5F5F)),
}
}
pub(crate) fn run_workload(&self, steps: usize) -> Result<u64, String> {
workload::run(self, steps)
}
}
pub fn run_seeded_workload(seed: u64, steps: usize) -> Result<u64, String> {
Sim::new(seed).run_workload(steps)
}
pub fn replay_seed(default: u64) -> u64 {
seed_from_env(default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seed_from_env_falls_back_to_default_when_unset() {
let chosen = seed_from_env(42);
assert!(
chosen == 42 || std::env::var("BATPAK_SEED").is_ok(),
"PROPERTY: absent BATPAK_SEED yields the supplied default seed"
);
}
#[test]
fn same_seed_same_digest() {
let a = Sim::new(7)
.run_workload(64)
.expect("workload must hold invariants");
let b = Sim::new(7)
.run_workload(64)
.expect("workload must hold invariants");
assert_eq!(
a, b,
"PROPERTY: identical seeds must yield identical op-trace digests"
);
}
#[test]
fn different_seeds_diverge() {
let a = Sim::new(1)
.run_workload(64)
.expect("workload must hold invariants");
let b = Sim::new(2)
.run_workload(64)
.expect("workload must hold invariants");
assert_ne!(
a, b,
"PROPERTY: distinct seeds should (almost surely) diverge over 64 steps"
);
}
#[test]
fn run_seeded_workload_is_the_real_seed_dependent_digest() {
let d1 = run_seeded_workload(7, 64).expect("seed 7 must hold invariants");
let d1_again = run_seeded_workload(7, 64).expect("seed 7 replay must hold invariants");
let d2 = run_seeded_workload(9, 64).expect("seed 9 must hold invariants");
assert_eq!(
d1, d1_again,
"PROPERTY: run_seeded_workload must be deterministic for a fixed seed"
);
assert_ne!(
d1, d2,
"PROPERTY: run_seeded_workload must depend on the seed — a constant body \
(Ok(0)/Ok(1)) would collapse distinct seeds to the same digest"
);
}
}