use std::fs;
use std::path::PathBuf;
use serde::Deserialize;
use super::prompt::PAD;
use super::prompt::qwen3;
use super::prompt::subset_path;
use crate::error::CanddingError;
use crate::error::LengthKind;
use crate::tokenizer::PadToken;
use crate::tokenizer::PromptTemplate;
use crate::tokenizer::PromptTokenizer;
const MINIMUM: usize = 49;
#[derive(Deserialize)]
struct Fixture {
prefix_ids: Vec<u32>,
suffix_ids: Vec<u32>,
rows: Vec<Row>,
cases: Vec<Case>,
}
#[derive(Deserialize)]
struct Row {
instruction: String,
query: String,
document: String,
ids: Vec<u32>,
}
#[derive(Deserialize)]
struct Case {
row: usize,
max_length: usize,
reference: String,
ids: Vec<u32>,
}
fn fixture() -> Fixture {
let path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/prompt_truncation.json");
serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap()
}
#[test]
fn every_row_encodes_to_the_published_files_ids() {
let fixture = fixture();
let tokenizer = qwen3(&subset_path(), 8192);
assert_eq!(tokenizer.prefix_len(), fixture.prefix_ids.len());
assert_eq!(tokenizer.suffix_ids(), fixture.suffix_ids.as_slice());
for (index, row) in fixture.rows.iter().enumerate() {
let ids = tokenizer
.prompt_ids(&row.instruction, &row.query, &row.document)
.unwrap();
assert_eq!(ids, row.ids, "row {index}");
}
}
#[test]
fn every_case_keeps_the_references_ids_token_for_token() {
let fixture = fixture();
for case in &fixture.cases {
let row = &fixture.rows[case.row];
let label = format!(
"row {} max_length {} ({})",
case.row, case.max_length, case.reference
);
if case.max_length < MINIMUM {
let refused = PromptTokenizer::from_file(
&subset_path(),
case.max_length,
PadToken::Id(PAD),
PromptTemplate::Qwen3Reranker,
);
assert!(
matches!(
refused,
Err(CanddingError::LengthTooSmall {
minimum: MINIMUM,
kind: LengthKind::Prompt { .. },
..
})
),
"{label}"
);
continue;
}
let tokenizer = qwen3(&subset_path(), case.max_length);
let ids = tokenizer
.prompt_ids(&row.instruction, &row.query, &row.document)
.unwrap();
assert_eq!(ids, case.ids, "{label}");
let pair = [(row.query.as_str(), row.document.as_str())];
assert_eq!(
tokenizer.count(&row.instruction, &pair).unwrap(),
vec![case.ids.len()],
"{label}"
);
}
}
#[test]
fn the_fixture_covers_the_minimum_a_refusal_and_the_edges_of_the_content() {
let fixture = fixture();
let raw = tokenizers::Tokenizer::from_file(subset_path()).unwrap();
let prefix = PromptTemplate::Qwen3Reranker.prefix();
let head_ids = |row: &Row| -> Vec<u32> {
let head = format!(
"{prefix}<Instruct>: {}\n<Query>: {}\n<Document>:",
row.instruction, row.query
);
let encoding = raw.encode(head.as_str(), false).unwrap();
[encoding.get_ids(), &fixture.suffix_ids].concat()
};
let has = |check: &dyn Fn(&Case) -> bool| fixture.cases.iter().any(check);
assert!(has(&|c| c.max_length == MINIMUM));
assert!(has(&|c| c.max_length < MINIMUM));
assert!(has(&|c| {
let row = &fixture.rows[c.row];
!row.document.is_empty() && c.ids == head_ids(row)
}));
assert!(has(&|c| {
let row = &fixture.rows[c.row];
c.ids.len() == c.max_length && head_ids(row).len() > c.max_length && c.max_length > 64
}));
assert!(has(
&|c| fixture.rows[c.row].instruction.is_empty() && c.ids.len() < fixture.rows[c.row].ids.len()
));
}