use super::*;
#[test]
fn producer_constructs_with_default_params() {
let p = LdmParams::adjust_for(27, 9);
assert_eq!(p.window_log, 27);
assert_eq!(p.min_match_length, 32);
assert_eq!(p.hash_rate_log, 4);
assert_eq!(p.bucket_size_log, 8);
}
fn test_params() -> LdmParams {
LdmParams {
window_log: 27,
hash_log: 10,
hash_rate_log: 4,
min_match_length: 32,
bucket_size_log: 4,
}
}
#[test]
fn clear_resets_rolling_hash_state() {
let mut producer = LdmProducer::new(test_params());
let mut out = Vec::new();
let data = [0xAAu8; 256];
producer.generate_into(&data, 0, 0, data.len(), &mut out);
let advanced = producer.hash_state.rolling;
assert_ne!(
advanced,
gear_hash::GEAR_HASH_INIT,
"rolling hash should have moved after generate_into"
);
producer.clear();
assert_eq!(
producer.hash_state.rolling,
gear_hash::GEAR_HASH_INIT,
"clear must rewind to GEAR_HASH_INIT"
);
}
#[test]
fn generate_into_emits_long_range_match_on_repeated_payload() {
let mut producer = LdmProducer::new(test_params());
let p = producer.params();
assert_eq!(p.min_match_length, 32);
const PAYLOAD: usize = 4096;
const GAP: usize = 64 * 1024;
let mut history = Vec::with_capacity(2 * PAYLOAD + GAP);
let mut prng: u32 = 0x1234_5678;
let payload: alloc::vec::Vec<u8> = (0..PAYLOAD)
.map(|_| {
prng = prng.wrapping_mul(1_103_515_245).wrapping_add(12_345);
(prng >> 16) as u8
})
.collect();
history.extend_from_slice(&payload);
history.extend((0..GAP).map(|i| (i % 251) as u8));
history.extend_from_slice(&payload);
let mut out = Vec::new();
producer.generate_into(&history, 0, 0, history.len(), &mut out);
assert!(
!out.is_empty(),
"long-range repetition must produce at least one LDM sequence"
);
for seq in &out {
assert!(
seq.match_length >= p.min_match_length as usize,
"every emitted match must reach min_match_length \
(got match_length = {})",
seq.match_length
);
}
let crossing_gap = out.iter().any(|s| s.offset >= GAP);
assert!(
crossing_gap,
"at least one emitted sequence must have offset >= GAP \
({GAP}); offsets observed: {:?}",
out.iter().map(|s| s.offset).collect::<alloc::vec::Vec<_>>()
);
}
#[test]
fn generate_into_preserves_bucket_entries_across_history_slide() {
let mut producer = LdmProducer::new(test_params());
let p = producer.params();
assert_eq!(p.min_match_length, 32);
const PAYLOAD: usize = 4096;
const GAP_A: usize = 32 * 1024;
const GAP_B: usize = 32 * 1024;
let mut prng: u32 = 0xC0FFEEEE;
let payload: alloc::vec::Vec<u8> = (0..PAYLOAD)
.map(|_| {
prng = prng.wrapping_mul(1_103_515_245).wrapping_add(12_345);
(prng >> 16) as u8
})
.collect();
let mut frame = alloc::vec::Vec::with_capacity(2 * PAYLOAD + GAP_A + GAP_B);
frame.extend_from_slice(&payload);
frame.extend((0..GAP_A).map(|i| (i % 251) as u8));
frame.extend_from_slice(&payload);
frame.extend((0..GAP_B).map(|i| ((i + 17) % 241) as u8));
let mut out1 = Vec::new();
let split_at = PAYLOAD + GAP_A;
producer.generate_into(&frame[..split_at], 0, 0, split_at, &mut out1);
let eviction = PAYLOAD / 2; let live = &frame[eviction..];
let history_abs_start = eviction;
let block_start_abs = PAYLOAD + GAP_A; let block_end_abs = block_start_abs + PAYLOAD;
let mut out2 = Vec::new();
producer.generate_into(
live,
history_abs_start,
block_start_abs,
block_end_abs,
&mut out2,
);
assert!(
!out2.is_empty(),
"cross-slide long-range match must survive a window eviction"
);
let live_len = live.len();
for seq in &out2 {
assert!(
seq.match_length >= p.min_match_length as usize,
"every emitted match must reach min_match_length (got {})",
seq.match_length
);
assert!(
seq.offset <= live_len,
"back-ref offset {} must stay within the live \
history (= {} bytes); offsets larger than this \
are the smoking gun for stale slice-relative \
entries surviving the eviction",
seq.offset,
live_len
);
}
let crossed_gap = out2.iter().any(|s| s.offset >= GAP_A);
assert!(
crossed_gap,
"at least one emitted sequence must hit a back-ref of \
>= GAP_A ({GAP_A}) — that's the long-range match \
into the surviving tail of payload #1 the bucket \
entries from call 1 are supposed to produce; \
offsets observed: {:?}",
out2.iter()
.map(|s| s.offset)
.collect::<alloc::vec::Vec<_>>()
);
}
#[test]
fn generate_into_empty_range_is_noop() {
let mut producer = LdmProducer::new(test_params());
let mut out = Vec::new();
let data = [0u8; 128];
let pre = producer.hash_state.rolling;
producer.generate_into(&data, 0, 64, 64, &mut out);
assert!(out.is_empty());
assert_eq!(producer.hash_state.rolling, pre);
}