use falcon_mdf::{BlockMap, Mf4File};
use std::collections::HashSet;
use std::path::{Path, PathBuf};
fn reference_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("test_data")
.join("reference")
}
fn reference_files() -> Vec<PathBuf> {
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("mf4"))
{
out.push(path);
}
}
}
let mut files = Vec::new();
walk(&reference_dir(), &mut files);
files.sort();
files
}
fn skip_if_empty(files: &[PathBuf]) -> bool {
if files.is_empty() {
eprintln!("SKIP: no reference corpus under test_data/reference");
return true;
}
false
}
#[test]
fn the_block_map_of_every_reference_file_holds_the_invariants() {
let files = reference_files();
if skip_if_empty(&files) {
return;
}
for path in &files {
let file = Mf4File::open(path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let map = file.block_map();
blocks_are_sorted_and_disjoint(path, &map);
the_id_block_sits_where_the_format_puts_it(path, &map);
blocks_and_gaps_account_for_every_byte(path, &map);
every_link_resolves_or_is_warned_about(path, &map);
labels_and_counts_describe_the_blocks_they_belong_to(path, &map);
block_at_finds_a_block_exactly_at_its_start(path, &map);
}
eprintln!(
"block map invariants held across {} reference files",
files.len()
);
}
fn blocks_are_sorted_and_disjoint(path: &Path, map: &BlockMap) {
for pair in map.blocks.windows(2) {
let (a, b) = (&pair[0], &pair[1]);
assert!(
a.address < b.address,
"{}: block addresses not strictly ascending: {:#x} then {:#x}",
path.display(),
a.address,
b.address
);
assert!(
a.address + a.length <= b.address,
"{}: the block at {:#x} runs {} bytes, into the block at {:#x}",
path.display(),
a.address,
a.length,
b.address
);
}
}
fn the_id_block_sits_where_the_format_puts_it(path: &Path, map: &BlockMap) {
let first = map
.blocks
.first()
.unwrap_or_else(|| panic!("{}: the map lists no blocks at all", path.display()));
assert_eq!(
first.address,
0,
"{}: the first block sits at {:#x}, not 0",
path.display(),
first.address
);
assert_eq!(
first.length,
64,
"{}: the identification block at 0 is {} bytes, not 64",
path.display(),
first.length
);
assert!(
first.block_type == "MDF " || first.block_type == "UnFi",
"{}: the identification block at 0 reports {:?}, not \"MDF \" or \"UnFi\"",
path.display(),
first.block_type
);
assert!(
map.block_at(64).is_some(),
"{}: no block at 64, where the header block always sits",
path.display()
);
}
fn blocks_and_gaps_account_for_every_byte(path: &Path, map: &BlockMap) {
let gap_bytes: u64 = map.gaps.iter().map(|gap| gap.length).sum();
assert_eq!(
map.covered_bytes + gap_bytes,
map.file_size,
"{}: {} covered bytes + {} gap bytes do not make the {}-byte file",
path.display(),
map.covered_bytes,
gap_bytes,
map.file_size
);
let mut block = 0usize;
for gap in &map.gaps {
let gap_end = gap.address + gap.length;
while block < map.blocks.len()
&& map.blocks[block].address + map.blocks[block].length <= gap.address
{
block += 1;
}
if block < map.blocks.len() {
assert!(
map.blocks[block].address >= gap_end,
"{}: the gap at {:#x}..{:#x} overlaps the block at {:#x}",
path.display(),
gap.address,
gap_end,
map.blocks[block].address
);
}
}
}
fn every_link_resolves_or_is_warned_about(path: &Path, map: &BlockMap) {
for block in &map.blocks {
for (index, &link) in block.links.iter().enumerate() {
if link == 0 {
continue;
}
let resolves = map.block_at(link).is_some();
let warned = map
.warnings
.iter()
.any(|warning| warning.contains(&format!("{link:#x}")));
let label = block.link_labels.get(index).map_or("?", String::as_str);
assert!(
resolves || warned,
"{}: link {index} ({label}) of the block at {:#x} points to {link:#x}, \
which is neither a listed block nor a warning",
path.display(),
block.address
);
}
}
}
fn labels_and_counts_describe_the_blocks_they_belong_to(path: &Path, map: &BlockMap) {
for block in &map.blocks {
assert_eq!(
block.link_labels.len(),
block.links.len(),
"{}: the block at {:#x} has {} links but {} labels",
path.display(),
block.address,
block.links.len(),
block.link_labels.len()
);
}
let counts = map.type_counts();
let counted: usize = counts.iter().map(|(_, n)| *n).sum();
assert_eq!(
counted,
map.blocks.len(),
"{}: type_counts lists {counted} blocks, the map holds {}",
path.display(),
map.blocks.len()
);
for (block_type, count) in &counts {
let actual = map
.blocks
.iter()
.filter(|b| &b.block_type == block_type)
.count();
assert_eq!(
actual,
*count,
"{}: type_counts says {count} {block_type} blocks, the map holds {actual}",
path.display()
);
}
}
fn block_at_finds_a_block_exactly_at_its_start(path: &Path, map: &BlockMap) {
let starts: HashSet<u64> = map.blocks.iter().map(|b| b.address).collect();
let mut probes = Vec::new();
for block in &map.blocks {
probes.push(block.address);
probes.push(block.address + 1);
}
for gap in &map.gaps {
probes.push(gap.address);
probes.push(gap.address + gap.length - 1);
}
probes.push(map.file_size);
probes.push(map.file_size + 1);
for address in probes {
assert_eq!(
map.block_at(address).is_some(),
starts.contains(&address),
"{}: block_at({address:#x}) disagrees with the block list",
path.display()
);
}
}
#[test]
fn scanning_a_file_twice_gives_the_same_map() {
let files = reference_files();
if skip_if_empty(&files) {
return;
}
for path in &files {
let file = Mf4File::open(path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let first = file.block_map();
let second = file.block_map();
assert_eq!(
first.blocks.len(),
second.blocks.len(),
"{}: two scans found different block counts ({} vs {})",
path.display(),
first.blocks.len(),
second.blocks.len()
);
assert_eq!(
first.covered_bytes,
second.covered_bytes,
"{}: two scans covered different byte counts ({} vs {})",
path.display(),
first.covered_bytes,
second.covered_bytes
);
for (a, b) in first.blocks.iter().zip(&second.blocks) {
assert_eq!(
a.address,
b.address,
"{}: the two scans disagree at {:#x}",
path.display(),
a.address
);
}
}
}