use hunkpick::{emit, parser, select, validate};
use std::fs;
use std::path::{Path, PathBuf};
fn seed_dir(name: &str) -> Option<PathBuf> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("fuzz/seeds")
.join(name);
dir.is_dir().then_some(dir)
}
fn seeds(dir: &Path) -> Vec<(String, Vec<u8>)> {
let mut out: Vec<(String, Vec<u8>)> = fs::read_dir(dir)
.expect("reading the seed directory")
.map(|e| e.expect("a directory entry").path())
.filter(|p| p.is_file())
.map(|p| {
let name = p
.file_name()
.expect("a file name")
.to_string_lossy()
.into_owned();
(name, fs::read(&p).expect("reading a seed"))
})
.collect();
out.sort_by(|a, b| a.0.cmp(&b.0));
out
}
#[test]
fn every_shared_seed_parses_and_round_trips() {
let Some(dir) = seed_dir("diff") else { return };
let files = seeds(&dir);
assert!(!files.is_empty(), "fuzz/seeds/diff is empty");
for (name, bytes) in files {
let patch =
parser::parse(&bytes).unwrap_or_else(|e| panic!("seed {name} does not parse: {e}"));
assert_eq!(
emit::emit(&patch),
bytes,
"seed {name} does not come back out of emit unchanged"
);
}
}
#[test]
fn every_selector_seed_resolves_against_its_own_diff() {
let Some(dir) = seed_dir("selectors") else {
return;
};
let files = seeds(&dir);
assert!(!files.is_empty(), "fuzz/seeds/selectors is empty");
for (name, bytes) in files {
let nul = bytes
.iter()
.position(|&b| b == 0)
.unwrap_or_else(|| panic!("seed {name} carries no NUL separating diff from selectors"));
let (diff, rest) = (&bytes[..nul], &bytes[nul + 1..]);
let patch =
parser::parse(diff).unwrap_or_else(|e| panic!("seed {name} does not parse: {e}"));
validate::validate_input(&patch)
.unwrap_or_else(|e| panic!("seed {name} is not a diff the CLI would accept: {e}"));
let args: Vec<String> = rest
.split(|&b| b == b'\n')
.filter(|l| !l.is_empty())
.map(|l| {
String::from_utf8(l.to_vec())
.unwrap_or_else(|_| panic!("seed {name}: selectors are ASCII"))
})
.collect();
assert!(!args.is_empty(), "seed {name} names no selector");
let selectors = select::parse_selectors(&args)
.unwrap_or_else(|e| panic!("seed {name}: selectors {args:?} do not parse: {e}"));
let result = select::select(&patch, &selectors)
.unwrap_or_else(|e| panic!("seed {name}: selectors {args:?} do not resolve: {e}"));
validate::validate_internal(&result)
.unwrap_or_else(|e| panic!("seed {name}: the selection is not self-consistent: {e}"));
}
}
#[test]
fn the_seeds_reach_both_branches_of_id_resolution() {
let Some(dir) = seed_dir("selectors") else {
return;
};
let mut unique = 0usize;
let mut shared = 0usize;
for (_, bytes) in seeds(&dir) {
let Some(nul) = bytes.iter().position(|&b| b == 0) else {
continue;
};
let (diff, rest) = (&bytes[..nul], &bytes[nul + 1..]);
if !rest.starts_with(b"@") {
continue;
}
let patch = parser::parse(diff).expect("a seed parses");
let args: Vec<String> = rest
.split(|&b| b == b'\n')
.filter(|l| !l.is_empty())
.map(|l| String::from_utf8(l.to_vec()).expect("ASCII selectors"))
.collect();
let selectors = select::parse_selectors(&args).expect("a seed's selectors parse");
let result = select::select(&patch, &selectors).expect("a seed's selectors resolve");
let picked: usize = result
.files
.iter()
.map(|f| match &f.content {
hunkpick::model::FileContent::Text(hunks) => hunks.len(),
hunkpick::model::FileContent::Binary(_) => 1,
})
.sum();
if picked > 1 { shared += 1 } else { unique += 1 }
}
assert!(unique > 0, "no seed selects by an id naming one sub-hunk");
assert!(
shared > 0,
"no seed selects by an id shared between sub-hunks"
);
}