use super::{fuzz_corpus, mutate};
use crate::testing::{Executor, Harness};
use crate::{compile, unit};
use std::panic::{AssertUnwindSafe, catch_unwind};
const STATEMENT_LIMIT: u64 = 20_000;
#[test]
fn mutated_programs_run_alike_on_the_interpreter_and_the_vm() {
std::thread::Builder::new().stack_size(64 << 20).spawn(mutated_runs).unwrap().join().unwrap();
}
fn mutated_runs() {
let iterations: usize = std::env::var("IRONWORK_DIFFERENTIAL_ITERATIONS").ok().and_then(|v| v.parse().ok()).unwrap_or(2_000);
let corpus = fuzz_corpus();
let mut seed: u64 = std::env::var("IRONWORK_DIFFERENTIAL_SEED").ok().and_then(|v| v.parse().ok()).filter(|&s| s != 0).unwrap_or(0x2545_F491_4F6C_DD1D);
let mut next = move || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
let mut failures = Vec::new();
for i in 0..iterations {
let mutated = mutate(&corpus[i % corpus.len()], &mut next);
let compiles = catch_unwind(|| {
let main = syntax::parse_all_with(&mutated, &syntax::copy::Libraries::default()).ok().and_then(|p| p.into_iter().next());
main.is_some_and(|p| compile(p, &[]).is_ok())
});
if !compiles.unwrap_or(false) {
continue;
}
let path = std::env::temp_dir().join(format!("ironwork-differential-{i}.cbl"));
std::fs::write(&path, &mutated).unwrap();
let run = catch_unwind(AssertUnwindSafe(|| Harness::source(&mutated).statement_limit(STATEMENT_LIMIT).clock(unit::Clock::Fixed(1_790_510_400, 42)).run(Executor::Interpreter)));
match run {
Ok(_) => std::fs::remove_file(&path).unwrap(),
Err(panic) => {
let message = panic.downcast_ref::<String>().cloned().or_else(|| panic.downcast_ref::<&str>().map(|s| s.to_string())).unwrap_or_default();
failures.push(format!("{}: {}", path.display(), message.lines().take(3).collect::<Vec<_>>().join(" | ")));
}
}
}
assert!(failures.is_empty(), "{} of {iterations} mutated programs failed:\n{}", failures.len(), failures.join("\n"));
}