//! `wai.live.room` (extensions/live-rooms.md): the committed corpus //! `live-conformance/rooms/vectors.json` regenerates byte for byte from its fixed seeds, //! every vector in it gets the outcome it states, it refuses with every reason the //! extension adds, and the extension's §11 lists exactly those reasons. #![cfg(feature = "live_room")] use std::collections::BTreeSet; use std::path::PathBuf; use wai::live_room::{corpus, Reason}; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn committed() -> String { std::fs::read_to_string(wai_dir().join("live-conformance/rooms/vectors.json")).expect("the committed corpus") } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { assert!(committed() == corpus::generate(), "live-conformance/rooms/vectors.json differs from `wai_live_room_vectors gen`"); } #[test] fn every_vector_gets_its_stated_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// Every reason this extension adds has a vector, a proof (§10.5), a request of `seek` /// (§10.6) or a case of `frames` (§8.3) refused with it, and the corpus has the room shapes /// §12 names, accepted. #[test] fn the_corpus_covers_every_reason_and_room() { let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); let vectors = doc["vectors"].as_array().unwrap(); let proofs = doc["proofs"].as_array().unwrap(); let seek = doc["seek"].as_array().unwrap(); let frames = doc["frames"].as_array().unwrap(); let reasons: BTreeSet<&str> = vectors.iter().chain(proofs).chain(seek).chain(frames).filter_map(|v| v["reason"].as_str()).collect(); for r in Reason::ALL { assert!(reasons.contains(r.code()), "nothing in the corpus is refused with {r}"); } for name in ["lesson", "broadcast", "takeover", "redundant_failover", "promote_viewer", "dvr", "ai_participant", "ai_captioner", "jam", "timed_profile"] { let v = vectors.iter().find(|v| v["name"] == name).unwrap_or_else(|| panic!("no vector {name}")); assert_eq!(v["outcome"], "accept", "{name}"); } let outcomes: BTreeSet<&str> = seek.iter().filter_map(|c| c["outcome"].as_str()).collect(); assert_eq!(outcomes, BTreeSet::from(["refuse", "serve"]), "seek has requests served and refused"); for name in ["frames_jam", "frames_timed_profile", "frames_untimed"] { let c = frames.iter().find(|c| c["name"] == name).unwrap_or_else(|| panic!("no frame case {name}")); assert_eq!(c["outcome"], "accept", "{name}"); } } /// §8.3 over one packet: the frame size of every TOC configuration (RFC 6716 §3.1, Table 2), /// and a profile that states no frame reads nothing. #[test] fn the_toc_byte_states_the_frame() { use wai::live_room::{frame_check, opus_frame_us, Reason as R, OPUS_PACKET_CAPABILITY}; let want = |c: u8| match c { 0..=11 => [10_000, 20_000, 40_000, 60_000][c as usize % 4], 12..=15 => [10_000, 20_000][c as usize % 2], _ => [2_500, 5_000, 10_000, 20_000][c as usize % 4], }; for c in 0..32u8 { assert_eq!(opus_frame_us(c), want(c), "config {c}"); } let envelope = |p: &[u8]| { let m = format!("{{\"model_requirement\":{{\"capability\":\"{OPUS_PACKET_CAPABILITY}\"}}}}"); let mut b = b"WAI1".to_vec(); b.extend_from_slice(&(m.len() as u32).to_le_bytes()); b.extend_from_slice(m.as_bytes()); b.extend_from_slice(&(p.len() as u32).to_le_bytes()); b.extend_from_slice(p); b }; assert_eq!(frame_check(5_000, &envelope(&[29 << 3])), Ok(Some(5_000)), "CELT-only 5 ms, mono, one frame"); assert_eq!(frame_check(5_000, &envelope(&[29 << 3 | 2])), Err(R::FrameMismatch), "two frames of 5 ms (code 2)"); assert_eq!(frame_check(20_000, &envelope(&[1 << 3])), Ok(Some(20_000)), "SILK-only 20 ms"); assert_eq!(frame_check(5_000, &envelope(&[])), Err(R::FrameUnreadable), "a packet of no bytes"); assert_eq!(frame_check(0, b"anything"), Ok(None), "a profile that states no frame"); } /// §11 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. #[test] fn the_extension_lists_the_reasons() { let text = std::fs::read_to_string(wai_dir().join("extensions/live-rooms.md")).expect("the extension"); let at = text.find("\n## 11. Reasons").expect("a §11"); 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::ALL.iter().map(|r| r.code().to_string()).collect(); assert_eq!(listed, want, "§11's table and Reason::ALL differ"); }