open-standards/wai/wai-rs/tests/support/require_vectors.rs
Transaction Science 648f407f09 wai: the headline decode tests fail on a missing input, and CI supplies their inputs
The tests behind WAI's whole-decode claims read inputs from outside the
repository and returned early — reporting a pass — when those inputs were
absent. No workflow supplied them, so the claims were not checked
continuously.

- WAI_REQUIRE_SOTA_VECTORS=1 turns a missing input into a failure. A path
  variable naming a missing or incomplete input fails with or without the
  switch. Local runs without either still skip, and every skip prints a
  banner saying the claim was not verified.
- wai-sota-vectors.yml regenerates the int_hyper vectors from the pretrained
  mbt2018_mean checkpoint (pinned by SHA-256, with a pinned Python stack) and
  runs int_hyper_synth and int_video_sota_keyframe with the switch set. The
  regenerated vectors identify one run, not one machine: the encoder side is
  float.
- wai-jpegai-e2e.yml fetches the pinned JPEG AI inputs from the public models
  root, checks each against a SHA-256 manifest, and runs the whole-decode and
  reconstruction-binding tests with the switch set, requiring each test by
  name and the pinned pixel hash.
- Both jobs fail on any skip banner, and trigger on the kernels and float
  helpers the decode imports.
- The int_hyper claims are stated as the checks CI runs, with its quality
  measured on the pinned image; JPEG AI cross-machine identity is stated as a
  measurement, not a registry-class claim. determinism-tiers §5–§6 record the
  jobs.

Neither workflow has yet run through the forge.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 18:40:59 -04:00

204 lines
9.7 KiB
Rust

