use alloc::vec;
use super::*;
fn new_table(window: usize) -> MatchTable {
let mut t = MatchTable::new(window);
t.hash_log = 8;
t.chain_log = 8;
t.hash3_log = 0;
t
}
#[test]
fn set_dictionary_limit_from_primed_bytes_zero_clears_limit() {
let mut t = new_table(64);
t.history_abs_start = 100;
t.dictionary_limit_abs = Some(123);
t.set_dictionary_limit_from_primed_bytes(0);
assert_eq!(t.dictionary_limit_abs, None);
}
#[test]
fn set_dictionary_limit_from_primed_bytes_offsets_from_history_start() {
let mut t = new_table(64);
t.history_abs_start = 100;
t.set_dictionary_limit_from_primed_bytes(40);
assert_eq!(t.dictionary_limit_abs, Some(140));
}
#[test]
fn dms_cache_rebuilds_across_hc_bt_layout_switch() {
let mut t = new_table(64);
t.hash_log = 12;
t.search_mls = 4;
t.push_test_chunk(vec![7u8; 48]);
t.ensure_tables();
let region = 48;
t.prime_dms_hc(region);
assert_eq!(t.dms.table().unwrap().layout, DmsDictLayout::Hc);
assert_eq!(t.dms.table().unwrap().chain_table.len(), region);
t.prime_dms_bt(region);
assert_eq!(t.dms.table().unwrap().layout, DmsDictLayout::Bt);
assert_eq!(t.dms.table().unwrap().chain_table.len(), 2 * region);
t.prime_dms_hc(region);
assert_eq!(t.dms.table().unwrap().layout, DmsDictLayout::Hc);
assert_eq!(t.dms.table().unwrap().chain_table.len(), region);
}
#[test]
fn skip_matching_bt_incompressible_routes_through_sparse_block() {
let mut t = new_table(32);
t.push_test_chunk(vec![0u8; 32]);
t.ensure_tables();
t.uses_bt = true;
t.is_btultra2 = false;
t.search_depth = 4;
let before_skip_until = t.skip_insert_until_abs;
t.skip_matching(Some(true));
assert!(t.skip_insert_until_abs >= t.window_size);
assert!(t.skip_insert_until_abs > before_skip_until);
}
#[test]
fn skip_matching_bt_dense_routes_through_bt_update_tree() {
let mut t = new_table(32);
t.push_test_chunk(vec![1u8; 32]);
t.ensure_tables();
t.uses_bt = true;
t.is_btultra2 = false;
t.search_depth = 4;
t.skip_matching(None);
assert_eq!(t.skip_insert_until_abs, t.history_abs_start + t.window_size);
}
#[test]
fn replay_history_for_rebase_bt_walks_inserted_prefix() {
let mut t = new_table(64);
t.history = vec![0u8; 64];
for (i, slot) in t.history.iter_mut().enumerate() {
*slot = (i % 17) as u8;
}
t.history_start = 0;
t.history_abs_start = 0;
t.window_size = 64;
t.position_base = 0;
t.search_depth = 4;
t.uses_bt = true;
t.ensure_tables();
assert!(t.hash_table().iter().all(|&v| v == HC_EMPTY));
t.replay_history_for_rebase_bt(0, 32);
assert!(
t.hash_table().iter().any(|&v| v != HC_EMPTY),
"BT replay must populate hash table"
);
}
#[test]
fn begin_rebase_clears_index_tables_and_resets_base() {
let mut t = new_table(32);
t.tables = vec![7; 48];
t.chain_off = 16;
t.hash3_off = 32;
t.chain_table_mut().fill(9);
t.hash3_table_mut().fill(5);
t.history_abs_start = 50;
t.position_base = 0;
t.index_shift = 4;
t.begin_rebase();
assert_eq!(t.position_base, 50);
assert_eq!(t.index_shift, 0);
assert!(t.hash_table().iter().all(|&v| v == HC_EMPTY));
assert!(t.chain_table().iter().all(|&v| v == HC_EMPTY));
assert!(t.hash3_table().iter().all(|&v| v == HC_EMPTY));
}
#[test]
fn the_hoisted_hash3_fill_matches_the_per_position_loop() {
for (floor, index_shift) in [(0, 0), (12, 70_000)] {
let build = |hoisted: bool| {
let mut t = new_table(64);
t.history = b"abcdef_abcdef_abcdef_abcdef_abcdef_abcdef".to_vec();
t.history_start = 0;
t.history_abs_start = floor;
t.position_base = floor;
t.index_shift = index_shift;
t.window_size = t.history.len();
t.chunk_lens.push_back(t.history.len());
t.hash3_log = 6;
t.is_btultra2 = true;
t.ensure_tables();
t.next_to_update3 = floor;
if hoisted {
assert!(
t.fill_hash3_hoisted(floor + 30),
"the fast path must apply here"
);
} else {
for target in floor + 1..=floor + 30 {
t.next_to_update3 = target - 1;
let mut cursor = t.next_to_update3;
while cursor < target {
t.insert_hash3_only_no_rebase(cursor);
cursor += 1;
}
t.next_to_update3 = target;
}
}
(t.hash3_table().to_vec(), t.next_to_update3)
};
let (hoisted_table, hoisted_cursor) = build(true);
let (loop_table, loop_cursor) = build(false);
assert_eq!(hoisted_cursor, loop_cursor, "the cursor must land alike");
assert_eq!(
hoisted_table, loop_table,
"floor {floor}, shift {index_shift}: the hoisted fill wrote a different hash3 table",
);
assert!(
hoisted_table.iter().any(|&v| v != HC_EMPTY),
"fixture precondition: the fill must populate something",
);
}
}
#[test]
fn a_hash3_catch_up_past_the_index_range_rebases_first() {
let mut t = new_table(64);
t.history = b"abcdef_abcdef_abcdef_abcdef_abcdef_abcdef".to_vec();
t.history_start = 0;
t.history_abs_start = 0;
t.window_size = t.history.len();
t.chunk_lens.push_back(t.history.len());
t.hash3_log = 6;
t.is_btultra2 = true;
t.search_depth = 4;
t.ensure_tables();
t.index_shift = u32::MAX as usize - 10;
assert!(
!t.can_skip_rebase_check(20),
"fixture precondition: the hoisted fill cannot take this span"
);
t.update_hash3_until(20);
assert_eq!(t.index_shift, 0, "the positions were re-encoded");
assert_eq!(t.next_to_update3, 20);
assert!(t.hash3_table().iter().any(|&v| v != HC_EMPTY));
}
#[test]
fn rebase_positions_cold_rebuilds_hash3_for_btultra2() {
let mut t = new_table(64);
t.history = b"abcdef_abcdef_abcdef_abcdef_abcdef_abcdef".to_vec();
t.history_start = 0;
t.history_abs_start = 0;
t.window_size = t.history.len();
t.chunk_lens.push_back(t.history.len());
t.hash_log = 8;
t.chain_log = 8;
t.hash3_log = 6;
t.is_btultra2 = true;
t.search_depth = 4;
t.ensure_tables();
t.update_hash3_until(20);
assert!(
t.hash3_table().iter().any(|&v| v != HC_EMPTY),
"fixture precondition: hash3 must be non-empty before rebase"
);
t.rebase_positions_cold(20);
assert!(
t.hash3_table().iter().any(|&v| v != HC_EMPTY),
"rebase must repopulate the HC3 side table — \
btultra2 short-match selection depends on it"
);
}
#[test]
fn insert_positions_with_step_zero_step_is_noop() {
let mut t = new_table(32);
t.history = vec![0u8; 32];
t.push_test_chunk(vec![0u8; 32]);
t.ensure_tables();
let next_to_update3_before = t.next_to_update3;
t.insert_positions_with_step(0, 16, 0);
assert!(t.hash_table().iter().all(|&v| v == HC_EMPTY));
assert_eq!(t.next_to_update3, next_to_update3_before);
}
#[test]
fn insert_positions_with_step_saturating_step_breaks_loop() {
let mut t = new_table(32);
t.history = vec![1u8; 32];
t.push_test_chunk(vec![1u8; 32]);
t.ensure_tables();
t.insert_positions_with_step(0, 16, usize::MAX);
let non_empty = t.hash_table().iter().filter(|&&v| v != HC_EMPTY).count();
assert!(
non_empty <= 1,
"step=usize::MAX must break after the first insert"
);
}
#[test]
fn apply_limited_update_after_long_match_hc_mode_is_noop() {
let mut t = new_table(32);
t.uses_bt = false;
t.skip_insert_until_abs = 100;
t.apply_limited_update_after_long_match(1000);
assert_eq!(
t.skip_insert_until_abs, 100,
"HC mode must not adjust skip cursor"
);
}
#[test]
fn apply_limited_update_after_long_match_bt_mode_caps_gap_at_384() {
let mut t = new_table(32);
t.uses_bt = true;
t.skip_insert_until_abs = 0;
t.apply_limited_update_after_long_match(1000);
assert_eq!(t.skip_insert_until_abs, 808);
}
#[test]
fn apply_limited_update_after_long_match_small_gap_is_noop() {
let mut t = new_table(32);
t.uses_bt = true;
t.skip_insert_until_abs = 800;
t.apply_limited_update_after_long_match(1000);
assert_eq!(t.skip_insert_until_abs, 800);
}
#[test]
fn emit_optimal_plan_empty_plan_emits_full_literals() {
let mut t = new_table(8);
t.push_test_chunk(b"abcdefgh".to_vec());
let mut emitted: Vec<u8> = Vec::new();
t.emit_optimal_plan(8, &[], &mut |seq| {
if let Sequence::Literals { literals } = seq {
emitted.extend_from_slice(literals);
}
});
assert_eq!(emitted, b"abcdefgh");
}
#[test]
fn emit_optimal_plan_skips_oversized_plan_item_and_emits_trailing_literals() {
let mut t = new_table(8);
t.push_test_chunk(b"abcdefgh".to_vec());
let plan = [HcOptimalSequence {
offset: 1,
lit_len: 4,
match_len: 99, }];
let mut triples = 0usize;
let mut trailing: Vec<u8> = Vec::new();
t.emit_optimal_plan(8, &plan, &mut |seq| match seq {
Sequence::Triple { .. } => triples += 1,
Sequence::Literals { literals } => trailing.extend_from_slice(literals),
});
assert_eq!(triples, 0, "oversized plan item must be skipped");
assert_eq!(
trailing, b"abcdefgh",
"trailing-literals path must emit the full window when plan skipped everything"
);
}
#[test]
fn reset_clears_uncommitted_bytes_left_by_an_abandoned_fill() {
let mut t = new_table(64);
t.fill_uncommitted(8, |buf| {
buf.extend_from_slice(b"abcdefgh");
(8, true)
});
assert_eq!(t.uncommitted().len(), 8, "fill must stage the bytes");
t.reset(|_| {});
assert!(
t.uncommitted().is_empty(),
"reset must drop bytes no block claimed"
);
assert!(t.live_history().is_empty(), "reset must clear the window");
}
#[test]
fn reserve_for_frame_takes_the_request_as_given() {
let mut t = new_table(1 << 20);
t.reserve_for_frame(1024);
assert!(t.history.capacity() >= 1024, "the request must be honoured");
assert!(
t.history.capacity() < 64 * 1024,
"reservation must not add a format-maximum block on top: got {}",
t.history.capacity()
);
}
#[test]
fn ensure_tables_releases_the_buffer_when_the_layout_shrinks() {
let mut t = new_table(1 << 20);
t.hash_log = 20;
t.chain_log = 20;
t.hash3_log = 0;
t.ensure_tables();
let large = t.tables.capacity();
assert!(large >= 2 << 20, "fixture precondition: a large layout");
t.hash_log = 10;
t.chain_log = 10;
t.ensure_tables();
assert!(
t.tables.capacity() < large / 2,
"a smaller layout must release the oversized buffer, kept {} of {large}",
t.tables.capacity()
);
}
#[test]
fn clone_from_replaces_the_uncommitted_count_with_the_sources() {
let mut source = new_table(64);
source.fill_uncommitted(4, |buf| {
buf.extend_from_slice(b"wxyz");
(4, true)
});
let mut dest = new_table(64);
dest.fill_uncommitted(8, |buf| {
buf.extend_from_slice(b"abcdefgh");
(8, true)
});
dest.clone_from(&source);
assert_eq!(
dest.uncommitted(),
b"wxyz",
"clone_from must adopt the source's uncommitted count"
);
}