Files
lightcraft/crates/raw/tests/sparse_corpus.rs
T
Chad ElliottandClaude Opus 5.5 3e42f2e05c feat(testkit): #1037 tests record why they skip
libtest has no skipped status and hides the output of passing tests, so a
test that returns early without a GPU, a corpus file, a craft-fonts face or
a model passed silently. `lightcraft-testkit` (the testkit slot the layering
table already reserves, dev-dependency only, std only) adds
`skip!(Need, "reason")`: it prints the note as before and, when
LIGHTCRAFT_SKIP_LOG names a file, appends one JSON line (need, reason, test,
binary, file:line). Plain `cargo test` is unchanged. The 61 existing skip
sites use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:33:54 -05:00

109 lines
5.3 KiB
Rust

//! [`lightcraft_raw::sparse::header_plan`] over `corpus/raw/**` (git-ignored CC0 samples from
//! raw.pixls.us, fetched with `cargo xtask corpus --download`; `LIGHTCRAFT_CORPUS` overrides the
//! corpus root). Skips cleanly when absent.
//!
//! For every file the plan reaches `Ready` on, probing the partly loaded buffer must give exactly
//! what probing the whole file gives — with the skipped bytes zero and again with them 0xFF, so a
//! reader that looked at skipped data would be caught. CR3 and DNG files must be described from a
//! small part of the file (`cargo test -p lightcraft-raw --release --test sparse_corpus -- --nocapture`
//! prints what each format reads).
use lightcraft_raw::sparse::{HeaderPlan, Ranges, header_plan, preview_plan};
use lightcraft_raw::{embedded_preview, probe, probe_info};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
fn corpus_root() -> PathBuf {
std::env::var_os("LIGHTCRAFT_CORPUS").map(PathBuf::from).unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
}
/// Run the plan (with the embedded preview when `preview`), loading from `file` what it asks for
/// into a buffer whose other bytes are `fill`.
fn run(file: &[u8], fill: u8, preview: bool) -> (HeaderPlan, Vec<u8>, Ranges) {
let mut buf = vec![fill; file.len()];
let mut loaded = Ranges::new();
let prefix = file.len().min(512 * 1024);
buf[..prefix].copy_from_slice(&file[..prefix]);
loaded.insert(0, prefix as u64);
for _ in 0..32 {
match if preview { preview_plan(&buf, &loaded) } else { header_plan(&buf, &loaded) } {
HeaderPlan::Need(r) => {
for &(a, b) in r.spans() {
assert!(!loaded.overlaps(a, b), "asked again for {a}..{b}");
buf[a as usize..b as usize].copy_from_slice(&file[a as usize..b as usize]);
loaded.insert(a, b);
}
}
done => return (done, buf, loaded),
}
}
panic!("no answer after 32 rounds");
}
/// What an import learns from a file: the raw description and the metadata.
fn describe(bytes: &[u8]) -> String {
let e = lightcraft_meta::embedded(bytes);
format!("{:?}\n{:?}\n{:?}", probe_info(bytes), lightcraft_meta::extract(bytes), e.xmp)
}
#[test]
fn partial_reads_describe_raws_as_whole_files_do() {
let dir = corpus_root().join("raw");
let mut paths: Vec<PathBuf> = std::fs::read_dir(corpus_root().join("leica"))
.into_iter()
.chain(std::fs::read_dir(&dir))
.flatten()
.flatten()
.map(|e| e.path())
.filter(|p| p.is_file() && !p.extension().is_some_and(|e| e == "part" || e == "txt" || e == "md"))
.collect();
if paths.is_empty() {
lightcraft_testkit::skip!(Corpus, "{} absent", dir.display());
return;
}
paths.sort();
// format → (files ready, bytes read, bytes total, files read whole)
let mut stats: BTreeMap<String, (usize, u64, u64, usize)> = BTreeMap::new();
for p in paths {
let name = p.file_name().unwrap().to_string_lossy().to_string();
let file = std::fs::read(&p).unwrap();
let format = probe(&file).map_or("?".to_string(), |f| format!("{f:?}"));
let (plan, zeros, loaded) = run(&file, 0, false);
let entry = stats.entry(format.clone()).or_default();
match plan {
HeaderPlan::Ready => {
let (plan_ff, ones, loaded_ff) = run(&file, 0xff, false);
assert_eq!((plan_ff, &loaded_ff), (HeaderPlan::Ready, &loaded), "{name}: the plan read skipped bytes");
let whole = describe(&file);
assert_eq!(describe(&zeros), whole, "{name}: differs from the whole file (skipped bytes 0)");
assert_eq!(describe(&ones), whole, "{name}: differs from the whole file (skipped bytes 0xFF)");
entry.0 += 1;
entry.1 += loaded.total();
entry.2 += file.len() as u64;
// with the preview (for a raw shown from it), the preview is what the whole file gives too
let preview = embedded_preview(&file);
for fill in [0, 0xff] {
let (plan, buf, _) = run(&file, fill, true);
assert_eq!(plan, HeaderPlan::Ready, "{name}: preview plan");
assert_eq!(describe(&buf), whole, "{name}: preview plan, skipped bytes {fill}");
assert!(embedded_preview(&buf) == preview, "{name}: a different embedded preview (skipped bytes {fill})");
}
if loaded.total() * 4 > file.len() as u64 {
eprintln!("{name}: read {:.0} %", loaded.total() as f64 * 100.0 / file.len() as f64);
}
if matches!(format.as_str(), "Cr3" | "Dng") && file.len() > 8 << 20 {
assert!(loaded.total() * 4 < file.len() as u64, "{name}: read {} of {} bytes", loaded.total(), file.len());
}
}
HeaderPlan::Whole(why) => {
entry.3 += 1;
eprintln!("{name}: whole ({why})");
}
HeaderPlan::Need(_) => unreachable!(),
}
}
for (format, (ready, read, total, whole)) in stats {
eprintln!("{format:>10}: {ready} described from {:.1} % of their bytes, {whole} read whole", read as f64 * 100.0 / total.max(1) as f64);
}
}