From 418abfe79e4dcefed065ce69757220700af7ea6c Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:16:02 -0700 Subject: [PATCH] test: cover the 1.6.1 provenance work where it was assumed, not asserted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/dirimage/tests.rs | 108 +++++++++++++++++++++++++++++++++++++ src/mux/codec/dvdsub.rs | 18 +++++++ src/mux/codec/flac.rs | 15 ++++++ src/mux/codec/lpcm.rs | 17 ++++++ src/mux/codec/mpegaudio.rs | 15 ++++++ src/mux/timeline.rs | 44 ++++++++++++++- 6 files changed, 215 insertions(+), 2 deletions(-) diff --git a/src/dirimage/tests.rs b/src/dirimage/tests.rs index 52a1283..234214b 100644 --- a/src/dirimage/tests.rs +++ b/src/dirimage/tests.rs @@ -914,3 +914,111 @@ fn dump_title_sets_for_an_image() { 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 { + 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" + ); +} diff --git a/src/mux/codec/dvdsub.rs b/src/mux/codec/dvdsub.rs index 441ec76..62930e9 100644 --- a/src/mux/codec/dvdsub.rs +++ b/src/mux/codec/dvdsub.rs @@ -717,4 +717,22 @@ mod tests { let result = format_palette(&[[0x00, 16, 128, 128]], 0, 0); 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)); + } } diff --git a/src/mux/codec/flac.rs b/src/mux/codec/flac.rs index 43c1f3b..b908af3 100644 --- a/src/mux/codec/flac.rs +++ b/src/mux/codec/flac.rs @@ -271,4 +271,19 @@ mod tests { ); 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)); + } } diff --git a/src/mux/codec/lpcm.rs b/src/mux/codec/lpcm.rs index e1dfc38..413cdd0 100644 --- a/src/mux/codec/lpcm.rs +++ b/src/mux/codec/lpcm.rs @@ -279,4 +279,21 @@ mod tests { let f = parser.parse(&make_pes(vec![0xAA, 0xBB], None)); 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)); + } } diff --git a/src/mux/codec/mpegaudio.rs b/src/mux/codec/mpegaudio.rs index cab0677..f1df8cf 100644 --- a/src/mux/codec/mpegaudio.rs +++ b/src/mux/codec/mpegaudio.rs @@ -319,4 +319,19 @@ mod tests { // a manufactured tail frame would break this even if it were non-empty. 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)); + } } diff --git a/src/mux/timeline.rs b/src/mux/timeline.rs index 08ada22..1cef106 100644 --- a/src/mux/timeline.rs +++ b/src/mux/timeline.rs @@ -1044,6 +1044,21 @@ mod tests { /// Clips with byte spans, built from the real mark table so provenance and /// 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 { let mut clips = seamless_branching_clips(); // 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() { let clips = clips_with_spans(); 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 = 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!( - plan.place(7_900_000_000_000, 0, true, None).is_some(), - "a frame with no source offset must still be placed" + !candidates.is_empty(), + "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" ); }