//! What a test does when the out-of-tree inputs behind a headline decode claim are
//! absent.
//!
//! Some decode claims are tested against inputs too large, or not redistributable,
//! to commit: the `int_hyper` vectors (the pretrained mbt2018_mean, exported to
//! integers) and the JPEG-AI whole-decoder bundle. A test that `return`s when its
//! inputs are missing is reported as *passed*, so a run with nothing to check looks
//! exactly like a run that checked everything. These helpers remove that ambiguity:
//!
//! - `WAI_REQUIRE_SOTA_VECTORS` unset (or `0`/`false`/`no`): the test still skips,
//! but writes a banner to the process's real stderr — past libtest's output
//! capture, so it shows in an ordinary `cargo test` run, not only under
//! `--nocapture`.
//! - `WAI_REQUIRE_SOTA_VECTORS=1`: a missing input is a test failure naming the
//! tool that produces it. A run that claims to verify these decodes sets it.
//!
//! Either way, with or without the switch, a variable that names a test's inputs
//! makes them required: pointing a test at inputs and having it pass because they
//! are not there is the failure this module exists to prevent. So the test fails
//! when a variable read through [`env_paths`] names a path that does not exist,
//! and when a directory variable read through [`env_dir`] (`WAI_SOTA_INT_DIR`,
//! `WAI_JPEGAI_ASSETS`) names something that is not a directory, or a directory
//! missing a file the test reads ([`InputDir::absent`]). The JPEG-AI tests that
//! take a directory through [`env_paths`] fail the same way on a missing file,
//! because their loaders do not skip. So without the switch a test skips only when
//! no variable names what is missing: when a variable read through [`env_paths`]
//! is unset (the ones that are set must still name existing paths), or when the
//! variable of [`env_dir`] is unset or empty and the default location it falls
//! back to is absent or incomplete. [`env_paths`] has no default, so an empty value
//! there names no existing path and fails. `tests/require_vectors_rule.rs` checks
//! this rule by running these helpers in child processes, with the switch unset and
//! set.
//!
//! Where each covered input comes from, and who verifies it:
//!
//! | test | inputs | produced by | verified in CI |
//! |---|---|---|---|
//! | `int_hyper_synth` (pretrained-model cases) | `WAI_SOTA_INT_DIR` | `tools/wai_sota_vectors.sh` | yes — `wai-sota-vectors.yml` regenerates and requires them |
//! | `int_video_sota_keyframe` | `WAI_SOTA_INT_DIR` | `tools/wai_sota_vectors.sh` | yes — same job |
//! | `jpegai_e2e_full` | `WAI_JPEGAI_{CODESTREAM,WEIGHTS_DIR,NORM}` | `tools/wai_jpegai_fetch_e2e_inputs.sh` (the models root's `jpegai/v1/`, pinned by `tools/jpegai-e2e-v1.sha256`) | yes — `wai-jpegai-e2e.yml` fetches, checks and requires them |
//! | `jpegai_reconstruction_binding` | same, plus optional `WAI_JPEGAI_EXPECT_SHA256` | same | yes — same job, with the decoded-pixel hash pinned |
//! | `jpegai_live` | `WAI_JPEGAI_ASSETS` (default `../jpegai-vm-assets`) | the local JPEG-AI asset directory (not in the repository) | no — local only; also needs the `neural` + `codecs` features |
//!
//! Tests that still skip silently on absent inputs and are NOT covered by this
//! switch — an open gap, listed here so it stays visible:
//! `jpegai_*_calibration.rs`, `jpegai_e2e.rs`, `jpegai_e2e_{chain,chroma,dequant,
//! frontend,hyperlatent,synthesis}.rs`, `jpegai_mans_golden.rs`,
//! `jpegai_postfilter_chain.rs` (per-stage and staged JPEG-AI fixtures traced from
//! the reference software), `neural_native_decode.rs` (float ONNX decoders, models
//! and `/tmp` fixtures built per machine), and the `WAI_JPEGAI_*` unit tests in
//! `src/jpegai_synthesis.rs` and `src/neural/mod.rs`.
use std::io::Write;
use std::path::{Path, PathBuf};
pub const SWITCH: &str = "WAI_REQUIRE_SOTA_VECTORS";
/// Whether this run has declared that the out-of-tree inputs must be present.
pub fn required() -> bool {
match std::env::var(SWITCH) {
Ok(v) => !matches!(v.trim().to_ascii_lowercase().as_str(), "" | "0" | "false" | "no"),
Err(_) => false,
}
}
/// Call at the point a test would otherwise return early for lack of inputs.
/// Panics when [`required`]; otherwise announces the skip where it cannot be
/// missed. The caller returns (or skips the sub-check) after this.
pub fn absent(test: &str, missing: &str, produce: &str) {
let msg = format!("{test}: NOT VERIFIED — {missing}\n produce with: {produce}");
if required() {
panic!("{msg}\n {SWITCH} is set, so a missing input is a failure, not a skip");
}
// Deliberately not eprintln!: libtest captures the print macros of a passing
// test, which is how skips became invisible in the first place.
// wai-sota-vectors.yml and wai-jpegai-e2e.yml fail on "SKIPPED" or
// "NOT VERIFIED" in their test log, so keep both words in this banner.
let _ = writeln!(
std::io::stderr(),
"\n!!! SKIPPED — {msg}\n set {SWITCH}=1 to make a missing input fail\n"
);
}
/// Read a JSON input. `None` only when the file does not exist; a file that exists
/// but cannot be read or parsed is a broken input and fails the test either way.
#[allow(dead_code)]
pub fn load_json(p: &Path) -> Option<serde_json::Value> {
if !p.exists() {
return None;
}
let bytes = std::fs::read(p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
Some(serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", p.display())))
}
/// Every named JSON file from `dir`, or `None` after [`InputDir::absent`] names
/// the ones missing (which fails instead when a variable named `dir`).
#[allow(dead_code)]
pub fn load_all<const N: usize>(
test: &str,
dir: &InputDir,
names: [&str; N],
produce: &str,
) -> Option<[serde_json::Value; N]> {
let loaded = names.map(|n| load_json(&dir.join(n)));
let missing: Vec<&str> =
names.iter().zip(&loaded).filter(|(_, v)| v.is_none()).map(|(n, _)| *n).collect();
if !missing.is_empty() {
dir.absent(test, &format!("{} not in {}", missing.join(", "), dir.display()), produce);
return None;
}
Some(loaded.map(Option::unwrap))
}
/// The environment variables a test reads its input paths from. `None` (after
/// [`absent`]) when any is unset. A variable that IS set but names a path that does
/// not exist is a misconfigured run — pointing a test at a file and having it pass
/// because the file is missing is the failure this module exists to prevent — so
/// that fails regardless of the switch, and whether or not the other variables are
/// set.
#[allow(dead_code)]
pub fn env_paths<const N: usize>(test: &str, vars: [&str; N], produce: &str) -> Option<[String; N]> {
let got = vars.map(|v| std::env::var(v).ok());
// The set ones first: a variable naming a missing path fails even when another
// variable the test reads is unset, which would otherwise end the test as a skip.
for (v, p) in vars.iter().zip(&got) {
if let Some(p) = p {
assert!(Path::new(p).exists(), "{test}: {v}={p} does not exist (produce with: {produce})");
}
}
let unset: Vec<&str> =
vars.iter().zip(&got).filter(|(_, g)| g.is_none()).map(|(v, _)| *v).collect();
if !unset.is_empty() {
absent(test, &format!("{} not set", unset.join(" + ")), produce);
return None;
}
Some(got.map(Option::unwrap))
}
/// The directory a test reads its inputs from, and whether a variable named it.
/// Made by [`env_dir`].
#[allow(dead_code)]
pub struct InputDir {
path: PathBuf,
/// The variable that named `path`; `None` when `path` is the default.
named_by: Option<String>,
}
#[allow(dead_code)]
impl InputDir {
pub fn path(&self) -> &Path {
&self.path
}
pub fn join(&self, name: impl AsRef<Path>) -> PathBuf {
self.path.join(name)
}
pub fn display(&self) -> std::path::Display<'_> {
self.path.display()
}
/// [`absent`] for an input that belongs in this directory. When a variable
/// named the directory, the run pointed the test at its inputs, so a missing
/// one fails whether or not the switch is set. Only the default directory may
/// be absent or incomplete and skip.
pub fn absent(&self, test: &str, missing: &str, produce: &str) {
if let Some(var) = &self.named_by {
panic!(
"{test}: NOT VERIFIED — {missing}\n produce with: {produce}\n \
{var} names this directory, so a missing input is a failure with or \
without {SWITCH}"
);
}
absent(test, missing, produce);
}
}
impl AsRef<Path> for InputDir {
fn as_ref(&self) -> &Path {
&self.path
}
}
/// The directory a test reads its inputs from: the variable `var` when it is set
/// and non-empty, else `default`. Empty counts as unset, as it does for
/// `tools/wai_sota_vectors.sh`'s own `${1:-/tmp/sota_int}`, so the script and the
/// tests fall back to the same place. A variable that IS set names the test's
/// inputs: it fails the test, regardless of the switch, when it does not name a
/// directory (as in [`env_paths`]), and the returned [`InputDir`] fails it when a
/// file is missing from that directory. The default may be absent or incomplete:
/// the test then goes through [`absent`].
#[allow(dead_code)]
pub fn env_dir(test: &str, var: &str, default: &str, produce: &str) -> InputDir {
match std::env::var(var) {
Ok(p) if !p.is_empty() => {
assert!(
Path::new(&p).is_dir(),
"{test}: {var}={p} is not a directory (produce with: {produce})"
);
InputDir { path: PathBuf::from(p), named_by: Some(var.to_owned()) }
}
_ => InputDir { path: PathBuf::from(default), named_by: None },
}
}