An entry's fallback is an ordered ladder: null, one rung as an object, or two to eight rungs as an array in strictly falling value, no object named twice (§4.2 rule 3.4, timeliness_ladder_invalid). One rung has one form, the object, so an entry with one fallback object is a ladder of one rung and reads, judges and plans as it did; the cache-authority corpus regenerates unchanged. §5.3 states which rung applies: at by_ms the primary held, else the first rung held; holding neither, the sink has passed every rung, states `passed`, presents the primary late at its value at arrival, and at the end, having presented nothing, abandons and states `ended`. §6.3 and §7.3 take the ladder, and a plan's choice names its rung. §7.5 and timeliness::scheduler run a sink's plans against the instants it first held each object: fetch, abandon, present, passed and ended by exact steps, a function of those inputs alone. A later plan replaces the one in force wholly. plans_invalid and arrivals_invalid refuse a run's inputs. The corpus gains the group E, ladder claims, verdicts and plans, and runs, each outcome declared in the generator. timeliness_verify.py writes the ladder and the run from the text, with mutations and weakenings for both, and claim_verify.py holds rule 3.4. Co-Authored-By: Claude Opus 5.5 <[email protected]>
108 lines
4.9 KiB
Rust
108 lines
4.9 KiB
Rust
//! `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<String, String> {
|
|
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<String> {
|
|
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<String, String> = 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<String> = 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<String> = 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"));
|
|
}
|