test: constrain MP4 composition timing, MLP substream directory, and codec-private absence
Mutation testing over src/mux/. No production change — 49 survivors killed, all proven red before green. The MP4 composition-time chain was entirely unconstrained: VideoTiming::ctts, build_ctts and parse_ctts could each return a constant and the suite stayed green. Confirmed on HEAD: build_ctts -> vec![] passes all 1,220 mux tests. A demuxed B-frame title presenting in decode order would have shipped. The cause is a test whose name asserts coverage its body does not deliver — stts_and_ctts_expand builds an stts box and never touches ctts, and write_then_read_round_trip asserts sample sizes and keyframe flags but not one PTS. Same shape as the set_speed forwarding finding, different disguise. mlp_num_substreams / mlp_substr_header_size: every TrueHD fixture in the crate uses one substream and no extraword, so both could return a constant and agree with all of them. These position mlp_parity_ok's window over the AU header, so a constant mis-windows the parity check on exactly the multi-substream AUs that carry 7.1 and Atmos. CodecPrivate absent vs empty: mkv.rs writes Some(bytes) verbatim and omits the element on None (RFC 9559 5.1.4.1.24), so a zero-length Some emits a track header asserting the config IS empty. Four parsers could return Some(vec![]) before any frame. Also: mandatory ISO/IEC 14496-12 boxes (tkhd, vmhd, smhd, dinf, mdhd) could each build empty; HEVC num_extra_slice_header_bits (H.265 7.3.2.3) was never non-zero in any fixture, so the slice-type offset skip was unexercised; chapter names from the disc go straight into <ChapterString> and the & escape must run first; a stray 0x47 in a payload must not latch a TS resync. Documented as equivalent rather than killed: CodecParser::flush and the three parser flush bodies that differ from the mutant only by a tracing call, and DropTally::log_summary.
This commit is contained in:
@@ -190,6 +190,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A dropped ADTS frame is dropped BECAUSE its header failed validation, so
|
||||
/// the very fields a duration would come from (sampling_frequency_index, and
|
||||
/// the 1024-samples-per-AAC-frame constant applied to it) are the ones known
|
||||
/// to be untrustworthy. This gate therefore reports the drop's duration as
|
||||
/// zero rather than deriving a number from a header it has just rejected —
|
||||
/// the honest answer, and the one the count alongside it must be read with.
|
||||
/// A nonzero constant here would report silence that was never measured.
|
||||
#[test]
|
||||
fn dropped_frames_are_counted_but_their_duration_is_not_invented() {
|
||||
let mut parser = AdtsParser::new();
|
||||
// Three frames whose sampling_frequency_index is a reserved value (13),
|
||||
// so `adts_verdict` rejects each one.
|
||||
let mut bad = adts_frame(32);
|
||||
bad[2] = (bad[2] & 0b1100_0011) | (13 << 2);
|
||||
for i in 0..3 {
|
||||
let out = parser.parse(&make_pes(bad.clone(), Some(i * 90_000)));
|
||||
assert!(out.is_empty(), "an invalid ADTS frame is not emitted");
|
||||
}
|
||||
assert_eq!(parser.dropped_frames(), 3, "every drop is counted");
|
||||
assert_eq!(
|
||||
parser.dropped_duration_ns(),
|
||||
0,
|
||||
"the duration comes from the header that just failed validation, so \
|
||||
it is reported as unmeasured rather than guessed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_sample_rate_index_is_dropped() {
|
||||
// sr_index = 13 (reserved). byte2 bits5..2 = 1101 → 0x34.
|
||||
|
||||
Reference in New Issue
Block a user