//! Exact tone mapping (extensions/exact-tonemap.md): the committed corpus //! `live-conformance/tonemap/` regenerates byte for byte, holds no other file, every case and //! envelope in it gets the outcome it states, the extension's §9 lists exactly the reasons the //! reference reports, §6 states the corpus's measurement, and `frames.rgb8` is the decode of //! the int_video clip whose digests the integer video corpus publishes. The generator itself //! asserts that the corpus gives every reason. #![cfg(feature = "exact_tonemap")] use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use wai::exact_tonemap::{corpus, Reason}; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read_tree(dir: &Path, prefix: &str, out: &mut BTreeMap>) { for e in std::fs::read_dir(dir).expect("the committed corpus").flatten() { let name = format!("{prefix}{}", e.file_name().to_string_lossy()); if e.file_type().expect("a type").is_dir() { read_tree(&e.path(), &format!("{name}/"), out); } else if name != "README.md" { out.insert(name, std::fs::read(e.path()).expect("a file")); } } } fn committed() -> BTreeMap> { let mut out = BTreeMap::new(); read_tree(&wai_dir().join("live-conformance/tonemap"), "", &mut out); out } fn section(text: &str, heading: &str) -> String { let at = text.find(heading).unwrap_or_else(|| panic!("a section {heading}")); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); text[at..end].to_string() } fn extension() -> String { std::fs::read_to_string(wai_dir().join("extensions/exact-tonemap.md")).expect("the extension") } #[test] fn the_committed_corpus_regenerates_and_every_case_gets_its_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}\n(run `wai_tonemap_vectors gen`)", fails.join("\n")); } /// §9 lists exactly the codes `Reason::ALL` has, each once, in order. #[test] fn the_extension_lists_the_reasons() { let listed: Vec = section(&extension(), "\n## 9. Reasons") .lines() .filter(|l| l.starts_with("| `")) .filter_map(|l| l.split('`').nth(1).map(String::from)) .collect(); let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); assert_eq!(listed, want, "§9's table and Reason::ALL differ"); } /// §6's table states the measurement the corpus records. #[test] fn the_extension_states_the_measurement() { let s = section(&extension(), "\n## 6. "); let c = corpus::build(); for (k, (r, n, max, mean)) in corpus::measurement_figures(&c.measurement) { let term = format!("`wai-tonemap:{k}`"); let row = s.lines().find(|l| l.starts_with(&format!("| {term} "))).unwrap_or_else(|| panic!("§6 has no row for {term}")); let cells: Vec<&str> = row.split('|').map(str::trim).collect(); assert_eq!(&cells[2..6], &[r.to_string().as_str(), n.to_string().as_str(), max.to_string().as_str(), mean.as_str()], "§6's row for {term}"); } } /// `frames.rgb8` is the int_video clip as `wai.video.int_motion` decodes it, and each frame's /// digest is the one the clip's sidecar publishes. #[test] fn the_frames_are_the_int_video_decode() { let (w, h, frames) = corpus::video_frames(); let committed = committed(); assert_eq!(committed["frames.rgb8"], frames.concat()); let sidecar: serde_json::Value = serde_json::from_slice(&std::fs::read(wai_dir().join(corpus::SIDECAR_PATH)).expect("the sidecar")).expect("json"); let want: Vec = sidecar["int_video"]["frames_sha256"].as_array().expect("digests").iter().map(|v| v.as_str().expect("hex").to_string()).collect(); let got: Vec = frames.iter().map(|f| wai::exact_tonemap::hex(&wai::exact_tonemap::digest(f))).collect(); assert_eq!(got, want); assert_eq!((w, h), (48, 48)); }