//! `wai.live.provenance` (extensions/live-provenance.md): the committed corpus //! `live-conformance/c2pa-live/` regenerates byte for byte from its fixed seeds and the //! packaging corpus's floor, holds no other file, every vector in it gets the outcome it //! states, every reason of §12 refuses a vector, and §12 lists exactly the reasons the //! reference reports. #![cfg(feature = "live_provenance")] use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use wai::live_provenance::corpus::{self, Media}; use wai::live_provenance::Reason; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read_tree(root: &Path, dir: &Path, out: &mut BTreeMap>) { for e in std::fs::read_dir(dir).expect("the committed corpus").flatten() { let p = e.path(); if p.is_dir() { read_tree(root, &p, out); } else { let rel = p.strip_prefix(root).expect("under the root").to_string_lossy().replace('\\', "/"); if rel != "README.md" { out.insert(rel, std::fs::read(&p).expect("a file")); } } } } fn committed() -> BTreeMap> { let dir = wai_dir().join("live-conformance/c2pa-live"); let mut out = BTreeMap::new(); read_tree(&dir, &dir, &mut out); out } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { let files = committed(); let media = Media::load(&wai_dir()).expect("the packaging corpus's floor"); let generated: BTreeMap> = corpus::generate(&media).into_iter().collect(); let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect(); let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect(); let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect(); assert!( extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_live_provenance_vectors gen`" ); } #[test] fn every_vector_gets_its_stated_outcome_and_every_reason_refuses_one() { let fails = corpus::verify_files(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// §12 of the extension lists exactly the codes `Reason::ALL` has, each once, in order, with /// the two C2PA codes the reference reports. #[test] fn the_extension_lists_the_reasons() { let text = std::fs::read_to_string(wai_dir().join("extensions/live-provenance.md")).expect("the extension"); let at = text.find("\n## 12. Reasons").expect("a §12"); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); let rows: Vec> = text[at..end] .lines() .filter(|l| l.starts_with("| `")) .map(|l| l.split('|').map(|c| c.trim().trim_matches('`').to_string()).collect()) .collect(); let listed: Vec<&str> = rows.iter().map(|r| r[1].as_str()).collect(); let want: Vec<&str> = Reason::ALL.iter().map(|r| r.code()).collect(); assert_eq!(listed, want, "§12's table and Reason::ALL differ"); for (r, row) in Reason::ALL.iter().zip(&rows) { let code = |c: &str| if c == "—" { None } else { Some(c.to_string()) }; assert_eq!(code(&row[3]), r.c2pa().map(String::from), "{}: the §15 code", r.code()); assert_eq!(code(&row[4]), r.livevideo().map(String::from), "{}: the §19.7 code", r.code()); } }