Files
craft-libs/crates/psd/tests/it/corpus_atn.rs
Dylan M. Taylor ee78a9bd46 craft-psd: Photoshop PSD/PSB reader and writer, moved from PhotoCraft (#875)
PhotoCraft's `photocraft-psd` crate (crates/psd at storytold/photocraft
63b641b55e54bf8f5c83efd977fdb16a2c255431) as `craft-psd`: the lossless
PSD/PSB model, reader, writer and builder, descriptors, the Photoshop
resource files that share its primitives (.abr, .asl, .aco, .act, .ase,
.grd, .pat, .atn, .csh, .shc) and the Photoshop blocks of a layered TIFF.
The crate had no dependency on any other PhotoCraft crate.

Code is unchanged apart from the crate name, craft-libs' rustfmt width,
comments that named other PhotoCraft crates, issue references (now
`photocraft#N`), and the corpus tests' directory (`CRAFT_PSD_CORPUS` or
`corpus/` at the workspace root). Tests, proptests, fuzz targets and
fixtures move with it.

Part of #778, #786.
2026-11-11 17:00:33 -05:00

61 lines
2.5 KiB
Rust

//! Corpus test (feature `corpus`): every `.atn` under
//! `corpus/atn/` (or `$CRAFT_PSD_CORPUS/atn/`; PhotoCraft's `cargo xtask corpus --atn` fetches it) parses, and writing it back
//! reproduces the bytes exactly. A missing corpus fails.
#![cfg(feature = "corpus")]
use std::path::{Path, PathBuf};
use craft_psd::atn;
fn collect(dir: &Path, out: &mut Vec<PathBuf>) {
// An unreadable folder fails the test rather than shrinking the corpus.
let rd = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("{}: {e}", dir.display()));
for e in rd {
let p = e.unwrap_or_else(|e| panic!("{}: {e}", dir.display())).path();
if p.is_dir() {
collect(&p, out);
} else if p.extension().and_then(|e| e.to_str()).is_some_and(|e| e.eq_ignore_ascii_case("atn")) {
out.push(p);
}
}
}
/// Sets in the corpus; raise, never lower.
const FLOOR: usize = 16;
#[test]
fn atn_corpus_parses_and_is_byte_stable() {
let root = crate::corpus_dir("atn");
assert!(root.is_dir(), "{} is missing: run `cargo xtask corpus --atn`", root.display());
let mut files = Vec::new();
collect(&root, &mut files);
files.sort();
let mut failures = Vec::new();
let (mut actions, mut steps) = (0usize, 0usize);
for p in &files {
let name = p.strip_prefix(&root).unwrap_or(p).display().to_string();
let bytes = std::fs::read(p).unwrap();
let file = match atn::parse(&bytes) {
Ok(f) => f,
Err(e) => {
failures.push(format!("{name}: {e}"));
continue;
}
};
if !file.warnings.is_empty() {
failures.push(format!("{name}: warnings {:?}", file.warnings));
}
actions += file.actions.len();
steps += file.actions.iter().map(|a| a.items.len()).sum::<usize>();
match atn::write(&file) {
Ok(written) if written == bytes => {}
Ok(written) => failures.push(format!("{name}: rewritten bytes differ ({} vs {} bytes)", written.len(), bytes.len())),
Err(e) => failures.push(format!("{name}: write failed: {e}")),
}
println!("{name}: {} actions, {} steps", file.actions.len(), file.actions.iter().map(|a| a.items.len()).sum::<usize>());
}
println!("{} files, {actions} actions, {steps} steps", files.len());
assert!(files.len() >= FLOOR, "only {} .atn files; the corpus floor is {FLOOR}", files.len());
assert!(failures.is_empty(), "{} failures:\n{}", failures.len(), failures.join("\n"));
}