use dash_runtime::address::{PlanOptions, plan_representation};
use dash_runtime::mpd::Mpd;
use dash_runtime::selection::RepRef;
use quick_xml::Reader;
use quick_xml::events::Event;
const UNSUPPORTED_SIXTH_EDITION: &[&str] = &["example_G27.mpd"];
fn schema_dir() -> Option<std::path::PathBuf> {
let Some(root) = test_fixtures::private_root() else {
eprintln!("SKIPPED dash_iso_examples: private repo not available");
return None;
};
let dir = root.join("specs/iso_iec_23009-1_dash_2026_ed6_schema");
if dir.join("DASH-MPD.xsd").is_file() {
Some(dir)
} else {
panic!(
"dash_iso_examples: private repo is checked out but {} is missing",
dir.display()
);
}
}
fn independent_counts(xml: &str) -> (usize, usize, usize) {
let mut reader = Reader::from_str(xml);
let (mut periods, mut sets, mut reps) = (0, 0, 0);
loop {
match reader.read_event().expect("well-formed XML") {
Event::Start(e) | Event::Empty(e) => match e.name().as_ref() {
"Period" => periods += 1,
"AdaptationSet" => sets += 1,
"Representation" => reps += 1,
_ => {}
},
Event::Eof => break,
_ => {}
}
}
(periods, sets, reps)
}
fn examples() -> Option<Vec<(String, String)>> {
let dir = schema_dir()?;
let mut out = Vec::new();
for entry in std::fs::read_dir(&dir).expect("read schema dir") {
let path = entry.expect("dir entry").path();
if path.extension().and_then(|e| e.to_str()) == Some("mpd") {
let name = path.file_name().unwrap().to_string_lossy().into_owned();
out.push((name, std::fs::read_to_string(&path).expect("read example")));
}
}
out.sort();
Some(out)
}
#[test]
fn every_supported_iso_example_parses_with_the_independent_element_counts() {
let Some(all) = examples() else { return };
assert!(
all.len() >= 40,
"expected the 41 ISO examples, found {}",
all.len()
);
let mut checked = 0;
for (name, xml) in &all {
if UNSUPPORTED_SIXTH_EDITION.contains(&name.as_str()) {
continue;
}
let mpd = Mpd::parse(xml.as_bytes()).unwrap_or_else(|e| panic!("{name}: {e}"));
let (periods, sets, reps) = independent_counts(xml);
let got_sets: usize = mpd.periods.iter().map(|p| p.adaptation_sets.len()).sum();
let got_reps: usize = mpd
.periods
.iter()
.flat_map(|p| &p.adaptation_sets)
.map(|a| a.representations.len())
.sum();
assert_eq!(mpd.periods.len(), periods, "{name}: Period count");
assert_eq!(got_sets, sets, "{name}: AdaptationSet count");
assert_eq!(got_reps, reps, "{name}: Representation count");
checked += 1;
}
assert!(checked >= 39, "only {checked} examples were checked");
}
#[test]
fn the_sixth_edition_segment_timeline_pattern_example_is_rejected_not_guessed() {
let Some(all) = examples() else { return };
for name in UNSUPPORTED_SIXTH_EDITION {
let (_, xml) = all
.iter()
.find(|(n, _)| n == name)
.unwrap_or_else(|| panic!("{name} missing from the corpus"));
let rejected_at_parse = Mpd::parse(xml.as_bytes()).is_err();
let rejected_at_plan = Mpd::parse(xml.as_bytes()).is_ok_and(|mpd| {
plan_representation(&mpd, RepRef::at(0, 0, 0), &PlanOptions::default()).is_err()
});
assert!(
rejected_at_parse || rejected_at_plan,
"{name}: a Pattern timeline must be rejected, not guessed"
);
}
}