diff --git a/src/bitwriter.rs b/src/bitwriter.rs index f64a412..2775a41 100644 --- a/src/bitwriter.rs +++ b/src/bitwriter.rs @@ -349,213 +349,3 @@ impl BitWriter { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::bitcursor::BitCursor; - - /// Round-trip: write a set of primitives, read them back, assert - /// equality. - #[test] - fn roundtrip_b_bb_3b() { - let mut w = BitWriter::new(); - w.write_b(true); - w.write_b(false); - w.write_bb(2); - w.write_3b(6); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - assert!(c.read_b().unwrap()); - assert!(!c.read_b().unwrap()); - assert_eq!(c.read_bb().unwrap(), 2); - assert_eq!(c.read_3b().unwrap(), 6); - } - - #[test] - fn roundtrip_bs_specials() { - for v in [0i16, 256, 1, -1, 100, -100, 12345, -12345] { - let mut w = BitWriter::new(); - w.write_bs(v); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_bs().unwrap(), v, "v={v}"); - } - } - - #[test] - fn roundtrip_bl_specials() { - for v in [0i32, 1, -1, 255, 256, 100_000, -100_000, i32::MAX, i32::MIN] { - let mut w = BitWriter::new(); - w.write_bl(v); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_bl().unwrap(), v, "v={v}"); - } - } - - #[test] - fn roundtrip_bd_specials() { - for v in [0.0f64, 1.0, 2.5, -42.125, 1e100, -1e-100] { - let mut w = BitWriter::new(); - w.write_bd(v); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let got = c.read_bd().unwrap(); - if v == 0.0 { - assert_eq!(got, 0.0); - } else if v == 1.0 { - assert_eq!(got, 1.0); - } else { - assert!((got - v).abs() < 1e-10, "v={v} got={got}"); - } - } - } - - #[test] - fn roundtrip_dd_default_and_full() { - for (default, v) in [ - (0.0f64, 0.0f64), - (1.0, 1.0), - (12.5, -42.125), - (-1e-100, 1e100), - ] { - let mut w = BitWriter::new(); - w.write_dd(default, v); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_dd(default).unwrap(), v); - } - } - - #[test] - fn roundtrip_rc_rs_rl_rd() { - let mut w = BitWriter::new(); - w.write_rc(0xAB); - w.write_rs(-12345); - w.write_rl(0xDEAD_BEEF); - w.write_rd(42.5); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_rc().unwrap(), 0xAB); - assert_eq!(c.read_rs().unwrap(), -12345); - assert_eq!(c.read_rl().unwrap(), 0xDEAD_BEEF); - assert_eq!(c.read_rd().unwrap(), 42.5); - } - - #[test] - fn roundtrip_handle() { - let mut w = BitWriter::new(); - w.write_handle(5, 0x0F); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let h = c.read_handle().unwrap(); - assert_eq!(h.code, 5); - assert_eq!(h.counter, 1); - assert_eq!(h.value, 0x0F); - } - - // ================================================================ - // Regression tests for correctness bugs surfaced by the external - // release-readiness audit. - // ================================================================ - - /// `position_bits` must return `N * 8` after exactly `N` whole - /// bytes have been written, not `(N - 1) * 8`. - #[test] - fn position_bits_after_whole_bytes() { - let mut w = BitWriter::new(); - assert_eq!(w.position_bits(), 0); - w.write_bits(0xAB, 8); - assert_eq!(w.position_bits(), 8, "after 1 byte"); - w.write_bits(0xCD, 8); - assert_eq!(w.position_bits(), 16, "after 2 bytes"); - w.write_bits(0x1, 1); - assert_eq!(w.position_bits(), 17, "after 2 bytes + 1 bit"); - // Fill out the current byte. - w.write_bits(0, 7); - assert_eq!(w.position_bits(), 24, "after 3 bytes (boundary)"); - } - - /// `write_mc` must not overflow on `i64::MIN`. The two's-complement - /// negation `-i64::MIN` is unrepresentable as `i64`; the encoder - /// must use `unsigned_abs` instead. - #[test] - fn write_mc_i64_min_does_not_overflow() { - let mut w = BitWriter::new(); - w.write_mc(i64::MIN).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - // Round-trip must preserve the value. - assert_eq!(c.read_mc().unwrap(), i64::MIN); - } - - #[test] - fn write_mc_i64_max_roundtrips() { - let mut w = BitWriter::new(); - w.write_mc(i64::MAX).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_mc().unwrap(), i64::MAX); - } - - /// `try_write_3b` returns `Err(Error::Invalid3B)` for values above - /// `7` instead of panicking. - #[test] - fn try_write_3b_rejects_invalid_values() { - for bad in [8u8, 9, 15, 128, 255] { - let mut w = BitWriter::new(); - let err = w.try_write_3b(bad).unwrap_err(); - assert!( - matches!(err, crate::error::Error::Invalid3B { value } if value == bad), - "bad={bad} err={err:?}" - ); - } - } - - #[test] - fn try_write_3b_accepts_representable_values() { - for good in 0u8..=7 { - let mut w = BitWriter::new(); - assert!(w.try_write_3b(good).is_ok(), "good={good}"); - } - } - - /// BLL must round-trip across the full byte-length spectrum the - /// three-bit count can represent (0..=7 bytes). - #[test] - fn bll_roundtrip_spans_representable_range() { - let values: [u64; 11] = [ - 0, - 1, - 0xFF, - 0x100, - 0xFFFF, - 0x10000, - 0xFFFF_FFFF, - 0xFFFF_FFFF_FFFF, - 1u64 << 40, - 1u64 << 48, - (1u64 << 56) - 1, - ]; - for v in values { - let mut w = BitWriter::new(); - w.write_bll(v) - .unwrap_or_else(|e| panic!("write_bll({v}): {e}")); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let got = c.read_bll().unwrap(); - assert_eq!(got, v, "roundtrip mismatch for v={v:#018X}"); - } - } - - #[test] - fn bll_rejects_values_requiring_more_than_56_bits() { - let mut w = BitWriter::new(); - let res = w.write_bll(1u64 << 56); - assert!( - matches!(res, Err(crate::error::Error::BllOverflow { .. })), - "res={res:?}" - ); - } -} diff --git a/src/element_encoder.rs b/src/element_encoder.rs index bcdc8d0..01a0af1 100644 --- a/src/element_encoder.rs +++ b/src/element_encoder.rs @@ -150,170 +150,3 @@ impl ElementEncoder for Point { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::bitcursor::BitCursor; - use crate::entities::{Point3D, arc, circle, line, point}; - - fn default_extrusion() -> Vec3D { - Vec3D { - x: 0.0, - y: 0.0, - z: 1.0, - } - } - - // -------- LINE round-trips -------- - - #[test] - fn line_2d_roundtrips_via_encoder_and_decoder() { - let original = Line { - start: Point3D { - x: 1.0, - y: 2.0, - z: 0.0, - }, - end: Point3D { - x: 6.0, - y: 5.0, - z: 0.0, - }, - thickness: 0.0, - extrusion: default_extrusion(), - is_2d: true, - }; - let mut w = BitWriter::new(); - original.encode(&mut w, Version::R2018).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let round = line::decode(&mut c).unwrap(); - assert_eq!(round, original); - } - - #[test] - fn line_3d_with_explicit_thickness_and_extrusion_roundtrips() { - let original = Line { - start: Point3D { - x: 1.0, - y: 3.0, - z: 5.0, - }, - end: Point3D { - x: 3.0, - y: 7.0, - z: 11.0, - }, - thickness: 2.5, - extrusion: Vec3D { - x: 0.5, - y: 0.25, - z: 0.75, - }, - is_2d: false, - }; - let mut w = BitWriter::new(); - original.encode(&mut w, Version::R2018).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let round = line::decode(&mut c).unwrap(); - assert_eq!(round, original); - } - - // -------- CIRCLE round-trip -------- - - #[test] - fn circle_default_thickness_and_extrusion_roundtrips() { - let original = Circle { - center: Point3D { - x: 10.0, - y: 20.0, - z: 0.0, - }, - radius: 5.0, - thickness: 0.0, - extrusion: default_extrusion(), - }; - let mut w = BitWriter::new(); - original.encode(&mut w, Version::R2018).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let round = circle::decode(&mut c).unwrap(); - assert_eq!(round, original); - } - - // -------- ARC round-trip -------- - - #[test] - fn arc_quarter_circle_roundtrips() { - let original = Arc { - center: Point3D { - x: 0.0, - y: 0.0, - z: 0.0, - }, - radius: 10.0, - thickness: 0.0, - extrusion: default_extrusion(), - start_angle: 0.0, - end_angle: std::f64::consts::FRAC_PI_2, - }; - let mut w = BitWriter::new(); - original.encode(&mut w, Version::R2018).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let round = arc::decode(&mut c).unwrap(); - assert_eq!(round.center, original.center); - assert_eq!(round.radius, original.radius); - assert_eq!(round.start_angle, original.start_angle); - assert!((round.end_angle - original.end_angle).abs() < 1e-12); - } - - // -------- POINT round-trip -------- - - #[test] - fn point_default_fields_roundtrip() { - let original = Point { - position: Point3D { - x: 1.25, - y: 2.5, - z: 3.75, - }, - thickness: 0.0, - extrusion: default_extrusion(), - x_axis_angle: 0.0, - }; - let mut w = BitWriter::new(); - original.encode(&mut w, Version::R2018).unwrap(); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - let round = point::decode(&mut c).unwrap(); - assert_eq!(round, original); - } - - // -------- BE/BT encoder/decoder symmetry -------- - - #[test] - fn be_default_encodes_as_one_bit() { - let mut w = BitWriter::new(); - write_be(&mut w, default_extrusion()); - // 1 bit + 7 padding = 1 byte. - assert_eq!(w.as_slice().len(), 1); - // The one bit is true (0x80 after MSB-first packing). - assert_eq!(w.as_slice()[0] & 0x80, 0x80); - } - - #[test] - fn bt_nonzero_encodes_flag_plus_bd() { - let mut w = BitWriter::new(); - write_bt(&mut w, 2.5); - let bytes = w.into_bytes(); - let mut c = BitCursor::new(&bytes); - // Flag bit should be false, then a BD follows. - let flag = c.read_b().unwrap(); - assert!(!flag); - let read = c.read_bd().unwrap(); - assert_eq!(read, 2.5); - } -} diff --git a/src/handle_allocator.rs b/src/handle_allocator.rs index 23ea3b4..384fa32 100644 --- a/src/handle_allocator.rs +++ b/src/handle_allocator.rs @@ -105,75 +105,3 @@ impl HandleAllocator { self.next } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fresh_allocator_returns_monotonic_handles_starting_at_1() { - let mut ha = HandleAllocator::new(); - assert_eq!(ha.allocate(), 1); - assert_eq!(ha.allocate(), 2); - assert_eq!(ha.allocate(), 3); - assert_eq!(ha.allocated_count(), 3); - } - - #[test] - fn reserve_then_allocate_skips_reserved_values() { - let mut ha = HandleAllocator::new(); - ha.reserve(1); - ha.reserve(2); - // First allocate should skip to 3. - assert_eq!(ha.allocate(), 3); - assert_eq!(ha.allocate(), 4); - } - - #[test] - fn reserve_range_in_the_middle_does_not_confuse_allocator() { - let mut ha = HandleAllocator::new(); - ha.reserve(5); - ha.reserve(6); - ha.reserve(7); - let seen: Vec = (0..8).map(|_| ha.allocate()).collect(); - // 1..=4, then skip 5..=7, then 8, 9, 10, 11. - assert_eq!(seen, vec![1, 2, 3, 4, 8, 9, 10, 11]); - assert!(ha.contains(5)); - assert!(ha.contains(6)); - assert!(ha.contains(7)); - } - - #[test] - fn starting_at_lets_caller_seed_the_counter() { - let mut ha = HandleAllocator::starting_at(0x100); - assert_eq!(ha.allocate(), 0x100); - assert_eq!(ha.allocate(), 0x101); - } - - #[test] - fn starting_at_zero_is_clamped_to_one() { - let mut ha = HandleAllocator::starting_at(0); - assert_eq!(ha.allocate(), 1, "handle 0 is not legal in DWG"); - } - - #[test] - fn reserve_is_idempotent() { - let mut ha = HandleAllocator::new(); - ha.reserve(42); - ha.reserve(42); - ha.reserve(42); - assert_eq!(ha.allocated_count(), 1); - assert!(ha.contains(42)); - } - - #[test] - fn allocated_count_matches_actual_unique_handles() { - let mut ha = HandleAllocator::new(); - ha.reserve(10); - ha.reserve(20); - let a = ha.allocate(); - let b = ha.allocate(); - assert!(a != b); - assert_eq!(ha.allocated_count(), 4); - } -} diff --git a/src/handle_map.rs b/src/handle_map.rs index 811d228..992f3e9 100644 --- a/src/handle_map.rs +++ b/src/handle_map.rs @@ -374,305 +374,3 @@ fn read_signed_mc(r: &mut BitCursor<'_>) -> Result { } signed_modular_char_value(value, negate) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn empty_map_from_terminator() { - // Size-0 section header → empty map. - let bytes = [0x00, 0x00]; - let map = HandleMap::parse(&bytes).unwrap(); - assert!(map.entries.is_empty()); - } - - #[test] - fn parses_single_entry_section() { - // Section with one (handle_delta, offset_delta) = (1, 10) pair. - // - // Handle delta = +1 → signed MC byte = 0x01 (cont=0, data=1, neg=0). - // Offset delta = +10 → signed MC byte = 0x0A. - // Section size = 4 (2 pair bytes + 2 CRC bytes); CRC ignored here. - let mut data = Vec::new(); - data.extend_from_slice(&4u16.to_be_bytes()); - data.push(0x01); - data.push(0x0A); - data.extend_from_slice(&[0x00, 0x00]); // placeholder CRC - data.extend_from_slice(&[0x00, 0x00]); // terminator - let map = HandleMap::parse(&data).unwrap(); - assert_eq!(map.entries.len(), 1); - assert_eq!(map.entries[0].handle, 1); - assert_eq!(map.entries[0].offset, 10); - } - - #[test] - fn monotonic_handles_negative_offset_jump() { - // Two entries: (h=1, off=100), (h=2, off=50). - // Deltas: (+1, +100), (+1, -50). - // - // +100 is too big for a single terminating-byte signed MC (only - // 6 data bits = 0..=63), so it takes two bytes: - // byte 0: cont=1, data = 100 & 0x7F = 0x64 → 0x80 | 0x64 = 0xE4 - // byte 1: cont=0, negate=0, data = 0 → 0x00 - // - // -50 fits in one signed MC terminating byte: - // byte: cont=0, negate=1, data = 50 & 0x3F = 0x32 → 0x72 - let payload: Vec = vec![ - 0x01, // h_delta = +1 - 0xE4, // o_delta byte 0 (continues) - 0x00, // o_delta byte 1 (terminates, value = 100) - 0x01, // h_delta = +1 - 0x72, // o_delta = -50 - ]; - let mut data = Vec::new(); - data.extend_from_slice(&((payload.len() + 2) as u16).to_be_bytes()); - data.extend_from_slice(&payload); - data.extend_from_slice(&[0x00, 0x00]); // CRC placeholder - data.extend_from_slice(&[0x00, 0x00]); // terminator section - let map = HandleMap::parse(&data).unwrap(); - assert_eq!(map.entries.len(), 2); - assert_eq!( - map.entries[0], - HandleEntry { - handle: 1, - offset: 100 - } - ); - assert_eq!( - map.entries[1], - HandleEntry { - handle: 2, - offset: 50 - } - ); - } - - #[test] - fn handle_delta_is_unsigned_so_bit_0x40_is_magnitude_not_sign() { - // One pair: handle delta byte 0x56, offset delta byte 0x04. - // - // Read as an UNSIGNED modular char, 0x56 is 86. Read as a - // SIGNED one it would be -(0x56 & 0x3F) = -22 — the decode bug - // this test pins. 86 is the correct value: handle numbers only - // grow inside a handle section, so the encoder spends all seven - // low bits on magnitude. - let payload: Vec = vec![0x56, 0x04]; - let mut data = Vec::new(); - data.extend_from_slice(&((payload.len() + 2) as u16).to_be_bytes()); - data.extend_from_slice(&payload); - data.extend_from_slice(&[0x00, 0x00]); // CRC placeholder - data.extend_from_slice(&[0x00, 0x00]); // terminator - let map = HandleMap::parse(&data).unwrap(); - assert_eq!(map.entries.len(), 1); - assert_eq!(map.entries[0].handle, 86); - assert_eq!(map.entries[0].offset, 4); - } - - #[test] - fn multi_byte_handle_delta_carries_seven_bits_in_its_terminator() { - // 0xC1 0x02 = continuation limb 0x41 (65) + terminator 0x02. - // Unsigned: 65 | (2 << 7) = 321. The signed reading would mask - // the terminator to six bits — same value here — but would also - // treat a terminator with 0x40 set as a sign; the companion - // pair below (0x81 0x41) proves the difference: unsigned gives - // 1 | (0x41 << 7) = 8321, signed would give -1. - let payload: Vec = vec![0xC1, 0x02, 0x01, 0x81, 0x41, 0x01]; - let mut data = Vec::new(); - data.extend_from_slice(&((payload.len() + 2) as u16).to_be_bytes()); - data.extend_from_slice(&payload); - data.extend_from_slice(&[0x00, 0x00]); - data.extend_from_slice(&[0x00, 0x00]); - let map = HandleMap::parse(&data).unwrap(); - assert_eq!(map.entries.len(), 2); - assert_eq!(map.entries[0].handle, 321); - assert_eq!(map.entries[1].handle, 321 + 8321); - } - - #[test] - fn accumulators_restart_on_every_handle_section() { - // Section 1: (handle 5, offset 100). Section 2 opens with the - // pair (7, 40) — which is the ABSOLUTE handle 7 at ABSOLUTE - // offset 40, not handle 12 at offset 140. Handle 7 < 12 also - // makes the two readings distinguishable in one assertion. - let mut data = Vec::new(); - let s1: Vec = vec![0x05, 0xE4, 0x00]; // h=+5, o=+100 (two-byte MC) - data.extend_from_slice(&((s1.len() + 2) as u16).to_be_bytes()); - data.extend_from_slice(&s1); - data.extend_from_slice(&[0x00, 0x00]); - let s2: Vec = vec![0x07, 0x28]; // h=7, o=40 - data.extend_from_slice(&((s2.len() + 2) as u16).to_be_bytes()); - data.extend_from_slice(&s2); - data.extend_from_slice(&[0x00, 0x00]); - data.extend_from_slice(&[0x00, 0x00]); // terminator - let map = HandleMap::parse(&data).unwrap(); - assert_eq!( - map.entries, - vec![ - HandleEntry { - handle: 5, - offset: 100 - }, - HandleEntry { - handle: 7, - offset: 40 - }, - ] - ); - } - - // -------- L12-08: writer tests -------- - - #[test] - fn signed_mc_short_roundtrip() { - for v in [0i64, 1, -1, 0x3F, -0x3F] { - let mut buf = Vec::new(); - write_signed_mc(&mut buf, v); - let mut c = BitCursor::new(&buf); - let read = read_signed_mc(&mut c).unwrap(); - assert_eq!(read, v, "roundtrip mismatch for v={v}"); - } - } - - #[test] - fn signed_mc_multi_byte_roundtrip() { - for v in [100i64, -100, 1000, -1000, 0xFFFF, -0xFFFF] { - let mut buf = Vec::new(); - write_signed_mc(&mut buf, v); - let mut c = BitCursor::new(&buf); - let read = read_signed_mc(&mut c).unwrap(); - assert_eq!(read, v, "roundtrip mismatch for v={v}"); - } - } - - #[test] - fn write_handle_map_empty_map_emits_only_the_terminator_section() { - let map = HandleMap::default(); - let mut w = BitWriter::new(); - let bytes = write_handle_map(&map, &mut w, Version::R2018).unwrap(); - // Just the empty size-2 section and its CRC, as real files end. - assert_eq!(bytes, vec![0x00, 0x02, 0x01, 0xD0]); - // Parse roundtrips to empty. - let parsed = HandleMap::parse(&bytes).unwrap(); - assert!(parsed.entries.is_empty()); - } - - #[test] - fn write_handle_map_single_entry_roundtrips_through_parse() { - let map = HandleMap { - entries: vec![HandleEntry { - handle: 1, - offset: 10, - }], - }; - let mut w = BitWriter::new(); - let bytes = write_handle_map(&map, &mut w, Version::R2018).unwrap(); - let parsed = HandleMap::parse(&bytes).unwrap(); - assert_eq!(parsed.entries, map.entries); - } - - #[test] - fn write_handle_map_multi_entry_with_negative_offset_delta_roundtrips() { - let map = HandleMap { - entries: vec![ - HandleEntry { - handle: 1, - offset: 100, - }, - HandleEntry { - handle: 2, - offset: 50, - }, - HandleEntry { - handle: 10, - offset: 500, - }, - ], - }; - let mut w = BitWriter::new(); - let bytes = write_handle_map(&map, &mut w, Version::R2018).unwrap(); - let parsed = HandleMap::parse(&bytes).unwrap(); - assert_eq!(parsed.entries, map.entries); - } - - #[test] - fn write_handle_map_roundtrips_deltas_that_set_bit_0x40() { - // Handle deltas of 64..=127 are exactly the range the signed-MC - // misreading corrupted; make the writer/reader pair prove them. - let entries: Vec = (0u64..64) - .map(|i| HandleEntry { - handle: 1 + i * 86, - offset: 4 + i * 70, - }) - .collect(); - let map = HandleMap { - entries: entries.clone(), - }; - let mut w = BitWriter::new(); - let bytes = write_handle_map(&map, &mut w, Version::R2018).unwrap(); - assert_eq!(HandleMap::parse(&bytes).unwrap().entries, entries); - } - - #[test] - fn write_handle_map_splits_a_section_when_the_handle_moves_backward() { - // The handle delta is unsigned on the wire, so a backward jump - // has to open a new section (whose accumulators restart at 0). - let entries = vec![ - HandleEntry { - handle: 500, - offset: 10, - }, - HandleEntry { - handle: 900, - offset: 20, - }, - HandleEntry { - handle: 7, - offset: 30, - }, - HandleEntry { - handle: 9, - offset: 40, - }, - ]; - let map = HandleMap { - entries: entries.clone(), - }; - let mut w = BitWriter::new(); - let bytes = write_handle_map(&map, &mut w, Version::R2018).unwrap(); - // Two data sections plus the empty size-2 terminator section. - let mut sections = 0; - let mut pos = 0usize; - while pos + 2 <= bytes.len() { - let size = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; - if size == 2 { - break; - } - sections += 1; - pos += 2 + size; - } - assert_eq!(sections, 2); - assert_eq!(HandleMap::parse(&bytes).unwrap().entries, entries); - } - - #[test] - fn write_handle_map_many_entries_produces_multiple_sections() { - // Enough entries that the writer must emit > 1 section. At 4 - // bytes per short-delta pair, ~508 pairs max per section. Emit - // 1500 entries so the writer splits at least 3 times. - let entries: Vec = (1u64..=1500) - .map(|h| HandleEntry { - handle: h, - offset: h * 10, - }) - .collect(); - let map = HandleMap { - entries: entries.clone(), - }; - let mut w = BitWriter::new(); - let bytes = write_handle_map(&map, &mut w, Version::R2018).unwrap(); - let parsed = HandleMap::parse(&bytes).unwrap(); - assert_eq!(parsed.entries.len(), entries.len()); - assert_eq!(parsed.entries, entries); - } -} diff --git a/src/lz77_encode.rs b/src/lz77_encode.rs index 8101025..05c5025 100644 --- a/src/lz77_encode.rs +++ b/src/lz77_encode.rs @@ -107,109 +107,3 @@ pub fn compress(input: &[u8]) -> Result> { pub fn compress_infallible(input: &[u8]) -> Vec { compress(input).expect("unencodable LZ77 literal length") } - -#[cfg(test)] -mod tests { - use super::*; - use crate::lz77; - - fn roundtrip(input: &[u8]) { - let enc = compress(input).expect("encode"); - let dec = lz77::decompress(&enc, None).expect("decode"); - assert_eq!(dec, input, "round-trip failed for {} bytes", input.len()); - } - - #[test] - fn encode_empty_round_trips() { - roundtrip(b""); - } - - #[test] - fn encode_short_literal_round_trips() { - // 5 bytes = within 0x01..=0x0F simple range. - roundtrip(b"ABCDE"); - } - - #[test] - fn encode_exact_18_byte_boundary() { - let input: Vec = (0..18u8).collect(); - roundtrip(&input); - } - - #[test] - fn encode_19_byte_extension_boundary() { - let input: Vec = (0..19u8).collect(); - roundtrip(&input); - } - - #[test] - fn encode_repeating_round_trips() { - roundtrip(b"AAAAAAAA"); - } - - #[test] - fn encode_long_literal_round_trips() { - // 275 bytes = matches the decoder's `extended_literal_length` - // fixture (0x0F + 0xFF + 0x02 + 3 = 275). - let input: Vec = (0..275).map(|i| (i & 0xFF) as u8).collect(); - roundtrip(&input); - } - - #[test] - fn encode_very_long_multi_extension() { - // 1024 bytes — requires multiple 0x00 extension bytes. - let input: Vec = (0..1024).map(|i| (i & 0xFF) as u8).collect(); - roundtrip(&input); - } - - #[test] - fn encode_rejects_1_byte_input() { - assert!(matches!( - compress(&[0x42]), - Err(Error::Lz77UnencodableLength(1)) - )); - } - - #[test] - fn encode_rejects_3_byte_input() { - assert!(matches!( - compress(b"ABC"), - Err(Error::Lz77UnencodableLength(3)) - )); - } - - // -------- L12-09: additional coverage -------- - - /// Spec §4.7 boundary: 4 bytes is the smallest representable initial - /// literal (literal-length byte = 1 → `1 + 3 = 4`). Confirms the - /// lower edge of the encoding gap is the correct cliff. - #[test] - fn encode_4_byte_input_is_smallest_representable() { - // Must succeed at exactly 4 bytes. - assert!(compress(&[1, 2, 3, 4]).is_ok()); - // Must fail at 3. - assert!(matches!( - compress(&[1, 2, 3]), - Err(Error::Lz77UnencodableLength(3)) - )); - } - - /// Spec §4.7: when a run of 0xFF extension bytes rolls over, the - /// terminator byte must be non-zero. Exercise at a size where the - /// rollover forces the extension pipeline to step twice. - #[test] - fn encode_rollover_boundary_roundtrips() { - // 0x0F + 0xFF + 0x01 + 3 = 0x112 = 274 bytes. - let input: Vec = (0..274).map(|i| (i & 0xFF) as u8).collect(); - roundtrip(&input); - } - - /// Very large literal exercises multi-byte rollover; chosen to - /// require two full 0xFF extension bytes before the terminator. - #[test] - fn encode_large_literal_multi_rollover_roundtrips() { - // 0x0F + 0xFF + 0xFF + 0x10 + 3 = 0x220 = 544 bytes. - let input: Vec = (0..544).map(|i| (i & 0x7F) as u8).collect(); - roundtrip(&input); - } -} diff --git a/src/reed_solomon_encode.rs b/src/reed_solomon_encode.rs index 0475d0e..29128e7 100644 --- a/src/reed_solomon_encode.rs +++ b/src/reed_solomon_encode.rs @@ -65,64 +65,3 @@ pub fn encode_with(code: Code, message: &[u8]) -> Result> { pub fn encode(message: &[u8]) -> Result> { encode_with(Code::SYSTEM_PAGE, message) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::reed_solomon; - - #[test] - fn encode_rejects_wrong_message_length() { - assert!(encode(&[0u8; 100]).is_err()); - assert!(encode(&[0u8; 240]).is_err()); - assert!(encode(&[]).is_err()); - } - - #[test] - fn encoded_all_zero_message_has_zero_syndromes() { - let msg = vec![0u8; MESSAGE_BYTES]; - let mut cw = encode(&msg).unwrap(); - assert_eq!(cw.len(), CODEWORD_BYTES); - // Intact codeword — verify must succeed without correction. - reed_solomon::verify(&mut cw).expect("encoded codeword must be intact"); - } - - #[test] - fn encoded_all_ones_message_verifies() { - let msg = vec![0xFFu8; MESSAGE_BYTES]; - let mut cw = encode(&msg).unwrap(); - reed_solomon::verify(&mut cw).expect("all-0xFF encodes cleanly"); - } - - #[test] - fn encoded_counter_pattern_verifies() { - let msg: Vec = (0..MESSAGE_BYTES).map(|i| (i & 0xFF) as u8).collect(); - let mut cw = encode(&msg).unwrap(); - // Sanity: message section of codeword equals the input. - assert_eq!(&cw[..MESSAGE_BYTES], msg.as_slice()); - reed_solomon::verify(&mut cw).expect("counter pattern encodes cleanly"); - } - - #[test] - fn encoded_random_ish_pattern_verifies() { - // A slightly less regular byte stream than the counter; LCG-style. - let mut msg = Vec::with_capacity(MESSAGE_BYTES); - let mut state: u32 = 0xDEAD_BEEF; - for _ in 0..MESSAGE_BYTES { - state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345); - msg.push(((state >> 16) & 0xFF) as u8); - } - let mut cw = encode(&msg).unwrap(); - reed_solomon::verify(&mut cw).expect("LCG pattern encodes cleanly"); - } - - #[test] - fn encoded_codeword_is_exactly_255_bytes() { - let msg = vec![0x42u8; MESSAGE_BYTES]; - let cw = encode(&msg).unwrap(); - assert_eq!(cw.len(), 255); - // And the leading MESSAGE_BYTES of the codeword is the message - // — systematic encoding preserves the input. - assert_eq!(&cw[..MESSAGE_BYTES], msg.as_slice()); - } -} diff --git a/src/section_writer.rs b/src/section_writer.rs index d82ab8a..ebd89af 100644 --- a/src/section_writer.rs +++ b/src/section_writer.rs @@ -174,104 +174,3 @@ fn write_u32_le(buf: &mut [u8], offset: usize, value: u32) { let bytes = value.to_le_bytes(); buf[offset..offset + 4].copy_from_slice(&bytes); } - -#[cfg(test)] -mod tests { - use super::*; - use crate::cipher::section_page_mask; - - #[test] - fn build_empty_section() { - let s = build_section(&[], 1, 0x1000).unwrap(); - assert_eq!(s.bytes.len() % 32, 0); - assert_eq!(s.decompressed_size, 0); - // compressed is the literal-only empty stream = [0x11] = 1 byte. - assert_eq!(s.compressed_size, 1); - } - - #[test] - fn build_small_section() { - let data = b"Hello, world!"; // 13 bytes - let s = build_section(data, 5, 0x2000).unwrap(); - assert_eq!(s.decompressed_size, 13); - assert!(s.compressed_size > 13); // framing adds a few bytes - assert_eq!(s.bytes.len() % 32, 0); - } - - #[test] - fn header_roundtrips_sec_mask() { - // Build a section, then strip the Sec_Mask to verify it recovers - // the literal page_type tag. - let data = b"ABCDEFGH"; - let s = build_section(data, 1, 0x1000).unwrap(); - let header_copy: [u8; HEADER_SIZE] = s.bytes[..32].try_into().unwrap(); - let mask = section_page_mask(0x1000); - // Undo the mask on the first word. - let word0 = u32::from_le_bytes(header_copy[..4].try_into().unwrap()); - assert_eq!(word0 ^ mask, DATA_PAGE_HEADER_TYPE); - // And verify the section-number word recovers correctly. - let word1 = u32::from_le_bytes(header_copy[4..8].try_into().unwrap()); - assert_eq!(word1 ^ mask, 1); - // The padded length past the header should equal compressed_size. - let word2 = u32::from_le_bytes(header_copy[8..12].try_into().unwrap()); - assert_eq!(word2 ^ mask, s.compressed_size); - } - - #[test] - fn checksum_deterministic() { - let a = compute_checksum(b"HELLO", 0); - let b = compute_checksum(b"HELLO", 0); - assert_eq!(a, b); - // And different inputs produce different sums in the overwhelming - // majority of cases. - let c = compute_checksum(b"HELLO!", 0); - assert_ne!(a, c); - } - - #[test] - fn checksum_seed_chains() { - // Chaining: checksum(B, seed=checksum(A, 0)) should not equal - // checksum(AB, 0) in general (the spec's algorithm isn't a - // simple running checksum), but our implementation should be - // deterministic regardless. - let s1 = compute_checksum(b"PART1", 0); - let s2 = compute_checksum(b"PART2", s1); - assert_ne!(s2, 0); - } - - /// `compute_checksum` is the Stage-1 hot loop (issue #17) and is a - /// tempting target for "optimisation". Pin its exact output across - /// lengths and seeds so any future restructuring — unrolling, - /// chunking, a wider accumulator — has to stay byte-identical. - #[test] - fn checksum_matches_reference_definition_at_every_length() { - /// The spec §4.6.1 definition, written out one byte at a time. - fn reference(data: &[u8], seed: u32) -> u32 { - let mut sum: u32 = seed; - for &b in data { - sum = sum.rotate_left(1); - sum = sum.wrapping_add(b as u32); - } - sum - } - - // Deterministic pseudo-random bytes — a uniform fill would not - // catch a wrong-order unroll. - let mut data = Vec::with_capacity(300); - let mut s: u32 = 0x1234_5678; - for _ in 0..300 { - s = s.wrapping_mul(1_103_515_245).wrapping_add(12345); - data.push((s >> 16) as u8); - } - - for len in 0..data.len() { - for seed in [0u32, 1, 0xFFFF_FFFF, 0x8000_0001] { - assert_eq!( - compute_checksum(&data[..len], seed), - reference(&data[..len], seed), - "checksum diverged from the reference at len={len} seed={seed:#010x}" - ); - } - } - } -}