Files
Sylpheed/crates/sylpheed-formats/tests/ui_surfaces_disc.rs
Fabian Hamm 64f3df7d72
All checks were successful
CI / Native — linux (pull_request) Successful in 33m40s
CI / WASM — Web (pull_request) Successful in 29m29s
CI / Formatting (pull_request) Successful in 1m1s
test(formats): make $SYLPHEED_DISC an actual control (#16 remedy 3)
Before this commit, `unset SYLPHEED_DISC` did not disable the disc-backed
suites on the machine that has the disc: every `disc_root()` fell back to a
hardcoded absolute path that exists on this box. The env var looked like a
control and was not one. Same for $SYLPHEED_RES3D and $SYLPHEED_ISO.

Replace the duplicated resolvers with one `tests/common/mod.rs`:

  - 17 local `disc_root()` definitions -> 1
  - 7 copies of the skip macro -> 1 (`skip_without_disc!` and siblings)
  - 16 hardcoded absolute paths -> 0 executable ones
    (3 of those were inline in `mesh_disc.rs`, in no resolver at all,
     and 2 were in `examples/`)
  - `corpus_report.rs` now reports on the SAME resolver the suites use,
    instead of a second copy of the logic its own comments flagged as a
    drift risk.

The 17 copies had already drifted into FIVE variants, and they were not all
the same function. `movie_manifest_disc`, `movie_subtitle_disc` and `slb_disc`
honoured $SYLPHEED_DISC and nothing else, while the other 14 fell back. So one
name already meant two things -- a third instance of the shape #16 is about.
The shared helper adopts the env-only behaviour those three already had, rather
than inventing a sixth variant.

Two module docs still described the fallback after it was deleted, which is the
same defect in prose: `texture_disc` claimed "or the default dev path exists"
and `pak_idxd_disc` said "or drop it at the default dev path below". Both now
say what the code does.

Verified both ways on the machine that HAS the corpus, which is the only place
this refactor can be falsified:

  A  env unset  -> "ABSENT -- $SYLPHEED_DISC unset; its suites self-skip"
                   suites=31 passed=209 failed=0 ignored=14, slowest 0.12s
  B  env set    -> "PRESENT via $SYLPHEED_DISC" (all three corpora)
                   suites=31 passed=209 failed=0 ignored=14,
                   slowest 1235.53s (mesh_consistency_disc)

Identical tallies, opposite corpus states, ~10000x apart in wall clock. (A) is
new behaviour -- it was previously unreachable here. (B) proves nothing broke.

`just test-disc` sources `.env` (already gitignored) for the set case. Note the
quoting trap documented there: the corpus paths contain spaces, and an unquoted
`VAR=a b c` parses as "run command `b`", failing silently into ABSENT -- which
looks exactly like a working skip.

Remedy (1) (`#[ignore]` + `--ignored`) is deliberately NOT done here: (3) already
moves the mode from the filesystem into the environment, and `#[ignore]` already
carries three meanings in this directory (corpus-absent, known-failing, bare).
Overloading it a fourth time would re-create the defect.

`cargo fmt --all --check` clean; no new compiler warnings.

Refs #16

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 20:47:23 +02:00

144 lines
4.8 KiB
Rust

//! Disc-wide guards for the 2D surface formats, locking in what was measured on
//! 2026-08-11. Skipped (as no-ops) when the extracted disc is absent.
//!
//! These assert *disc-wide invariants* rather than one hand-picked file, because
//! each of the findings they guard was originally missed by reasoning from a
//! sample: T8aD's "~15 % unsupported variants" were a wrong model, and LSTA's
//! "a few entries disagree with the count" was a miscount.
use std::path::{Path, PathBuf};
use sylpheed_formats::{lsta, pak::PakArchive, ratc, t8ad};
mod common;
use common::skip_without_disc;
/// Every entry of every pak, plus every RATC child, as raw bytes.
fn for_each_blob(root: &Path, mut f: impl FnMut(&str, &str, &[u8])) {
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
for p in &paks {
let pak_name = p.file_name().unwrap().to_string_lossy().to_string();
let Ok(arc) = PakArchive::open(p) else {
continue;
};
for e in arc.entries() {
let Ok(bytes) = arc.read(e) else { continue };
f(&pak_name, &format!("{:08x}", e.name_hash), &bytes);
if ratc::is_ratc(&bytes) {
if let Some(kids) = ratc::parse(&bytes) {
for k in &kids {
if k.offset + k.size <= bytes.len() {
f(&pak_name, &k.name, &bytes[k.offset..k.offset + k.size]);
}
}
}
}
}
}
}
/// A T8aD surface is a list of sub-rectangles, and on this disc **every** one
/// decodes. Regressing the rectangle model would show up here as a decode gap,
/// which is exactly how the old 256-grid reading looked (96 %, not 100 %).
#[test]
fn every_t8ad_on_the_disc_decodes() {
skip_without_disc!(root);
let (mut total, mut ok) = (0usize, 0usize);
let mut first_failure = None;
for_each_blob(&root, |pak, name, b| {
if !t8ad::is_t8ad(b) || b.len() < 0x40 {
return;
}
total += 1;
if t8ad::parse(b).is_some() {
ok += 1;
} else if first_failure.is_none() {
first_failure = Some(format!("{pak}:{name}"));
}
});
assert!(
total > 19_000,
"expected the disc's ~19 216 surfaces, saw {total}"
);
assert_eq!(ok, total, "first failure: {first_failure:?}");
}
/// An LSTA's header count is exact and counts **both** kinds of element: T8aD
/// sprites and `PRMD` primitives. (It was long read as unreliable because the
/// comparison ignored primitives.)
#[test]
fn lsta_count_equals_sprites_plus_primitives() {
skip_without_disc!(root);
let count_magic = |b: &[u8], magic: &[u8; 4]| {
let (mut n, mut i) = (0usize, 4usize);
while i + 4 <= b.len() {
if &b[i..i + 4] == magic {
n += 1;
i += 4;
} else {
i += 1;
}
}
n
};
let (mut lists, mut exact) = (0usize, 0usize);
let mut bad = Vec::new();
for_each_blob(&root, |pak, name, b| {
if !lsta::is_lsta(b) || b.len() < 8 {
return;
}
lists += 1;
let declared = u32::from_be_bytes([b[4], b[5], b[6], b[7]]) as usize;
let sprites = count_magic(b, b"T8aD");
let prims = count_magic(b, b"PRMD");
if declared == sprites + prims {
exact += 1;
} else if bad.len() < 4 {
bad.push(format!(
"{pak}:{name} declared {declared} != {sprites}+{prims}"
));
}
});
assert!(
lists >= 60,
"expected the disc's 64 LSTA lists, saw {lists}"
);
assert_eq!(exact, lists, "mismatches: {bad:?}");
}
/// Nested RATC records are **leaves**: they carry no child list of their own.
/// "One level deep" describes the data, not a parser limit.
#[test]
fn ratc_nesting_is_exactly_one_level() {
skip_without_disc!(root);
let mut grandchildren = 0usize;
let mut bundles = 0usize;
for_each_blob(&root, |_, _, b| {
if !ratc::is_ratc(b) {
return;
}
let Some(kids) = ratc::parse(b) else { return };
bundles += 1;
for k in &kids {
if k.offset + k.size > b.len() {
continue;
}
let sub = &b[k.offset..k.offset + k.size];
if ratc::is_ratc(sub) {
grandchildren += ratc::parse(sub).map(|g| g.len()).unwrap_or(0);
}
}
});
assert!(
bundles > 2_000,
"expected thousands of RATC bundles, saw {bundles}"
);
assert_eq!(grandchildren, 0, "a nested RATC record listed children");
}