#[path = "../../support/workspace_root.rs"]
#[allow(dead_code)]
mod workspace_root;
#[path = "../../support/models_lock_manifest.rs"]
#[allow(dead_code)]
mod models_lock_manifest;
#[allow(unused_imports)]
pub use workspace_root::{checkout_parent, models_root, workspace_root};
use std::path::{Path, PathBuf};
#[allow(dead_code)]
pub const HF_REPO: &str = "aufklarer/SpeechBrain-ECAPA-VoxLingua107-21M-CoreML";
#[allow(dead_code)]
pub const HF_REVISION: &str = "2aa4d715a79e410d5f9aa32bd7a4fc9225bf9eb0";
#[allow(dead_code)]
pub const SOURCE_MODEL: &str = "speechbrain/lang-id-voxlingua107-ecapa";
#[allow(dead_code)]
pub const SOURCE_REVISION: &str = "0253049ae131d6a4be1c4f0d8b0ff483a0f8c8e9";
#[allow(dead_code)]
pub const BUNDLE_NAME: &str = "SpeechBrainECAPAVoxLingua107.mlmodelc";
#[allow(dead_code)]
pub const VENDOR_DIR: &str = "lid";
#[allow(dead_code)]
pub fn artifact_sha256() -> Vec<(String, String)> {
models_lock_manifest::bundle_manifest(
&workspace_root::workspace_root(),
VENDOR_DIR,
HF_REVISION,
BUNDLE_NAME,
)
}
#[allow(dead_code)]
pub fn models_dir() -> PathBuf {
std::env::var_os("LID_TEST_MODELS")
.map_or_else(|| workspace_root::models_root().join("lid"), PathBuf::from)
}
#[allow(dead_code)]
pub fn model_path() -> PathBuf {
models_dir().join(BUNDLE_NAME)
}
#[allow(dead_code)]
pub fn fixture_path(relative: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("lid")
.join("fixtures")
.join(relative)
}
#[allow(dead_code)]
pub fn sha256_hex(bytes: &[u8]) -> String {
use core::fmt::Write;
use sha2::{Digest, Sha256};
Sha256::digest(bytes)
.iter()
.fold(String::new(), |mut acc, b| {
let _ = write!(acc, "{b:02x}");
acc
})
}
#[allow(dead_code)]
pub fn sha256_file(path: &Path) -> String {
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {path:?}: {e}"));
sha256_hex(&bytes)
}
#[allow(dead_code)]
pub fn read_wav_16k_mono(path: &Path) -> Vec<f32> {
let mut reader =
hound::WavReader::open(path).unwrap_or_else(|e| panic!("open {}: {e}", path.display()));
let spec = reader.spec();
assert_eq!(
spec.sample_rate,
16_000,
"{}: must be 16 kHz",
path.display()
);
assert_eq!(spec.channels, 1, "{}: must be mono", path.display());
match spec.sample_format {
hound::SampleFormat::Int => {
let scale = 1.0 / (1_i64 << (spec.bits_per_sample - 1)) as f32;
reader
.samples::<i32>()
.map(|s| s.expect("decode sample") as f32 * scale)
.collect()
}
hound::SampleFormat::Float => reader
.samples::<f32>()
.map(|s| s.expect("decode sample"))
.collect(),
}
}
#[allow(dead_code)]
pub fn collect_files_rel(dir: &Path, prefix: &str, out: &mut Vec<String>) {
let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {dir:?}: {e}"));
for entry in entries {
let entry = entry.unwrap_or_else(|e| panic!("dir entry under {dir:?}: {e}"));
let name = entry.file_name().to_string_lossy().into_owned();
if name.starts_with("._") || name == ".DS_Store" {
continue;
}
let rel = if prefix.is_empty() {
name
} else {
format!("{prefix}/{name}")
};
let file_type = entry
.file_type()
.unwrap_or_else(|e| panic!("file_type {:?}: {e}", entry.path()));
if file_type.is_dir() {
collect_files_rel(&entry.path(), &rel, out);
} else {
out.push(rel);
}
}
}
#[allow(dead_code)]
pub fn assert_exact_sha_manifest(dir: &Path, cases: &[(String, String)]) {
use std::collections::BTreeSet;
let mut found = Vec::new();
collect_files_rel(dir, "", &mut found);
let on_disk: BTreeSet<String> = found.into_iter().collect();
let pinned: BTreeSet<String> = cases.iter().map(|(rel, _)| rel.clone()).collect();
if on_disk != pinned {
let missing: Vec<&String> = pinned.difference(&on_disk).collect();
let extra: Vec<&String> = on_disk.difference(&pinned).collect();
panic!(
"artifact manifest mismatch under {dir:?}:\n \
missing (pinned but not on disk): {missing:?}\n \
extra (on disk but not pinned): {extra:?}"
);
}
for (relative, expected) in cases {
assert_eq!(
&sha256_file(&dir.join(relative)),
expected,
"sha256 drift on artifact {relative} under {dir:?}"
);
}
}
#[allow(dead_code)]
pub const GRAPH_DEFAULT_SHAPE_FRAMES: usize = 301;
#[allow(dead_code)]
pub const GRAPH_DEFAULT_SHAPE_SAMPLES: usize = 48_000;
#[allow(dead_code)]
fn default_shape_refusal() -> Option<&'static String> {
use std::sync::OnceLock;
use coremlit::audio::lid::{DEFAULT_WINDOW_SAMPLES, Error, Identifier};
static PROBE: OnceLock<Option<String>> = OnceLock::new();
PROBE
.get_or_init(|| {
assert_eq!(
coremlit::audio::lid::frame_count(GRAPH_DEFAULT_SHAPE_SAMPLES),
GRAPH_DEFAULT_SHAPE_FRAMES,
"the probe's sample count and frame count disagree"
);
let identifier = Identifier::from_file(model_path())
.unwrap_or_else(|e| panic!("host-capability probe: load identifier: {e}"));
let samples = read_wav_16k_mono(&fixture_path("audio/udhr_th_16k.wav"));
identifier
.log_probabilities(&samples[..DEFAULT_WINDOW_SAMPLES as usize])
.unwrap_or_else(|e| {
panic!(
"host-capability probe: this host cannot predict at the door's own \
default window ({DEFAULT_WINDOW_SAMPLES} samples): {e} — that is a \
broken model or a broken host, not the narrow default-shape \
refusal this probe excuses, so it must red"
)
});
match identifier.log_probabilities(&samples[..GRAPH_DEFAULT_SHAPE_SAMPLES]) {
Ok(_) => None,
Err(e @ Error::Prediction(_)) => Some(format!(
"this host's CoreML refuses to predict at the graph's own \
DefaultShapes [1, {GRAPH_DEFAULT_SHAPE_FRAMES}, 60] \
({GRAPH_DEFAULT_SHAPE_SAMPLES} samples, 3 s) under the door's \
default placement, while answering normally at the default \
{DEFAULT_WINDOW_SAMPLES}-sample window: {e}"
)),
Err(e) => panic!(
"host-capability probe: predicting at the graph's default shape failed \
with {e} — only a CoreML `Prediction` refusal is a host limitation, \
so this reds"
),
}
})
.as_ref()
}
#[allow(dead_code)]
pub fn skipped_for_the_default_shape_refusal(gate: &str) -> bool {
use std::io::Write;
let Some(reason) = default_shape_refusal() else {
return false;
};
let line = format!("model-gates | SKIPPED {gate}: {reason}\n");
let mut fd2 = std::mem::ManuallyDrop::new(unsafe {
<std::fs::File as std::os::fd::FromRawFd>::from_raw_fd(2)
});
let _ = fd2.write_all(line.as_bytes());
true
}
#[path = "../../support/model_gate_report.rs"]
mod model_gate_report;
#[test]
fn model_gate_report() {
model_gate_report::report(&[("LID_TEST_MODELS", models_dir())]);
}