use super::*;
#[test]
fn model_error_wraps_load_via_from() {
let inner = crate::LoadError::NotFound("seg.mlmodelc".into());
let e: ModelError = inner.into();
assert!(matches!(e, ModelError::Load(_)));
}
#[test]
fn model_error_contract_mismatch_displays_feature_and_shapes() {
let e = ModelError::ContractMismatch(ContractMismatch::new(
"segments",
"[1, 589, 7] f32".to_string(),
"[1, 592, 7] f32".to_string(),
));
let rendered = e.to_string();
assert!(rendered.contains("segments"));
assert!(rendered.contains("589"));
assert!(rendered.contains("592"));
}
#[test]
fn infer_error_wraps_prediction_and_tensor_via_from() {
let e: InferError = crate::PredictionError::StateUnsupported.into();
assert!(matches!(e, InferError::Prediction(_)));
let e: InferError = crate::TensorError::ShapeMismatch(crate::ShapeMismatch::new(4, 2)).into();
assert!(matches!(e, InferError::Tensor(_)));
}
#[test]
fn infer_error_non_finite_output_displays_index() {
let e = InferError::NonFiniteOutput(42);
assert_eq!(
e.to_string(),
"output contains a non-finite value at index 42"
);
}
#[test]
fn infer_error_input_length_displays_got_and_expected() {
let e = InferError::InputLength(InputLength::new(100, 160_000));
let rendered = e.to_string();
assert!(rendered.contains("100"));
assert!(rendered.contains("160000"));
}
#[test]
fn infer_error_output_shape_displays_got_and_expected() {
let e = InferError::OutputShape(OutputShape::new(vec![1, 7, 589], vec![1, 589, 7]));
let rendered = e.to_string();
assert!(rendered.contains("[1, 7, 589]"));
assert!(rendered.contains("[1, 589, 7]"));
}
#[test]
fn infer_error_non_finite_input_displays_index() {
let e = InferError::NonFiniteInput(7);
assert_eq!(
e.to_string(),
"input contains a non-finite value at index 7"
);
}
#[test]
fn infer_error_f16_overflow_input_displays_index_and_honest_reason() {
let e = InferError::F16OverflowInput(42);
let rendered = e.to_string();
assert!(rendered.contains("index 42"), "{rendered}");
assert!(
rendered.contains("finite in f32") && rendered.contains("f16"),
"{rendered}"
);
}
#[test]
fn infer_error_empty_mask_displays_message() {
let e = InferError::EmptyMask;
assert_eq!(e.to_string(), "mask has no active (true) frame");
}
#[test]
fn extract_error_composes_model_arm() {
let model_err: ModelError = crate::LoadError::NotFound("seg.mlmodelc".into()).into();
let e: ExtractError = model_err.into();
assert!(matches!(e, ExtractError::Model(ModelError::Load(_))));
}
#[test]
fn extract_error_composes_infer_arm() {
let infer_err: InferError =
crate::TensorError::ShapeMismatch(crate::ShapeMismatch::new(4, 2)).into();
let e: ExtractError = infer_err.into();
assert!(matches!(e, ExtractError::Infer(InferError::Tensor(_))));
}
#[test]
fn extract_error_empty_samples_displays_message() {
assert_eq!(ExtractError::EmptySamples.to_string(), "samples is empty");
}
#[test]
fn extract_error_zero_step_samples_displays_message() {
assert_eq!(
ExtractError::ZeroStepSamples.to_string(),
"step_samples must be > 0"
);
}
#[test]
fn extract_error_step_samples_exceeds_window_displays_both() {
let e = ExtractError::StepSamplesExceedsWindow(StepSamplesExceedsWindow::new(200_000, 160_000));
let rendered = e.to_string();
assert!(rendered.contains("200000"));
assert!(rendered.contains("160000"));
}
#[test]
fn extract_error_unsupported_step_samples_displays_both() {
let e = ExtractError::UnsupportedStepSamples(UnsupportedStepSamples::new(8_000, 16_000));
let rendered = e.to_string();
assert!(rendered.contains("8000"));
assert!(rendered.contains("16000"));
}
#[test]
fn extract_error_onset_out_of_range_displays_value() {
let e = ExtractError::OnsetOutOfRange(1.5);
let rendered = e.to_string();
assert!(rendered.contains("1.5"));
assert!(rendered.contains("(0.0, 1.0]"));
}
#[test]
fn extract_error_frame_count_mismatch_displays_both() {
let e = ExtractError::FrameCountMismatch(FrameCountMismatch::new(589, 588));
let rendered = e.to_string();
assert!(rendered.contains("589"));
assert!(rendered.contains("588"));
}
#[test]
fn extract_error_output_frame_count_overflow_displays_message() {
let rendered = ExtractError::OutputFrameCountOverflow.to_string();
assert!(rendered.contains("num_output_frames overflows usize"));
}
#[test]
fn zero_extraction_dimension_displays_the_part_name() {
for (part, name) in [
(ExtractionPart::NumChunks, "num_chunks"),
(ExtractionPart::NumFramesPerChunk, "num_frames_per_chunk"),
(ExtractionPart::Count, "count"),
] {
let rendered = ExtractError::ZeroExtractionDimension(part).to_string();
assert!(rendered.contains(name), "{rendered} must name {name}");
assert!(rendered.contains("non-zero"), "{rendered}");
}
}
#[test]
fn extraction_len_mismatch_displays_part_got_and_expected() {
let m = ExtractionLenMismatch::new(ExtractionPart::RawEmbeddings, 767, 768);
assert_eq!(m.part(), ExtractionPart::RawEmbeddings);
assert_eq!(m.got(), 767);
assert_eq!(m.expected(), 768);
let rendered = ExtractError::ExtractionLenMismatch(m).to_string();
assert!(rendered.contains("raw_embeddings"), "{rendered}");
assert!(rendered.contains("767"), "{rendered}");
assert!(rendered.contains("768"), "{rendered}");
}
#[test]
fn extraction_geometry_overflow_displays_part_and_both_dimensions() {
let g = ExtractionGeometryOverflow::new(ExtractionPart::Segmentations, 4_294_967_296, 8);
assert_eq!(g.part(), ExtractionPart::Segmentations);
assert_eq!(g.num_chunks(), 4_294_967_296);
assert_eq!(g.num_frames_per_chunk(), 8);
let rendered = ExtractError::ExtractionGeometryOverflow(g).to_string();
assert!(rendered.contains("segmentations"), "{rendered}");
assert!(rendered.contains("4294967296"), "{rendered}");
assert!(rendered.contains("overflows usize"), "{rendered}");
}
#[test]
fn invalid_sliding_window_displays_all_three_components() {
let w = crate::audio::speaker::window::SlidingWindow::new(0.0, 10.0, 0.0);
let e = InvalidSlidingWindow::new(ExtractionPart::ChunksSw, w);
assert_eq!(e.part(), ExtractionPart::ChunksSw);
assert_eq!(e.window(), w);
let rendered = ExtractError::InvalidSlidingWindow(e).to_string();
assert!(rendered.contains("chunks_sw"), "{rendered}");
assert!(rendered.contains("10"), "{rendered}");
assert!(rendered.contains("step 0"), "{rendered}");
}
#[test]
fn misaligned_chunk_placement_displays_the_chunk_and_both_frames() {
let m = ChunkPlacementMismatch::new(1, 3, 2);
assert_eq!((m.chunk(), m.aggregated(), m.reconstructed()), (1, 3, 2));
let rendered = ExtractError::MisalignedChunkPlacement(m).to_string();
assert!(rendered.contains("chunk 1"), "{rendered}");
assert!(rendered.contains("output frame 3"), "{rendered}");
assert!(rendered.contains("frame 2"), "{rendered}");
assert!(rendered.contains("reconstruction"), "{rendered}");
}
#[test]
fn frame_step_not_representable_in_f32_displays_both_the_step_and_its_image() {
let w = crate::audio::speaker::window::SlidingWindow::new(0.0, 1.0, 7.0e-46);
let e = InvalidSlidingWindow::new(ExtractionPart::FramesSw, w);
let rendered = ExtractError::FrameStepNotRepresentableInF32(e).to_string();
assert!(rendered.contains("frames_sw"), "{rendered}");
assert!(rendered.contains("7e-46"), "{rendered}");
assert!(rendered.contains("narrows to 0e0"), "{rendered}");
}
#[test]
fn non_binary_segmentation_displays_the_index_the_value_and_the_slot() {
use crate::audio::speaker::segment::SEG_NUM_SLOTS;
let flat = ((2 * 7 + 5) * SEG_NUM_SLOTS) + 1;
assert_eq!(flat, 58);
let n = NonBinarySegmentation::new(flat, 0.3);
assert_eq!((n.index(), n.value(), n.slot()), (58, 0.3, 1));
let rendered = ExtractError::NonBinarySegmentation(n).to_string();
assert!(rendered.contains("segmentations[58]"), "{rendered}");
assert!(rendered.contains("is 0.3"), "{rendered}");
assert!(rendered.contains("slot 1"), "{rendered}");
assert!(rendered.contains("exactly 0.0 or 1.0"), "{rendered}");
let rendered =
ExtractError::NonBinarySegmentation(NonBinarySegmentation::new(0, 4.5)).to_string();
assert!(rendered.contains("segmentations[0]"), "{rendered}");
assert!(rendered.contains("is 4.5"), "{rendered}");
assert!(rendered.contains("slot 0"), "{rendered}");
let rendered =
ExtractError::NonBinarySegmentation(NonBinarySegmentation::new(2, f64::NAN)).to_string();
assert!(rendered.contains("segmentations[2]"), "{rendered}");
assert!(rendered.contains("NaN"), "{rendered}");
assert!(
rendered.contains(&format!("slot {}", 2 % SEG_NUM_SLOTS)),
"{rendered}"
);
}
#[test]
fn active_slot_without_embedding_displays_chunk_and_slot() {
let a = ActiveSlotWithoutEmbedding::new(7, 2);
assert_eq!((a.chunk(), a.slot()), (7, 2));
let rendered = ExtractError::ActiveSlotWithoutEmbedding(a).to_string();
assert!(rendered.contains("chunk 7"), "{rendered}");
assert!(rendered.contains("slot 2"), "{rendered}");
assert!(
rendered.contains("cannot reach the clustering"),
"{rendered}"
);
assert!(rendered.contains("0.01"), "{rendered}");
assert!(!rendered.contains("NORM_EPSILON"), "{rendered}");
}
#[test]
fn count_not_segmentation_derived_displays_frame_got_and_expected() {
let c = CountNotSegmentationDerived::new(41, 4, 1);
assert_eq!((c.frame(), c.got(), c.expected()), (41, 4, 1));
let rendered = ExtractError::CountNotSegmentationDerived(c).to_string();
assert!(rendered.contains("count[41]"), "{rendered}");
assert!(rendered.contains(" is 4 "), "{rendered}");
assert!(rendered.contains("derive 1"), "{rendered}");
let under = CountNotSegmentationDerived::new(0, 0, 1);
let rendered = ExtractError::CountNotSegmentationDerived(under).to_string();
assert!(rendered.contains("count[0]"), "{rendered}");
assert!(rendered.contains(" is 0 "), "{rendered}");
assert!(rendered.contains("derive 1"), "{rendered}");
}
#[test]
fn output_frame_count_too_large_displays_both_the_derived_count_and_the_cap() {
let rendered =
ExtractError::OutputFrameCountTooLarge(crate::audio::speaker::extract::MAX_OUTPUT_FRAMES + 1)
.to_string();
assert!(rendered.contains("4194305"), "{rendered}");
assert!(rendered.contains("4194304"), "{rendered}");
assert!(rendered.contains("MAX_OUTPUT_FRAMES"), "{rendered}");
}
#[test]
fn non_finite_raw_embedding_displays_the_index_it_decodes_to_chunk_slot_dimension() {
use crate::audio::speaker::{embed::EMBEDDING_DIM, segment::SEG_NUM_SLOTS};
let flat = ((2 * SEG_NUM_SLOTS + 1) * EMBEDDING_DIM) + 5;
assert_eq!(flat, 1797);
let rendered = ExtractError::NonFiniteRawEmbedding(flat).to_string();
assert!(rendered.contains("raw_embeddings[1797]"), "{rendered}");
assert!(rendered.contains("chunk 2"), "{rendered}");
assert!(rendered.contains("slot 1"), "{rendered}");
assert!(rendered.contains("dimension 5"), "{rendered}");
let rendered = ExtractError::NonFiniteRawEmbedding(0).to_string();
assert!(rendered.contains("raw_embeddings[0]"), "{rendered}");
assert!(rendered.contains("chunk 0"), "{rendered}");
assert!(rendered.contains("slot 0"), "{rendered}");
assert!(rendered.contains("dimension 0"), "{rendered}");
let last = SEG_NUM_SLOTS * EMBEDDING_DIM - 1;
let rendered = ExtractError::NonFiniteRawEmbedding(last).to_string();
assert!(rendered.contains("chunk 0"), "{rendered}");
assert!(
rendered.contains(&format!("slot {}", SEG_NUM_SLOTS - 1)),
"{rendered}"
);
assert!(
rendered.contains(&format!("dimension {}", EMBEDDING_DIM - 1)),
"{rendered}"
);
}
#[test]
fn extraction_tensor_bytes_too_large_displays_the_total_and_the_cap() {
use crate::audio::speaker::extract::MAX_EXTRACTION_TENSOR_BYTES;
let rendered =
ExtractError::ExtractionTensorBytesTooLarge(MAX_EXTRACTION_TENSOR_BYTES + 1).to_string();
assert!(
rendered.contains(&(MAX_EXTRACTION_TENSOR_BYTES + 1).to_string()),
"{rendered}"
);
assert!(
rendered.contains("MAX_EXTRACTION_TENSOR_BYTES"),
"{rendered}"
);
assert!(
rendered.contains(&MAX_EXTRACTION_TENSOR_BYTES.to_string()),
"{rendered}"
);
assert!(rendered.contains("chunk grid"), "{rendered}");
}
#[test]
fn extraction_chunk_count_too_large_displays_the_count_and_the_cap() {
use crate::audio::speaker::extract::MAX_EXTRACTION_CHUNKS;
let rendered = ExtractError::ExtractionChunkCountTooLarge(MAX_EXTRACTION_CHUNKS + 1).to_string();
assert!(
rendered.contains(&(MAX_EXTRACTION_CHUNKS + 1).to_string()),
"{rendered}"
);
assert!(rendered.contains("MAX_EXTRACTION_CHUNKS"), "{rendered}");
assert!(
rendered.contains(&MAX_EXTRACTION_CHUNKS.to_string()),
"{rendered}"
);
assert!(rendered.contains("chunks"), "{rendered}");
let bytes = ExtractError::ExtractionTensorBytesTooLarge(1).to_string();
assert_ne!(
rendered.split_whitespace().collect::<Vec<_>>(),
bytes.split_whitespace().collect::<Vec<_>>()
);
}
#[test]
fn calibrate_error_profile_length_displays_got_and_expected() {
use crate::audio::speaker::embed::EMBEDDING_DIM;
let e = CalibrateError::ProfileLength(ProfileLength::new(192, EMBEDDING_DIM));
let rendered = e.to_string();
assert!(rendered.contains("192"), "{rendered}");
assert!(rendered.contains(&EMBEDDING_DIM.to_string()), "{rendered}");
}
#[test]
fn calibrate_error_degenerate_profile_names_the_score_source_that_refused() {
use crate::audio::speaker::calibrate::Scoring;
let cosine = CalibrateError::DegenerateProfile(Scoring::Cosine).to_string();
let plda = CalibrateError::DegenerateProfile(Scoring::PldaCosine).to_string();
assert!(cosine.contains("Cosine"), "{cosine}");
assert!(plda.contains("PldaCosine"), "{plda}");
assert_ne!(cosine, plda);
}
#[test]
fn calibrate_error_wraps_diarics_plda_error_and_coremlits_own_refusal_via_from() {
let e: CalibrateError = diaric::plda::Error::DegenerateInput.into();
assert!(matches!(e, CalibrateError::Plda(_)));
assert!(e.to_string().contains("plda"), "{e}");
let e: CalibrateError = ScoreNormRefusal::EmptyCohort.into();
assert!(matches!(e, CalibrateError::ScoreNorm(_)));
assert!(e.to_string().contains("cohort"), "{e}");
}
#[test]
fn diarics_refusals_arrive_translated_and_carry_none_of_its_numbers() {
use diaric::score_norm::{AsNormOptions, CohortStats};
let default = AsNormOptions::new();
let spread = [0.1_f64, 0.2, 0.3];
let truth = CohortStats::from_scores(spread, &default).expect("a usable cohort");
let deviation = truth.deviation();
let mean = truth.mean();
assert!(deviation > 0.0 && deviation < 2.0);
let refusal = |scores: &[f64], options: &AsNormOptions| {
CohortStats::from_scores(scores.iter().copied(), options)
.expect_err("these scores must be refused")
};
let cases = [
(
refusal(&[], &default),
ScoreNormRefusal::EmptyCohort,
"an empty cohort",
),
(
refusal(&[0.5], &default),
ScoreNormRefusal::CohortTooSmall(CohortSelection::new(1, 9)),
"a one-score cohort",
),
(
refusal(&spread, &default.with_min_deviation(2.0)),
ScoreNormRefusal::DegenerateCohort(CohortSelection::new(3, 9)),
"a cohort under a floor it cannot clear",
),
(
refusal(&[f64::NAN, 0.0, 1.0], &default),
ScoreNormRefusal::NonFiniteScore,
"a non-finite cohort score",
),
];
for (from_diaric, expected, what) in cases {
let leaked = from_diaric.to_string();
let got = ScoreNormRefusal::translate(from_diaric, 9);
assert_eq!(got, expected, "{what} translated from: {leaked}");
let wrapped = CalibrateError::ScoreNorm(got);
let display = wrapped.to_string();
let debug = format!("{wrapped:?}");
for number in [deviation, mean] {
for spelling in [
format!("{number}"),
format!("{number:?}"),
format!("{number:.3e}"),
format!("{number:.6e}"),
] {
assert!(
!display.contains(&spelling),
"{what}: {spelling} in {display}"
);
assert!(!debug.contains(&spelling), "{what}: {spelling} in {debug}");
}
}
assert!(!display.contains("NaN"), "{what}: {display}");
assert!(!debug.contains('.'), "{what}: {debug}");
}
}
#[test]
fn every_sanitized_refusal_reads_as_its_own_category() {
let selection = CohortSelection::new(3, 9);
let all = [
ScoreNormRefusal::EmptyCohort,
ScoreNormRefusal::CohortTooSmall(selection),
ScoreNormRefusal::DegenerateCohort(selection),
ScoreNormRefusal::InvalidMinDeviation,
ScoreNormRefusal::NonFiniteScore,
ScoreNormRefusal::NonFiniteResult,
ScoreNormRefusal::ZScoreCancellation,
];
let mut rendered: Vec<String> = all
.iter()
.map(|r| CalibrateError::ScoreNorm(*r).to_string())
.collect();
let count = rendered.len();
rendered.sort();
rendered.dedup();
assert_eq!(rendered.len(), count, "{rendered:?}");
for refusal in all {
assert!(
!format!("{refusal:?}").contains('.'),
"a refusal carries counts only: {refusal:?}"
);
}
}
#[test]
fn a_sanitized_refusal_still_reports_the_counts_and_the_floor() {
let selection = CohortSelection::new(3, 17);
assert_eq!(selection.selected(), 3);
assert_eq!(selection.considered(), 17);
let too_small =
CalibrateError::ScoreNorm(ScoreNormRefusal::CohortTooSmall(selection)).to_string();
assert!(too_small.contains('3'), "{too_small}");
assert!(too_small.contains("17"), "{too_small}");
assert!(
too_small.contains(&diaric::score_norm::MIN_COHORT_SCORES.to_string()),
"the floor that was breached must be named: {too_small}"
);
let degenerate =
CalibrateError::ScoreNorm(ScoreNormRefusal::DegenerateCohort(selection)).to_string();
assert!(degenerate.contains('3'), "{degenerate}");
assert!(degenerate.contains("17"), "{degenerate}");
assert!(
degenerate.contains("min_deviation"),
"the floor that was breached must be named: {degenerate}"
);
}
#[test]
fn calibrate_error_foreign_speaker_says_it_would_have_excluded_nothing() {
let rendered = CalibrateError::ForeignSpeaker.to_string();
assert!(rendered.contains("token"), "{rendered}");
assert!(rendered.contains("cohort"), "{rendered}");
}
#[test]
fn calibrate_error_plda_transform_unavailable_says_what_cannot_be_projected() {
let rendered = CalibrateError::PldaTransformUnavailable.to_string();
assert!(rendered.contains("PLDA transform"), "{rendered}");
assert!(rendered.contains("PldaCosine"), "{rendered}");
}
#[test]
fn calibrate_error_scoring_mismatch_displays_both_sides_in_order() {
use crate::audio::speaker::calibrate::Scoring;
let m = ScoringMismatch::new(Scoring::Cosine, Scoring::PldaCosine);
assert_eq!(m.side(), Scoring::Cosine);
assert_eq!(m.other(), Scoring::PldaCosine);
let rendered = CalibrateError::ScoringMismatch(m).to_string();
assert!(rendered.contains("Cosine"), "{rendered}");
assert!(rendered.contains("PldaCosine"), "{rendered}");
let reversed =
CalibrateError::ScoringMismatch(ScoringMismatch::new(Scoring::PldaCosine, Scoring::Cosine))
.to_string();
assert_ne!(rendered, reversed);
}
#[test]
fn calibrate_error_calibration_mismatch_names_which_side_was_taken_where() {
use crate::audio::speaker::calibrate::{AsNormOptions, Calibration, HeldOutCohort, Scoring};
let fresh = || {
Calibration::new(
HeldOutCohort::assuming_disjoint(Vec::new()),
AsNormOptions::new(),
)
.id()
};
let expected = fresh();
let enrolled = fresh();
let probe = fresh();
assert_ne!(expected, enrolled);
assert_ne!(enrolled, probe);
let m = CalibrationMismatch::new(expected, enrolled, probe);
assert_eq!(m.expected(), expected);
assert_eq!(m.enrolled(), enrolled);
assert_eq!(m.probe(), probe);
let rendered = CalibrateError::CalibrationMismatch(m).to_string();
assert!(rendered.contains("AS-Norm"), "{rendered}");
assert!(rendered.contains("calibration"), "{rendered}");
let stale_enrolment =
CalibrateError::CalibrationMismatch(CalibrationMismatch::new(probe, enrolled, probe))
.to_string();
let stale_probe =
CalibrateError::CalibrationMismatch(CalibrationMismatch::new(enrolled, enrolled, probe))
.to_string();
assert_ne!(rendered, stale_enrolment);
assert_ne!(rendered, stale_probe);
assert_ne!(stale_enrolment, stale_probe);
let metric =
CalibrateError::ScoringMismatch(ScoringMismatch::new(Scoring::Cosine, Scoring::PldaCosine))
.to_string();
assert_ne!(rendered, metric);
}