use alloc::vec;
use alloc::vec::Vec;
use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
enum CapturedSeq {
Triple {
lit_len: usize,
offset: usize,
match_len: usize,
},
Literals {
lit_len: usize,
},
}
fn record_seq<'a>(out: &'a mut Vec<CapturedSeq>) -> impl FnMut(Sequence<'_>) + 'a {
move |seq| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => out.push(CapturedSeq::Triple {
lit_len: literals.len(),
offset,
match_len,
}),
Sequence::Literals { literals } => out.push(CapturedSeq::Literals {
lit_len: literals.len(),
}),
}
}
fn build_dfast_with(data: &[u8]) -> DfastMatchGenerator {
let mut dfast = DfastMatchGenerator::new(data.len().next_power_of_two().max(64));
dfast.ensure_hash_tables();
dfast.add_data(data.to_vec(), |_| {});
dfast
}
#[test]
fn dfast_repcode_extension_emits_zero_literal_rep_on_constant_run() {
let data: Vec<u8> = vec![b'A'; 64];
let mut dfast = build_dfast_with(&data);
dfast.offset_hist = [4, 1, 8];
let current_abs_start = dfast.history_abs_start + dfast.window_size - data.len();
let current_len = data.len();
let pos = 10usize;
let mut literals_start = pos;
let mut seqs = Vec::new();
let new_pos = {
let mut rec = record_seq(&mut seqs);
dfast.extend_with_repcode_after_match(
current_abs_start,
current_len,
pos,
&mut literals_start,
&mut rec,
)
};
assert!(
new_pos > pos,
"helper must advance pos past at least one rep match \
(pos={pos}, new_pos={new_pos})"
);
assert_eq!(
literals_start, new_pos,
"helper must keep literals_start == new_pos so the caller's main \
loop sees zero pending literals after the rep chain"
);
assert!(!seqs.is_empty(), "helper must emit at least one Triple");
for seq in &seqs {
match seq {
CapturedSeq::Triple {
lit_len,
offset,
match_len: _,
} => {
assert_eq!(
*lit_len, 0,
"rep emission must be zero-literal (got {seq:?})"
);
assert_eq!(
*offset, 1,
"rep emission must use the swapped-in offset_hist[1] = 1 \
(got {seq:?})"
);
}
CapturedSeq::Literals { .. } => {
panic!("rep extension must not emit a Literals tail: {seq:?}");
}
}
}
}
#[test]
fn dfast_repcode_extension_walks_into_retained_history() {
let block_a: Vec<u8> = vec![b'C'; 64];
let block_b: Vec<u8> = vec![b'C'; 32];
let mut dfast = DfastMatchGenerator::new(256);
dfast.ensure_hash_tables();
dfast.add_data(block_a, |_| {});
dfast.add_data(block_b.clone(), |_| {});
dfast.offset_hist = [4, 40, 8];
let current_len = block_b.len();
let current_abs_start = dfast.history_abs_start + dfast.window_size - current_len;
let pos = 5usize;
let mut literals_start = pos;
let mut seqs = Vec::new();
let new_pos = {
let mut rec = record_seq(&mut seqs);
dfast.extend_with_repcode_after_match(
current_abs_start,
current_len,
pos,
&mut literals_start,
&mut rec,
)
};
assert!(
new_pos > pos,
"rep with offset > block-local pos must still emit a match when the \
candidate lives in retained history (pos={pos}, new_pos={new_pos})"
);
assert_eq!(seqs.len(), 1, "expected one rep emit, got {seqs:?}");
match &seqs[0] {
CapturedSeq::Triple {
lit_len,
offset,
match_len: _,
} => {
assert_eq!(*lit_len, 0, "rep emit must be zero-literal");
assert_eq!(*offset, 40, "rep emit must use the cross-block offset 40");
}
other => panic!("expected Triple, got {other:?}"),
}
}
#[test]
fn dfast_repcode_extension_accepts_exactly_four_byte_rep() {
let data: Vec<u8> = b"ABCD????ABCD!??????????ABCDX????".to_vec();
assert_eq!(data.len(), 32, "fixture invariant: 32 bytes");
let mut dfast = DfastMatchGenerator::new(64);
dfast.ensure_hash_tables();
dfast.add_data(data.clone(), |_| {});
dfast.offset_hist = [12, 8, 4];
let current_abs_start = dfast.history_abs_start + dfast.window_size - data.len();
let current_len = data.len();
let pos = 8usize;
let mut literals_start = pos;
let mut seqs = Vec::new();
let new_pos = {
let mut rec = record_seq(&mut seqs);
dfast.extend_with_repcode_after_match(
current_abs_start,
current_len,
pos,
&mut literals_start,
&mut rec,
)
};
assert_eq!(seqs.len(), 1, "expected one 4-byte rep emit, got {seqs:?}");
match &seqs[0] {
CapturedSeq::Triple {
lit_len,
offset,
match_len,
} => {
assert_eq!(*lit_len, 0, "rep emit must be zero-literal");
assert_eq!(*offset, 8, "rep emit must use offset 8 (offset_hist[1])");
assert_eq!(
*match_len, 4,
"rep emit must be exactly 4 bytes (upstream zstd MINMATCH floor). \
A regression back to DFAST_MIN_MATCH_LEN > 4 would skip \
this emission entirely and the test would fail with 0 seqs."
);
}
other => panic!("expected Triple, got {other:?}"),
}
assert_eq!(new_pos, pos + 4, "pos must advance by exactly 4");
assert_eq!(literals_start, new_pos, "literals_start must follow pos");
}
#[cfg(feature = "std")]
#[test]
fn dfast_default_level_roundtrip_with_repetitive_breaks_exercises_fast_loop() {
let mut data: Vec<u8> = Vec::with_capacity(64 * 64);
for _ in 0..64 {
data.extend_from_slice(&[b'A'; 60]);
data.push(b'B');
data.extend_from_slice(&[b'A'; 3]);
}
assert!(
data.len() > 4000,
"fixture invariant: long enough for fast loop"
);
let compressed = crate::encoding::compress_to_vec(
data.as_slice(),
crate::encoding::CompressionLevel::Default,
);
let mut decoder = crate::decoding::StreamingDecoder::new(compressed.as_slice())
.expect("default-level frame must decode");
let mut decoded = Vec::with_capacity(data.len());
std::io::Read::read_to_end(&mut decoder, &mut decoded)
.expect("fast-loop output must round-trip cleanly");
assert_eq!(
decoded, data,
"Default-level (use_fast_loop = true) roundtrip must be \
byte-for-byte exact on the repetitive-breaks fixture"
);
assert!(
compressed.len() * 2 < data.len(),
"fast loop must compress repetitive runs to at least 2:1, \
got {} → {} bytes",
data.len(),
compressed.len()
);
}
#[test]
fn dfast_borrowed_window_matches_owned_sequence_stream() {
let mut whole: Vec<u8> = Vec::with_capacity(512);
for i in 0..512usize {
whole.push((i % 64) as u8);
}
let split = 300usize;
let window = 1usize << 16;
let mut owned = DfastMatchGenerator::new(window);
owned.ensure_hash_tables();
let mut owned_seqs: Vec<CapturedSeq> = Vec::new();
owned.add_data(whole[..split].to_vec(), |_| {});
{
let mut rec = record_seq(&mut owned_seqs);
owned.start_matching(&mut rec);
}
owned.add_data(whole[split..].to_vec(), |_| {});
{
let mut rec = record_seq(&mut owned_seqs);
owned.start_matching(&mut rec);
}
let mut borrowed = DfastMatchGenerator::new(window);
borrowed.ensure_hash_tables();
let mut borrowed_seqs: Vec<CapturedSeq> = Vec::new();
unsafe { borrowed.set_borrowed_window(&whole) };
{
let mut rec = record_seq(&mut borrowed_seqs);
borrowed.start_matching_borrowed(0, split, &mut rec);
}
{
let mut rec = record_seq(&mut borrowed_seqs);
borrowed.start_matching_borrowed(split, whole.len(), &mut rec);
}
assert_eq!(
owned_seqs, borrowed_seqs,
"dfast borrowed window must emit the identical sequence stream as the owned path",
);
assert_eq!(
owned.offset_hist, borrowed.offset_hist,
"rep state must match after both scans",
);
}