use core::time::Duration;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use dvb_conformance::{ConformanceMonitor, Indicator, Priority};
const TS_PACKET_SIZE: usize = 188;
const INTER_PACKET_US: u64 = 40;
fn read_fixture(name: &str) -> Vec<u8> {
let base = concat!(env!("CARGO_MANIFEST_DIR"), "/../fixtures");
let subdir = if name == "m6-single.ts" {
"/ts/"
} else {
"/dvb-si/"
};
let path = format!("{base}{subdir}{name}");
let mut f = File::open(&path).unwrap_or_else(|e| panic!("cannot open {path}: {e}"));
let mut buf = Vec::new();
f.read_to_end(&mut buf).unwrap();
buf
}
fn run_monitor_on_fixture(name: &str) -> Vec<dvb_conformance::ConformanceEvent> {
let data = read_fixture(name);
let mut monitor = ConformanceMonitor::new();
let mut all_events = Vec::new();
let n_packets = data.len() / TS_PACKET_SIZE;
for i in 0..n_packets {
let start = i * TS_PACKET_SIZE;
let end = start + TS_PACKET_SIZE;
if end > data.len() {
break;
}
let t = Duration::from_micros(i as u64 * INTER_PACKET_US);
let events = monitor.feed(&data[start..end], t);
all_events.extend(events.to_vec());
}
all_events
}
#[test]
fn m6_single_no_non_cc_priority1_events() {
let events = run_monitor_on_fixture("m6-single.ts");
let non_cc_p1: Vec<_> = events
.iter()
.filter(|e| {
matches!(e.priority, Priority::First) && e.indicator != Indicator::ContinuityCountError
})
.collect();
if !non_cc_p1.is_empty() {
for e in &non_cc_p1 {
eprintln!(
"non-CC P1 event on m6-single.ts: {:?} pid={:?} detail={}",
e.indicator, e.pid, e.detail
);
}
panic!(
"m6-single.ts raised {} non-CC Priority-1 event(s) — investigate",
non_cc_p1.len()
);
}
let cc_count = events
.iter()
.filter(|e| e.indicator == Indicator::ContinuityCountError)
.count();
assert!(
cc_count > 0,
"m6-single.ts is known to have CC discontinuities — expected some ContinuityCountError events"
);
let tstd_errors: Vec<_> = events
.iter()
.filter(|e| {
e.indicator == Indicator::BufferError
|| e.indicator == Indicator::EmptyBufferError
|| e.indicator == Indicator::DataDelayError
})
.collect();
if !tstd_errors.is_empty() {
for e in &tstd_errors {
eprintln!(
"T-STD event on m6-single.ts: {:?} pid={:?} detail={}",
e.indicator, e.pid, e.detail
);
}
panic!(
"m6-single.ts raised {} T-STD event(s) on a clean fixture — investigate",
tstd_errors.len()
);
}
}
#[test]
fn tnt_fixture_events_are_documented() {
let events = run_monitor_on_fixture("tnt-5w-12732v-isi6-10s.ts");
let p1_count = events
.iter()
.filter(|e| matches!(e.priority, Priority::First))
.count();
eprintln!(
"tnt fixture: {} total events, {} P1 (expected for T2-MI outer stream)",
events.len(),
p1_count
);
}
fn read_france_tnt() -> Option<Vec<u8>> {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let path = manifest_dir
.parent() .unwrap()
.join(".test-streams")
.join("france-tnt-uhf32.ts");
let mut f = File::open(&path).ok()?;
let mut buf = Vec::new();
f.read_to_end(&mut buf).ok()?;
Some(buf)
}
#[test]
fn france_tnt_uhf32_clean_on_new_736_indicators() {
let data = match read_france_tnt() {
Some(d) => d,
None => {
eprintln!(
"SKIP: .test-streams/france-tnt-uhf32.ts not found — fetch the private test-streams fixture",
);
return;
}
};
let mut monitor = ConformanceMonitor::new();
let n_packets = data.len() / TS_PACKET_SIZE;
let new_indicators: &[Indicator] = &[
Indicator::EitPfError,
Indicator::NitOtherError,
Indicator::SdtOtherError,
Indicator::EitOtherError,
Indicator::SiMinGapError,
];
let mut violations: Vec<dvb_conformance::ConformanceEvent> = Vec::new();
for i in 0..n_packets {
let start = i * TS_PACKET_SIZE;
let end = start + TS_PACKET_SIZE;
if end > data.len() {
break;
}
let t = Duration::from_micros(i as u64 * INTER_PACKET_US);
let events = monitor.feed(&data[start..end], t);
for e in events {
if new_indicators.contains(&e.indicator) {
violations.push(e.clone());
}
}
}
if !violations.is_empty() {
for v in &violations {
eprintln!(
"#736 violation on france-tnt-uhf32.ts: {:?} pid={:?} at={:?} detail={}",
v.indicator, v.pid, v.at, v.detail
);
}
panic!(
"france-tnt-uhf32.ts raised {} #736-indicator violation(s) on a clean real stream — the check is wrong, not the stream",
violations.len()
);
}
}