use super::*;
use crate::scheduler::confidence::{
MAX_SENTENCE_DEFER_TOKENS, THINK_DEFER_ABS_CEILING, THINK_DEFER_BUDGET_FACTOR,
should_inject_think_end,
};
#[test]
fn stage_names_are_distinct_and_stable() {
let names: [&'static str; 9] = [
f2_confidence::F2ConfidenceEarlyStop.name(),
mid_word::MidWordThinkEndMask.name(),
post_close::PostCloseThinkMask.name(),
tool_during_think::ToolCallDuringThinkingMask.name(),
forced_think_end::ForcedThinkEndInjector.name(),
pin_tool_call::PinToToolCallStart.name(),
forced_token::ForcedTokenFastPath.name(),
grammar_bitmask::GrammarBitmaskApply.name(),
min_tokens_eos::EosHoldMask.name(),
];
for i in 0..names.len() {
for j in (i + 1)..names.len() {
assert_ne!(
names[i], names[j],
"stage names must be distinct ({} == {})",
names[i], names[j]
);
}
}
assert_eq!(names[0], "f2_confidence_early_stop");
assert_eq!(names[1], "mid_word_think_end_mask");
assert_eq!(names[2], "post_close_think_mask");
assert_eq!(names[3], "tool_call_during_thinking_mask");
assert_eq!(names[4], "forced_think_end_injector");
assert_eq!(names[5], "pin_to_tool_call_start");
assert_eq!(names[6], "forced_token_fastpath");
assert_eq!(names[7], "grammar_bitmask_apply");
assert_eq!(names[8], "eos_hold_mask");
}
#[test]
fn argmax_invariance_advertisement() {
assert!(f2_confidence::F2ConfidenceEarlyStop.is_argmax_invariant());
assert!(!mid_word::MidWordThinkEndMask.is_argmax_invariant());
assert!(!post_close::PostCloseThinkMask.is_argmax_invariant());
assert!(!tool_during_think::ToolCallDuringThinkingMask.is_argmax_invariant());
assert!(!forced_think_end::ForcedThinkEndInjector.is_argmax_invariant());
assert!(!pin_tool_call::PinToToolCallStart.is_argmax_invariant());
assert!(!forced_token::ForcedTokenFastPath.is_argmax_invariant());
assert!(!grammar_bitmask::GrammarBitmaskApply.is_argmax_invariant());
}
#[test]
fn forced_think_end_gate_semantics() {
assert!(!should_inject_think_end(false, false, false, false));
assert!(!should_inject_think_end(false, true, true, true));
assert!(should_inject_think_end(true, true, false, true));
assert!(should_inject_think_end(true, false, false, true));
assert!(!should_inject_think_end(true, true, false, false));
assert!(!should_inject_think_end(true, true, true, false));
assert!(should_inject_think_end(true, false, true, false));
assert!(!should_inject_think_end(true, false, false, false));
}
#[test]
fn defer_override_math_constants() {
let budget: u32 = 100;
let thinking_tokens: u32 = budget.saturating_mul(THINK_DEFER_BUDGET_FACTOR);
assert!(thinking_tokens >= budget.saturating_mul(THINK_DEFER_BUDGET_FACTOR));
let unlimited_tokens: u32 = THINK_DEFER_ABS_CEILING;
assert!(unlimited_tokens >= THINK_DEFER_ABS_CEILING);
let defer_count: u32 = MAX_SENTENCE_DEFER_TOKENS;
assert!(defer_count >= MAX_SENTENCE_DEFER_TOKENS);
}
#[test]
fn logits_context_field_set_is_stable() {
let scratch = crate::scheduler::sched_ctx::DecodeScratch::default();
let io = crate::scheduler::io::SchedIo::for_test();
let ctx = LogitsContext {
scratch: &scratch,
tel: &*io.tel,
clock: &*io.clock,
watchdog: crate::scheduler::helpers::WatchdogParams::default(),
boundary_mask: None,
mid_word_mask: None,
sampling: SamplingLevers::default(),
think_end_token: Some(1),
think_start_token: Some(2),
tool_call_start_token: Some(3),
tool_call_end_token: Some(4),
thinking_closed_by: Some(3),
verify_pos: 0,
};
let ctx2 = ctx.clone();
assert_eq!(ctx2.think_end_token, Some(1));
assert_eq!(ctx2.think_start_token, Some(2));
assert_eq!(ctx2.tool_call_start_token, Some(3));
assert_eq!(ctx2.tool_call_end_token, Some(4));
assert_eq!(ctx2.thinking_closed_by, Some(3));
assert_eq!(ctx.tool_call_end_token, Some(4));
}
#[test]
fn run_pipeline_signature_is_stable() {
type RunPipelineFn = fn(&mut [f32], &mut ActiveSeq, &LogitsContext) -> Option<u32>;
let _ptr: RunPipelineFn = run_pipeline;
type ProcessPositionFn = fn(
&mut [f32],
&mut ActiveSeq,
&LogitsContext,
&metrale_sampling::SamplingParams,
crate::scheduler::sample_step::PositionKind,
) -> Option<u32>;
let _pp: ProcessPositionFn = process_position_logits;
type RunPipelineWithPathFn =
fn(&mut [f32], &mut ActiveSeq, &LogitsContext, &'static str) -> Option<u32>;
let _rpp: RunPipelineWithPathFn = run_pipeline_with_path;
}
#[test]
fn non_mtp_path_has_no_inline_pipeline_fork() {
const SRC: &str = include_str!("../decode_logits_seq.rs");
assert!(
!SRC.contains("f32::NEG_INFINITY"),
"decode_logits_seq.rs must not hard-mask inline; masking belongs in logit_processors stages"
);
assert!(
!SRC.contains("repetition_penalty: a.repetition_penalty"),
"decode_logits_seq.rs must not inline the SamplingParams penalty literal; use penalty_params_for"
);
assert!(
!SRC.contains("MIN_REASONING_TOKENS"),
"A4 floor must live only in penalty_params_for, not inline in decode_logits_seq.rs"
);
assert!(
!SRC.contains("b1_record_low_margin") && !SRC.contains("low_margin_in_body"),
"B1 margin detector must live only in logit_processors::b1_margin, not inline"
);
assert!(
!SRC.contains("if false && "),
"the dead C4v1 `if false && low_margin_in_body` block must be deleted"
);
for marker in [
"Mid-word `</think>` defer",
"one-shot pin-to-tool-call-start",
"Forced-token fast-path (xgrammar Tier 3b",
"Apply grammar bitmask BEFORE sampling",
] {
assert!(
!SRC.contains(marker),
"stale inline per-stage block `{marker}` still present in decode_logits_seq.rs"
);
}
assert!(
SRC.contains("process_position_logits"),
"decode_logits_seq.rs must call the unified process_position_logits"
);
}
#[test]
fn unified_fn_includes_a4_and_b1_stages() {
const SAMPLE_STEP_SRC: &str = include_str!("../sample_step/penalties.rs");
assert!(
SAMPLE_STEP_SRC.contains("min_reasoning_floor: u32")
&& SAMPLE_STEP_SRC.contains("a.think_end_token"),
"A4 POST_THINK_MIN_REASONING floor must live in penalty_params_for"
);
const B1_SRC: &str = include_str!("b1_margin.rs");
assert!(
B1_SRC.contains("fn observe") && B1_SRC.contains("LOW_MARGIN_THRESHOLD"),
"B1 margin observer must live in logit_processors::b1_margin"
);
const MOD_SRC: &str = include_str!("mod.rs");
assert!(
MOD_SRC.contains("b1_margin::observe") && MOD_SRC.contains("PositionKind::FinalDecode"),
"process_position_logits must call B1 observe gated on FinalDecode"
);
assert!(
MOD_SRC.contains("ctx.sampling.force_temp_zero")
&& MOD_SRC.contains("apply_penalties_and_bias"),
"process_position_logits must own the force-temp-zero bypass and penalties+bias"
);
const VERIFY_SRC: &str = include_str!("../verify_pipeline_helper.rs");
assert!(
VERIFY_SRC.contains("process_position_logits"),
"verify_pick_with_pipeline must call the unified process_position_logits"
);
}
#[test]
fn unified_fn_does_not_advance_matcher() {
const MOD_SRC: &str = include_str!("mod.rs");
let start = MOD_SRC
.find("pub fn process_position_logits")
.expect("process_position_logits must exist in mod.rs");
let body = &MOD_SRC[start..];
assert!(
!body.contains(".accept_token(") && !body.contains(".rollback("),
"process_position_logits must not call gs.accept_token / gs.rollback (R1: caller-owned)"
);
}