use std::path::Path;
use super::*;
#[test]
fn decoding_defaults_match_swift() {
let o = DecodingOptions::new();
assert_eq!(o.task(), Task::Transcribe);
assert!(o.language().is_empty()); assert_eq!(o.temperature(), 0.0);
assert_eq!(o.temperature_increment_on_fallback(), 0.2);
assert_eq!(o.temperature_fallback_count(), 5);
assert_eq!(o.sample_length(), 224);
assert_eq!(o.top_k(), 5);
assert_eq!(o.seed(), None); assert!(o.use_prefill_prompt());
assert_eq!(o.use_prefill_prompt(), DEFAULT_USE_PREFILL_PROMPT); assert!(!o.detect_language());
assert!(!o.skip_special_tokens());
assert!(!o.without_timestamps());
assert!(!o.word_timestamps());
assert_eq!(o.max_initial_timestamp(), None);
assert_eq!(o.max_window_seek(), None);
assert!(o.clip_timestamps_slice().is_empty());
assert_eq!(o.window_clip_time(), 1.0);
assert!(o.prompt_tokens_slice().is_empty());
assert!(o.prefix_tokens_slice().is_empty());
assert!(!o.suppress_blank());
assert!(o.suppress_tokens_slice().is_empty());
assert_eq!(o.compression_ratio_threshold(), Some(2.4));
assert_eq!(o.logprob_threshold(), Some(-1.0));
assert_eq!(o.first_token_logprob_threshold(), Some(-1.5));
assert_eq!(o.no_speech_threshold(), Some(0.6));
assert_eq!(o.concurrent_worker_count().get(), 16);
assert_eq!(o.chunking_strategy(), ChunkingStrategy::Disabled);
assert!(!o.verbose());
assert!(o.drop_blank_audio());
assert_eq!(o.word_grouping(), WordGrouping::SwiftParity);
assert_eq!(o.alignment_gather(), AlignmentGather::Complete);
assert_eq!(DecodingOptions::default(), DecodingOptions::new());
}
#[test]
fn drop_blank_audio_defaults_on_and_opts_out_to_swift_parity() {
let o = DecodingOptions::new();
assert!(o.drop_blank_audio());
assert_eq!(o.drop_blank_audio(), DEFAULT_DROP_BLANK_AUDIO);
assert!(
!DecodingOptions::new()
.maybe_drop_blank_audio(false)
.drop_blank_audio()
);
assert!(
DecodingOptions::new()
.with_drop_blank_audio()
.drop_blank_audio()
);
let mut m = DecodingOptions::new();
m.clear_drop_blank_audio();
assert!(!m.drop_blank_audio(), "clear_ opts out to Swift parity");
m.set_drop_blank_audio();
assert!(m.drop_blank_audio());
m.update_drop_blank_audio(false);
assert!(!m.drop_blank_audio());
}
#[test]
fn swift_parity_option_deviations_are_exactly_two() {
let o = DecodingOptions::new();
assert!(o.drop_blank_audio());
assert!(
!DecodingOptions::new()
.maybe_drop_blank_audio(false)
.drop_blank_audio()
);
assert_eq!(o.word_grouping(), WordGrouping::SwiftParity);
assert!(
DecodingOptions::new()
.with_word_grouping(WordGrouping::FineGrained)
.word_grouping()
.is_fine_grained()
);
assert_eq!(o.alignment_gather(), AlignmentGather::Complete);
assert!(
DecodingOptions::new()
.with_alignment_gather(AlignmentGather::SwiftParity)
.alignment_gather()
.is_swift_parity()
);
assert!(
!o.skip_special_tokens(),
"Swift default: special tokens rendered"
);
assert!(!o.word_timestamps());
assert_eq!(o.sample_length(), 224);
assert_eq!(o.temperature(), 0.0);
}
#[cfg(feature = "serde")]
#[test]
fn drop_blank_audio_serde_default_is_true_not_bool_default() {
let omitted: DecodingOptions = serde_json::from_str("{}").unwrap();
assert!(
omitted.drop_blank_audio(),
"an omitted field must default to dropping, not to bool::default()"
);
let explicit_false: DecodingOptions =
serde_json::from_str(r#"{"drop_blank_audio":false}"#).unwrap();
assert!(!explicit_false.drop_blank_audio());
for wanted in [true, false] {
let options = DecodingOptions::new().maybe_drop_blank_audio(wanted);
let json = serde_json::to_string(&options).unwrap();
assert!(json.contains("drop_blank_audio"));
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
options
);
}
}
#[test]
fn builder_and_mutator_vocabulary() {
let o = DecodingOptions::new()
.with_temperature(0.4)
.with_no_speech_threshold(0.9) .maybe_logprob_threshold(None) .with_without_timestamps() .with_seed(7);
assert_eq!(o.temperature(), 0.4);
assert_eq!(o.no_speech_threshold(), Some(0.9));
assert_eq!(o.logprob_threshold(), None);
assert!(o.without_timestamps());
assert_eq!(o.seed(), Some(7));
let mut m = DecodingOptions::new();
m.set_top_k(10)
.clear_compression_ratio_threshold()
.set_detect_language()
.set_seed(11);
assert_eq!(m.top_k(), 10);
assert_eq!(m.compression_ratio_threshold(), None);
assert!(m.detect_language());
assert_eq!(m.seed(), Some(11));
m.update_seed(Some(12));
assert_eq!(m.seed(), Some(12));
m.clear_seed();
assert_eq!(m.seed(), None);
let via_maybe = DecodingOptions::new().maybe_seed(Some(13));
assert_eq!(via_maybe.seed(), Some(13));
}
#[test]
fn enums_round_trip_and_display() {
for t in [Task::Transcribe, Task::Translate] {
assert_eq!(t.as_str().parse::<Task>().unwrap(), t);
}
assert_eq!(ChunkingStrategy::Vad.to_string(), "vad");
assert_eq!(ChunkingStrategy::Disabled.as_str(), "none"); assert_eq!(
"none".parse::<ChunkingStrategy>().unwrap(),
ChunkingStrategy::Disabled
);
assert!("bogus".parse::<Task>().is_err());
for g in [WordGrouping::FineGrained, WordGrouping::SwiftParity] {
assert_eq!(g.as_str().parse::<WordGrouping>().unwrap(), g);
}
assert_eq!(WordGrouping::FineGrained.to_string(), "fine_grained");
assert_eq!(WordGrouping::SwiftParity.as_str(), "swift_parity");
assert!("bogus".parse::<WordGrouping>().is_err());
for g in [AlignmentGather::SwiftParity, AlignmentGather::Complete] {
assert_eq!(g.as_str().parse::<AlignmentGather>().unwrap(), g);
}
assert_eq!(AlignmentGather::Complete.to_string(), "complete");
assert_eq!(AlignmentGather::SwiftParity.as_str(), "swift_parity");
assert!("bogus".parse::<AlignmentGather>().is_err());
}
#[test]
fn word_grouping_defaults_to_swift_parity() {
assert_eq!(WordGrouping::default(), WordGrouping::SwiftParity);
assert_eq!(
DecodingOptions::new().word_grouping(),
WordGrouping::SwiftParity
);
assert!(DecodingOptions::new().word_grouping().is_swift_parity());
let built = DecodingOptions::new().with_word_grouping(WordGrouping::FineGrained);
assert_eq!(built.word_grouping(), WordGrouping::FineGrained);
assert!(built.word_grouping().is_fine_grained());
let mut m = DecodingOptions::new();
m.set_word_grouping(WordGrouping::FineGrained);
assert_eq!(m.word_grouping(), WordGrouping::FineGrained);
m.set_word_grouping(WordGrouping::SwiftParity);
assert_eq!(m.word_grouping(), WordGrouping::SwiftParity);
}
#[cfg(feature = "serde")]
#[test]
fn word_grouping_serde_omitted_stays_swift_parity() {
let omitted: DecodingOptions = serde_json::from_str("{}").unwrap();
assert_eq!(omitted.word_grouping(), WordGrouping::SwiftParity);
let fine: DecodingOptions = serde_json::from_str(r#"{"word_grouping":"fine_grained"}"#).unwrap();
assert_eq!(fine.word_grouping(), WordGrouping::FineGrained);
for wanted in [WordGrouping::FineGrained, WordGrouping::SwiftParity] {
let options = DecodingOptions::new().with_word_grouping(wanted);
let json = serde_json::to_string(&options).unwrap();
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
options
);
}
}
#[test]
fn alignment_gather_defaults_to_complete() {
assert_eq!(AlignmentGather::default(), AlignmentGather::Complete);
assert_eq!(
DecodingOptions::new().alignment_gather(),
AlignmentGather::Complete
);
assert!(DecodingOptions::new().alignment_gather().is_complete());
let built = DecodingOptions::new().with_alignment_gather(AlignmentGather::SwiftParity);
assert_eq!(built.alignment_gather(), AlignmentGather::SwiftParity);
assert!(built.alignment_gather().is_swift_parity());
let mut m = DecodingOptions::new();
m.set_alignment_gather(AlignmentGather::SwiftParity);
assert_eq!(m.alignment_gather(), AlignmentGather::SwiftParity);
m.set_alignment_gather(AlignmentGather::Complete);
assert_eq!(m.alignment_gather(), AlignmentGather::Complete);
}
#[cfg(feature = "serde")]
#[test]
fn alignment_gather_serde_omitted_stays_complete() {
let omitted: DecodingOptions = serde_json::from_str("{}").unwrap();
assert_eq!(omitted.alignment_gather(), AlignmentGather::Complete);
let parity: DecodingOptions =
serde_json::from_str(r#"{"alignment_gather":"swift_parity"}"#).unwrap();
assert_eq!(parity.alignment_gather(), AlignmentGather::SwiftParity);
for wanted in [AlignmentGather::SwiftParity, AlignmentGather::Complete] {
let options = DecodingOptions::new().with_alignment_gather(wanted);
let json = serde_json::to_string(&options).unwrap();
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
options
);
}
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trips_and_fills_defaults() {
let full = DecodingOptions::new().with_temperature(0.7);
let json = serde_json::to_string(&full).unwrap();
assert!(!json.contains("max_initial_timestamp")); assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
full
);
let partial: DecodingOptions = serde_json::from_str(r#"{"temperature":0.5}"#).unwrap();
assert_eq!(partial.temperature(), 0.5);
assert_eq!(partial.top_k(), 5);
assert_eq!(
serde_json::from_str::<DecodingOptions>("{}").unwrap(),
DecodingOptions::new()
);
}
#[test]
fn detect_language_default_couples_to_prefill() {
let unset = DecodingOptions::new();
assert!(unset.use_prefill_prompt());
assert!(!unset.detect_language());
assert_eq!(unset.detect_language, None, "constructed unset, not false");
let mut no_prefill = DecodingOptions::new();
no_prefill.clear_use_prefill_prompt();
assert!(no_prefill.detect_language());
let mut explicit_false = DecodingOptions::new();
explicit_false
.clear_use_prefill_prompt()
.clear_detect_language();
assert!(!explicit_false.detect_language());
let mut late_prefill = DecodingOptions::new();
late_prefill.clear_detect_language();
late_prefill.clear_use_prefill_prompt();
assert!(!late_prefill.detect_language());
let explicit_true = DecodingOptions::new().with_detect_language();
assert!(explicit_true.use_prefill_prompt());
assert!(explicit_true.detect_language());
let via_update = DecodingOptions::new()
.maybe_use_prefill_prompt(false)
.maybe_detect_language(false);
assert!(!via_update.detect_language());
}
#[test]
fn detect_language_pinned_construction_vs_mutation_histories() {
let constructed = DecodingOptions::new().maybe_use_prefill_prompt(false);
assert_eq!(constructed.detect_language, None, "still unset");
assert!(constructed.detect_language());
let mut mutated = DecodingOptions::new();
assert!(mutated.use_prefill_prompt()); mutated.clear_use_prefill_prompt(); assert_eq!(mutated.detect_language, None, "still unset");
assert!(
mutated.detect_language(),
"pinned deviation: Swift's equivalent history stays false here"
);
assert_eq!(constructed.detect_language(), mutated.detect_language());
}
#[cfg(feature = "serde")]
#[test]
fn detect_language_serde_tristate() {
let json = serde_json::to_string(&DecodingOptions::new()).unwrap();
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(!value.as_object().unwrap().contains_key("detect_language"));
let missing: DecodingOptions = serde_json::from_str("{}").unwrap();
assert_eq!(missing.detect_language, None);
assert!(!missing.detect_language());
let coupled: DecodingOptions = serde_json::from_str(r#"{"use_prefill_prompt":false}"#).unwrap();
assert_eq!(coupled.detect_language, None);
assert!(coupled.detect_language());
let explicit_false: DecodingOptions =
serde_json::from_str(r#"{"use_prefill_prompt":false,"detect_language":false}"#).unwrap();
assert_eq!(explicit_false.detect_language, Some(false));
assert!(!explicit_false.detect_language());
let json = serde_json::to_string(&explicit_false).unwrap();
assert!(json.contains("\"detect_language\":false"));
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
explicit_false
);
let explicit_true = DecodingOptions::new().with_detect_language();
let json = serde_json::to_string(&explicit_true).unwrap();
assert!(json.contains("\"detect_language\":true"));
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
explicit_true
);
}
#[test]
fn compute_defaults_match_swift_model_compute_options() {
assert_eq!(DEFAULT_MEL_COMPUTE_UNITS, crate::ComputeUnits::CpuAndGpu);
assert_eq!(
DEFAULT_ENCODER_COMPUTE_UNITS,
crate::ComputeUnits::CpuAndNeuralEngine
);
assert_eq!(
DEFAULT_DECODER_COMPUTE_UNITS,
crate::ComputeUnits::CpuAndNeuralEngine
);
let c = ComputeOptions::new();
assert_eq!(c.mel(), DEFAULT_MEL_COMPUTE_UNITS);
assert_eq!(c.encoder(), DEFAULT_ENCODER_COMPUTE_UNITS);
assert_eq!(c.decoder(), DEFAULT_DECODER_COMPUTE_UNITS);
assert_eq!(Options::new("m", "t").compute(), c);
}
#[test]
fn task_predicates_display_and_default() {
assert!(Task::Transcribe.is_transcribe());
assert!(!Task::Transcribe.is_translate());
assert!(Task::Translate.is_translate());
assert_eq!(Task::default(), Task::Transcribe);
assert_eq!(Task::Transcribe.to_string(), "transcribe");
}
#[test]
fn chunking_strategy_predicates_and_default() {
assert!(ChunkingStrategy::Disabled.is_disabled());
assert!(!ChunkingStrategy::Disabled.is_vad());
assert!(ChunkingStrategy::Vad.is_vad());
assert_eq!(ChunkingStrategy::default(), ChunkingStrategy::Disabled);
}
#[test]
fn parse_errors_are_opaque_and_display() {
assert_eq!(
"bogus".parse::<Task>().unwrap_err().to_string(),
"unknown task name"
);
assert_eq!(
"bogus".parse::<ChunkingStrategy>().unwrap_err().to_string(),
"unknown chunking strategy name"
);
}
#[test]
fn compute_options_builder_and_default() {
let c = ComputeOptions::new()
.with_mel(crate::ComputeUnits::CpuOnly)
.with_encoder(crate::ComputeUnits::CpuOnly)
.with_decoder(crate::ComputeUnits::CpuOnly);
assert_eq!(c.mel(), crate::ComputeUnits::CpuOnly);
assert_eq!(c.encoder(), crate::ComputeUnits::CpuOnly);
assert_eq!(c.decoder(), crate::ComputeUnits::CpuOnly);
assert_eq!(ComputeOptions::default(), ComputeOptions::new());
}
#[test]
fn options_new_requires_folders_and_defaults_rest() {
let o = Options::new("/models/whisper", "/models/tokenizer");
assert_eq!(o.model_folder(), Path::new("/models/whisper"));
assert_eq!(o.tokenizer_folder(), Path::new("/models/tokenizer"));
assert_eq!(o.compute(), ComputeOptions::new());
assert!(!o.prewarm()); assert!(o.load()); }
#[test]
fn options_builder_and_mutator_vocabulary() {
let o = Options::new("a", "b")
.with_prewarm()
.with_compute(ComputeOptions::new().with_mel(crate::ComputeUnits::CpuOnly));
assert!(o.prewarm());
assert_eq!(o.compute().mel(), crate::ComputeUnits::CpuOnly);
let mut m = Options::new("a", "b");
m.clear_load();
assert!(!m.load());
m.update_load(true);
assert!(m.load());
m.set_prewarm();
assert!(m.prewarm());
m.clear_prewarm();
assert!(!m.prewarm());
}
#[cfg(feature = "serde")]
#[test]
fn compute_options_serde_partial_uses_whisperkit_defaults() {
let c: ComputeOptions = serde_json::from_str("{}").unwrap();
assert_eq!(c, ComputeOptions::new());
assert_ne!(c.mel(), crate::ComputeUnits::default());
let json = serde_json::to_string(&ComputeOptions::new()).unwrap();
assert_eq!(
serde_json::from_str::<ComputeOptions>(&json).unwrap(),
ComputeOptions::new()
);
}
#[cfg(feature = "serde")]
#[test]
fn chunking_strategy_serde_renames_disabled_to_none() {
assert_eq!(
serde_json::to_string(&ChunkingStrategy::Disabled).unwrap(),
"\"none\""
);
assert_eq!(
serde_json::to_string(&ChunkingStrategy::Vad).unwrap(),
"\"vad\""
);
assert_eq!(
serde_json::from_str::<ChunkingStrategy>("\"none\"").unwrap(),
ChunkingStrategy::Disabled
);
}
#[cfg(feature = "serde")]
#[test]
fn task_serde_uses_snake_case() {
assert_eq!(
serde_json::to_string(&Task::Transcribe).unwrap(),
"\"transcribe\""
);
assert_eq!(
serde_json::from_str::<Task>("\"translate\"").unwrap(),
Task::Translate
);
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_round_trips_and_fills_defaults() {
let full = Options::new("/models/whisper", "/models/tokenizer").with_prewarm();
let json = serde_json::to_string(&full).unwrap();
assert_eq!(serde_json::from_str::<Options>(&json).unwrap(), full);
let partial: Options =
serde_json::from_str(r#"{"model_folder":"/m","tokenizer_folder":"/t"}"#).unwrap();
assert_eq!(partial.compute(), ComputeOptions::new());
assert!(!partial.prewarm());
assert!(partial.load());
}
#[cfg(feature = "serde")]
#[test]
fn decoding_options_empty_collections_skip_serializing() {
let json = serde_json::to_string(&DecodingOptions::new()).unwrap();
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let object = value.as_object().unwrap();
assert!(!object.contains_key("language"));
assert!(!object.contains_key("clip_timestamps"));
assert!(!object.contains_key("prompt_tokens"));
assert!(!object.contains_key("prefix_tokens"));
assert!(!object.contains_key("suppress_tokens"));
assert!(!object.contains_key("detect_language"));
assert!(!object.contains_key("seed"));
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
DecodingOptions::new()
);
}
#[cfg(feature = "serde")]
#[test]
fn seed_serde_absent_when_unset_round_trips_when_set() {
let json = serde_json::to_string(&DecodingOptions::new()).unwrap();
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(!value.as_object().unwrap().contains_key("seed"));
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json)
.unwrap()
.seed(),
None
);
let missing: DecodingOptions = serde_json::from_str("{}").unwrap();
assert_eq!(missing.seed(), None);
for seed in [0u64, 1, 42, u64::MAX] {
let explicit = DecodingOptions::new().with_seed(seed);
let json = serde_json::to_string(&explicit).unwrap();
assert!(json.contains(&format!("\"seed\":{seed}")));
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
explicit
);
}
}
#[cfg(feature = "serde")]
#[test]
fn compute_units_rejects_unknown_names() {
let err = serde_json::from_str::<ComputeOptions>(r#"{"mel":"bogus"}"#).unwrap_err();
assert!(err.to_string().contains("unknown compute units name"));
}
#[cfg(feature = "serde")]
#[test]
fn non_finite_floats_are_rejected_at_the_serde_boundary() {
for bad in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert!(
serde_json::to_string(&DecodingOptions::new().with_temperature(bad)).is_err(),
"a non-finite scalar `temperature` must be refused, not written as null"
);
assert!(serde_json::to_string(&DecodingOptions::new().with_window_clip_time(bad)).is_err());
assert!(
serde_json::to_string(&DecodingOptions::new().with_compression_ratio_threshold(bad)).is_err(),
"a non-finite threshold must be refused, or it round-trips to a forged `None`"
);
assert!(
serde_json::to_string(&DecodingOptions::new().with_clip_timestamps(vec![0.0, bad])).is_err()
);
}
assert!(serde_json::from_str::<DecodingOptions>(r#"{"temperature":1e400}"#).is_err());
assert!(serde_json::from_str::<DecodingOptions>(r#"{"clip_timestamps":[1e400]}"#).is_err());
assert!(
serde_json::from_str::<DecodingOptions>(r#"{"compression_ratio_threshold":1e400}"#).is_err()
);
let disabled: DecodingOptions =
serde_json::from_str(r#"{"compression_ratio_threshold":null}"#).unwrap();
assert_eq!(disabled.compression_ratio_threshold(), None);
let finite = DecodingOptions::new()
.with_temperature(-0.2)
.with_window_clip_time(0.5)
.with_compression_ratio_threshold(2.4)
.maybe_logprob_threshold(None)
.with_clip_timestamps(vec![0.0, 1.5, 3.0]);
let json = serde_json::to_string(&finite).unwrap();
assert_eq!(
serde_json::from_str::<DecodingOptions>(&json).unwrap(),
finite
);
}