use keyhog_scanner::testing::{
attribute_matches_to_chunks, fold_overlapping_same_pid_inplace, gpu_phase2_hits_are_dense,
};
use vyre_libs::scan::LiteralMatch;
#[test]
fn fold_collapses_same_pid_overlap() {
let mut matches = vec![
LiteralMatch::new(1, 10, 20),
LiteralMatch::new(1, 15, 25),
LiteralMatch::new(2, 30, 35),
];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].pattern_id, 1);
assert_eq!(matches[0].start, 10);
assert_eq!(matches[0].end, 25);
assert_eq!(matches[1].pattern_id, 2);
}
#[test]
fn fold_keeps_distinct_pids_with_same_range() {
let mut matches = vec![LiteralMatch::new(1, 10, 20), LiteralMatch::new(2, 10, 20)];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 2);
}
#[test]
fn fold_empty_input() {
let mut matches: Vec<LiteralMatch> = vec![];
fold_overlapping_same_pid_inplace(&mut matches);
assert!(matches.is_empty());
}
#[test]
fn fold_single_match() {
let mut matches = vec![LiteralMatch::new(1, 10, 20)];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].start, 10);
assert_eq!(matches[0].end, 20);
}
#[test]
fn fold_exact_duplicates() {
let mut matches = vec![
LiteralMatch::new(1, 10, 20),
LiteralMatch::new(1, 10, 20),
LiteralMatch::new(1, 10, 20),
];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].start, 10);
assert_eq!(matches[0].end, 20);
}
#[test]
fn fold_touching_spans_same_pid_merge() {
let mut matches = vec![LiteralMatch::new(1, 10, 20), LiteralMatch::new(1, 20, 30)];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].start, 10);
assert_eq!(matches[0].end, 30);
}
#[test]
fn fold_non_touching_spans_same_pid_stay_separate() {
let mut matches = vec![LiteralMatch::new(1, 10, 20), LiteralMatch::new(1, 21, 30)];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 2);
}
#[test]
fn fold_three_way_chain_merge() {
let mut matches = vec![
LiteralMatch::new(1, 10, 20),
LiteralMatch::new(1, 15, 25),
LiteralMatch::new(1, 22, 35),
];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].start, 10);
assert_eq!(matches[0].end, 35);
}
#[test]
fn fold_unsorted_input_still_works() {
let mut matches = vec![
LiteralMatch::new(1, 30, 40),
LiteralMatch::new(1, 10, 20),
LiteralMatch::new(1, 15, 25),
];
fold_overlapping_same_pid_inplace(&mut matches);
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].start, 10);
assert_eq!(matches[0].end, 25);
assert_eq!(matches[1].start, 30);
assert_eq!(matches[1].end, 40);
}
#[test]
fn fold_output_sorted_by_start() {
let mut matches = vec![
LiteralMatch::new(2, 50, 60),
LiteralMatch::new(1, 10, 20),
LiteralMatch::new(2, 5, 15),
];
fold_overlapping_same_pid_inplace(&mut matches);
for w in matches.windows(2) {
assert!(w[0].start <= w[1].start, "output not sorted by start");
}
}
#[test]
fn attribute_routes_to_correct_chunk() {
let entries = vec![(0, 0, 100), (1, 108, 50), (2, 166, 80)];
let matches = vec![
LiteralMatch::new(7, 50, 60),
LiteralMatch::new(7, 120, 130),
LiteralMatch::new(7, 200, 210),
];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 3);
assert_eq!(per_chunk[0], vec![(7, 50, 60)]);
assert_eq!(per_chunk[1], vec![(7, 12, 22)]);
assert_eq!(per_chunk[2], vec![(7, 34, 44)]);
}
#[test]
fn attribute_drops_cross_boundary_match() {
let entries = vec![(0, 0, 100), (1, 108, 50)];
let matches = vec![LiteralMatch::new(7, 95, 110)];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 2);
assert!(per_chunk[0].is_empty());
assert!(per_chunk[1].is_empty());
}
#[test]
fn attribute_drops_unknown_pid() {
let entries = vec![(0, 0, 100)];
let matches = vec![LiteralMatch::new(999, 10, 20)];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 1);
assert!(per_chunk[0].is_empty());
}
#[test]
fn attribute_empty_matches_returns_empty_chunks() {
let entries = vec![(0, 0, 100), (1, 108, 50)];
let matches: Vec<LiteralMatch> = vec![];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 2);
assert!(per_chunk[0].is_empty());
assert!(per_chunk[1].is_empty());
}
#[test]
fn attribute_empty_entries_returns_empty_chunks() {
let entries: Vec<(usize, usize, usize)> = vec![];
let matches = vec![LiteralMatch::new(1, 10, 20)];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 0);
assert!(per_chunk.is_empty());
}
#[test]
fn attribute_match_at_chunk_boundary() {
let entries = vec![(0, 0, 100), (1, 108, 50)];
let matches = vec![LiteralMatch::new(1, 108, 118)];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 2);
assert_eq!(per_chunk[1], vec![(1, 0, 10)]);
}
#[test]
fn attribute_match_at_chunk_end() {
let entries = vec![(0, 0, 100)];
let matches = vec![LiteralMatch::new(1, 90, 100)];
let per_chunk = attribute_matches_to_chunks(&matches, &entries, 10, 1);
assert_eq!(per_chunk[0], vec![(1, 90, 100)]);
}
#[test]
fn dense_phase2_guard_trips_on_creddata_shape() {
assert!(
gpu_phase2_hits_are_dense(1_306_914, 56_467_764, 4_096),
"CredData forced-GPU batch produced one literal hit per ~43 bytes and must reroute"
);
}
#[test]
fn dense_phase2_guard_ignores_small_or_sparse_batches() {
assert!(!gpu_phase2_hits_are_dense(99_999, 1_000_000, 4_096));
assert!(!gpu_phase2_hits_are_dense(100_000, 100_000_000, 4_096));
assert!(!gpu_phase2_hits_are_dense(1_000_000, 16_000_000, 1));
}