use autumn_web::sim::sweep::{SweepOutcome, sweep_proptest};
use autumn_web::{always, sometimes};
use proptest::prelude::*;
const DEFAULT_SEED_COUNT: u64 = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Op {
Deposit(u32),
Withdraw(u32),
}
impl Arbitrary for Op {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): ()) -> Self::Strategy {
prop_oneof![
(1u32..100).prop_map(Op::Deposit),
(1u32..100).prop_map(Op::Withdraw),
]
.boxed()
}
}
fn apply_ops(ops: &[Op]) {
let mut balance: i64 = 0;
for op in ops {
match *op {
Op::Deposit(amount) => balance += i64::from(amount),
Op::Withdraw(amount) => balance -= i64::from(amount).min(balance),
}
always!(balance >= 0, "balance went negative: {balance}");
sometimes!(balance == 0, "balance-returned-to-zero");
}
}
fn seed_count() -> u64 {
std::env::var("AUTUMN_SIM_SEEDS")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(DEFAULT_SEED_COUNT)
}
fn replay_command(seed: u64) -> String {
format!(
" replay: AUTUMN_SIM_SEEDS={} cargo run -p autumn-web --release --features sim-testing --bin sim-sweep",
seed.wrapping_add(1),
)
}
fn main() {
let count = seed_count();
let strategy = proptest::collection::vec(any::<Op>(), 1..32);
println!("sim-sweep: sweeping {count} seed(s) (0..{count}) against the account demo scenario");
match sweep_proptest(0..count, &strategy, |_sim, ops| apply_ops(ops)) {
SweepOutcome::Passed { seeds_run } => {
println!("sim-sweep: PASSED — {seeds_run} seed(s), non-vacuous");
}
SweepOutcome::Failed { seeds_run, failure } => {
eprintln!("sim-sweep: FAILED after {seeds_run} seed(s)");
eprintln!("{failure}");
eprintln!("{}", replay_command(failure.seed));
std::process::exit(1);
}
SweepOutcome::Vacuous {
seeds_run,
unsatisfied,
} => {
eprintln!(
"sim-sweep: VACUOUS — {seeds_run} seed(s) all passed, but sometimes! label(s) \
were observed and never satisfied across the whole sweep: {}",
unsatisfied.into_iter().collect::<Vec<_>>().join(", ")
);
std::process::exit(1);
}
SweepOutcome::Empty => {
eprintln!(
"sim-sweep: EMPTY — AUTUMN_SIM_SEEDS={count} swept zero seeds; nothing was tested"
);
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replay_command_emits_a_seed_count_that_covers_the_failing_seed_and_parses_as_plain_decimal()
{
let command = replay_command(300);
let count_str = command
.split("AUTUMN_SIM_SEEDS=")
.nth(1)
.and_then(|rest| rest.split_whitespace().next())
.expect("replay command must carry a seed count");
let count: u64 = count_str.parse().unwrap_or_else(|err| {
panic!("replay count {count_str:?} must parse as plain decimal u64: {err}")
});
assert!(
count > 300,
"replay count {count} must exceed the failing seed 300 so re-sweeping 0..{count} reaches it"
);
}
}