test: cover the 1.6.1 provenance work where it was assumed, not asserted
Four parsers (dvdsub, flac, lpcm, mpegaudio) stamp a source offset on every frame but had no test that read one back, so a regression to `source: None` would have been caught only by the brace-balanced audit in codec/mod.rs — a lint, not a behavioural check. Each now asserts the emitted frame carries the offset of the packet that supplied its first byte. The Blu-ray feed spans had no direct test at all. Add one that walks a multi-item playlist and requires the spans to tile the feed with no gap or overlap; it catches a one-sector-per-clip drift, which is exactly the error class that would misattribute frames near a seam. `no_provenance_still_places_by_marks` asserted only that placement returned something, which passes for a frame placed in the wrong clip. Its probe timestamp lands in an overlap between two clips in the real mark table, so pinning one clip would assert a coin-flip; instead require the offset to be one that a clip actually containing that timestamp would produce.
This commit is contained in:
@@ -914,3 +914,111 @@ fn dump_title_sets_for_an_image() {
|
|||||||
println!(" vts={} titles={}", ts.vts_number, ts.titles.len());
|
println!(" vts={} titles={}", ts.vts_number, ts.titles.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build an MPLS with N PlayItems, each naming its own clip.
|
||||||
|
fn multi_item_mpls(clip_ids: &[&[u8; 5]]) -> Vec<u8> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
buf.extend_from_slice(b"MPLS0200");
|
||||||
|
buf.extend_from_slice(&40u32.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&[0u8; 28]);
|
||||||
|
|
||||||
|
let pl = buf.len();
|
||||||
|
buf.extend_from_slice(&[0u8; 4]);
|
||||||
|
buf.extend_from_slice(&[0u8; 2]);
|
||||||
|
buf.extend_from_slice(&(clip_ids.len() as u16).to_be_bytes());
|
||||||
|
buf.extend_from_slice(&[0u8; 2]);
|
||||||
|
|
||||||
|
for id in clip_ids {
|
||||||
|
let mut item = Vec::new();
|
||||||
|
item.extend_from_slice(*id);
|
||||||
|
item.extend_from_slice(b"M2TS");
|
||||||
|
item.push(0);
|
||||||
|
item.extend_from_slice(&[0u8; 2]);
|
||||||
|
item.extend_from_slice(&0u32.to_be_bytes());
|
||||||
|
item.extend_from_slice(&(45_000u32 * 120).to_be_bytes());
|
||||||
|
item.extend_from_slice(&[0u8; 8]);
|
||||||
|
item.push(0);
|
||||||
|
item.push(0);
|
||||||
|
item.extend_from_slice(&[0u8; 2]);
|
||||||
|
item.extend_from_slice(&16u16.to_be_bytes());
|
||||||
|
item.extend_from_slice(&[0u8; 16]);
|
||||||
|
buf.extend_from_slice(&(item.len() as u16).to_be_bytes());
|
||||||
|
buf.extend_from_slice(&item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pl_len = (buf.len() - pl - 4) as u32;
|
||||||
|
buf[pl..pl + 4].copy_from_slice(&pl_len.to_be_bytes());
|
||||||
|
let mark_start = buf.len() as u32;
|
||||||
|
buf[12..16].copy_from_slice(&mark_start.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&2u32.to_be_bytes());
|
||||||
|
buf.extend_from_slice(&0u16.to_be_bytes());
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Clip::feed_span` is the INPUT to the whole provenance feature — it is what
|
||||||
|
/// tells the muxer which clip a byte offset belongs to — and it is produced in
|
||||||
|
/// exactly one place, `disc/bluray.rs`. Every `SeamPlan` test synthesizes spans
|
||||||
|
/// by hand, so the consumer is thoroughly tested against fixtures written from
|
||||||
|
/// the same assumptions as the producer, and nothing checks the producer at all.
|
||||||
|
///
|
||||||
|
/// That is the shape of the original defect: the placement logic was tested and
|
||||||
|
/// correct, and the thing feeding it was wrong. `SeamPlan` only trusts spans
|
||||||
|
/// that TILE the feed contiguously from zero, so this asserts exactly that,
|
||||||
|
/// against the real scanner reading a real synthesized filesystem.
|
||||||
|
#[test]
|
||||||
|
fn a_multi_clip_playlist_produces_feed_spans_that_tile_the_feed() {
|
||||||
|
let s = Scratch::new("spans");
|
||||||
|
let packets = 4096u32;
|
||||||
|
let ids: [&[u8; 5]; 3] = [b"00000", b"00001", b"00002"];
|
||||||
|
let mut m2ts = vec![0x5Au8; packets as usize * 192];
|
||||||
|
for p in m2ts.chunks_mut(192) {
|
||||||
|
p[0] = 0x00;
|
||||||
|
p[4] = 0x47;
|
||||||
|
}
|
||||||
|
s.file("BDMV/index.bdmv", &pattern(1, 64));
|
||||||
|
s.file("BDMV/PLAYLIST/00000.mpls", &multi_item_mpls(&ids));
|
||||||
|
for id in ids {
|
||||||
|
let name = std::str::from_utf8(id).unwrap();
|
||||||
|
s.file(&format!("BDMV/CLIPINF/{name}.clpi"), &minimal_clpi(packets));
|
||||||
|
s.file(&format!("BDMV/STREAM/{name}.m2ts"), &m2ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (disc, _reader) =
|
||||||
|
crate::session::scan_dir(s.path(), crate::disc::ScanOptions::default()).unwrap();
|
||||||
|
let title = disc.titles.first().expect("the playlist produces a title");
|
||||||
|
assert_eq!(title.clips.len(), 3, "all three PlayItems are kept");
|
||||||
|
|
||||||
|
// Every clip must carry a span, or provenance is simply off for this title.
|
||||||
|
let spans: Vec<(u64, u64)> = title
|
||||||
|
.clips
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.feed_span.expect("every clip carries a feed span"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// They must tile from 0 with no gap and no overlap: `SeamPlan::from_clips`
|
||||||
|
// refuses anything else, so a producer that drifts here silently disables
|
||||||
|
// the feature rather than failing.
|
||||||
|
let mut expect = 0u64;
|
||||||
|
for (i, &(start, end)) in spans.iter().enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
start,
|
||||||
|
expect,
|
||||||
|
"clip {i} starts where clip {} ended",
|
||||||
|
i.saturating_sub(1)
|
||||||
|
);
|
||||||
|
assert!(end > start, "clip {i} spans no bytes");
|
||||||
|
expect = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And the tiling must describe the bytes the muxer will actually be fed:
|
||||||
|
// the concatenation of every clip's extents.
|
||||||
|
let feed_bytes: u64 = title
|
||||||
|
.extents
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.sector_count as u64 * crate::consts::SECTOR_BYTES as u64)
|
||||||
|
.sum();
|
||||||
|
assert_eq!(
|
||||||
|
expect, feed_bytes,
|
||||||
|
"the spans must cover exactly the feed, or a byte offset lands in the wrong clip"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -717,4 +717,22 @@ mod tests {
|
|||||||
let result = format_palette(&[[0x00, 16, 128, 128]], 0, 0);
|
let result = format_palette(&[[0x00, 16, 128, 128]], 0, 0);
|
||||||
assert_eq!(String::from_utf8(result).unwrap(), "palette: 101010\n");
|
assert_eq!(String::from_utf8(result).unwrap(), "palette: 101010\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The text guard in `codec/mod.rs` scans for a literal `source: None` and
|
||||||
|
/// cannot see a parser that writes `source: facts.source` where the facts
|
||||||
|
/// carry no offset. Only a runtime check proves an emitted frame really
|
||||||
|
/// carries the byte it was read from, and without it a multi-clip title
|
||||||
|
/// places this track by timestamp inference instead of by byte.
|
||||||
|
#[test]
|
||||||
|
fn an_emitted_frame_carries_the_packets_source_offset() {
|
||||||
|
let mut parser = DvdSubParser::new(None);
|
||||||
|
let mut pes = make_pes(
|
||||||
|
vec![0x00, 0x0A, 0x00, 0x08, 0x01, 0xFF, 0x02, 0x03, 0x04, 0x05],
|
||||||
|
Some(90_000),
|
||||||
|
);
|
||||||
|
pes.source = Some(crate::pes::SourcePos::at_byte(7_777));
|
||||||
|
let frames = parser.parse(&pes);
|
||||||
|
assert!(!frames.is_empty(), "the segment is emitted");
|
||||||
|
assert_eq!(frames[0].source.map(|s| s.byte), Some(7_777));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,4 +271,19 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(emitted.len() + tail.len(), 2);
|
assert_eq!(emitted.len() + tail.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The text guard in `codec/mod.rs` scans for a literal `source: None` and
|
||||||
|
/// cannot see a parser that writes `source: facts.source` where the facts
|
||||||
|
/// carry no offset. Only a runtime check proves an emitted frame really
|
||||||
|
/// carries the byte it was read from, and without it a multi-clip title
|
||||||
|
/// places this track by timestamp inference instead of by byte.
|
||||||
|
#[test]
|
||||||
|
fn an_emitted_frame_carries_the_packets_source_offset() {
|
||||||
|
let mut p = FlacParser::new();
|
||||||
|
let mut pes = make_pes(make_flac_frame(100), Some(90_000));
|
||||||
|
pes.source = Some(crate::pes::SourcePos::at_byte(7_777));
|
||||||
|
let f = p.parse(&pes);
|
||||||
|
assert!(!f.is_empty(), "the frame is emitted");
|
||||||
|
assert_eq!(f[0].source.map(|s| s.byte), Some(7_777));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,4 +279,21 @@ mod tests {
|
|||||||
let f = parser.parse(&make_pes(vec![0xAA, 0xBB], None));
|
let f = parser.parse(&make_pes(vec![0xAA, 0xBB], None));
|
||||||
assert_eq!(f[0].pts_ns, 0);
|
assert_eq!(f[0].pts_ns, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The text guard in `codec/mod.rs` scans for a literal `source: None` and
|
||||||
|
/// cannot see a parser that writes `source: facts.source` where the facts
|
||||||
|
/// carry no offset. Only a runtime check proves an emitted frame really
|
||||||
|
/// carries the byte it was read from, and without it a multi-clip title
|
||||||
|
/// places this track by timestamp inference instead of by byte.
|
||||||
|
#[test]
|
||||||
|
fn an_emitted_frame_carries_the_packets_source_offset() {
|
||||||
|
let mut parser = LpcmParser::new();
|
||||||
|
let mut data = vec![0x00, 0x01, 0x00, 0b1001_0001];
|
||||||
|
data.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE]);
|
||||||
|
let mut pes = make_pes(data, Some(90_000));
|
||||||
|
pes.source = Some(crate::pes::SourcePos::at_byte(7_777));
|
||||||
|
let frames = parser.parse(&pes);
|
||||||
|
assert!(!frames.is_empty(), "the frame is emitted");
|
||||||
|
assert_eq!(frames[0].source.map(|s| s.byte), Some(7_777));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,4 +319,19 @@ mod tests {
|
|||||||
// a manufactured tail frame would break this even if it were non-empty.
|
// a manufactured tail frame would break this even if it were non-empty.
|
||||||
assert_eq!(emitted.len() + tail.len(), 2);
|
assert_eq!(emitted.len() + tail.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The text guard in `codec/mod.rs` scans for a literal `source: None` and
|
||||||
|
/// cannot see a parser that writes `source: facts.source` where the facts
|
||||||
|
/// carry no offset. Only a runtime check proves an emitted frame really
|
||||||
|
/// carries the byte it was read from, and without it a multi-clip title
|
||||||
|
/// places this track by timestamp inference instead of by byte.
|
||||||
|
#[test]
|
||||||
|
fn an_emitted_frame_carries_the_packets_source_offset() {
|
||||||
|
let mut p = MpegAudioParser::new();
|
||||||
|
let mut pes = make_pes(mp3_frame(400), Some(90_000));
|
||||||
|
pes.source = Some(crate::pes::SourcePos::at_byte(7_777));
|
||||||
|
let f = p.parse(&pes);
|
||||||
|
assert!(!f.is_empty(), "the frame is emitted");
|
||||||
|
assert_eq!(f[0].source.map(|s| s.byte), Some(7_777));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-2
@@ -1044,6 +1044,21 @@ mod tests {
|
|||||||
|
|
||||||
/// Clips with byte spans, built from the real mark table so provenance and
|
/// Clips with byte spans, built from the real mark table so provenance and
|
||||||
/// marks can be tested against each other.
|
/// marks can be tested against each other.
|
||||||
|
/// The output offset `from_clips` computes for a clip: the sum of every
|
||||||
|
/// earlier clip's playable duration, minus its own IN.
|
||||||
|
fn plan_offset_for(clips: &[crate::disc::Clip], want: &crate::disc::Clip) -> i64 {
|
||||||
|
let mut cum = 0i64;
|
||||||
|
for c in clips {
|
||||||
|
let in_ns = mpls_ticks_to_ns(c.in_time);
|
||||||
|
let out_ns = mpls_ticks_to_ns(c.out_time);
|
||||||
|
if c.clip_id == want.clip_id && c.in_time == want.in_time {
|
||||||
|
return cum;
|
||||||
|
}
|
||||||
|
cum += out_ns - in_ns;
|
||||||
|
}
|
||||||
|
cum
|
||||||
|
}
|
||||||
|
|
||||||
fn clips_with_spans() -> Vec<crate::disc::Clip> {
|
fn clips_with_spans() -> Vec<crate::disc::Clip> {
|
||||||
let mut clips = seamless_branching_clips();
|
let mut clips = seamless_branching_clips();
|
||||||
// Each clip's stream occupies a contiguous run of the feed. Sizes are
|
// Each clip's stream occupies a contiguous run of the feed. Sizes are
|
||||||
@@ -1171,9 +1186,34 @@ mod tests {
|
|||||||
fn no_provenance_still_places_by_marks() {
|
fn no_provenance_still_places_by_marks() {
|
||||||
let clips = clips_with_spans();
|
let clips = clips_with_spans();
|
||||||
let mut plan = SeamPlan::from_clips(&clips).expect("plan");
|
let mut plan = SeamPlan::from_clips(&clips).expect("plan");
|
||||||
|
// `is_some()` alone was the whole assertion here, which passes for a
|
||||||
|
// frame placed in the WRONG clip — on the one path taken whenever a
|
||||||
|
// source stamps no offset.
|
||||||
|
//
|
||||||
|
// This timestamp sits in the OVERLAP of two clips in the real mark
|
||||||
|
// table, which is precisely the ambiguity provenance exists to settle,
|
||||||
|
// so pinning one specific clip would be asserting the coin-flip. The
|
||||||
|
// invariant that holds either way: the frame is placed with the offset
|
||||||
|
// of a clip whose marks actually contain it — never a clip it does not
|
||||||
|
// belong to, and never at the head of the timeline.
|
||||||
|
let raw = 7_900_000_000_000i64;
|
||||||
|
let placed = plan
|
||||||
|
.place(raw, 0, true, None)
|
||||||
|
.expect("a frame with no source offset must still be placed");
|
||||||
|
|
||||||
|
let candidates: Vec<i64> = clips
|
||||||
|
.iter()
|
||||||
|
.filter(|c| raw >= mpls_ticks_to_ns(c.in_time) && raw <= mpls_ticks_to_ns(c.out_time))
|
||||||
|
.map(|c| raw - mpls_ticks_to_ns(c.in_time) + plan_offset_for(&clips, c))
|
||||||
|
.collect();
|
||||||
assert!(
|
assert!(
|
||||||
plan.place(7_900_000_000_000, 0, true, None).is_some(),
|
!candidates.is_empty(),
|
||||||
"a frame with no source offset must still be placed"
|
"fixture check: the probe timestamp must sit inside at least one clip"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
candidates.contains(&placed),
|
||||||
|
"placed at {placed}, but no clip containing this timestamp maps it there \
|
||||||
|
(candidates {candidates:?}) — a frame was given a clip it does not belong to"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user