use super::*;
use crate::encoding::match_table::storage::MatchTable;
fn table_with_history(buf: &[u8]) -> MatchTable {
let mut t = MatchTable::new(buf.len().max(8));
t.history = buf.to_vec();
t.history_start = 0;
t.history_abs_start = 0;
t.window_size = buf.len();
t.position_base = 0;
t.hash_log = 8;
t.chain_log = 8;
t.hash3_log = 0;
t.ensure_tables();
t.chunk_lens.push_back(buf.len());
t
}
#[test]
fn repcode_candidate_returns_none_when_suffix_too_short() {
let mut t = table_with_history(b"abc");
t.offset_hist = [1, 2, 3];
assert!(!HcMatcher::repcode_candidate(&t, 0, 1).is_match());
}
#[test]
fn repcode_candidate_skips_rep_at_history_boundary() {
let mut t = table_with_history(b"abcdefgh");
t.offset_hist = [5, 6, 7];
let result = HcMatcher::repcode_candidate(&t, 4, 1);
assert!(!result.is_match(), "no rep can land in-range");
}
#[test]
fn find_best_match_returns_none_for_short_suffix() {
let hc = HcMatcher::new(2, 4, 32);
let t = table_with_history(b"abc");
assert!(
!hc.find_best_match::<false>(t.live_history(), false, &t, 0, 1)
.is_match()
);
}
#[test]
fn hash_chain_candidate_picks_longest_forward_over_shorter_with_backward_room() {
let mut t = MatchTable::new(64);
t.history = b"AAAabcdefZMQabcdefIJBAAAabcdefIJKKKKKKKK".to_vec();
t.history_start = 0;
t.history_abs_start = 0;
t.window_size = t.history.len();
t.position_base = 0;
t.hash_log = 8;
t.chain_log = 8;
t.hash3_log = 0;
t.ensure_tables();
t.chunk_lens.push_back(t.history.len());
t.insert_positions(0, 24);
let hc = HcMatcher::new(2, 16, 64);
let c0 = hc.hash_chain_candidate::<false>(t.live_history(), false, &t, 24);
assert!(c0.is_match(), "forward match must be found");
assert_eq!(c0.match_len, 8, "longest forward match is 8 (idx 12)");
assert_eq!(
c0.offset, 12,
"winner is the forward-8 candidate at offset 12"
);
}
#[test]
fn hash_chain_candidate_forward_ties_keep_first_visited() {
let mut t = MatchTable::new(64);
t.history = b"abcdefghAabcdefghBabcdefghCabcdefghDZZZZ".to_vec();
assert_eq!(t.history.len(), 40);
t.history_start = 0;
t.history_abs_start = 0;
t.window_size = t.history.len();
t.position_base = 0;
t.hash_log = 8;
t.chain_log = 8;
t.hash3_log = 0;
t.ensure_tables();
t.chunk_lens.push_back(t.history.len());
let abs_pos = 27usize;
let concat = t.live_history();
let probe_hash = t.hash_position(&concat[abs_pos..]);
t.hash_table[probe_hash] = 9 + 1;
let chain_mask = (1usize << t.chain_log) - 1;
t.chain_table[9 & chain_mask] = 18 + 1;
t.chain_table[18 & chain_mask] = HC_EMPTY;
let hc = HcMatcher::new(2, 16, 64);
let cand = hc.hash_chain_candidate::<false>(t.live_history(), false, &t, abs_pos);
assert!(cand.is_match(), "walk must still produce a match");
assert_eq!(
cand.match_len, 8,
"both candidates have an 8-byte forward prefix"
);
assert_eq!(
cand.offset, 18,
"forward-length ties keep the first-visited candidate (pos 9, \
offset 18); a value of 9 would mean the equal-length later \
candidate displaced it (non-upstream gain-based tie-break)"
);
}