//! Replay every committed part-geometry input through the oracle, on stable. //! //! The soak tier runs on astra, on nightly, when the box is idle. That is the //! wrong place for the only copy of a property to live: a crash found there is //! fixed here, and nothing on a developer's machine would notice it coming //! back. //! //! So both corpora replay as an ordinary `cargo test`: //! //! - `fuzz/seeds/plan/` is human intent -- the boundaries where part arithmetic //! goes wrong, and the off-by-ones either side of each. //! - `fuzz/regressions/` is inputs that once found a bug. Anything landing //! there is a permanent test by virtue of the directory, with no test //! function to write and no chance of forgetting one. //! //! The assertions are `s3_storage::oracle::check_plan` and `check_auto`, the //! same functions the libFuzzer target calls, so the two cannot check different //! things. use std::path::Path; /// Decode one corpus file the way the fuzz target's `Arbitrary` impl does: a /// `(u64, u64)` tuple, eight little-endian bytes each. /// /// This mirrors `libfuzzer_sys`'s decoding rather than calling it, so that the /// replay does not need the fuzzing dependency on stable. The coupling is /// deliberate and narrow: if the target's input type ever changes, this is the /// one place that has to change with it, and a mismatch shows up as a replay /// that decodes nonsense rather than as a silent pass. fn decode(bytes: &[u8]) -> Option<(u64, u64)> { if bytes.len() < 16 { return None; } let total = u64::from_le_bytes(bytes[0..8].try_into().ok()?); let part = u64::from_le_bytes(bytes[8..16].try_into().ok()?); Some((total, part)) } /// Replay one directory, returning how many inputs it held. /// /// A missing directory is fine and returns zero: `fuzz/regressions/` holds only /// a README until the first crash. fn replay(dir: &Path) -> usize { let Ok(entries) = std::fs::read_dir(dir) else { return 0; }; let mut count = 0; for entry in entries { let path = entry.expect("readable dir entry").path(); if !path.is_file() { continue; } // READMEs are documentation, not inputs. if path.extension().is_some_and(|e| e == "md") { continue; } let bytes = std::fs::read(&path).expect("readable input"); let Some((total_size, raw_part)) = decode(&bytes) else { continue; }; // The same three calls the target makes, in the same order, so a crash // reproduces here unchanged. let outcome = std::panic::catch_unwind(|| { s3_storage::oracle::check_plan(total_size, raw_part as usize); const MIN_PART: u64 = 5 * 1024 * 1024; const MAX_PART: u64 = 5 * 1024 * 1024 * 1024; let shaped = MIN_PART + (raw_part % (MAX_PART - MIN_PART + 1)); s3_storage::oracle::check_plan(total_size, shaped as usize); s3_storage::oracle::check_auto(total_size); }); assert!( outcome.is_ok(), "oracle failed on {} (total_size {total_size}, part_size {raw_part})", path.display() ); count += 1; } count } #[test] fn plan_seeds_satisfy_the_oracle() { let n = replay( Path::new(env!("CARGO_MANIFEST_DIR")) .join("fuzz/seeds/plan") .as_path(), ); assert!(n > 0, "no plan seeds found; the corpus is the point"); println!("replayed {n} seeds"); } #[test] fn every_past_crash_stays_fixed() { // No assertion on the count. An empty regressions directory is the good // state, and a test demanding a crash to have happened would fail on a // healthy crate. let n = replay( Path::new(env!("CARGO_MANIFEST_DIR")) .join("fuzz/regressions") .as_path(), ); println!("replayed {n} regression inputs"); }