Merge pull request 'test: make $SYLPHEED_DISC an actual control, not a decoration (#16 remedy 3)' (#22) from fix/corpus-control into main
Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
@@ -25,8 +25,12 @@ fn is_value(s: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC")
|
// No hardcoded fallback: it made SYLPHEED_DISC look like a control while
|
||||||
.unwrap_or_else(|_| "/home/fabi/RE - Project Sylpheed/sylph_extract".into());
|
// one machine's directory layout decided the outcome (#16).
|
||||||
|
let Ok(root) = std::env::var("SYLPHEED_DISC") else {
|
||||||
|
eprintln!("set SYLPHEED_DISC to the extracted disc root");
|
||||||
|
std::process::exit(2);
|
||||||
|
};
|
||||||
let wanted: Vec<String> = std::env::args().skip(1).collect();
|
let wanted: Vec<String> = std::env::args().skip(1).collect();
|
||||||
let arc = PakArchive::open(std::path::Path::new(&root).join("dat/GP_MAIN_GAME_E.pak")).unwrap();
|
let arc = PakArchive::open(std::path::Path::new(&root).join("dat/GP_MAIN_GAME_E.pak")).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,14 @@ fn is_value(s: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
// argv[1], else SYLPHEED_DISC. No hardcoded fallback -- see #16.
|
||||||
let root = std::env::args()
|
let root = std::env::args()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.unwrap_or_else(|| "/home/fabi/RE - Project Sylpheed/sylph_extract".into());
|
.or_else(|| std::env::var("SYLPHEED_DISC").ok())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
eprintln!("usage: defaulted_fields <disc-root> [paks...] (or set SYLPHEED_DISC)");
|
||||||
|
std::process::exit(2);
|
||||||
|
});
|
||||||
let paks: Vec<String> = std::env::args().skip(2).collect();
|
let paks: Vec<String> = std::env::args().skip(2).collect();
|
||||||
let paks = if paks.is_empty() {
|
let paks = if paks.is_empty() {
|
||||||
vec![
|
vec![
|
||||||
|
|||||||
@@ -4,31 +4,11 @@
|
|||||||
//! `build_caption_text` generalises the key parser to all eight.
|
//! `build_caption_text` generalises the key parser to all eight.
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use sylpheed_formats::{movie_subtitle, PakArchive};
|
use sylpheed_formats::{movie_subtitle, PakArchive};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::skip_without_disc;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let d = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
d.join("dat").is_dir().then(|| d.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: set SYLPHEED_DISC");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_eight_caption_families_are_read() {
|
fn all_eight_caption_families_are_read() {
|
||||||
|
|||||||
79
crates/sylpheed-formats/tests/common/mod.rs
Normal file
79
crates/sylpheed-formats/tests/common/mod.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
//! One place that decides where the disc corpora are — issue #16, remedy (3).
|
||||||
|
//!
|
||||||
|
//! # What this replaces
|
||||||
|
//!
|
||||||
|
//! Seventeen files under `tests/` each defined their own `disc_root()`, and they
|
||||||
|
//! had **already drifted into five variants**. Four were the same thing written
|
||||||
|
//! four ways (differing only in return type and style). The fifth —
|
||||||
|
//! `movie_manifest_disc`, `movie_subtitle_disc`, `slb_disc` — did something
|
||||||
|
//! materially different: it honoured `SYLPHEED_DISC` **and nothing else**.
|
||||||
|
//!
|
||||||
|
//! So one function name meant two different things in one directory, which is
|
||||||
|
//! the same "one name, several meanings" defect #16 identifies in
|
||||||
|
//! `SYLPHEED_DISC` itself and in `#[ignore]`.
|
||||||
|
//!
|
||||||
|
//! # Why the env var, and no fallback
|
||||||
|
//!
|
||||||
|
//! The fourteen copies with a fallback hardcoded one machine's absolute layout:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! /home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! That made `unset SYLPHEED_DISC` a no-op there: whether the disc suites ran
|
||||||
|
//! was a property of *the machine's directory layout*, invisible in the command
|
||||||
|
//! and in the output. The env var looked like a control and was not one.
|
||||||
|
//!
|
||||||
|
//! This module adopts the behaviour three of those files already had, rather
|
||||||
|
//! than inventing a new one: **the environment decides, always.** Point
|
||||||
|
//! `SYLPHEED_DISC` at the extracted disc and the suites run; leave it unset and
|
||||||
|
//! they skip. Same command, same answer, on every machine.
|
||||||
|
//!
|
||||||
|
//! `just test-disc` reads `.env` (already gitignored as a local dev override)
|
||||||
|
//! so no absolute path has to live in the source tree again.
|
||||||
|
//!
|
||||||
|
//! # This does not fix the tally
|
||||||
|
//!
|
||||||
|
//! A skipped suite still counts as `passed` — `#[ignore]` is static and cannot
|
||||||
|
//! move at runtime. That is why `tests/corpus_report.rs` exists: it prints which
|
||||||
|
//! corpora resolved, and it is the thing to read. This module only makes the
|
||||||
|
//! *control* honest, so that report can now say `PRESENT via $SYLPHEED_DISC`
|
||||||
|
//! and mean it.
|
||||||
|
// `tests/common/mod.rs` is compiled into EVERY integration-test binary, and each
|
||||||
|
// one uses only the resolver (and maybe the macro) it needs. Without these, every
|
||||||
|
// binary warns about the parts it did not use.
|
||||||
|
#![allow(dead_code, unused_macros, unused_imports)]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// The extracted disc root — the directory containing `dat/`.
|
||||||
|
pub fn disc_root() -> Option<PathBuf> {
|
||||||
|
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
|
||||||
|
p.join("dat").is_dir().then_some(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The extracted `resource3d` directory (`Stage_SNN.xpr` models).
|
||||||
|
pub fn res3d_dir() -> Option<PathBuf> {
|
||||||
|
let p = PathBuf::from(std::env::var("SYLPHEED_RES3D").ok()?);
|
||||||
|
p.is_dir().then_some(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The retail ISO image itself, not a directory.
|
||||||
|
pub fn iso_path() -> Option<PathBuf> {
|
||||||
|
let p = PathBuf::from(std::env::var("SYLPHEED_ISO").ok()?);
|
||||||
|
p.is_file().then_some(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind the disc root or return from the test.
|
||||||
|
///
|
||||||
|
/// The early return keeps the test *passing*, which is why the tally cannot
|
||||||
|
/// distinguish a skip from a real run — see `corpus_report.rs`.
|
||||||
|
macro_rules! skip_without_disc {
|
||||||
|
($root:ident) => {
|
||||||
|
let Some($root) = crate::common::disc_root() else {
|
||||||
|
eprintln!("SKIP: set SYLPHEED_DISC to the extracted disc root");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use skip_without_disc;
|
||||||
@@ -3,94 +3,62 @@
|
|||||||
//! # Why this exists
|
//! # Why this exists
|
||||||
//!
|
//!
|
||||||
//! `cargo test --workspace` reports **the same tally whether or not the disc
|
//! `cargo test --workspace` reports **the same tally whether or not the disc
|
||||||
//! corpus was exercised** — see issue #16. Measured: the disc suites ran on a
|
//! corpus was exercised** — issue #16. Measured on `main` @ `4ca0b8e`: this
|
||||||
//! developer desktop (1 936 s, `mesh_consistency_disc` alone 1 220 s) and
|
//! desktop ran the disc suites (`mesh_consistency_disc` alone **1 215 s**) and
|
||||||
//! skipped on CI (2.4 s total), and *both* reported `207 passed / 0 failed /
|
//! CI skipped them (2.4 s total), and *both* reported
|
||||||
//! 14 ignored` across 30 suites.
|
//! `209 passed / 0 failed / 14 ignored` across 31 suites.
|
||||||
//!
|
//!
|
||||||
//! Two mechanisms compound, and either alone would be survivable:
|
//! Two mechanisms compound:
|
||||||
//!
|
//!
|
||||||
//! 1. **A skip is a passing test.** The disc suites do `eprintln!("SKIP: …")`
|
//! 1. **A skip is a passing test.** The gated suites print `SKIP:` and return
|
||||||
//! and return early from a test that still passes, so a skipped suite and a
|
//! early from a test that still passes, so a skipped suite and a fully
|
||||||
//! fully exercised one both score `1 passed`. The totals are invariant.
|
//! exercised one both score `1 passed`.
|
||||||
//! 2. **The message is invisible.** `cargo test` captures a *passing* test's
|
//! 2. **The message is invisible.** `cargo test` captures a passing test's
|
||||||
//! output, so neither log contains a `SKIP:` line. The absence of one proves
|
//! output, so neither log contains a `SKIP:` line.
|
||||||
//! nothing, which makes the obvious check useless too.
|
|
||||||
//!
|
//!
|
||||||
//! And `14 ignored` cannot help: `#[ignore]` is static, so that column is the
|
//! And `14 ignored` cannot help: `#[ignore]` is static, so that column is the
|
||||||
//! literal count of attributes in the source and cannot move at runtime.
|
//! literal count of attributes in the source and cannot move at runtime.
|
||||||
//!
|
//!
|
||||||
//! This test is the fix for the *report*, not for the control. It always runs,
|
//! This test fixes the *report*. `tests/common/mod.rs` fixes the *control*.
|
||||||
//! never fails, and records what was actually available — to a file, because a
|
|
||||||
//! passing test's stdout is captured and would be invisible in exactly the CI
|
|
||||||
//! log that needs it.
|
|
||||||
|
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::io::Write as _;
|
use std::io::Write as _;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Resolution mirrors the per-suite helpers exactly. If one of those changes,
|
mod common;
|
||||||
/// this drifts — which is itself an argument for the shared helper in #16's
|
|
||||||
/// remedy (3).
|
|
||||||
fn resolve(
|
|
||||||
env: &str,
|
|
||||||
fallback: &str,
|
|
||||||
want_dir_child: Option<&str>,
|
|
||||||
want_file: bool,
|
|
||||||
) -> (String, Option<PathBuf>) {
|
|
||||||
let ok = |p: &Path| -> bool {
|
|
||||||
match (want_dir_child, want_file) {
|
|
||||||
(Some(child), _) => p.join(child).is_dir(),
|
|
||||||
(None, true) => p.is_file(),
|
|
||||||
(None, false) => p.is_dir(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Ok(v) = std::env::var(env) {
|
|
||||||
let p = PathBuf::from(&v);
|
|
||||||
if ok(&p) {
|
|
||||||
return (format!("PRESENT via ${env}"), Some(p));
|
|
||||||
}
|
|
||||||
return (format!("${env} is set but does not resolve: {v}"), None);
|
|
||||||
}
|
|
||||||
let p = PathBuf::from(fallback);
|
|
||||||
if ok(&p) {
|
|
||||||
// The important case. ${env} is unset, yet the corpus resolved anyway,
|
|
||||||
// so the suites run because of this machine's directory layout — a
|
|
||||||
// property invisible in the command and in the output.
|
|
||||||
return (
|
|
||||||
format!("PRESENT via the HARDCODED fallback, NOT ${env}"),
|
|
||||||
Some(p),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
(
|
|
||||||
"ABSENT — the gated suites will self-skip and still count as passed".into(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn target_dir() -> Option<PathBuf> {
|
/// Describe one corpus. Resolution is delegated to `common`, so this cannot
|
||||||
let exe = std::env::current_exe().ok()?;
|
/// drift from what the suites themselves do — the previous version duplicated
|
||||||
exe.ancestors()
|
/// the resolution logic and said so in its own comments.
|
||||||
.find(|a| a.file_name().is_some_and(|n| n == "target"))
|
fn status(var: &str, resolved: Option<PathBuf>) -> (String, Option<PathBuf>) {
|
||||||
.map(PathBuf::from)
|
match (std::env::var(var), resolved) {
|
||||||
|
(Ok(_), Some(p)) => (format!("PRESENT via ${var}"), Some(p)),
|
||||||
|
// Set but unusable is NOT the same as absent, and wants a different
|
||||||
|
// fix: a typo or a moved directory rather than a machine without the
|
||||||
|
// corpus. Reporting them alike would send someone hunting the wrong one.
|
||||||
|
(Ok(v), None) => (format!("${var} is set but does not resolve: {v}"), None),
|
||||||
|
(Err(_), _) => (
|
||||||
|
format!("ABSENT — ${var} unset; its suites self-skip and still count as passed"),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn corpus_report() {
|
fn corpus_report() {
|
||||||
let corpora = [
|
let rows = [
|
||||||
("SYLPHEED_DISC", "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)", Some("dat"), false),
|
status("SYLPHEED_DISC", common::disc_root()),
|
||||||
("SYLPHEED_RES3D", "/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d", None, false),
|
status("SYLPHEED_RES3D", common::res3d_dir()),
|
||||||
("SYLPHEED_ISO", "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso", None, true),
|
status("SYLPHEED_ISO", common::iso_path()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut out = String::from("test corpus report (issue #16)\n");
|
let mut out = String::from("test corpus report (issue #16)\n");
|
||||||
let mut any = false;
|
let mut any = false;
|
||||||
for (env, fallback, child, file) in corpora {
|
for (text, path) in &rows {
|
||||||
let (status, path) = resolve(env, fallback, child, file);
|
|
||||||
any |= path.is_some();
|
any |= path.is_some();
|
||||||
let _ = writeln!(out, " {env:<15} {status}");
|
let _ = writeln!(out, " {text}");
|
||||||
if let Some(p) = path {
|
if let Some(p) = path {
|
||||||
let _ = writeln!(out, " {:<15} -> {}", "", p.display());
|
let _ = writeln!(out, " -> {}", p.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
@@ -107,15 +75,14 @@ fn corpus_report() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Captured for a passing test, so it is only visible with --show-output or
|
// Captured for a passing test, so visible only with --show-output. Kept
|
||||||
// --nocapture. Kept anyway: it is the natural place to look locally.
|
// because it is the natural place to look locally.
|
||||||
println!("{out}");
|
println!("{out}");
|
||||||
|
|
||||||
// The channel that survives capture, and the one CI reads.
|
// The channel that survives capture, and the one CI reads.
|
||||||
if let Some(dir) = target_dir() {
|
if let Some(dir) = target_dir() {
|
||||||
let _ = std::fs::write(dir.join("sylpheed-corpus-report.txt"), &out);
|
let _ = std::fs::write(dir.join("sylpheed-corpus-report.txt"), &out);
|
||||||
}
|
}
|
||||||
// Gitea and GitHub both honour this; it puts the block in the run summary.
|
|
||||||
if let Ok(p) = std::env::var("GITHUB_STEP_SUMMARY") {
|
if let Ok(p) = std::env::var("GITHUB_STEP_SUMMARY") {
|
||||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
if let Ok(mut f) = std::fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
@@ -127,31 +94,30 @@ fn corpus_report() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ABSENT branch is the one CI takes, and it cannot be reached on a machine
|
fn target_dir() -> Option<PathBuf> {
|
||||||
/// that has the corpora — so it is exercised directly here rather than shipped
|
let exe = std::env::current_exe().ok()?;
|
||||||
/// unrun. Same for the "set but does not resolve" branch, which is what a typo
|
exe.ancestors()
|
||||||
/// in the env var produces.
|
.find(|a| a.file_name().is_some_and(|n| n == "target"))
|
||||||
#[test]
|
.map(PathBuf::from)
|
||||||
fn resolve_renders_every_branch() {
|
}
|
||||||
let missing = "/nonexistent/sylpheed/corpus";
|
|
||||||
|
/// The three states must render distinctly, and CI only ever exercises one of
|
||||||
// env unset + fallback missing -> ABSENT
|
/// them — so they are driven directly here rather than shipped unrun.
|
||||||
std::env::remove_var("SYLPHEED_TEST_PROBE");
|
#[test]
|
||||||
let (status, path) = resolve("SYLPHEED_TEST_PROBE", missing, None, false);
|
fn status_renders_every_state() {
|
||||||
assert!(status.starts_with("ABSENT"), "{status}");
|
let probe = "SYLPHEED_TEST_PROBE";
|
||||||
assert!(path.is_none());
|
std::env::remove_var(probe);
|
||||||
|
let (text, path) = status(probe, None);
|
||||||
// env set to something that does not resolve -> named as such, NOT absent,
|
assert!(text.starts_with("ABSENT"), "{text}");
|
||||||
// because those two states want different fixes.
|
assert!(path.is_none());
|
||||||
std::env::set_var("SYLPHEED_TEST_PROBE", missing);
|
|
||||||
let (status, path) = resolve("SYLPHEED_TEST_PROBE", missing, None, false);
|
std::env::set_var(probe, "/nonexistent/sylpheed/corpus");
|
||||||
assert!(status.contains("is set but does not resolve"), "{status}");
|
let (text, _) = status(probe, None);
|
||||||
assert!(path.is_none());
|
assert!(text.contains("is set but does not resolve"), "{text}");
|
||||||
|
|
||||||
// env set and valid -> attributed to the env var, not the fallback
|
std::env::set_var(probe, env!("CARGO_MANIFEST_DIR"));
|
||||||
std::env::set_var("SYLPHEED_TEST_PROBE", env!("CARGO_MANIFEST_DIR"));
|
let (text, path) = status(probe, Some(PathBuf::from(env!("CARGO_MANIFEST_DIR"))));
|
||||||
let (status, path) = resolve("SYLPHEED_TEST_PROBE", missing, None, false);
|
assert_eq!(text, format!("PRESENT via ${probe}"));
|
||||||
assert_eq!(status, "PRESENT via $SYLPHEED_TEST_PROBE");
|
assert!(path.is_some());
|
||||||
assert!(path.is_some());
|
std::env::remove_var(probe);
|
||||||
std::env::remove_var("SYLPHEED_TEST_PROBE");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,27 +13,8 @@ use std::path::{Path, PathBuf};
|
|||||||
use sylpheed_formats::hash::tag_hash;
|
use sylpheed_formats::hash::tag_hash;
|
||||||
use sylpheed_formats::{IdxdObject, PakArchive};
|
use sylpheed_formats::{IdxdObject, PakArchive};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::skip_without_disc;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
default.join("dat").is_dir().then(|| default.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every `.pak` on the disc, recursively.
|
/// Every `.pak` on the disc, recursively.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -8,27 +8,8 @@ use std::path::{Path, PathBuf};
|
|||||||
use sylpheed_formats::hash::ixud_hash_str;
|
use sylpheed_formats::hash::ixud_hash_str;
|
||||||
use sylpheed_formats::{IxudObject, PakArchive};
|
use sylpheed_formats::{IxudObject, PakArchive};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::skip_without_disc;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let d = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
d.join("dat").is_dir().then(|| d.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: set SYLPHEED_DISC");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn all_paks(root: &Path) -> Vec<PathBuf> {
|
fn all_paks(root: &Path) -> Vec<PathBuf> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
|
|||||||
@@ -11,25 +11,12 @@
|
|||||||
//! rather than as a snapshot of the bug.
|
//! rather than as a snapshot of the bug.
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use sylpheed_formats::mesh::Xbg7Model;
|
use sylpheed_formats::mesh::Xbg7Model;
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rounded (w, h, d) of a model's own geometry.
|
/// Rounded (w, h, d) of a model's own geometry.
|
||||||
fn span(m: &Xbg7Model) -> Option<[i64; 3]> {
|
fn span(m: &Xbg7Model) -> Option<[i64; 3]> {
|
||||||
|
|||||||
@@ -9,16 +9,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use sylpheed_formats::mesh::{material_groups, node_transforms, submesh_albedos, Xbg7Model};
|
use sylpheed_formats::mesh::{material_groups, node_transforms, submesh_albedos, Xbg7Model};
|
||||||
|
|
||||||
fn res3d_dir() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_RES3D") {
|
use common::res3d_dir;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = PathBuf::from("/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d");
|
|
||||||
default.is_dir().then_some(default)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "requires extracted disc models — set SYLPHEED_RES3D"]
|
#[ignore = "requires extracted disc models — set SYLPHEED_RES3D"]
|
||||||
@@ -378,10 +370,11 @@ fn hero_ship_grouped_pool_decodes() {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn stage_models_decode() {
|
fn stage_models_decode() {
|
||||||
use sylpheed_formats::mesh::Xbg7Model;
|
use sylpheed_formats::mesh::Xbg7Model;
|
||||||
let dir = std::env::var("SYLPHEED_RES3D").unwrap_or_else(|_| {
|
let Some(dir) = res3d_dir() else {
|
||||||
"/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
eprintln!("SKIP: set SYLPHEED_RES3D to the extracted resource3d directory");
|
||||||
});
|
return;
|
||||||
let path = format!("{dir}/Stage_S10.xpr");
|
};
|
||||||
|
let path = format!("{}/Stage_S10.xpr", dir.display());
|
||||||
let bytes = std::fs::read(&path).expect("read Stage_S10");
|
let bytes = std::fs::read(&path).expect("read Stage_S10");
|
||||||
let models = Xbg7Model::stage_models(&bytes);
|
let models = Xbg7Model::stage_models(&bytes);
|
||||||
for m in &models {
|
for m in &models {
|
||||||
@@ -423,9 +416,10 @@ fn stage_models_decode() {
|
|||||||
fn stage_models_sweep() {
|
fn stage_models_sweep() {
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use sylpheed_formats::mesh::Xbg7Model;
|
use sylpheed_formats::mesh::Xbg7Model;
|
||||||
let dir = std::env::var("SYLPHEED_RES3D").unwrap_or_else(|_| {
|
let Some(dir) = res3d_dir() else {
|
||||||
"/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
eprintln!("SKIP: set SYLPHEED_RES3D to the extracted resource3d directory");
|
||||||
});
|
return;
|
||||||
|
};
|
||||||
let mut names: Vec<_> = std::fs::read_dir(&dir)
|
let mut names: Vec<_> = std::fs::read_dir(&dir)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filter_map(|e| e.ok().map(|e| e.file_name().into_string().unwrap()))
|
.filter_map(|e| e.ok().map(|e| e.file_name().into_string().unwrap()))
|
||||||
@@ -434,7 +428,7 @@ fn stage_models_sweep() {
|
|||||||
names.sort();
|
names.sort();
|
||||||
let mut tot = 0usize;
|
let mut tot = 0usize;
|
||||||
for n in &names {
|
for n in &names {
|
||||||
let bytes = std::fs::read(format!("{dir}/{n}")).unwrap();
|
let bytes = std::fs::read(format!("{}/{n}", dir.display())).unwrap();
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let models = Xbg7Model::stage_models(&bytes);
|
let models = Xbg7Model::stage_models(&bytes);
|
||||||
let dt = t0.elapsed().as_millis();
|
let dt = t0.elapsed().as_millis();
|
||||||
@@ -455,10 +449,11 @@ fn stage_models_sweep() {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn stage_models_quality_audit() {
|
fn stage_models_quality_audit() {
|
||||||
use sylpheed_formats::mesh::Xbg7Model;
|
use sylpheed_formats::mesh::Xbg7Model;
|
||||||
let dir = std::env::var("SYLPHEED_RES3D").unwrap_or_else(|_| {
|
let Some(dir) = res3d_dir() else {
|
||||||
"/home/fabi/RE - Project Sylpheed/sylph_extract/hidden/resource3d".to_string()
|
eprintln!("SKIP: set SYLPHEED_RES3D to the extracted resource3d directory");
|
||||||
});
|
return;
|
||||||
let bytes = std::fs::read(format!("{dir}/Stage_S07.xpr")).unwrap();
|
};
|
||||||
|
let bytes = std::fs::read(format!("{}/Stage_S07.xpr", dir.display())).unwrap();
|
||||||
let models = Xbg7Model::stage_models(&bytes);
|
let models = Xbg7Model::stage_models(&bytes);
|
||||||
let (mut small, mut mid, mut huge, mut dupnames) = (0, 0, 0, 0);
|
let (mut small, mut mid, mut huge, mut dupnames) = (0, 0, 0, 0);
|
||||||
let mut seen = std::collections::HashSet::new();
|
let mut seen = std::collections::HashSet::new();
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ use sylpheed_formats::movie_manifest;
|
|||||||
use sylpheed_formats::slb::VoiceLang;
|
use sylpheed_formats::slb::VoiceLang;
|
||||||
use sylpheed_formats::PakArchive;
|
use sylpheed_formats::PakArchive;
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
|
use common::disc_root;
|
||||||
p.join("dat").is_dir().then_some(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the manifest + `eng\sounds.tbl` out of `tables.pak`.
|
/// Read the manifest + `eng\sounds.tbl` out of `tables.pak`.
|
||||||
fn load_manifest_and_sounds(root: &PathBuf) -> (Vec<u8>, Vec<u8>) {
|
fn load_manifest_and_sounds(root: &PathBuf) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
//! Real-disc test for the movie subtitle chain. Skipped when the extracted disc
|
//! Real-disc test for the movie subtitle chain. Skipped when the extracted disc
|
||||||
//! is absent (set `SYLPHEED_DISC` to the extract root to enable).
|
//! is absent (set `SYLPHEED_DISC` to the extract root to enable).
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use sylpheed_formats::movie_subtitle::{self, SubLang};
|
use sylpheed_formats::movie_subtitle::{self, SubLang};
|
||||||
use sylpheed_formats::PakArchive;
|
use sylpheed_formats::PakArchive;
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
|
use common::disc_root;
|
||||||
p.join("dat").is_dir().then_some(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolves_english_radio_subtitles() {
|
fn resolves_english_radio_subtitles() {
|
||||||
|
|||||||
@@ -1,40 +1,16 @@
|
|||||||
//! Integration tests against the real extracted Project Sylpheed disc.
|
//! Integration tests against the real extracted Project Sylpheed disc.
|
||||||
//!
|
//!
|
||||||
//! These are **skipped** (pass as no-ops) when the extracted disc is not present,
|
//! These are **skipped** (pass as no-ops) when the extracted disc is not present,
|
||||||
//! so the suite still runs on machines/CI without the game. Point at the disc via
|
//! so the suite still runs on machines/CI without the game. Point at the disc
|
||||||
//! the `SYLPHEED_DISC` env var, or drop it at the default dev path below.
|
//! with the `SYLPHEED_DISC` env var; there is no path fallback (see #16).
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use sylpheed_formats::texture::X360Texture;
|
use sylpheed_formats::texture::X360Texture;
|
||||||
use sylpheed_formats::{IdxdObject, PakArchive};
|
use sylpheed_formats::{IdxdObject, PakArchive};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::skip_without_disc;
|
||||||
|
|
||||||
/// Locate the extracted disc root, or `None` to skip.
|
/// Locate the extracted disc root, or `None` to skip.
|
||||||
fn disc_root() -> Option<PathBuf> {
|
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deftables_header_and_first_entry() {
|
fn deftables_header_and_first_entry() {
|
||||||
skip_without_disc!(root);
|
skip_without_disc!(root);
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ use sylpheed_formats::hash::name_hash;
|
|||||||
use sylpheed_formats::slb::{self, VoiceLang};
|
use sylpheed_formats::slb::{self, VoiceLang};
|
||||||
use sylpheed_formats::PakArchive;
|
use sylpheed_formats::PakArchive;
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
|
use common::disc_root;
|
||||||
p.join("dat").is_dir().then_some(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read `[off, off+size)` from `dat/sound.p00..` (segments concatenated).
|
/// Read `[off, off+size)` from `dat/sound.p00..` (segments concatenated).
|
||||||
fn read_range(root: &PathBuf, mut off: u64, size: usize) -> Vec<u8> {
|
fn read_range(root: &PathBuf, mut off: u64, size: usize) -> Vec<u8> {
|
||||||
|
|||||||
@@ -5,31 +5,12 @@
|
|||||||
//! `RIFF`. The banks that looked fine were the ones whose leading segment is
|
//! `RIFF`. The banks that looked fine were the ones whose leading segment is
|
||||||
//! silence. One rule, two outcomes.
|
//! silence. One rule, two outcomes.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
use sylpheed_formats::{slb, PakArchive};
|
use sylpheed_formats::{slb, PakArchive};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::skip_without_disc;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
default.join("dat").is_dir().then(|| default.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: set SYLPHEED_DISC");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bank(root: &Path, n: u32) -> Vec<u8> {
|
fn bank(root: &Path, n: u32) -> Vec<u8> {
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
//! Integration test: run the XPR2 texture pipeline against REAL `.xpr` files
|
//! Integration test: run the XPR2 texture pipeline against REAL `.xpr` files
|
||||||
//! read directly from the retail disc image, reproducing exactly what the
|
//! read directly from the retail disc image, reproducing exactly what the
|
||||||
//! viewer's texture-preview path does (`identify_format` → `X360Texture::
|
//! viewer's texture-preview path does (`identify_format` → `X360Texture::
|
||||||
//! from_xpr2`). Skipped unless `SYLPHEED_ISO` points at the disc (or the
|
//! from_xpr2`). Skipped unless `SYLPHEED_ISO` points at the disc image.
|
||||||
//! default dev path exists).
|
|
||||||
//!
|
//!
|
||||||
//! Run: `cargo test -p sylpheed-formats --test texture_disc -- --ignored --nocapture`
|
//! Run: `cargo test -p sylpheed-formats --test texture_disc -- --ignored --nocapture`
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use sylpheed_formats::texture::X360Texture;
|
use sylpheed_formats::texture::X360Texture;
|
||||||
use sylpheed_formats::vfs::identify_format;
|
use sylpheed_formats::vfs::identify_format;
|
||||||
|
|
||||||
fn iso_path() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_ISO") {
|
use common::iso_path;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.is_file() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = PathBuf::from(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso",
|
|
||||||
);
|
|
||||||
default.is_file().then_some(default)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore = "requires the retail ISO — set SYLPHEED_ISO"]
|
#[ignore = "requires the retail ISO — set SYLPHEED_ISO"]
|
||||||
|
|||||||
@@ -17,25 +17,12 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::disc_root;
|
||||||
|
|
||||||
const DECL_TABLE_AT: usize = 0x20;
|
const DECL_TABLE_AT: usize = 0x20;
|
||||||
const DECL_ENTRY: usize = 60;
|
const DECL_ENTRY: usize = 60;
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
||||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
|
|||||||
@@ -16,21 +16,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
||||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||||
|
|||||||
@@ -17,21 +17,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
||||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||||
|
|||||||
@@ -21,30 +21,8 @@ use sylpheed_formats::{
|
|||||||
ui_layout::{self, ComposeOptions},
|
ui_layout::{self, ComposeOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::skip_without_disc;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every parseable screen build on the disc, as (pak name, bundle bytes).
|
/// Every parseable screen build on the disc, as (pak name, bundle bytes).
|
||||||
fn builds(root: &Path) -> Vec<(String, Vec<u8>)> {
|
fn builds(root: &Path) -> Vec<(String, Vec<u8>)> {
|
||||||
|
|||||||
@@ -9,21 +9,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
||||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||||
|
|||||||
@@ -21,21 +21,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
||||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||||
|
|||||||
@@ -10,30 +10,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{lsta, pak::PakArchive, ratc, t8ad};
|
use sylpheed_formats::{lsta, pak::PakArchive, ratc, t8ad};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::skip_without_disc;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! skip_without_disc {
|
|
||||||
($root:ident) => {
|
|
||||||
let Some($root) = disc_root() else {
|
|
||||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every entry of every pak, plus every RATC child, as raw bytes.
|
/// 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])) {
|
fn for_each_blob(root: &Path, mut f: impl FnMut(&str, &str, &[u8])) {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ use sylpheed_formats::idxd::IdxdObject;
|
|||||||
use sylpheed_formats::pak::PakArchive;
|
use sylpheed_formats::pak::PakArchive;
|
||||||
use sylpheed_formats::unit_layout::{fields, Kind};
|
use sylpheed_formats::unit_layout::{fields, Kind};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::disc_root;
|
||||||
|
|
||||||
/// Live objects identified in the dump, with the disc record each one is.
|
/// Live objects identified in the dump, with the disc record each one is.
|
||||||
/// `bf001` is here because full-record agreement is what identified it: the
|
/// `bf001` is here because full-record agreement is what identified it: the
|
||||||
/// four-value signature also fitted `UN_be005_ADAN_SpaceFortress`, which
|
/// four-value signature also fitted `UN_be005_ADAN_SpaceFortress`, which
|
||||||
@@ -31,19 +34,6 @@ const IDENTIFIED: &[(&str, &str)] = &[
|
|||||||
|
|
||||||
const DUMP: &str = include_str!("../../../docs/re/captures/stage02-live-unit-definitions-deep.txt");
|
const DUMP: &str = include_str!("../../../docs/re/captures/stage02-live-unit-definitions-deep.txt");
|
||||||
|
|
||||||
fn disc_root() -> Option<String> {
|
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
|
||||||
if std::path::Path::new(&p).join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let d = "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)";
|
|
||||||
std::path::Path::new(d)
|
|
||||||
.join("dat")
|
|
||||||
.is_dir()
|
|
||||||
.then(|| d.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `va -> offset -> value`, from the dump's `addr +off hex u32 f32` columns.
|
/// `va -> offset -> value`, from the dump's `addr +off hex u32 f32` columns.
|
||||||
fn live() -> BTreeMap<String, BTreeMap<usize, f32>> {
|
fn live() -> BTreeMap<String, BTreeMap<usize, f32>> {
|
||||||
let mut out: BTreeMap<String, BTreeMap<usize, f32>> = BTreeMap::new();
|
let mut out: BTreeMap<String, BTreeMap<usize, f32>> = BTreeMap::new();
|
||||||
@@ -70,7 +60,8 @@ fn mapped_fields_match_the_disc_records() {
|
|||||||
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("main pak");
|
let pak =
|
||||||
|
PakArchive::open(format!("{}/dat/GP_MAIN_GAME_E.pak", disc.display())).expect("main pak");
|
||||||
let live = live();
|
let live = live();
|
||||||
let floats: Vec<_> = fields()
|
let floats: Vec<_> = fields()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
22
justfile
22
justfile
@@ -71,6 +71,28 @@ sniff-unknown:
|
|||||||
test:
|
test:
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
|
|
||||||
|
# Run the disc-backed suites. They are gated on the environment ALONE now (#16):
|
||||||
|
# no source file hardcodes a path any more, so nothing runs by accident because
|
||||||
|
# a machine happens to have a directory. Put your paths in `.env` (already
|
||||||
|
# gitignored as a local dev override):
|
||||||
|
#
|
||||||
|
# SYLPHEED_DISC="/path/to/extracted/disc" # the dir containing dat/
|
||||||
|
#
|
||||||
|
# QUOTE the values. These paths contain spaces, and an unquoted `VAR=a b c`
|
||||||
|
# is parsed as "run command b with VAR=a" -- it fails silently into ABSENT.
|
||||||
|
# SYLPHEED_RES3D=/path/to/hidden/resource3d
|
||||||
|
# SYLPHEED_ISO=/path/to/game.iso
|
||||||
|
#
|
||||||
|
# Read the corpus block the run prints -- the pass/fail tally is identical
|
||||||
|
# whether or not the corpus was exercised, which is the whole of #16.
|
||||||
|
test-disc:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
[ -f .env ] && set -a && . ./.env && set +a
|
||||||
|
cargo test --workspace
|
||||||
|
echo "--- corpus report ---"
|
||||||
|
cat target/sylpheed-corpus-report.txt
|
||||||
|
|
||||||
# Run tests including ISO integration tests (requires SYLPHEED_ISO env var)
|
# Run tests including ISO integration tests (requires SYLPHEED_ISO env var)
|
||||||
test-integration:
|
test-integration:
|
||||||
SYLPHEED_ISO=./game.iso cargo test --workspace -- --include-ignored
|
SYLPHEED_ISO=./game.iso cargo test --workspace -- --include-ignored
|
||||||
|
|||||||
Reference in New Issue
Block a user