Lint the test code, and fix the 74 findings it had been hiding

Every other repo's CI now runs clippy with --all-targets. libfreemkv,
the crate the other seven build against and the one held up as the
reference workflow, was the last one still linting the library only — so
its ~3,000 tests, by far the largest body of test code in the project,
had never been linted at all. Turning the flag on surfaced 74 findings.

Most were mechanical and applied with clippy --fix. The rest, by hand:

- Four discarded Results in decrypt.rs. css::descramble_region returns a
  Result and four CSS tests threw it away, so a descramble that FAILED
  would have surfaced as a confusing buffer-comparison mismatch instead
  of the actual error. They expect() now.
- A dead `kp` field on the PlantedWalk fixture. The test deliberately
  asserts Kp as the explicit AES-G3(dk, 1) relation from [C] §3.2.4
  rather than against a stored value — its doc comment says so — which
  makes the field not just unused but a trap: the obvious "fix" of
  asserting against it would quietly weaken the test to comparing the
  fixture with itself. Removed.
- Two hand-rolled ICB counters in the HD-DVD fixtures, a needless mut,
  three vec!s that only ever needed arrays, a filter_map whose every arm
  was Some, and a Vec::new()+push chain.
- Doc list indentation in mkv.rs and mp4/read.rs, which was mis-rendering
  in the generated docs.
- A five-[u8; 16]-tuple return type named FourLevelParts.

