Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 19s
WAI operation catalogue / does this change touch it (push) Successful in 27s
wai-audit collector and player integration / does this change touch it (push) Successful in 21s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 15s
wai-quantum studio and desktop / does this change touch it (push) Successful in 15s
wai-quantum for JavaScript / does this change touch it (push) Successful in 3m39s
WAI Studio / does this change touch it (push) Successful in 3m22s
wai-fleet service / does this change touch it (push) Successful in 3m57s
wai-app and Create / wai-app · does this change touch it (push) Successful in 4m14s
wai-ledger service / does this change touch it (push) Successful in 3m47s
wai-sink-swift native sink package / does this change touch it (push) Successful in 3m29s
wai-sink-android native sink library / does this change touch it (push) Successful in 3m35s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 4m25s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 3m6s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 9m9s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 5m12s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 8m31s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 8s
WAI operation catalogue / the catalogue and every door · linux (push) Failing after 1m40s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m0s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 35s
wai-audit collector and player integration / collector · linux (push) Successful in 4m1s
wai-quantum for JavaScript / javascript · linux (push) Successful in 3m53s
wai-ledger service / service · linux (push) Successful in 3m23s
wai-fleet service / service · linux (push) Successful in 3m57s
wai-quantum studio and desktop / studio · browser (push) Successful in 7m8s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 6m41s
WAI Studio / Studio in WebKit · linux (push) Successful in 21m29s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Failing after 2m31s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m48s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 10m43s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m35s
wai-fleet service / service · macos (push) Successful in 54s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m53s
wai-ledger service / service · macos (push) Successful in 59s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 53s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 8m53s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 7m47s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 6m44s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 15m9s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m34s
floor-conformance/ is two decoder suites of `wai conform`: `floor`, the mandatory floor every sink decodes, and `video-floor`, the AV1 case a sink that presents video decodes. Each writes a conform-result/1 record of its own, so the record's shape and admission are unchanged. - `--presents <classes>` applies SPEC §5's class rule: with `video`, both suites run and are recorded; without it, the video floor is reported not run, with the rule, and no record is written. - A decoder with no decoder for a case's capability answers `unsupported <capability>` (HTTP: 501); the case is reported not run and recorded as not passed, so a sink that presents video with no AV1 decoder does not pass the video floor. Beside --independent, a check floor_verify.py did not run is reported not run. - The AV1 case's output is the I420 frames in output order, each frame held to its digest and the clip to its own. - The catalogue gains --presents (ops export and actions regenerated); project_verify.py knows the two suites; CLI tests cover a sink that decodes everything, one with no AV1 decoder and one that presents audio only. Co-Authored-By: Claude <[email protected]>
351 lines
22 KiB
Rust
351 lines
22 KiB
Rust
//! `conform-result/1` run against WAI's native sink (conformance-results §4.1), through its Rust
|
|
//! module and through the C interface its platform integrations link, against its browser sink,
|
|
//! against the score decoder and the integer-synthesis learned decoder, and against a cache in
|
|
//! front of the reference origin, each run through `wai conform` as another implementation would
|
|
//! be. Each run is conformant, `wai verify` accepts each record, `wai verify --conformant` admits
|
|
//! each against the corpus of this checkout, and gives each area its verdict. Planted faults: a
|
|
//! native sink that presents an object whose digest does not match, a browser sink that shows a
|
|
//! frame whose object was refused as verified, a decoder that changes one byte of its output and a
|
|
//! cache that applies a content coding are each refused (`not_conformant`); the native sink run
|
|
//! against a copy of the corpus changed to suit it is conformant there, and refused against this
|
|
//! checkout's (`corpus_mismatch`); a case the corpus marks informative that does not pass leaves
|
|
//! the verdict conformant and a required one does not; a record that marks a required case
|
|
//! informative, signed again, is refused (`corpus_mismatch`); a record of a suite this build does
|
|
//! not run is refused (`suite_unknown`).
|
|
#![cfg(all(feature = "quickstart", feature = "score", feature = "native_sink", feature = "browser_sink", feature = "neural_int"))]
|
|
|
|
use serde_json::Value;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Output};
|
|
|
|
fn tmp(name: &str) -> PathBuf {
|
|
let d = std::env::temp_dir().join(format!("wai-conform-sinks-{name}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
std::fs::create_dir_all(&d).unwrap();
|
|
d
|
|
}
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
|
|
}
|
|
|
|
fn wai(dir: &Path, args: &[&str]) -> Output {
|
|
Command::new(env!("CARGO_BIN_EXE_wai")).current_dir(dir).args(args).output().expect("wai runs")
|
|
}
|
|
|
|
fn text(o: &Output) -> String {
|
|
format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr))
|
|
}
|
|
|
|
fn q(p: &str) -> String {
|
|
format!("'{}'", p.replace('\'', "'\\''"))
|
|
}
|
|
|
|
/// `wai conform <kind> --suite <suite> --impl <imp> --root <root> --json`, its record written
|
|
/// to `out`: the exit status and the JSON envelope.
|
|
fn conform(dir: &Path, root: &Path, kind: &str, suite: &str, imp: &str, out: &Path) -> (i32, Value) {
|
|
let o = wai(dir, &["conform", kind, "--suite", suite, "--impl", imp, "--root", root.to_str().unwrap(), "--out", out.to_str().unwrap(), "--yes", "--json"]);
|
|
let v: Value = serde_json::from_slice(&o.stdout).unwrap_or_else(|_| panic!("JSON: {}", text(&o)));
|
|
(o.status.code().unwrap_or(-1), v)
|
|
}
|
|
|
|
/// The reference native sink, through its module or its C interface.
|
|
fn native(c_abi: bool) -> String {
|
|
format!("{} answers {}{{case}}", q(env!("CARGO_BIN_EXE_wai_native_sink_vectors")), if c_abi { "--c-abi " } else { "" })
|
|
}
|
|
|
|
/// A sink that presents an object whose digest does not match the SHA-256 its name states: the
|
|
/// reference sink's answers with that refusal's `present` turned true.
|
|
fn planted() -> String {
|
|
format!("{} | sed '/object_digest_mismatch/s/\"present\":false/\"present\":true/'", native(false))
|
|
}
|
|
|
|
/// The reference browser sink, its answer to one call.
|
|
fn browser() -> String {
|
|
format!("{} answers {{case}}", q(env!("CARGO_BIN_EXE_wai_browser_sink_vectors")))
|
|
}
|
|
|
|
/// The reference integer-synthesis decoder.
|
|
fn int_synth() -> String {
|
|
format!("{} decode {{model}} {{in}} {{out}}", q(env!("CARGO_BIN_EXE_wai_int_synth_vectors")))
|
|
}
|
|
|
|
#[test]
|
|
fn the_sinks_and_the_decoders_conform_and_each_record_is_admitted() {
|
|
let d = tmp("own");
|
|
let root = wai_dir();
|
|
let w = q(env!("CARGO_BIN_EXE_wai"));
|
|
let runs = [
|
|
("sink", "native-sink", native(false), "native-sink"),
|
|
("sink", "native-sink", native(true), "native-sink-c-abi"),
|
|
("sink", "browser", browser(), "browser"),
|
|
("decoder", "score", format!("{w} score render {{in}} --pcm {{out}}"), "score"),
|
|
("decoder", "neural", int_synth(), "neural"),
|
|
];
|
|
for (kind, suite, imp, name) in &runs {
|
|
let (code, v) = conform(&d, &root, kind, suite, imp, &d.join(format!("{name}.wai-conform.json")));
|
|
assert_eq!(code, 0, "{name}: {v:#}");
|
|
assert_eq!(v["data"]["conformant"], true, "{name}");
|
|
assert_eq!(v["data"]["failed"], 0, "{name}");
|
|
}
|
|
let rec: Value = serde_json::from_slice(&std::fs::read(d.join("native-sink.wai-conform.json")).unwrap()).unwrap();
|
|
assert_eq!((rec["kind"].as_str(), rec["suite"].as_str()), (Some("sink"), Some("native-sink")));
|
|
// Every session of the corpus and every configuration it refuses is a case.
|
|
let corpus: Value = serde_json::from_slice(&std::fs::read(root.join("live-conformance/native-sink/vectors.json")).unwrap()).unwrap();
|
|
let n = corpus["cases"].as_array().unwrap().len() + corpus["configs"].as_array().unwrap().len();
|
|
assert_eq!(rec["summary"]["cases"].as_u64(), Some(n as u64), "{}", rec["summary"]);
|
|
|
|
// Every call of the browser corpus, its chain aside, and every vector of the learned decoder's.
|
|
let rec: Value = serde_json::from_slice(&std::fs::read(d.join("browser.wai-conform.json")).unwrap()).unwrap();
|
|
assert_eq!(rec["summary"]["cases"].as_u64(), Some(110), "{}", rec["summary"]);
|
|
let rec: Value = serde_json::from_slice(&std::fs::read(d.join("neural.wai-conform.json")).unwrap()).unwrap();
|
|
let names: Vec<&str> = rec["cases"].as_array().unwrap().iter().map(|c| c["name"].as_str().unwrap()).collect();
|
|
assert_eq!(names, vec!["gradient", "bloom", "facade", "photo"]);
|
|
// No suite of these marks a case informative, and none takes --level.
|
|
for (kind, suite, imp, _) in &runs[2..] {
|
|
let o = wai(&d, &["conform", kind, "--suite", suite, "--impl", imp, "--root", root.to_str().unwrap(), "--level", "all", "--out", d.join("x.wai-conform.json").to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(2), "{suite}: {}", text(&o));
|
|
assert!(text(&o).contains("marks no case informative"), "{}", text(&o));
|
|
}
|
|
// The decoder names the parameter set it decodes against.
|
|
let o = wai(&d, &["conform", "decoder", "--suite", "neural", "--impl", &format!("{} decode {{in}} {{out}}", q(env!("CARGO_BIN_EXE_wai_int_synth_vectors"))), "--root", root.to_str().unwrap(), "--out", d.join("x.wai-conform.json").to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(2), "{}", text(&o));
|
|
assert!(text(&o).contains("names {model}"), "{}", text(&o));
|
|
|
|
let o = wai(&d, &["verify", d.to_str().unwrap()]);
|
|
assert!(o.status.success() && text(&o).contains("5 conformance results verified"), "{}", text(&o));
|
|
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), d.to_str().unwrap()]);
|
|
assert!(o.status.success(), "{}", text(&o));
|
|
assert_eq!(text(&o).matches("ok admitted").count(), 5, "{}", text(&o));
|
|
assert!(text(&o).contains("every conformance result admits its implementation"), "{}", text(&o));
|
|
// An area verdict for each area the records are in: media's two suites run here, its floor
|
|
// suites named as run by no record, and live's two sinks, its relay suite named as run by no
|
|
// record.
|
|
assert!(text(&o).contains("area media: conformant over its 2 records, every one admitted (decoder neural, decoder score); not run, no record names it: decoder floor, decoder video-floor\n"), "{}", text(&o));
|
|
assert!(text(&o).contains("area live: conformant over its 3 records, every one admitted (sink browser, sink native-sink); not run, no record names it: relay moqt\n"), "{}", text(&o));
|
|
assert!(!text(&o).contains("area trust") && !text(&o).contains("area delivery"), "{}", text(&o));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
}
|
|
|
|
#[test]
|
|
fn a_planted_failing_browser_sink_and_a_decoder_that_changes_one_byte_are_refused() {
|
|
let d = tmp("planted-browser");
|
|
let root = wai_dir();
|
|
// A browser sink that shows a frame whose object, chain or provenance was refused as verified.
|
|
let shown = format!("{} | sed 's/\"state\":\"refused\"/\"state\":\"verified\"/'", browser());
|
|
let (code, v) = conform(&d, &root, "sink", "browser", &shown, &d.join("browser.wai-conform.json"));
|
|
assert_eq!(code, 1, "{v:#}");
|
|
assert_eq!(v["data"]["conformant"], false);
|
|
let failed: Vec<&str> = v["data"]["failures"].as_array().unwrap().iter().map(|f| f["name"].as_str().unwrap()).collect();
|
|
assert_eq!(failed, vec!["frames/cmaf_provenance_refused", "frames/chain_refused", "frames/object_refused", "frames/refused_over_pending"], "exactly the frames it shows wrongly");
|
|
// A decoder that changes one byte of what it decodes, the hundredth.
|
|
let flip = format!("{} >/dev/null && dd if={{out}} bs=1 skip=100 count=1 2>/dev/null | LC_ALL=C tr '\\000-\\377' '\\001-\\377\\000' | dd of={{out}} bs=1 seek=100 count=1 conv=notrunc 2>/dev/null", int_synth());
|
|
let (code, v) = conform(&d, &root, "decoder", "neural", &flip, &d.join("neural.wai-conform.json"));
|
|
assert_eq!(code, 1, "{v:#}");
|
|
assert_eq!((v["data"]["failed"].as_u64(), v["data"]["conformant"].as_bool()), (Some(4), Some(false)), "one byte changed fails every vector: {v:#}");
|
|
// Each record is a genuine record of a run that did not pass: it verifies, and admits nothing.
|
|
let o = wai(&d, &["verify", d.to_str().unwrap()]);
|
|
assert!(o.status.success() && text(&o).matches("not conformant").count() == 2, "{}", text(&o));
|
|
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), d.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert_eq!(text(&o).matches("FAIL admission rule 2 not_conformant").count(), 2, "{}", text(&o));
|
|
assert!(text(&o).contains("area media: not conformant: refused decoder neural") && text(&o).contains("area live: not conformant: refused sink browser"), "{}", text(&o));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
}
|
|
|
|
/// A cache in front of the reference origin, started by `mutators.py`: `relay` changes nothing,
|
|
/// the others each break http-delivery §9 one way.
|
|
fn cache(mode: &str, root: &Path) -> String {
|
|
format!("python3 -I {} --mode {mode} --listen {{port}} --upstream {{origin}}", q(root.join("cache-conformance/mutators.py").to_str().unwrap()))
|
|
}
|
|
|
|
/// What a cache run reads, copied under `dir`: the corpus's tests (`tests` written over them where
|
|
/// given), its driver and proxies, the BLAKE3 the driver checks with, and the objects and the
|
|
/// parameter set it stages.
|
|
fn cache_corpus_copy(dir: &Path, tests: Option<&Value>) -> PathBuf {
|
|
let root = wai_dir();
|
|
let copy = dir.join("wai");
|
|
fn copy_tree(from: &Path, to: &Path) {
|
|
std::fs::create_dir_all(to).unwrap();
|
|
for e in std::fs::read_dir(from).unwrap().flatten() {
|
|
let t = to.join(e.file_name());
|
|
if e.path().is_dir() { copy_tree(&e.path(), &t) } else { std::fs::copy(e.path(), t).unwrap(); }
|
|
}
|
|
}
|
|
for d in ["registry", "claim-conformance", "cache-conformance", "http-conformance", "wai-rs/tests/vectors/int_mlicpp"] {
|
|
std::fs::create_dir_all(copy.join(d)).unwrap();
|
|
}
|
|
for f in ["SPEC.md", "CHANGELOG.md", "registry/capabilities.json", "claim-conformance/_blake3.py", "cache-conformance/driver.py", "cache-conformance/mutators.py", "cache-conformance/tests.json", "wai-rs/tests/vectors/int_mlicpp/model.wmb"] {
|
|
std::fs::copy(root.join(f), copy.join(f)).unwrap();
|
|
}
|
|
copy_tree(&root.join("http-conformance"), ©.join("http-conformance"));
|
|
if let Some(t) = tests {
|
|
std::fs::write(copy.join("cache-conformance/tests.json"), serde_json::to_vec_pretty(t).unwrap()).unwrap();
|
|
}
|
|
copy
|
|
}
|
|
|
|
#[test]
|
|
fn a_cache_conforms_on_its_required_tests_and_an_informative_one_is_not_counted() {
|
|
let d = tmp("cache");
|
|
let root = wai_dir();
|
|
let r = root.to_str().unwrap();
|
|
// A relay, which §9 rule 7 allows: every MUST test passes, the informative ones say it stored
|
|
// nothing, and the record marks each of them informative.
|
|
let (code, v) = conform(&d, &root, "cache", "cache", &cache("relay", &root), &d.join("relay.wai-conform.json"));
|
|
assert_eq!(code, 0, "{v:#}");
|
|
assert_eq!((v["data"]["conformant"].as_bool(), v["data"]["informative"].as_u64()), (Some(true), Some(4)), "{v:#}");
|
|
let rec: Value = serde_json::from_slice(&std::fs::read(d.join("relay.wai-conform.json")).unwrap()).unwrap();
|
|
let informative: Vec<&str> = rec["cases"].as_array().unwrap().iter().filter(|c| c["level"] == "informative").map(|c| c["name"].as_str().unwrap()).collect();
|
|
assert_eq!(informative, vec!["S1 stored", "S2 range_stored", "S3 stored_after_pause", "S4 origin_share"]);
|
|
// --level all holds every case required: no case is marked, and the stricter record is admitted too.
|
|
let o = wai(&d, &["conform", "cache", "--suite", "cache", "--impl", &cache("relay", &root), "--root", r, "--level", "all", "--out", d.join("relay-all.wai-conform.json").to_str().unwrap(), "--json"]);
|
|
assert_eq!(o.status.code(), Some(0), "{}", text(&o));
|
|
let all = std::fs::read_to_string(d.join("relay-all.wai-conform.json")).unwrap();
|
|
assert!(!all.contains("\"level\""), "{all}");
|
|
assert_eq!(wai(&d, &["conform", "cache", "--suite", "cache", "--impl", &cache("relay", &root), "--root", r, "--level", "most", "--out", d.join("x.wai-conform.json").to_str().unwrap()]).status.code(), Some(2));
|
|
// A cache command that names no port is a usage error, before anything starts; one that ends
|
|
// before it listens is an implementation that does not answer, said as soon as it ends.
|
|
assert_eq!(wai(&d, &["conform", "cache", "--suite", "cache", "--impl", "python3 -I x.py {origin}", "--root", r, "--out", d.join("x.wai-conform.json").to_str().unwrap()]).status.code(), Some(2));
|
|
let t0 = std::time::Instant::now();
|
|
let o = wai(&d, &["conform", "cache", "--suite", "cache", "--impl", "false {port} {origin}", "--root", r, "--out", d.join("x.wai-conform.json").to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(6), "{}", text(&o));
|
|
assert!(text(&o).contains("ended (exit Some(1)) before it answered") && t0.elapsed() < std::time::Duration::from_secs(30), "{:?} {}", t0.elapsed(), text(&o));
|
|
assert!(!d.join("x.wai-conform.json").exists(), "no record of a run that did not start");
|
|
let o = wai(&d, &["verify", "--conformant", "--root", r, d.to_str().unwrap()]);
|
|
assert!(o.status.success(), "{}", text(&o));
|
|
assert_eq!(text(&o).matches("ok admitted").count(), 2, "{}", text(&o));
|
|
assert!(text(&o).contains("area delivery: conformant over its 2 records, every one admitted (cache cache); not run, no record names it: packager policy, origin http"), "{}", text(&o));
|
|
|
|
// A cache that applies a content coding fails C7, a MUST test: not conformant, refused at rule 2.
|
|
let p = d.join("gzip");
|
|
std::fs::create_dir_all(&p).unwrap();
|
|
let (code, v) = conform(&d, &root, "cache", "cache", &cache("gzip", &root), &p.join("gzip.wai-conform.json"));
|
|
assert_eq!(code, 1, "{v:#}");
|
|
let failed: Vec<(&str, bool)> = v["data"]["failures"].as_array().unwrap().iter().map(|f| (f["name"].as_str().unwrap(), f["informative"] == true)).collect();
|
|
assert_eq!(failed, vec![("C7 no_coding", false)], "{v:#}");
|
|
let o = wai(&d, &["verify", "--conformant", "--root", r, p.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("FAIL admission rule 2 not_conformant"), "{}", text(&o));
|
|
|
|
// The same cache against a copy of the corpus that marks C7 informative: its failure is
|
|
// reported and not counted, so the run is conformant there and admitted by a party holding
|
|
// that copy; against this checkout's corpus, where C7 is required, it is refused.
|
|
let mut tests: Value = serde_json::from_slice(&std::fs::read(root.join("cache-conformance/tests.json")).unwrap()).unwrap();
|
|
for t in tests["tests"].as_array_mut().unwrap() {
|
|
if t["id"] == "C7" {
|
|
t["level"] = Value::from("informative");
|
|
}
|
|
}
|
|
let copy = cache_corpus_copy(&d.join("copy"), Some(&tests));
|
|
let out = d.join("informative.wai-conform.json");
|
|
let (code, v) = conform(&d, ©, "cache", "cache", &cache("gzip", &root), &out);
|
|
assert_eq!(code, 0, "an informative case that did not pass leaves the run conformant: {v:#}");
|
|
assert_eq!((v["data"]["failed"].as_u64(), v["data"]["informative_failed"].as_u64()), (Some(1), Some(1)), "{v:#}");
|
|
assert!(wai(&d, &["verify", "--conformant", "--root", copy.to_str().unwrap(), out.to_str().unwrap()]).status.success());
|
|
let o = wai(&d, &["verify", "--conformant", "--root", r, out.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("FAIL admission rule 4 corpus_mismatch"), "{}", text(&o));
|
|
|
|
// The record of the run against this checkout's corpus, C7 marked informative, the verdict
|
|
// made to agree and signed again: it verifies, and its digest is this corpus's, but the corpus
|
|
// requires C7, so it admits nothing.
|
|
let rec: Value = serde_json::from_slice(&std::fs::read(p.join("gzip.wai-conform.json")).unwrap()).unwrap();
|
|
let mut m = rec.as_object().unwrap().clone();
|
|
m.remove("sig");
|
|
m.remove("signer_pubkey");
|
|
for c in m["cases"].as_array_mut().unwrap() {
|
|
if c["name"] == "C7 no_coding" {
|
|
c["level"] = Value::from("informative");
|
|
}
|
|
}
|
|
m.insert("conformant".into(), Value::Bool(true));
|
|
let m = wai::conform_result::sign(m, &ed25519_dalek::SigningKey::from_bytes(&[0x46; 32])).unwrap();
|
|
let relevel = d.join("relevelled.wai-conform.json");
|
|
std::fs::write(&relevel, serde_json::to_vec_pretty(&Value::Object(m)).unwrap()).unwrap();
|
|
let o = wai(&d, &["verify", relevel.to_str().unwrap()]);
|
|
assert!(o.status.success() && text(&o).contains("conformant"), "signed again, it verifies: {}", text(&o));
|
|
let o = wai(&d, &["verify", "--conformant", "--root", r, relevel.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("FAIL admission rule 4 corpus_mismatch: it marks C7 no_coding informative"), "{}", text(&o));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
}
|
|
|
|
#[test]
|
|
fn a_planted_failing_sink_is_refused() {
|
|
let d = tmp("planted");
|
|
let root = wai_dir();
|
|
let out = d.join("planted.wai-conform.json");
|
|
let (code, v) = conform(&d, &root, "sink", "native-sink", &planted(), &out);
|
|
assert_eq!(code, 1, "{v:#}");
|
|
assert_eq!(v["data"]["conformant"], false);
|
|
let failed: Vec<&str> = v["data"]["failures"].as_array().unwrap().iter().map(|f| f["name"].as_str().unwrap()).collect();
|
|
assert!(failed.contains(&"tampered-fragment"), "{failed:?}");
|
|
assert!(!failed.contains(&"signed-vod"), "a session with no refused object still passes: {failed:?}");
|
|
// The record is a genuine record of a run that did not pass: it verifies, and admits nothing.
|
|
let o = wai(&d, &["verify", out.to_str().unwrap()]);
|
|
assert!(o.status.success() && text(&o).contains("not conformant"), "{}", text(&o));
|
|
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), out.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("FAIL admission rule 2 not_conformant"), "{}", text(&o));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
}
|
|
|
|
#[test]
|
|
fn a_sink_run_against_a_corpus_changed_to_suit_it_is_refused() {
|
|
let d = tmp("changed");
|
|
let root = wai_dir();
|
|
// A copy of what a run reads, its answers changed to the planted sink's.
|
|
let copy = d.join("wai");
|
|
std::fs::create_dir_all(copy.join("registry")).unwrap();
|
|
std::fs::create_dir_all(copy.join("live-conformance/native-sink")).unwrap();
|
|
for f in ["SPEC.md", "CHANGELOG.md", "registry/capabilities.json"] {
|
|
std::fs::copy(root.join(f), copy.join(f)).unwrap();
|
|
}
|
|
let mut corpus: Value = serde_json::from_slice(&std::fs::read(root.join("live-conformance/native-sink/vectors.json")).unwrap()).unwrap();
|
|
for c in corpus["cases"].as_array_mut().unwrap() {
|
|
for call in c["calls"].as_array_mut().unwrap() {
|
|
if call["answer"]["refused"] == "object_digest_mismatch" {
|
|
call["answer"]["present"] = Value::Bool(true);
|
|
}
|
|
}
|
|
}
|
|
std::fs::write(copy.join("live-conformance/native-sink/vectors.json"), serde_json::to_vec_pretty(&corpus).unwrap()).unwrap();
|
|
let out = d.join("suited.wai-conform.json");
|
|
let (code, v) = conform(&d, ©, "sink", "native-sink", &planted(), &out);
|
|
assert_eq!(code, 0, "against the changed corpus the planted sink passes: {v:#}");
|
|
let o = wai(&d, &["verify", "--conformant", "--root", copy.to_str().unwrap(), out.to_str().unwrap()]);
|
|
assert!(o.status.success(), "a relying party holding the changed corpus admits it: {}", text(&o));
|
|
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), out.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("FAIL admission rule 4 corpus_mismatch"), "{}", text(&o));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
}
|
|
|
|
#[test]
|
|
fn a_record_of_a_suite_this_build_does_not_run_is_refused() {
|
|
let d = tmp("unknown");
|
|
let root = wai_dir();
|
|
let out = d.join("native-sink.wai-conform.json");
|
|
let (code, _) = conform(&d, &root, "sink", "native-sink", &native(false), &out);
|
|
assert_eq!(code, 0);
|
|
// --level is for a suite whose corpus marks a case informative; the native sink's marks none.
|
|
let o = wai(&d, &["conform", "sink", "--suite", "native-sink", "--impl", &native(false), "--root", root.to_str().unwrap(), "--level", "required", "--out", d.join("x.wai-conform.json").to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(2), "{}", text(&o));
|
|
let rec: Value = serde_json::from_slice(&std::fs::read(&out).unwrap()).unwrap();
|
|
let mut r = rec.as_object().unwrap().clone();
|
|
r.remove("sig");
|
|
r.remove("signer_pubkey");
|
|
r.insert("suite".into(), Value::from("nonesuch"));
|
|
let key = ed25519_dalek::SigningKey::from_bytes(&[0x45; 32]);
|
|
let r = wai::conform_result::sign(r, &key).unwrap();
|
|
let other = d.join("nonesuch.wai-conform.json");
|
|
std::fs::write(&other, serde_json::to_vec_pretty(&Value::Object(r)).unwrap()).unwrap();
|
|
assert!(wai(&d, &["verify", other.to_str().unwrap()]).status.success(), "signed again, it verifies");
|
|
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), other.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("FAIL admission rule 3 suite_unknown"), "{}", text(&o));
|
|
// --root alone is a usage error: it names the corpus --conformant compares with.
|
|
assert_eq!(wai(&d, &["verify", "--root", root.to_str().unwrap(), out.to_str().unwrap()]).status.code(), Some(2));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
}
|