Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 0 additions & 123 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,126 +379,3 @@ pub fn assert_no_unknown_objects(file: &DwgFile, version: Version) -> Result<()>
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parsemode_default_is_best_effort() {
assert_eq!(ParseMode::default(), ParseMode::BestEffort);
}

#[test]
fn decoded_complete_has_no_warnings() {
let d = Decoded::complete(42);
assert!(d.complete);
assert!(d.diagnostics.warnings.is_empty());
}

#[test]
fn decoded_partial_preserves_diagnostics() {
let mut diag = Diagnostics::default();
diag.warn("test", "something went off");
let d = Decoded::partial("value", diag);
assert!(!d.complete);
assert_eq!(d.diagnostics.warnings.len(), 1);
}

#[test]
fn decoded_map_preserves_diagnostics() {
let mut diag = Diagnostics::default();
diag.warn("a", "b");
let d = Decoded::partial(10_i32, diag);
let mapped = d.map(|n| n * 2);
assert_eq!(mapped.value, 20);
assert_eq!(mapped.diagnostics.warnings.len(), 1);
assert!(!mapped.complete);
}

#[test]
fn diagnostics_is_clean_on_default() {
let d = Diagnostics::default();
assert!(d.is_clean());
}

#[test]
fn diagnostics_warn_mutates() {
let mut d = Diagnostics::default();
d.warn("x", "y");
assert!(!d.is_clean());
assert_eq!(d.warnings[0].code, "x");
assert_eq!(d.warnings[0].message, "y");
assert_eq!(d.warnings[0].bit_offset, None);
}

#[test]
fn diagnostics_warn_at_carries_offset() {
let mut d = Diagnostics::default();
d.warn_at("truncated", 1234, "section ran short");
assert_eq!(d.warnings[0].bit_offset, Some(1234));
}

#[test]
fn confidence_zero_attempts_is_one() {
let d = Diagnostics::default();
assert_eq!(d.confidence(0), 1.0);
}

#[test]
fn confidence_all_clean_is_one() {
let d = Diagnostics::default();
assert_eq!(d.confidence(100), 1.0);
}

#[test]
fn confidence_counts_all_failure_modes() {
let mut d = Diagnostics {
skipped_records: 10,
failed_streams: 5,
partial_fields: 3,
..Default::default()
};
d.warn("a", "b");
d.warn("c", "d");
// 20 bad out of 100 → confidence 0.8
assert_eq!(d.confidence(100), 0.8);
}

#[test]
fn decoded_complete_initializes_new_fields() {
let d = Decoded::complete(7_u8);
assert_eq!(d.consumed_bits, 0);
assert_eq!(d.stopped_at_field, None);
}

#[test]
fn decoded_partial_initializes_new_fields() {
let d = Decoded::partial(7_u8, Diagnostics::default());
assert_eq!(d.consumed_bits, 0);
assert_eq!(d.stopped_at_field, None);
}

#[test]
fn decoded_partial_at_captures_marker() {
let mut diag = Diagnostics::default();
diag.warn("truncated_field", "ran out of bits in flags");
let d = Decoded::partial_at(0_u32, diag, 1_234, "flags");
assert!(!d.complete);
assert_eq!(d.consumed_bits, 1_234);
assert_eq!(d.stopped_at_field, Some("flags"));
assert_eq!(d.diagnostics.warnings[0].code, "truncated_field");
}

