use super::super::ssm_decode_ring::SsmDecodeRing;
use super::{
RollbackFallback, RollbackOutcome, find_last_boundary, find_last_boundary_with_snapshot,
rewind_buffers,
};
fn mask_of(boundary_ids: &[u32], vocab: usize) -> Vec<bool> {
let mut m = vec![false; vocab];
for &b in boundary_ids {
m[b as usize] = true;
}
m
}
#[test]
fn finds_last_boundary_skipping_min_keep() {
let tokens = [10, 99, 11, 12, 99, 13, 14, 15];
let mask = mask_of(&[99], 100);
assert_eq!(find_last_boundary(&tokens, &mask, 2), Some(4));
}
#[test]
fn boundary_inside_min_keep_window_is_ignored() {
let tokens = [10, 11, 12, 13, 14, 15, 99, 16];
let mask = mask_of(&[99], 100);
assert_eq!(find_last_boundary(&tokens, &mask, 3), None);
}
#[test]
fn no_boundary_in_buffer_returns_none() {
let tokens: Vec<u32> = (0..40).collect();
let mask = mask_of(&[200, 201], 256);
assert_eq!(find_last_boundary(&tokens, &mask, 4), None);
}
#[test]
fn buffer_shorter_than_min_keep_returns_none() {
let tokens = [99, 99, 99];
let mask = mask_of(&[99], 100);
assert_eq!(find_last_boundary(&tokens, &mask, 5), None);
}
#[test]
fn picks_latest_of_several_boundaries() {
let tokens = [0, 1, 99, 3, 4, 99, 6, 7, 99];
let mask = mask_of(&[99], 100);
assert_eq!(find_last_boundary(&tokens, &mask, 1), Some(5));
}
#[test]
fn snapshot_aware_search_picks_latest_boundary_with_a_snapshot() {
let tokens = [0, 1, 99, 3, 4, 99, 6, 7, 99, 9];
let mask = mask_of(&[99], 100);
assert_eq!(find_last_boundary(&tokens, &mask, 1), Some(8));
let mut ring = SsmDecodeRing::new(3);
ring.record(3);
ring.record(6);
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 1, &ring),
Some(5),
);
}
#[test]
fn snapshot_aware_search_declines_when_no_boundary_has_a_snapshot() {
let tokens = [0, 1, 99, 3, 4, 99, 6, 7];
let mask = mask_of(&[99], 100);
let mut ring = SsmDecodeRing::new(3);
ring.record(4);
ring.record(7);
assert!(find_last_boundary(&tokens, &mask, 1).is_some());
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 1, &ring),
None,
);
}
#[test]
fn snapshot_aware_search_respects_min_keep_window() {
let tokens = [0, 1, 2, 3, 4, 5, 99, 7, 8, 9];
let mask = mask_of(&[99], 100);
let mut ring = SsmDecodeRing::new(3);
ring.record(7);
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 4, &ring),
None,
);
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 3, &ring),
Some(6),
);
}
#[test]
fn snapshot_aware_search_empty_ring_declines() {
let tokens = [0, 99, 2, 3, 4, 5];
let mask = mask_of(&[99], 100);
let ring = SsmDecodeRing::new(3);
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 1, &ring),
None,
);
}
#[test]
fn snapshot_aware_search_after_eviction_only_sees_live_snapshots() {
let tokens = [0, 99, 2, 3, 99, 5, 6, 99, 8, 9, 99, 11];
let mask = mask_of(&[99], 100);
let mut ring = SsmDecodeRing::new(2);
ring.record(2);
ring.record(5);
ring.record(8);
ring.record(11);
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 1, &ring),
Some(10),
);
ring.truncate_after(8);
assert_eq!(
find_last_boundary_with_snapshot(&tokens, &mask, 1, &ring),
Some(7),
);
}
#[test]
fn rewind_truncates_output_and_seq_and_lowers_seq_len() {
let mut output = vec![50, 51, 52, 53, 54, 55];
let mut seq = vec![1, 2, 3, 50, 51, 52, 53, 54, 55];
let seq_len = 9;
let new_len = rewind_buffers(&mut output, &mut seq, seq_len, 4);
assert_eq!(output, vec![50, 51, 52, 53]);
assert_eq!(seq, vec![1, 2, 3, 50, 51, 52, 53]);
assert_eq!(new_len, 7, "seq_len must drop by the 2 rewound tokens");
}
#[test]
fn rewind_keeping_all_is_a_noop() {
let mut output = vec![50, 51, 52];
let mut seq = vec![1, 50, 51, 52];
let new_len = rewind_buffers(&mut output, &mut seq, 4, 3);
assert_eq!(output, vec![50, 51, 52]);
assert_eq!(seq, vec![1, 50, 51, 52]);
assert_eq!(new_len, 4);
}
#[test]
fn rewind_seq_len_saturates_at_zero() {
let mut output = vec![1, 2, 3, 4, 5];
let mut seq = vec![1, 2, 3, 4, 5];
let new_len = rewind_buffers(&mut output, &mut seq, 2, 1);
assert_eq!(output, vec![1]);
assert_eq!(new_len, 0, "saturating, never underflow");
}
#[test]
fn rollback_cap_is_two() {
assert_eq!(metrale_kernels::ROLLBACK_RESTEER_CAP, 2);
}
#[test]
fn fallback_variants_are_distinct() {
assert_ne!(
RollbackOutcome::Fallback(RollbackFallback::Disabled),
RollbackOutcome::Fallback(RollbackFallback::CapReached),
);
assert_ne!(
RollbackOutcome::Fallback(RollbackFallback::NoBoundary),
RollbackOutcome::Fallback(RollbackFallback::CapReached),
);
assert_ne!(
RollbackOutcome::Fallback(RollbackFallback::LayerStateNotRewindable),
RollbackOutcome::Fallback(RollbackFallback::NoSsmSnapshot),
);
assert_eq!(
RollbackOutcome::RolledBack { dropped: 7 },
RollbackOutcome::RolledBack { dropped: 7 },
);
}
struct StubRomHead;
impl super::RomHead for StubRomHead {
fn repetition_onset_score(&self, recent: &[u32]) -> f32 {
(recent.len() as f32 / 100.0).min(1.0)
}
}
#[test]
fn rom_head_trait_seam_is_callable() {
let head: std::sync::Arc<dyn super::RomHead> = std::sync::Arc::new(StubRomHead);
assert!((head.repetition_onset_score(&[1, 2, 3]) - 0.03).abs() < 1e-6);
assert_eq!(head.repetition_onset_score(&vec![0u32; 500]), 1.0);
}
#[test]
fn rom_head_absent_by_default() {
assert!(
crate::scheduler::sched_ctx::SchedCtx::for_test()
.rom_head
.is_none()
);
}
#[test]
fn the_reported_mismatch_refuses_to_rewind_instead_of_panicking() {
assert_eq!(super::grammar_rewind(96, 1), None);
assert_eq!(super::grammar_rewind(88, 1), None);
}
#[test]
fn an_accountable_span_still_rewinds_exactly() {
assert_eq!(super::grammar_rewind(4, 4), Some(4));
assert_eq!(super::grammar_rewind(4, 9), Some(4));
assert_eq!(super::grammar_rewind(0, 0), Some(0));
}
#[test]
fn the_boundary_is_inclusive_on_the_safe_side() {
assert_eq!(super::grammar_rewind(5, 5), Some(5));
assert_eq!(super::grammar_rewind(6, 5), None);
}
#[test]
fn it_does_not_silently_clamp_to_the_available_history() {
assert_ne!(super::grammar_rewind(96, 1), Some(1));
assert_eq!(super::grammar_rewind(96, 1), None);
}