use super::*;
#[cfg(test)]
use crate::encoding::CompressionLevel;
#[cfg(test)]
use crate::encoding::Matcher;
#[cfg(test)]
use crate::encoding::dfast::DfastMatchGenerator;
#[cfg(test)]
use crate::encoding::match_generator::MatchGeneratorDriver;
use alloc::vec::Vec;
#[test]
fn dfast_matches_roundtrip_multi_block_pattern() {
let pattern = [9, 21, 44, 184, 19, 96, 171, 109, 141, 251];
let first_block: Vec<u8> = pattern.iter().copied().cycle().take(128 * 1024).collect();
let second_block: Vec<u8> = pattern.iter().copied().cycle().take(128 * 1024).collect();
let mut matcher = DfastMatchGenerator::new(1 << 22);
let replay_sequence = |decoded: &mut Vec<u8>, seq: Sequence<'_>| match seq {
Sequence::Literals { literals } => decoded.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
decoded.extend_from_slice(literals);
let start = decoded.len() - offset;
for i in 0..match_len {
let byte = decoded[start + i];
decoded.push(byte);
}
}
};
matcher.commit_input(&first_block);
let mut history = Vec::new();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(history, first_block);
matcher.commit_input(&second_block);
let prefix_len = history.len();
matcher.start_matching(|seq| replay_sequence(&mut history, seq));
assert_eq!(&history[prefix_len..], second_block.as_slice());
}
#[test]
fn dfast_accepts_exact_five_byte_match() {
let mut data = Vec::new();
data.push(b'Z'); data.extend_from_slice(b"ABCDE"); data.extend_from_slice(b"!!!!!!!!!!!!!!!!!!!!!!!"); data.extend_from_slice(b"ABCDE"); data.push(b'F'); data.extend_from_slice(b"GHIJKLMNOPQRSTUVWXYZ"); assert_eq!(data.len(), 55);
let mut matcher = DfastMatchGenerator::new(1 << 22);
matcher.commit_input(&data);
let mut saw_five_byte_match = false;
let mut saw_longer_match = false;
matcher.start_matching(|seq| {
if let Sequence::Triple {
offset, match_len, ..
} = seq
{
if offset == 28 && match_len == 5 {
saw_five_byte_match = true;
} else if offset == 28 && match_len > 5 {
saw_longer_match = true;
}
}
});
assert!(
saw_five_byte_match,
"dfast must accept the exact-5-byte match — a 6-byte floor would skip it"
);
assert!(
!saw_longer_match,
"fixture pinned to length 5 — byte 33 ('F') must terminate the extension"
);
}
#[test]
fn driver_switches_backends_and_initializes_dfast_via_reset() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Default);
assert_eq!(
driver.active_backend(),
crate::encoding::strategy::BackendTag::Dfast
);
assert_eq!(driver.window_size(), (1u64 << 21));
driver.commit_input(b"abcabcabcabc");
assert_eq!(driver.get_last_space(), b"abcabcabcabc");
driver.skip_matching_with_hint(None);
driver.commit_input(b"abcabcabcabc");
let mut reconstructed = b"abcabcabcabc".to_vec();
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
reconstructed.extend_from_slice(literals);
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
});
assert_eq!(reconstructed, b"abcabcabcabcabcabcabcabc");
driver.reset(CompressionLevel::Fastest);
assert_eq!(driver.window_size(), (1u64 << 19));
}
#[test]
fn driver_level5_selects_row_backend() {
let mut driver = MatchGeneratorDriver::new(32, 2);
driver.reset(CompressionLevel::Level(5));
assert_eq!(
driver.active_backend(),
crate::encoding::strategy::BackendTag::Row
);
assert_eq!(
driver.parse,
crate::encoding::strategy::ParseMode::Greedy,
"L5 must route to start_matching_greedy (parse == Greedy)",
);
assert_eq!(
driver.row_matcher().lazy_depth,
0,
"row matcher lazy_depth must mirror the greedy parse mode",
);
}
#[test]
fn driver_level4_greedy_round_trip_single_slice() {
let mut driver = MatchGeneratorDriver::new(64, 2);
driver.reset(CompressionLevel::Level(4));
let input = b"abcdefgh_abcdefgh_abcdefgh_abcdefgh";
driver.commit_input(input);
let mut reconstructed: Vec<u8> = Vec::new();
let mut saw_triple = false;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => reconstructed.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
saw_triple = true;
reconstructed.extend_from_slice(literals);
let start = reconstructed.len() - offset;
for i in 0..match_len {
let byte = reconstructed[start + i];
reconstructed.push(byte);
}
}
});
assert_eq!(
reconstructed,
input.to_vec(),
"L4 greedy parse failed to reconstruct repeating-pattern input",
);
assert!(
saw_triple,
"L4 greedy parse on a repeating pattern must emit at least one match (Triple)",
);
}
#[test]
fn driver_level4_greedy_round_trip_cross_slice() {
let mut driver = MatchGeneratorDriver::new(32, 4);
driver.reset(CompressionLevel::Level(4));
let chunk = b"the quick brown fox jumps over!!";
assert_eq!(chunk.len(), 32);
driver.commit_input(chunk);
let mut first_recon: Vec<u8> = Vec::new();
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => first_recon.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
first_recon.extend_from_slice(literals);
let start = first_recon.len() - offset;
for i in 0..match_len {
let byte = first_recon[start + i];
first_recon.push(byte);
}
}
});
assert_eq!(
first_recon,
chunk.to_vec(),
"first slice failed to round-trip"
);
driver.commit_input(chunk);
let mut full = first_recon.clone();
let mut saw_cross_slice_match = false;
driver.start_matching(|seq| match seq {
Sequence::Literals { literals } => full.extend_from_slice(literals),
Sequence::Triple {
literals,
offset,
match_len,
} => {
if offset >= chunk.len() {
saw_cross_slice_match = true;
}
full.extend_from_slice(literals);
let start = full.len() - offset;
for i in 0..match_len {
let byte = full[start + i];
full.push(byte);
}
}
});
let mut expected = chunk.to_vec();
expected.extend_from_slice(chunk);
assert_eq!(
full, expected,
"cross-slice L4 greedy parse failed to reconstruct"
);
assert!(
saw_cross_slice_match,
"L4 greedy parse must match across slice boundaries (history is shared)",
);
}