fuzz/tests/seeds.rs
//! **The committed seeds stay valid**, on a plain stable `cargo test` (no libFuzzer, no
//! instrumentation): `cargo test --manifest-path fuzz/Cargo.toml --no-default-features`.
//!
//! Every file in `corpus/<target>/` is run through the target's entry function (a panic
//! fails the test), and every `ok-*` seed must be ACCEPTED: a seed that no longer verifies or
//! parses starts the fuzzer at the shallow end, which is a silent loss of coverage.
//!
//! `write_seeds` (ignored) regenerates the seeds from the throwaway keys in `src/lib.rs`:
//! `cargo test ... -- --ignored write_seeds`.
use std::path::{Path, PathBuf};
type Entry = fn(&[u8]) -> bool;
const TARGETS: &[(&str, Entry)] = &[
("pay_callback", monetize_fuzz::pay_callback),
("feed_config", monetize_fuzz::feed_config),
("signed_facts", monetize_fuzz::signed_facts),
("fleet_door", monetize_fuzz::fleet_door),
("fleet_orders", monetize_fuzz::fleet_orders),
];
fn corpus(target: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("corpus")
.join(target)
}
#[test]
fn every_target_has_a_bin_and_accepted_seeds() {
let manifest =
std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml")).unwrap();
let mut wrong = Vec::new();
for (target, entry) in TARGETS {
if !manifest.contains(&format!("path = \"fuzz_targets/{target}.rs\"")) {
wrong.push(format!("{target}: no [[bin]] in fuzz/Cargo.toml"));
}
let Ok(dir) = std::fs::read_dir(corpus(target)) else {
wrong.push(format!("{target}: no corpus/{target}/"));
continue;
};
let mut ok_seeds = 0;
for file in dir.flatten() {
let name = file.file_name().to_string_lossy().to_string();
let bytes = std::fs::read(file.path()).unwrap();
let accepted = entry(&bytes);
if name.starts_with("ok-") {
ok_seeds += 1;
if !accepted {
wrong.push(format!("{target}: seed {name} is not accepted"));
}
} else if name.starts_with("refused-") && accepted {
wrong.push(format!(
"{target}: seed {name} is accepted, but it is a refusal"
));
}
}
if ok_seeds == 0 {
wrong.push(format!(
"{target}: no ok-* seed, so nothing proves the parser's deep end is reached"
));
}
}
assert!(
wrong.is_empty(),
"the fuzz corpus is not what the targets need:\n {}",
wrong.join("\n ")
);
}
/// The committed seeds are the ones `src/lib.rs` makes: a seed edited by hand, or a key or
/// a wire format that moved without the seeds, fails here by name.
#[test]
fn the_committed_seeds_are_the_generated_ones() {
let mut wrong = Vec::new();
for (target, name, bytes) in monetize_fuzz::seeds() {
match std::fs::read(corpus(target).join(&name)) {
Ok(on_disk) if on_disk == bytes => {}
Ok(_) => wrong.push(format!(
"{target}/{name}: differs from seeds() (run write_seeds)"
)),
Err(_) => wrong.push(format!("{target}/{name}: missing (run write_seeds)")),
}
}
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
}
#[test]
#[ignore]
fn write_seeds() {
for (target, name, bytes) in monetize_fuzz::seeds() {
std::fs::create_dir_all(corpus(target)).unwrap();
std::fs::write(corpus(target).join(name), bytes).unwrap();
}
}