#[test]
fn decoded_map_preserves_partial_at_markers() {
let mut diag = Diagnostics::default();
diag.warn("x", "y");
let d = Decoded::partial_at(10_i32, diag, 64, "thickness");
let mapped = d.map(|n| n.to_string());
assert_eq!(mapped.value, "10");
assert_eq!(mapped.consumed_bits, 64);
assert_eq!(mapped.stopped_at_field, Some("thickness"));
assert!(!mapped.complete);
}
}
261 changes: 0 additions & 261 deletions src/bitcursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,264 +479,3 @@ impl Handle {
matches!(self.code, 0x6 | 0x8 | 0xA | 0xC)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::bitwriter::BitWriter;

#[test]
fn read_handle_accepts_a_full_eight_byte_counter() {
let mut w = BitWriter::new();
w.write_handle(4, u64::MAX);
let bytes = w.into_bytes();
let h = BitCursor::new(&bytes).read_handle().unwrap();
assert_eq!(h.code, 4);
assert_eq!(h.counter, 8);
assert_eq!(h.value, u64::MAX);
}

#[test]
fn read_handle_rejects_a_counter_above_eight() {
// Code 2, counter 11 — the signature the sample_AC1032.dwg
// phantom records carried (#44/#51). Eleven value bytes cannot
// fit a u64, so the cursor is provably not on a handle field.
let mut w = BitWriter::new();
w.write_rc(0x2B);
for b in 0..11u8 {
w.write_rc(b);
}
let bytes = w.into_bytes();
let err = BitCursor::new(&bytes).read_handle().unwrap_err();
match err {
Error::HandleCounterTooLarge { code, counter } => {
assert_eq!(code, 2);
assert_eq!(counter, 11);
}
other => panic!("expected HandleCounterTooLarge, got {other:?}"),
}
}

/// Helper: pack a sequence of `(value, bit_count)` pairs MSB-first into a
/// byte vector. Pads the final byte with zeros. Used to construct bit
/// streams for spec example tests without pen-and-paper arithmetic.
fn pack_bits(fields: &[(u64, u32)]) -> Vec<u8> {
let mut acc: u128 = 0;
let mut nbits: u32 = 0;
for &(v, k) in fields {
assert!(k <= 64);
assert!(nbits + k <= 128, "test helper overflow");
acc = (acc << k) | (v as u128);
nbits += k;
}
// pad to byte boundary
let pad = (8 - (nbits % 8)) % 8;
acc <<= pad as u128;
let total = nbits + pad;
let mut out = Vec::with_capacity((total / 8) as usize);
let mut remaining = total;
while remaining > 0 {
remaining -= 8;
out.push(((acc >> remaining) & 0xFF) as u8);
}
out
}

// Spec §2.2 — BITSHORT worked example:
// Stream: 00 00000001 00000001 10 11 01 00001111 10
// 5 shorts: 257, 0, 256, 15, 0
#[test]
fn spec_2_2_bitshort_example() {
let stream = pack_bits(&[
(0b00, 2), // tag 00 → 16-bit LE
(0b0000_0001, 8), // LSB
(0b0000_0001, 8), // MSB → 257
(0b10, 2), // → 0
(0b11, 2), // → 256
(0b01, 2), // tag 01 → 8-bit
(0b0000_1111, 8), // = 15
(0b10, 2), // → 0
]);
let mut c = BitCursor::new(&stream);
assert_eq!(c.read_bs().unwrap(), 257);
assert_eq!(c.read_bs().unwrap(), 0);
assert_eq!(c.read_bs().unwrap(), 256);
assert_eq!(c.read_bs().unwrap(), 15);
assert_eq!(c.read_bs().unwrap(), 0);
}

// Spec §2.3 — BITLONG worked example:
// Stream: 00 00000001 00000001 00000000 00000000 10 01 00001111 10
// 4 longs: 257, 0, 15, 0
#[test]
fn spec_2_3_bitlong_example() {
let stream = pack_bits(&[
(0b00, 2), // tag 00 → 32-bit LE
(0x01, 8), // byte 0 (LSB)
(0x01, 8), // byte 1
(0x00, 8), // byte 2
(0x00, 8), // byte 3 (MSB) → 257
(0b10, 2), // → 0
(0b01, 2), // tag 01 → 8-bit
(0b0000_1111, 8), // = 15
(0b10, 2), // → 0
]);
let mut c = BitCursor::new(&stream);
assert_eq!(c.read_bl().unwrap(), 257);
assert_eq!(c.read_bl().unwrap(), 0);
assert_eq!(c.read_bl().unwrap(), 15);
assert_eq!(c.read_bl().unwrap(), 0);
}

// Spec §2.5 — BITDOUBLE special cases + one full double.
#[test]
fn spec_2_5_bitdouble_specials() {
// Prefix 01 10 00 then 8 LE bytes of 2.0.
let bytes_2p0 = 2.0f64.to_le_bytes();
let mut fields: Vec<(u64, u32)> = vec![
(0b01, 2), // → 1.0
(0b10, 2), // → 0.0
(0b00, 2), // → read 8 raw LE bytes
];
for b in bytes_2p0 {
fields.push((b as u64, 8));
}
let stream = pack_bits(&fields);
let mut c = BitCursor::new(&stream);
assert_eq!(c.read_bd().unwrap(), 1.0);
assert_eq!(c.read_bd().unwrap(), 0.0);
assert_eq!(c.read_bd().unwrap(), 2.0);
}

// Spec §2.5 — DD (bitdouble with default) examples.
#[test]
fn spec_2_5_bitdouble_with_default_variants() {
let default = 12.5f64;
let patch4 = f64::from_le_bytes([1, 2, 3, 4, 0, 0, 0x29, 0x40]);
let patch6 = f64::from_le_bytes([9, 8, 7, 6, 5, 4, 0x29, 0x40]);
let full = -42.25f64;

let fields: Vec<(u64, u32)> = vec![
(0b00, 2),
(0b01, 2),
(1, 8),
(2, 8),
(3, 8),
(4, 8),
(0b10, 2),
(5, 8),
(4, 8),
(9, 8),
(8, 8),
(7, 8),
(6, 8),
];

let stream = pack_bits(&fields);
let mut c = BitCursor::new(&stream);
assert_eq!(c.read_dd(default).unwrap(), default);
assert_eq!(c.read_dd(default).unwrap(), patch4);
assert_eq!(c.read_dd(default).unwrap(), patch6);

let mut full_fields: Vec<(u64, u32)> = vec![(0b11, 2)];
for b in full.to_le_bytes() {
full_fields.push((b as u64, 8));
}
let stream = pack_bits(&full_fields);
let mut c = BitCursor::new(&stream);
assert_eq!(c.read_dd(default).unwrap(), full);
}

// §2.0 — single bits and 2-bit codes
#[test]
fn single_bits() {
let bytes = [0b1010_0000];
let mut c = BitCursor::new(&bytes);
assert!(c.read_b().unwrap());
assert!(!c.read_b().unwrap());
assert!(c.read_b().unwrap());
assert!(!c.read_b().unwrap());
}

#[test]
fn two_bit_codes() {
// 11 01 10 00 → 3, 1, 2, 0
let bytes = [0b1101_1000];
let mut c = BitCursor::new(&bytes);
assert_eq!(c.read_bb().unwrap(), 3);
assert_eq!(c.read_bb().unwrap(), 1);
assert_eq!(c.read_bb().unwrap(), 2);
assert_eq!(c.read_bb().unwrap(), 0);
}

// §2.1 — 3B
#[test]
fn read_3b_is_three_raw_bits() {
for value in 0u8..=7 {
let bytes = [value << 5];
assert_eq!(
BitCursor::new(&bytes).read_3b().unwrap(),
value,
"value={value}"
);
}
// Three bits, always — the next read starts at bit 3.
let bytes = [0b0011_1111];
let mut c = BitCursor::new(&bytes);
assert_eq!(c.read_3b().unwrap(), 0b001);
assert_eq!(c.position_bits(), 3);
}

// Byte alignment between objects, per §2.14.
#[test]
fn align_to_byte_drops_partial() {
let bytes = [0b1010_0000, 0xAB];
let mut c = BitCursor::new(&bytes);
// Consume 3 bits; cursor at bit 3.
let _ = c.read_bits(3).unwrap();
c.align_to_byte();
assert_eq!(c.read_rc().unwrap(), 0xAB);
}

#[test]
fn remaining_bits_tracks_position() {
let bytes = [0xFF, 0xFF];
let mut c = BitCursor::new(&bytes);
assert_eq!(c.remaining_bits(), 16);
let _ = c.read_bits(5).unwrap();
assert_eq!(c.remaining_bits(), 11);
}

#[test]
fn exhaustion_returns_error() {
let bytes = [0xFF];
let mut c = BitCursor::new(&bytes);
let _ = c.read_bits(8).unwrap();
assert!(matches!(c.read_b(), Err(Error::BitsExhausted { .. })));
}

// §2.13 — Handle reference
//
// Spec example: LAYER 0 has handle 0x0F stored as 5.1.0F
// → CODE=5 COUNTER=1 HANDLE=0x0F
// → bitstream: 0101 0001 00001111
#[test]
fn handle_layer_0_example() {
let bytes = [0b0101_0001, 0b0000_1111];
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);
assert!(h.is_absolute());
assert!(!h.is_offset());
}

#[test]
fn raw_types_are_byte_aligned_le() {
let bytes = [0x01, 0x00, 0x02, 0x00, 0x00, 0x00];
let mut c = BitCursor::new(&bytes);
assert_eq!(c.read_rs().unwrap(), 1);
assert_eq!(c.read_rl().unwrap(), 2);
}
}
Loading
Loading