//! `wai.delivery.timeliness` (extensions/timeliness.md): the committed corpus //! `live-conformance/timeliness/` regenerates byte for byte from its fixed seeds, holds no other //! file, every case in it gets the outcome it states, §9 lists exactly the reasons the reference //! reports, §6.3 exactly its outcomes and §7.5.3 its run's actions, jwp-receipts §3.1 lists the //! class codes at their steps, and delivery-telemetry §5.1 lists the switch reason of §8.6. #![cfg(feature = "timeliness")] use std::collections::BTreeMap; use std::path::PathBuf; use wai::timeliness::{corpus, scheduler, CODES, OUTCOMES}; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(path: &str) -> String { std::fs::read_to_string(wai_dir().join(path)).unwrap_or_else(|e| panic!("{path}: {e}")) } fn committed() -> BTreeMap { let dir = wai_dir().join("live-conformance/timeliness"); std::fs::read_dir(&dir) .expect("the committed corpus") .flatten() .map(|e| (e.file_name().to_string_lossy().into_owned(), e.path())) .filter(|(n, _)| n != "README.md") .map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file"))) .collect() } /// The text of the section of `text` that starts at the line `heading`, to the next heading of /// the same level. fn section<'a>(text: &'a str, heading: &str) -> &'a str { let at = text.find(&format!("\n{heading}")).unwrap_or_else(|| panic!("no {heading}")); let level = heading.split(' ').next().expect("a heading's hashes"); let end = text[at + 1..].find(&format!("\n{level} ")).map_or(text.len(), |e| at + 1 + e); &text[at..end] } /// The first code of each table row of a section: `| \`code\` | …`. fn listed(section: &str) -> Vec { section.lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect() } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { let files = committed(); let generated: BTreeMap = corpus::generate().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_timeliness_vectors gen`"); } #[test] fn every_case_gets_its_stated_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// §9 of the extension lists exactly the codes `CODES` has, each once, in order. #[test] fn the_extension_lists_the_reasons() { let text = read("extensions/timeliness.md"); let want: Vec = CODES.iter().map(|c| c.to_string()).collect(); assert_eq!(listed(section(&text, "## 9. Reasons")), want, "§9's table and CODES differ"); } /// §6.3's table lists exactly the outcomes `OUTCOMES` has, in order. #[test] fn the_extension_lists_the_outcomes() { let text = read("extensions/timeliness.md"); let want: Vec = OUTCOMES.iter().map(|c| c.to_string()).collect(); assert_eq!(listed(section(&text, "### 6.3 Lines")), want, "§6.3's table and OUTCOMES differ"); } /// §7.5.3 states each action a run decides, in backticks. #[test] fn the_extension_states_the_runs_actions() { let text = read("extensions/timeliness.md"); let steps = section(&text, "#### 7.5.3 Decisions"); for action in scheduler::ACTIONS { assert!(steps.contains(&format!("`{action}`")), "§7.5.3 does not state {action}"); } } /// jwp-receipts §3.1's table lists the class's codes: its rules at step 4, its checks against the /// group, its objects and their rungs at step 6. #[test] fn jwp_receipts_lists_the_class_codes() { let text = read("extensions/jwp-receipts.md"); let class: Vec<&str> = CODES.iter().copied().filter(|c| c.starts_with("timeliness_") && *c != "timeliness_contradicted").collect(); assert_eq!(class.len(), 14, "the class's codes"); for (k, code) in class.iter().enumerate() { let step = if k < 6 { 4 } else { 6 }; assert!(text.contains(&format!("| {step} | `{code}` |")), "jwp-receipts §3.1 does not list {code} at step {step}"); } } /// delivery-telemetry §5.1 lists the switch reason a sink records when it presents a fallback /// (§8.6), and the reference's list of switch reasons has it. #[test] fn delivery_telemetry_lists_the_switch_reason() { let text = read("extensions/delivery-telemetry.md"); let reasons = section(&text, "## 5. Switch reasons"); assert!(reasons.contains("`not-held-by-deadline`"), "delivery-telemetry §5.1 does not list not-held-by-deadline"); assert!(wai::delivery_terms::SWITCH_REASONS.contains(&"not-held-by-deadline")); }