91 lines
5 KiB
Rust
91 lines
5 KiB
Rust
//! `wai.video.encode` (extensions/platform-encode.md): the committed corpus
|
|
//! `encode-conformance/` regenerates byte for byte from its pinned fixture streams, every case
|
|
//! gets the outcome it states, every record reason of §12 is in it, and §12 lists exactly the
|
|
//! reasons the reference reports. Where the platform's media framework is built in (macOS), an
|
|
//! encode through it of each codec holds to §4.3, signs a record that verifies, and decodes in
|
|
//! the platform's decoder to one picture per frame; a host with no hardware engine fails that
|
|
//! test rather than skipping it.
|
|
#![cfg(feature = "platform_encode")]
|
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
use std::path::PathBuf;
|
|
use wai::platform_encode::{corpus, record::Reason, sha256_hex};
|
|
|
|
fn dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../encode-conformance")
|
|
}
|
|
|
|
fn streams() -> BTreeMap<String, Vec<u8>> {
|
|
corpus::FIXTURES.iter().map(|f| (f.file.to_string(), std::fs::read(dir().join("streams").join(f.file)).expect("a fixture stream"))).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn the_fixture_streams_are_the_pinned_bytes() {
|
|
let pins: serde_json::Value = serde_json::from_slice(&std::fs::read(dir().join("streams.json")).expect("streams.json")).expect("JSON");
|
|
let s = streams();
|
|
assert_eq!(pins.as_object().expect("an object").len(), s.len(), "streams.json pins every fixture and nothing else");
|
|
for (k, v) in &s {
|
|
assert_eq!(pins[k].as_str(), Some(sha256_hex(v).as_str()), "{k} is not the bytes streams.json pins");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn the_committed_corpus_regenerates_byte_for_byte() {
|
|
let generated = corpus::generate(&streams());
|
|
for (p, b) in &generated {
|
|
assert_eq!(std::fs::read(dir().join(p)).ok().as_ref(), Some(b), "{p} does not regenerate: run `wai_encode_vectors gen`");
|
|
}
|
|
for sub in ["records", "mutated"] {
|
|
for e in std::fs::read_dir(dir().join(sub)).expect("the corpus").flatten() {
|
|
let rel = format!("{sub}/{}", e.file_name().to_string_lossy());
|
|
assert!(generated.contains_key(&rel), "{rel} is not generated");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn every_case_gets_its_outcome_and_every_reason_appears() {
|
|
assert!(corpus::check(&dir()).is_ok(), "{:?}", corpus::check(&dir()));
|
|
let cases = corpus::cases(&streams());
|
|
let seen: BTreeSet<&str> = cases.iter().filter_map(|c| c.expect.map(Reason::code)).collect();
|
|
// The rendition's reason is the packager's corpus's (wai-pack tests/encode_rung.rs).
|
|
let want: BTreeSet<&str> = Reason::ALL.iter().map(|r| r.code()).filter(|c| *c != "encode-package-mismatch").collect();
|
|
assert_eq!(seen, want, "every record reason but the rendition's has a case");
|
|
assert!(cases.iter().filter(|c| c.expect.is_none()).count() >= corpus::FIXTURES.len(), "every fixture is accepted");
|
|
}
|
|
|
|
#[test]
|
|
fn the_reasons_table_is_the_references() {
|
|
let text = std::fs::read_to_string(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../extensions/platform-encode.md")).expect("the extension");
|
|
let at = text.find("## 12. Reasons").expect("§12");
|
|
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
|
|
let table: BTreeSet<String> = text[at..end]
|
|
.lines()
|
|
.filter(|l| l.starts_with("| `") && !l.contains("| producer |"))
|
|
.filter_map(|l| l.split('`').nth(1).map(String::from))
|
|
.collect();
|
|
let code: BTreeSet<String> = Reason::ALL.iter().map(|r| r.code().to_string()).collect();
|
|
assert_eq!(table, code, "§12's record reasons are exactly Reason::ALL");
|
|
}
|
|
|
|
/// The media framework's encoders, each codec, on this host: §4.3 holds, a signed record
|
|
/// verifies with the stream and the source held, and the platform's decoder gives one
|
|
/// picture per frame.
|
|
#[cfg(target_vendor = "apple")]
|
|
#[test]
|
|
fn the_platform_encoder_makes_a_stream_that_holds_and_decodes() {
|
|
use wai::platform_encode::{self as pe, Api, Codec, Options, record};
|
|
let src = corpus::source();
|
|
for codec in [Codec::Avc, Codec::Hevc] {
|
|
let p = pe::prepare(&src, codec, None, 400).expect("the corpus source");
|
|
let e = pe::encode(Api::Platform, &p.cfg, &Options::default(), p.frames.iter().map(Vec::as_slice), &corpus::PLAN).unwrap_or_else(|e| panic!("{}: {e}", codec.name()));
|
|
assert!(e.identity.hardware, "{}: the framework chose its software encoder: {:?}", codec.name(), e.identity);
|
|
let body = record::body(&e, &p.cfg, &p.source, &corpus::PLAN, None, None, corpus::ISSUED_AT);
|
|
let text = record::to_text(&record::sign(body, &ed25519_dalek::SigningKey::from_bytes(&corpus::SEED)));
|
|
let (hd, frames) = pe::y4m::read_all(&src).expect("the source");
|
|
let planes = frames.concat();
|
|
record::verify(&text, &record::Held { stream: Some(&e.stream), source: Some((&hd, &planes)) }).unwrap_or_else(|r| panic!("{}: {r}", codec.name()));
|
|
let n = pe::platform_decode(codec, &e.stream, &e.units, |f| if f.len() == p.cfg.picture_len() { Ok(()) } else { Err("a picture of another size".into()) }).expect("the platform decodes it");
|
|
assert_eq!(n, corpus::FRAMES, "{}: one picture per frame", codec.name());
|
|
}
|
|
}
|