use std::path::{Path, PathBuf};
use std::sync::OnceLock;
pub const ENV_TEST_MODELS: &str = "XYBRID_TEST_MODELS";
static MODELS_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
fn find_models_dir() -> Option<PathBuf> {
if let Ok(env_path) = std::env::var(ENV_TEST_MODELS) {
let path = PathBuf::from(env_path);
if path.exists() {
return Some(path);
}
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;
let manifest_path = PathBuf::from(&manifest_dir);
let candidates = [
manifest_path.join("../../integration-tests/fixtures/models"),
manifest_path.join("../integration-tests/fixtures/models"),
];
for fixtures_path in &candidates {
if fixtures_path.exists() && fixtures_path.is_dir() {
if let Ok(canonical) = fixtures_path.canonicalize() {
return Some(canonical);
}
return Some(fixtures_path.clone());
}
}
None
}
pub fn models_dir() -> Option<&'static PathBuf> {
MODELS_DIR.get_or_init(find_models_dir).as_ref()
}
pub fn model_path(model_name: &str) -> Option<PathBuf> {
let models = models_dir()?;
let path = models.join(model_name);
if path.exists() && path.is_dir() {
Some(path)
} else {
None
}
}
fn has_model_binaries(dir: &Path) -> bool {
const MODEL_EXTENSIONS: &[&str] = &["onnx", "safetensors", "gguf"];
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
if let Some(ext) = entry.path().extension() {
if MODEL_EXTENSIONS.iter().any(|e| ext == *e) {
return true;
}
}
}
}
false
}
pub fn model_available(model_name: &str) -> bool {
model_path(model_name)
.map(|p| has_model_binaries(&p))
.unwrap_or(false)
}
pub fn require_model(model_name: &str) -> PathBuf {
if let Some(path) = model_path(model_name) {
if has_model_binaries(&path) {
return path;
}
}
let models_dir_info = models_dir()
.map(|p| format!("Models directory: {}", p.display()))
.unwrap_or_else(|| "Models directory: NOT FOUND".to_string());
panic!(
r#"
Model '{}' not found!
{}
To download test models, run:
./integration-tests/download.sh {}
Or download all models:
./integration-tests/download.sh --all
You can also set XYBRID_TEST_MODELS environment variable to a custom path.
"#,
model_name, models_dir_info, model_name
);
}
pub fn model_or_skip(model_name: &str) -> Option<PathBuf> {
if let Some(path) = model_path(model_name) {
if has_model_binaries(&path) {
return Some(path);
}
}
eprintln!(
"Skipping test: model '{}' not downloaded. Run: ./integration-tests/download.sh {}",
model_name, model_name
);
None
}
pub fn fixtures_dir() -> Option<PathBuf> {
models_dir().and_then(|m| m.parent().map(|p| p.to_path_buf()))
}
pub fn list_available_models() -> Vec<String> {
let Some(models) = models_dir() else {
return vec![];
};
let Ok(entries) = std::fs::read_dir(models) else {
return vec![];
};
entries
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.filter(|e| has_model_binaries(&e.path()))
.filter_map(|e| e.file_name().into_string().ok())
.collect()
}
pub fn input_dir() -> Option<PathBuf> {
fixtures_dir().map(|f| f.join("input"))
}
pub fn test_audio(filename: &str) -> Option<PathBuf> {
input_dir().map(|d| d.join(filename)).filter(|p| p.exists())
}
pub fn default_test_audio() -> Option<PathBuf> {
test_audio("test_audio.wav")
}
pub fn test_text(filename: &str) -> Option<PathBuf> {
input_dir().map(|d| d.join(filename)).filter(|p| p.exists())
}
pub fn pipelines_dir() -> Option<PathBuf> {
fixtures_dir().map(|f| f.join("pipelines"))
}
pub fn pipeline(filename: &str) -> Option<PathBuf> {
pipelines_dir()
.map(|d| d.join(filename))
.filter(|p| p.exists())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_models_dir_found() {
if let Some(dir) = models_dir() {
assert!(dir.exists(), "Models directory should exist: {:?}", dir);
assert!(dir.is_dir(), "Models path should be a directory");
}
}
#[test]
fn test_model_available_returns_false_for_nonexistent() {
assert!(!model_available("nonexistent-model-xyz"));
}
#[test]
fn test_model_path_returns_none_for_nonexistent() {
assert!(model_path("nonexistent-model-xyz").is_none());
}
#[test]
fn test_list_available_models() {
let models = list_available_models();
drop(models);
}
#[test]
fn test_model_or_skip_returns_none_for_missing() {
let result = model_or_skip("definitely-not-a-real-model");
assert!(result.is_none());
}
}