use super::*;
use alloc::vec;
#[test]
fn gear_tab_anchor_entries_match_known_geartab_header() {
assert_eq!(GEAR_TAB[0], 0xf5b8f72c5f77775c, "GEAR_TAB[0]");
assert_eq!(GEAR_TAB[42], 0x869cb54a8749c161, "GEAR_TAB[42]");
assert_eq!(GEAR_TAB[80], 0x3bed519cbcb4e1e1, "GEAR_TAB[80]");
assert_eq!(GEAR_TAB[255], 0x2b4da14f2613d8f4, "GEAR_TAB[255]");
}
#[test]
fn stop_mask_default_params_use_high_bit_window() {
let state = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
assert_eq!(
state.stop_mask, 0xFE00_0000_0000_0000,
"default stop_mask must put the 7 active bits at the \
top of the 64-bit window (upstream zstd zstd_ldm.c:52-53)"
);
assert_eq!(state.rolling, GEAR_HASH_INIT);
}
#[test]
fn stop_mask_degenerate_path_returns_low_bit_mask() {
let state = GearHashState::new(4, 8); assert_eq!(
state.stop_mask,
(1u64 << 8) - 1,
"fallback mask must equal (1 << hash_rate_log) - 1 \
(upstream zstd zstd_ldm.c:56)"
);
}
#[test]
fn stop_mask_hash_rate_log_zero_disables_filter() {
let state = GearHashState::new(LDM_MIN_MATCH_LENGTH, 0);
assert_eq!(state.stop_mask, 0);
}
#[test]
fn reset_two_byte_stream_matches_hand_traced_recurrence() {
let mut state = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
reset(&mut state, &[0x00, 0x01]);
let expected = (((GEAR_HASH_INIT.wrapping_shl(1)).wrapping_add(GEAR_TAB[0])) << 1)
.wrapping_add(GEAR_TAB[1]);
assert_eq!(
state.rolling, expected,
"rolling recurrence (hash << 1) + GEAR_TAB[byte] regressed"
);
}
#[test]
fn reset_and_feed_produce_identical_rolling_state() {
let data: alloc::vec::Vec<u8> = (0u8..73).collect(); let mut s_reset = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
reset(&mut s_reset, &data);
let mut s_feed = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
s_feed.stop_mask = u64::MAX;
let mut splits = vec![0usize; LDM_BATCH_SIZE];
let (consumed, num) = feed(&mut s_feed, &data, &mut splits);
assert_eq!(consumed, data.len());
assert_eq!(num, 0, "u64::MAX mask must never trigger a split");
assert_eq!(
s_feed.rolling, s_reset.rolling,
"reset and feed must agree on the rolling state after \
identical input — guards the 4-wide unroll"
);
}
#[test]
fn feed_zero_mask_records_split_per_byte_up_to_batch_cap() {
let data: alloc::vec::Vec<u8> = vec![0u8; LDM_BATCH_SIZE * 2];
let mut state = GearHashState::new(LDM_MIN_MATCH_LENGTH, 0);
assert_eq!(state.stop_mask, 0);
let mut splits = vec![0usize; LDM_BATCH_SIZE];
let (consumed, num) = feed(&mut state, &data, &mut splits);
assert_eq!(num, LDM_BATCH_SIZE, "batch cap must be honoured");
assert_eq!(
consumed, LDM_BATCH_SIZE,
"consumed must equal LDM_BATCH_SIZE"
);
for (i, &s) in splits.iter().enumerate() {
assert_eq!(
s,
i + 1,
"upstream zstd records 1-based post-byte indices \
(zstd_ldm.c:111); splits[{i}] should be {}",
i + 1
);
}
}
#[test]
fn feed_concatenation_invariant() {
let part_a: alloc::vec::Vec<u8> = (0u8..30).collect();
let part_b: alloc::vec::Vec<u8> = (30u8..73).collect();
let mut joined = part_a.clone();
joined.extend_from_slice(&part_b);
let mut splits_a = vec![0usize; LDM_BATCH_SIZE];
let mut splits_b = vec![0usize; LDM_BATCH_SIZE];
let mut splits_joined = vec![0usize; LDM_BATCH_SIZE];
let mut s_chunked = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
let _ = feed(&mut s_chunked, &part_a, &mut splits_a);
let _ = feed(&mut s_chunked, &part_b, &mut splits_b);
let mut s_joined = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
let _ = feed(&mut s_joined, &joined, &mut splits_joined);
assert_eq!(
s_chunked.rolling, s_joined.rolling,
"chunked feed must leave the same rolling state as a single feed"
);
}
#[test]
#[should_panic(expected = "LDM_BATCH_SIZE")]
fn feed_panics_on_undersized_splits_buffer() {
let data = [0u8; 8];
let mut state = GearHashState::new(LDM_MIN_MATCH_LENGTH, LDM_HASH_RLOG);
let mut splits = vec![0usize; LDM_BATCH_SIZE - 1];
let _ = feed(&mut state, &data, &mut splits);
}