use super::*;
use crate::DataType;
use soundevents_dataset::RatedSoundEvent;
#[test]
fn num_classes_matches_the_rated_dataset() {
assert_eq!(NUM_CLASSES, 527);
assert_eq!(RatedSoundEvent::events().len(), NUM_CLASSES);
assert!(RatedSoundEvent::from_index(NUM_CLASSES - 1).is_some());
assert!(RatedSoundEvent::from_index(NUM_CLASSES).is_none());
}
#[test]
fn window_is_ten_seconds_at_the_contract_rate() {
assert_eq!(WINDOW_SAMPLES, 10 * SAMPLE_RATE_HZ as usize);
}
#[test]
fn default_compute_is_the_provisional_all() {
assert_eq!(DEFAULT_COMPUTE, ComputeUnits::All);
}
#[test]
fn options_default_equals_new() {
assert_eq!(ClassifierOptions::default(), ClassifierOptions::new());
assert_eq!(ClassifierOptions::new().compute(), DEFAULT_COMPUTE);
}
#[test]
fn options_with_and_set_compute() {
let opts = ClassifierOptions::new().with_compute(ComputeUnits::CpuAndNeuralEngine);
assert_eq!(opts.compute(), ComputeUnits::CpuAndNeuralEngine);
let mut opts = ClassifierOptions::new();
opts.set_compute(ComputeUnits::CpuOnly);
assert_eq!(opts.compute(), ComputeUnits::CpuOnly);
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_roundtrip_and_pinned_spelling() {
let opts = ClassifierOptions::new().with_compute(ComputeUnits::CpuAndGpu);
let json = serde_json::to_string(&opts).unwrap();
assert_eq!(json, "{\"compute\":\"cpu_and_gpu\"}");
let back: ClassifierOptions = serde_json::from_str(&json).unwrap();
assert_eq!(back, opts);
}
#[cfg(feature = "serde")]
#[test]
fn options_missing_compute_defaults_to_provisional_all() {
let opts: ClassifierOptions = serde_json::from_str("{}").unwrap();
assert_eq!(opts.compute(), DEFAULT_COMPUTE);
}
#[cfg(feature = "serde")]
#[test]
fn options_unknown_compute_spelling_is_rejected() {
assert!(serde_json::from_str::<ClassifierOptions>("{\"compute\":\"gpu\"}").is_err());
}
#[test]
fn validate_rejects_empty_audio() {
assert!(matches!(validate_window_input(&[]), Err(Error::EmptyAudio)));
}
#[test]
fn validate_rejects_overlong_audio_never_truncates() {
let long = vec![0.0f32; WINDOW_SAMPLES + 1];
assert!(matches!(
validate_window_input(&long),
Err(Error::AudioTooLong(e)) if e.len() == WINDOW_SAMPLES + 1 && e.max() == WINDOW_SAMPLES
));
}
#[test]
fn validate_reports_the_first_non_finite_sample() {
let mut samples = vec![0.0f32; 100];
samples[41] = f32::NAN;
samples[43] = f32::INFINITY;
assert!(matches!(
validate_window_input(&samples),
Err(Error::NonFiniteInput(41))
));
}
#[test]
fn classify_long_zero_k_guard_catches_non_finite_samples_beyond_one_window() {
let mut samples = vec![0.0f32; WINDOW_SAMPLES + 500];
samples[WINDOW_SAMPLES + 300] = f32::NAN;
assert!(matches!(
check_finite_samples(&samples),
Err(Error::NonFiniteInput(index)) if index == WINDOW_SAMPLES + 300
));
assert!(check_finite_samples(&vec![0.0f32; WINDOW_SAMPLES + 500]).is_ok());
}
#[test]
fn validate_accepts_one_sample_and_a_full_window() {
assert!(validate_window_input(&[0.5]).is_ok());
assert!(validate_window_input(&vec![0.0f32; WINDOW_SAMPLES]).is_ok());
}
#[test]
fn finite_logit_check_reports_the_index() {
let mut logits = vec![0.0f32; NUM_CLASSES];
assert!(check_finite_logits(&logits).is_ok());
logits[7] = f32::NEG_INFINITY;
assert!(matches!(
check_finite_logits(&logits),
Err(Error::NonFiniteOutput(7))
));
}
use crate::{
AxisRange, ComputeUnits, FeatureInfo, Model, ModelDescription, model::RawShapeConstraint,
};
fn fixed(name: &str, shape: &[usize], dtype: DataType) -> FeatureInfo {
multi_array(name, shape, dtype, false, 2, vec![shape.to_vec()], shape)
}
fn multi_array(
name: &str,
shape: &[usize],
dtype: DataType,
optional: bool,
raw_type: isize,
enumerated: Vec<Vec<usize>>,
pinned: &[usize],
) -> FeatureInfo {
FeatureInfo::from_parts(
name.to_string(),
shape.to_vec(),
Some(dtype),
optional,
Some(RawShapeConstraint::new(
raw_type,
enumerated,
pinned.iter().map(|d| AxisRange::new(*d, 1)).collect(),
)),
)
}
fn ced_description() -> ModelDescription {
ModelDescription::from_parts(
vec![fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F32)],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
Vec::new(),
)
}
fn check(description: &ModelDescription) -> Result<()> {
crate::model::contract::check_load_contract(description, &ced_contract())
.map_err(contract_violation)
}
#[test]
fn the_contract_accepts_the_converted_geometry() {
assert!(check(&ced_description()).is_ok());
}
#[test]
fn the_contract_refuses_a_flexible_mel_declaring_its_exact_numbers() {
let description = ModelDescription::from_parts(
vec![multi_array(
names::MEL,
&[1, N_MELS, N_FRAMES],
DataType::F32,
false,
3,
Vec::new(),
&[1, N_MELS, N_FRAMES],
)],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
Vec::new(),
);
let err = check(&description).unwrap_err();
assert!(
matches!(&err, Error::ContractMismatch(m) if m.feature() == names::MEL),
"{err}"
);
}
#[test]
fn the_contract_refuses_a_right_shaped_fp16_graph() {
let description = ModelDescription::from_parts(
vec![fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F16)],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
Vec::new(),
);
let err = check(&description).unwrap_err();
assert!(
matches!(&err, Error::ContractMismatch(m) if m.feature() == names::MEL),
"{err}"
);
}
#[test]
fn the_contract_refuses_a_transposed_shape_of_the_same_size() {
let description = ModelDescription::from_parts(
vec![fixed(names::MEL, &[1, N_FRAMES, N_MELS], DataType::F32)],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
Vec::new(),
);
assert!(matches!(
check(&description),
Err(Error::ContractMismatch(_))
));
}
#[test]
fn the_contract_refuses_a_different_class_count() {
let description = ModelDescription::from_parts(
vec![fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F32)],
vec![fixed(names::LOGITS, &[1, 521], DataType::F32)],
Vec::new(),
);
let err = check(&description).unwrap_err();
assert!(
matches!(&err, Error::ContractMismatch(m) if m.feature() == names::LOGITS),
"{err}"
);
}
#[test]
fn the_contract_refuses_an_extra_required_input() {
let description = ModelDescription::from_parts(
vec![
fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F32),
fixed("clip_mask", &[1, N_FRAMES], DataType::F32),
],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
Vec::new(),
);
assert!(
matches!(check(&description), Err(Error::UnsatisfiableInput(name)) if name == "clip_mask"),
"{:?}",
check(&description)
);
}
#[test]
fn the_contract_accepts_an_extra_optional_input() {
let description = ModelDescription::from_parts(
vec![
fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F32),
multi_array(
"mask",
&[1, N_FRAMES],
DataType::F32,
true,
2,
vec![vec![1, N_FRAMES]],
&[1, N_FRAMES],
),
],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
Vec::new(),
);
assert!(check(&description).is_ok());
}
#[test]
fn the_contract_refuses_an_optional_logits_output() {
let description = ModelDescription::from_parts(
vec![fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F32)],
vec![multi_array(
names::LOGITS,
&[1, NUM_CLASSES],
DataType::F32,
true,
2,
vec![vec![1, NUM_CLASSES]],
&[1, NUM_CLASSES],
)],
Vec::new(),
);
let err = check(&description).unwrap_err();
assert!(
matches!(&err, Error::ContractMismatch(m) if m.feature() == names::LOGITS),
"{err}"
);
}
#[test]
fn the_contract_refuses_a_graph_that_declares_state() {
let description = ModelDescription::from_parts(
vec![fixed(names::MEL, &[1, N_MELS, N_FRAMES], DataType::F32)],
vec![fixed(names::LOGITS, &[1, NUM_CLASSES], DataType::F32)],
vec![fixed("kv_cache", &[1, 8], DataType::F32)],
);
assert!(
matches!(check(&description), Err(Error::UnsatisfiableState(name)) if name == "kv_cache")
);
}
#[test]
fn the_ced_contract_refuses_the_vendored_silero_bundle() {
let bundle = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../Models/vadkit/silero-vad-unified-256ms-v6.2.1.mlmodelc");
assert!(
bundle.is_dir(),
"the vendored silero bundle is committed, so this gate is NOT model-gated; \
looked for {}",
bundle.display()
);
let model = Model::load(&bundle, ComputeUnits::CpuOnly).expect("the committed bundle loads");
assert!(
model.description().input(names::MEL).is_none(),
"silero declares no `mel`, which is what makes it this gate's model"
);
let violation = crate::model::contract::Checked::new(model, &ced_contract())
.expect_err("silero does not satisfy the CED contract");
assert!(
matches!(&violation, crate::model::contract::ContractViolation::Missing(m)
if m.feature() == names::MEL),
"expected `mel` missing, got {violation}"
);
}