Two mutations of the reference survived the corpus: ignoring a display record's declaration, and restarting a signal's grace at a repeated observation. Each now changes a log's outcome. Co-Authored-By: Claude Opus 5.5 <[email protected]>
97 lines
4.7 KiB
Rust
97 lines
4.7 KiB
Rust
//! `wai.live.termination` (extensions/stream-termination.md) and the display match of
|
|
//! capability-constraints §4: the committed corpus `live-conformance/termination/vectors.json`
|
|
//! regenerates byte for byte from its fixed seeds, every match and log in it gets the outcome it
|
|
//! states, every reason the extension adds refuses a log, the accepted logs are the cases §8
|
|
//! names, and the extension's §7 lists exactly the reasons the reference reports.
|
|
#![cfg(feature = "stream_termination")]
|
|
|
|
use std::collections::BTreeSet;
|
|
use std::path::PathBuf;
|
|
use wai::stream_termination::{corpus, Reason};
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
|
|
fn read(p: &str) -> String {
|
|
std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}"))
|
|
}
|
|
|
|
fn committed() -> String {
|
|
read("live-conformance/termination/vectors.json")
|
|
}
|
|
|
|
#[test]
|
|
fn the_committed_corpus_regenerates_from_its_seeds() {
|
|
assert!(committed() == corpus::generate(), "live-conformance/termination/vectors.json differs from `wai_stream_termination_vectors gen`");
|
|
}
|
|
|
|
#[test]
|
|
fn every_match_and_log_gets_its_stated_outcome() {
|
|
let fails = corpus::verify(&committed());
|
|
assert!(fails.is_empty(), "{}", fails.join("\n"));
|
|
}
|
|
|
|
/// Every reason refuses a log; the accepted logs are §8's; and the facts that make the
|
|
/// invisible seen are what the cases were written to show.
|
|
#[test]
|
|
fn the_corpus_covers_every_reason_and_case() {
|
|
let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap();
|
|
let logs = doc["logs"].as_array().unwrap();
|
|
let seen: BTreeSet<&str> = logs.iter().filter_map(|v| v["reason"].as_str()).collect();
|
|
for r in Reason::ALL {
|
|
assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}");
|
|
}
|
|
let get = |n: &str| logs.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no log {n}"));
|
|
for (n, why) in [
|
|
("hidden", "hidden"),
|
|
("occluded", "occluded"),
|
|
("display_off", "display_off"),
|
|
("idle", "idle"),
|
|
("autoplay_unattended", "autoplay_unattended"),
|
|
("user_stop", "user"),
|
|
("audio_hidden", "ended"),
|
|
("none_within", "ended"),
|
|
("display_change", "ended"),
|
|
("hidden_repeated", "hidden"),
|
|
("hdr", "user"),
|
|
] {
|
|
assert_eq!(get(n)["outcome"], "accept", "{n}");
|
|
assert_eq!(get(n)["terminated"], why, "{n}");
|
|
}
|
|
assert_eq!(get("open")["terminated"], serde_json::Value::Null);
|
|
// The hidden log: one segment past the grace is unwatched; its hops are per class, the
|
|
// origin unmetered, the relay absent.
|
|
let h = get("hidden");
|
|
assert_eq!(h["unwatched"]["segments"], 1);
|
|
assert_eq!(h["unseen_us"], 10_800_000);
|
|
assert_eq!(h["absent"], serde_json::json!(["relay"]));
|
|
assert_eq!(h["unmetered"], serde_json::json!([{ "role": "origin", "count": 4 }]));
|
|
// Two classes of one hop, two totals.
|
|
let classes: Vec<&str> = get("user_stop")["totals"].as_array().unwrap().iter().filter(|t| t["role"] == "sink_decode").map(|t| t["class"].as_str().unwrap()).collect();
|
|
assert_eq!(classes, ["OnChipCounter", "CalibratedInstrument"]);
|
|
assert_eq!(get("none_within")["panel"], serde_json::json!({ "within": 0, "none_within": 2 }));
|
|
assert_eq!(get("audio_hidden")["unwatched"], serde_json::Value::Null);
|
|
assert_eq!(get("segment_after_termination")["reason"], "after_termination");
|
|
assert_eq!(get("segment_after_termination")["at"], 7);
|
|
}
|
|
|
|
/// §7 of the extension lists exactly the codes `Reason::OWN` has, each once, in order.
|
|
#[test]
|
|
fn the_extension_lists_the_reasons() {
|
|
let text = read("extensions/stream-termination.md");
|
|
let at = text.find("\n## 7. Reasons").expect("a §7");
|
|
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
|
|
let listed: Vec<String> = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect();
|
|
let want: Vec<String> = Reason::OWN.iter().map(|r| r.code().to_string()).collect();
|
|
assert_eq!(listed, want, "§7's table and Reason::OWN differ");
|
|
}
|
|
|
|
/// capability-constraints §4.2's match table names the bits the reference sets, in order.
|
|
#[test]
|
|
fn capability_constraints_lists_the_match_bits() {
|
|
let text = read("extensions/capability-constraints.md");
|
|
let at = text.find("\n### 4.2 The match").expect("a §4.2");
|
|
let names: Vec<String> = text[at..].lines().skip_while(|l| !l.starts_with("| bit")).filter(|l| l.starts_with("| ") && !l.starts_with("| bit") && !l.starts_with("|---")).take_while(|l| l.starts_with("| ")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect();
|
|
assert_eq!(names, ["resolution", "refresh", "bit_depth", "dynamic_range"]);
|
|
}
|