mod common;
use std::sync::Arc;
use std::thread;
use transcribe_cpp::{Error, Feature, Itn, Model, Pnc, RunOptions, TimestampKind};
#[test]
fn transcribes_jfk_with_segments() {
let Some((model_path, pcm)) = common::smoke_fixtures("transcribes_jfk_with_segments") else {
return;
};
let model = Model::load(&model_path).unwrap();
assert!(!model.arch().is_empty(), "empty arch string");
let mut session = model.session().unwrap();
let result = session.run(&pcm, &RunOptions::default()).unwrap();
let text = result.text.trim().to_lowercase();
assert!(!text.is_empty(), "empty transcription");
assert!(
text.contains("country"),
"unexpected transcription: {:?}",
result.text
);
assert!(!result.segments.is_empty(), "no segments materialized");
}
#[test]
fn one_model_many_sessions() {
let Some((model_path, pcm)) = common::smoke_fixtures("one_model_many_sessions") else {
return;
};
let model = Model::load(&model_path).unwrap();
for _ in 0..2 {
let mut s = model.session().unwrap();
let r = s.run(&pcm, &RunOptions::default()).unwrap();
assert!(r.text.to_lowercase().contains("country"));
}
}
#[test]
fn batch_run_two_utterances() {
let Some((model_path, pcm)) = common::smoke_fixtures("batch_run_two_utterances") else {
return;
};
let mut session = Model::load(&model_path).unwrap().session().unwrap();
let inputs: Vec<&[f32]> = vec![&pcm, &pcm];
let results = session.run_batch(&inputs, &RunOptions::default()).unwrap();
assert_eq!(results.len(), 2);
for r in &results {
let t = r.as_ref().expect("utterance ok");
assert!(t.text.to_lowercase().contains("country"), "{:?}", t.text);
assert!(!t.segments.is_empty());
}
}
#[test]
fn capabilities_and_identity() {
let Some((model_path, _)) = common::smoke_fixtures("capabilities_and_identity") else {
return;
};
let model = Model::load(&model_path).unwrap();
assert_eq!(model.arch(), "whisper", "arch: {}", model.arch());
assert!(model.backend().to_lowercase().contains("cpu") || !model.backend().is_empty());
let caps = model.capabilities().unwrap();
assert!(caps.native_sample_rate > 0, "{caps:?}");
}
#[test]
fn pnc_changes_canary_prompt() {
let (Some(model_path), Some(pcm)) = (common::smoke_pnc_model(), common::smoke_audio()) else {
eprintln!("skip pnc_changes_canary_prompt: PNC model/audio unavailable");
return;
};
let model = Model::load(model_path).unwrap();
assert!(model.supports(Feature::Pnc));
let mut session = model.session().unwrap();
let run = |session: &mut transcribe_cpp::Session, pnc| {
session
.run(
&pcm,
&RunOptions {
language: Some("en".into()),
pnc,
..Default::default()
},
)
.unwrap()
.text
};
let default = run(&mut session, Pnc::Default);
let enabled = run(&mut session, Pnc::On);
let disabled = run(&mut session, Pnc::Off);
assert_eq!(default, enabled);
assert_ne!(disabled, enabled);
assert_eq!(disabled, disabled.to_lowercase());
}
#[test]
fn itn_changes_sensevoice_text_normalization() {
let (Some(model_path), Some(pcm)) = (common::smoke_itn_model(), common::smoke_audio()) else {
eprintln!("skip itn_changes_sensevoice_text_normalization: ITN model/audio unavailable");
return;
};
let model = Model::load(model_path).unwrap();
assert!(model.supports(Feature::Itn));
let mut session = model.session().unwrap();
let run = |session: &mut transcribe_cpp::Session, itn| {
session
.run(
&pcm,
&RunOptions {
language: Some("en".into()),
itn,
..Default::default()
},
)
.unwrap()
};
let default = run(&mut session, Itn::Default);
let disabled = run(&mut session, Itn::Off);
let enabled = run(&mut session, Itn::On);
assert_eq!(
(default.text, default.raw_text),
(enabled.text.clone(), enabled.raw_text.clone())
);
assert_ne!(enabled.text, disabled.text);
assert_eq!(disabled.text, disabled.text.to_lowercase());
assert!(disabled.raw_text.contains("<|woitn|>"));
assert!(enabled.raw_text.contains("<|withitn|>"));
}
#[test]
fn session_limits_are_sane() {
let Some((model_path, _)) = common::smoke_fixtures("session_limits_are_sane") else {
return;
};
let session = Model::load(&model_path).unwrap().session().unwrap();
let limits = session.limits().unwrap();
assert!(limits.effective_n_ctx >= 0);
assert!(limits.effective_max_audio_ms >= 0);
assert!(limits.max_kv_bytes >= 0);
}
#[test]
fn spec_decode_default_equals_disabled() {
let Some((model_path, pcm)) = common::smoke_fixtures("spec_decode_default_equals_disabled")
else {
return;
};
let mut session = Model::load(&model_path).unwrap().session().unwrap();
let base = session
.run(
&pcm,
&RunOptions {
spec_k_drafts: -1,
..Default::default()
},
)
.unwrap()
.text;
let nospec = session
.run(
&pcm,
&RunOptions {
spec_k_drafts: 0,
..Default::default()
},
)
.unwrap()
.text;
assert_eq!(base, nospec);
}
#[test]
fn empty_pcm_is_invalid_argument() {
let Some((model_path, _)) = common::smoke_fixtures("empty_pcm_is_invalid_argument") else {
return;
};
let mut session = Model::load(&model_path).unwrap().session().unwrap();
let err = session.run(&[], &RunOptions::default()).unwrap_err();
assert!(matches!(err, Error::InvalidArgument(_)), "got {err:?}");
}
#[test]
fn requested_timestamps_populate_rows() {
let Some((model_path, pcm)) = common::smoke_fixtures("requested_timestamps_populate_rows")
else {
return;
};
let model = Model::load(&model_path).unwrap();
let caps = model.capabilities().unwrap();
if caps.max_timestamp_kind == TimestampKind::None {
return;
}
let mut session = model.session().unwrap();
let result = session
.run(
&pcm,
&RunOptions {
timestamps: caps.max_timestamp_kind,
..Default::default()
},
)
.unwrap();
assert_ne!(result.timestamp_kind, TimestampKind::None);
assert!(!result.segments.is_empty());
assert!(result.segments.iter().any(|s| s.t1_ms >= s.t0_ms));
}
#[test]
fn timestamps_finer_than_supported_is_unsupported() {
let Some((model_path, pcm)) =
common::smoke_fixtures("timestamps_finer_than_supported_is_unsupported")
else {
return;
};
let model = Model::load(&model_path).unwrap();
let caps = model.capabilities().unwrap();
if caps.max_timestamp_kind == TimestampKind::Token {
return; }
let mut session = model.session().unwrap();
let err = session
.run(
&pcm,
&RunOptions {
timestamps: TimestampKind::Token,
..Default::default()
},
)
.unwrap_err();
assert!(matches!(err, Error::Unsupported(_)), "got {err:?}");
}
#[test]
fn close_ordering_drop_model_before_session() {
let Some((model_path, pcm)) =
common::smoke_fixtures("close_ordering_drop_model_before_session")
else {
return;
};
let model = Model::load(&model_path).unwrap();
let mut session = model.session().unwrap();
drop(model); let r = session.run(&pcm, &RunOptions::default()).unwrap();
assert!(r.text.to_lowercase().contains("country"));
drop(session); }
#[test]
fn shared_model_across_threads_serializes() {
let Some((model_path, pcm)) = common::smoke_fixtures("shared_model_across_threads_serializes")
else {
return;
};
let model = Arc::new(Model::load(&model_path).unwrap());
let pcm = Arc::new(pcm);
let handles: Vec<_> = (0..2)
.map(|i| {
let model = Arc::clone(&model);
let pcm = Arc::clone(&pcm);
thread::spawn(move || {
let mut s = model.session().unwrap();
let opts = RunOptions::default();
if i == 0 {
s.run(&pcm, &opts).unwrap().text
} else {
s.run_batch(&[&pcm], &opts).unwrap().remove(0).unwrap().text
}
})
})
.collect();
for h in handles {
let text = h.join().unwrap();
assert!(text.to_lowercase().contains("country"), "{text}");
}
}