//! `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 = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); let want: Vec = 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 = 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"]); }