Three lints are allowed at the specific sites, with reasons, because
they are wrong for this domain: the underscores in the bitstream-header
literals mark BITFIELD boundaries, not digit groups, so regrouping them
uniformly would satisfy the lint by destroying the only thing they
encode; and in three table-validation loops the loop variable is the
domain value under test (a DTS SFREQ code, an AMODE value, a palette
entry number), which is what the assertion messages name.
This commit is contained in:
Matthew Jackson
2026-07-31 15:08:37 -07:00
parent d50a7173ad
commit 71686f1407
30 changed files with 166 additions and 100 deletions
+4 -1
View File
@@ -41,7 +41,10 @@ jobs:
# would always fail on a fresh runner because there's no committed # would always fail on a fresh runner because there's no committed
# lockfile to lock against. The binary crates (freemkv, autorip, # lockfile to lock against. The binary crates (freemkv, autorip,
# bdemu) track Cargo.lock and DO use --locked. # bdemu) track Cargo.lock and DO use --locked.
- run: cargo clippy -- -D warnings # --all-targets so TEST code is linted too. Without it this crate — the
# reference implementation for the other seven — was the only one whose
# tests had never been linted at all, and it was hiding 74 findings.
- run: cargo clippy --all-targets -- -D warnings
working-directory: libfreemkv working-directory: libfreemkv
test: test:
+7 -2
View File
@@ -1344,9 +1344,14 @@ mod position_recovery_tests {
mkb mkb
} }
/// `(dkey, mk, pk, cvalue, mk_dv)` — the five 16-byte AACS keys the
/// four-level fixture plants. Named so the return type says what it is
/// rather than repeating `[u8; 16]` five times.
type FourLevelParts = ([u8; 16], [u8; 16], [u8; 16], [u8; 16], [u8; 16]);
/// The planted slot-2 material from the four-level fixture, reusable for /// The planted slot-2 material from the four-level fixture, reusable for
/// the malformed-MKB shapes below: `(dkey, mk, pk, cvalue, mk_dv)`. /// the malformed-MKB shapes below.
fn four_level_parts() -> ([u8; 16], [u8; 16], [u8; 16], [u8; 16], [u8; 16]) { fn four_level_parts() -> FourLevelParts {
let p = plant_four_level_mkb(); let p = plant_four_level_mkb();
let cvalues = mkb_find_cvalues(&p.mkb).expect("cvalues"); let cvalues = mkb_find_cvalues(&p.mkb).expect("cvalues");
let mut cv = [0u8; 16]; let mut cv = [0u8; 16];
+1 -1
View File
@@ -189,7 +189,7 @@ mod tests {
let unit_packets = (ALIGNED_UNIT_LEN as u64 / SOURCE_PACKET_LEN) as u32; // 32 let unit_packets = (ALIGNED_UNIT_LEN as u64 / SOURCE_PACKET_LEN) as u32; // 32
// Start so the unit covers [80, 80+31] = [80, 111]: overlaps at 100. // Start so the unit covers [80, 80+31] = [80, 111]: overlaps at 100.
let off = 80u64 * SOURCE_PACKET_LEN; let off = 80u64 * SOURCE_PACKET_LEN;
assert!(80 + unit_packets - 1 >= 100, "sanity: unit tails into seg"); assert!(80 + unit_packets > 100, "sanity: unit tails into seg");
assert_eq!( assert_eq!(
unit_disposition(off, &segs, Some(5)), unit_disposition(off, &segs, Some(5)),
UnitDisposition::Index(5) UnitDisposition::Index(5)
-3
View File
@@ -1707,8 +1707,6 @@ mod tests {
records: Vec<MkbRecord>, records: Vec<MkbRecord>,
/// The device key that covers slot 1 with zero descent. /// The device key that covers slot 1 with zero descent.
dk: DeviceKey, dk: DeviceKey,
/// The Processing Key the walk must produce for it.
kp: [u8; 16],
/// The Media Key the full chain must reach from that Processing Key. /// The Media Key the full chain must reach from that Processing Key.
km: [u8; 16], km: [u8; 16],
/// The `0x0c` C block of slot 1 — the cvalue the walk must select. /// The `0x0c` C block of slot 1 — the cvalue the walk must select.
@@ -1833,7 +1831,6 @@ mod tests {
uv: UV_REAL, uv: UV_REAL,
u_mask_shift: U_MASK_SHIFT, u_mask_shift: U_MASK_SHIFT,
}, },
kp,
km, km,
c_block1, c_block1,
} }
+4 -4
View File
@@ -837,7 +837,7 @@ mod tests {
// descramble-and-rekey lives in `css::descramble_region` (the recovery // descramble-and-rekey lives in `css::descramble_region` (the recovery
// seam calls it); the region change must re-crack region B's key. // seam calls it); the region change must re-crack region B's key.
let mut ended = key_a; let mut ended = key_a;
css::descramble_region(&mut buf, &mut ended); css::descramble_region(&mut buf, &mut ended).expect("descramble");
assert_eq!( assert_eq!(
&buf[0x80..2048], &buf[0x80..2048],
@@ -986,7 +986,7 @@ mod tests {
let (mut sector, plaintext) = make_css_sector(&title_key, &seed, 0xA5); let (mut sector, plaintext) = make_css_sector(&title_key, &seed, 0xA5);
// CSS descramble lives in `css::descramble_region` (the recovery seam // CSS descramble lives in `css::descramble_region` (the recovery seam
// calls it); `decrypt_sectors` only flags CSS sectors for recovery. // calls it); `decrypt_sectors` only flags CSS sectors for recovery.
css::descramble_region(&mut sector, &mut title_key); css::descramble_region(&mut sector, &mut title_key).expect("descramble");
assert_eq!( assert_eq!(
&sector[0x80..2048], &sector[0x80..2048],
&plaintext[0x80..2048], &plaintext[0x80..2048],
@@ -1016,7 +1016,7 @@ mod tests {
let mut buf = s0; let mut buf = s0;
buf.extend_from_slice(&s1); buf.extend_from_slice(&s1);
let mut title_key = title_key; let mut title_key = title_key;
css::descramble_region(&mut buf, &mut title_key); css::descramble_region(&mut buf, &mut title_key).expect("descramble");
assert_eq!( assert_eq!(
&buf[0x80..2048], &buf[0x80..2048],
&p0[0x80..2048], &p0[0x80..2048],
@@ -1095,7 +1095,7 @@ mod tests {
// Cache primed to key_a only — exactly what the one-shot scan crack yields. // Cache primed to key_a only — exactly what the one-shot scan crack yields.
let mut title_key = key_a; let mut title_key = key_a;
css::descramble_region(&mut buf, &mut title_key); css::descramble_region(&mut buf, &mut title_key).expect("descramble");
assert_eq!( assert_eq!(
&buf[0x80..2048], &buf[0x80..2048],
+18 -12
View File
@@ -716,12 +716,15 @@ mod tests {
/// Build a UDF with an `HVDVD_TS/` tree holding the listed `.evo` clips /// Build a UDF with an `HVDVD_TS/` tree holding the listed `.evo` clips
/// (name, sector count, data LBA). /// (name, sector count, data LBA).
fn make_hddvd_fs(disc: &mut MemDisc, evos: &[(&str, u32, u32)]) -> crate::udf::UdfFs { fn make_hddvd_fs(disc: &mut MemDisc, evos: &[(&str, u32, u32)]) -> crate::udf::UdfFs {
let mut files = Vec::new(); // ICBs are handed out from 100 upward, one per EVO, so the index IS
let mut icb = 100u32; // the offset from that base.
for (name, sectors, data_lba) in evos { let files: Vec<_> = evos
files.push(file(name, icb, *data_lba, sectors * 2048, true)); .iter()
icb += 1; .enumerate()
} .map(|(i, (name, sectors, data_lba))| {
file(name, 100 + i as u32, *data_lba, sectors * 2048, true)
})
.collect();
let root = DirSpec { let root = DirSpec {
name: String::new(), name: String::new(),
icb_lba: 10, icb_lba: 10,
@@ -1337,12 +1340,15 @@ mod tests {
evos: &[(&str, u32, u32)], evos: &[(&str, u32, u32)],
xpl: &[u8], xpl: &[u8],
) -> crate::udf::UdfFs { ) -> crate::udf::UdfFs {
let mut hv_files = Vec::new(); // ICBs are handed out from 100 upward, one per EVO, so the index IS
let mut icb = 100u32; // the offset from that base.
for (name, sectors, data_lba) in evos { let hv_files: Vec<_> = evos
hv_files.push(file(name, icb, *data_lba, sectors * 2048, true)); .iter()
icb += 1; .enumerate()
} .map(|(i, (name, sectors, data_lba))| {
file(name, 100 + i as u32, *data_lba, sectors * 2048, true)
})
.collect();
let root = DirSpec { let root = DirSpec {
name: String::new(), name: String::new(),
icb_lba: 10, icb_lba: 10,
+8 -8
View File
@@ -3432,7 +3432,7 @@ mod tests {
fn merged_extents_unions_sorts_and_merges() { fn merged_extents_unions_sorts_and_merges() {
// [300,310) ; [100,150) ; [150,200) adjacent→merges with prev ; // [300,310) ; [100,150) ; [150,200) adjacent→merges with prev ;
// [120,160) overlaps [100,150)&[150,200) ; [500,505) disjoint. // [120,160) overlaps [100,150)&[150,200) ; [500,505) disjoint.
let v = vec![ let v = [
ext(300, 10), ext(300, 10),
ext(100, 50), ext(100, 50),
ext(150, 50), ext(150, 50),
@@ -3450,7 +3450,7 @@ mod tests {
/// to a single range — no double-counting of shared content. /// to a single range — no double-counting of shared content.
#[test] #[test]
fn merged_extents_dedups_shared_clip() { fn merged_extents_dedups_shared_clip() {
let v = vec![ext(100, 50), ext(100, 50)]; let v = [ext(100, 50), ext(100, 50)];
assert_eq!(merged_extents(v.iter()), vec![(100, 50)]); assert_eq!(merged_extents(v.iter()), vec![(100, 50)]);
} }
@@ -3666,7 +3666,7 @@ mod tests {
#[test] #[test]
fn canonical_order_pushes_oversize_play_all_behind_real_main() { fn canonical_order_pushes_oversize_play_all_behind_real_main() {
const CAPACITY: u64 = 58_500_000_000; // 58.5 GB const CAPACITY: u64 = 58_500_000_000; // 58.5 GB
let mut titles = vec![ let mut titles = [
// Title 1 in the raw MPLS order — virtual play-all // Title 1 in the raw MPLS order — virtual play-all
title_with( title_with(
"00020.mpls", "00020.mpls",
@@ -3694,7 +3694,7 @@ mod tests {
#[test] #[test]
fn canonical_order_preserves_natural_ranking_on_normal_disc() { fn canonical_order_preserves_natural_ranking_on_normal_disc() {
const CAPACITY: u64 = 60_000_000_000; const CAPACITY: u64 = 60_000_000_000;
let mut titles = vec![ let mut titles = [
title_with("00100.mpls", 600.0, 500_000_000, 1), // 10 min menu (small) title_with("00100.mpls", 600.0, 500_000_000, 1), // 10 min menu (small)
title_with("00800.mpls", 7320.0, 55_000_000_000, 1), // 2h02m main feature title_with("00800.mpls", 7320.0, 55_000_000_000, 1), // 2h02m main feature
title_with("00200.mpls", 1800.0, 2_000_000_000, 1), // 30 min extra title_with("00200.mpls", 1800.0, 2_000_000_000, 1), // 30 min extra
@@ -3717,7 +3717,7 @@ mod tests {
#[test] #[test]
fn title_index_0_is_main_feature_dvd_the_dash_t_1_contract() { fn title_index_0_is_main_feature_dvd_the_dash_t_1_contract() {
const DVD9: u64 = 7_900_000_000; // dual-layer DVD const DVD9: u64 = 7_900_000_000; // dual-layer DVD
let mut titles = vec![ let mut titles = [
title_with("VTS_01_menu", 120.0, 200_000_000, 1), // 2m menu/setup loop title_with("VTS_01_menu", 120.0, 200_000_000, 1), // 2m menu/setup loop
title_with("VTS_02_main", 6540.0, 6_300_000_000, 1), // 1h49m main feature title_with("VTS_02_main", 6540.0, 6_300_000_000, 1), // 1h49m main feature
title_with("VTS_03_extra", 900.0, 800_000_000, 1), // 15m extra title_with("VTS_03_extra", 900.0, 800_000_000, 1), // 15m extra
@@ -3871,7 +3871,7 @@ mod tests {
let feature = title_sized(57_000_000_000, 7860.0, 11); // 2h11m, 11 chapters let feature = title_sized(57_000_000_000, 7860.0, 11); // 2h11m, 11 chapters
let bonus = title_sized(1_200_000_000, 600.0, 1); // 10m, 1 clip let bonus = title_sized(1_200_000_000, 600.0, 1); // 10m, 1 clip
let decoy = title_sized(400_000_000, 5460.0, 91); // 1h31m but tiny (reused) let decoy = title_sized(400_000_000, 5460.0, 91); // 1h31m but tiny (reused)
let mut v = vec![bonus, decoy, feature]; let mut v = [bonus, decoy, feature];
v.sort_by(|a, b| Disc::canonical_title_order(a, b, capacity)); v.sort_by(|a, b| Disc::canonical_title_order(a, b, capacity));
assert_eq!( assert_eq!(
v[0].size_bytes, 57_000_000_000, v[0].size_bytes, 57_000_000_000,
@@ -4610,14 +4610,14 @@ mod tests {
assert!(super::aligned_unit_keys_validate( assert!(super::aligned_unit_keys_validate(
&[(7, uk)], &[(7, uk)],
None, None,
&[enc.clone()], std::slice::from_ref(&enc),
ContentFormat::BdTs ContentFormat::BdTs
)); ));
// Wrong key -> cannot de-scramble a scrambled sample -> reject. // Wrong key -> cannot de-scramble a scrambled sample -> reject.
assert!(!super::aligned_unit_keys_validate( assert!(!super::aligned_unit_keys_validate(
&[(7, [0x00u8; 16])], &[(7, [0x00u8; 16])],
None, None,
&[enc.clone()], std::slice::from_ref(&enc),
ContentFormat::BdTs ContentFormat::BdTs
)); ));
// Empty key set against a scrambled sample -> reject. // Empty key set against a scrambled sample -> reject.
+5
View File
@@ -2201,6 +2201,11 @@ mod tests {
/// `[padding, Y, Cb, Cr]`. Every byte of every entry is distinct here, so /// `[padding, Y, Cb, Cr]`. Every byte of every entry is distinct here, so
/// a wrong stride, a wrong base or a shifted component shows up. /// a wrong stride, a wrong base or a shifted component shows up.
#[test] #[test]
// The loop variable is the DOMAIN VALUE being checked (a palette entry number), not a
// cursor into a collection: it is what the assertion message names and
// what the table is keyed by. `.iter().enumerate()` would rename the
// thing under test to `i` and read worse.
#[allow(clippy::needless_range_loop)]
fn pgc_palette_entries_read_at_correct_stride() { fn pgc_palette_entries_read_at_correct_stride() {
let mut pal = [[0u8; 4]; 16]; let mut pal = [[0u8; 4]; 16];
for (i, c) in pal.iter_mut().enumerate() { for (i, c) in pal.iter_mut().enumerate() {
+1 -1
View File
@@ -1306,7 +1306,7 @@ mod tests {
break; break;
} }
let abs = (lba - self.ext_start) / ALIGNED_UNIT_SECTORS + i as u32; let abs = (lba - self.ext_start) / ALIGNED_UNIT_SECTORS + i as u32;
if abs % 2 == 0 { if abs.is_multiple_of(2) {
chunk.fill(0x11); // CPI-clear (0x11 & 0xC0 == 0), no TS sync chunk.fill(0x11); // CPI-clear (0x11 & 0xC0 == 0), no TS sync
} else { } else {
chunk.fill(0xAB); // scrambled body (no TS sync) chunk.fill(0xAB); // scrambled body (no TS sync)
+4 -4
View File
@@ -370,10 +370,10 @@ mod tests {
if let Some(d) = desc { if let Some(d) = desc {
meta.descriptions.insert(lang.to_string(), d); meta.descriptions.insert(lang.to_string(), d);
} }
if meta.disc_number.is_none() { if meta.disc_number.is_none()
if let Some(d) = ds { && let Some(d) = ds
meta.disc_number = Some(d); {
} meta.disc_number = Some(d);
} }
} }
+1 -1
View File
@@ -1333,7 +1333,7 @@ mod gap_fill_tests {
// duplicate. Stream_number is computed from the EXISTING // duplicate. Stream_number is computed from the EXISTING
// labels' max(stream_number) per type so orphans (when they // labels' max(stream_number) per type so orphans (when they
// do fire) sort cleanly at the tail. // do fire) sort cleanly at the tail.
let labels = vec![ let labels = [
label(StreamLabelType::Audio, 1, "eng", "TrueHD"), label(StreamLabelType::Audio, 1, "eng", "TrueHD"),
label(StreamLabelType::Audio, 2, "fra", "AC-3"), label(StreamLabelType::Audio, 2, "fra", "AC-3"),
]; ];
+1 -1
View File
@@ -169,7 +169,7 @@ mod tests {
/// Mutation: skip the final `if !current.is_empty()` emit → trailing run lost. /// Mutation: skip the final `if !current.is_empty()` emit → trailing run lost.
#[test] #[test]
fn large_buffer_trailing_run_emitted() { fn large_buffer_trailing_run_emitted() {
let buf: Vec<u8> = (0..1000u32).map(|i| (0x41u8 + (i % 26) as u8)).collect(); let buf: Vec<u8> = (0..1000u32).map(|i| 0x41u8 + (i % 26) as u8).collect();
let got = extract_ascii_strings(&buf, 1); let got = extract_ascii_strings(&buf, 1);
// All printable, so one big run at the end. // All printable, so one big run at the end.
assert!(!got.is_empty()); assert!(!got.is_empty());
+8 -7
View File
@@ -1521,13 +1521,14 @@ mod tests {
/// consumed to keep the cursor aligned but never retained. /// consumed to keep the cursor aligned but never retained.
#[test] #[test]
fn full_stn_table_block_alignment() { fn full_stn_table_block_alignment() {
let mut entries: Vec<Vec<u8>> = Vec::new(); let mut entries: Vec<Vec<u8>> = vec![
entries.push(build_stream_entry_video(0x1011, 0x1B, 6, 1, None)); build_stream_entry_video(0x1011, 0x1B, 6, 1, None),
entries.push(build_stream_entry_audio(0x1100, 0x83, 6, 1, b"eng")); build_stream_entry_audio(0x1100, 0x83, 6, 1, b"eng"),
entries.push(build_stream_entry_audio(0x1101, 0x86, 3, 1, b"fra")); build_stream_entry_audio(0x1101, 0x86, 3, 1, b"fra"),
entries.push(build_stream_entry_pg(0x1200, 0x90, b"eng")); build_stream_entry_pg(0x1200, 0x90, b"eng"),
entries.push(build_stream_entry_pg(0x1201, 0x90, b"fra")); build_stream_entry_pg(0x1201, 0x90, b"fra"),
entries.push(build_stream_entry_pg(0x1202, 0x90, b"deu")); build_stream_entry_pg(0x1202, 0x90, b"deu"),
];
for i in 0..4u16 { for i in 0..4u16 {
entries.push(build_stream_entry_pg(0x1400 + i, 0x91, b"eng")); entries.push(build_stream_entry_pg(0x1400 + i, 0x91, b"eng"));
} }
+1 -1
View File
@@ -1738,7 +1738,7 @@ mod tests {
/// byte 5 = bsid 16 so the E-AC-3 paths are taken. CRC finalized so the frame /// byte 5 = bsid 16 so the E-AC-3 paths are taken. CRC finalized so the frame
/// passes the decodability gate. /// passes the decodability gate.
fn make_eac3_frame(strmtyp: u8, substreamid: u8, size: usize) -> Vec<u8> { fn make_eac3_frame(strmtyp: u8, substreamid: u8, size: usize) -> Vec<u8> {
assert!(size >= MIN_FRAME_BYTES && size % 2 == 0); assert!(size >= MIN_FRAME_BYTES && size.is_multiple_of(2));
let frmsiz = size / 2 - 1; let frmsiz = size / 2 - 1;
let mut f = vec![0u8; size]; let mut f = vec![0u8; size];
f[0] = 0x0B; f[0] = 0x0B;
+6 -1
View File
@@ -921,7 +921,7 @@ mod tests {
let core = make_dts_core(512); let core = make_dts_core(512);
let garbage = vec![0xE4, 0x3F, 0xE3, 0x90, 0xCC, 0x6C]; // real Bourne head bytes let garbage = vec![0xE4, 0x3F, 0xE3, 0x90, 0xCC, 0x6C]; // real Bourne head bytes
let mut garbage = garbage; let mut garbage = garbage;
garbage.extend(std::iter::repeat(0xAB).take(300)); garbage.extend(std::iter::repeat_n(0xAB, 300));
let next = make_dts_core(512); let next = make_dts_core(512);
let mut buf = core.clone(); let mut buf = core.clone();
buf.extend_from_slice(&garbage); buf.extend_from_slice(&garbage);
@@ -1931,6 +1931,11 @@ mod tests {
} }
#[test] #[test]
// The loop variable is the DOMAIN VALUE being checked (a DTS SFREQ code), not a
// cursor into a collection: it is what the assertion message names and
// what the table is keyed by. `.iter().enumerate()` would rename the
// thing under test to `i` and read worse.
#[allow(clippy::needless_range_loop)]
fn sr_validity_table_marks_reserved_codes() { fn sr_validity_table_marks_reserved_codes() {
// The core-header sample-rate validity table must have ZERO (reject) at // The core-header sample-rate validity table must have ZERO (reject) at
// exactly the reserved SFREQ codes {0,4,5,9,10} and a real rate // exactly the reserved SFREQ codes {0,4,5,9,10} and a real rate
+6 -1
View File
@@ -1456,6 +1456,11 @@ mod tests {
/// offset on any real stream that sets it — mislabelling every picture's /// offset on any real stream that sets it — mislabelling every picture's
/// coding type. /// coding type.
#[test] #[test]
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn nonzero_num_extra_slice_header_bits_shifts_the_slice_type_offset() { fn nonzero_num_extra_slice_header_bits_shifts_the_slice_type_offset() {
use super::super::coding::CodingType; use super::super::coding::CodingType;
@@ -2690,7 +2695,7 @@ mod tests {
} }
} }
fn put_bit(&mut self, b: u32) { fn put_bit(&mut self, b: u32) {
if self.nbits % 8 == 0 { if self.nbits.is_multiple_of(8) {
self.bytes.push(0); self.bytes.push(0);
} }
if b & 1 != 0 { if b & 1 != 0 {
+1 -1
View File
@@ -154,7 +154,7 @@ mod tests {
/// 0xFF 0xFB 0x90 0x00 — the canonical MP3 frame header. /// 0xFF 0xFB 0x90 0x00 — the canonical MP3 frame header.
fn mp3_frame(payload: usize) -> Vec<u8> { fn mp3_frame(payload: usize) -> Vec<u8> {
let mut f = vec![0xFF, 0xFB, 0x90, 0x00]; let mut f = vec![0xFF, 0xFB, 0x90, 0x00];
f.extend(std::iter::repeat(0xAA).take(payload)); f.extend(std::iter::repeat_n(0xAA, payload));
f f
} }
+1 -3
View File
@@ -417,9 +417,7 @@ mod tests {
let (_dt, rx) = let (_dt, rx) =
DemuxThread::spawn_zero_copy(pf_rx, rc_tx, (), None, Some(ts), None).unwrap(); DemuxThread::spawn_zero_copy(pf_rx, rc_tx, (), None, Some(ts), None).unwrap();
pf_tx pf_tx.send(Err(std::io::Error::other("boom"))).unwrap();
.send(Err(std::io::Error::new(std::io::ErrorKind::Other, "boom")))
.unwrap();
drop(pf_tx); drop(pf_tx);
let batches = collect_batches(&rx, Duration::from_secs(5)); let batches = collect_batches(&rx, Duration::from_secs(5));
+6 -3
View File
@@ -1880,7 +1880,7 @@ mod tests {
// would straddle AACS unit boundaries and decrypt under the wrong // would straddle AACS unit boundaries and decrypt under the wrong
// alignment. // alignment.
assert!( assert!(
count as u32 % ALIGN == 0 || (count as u32) < ALIGN, (count as u32).is_multiple_of(ALIGN) || (count as u32) < ALIGN,
"read count {count} is neither a whole number of units nor a sub-unit tail" "read count {count} is neither a whole number of units nor a sub-unit tail"
); );
} }
@@ -2457,7 +2457,10 @@ mod tests {
let h = halve_batch_size(size); let h = halve_batch_size(size);
assert!(h >= 1, "halve({size}) must never be 0"); assert!(h >= 1, "halve({size}) must never be 0");
assert!(h <= size, "halve({size}) = {h} must not grow"); assert!(h <= size, "halve({size}) = {h} must not grow");
assert!(h < 6 || h % 3 == 0, "halve({size}) = {h} is unit-unaligned"); assert!(
h < 6 || h.is_multiple_of(3),
"halve({size}) = {h} is unit-unaligned"
);
} }
} }
@@ -2483,7 +2486,7 @@ mod tests {
let d = double_batch_size(size, 4096); let d = double_batch_size(size, 4096);
assert!(d >= size, "double({size}) = {d} must not shrink"); assert!(d >= size, "double({size}) = {d} must not shrink");
assert!( assert!(
d < 6 || d % 3 == 0, d < 6 || d.is_multiple_of(3),
"double({size}) = {d} is unit-unaligned" "double({size}) = {d} is unit-unaligned"
); );
} }
+8 -8
View File
@@ -1043,15 +1043,15 @@ mod tests {
impl Stream for FakeStream { impl Stream for FakeStream {
fn read(&mut self) -> std::io::Result<Option<PesFrame>> { fn read(&mut self) -> std::io::Result<Option<PesFrame>> {
if let Some((halt, after)) = &self.cancel_halt { if let Some((halt, after)) = &self.cancel_halt
if self.reads >= *after { && self.reads >= *after
halt.cancel(); {
} halt.cancel();
} }
if let Some(after) = self.halt_err_at_read { if let Some(after) = self.halt_err_at_read
if self.reads >= after { && self.reads >= after
return Err(crate::error::Error::Halted.into()); {
} return Err(crate::error::Error::Halted.into());
} }
let f = self.frames.pop_front(); let f = self.frames.pop_front();
if f.is_some() { if f.is_some() {
+5
View File
@@ -1479,6 +1479,11 @@ mod tests {
/// end_master without a multi-terabyte buffer, which is why this is /// end_master without a multi-terabyte buffer, which is why this is
/// tested at the encoder. /// tested at the encoder.
#[test] #[test]
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn fixed_width_vint8_is_big_endian_over_the_full_payload() { fn fixed_width_vint8_is_big_endian_over_the_full_payload() {
assert_eq!( assert_eq!(
fixed_width_vint8(0x00AA_BB_CC_DD_EE_FF_11), fixed_width_vint8(0x00AA_BB_CC_DD_EE_FF_11),
+9 -8
View File
@@ -766,7 +766,7 @@ mod tests {
// First two packets: PAT, PMT. At least one video packet after. // First two packets: PAT, PMT. At least one video packet after.
assert_eq!(pids[0], PID_PAT); assert_eq!(pids[0], PID_PAT);
assert_eq!(pids[1], PID_PMT); assert_eq!(pids[1], PID_PMT);
assert!(pids.iter().any(|p| *p == PID_VIDEO)); assert!(pids.contains(&PID_VIDEO));
} }
#[test] #[test]
@@ -810,8 +810,8 @@ mod tests {
assert_ts_well_formed(&sink); assert_ts_well_formed(&sink);
let pids = extract_pids(&sink); let pids = extract_pids(&sink);
assert!(pids.iter().any(|p| *p == PID_VIDEO)); assert!(pids.contains(&PID_VIDEO));
assert!(pids.iter().any(|p| *p == PID_AUDIO)); assert!(pids.contains(&PID_AUDIO));
} }
#[test] #[test]
@@ -1129,10 +1129,11 @@ mod tests {
continue; continue;
} }
total_video += 1; total_video += 1;
if let Some(af) = af_body(pkt) { if let Some(af) = af_body(pkt)
if !af.is_empty() && (af[0] & 0x10) != 0 { && !af.is_empty()
pcr_indices.push(video_idx); && (af[0] & 0x10) != 0
} {
pcr_indices.push(video_idx);
} }
video_idx += 1; video_idx += 1;
} }
@@ -1582,7 +1583,7 @@ mod tests {
} }
let pids = extract_pids(&sink); let pids = extract_pids(&sink);
assert!( assert!(
!pids.iter().any(|p| *p == PID_AUDIO), !pids.contains(&PID_AUDIO),
"no audio track configured → no audio PID emitted" "no audio track configured → no audio PID emitted"
); );
} }
+1
View File
@@ -2024,6 +2024,7 @@ mod tests {
/// so a shipped DVD rip marked every P/B frame as a seek point; /// so a shipped DVD rip marked every P/B frame as a seek point;
/// - the reader ignored ReferenceBlock and read the always-0 reserved bit, /// - the reader ignored ReferenceBlock and read the always-0 reserved bit,
/// so EVERY BlockGroup frame read back as a non-keyframe. /// so EVERY BlockGroup frame read back as a non-keyframe.
///
/// Downstream that silently dropped all video on `mkv://`(MPEG-2)→`m2ts://` /// Downstream that silently dropped all video on `mkv://`(MPEG-2)→`m2ts://`
/// (TsMuxer drops non-key video until the first keyframe) and made /// (TsMuxer drops non-key video until the first keyframe) and made
/// `mkv://`→`mkv://` / `stdio://` fail E6008 (MkvMuxer opens a cluster only /// `mkv://`→`mkv://` / `stdio://` fail E6008 (MkvMuxer opens a cluster only
+15
View File
@@ -522,6 +522,11 @@ mod tests {
/// A synthetic legacy AC-3 header: syncword, crc, fscod=0 (48k), /// A synthetic legacy AC-3 header: syncword, crc, fscod=0 (48k),
/// frmsizecod, bsid=8, bsmod=0, acmod=7 (3/2), lfeon=1 → 5.1. /// frmsizecod, bsid=8, bsmod=0, acmod=7 (3/2), lfeon=1 → 5.1.
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn ac3_frame_5_1() -> Vec<u8> { fn ac3_frame_5_1() -> Vec<u8> {
let mut f = vec![0x0B, 0x77, 0x00, 0x00]; let mut f = vec![0x0B, 0x77, 0x00, 0x00];
// byte4: fscod(2)=0 | frmsizecod(6)=0b010110 (22) // byte4: fscod(2)=0 | frmsizecod(6)=0b010110 (22)
@@ -556,6 +561,11 @@ mod tests {
/// A synthetic Annex-E (E-AC-3) syncframe: bsid=16, fscod=0 (48 kHz), /// A synthetic Annex-E (E-AC-3) syncframe: bsid=16, fscod=0 (48 kHz),
/// numblkscod=3 (6 blocks), acmod=7 (3/2), lfeon=1 → 5.1, frmsiz=63 (128 B). /// numblkscod=3 (6 blocks), acmod=7 (3/2), lfeon=1 → 5.1, frmsiz=63 (128 B).
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn eac3_frame_5_1() -> Vec<u8> { fn eac3_frame_5_1() -> Vec<u8> {
// E-AC-3: syncword | strmtyp/substreamid/frmsiz | fscod/numblks/acmod/lfeon | bsid // E-AC-3: syncword | strmtyp/substreamid/frmsiz | fscod/numblks/acmod/lfeon | bsid
let mut f = vec![0x0B, 0x77]; let mut f = vec![0x0B, 0x77];
@@ -840,6 +850,11 @@ mod tests {
} }
#[test] #[test]
// The loop variable is the DOMAIN VALUE being checked (a DTS AMODE value), not a
// cursor into a collection: it is what the assertion message names and
// what the table is keyed by. `.iter().enumerate()` would rename the
// thing under test to `i` and read worse.
#[allow(clippy::needless_range_loop)]
fn ddts_channel_layout_speaker_count_matches_declared_channels() { fn ddts_channel_layout_speaker_count_matches_declared_channels() {
// The `ddts` box carries BOTH a channel count and a 16-bit speaker mask, // The `ddts` box carries BOTH a channel count and a 16-bit speaker mask,
// and a decoder may trust either. They must agree for all 16 AMODEs. // and a decoder may trust either. They must agree for all 16 AMODEs.
+7 -2
View File
@@ -1226,6 +1226,11 @@ mod tests {
} }
// A minimal AC-3 5.1 frame the audio parser accepts. // A minimal AC-3 5.1 frame the audio parser accepts.
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn ac3_frame() -> Vec<u8> { fn ac3_frame() -> Vec<u8> {
vec![ vec![
0x0B, 0x0B,
@@ -1503,11 +1508,11 @@ mod tests {
t.duration_secs = 7200.0; t.duration_secs = 7200.0;
let r = estimate_reserve(&t, &[0, 1]); let r = estimate_reserve(&t, &[0, 1]);
assert!( assert!(
r % (4 << 20) == 0, r.is_multiple_of(4 << 20),
"reserve is 4 MiB-aligned + 4 MiB buffer" "reserve is 4 MiB-aligned + 4 MiB buffer"
); );
assert!( assert!(
r >= 12 << 20 && r <= 20 << 20, (12 << 20..=20 << 20).contains(&r),
"≈12-16 MB for a 2h feature, got {r}" "≈12-16 MB for a 2h feature, got {r}"
); );
+18 -8
View File
@@ -1087,6 +1087,11 @@ mod tests {
} }
#[test] #[test]
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn write_then_read_round_trip() { fn write_then_read_round_trip() {
// Mux a small A/V title to an in-memory MP4, then demux it back and // Mux a small A/V title to an in-memory MP4, then demux it back and
// check the streams, codec_private, and sample payloads survive. // check the streams, codec_private, and sample payloads survive.
@@ -1453,6 +1458,11 @@ mod tests {
/// one of any of them round-trips through this crate unnoticed while making /// one of any of them round-trips through this crate unnoticed while making
/// the file unplayable elsewhere. /// the file unplayable elsewhere.
#[test] #[test]
// The underscores in these literals mark BITFIELD boundaries in the
// bitstream header being built (e.g. a 5-bit field then a 3-bit field),
// not thousands-style digit groups. Regrouping them uniformly would
// satisfy the lint by destroying the only thing they encode.
#[allow(clippy::unusual_byte_groupings)]
fn moov_tree_carries_the_mandatory_track_header_and_media_boxes() { fn moov_tree_carries_the_mandatory_track_header_and_media_boxes() {
use crate::disc::{ use crate::disc::{
AudioChannels, AudioStream, Codec, DiscTitle, FrameRate, HdrFormat, LabelPurpose, AudioChannels, AudioStream, Codec, DiscTitle, FrameRate, HdrFormat, LabelPurpose,
@@ -2148,8 +2158,8 @@ mod tests {
mdia.extend_from_slice(&mdhd); mdia.extend_from_slice(&mdhd);
mdia.extend_from_slice(&hdlr); mdia.extend_from_slice(&hdlr);
mdia.extend_from_slice(&minf); mdia.extend_from_slice(&minf);
let trak = mp4_box(b"trak", &mp4_box(b"mdia", &mdia));
trak mp4_box(b"trak", &mp4_box(b"mdia", &mdia))
} }
#[test] #[test]
@@ -2449,10 +2459,10 @@ mod tests {
} }
/// A `VisualSampleEntry` body with DISTINCT width and height, plus optional /// A `VisualSampleEntry` body with DISTINCT width and height, plus optional
/// child boxes (ISO/IEC 14496-12 §12.1.3): 6 reserved + 2 data_reference_index /// child boxes (ISO/IEC 14496-12 §12.1.3). The fixed part is 78 bytes:
/// + 16 pre_defined/reserved, then width(2) at 24 and height(2) at 26, then 50 /// 6 reserved, 2 data_reference_index, 16 pre_defined/reserved, width(2) at
/// more bytes of resolution / frame_count / compressorname / depth / pre_defined /// offset 24, height(2) at 26, then 50 more bytes of resolution,
/// to the 78-byte fixed part. /// frame_count, compressorname, depth and pre_defined.
fn visual_entry(width: u16, height: u16, children: &[u8]) -> Vec<u8> { fn visual_entry(width: u16, height: u16, children: &[u8]) -> Vec<u8> {
let mut b = vec![0u8; 78]; let mut b = vec![0u8; 78];
b[24..26].copy_from_slice(&width.to_be_bytes()); b[24..26].copy_from_slice(&width.to_be_bytes());
@@ -2492,7 +2502,7 @@ mod tests {
// And a VisualSampleEntry too short to hold the fixed part is refused // And a VisualSampleEntry too short to hold the fixed part is refused
// rather than read out of a shorter buffer. // rather than read out of a shorter buffer.
let short = stsd_with(b"avc1", &vec![0u8; 40]); let short = stsd_with(b"avc1", &[0u8; 40]);
assert!( assert!(
parse_stsd(&short).is_none(), parse_stsd(&short).is_none(),
"a truncated VisualSampleEntry has no dimensions to read" "a truncated VisualSampleEntry has no dimensions to read"
@@ -2519,7 +2529,7 @@ mod tests {
assert_eq!(info.height, 0, "an audio entry declares no height"); assert_eq!(info.height, 0, "an audio entry declares no height");
// Too short for the 28-byte fixed part: fall back to stereo, not to 0. // Too short for the 28-byte fixed part: fall back to stereo, not to 0.
let short = stsd_with(b"ac-3", &vec![0u8; 12]); let short = stsd_with(b"ac-3", &[0u8; 12]);
let info = parse_stsd(&short).expect("a short audio entry still names a codec"); let info = parse_stsd(&short).expect("a short audio entry still names a codec");
assert_eq!( assert_eq!(
info.channels, 2, info.channels, 2,
+12 -11
View File
@@ -4258,7 +4258,7 @@ mod tests {
let sb = s.start_byte(); let sb = s.start_byte();
if unit_byte >= sb && unit_byte < sb + s.byte_len() { if unit_byte >= sb && unit_byte < sb + s.byte_len() {
let n = (unit_byte - sb) / crate::aacs::content::ALIGNED_UNIT_LEN as u64; let n = (unit_byte - sb) / crate::aacs::content::ALIGNED_UNIT_LEN as u64;
let key = if n % 2 == 0 { let key = if n.is_multiple_of(2) {
FMTS_INDEX_KEYS[(s.index - 1) as usize] FMTS_INDEX_KEYS[(s.index - 1) as usize]
} else { } else {
FMTS_ALT_KEY FMTS_ALT_KEY
@@ -4281,16 +4281,17 @@ mod tests {
buf: &mut [u8], buf: &mut [u8],
recovery: bool, recovery: bool,
) -> crate::error::Result<usize> { ) -> crate::error::Result<usize> {
if let Some((a, b)) = self.fault_span { if let Some((a, b)) = self.fault_span
if lba >= a && lba < b { && lba >= a
self.probe_reads += 1; && lba < b
self.maybe_cancel(); {
return Err(crate::error::Error::DiscRead { self.probe_reads += 1;
sector: lba as u64, self.maybe_cancel();
status: None, return Err(crate::error::Error::DiscRead {
sense: None, sector: lba as u64,
}); status: None,
} sense: None,
});
} }
if lba < FMTS_CONTENT_LBA { if lba < FMTS_CONTENT_LBA {
self.meta_reads += 1; self.meta_reads += 1;
+4 -4
View File
@@ -232,10 +232,10 @@ mod tests {
fn pids(t: &DiscTitle) -> Vec<u16> { fn pids(t: &DiscTitle) -> Vec<u16> {
t.streams t.streams
.iter() .iter()
.filter_map(|s| match s { .map(|s| match s {
Stream::Video(v) => Some(v.pid), Stream::Video(v) => v.pid,
Stream::Audio(a) => Some(a.pid), Stream::Audio(a) => a.pid,
Stream::Subtitle(s) => Some(s.pid), Stream::Subtitle(s) => s.pid,
}) })
.collect() .collect()
} }
+3 -3
View File
@@ -1539,7 +1539,7 @@ mod tests {
// 0xAA filler and the embedded 00 00 01 sequence must be absent — the // 0xAA filler and the embedded 00 00 01 sequence must be absent — the
// malformed PES header contributed ZERO bytes to the elementary stream. // malformed PES header contributed ZERO bytes to the elementary stream.
assert!( assert!(
!pes.data.iter().any(|&b| b == 0xAA), !pes.data.contains(&0xAA),
"garbage PES-header bytes must not appear in the elementary stream" "garbage PES-header bytes must not appear in the elementary stream"
); );
assert!( assert!(
@@ -2124,7 +2124,7 @@ mod tests {
assert_eq!(out.len(), 1); assert_eq!(out.len(), 1);
// None of the 0xEE AF-only bytes may appear. // None of the 0xEE AF-only bytes may appear.
assert!( assert!(
!out[0].data.iter().any(|&b| b == 0xEE), !out[0].data.contains(&0xEE),
"AF-only packet bytes must never be appended as ES" "AF-only packet bytes must never be appended as ES"
); );
assert_eq!(out[0].data, vec![0x01, 0x02, 0x03, 0x04]); assert_eq!(out[0].data, vec![0x01, 0x02, 0x03, 0x04]);
@@ -2165,7 +2165,7 @@ mod tests {
assert_eq!(out.len(), 1); assert_eq!(out.len(), 1);
assert_eq!(out[0].data, vec![0x77, 0x88]); assert_eq!(out[0].data, vec![0x77, 0x88]);
assert!( assert!(
!out[0].data.iter().any(|&b| b == 0xBB), !out[0].data.contains(&0xBB),
"adaptation-field stuffing must not appear in the ES" "adaptation-field stuffing must not appear in the ES"
); );
} }
+1 -1
View File
@@ -1946,7 +1946,7 @@ mod tests {
let icb = build_entry_ads(266, 0, 16, &[(1, 2048, HOLE_LBA), (0, 2048, DATA_LBA)], &[]); let icb = build_entry_ads(266, 0, 16, &[(1, 2048, HOLE_LBA), (0, 2048, DATA_LBA)], &[]);
disc.put_bytes(PART_START + 12, &icb); disc.put_bytes(PART_START + 12, &icb);
let mut fs = super::read_filesystem(&mut disc).expect("volume mounts"); let fs = super::read_filesystem(&mut disc).expect("volume mounts");
let lba = fs let lba = fs
.file_start_lba(&mut disc, "/VIDEO_TS.IFO") .file_start_lba(&mut disc, "/VIDEO_TS.IFO")
.expect("a file whose first descriptor is a hole still has data"); .expect("a file whose first descriptor is a hole still has data");