#[cfg(feature = "onnx-inspect")]
use std::path::PathBuf;
use xybrid_core::execution::ExecutionTemplate;
#[cfg(feature = "onnx-inspect")]
use xybrid_core::execution::{PostprocessingStep, PreprocessingStep};
#[cfg(feature = "onnx-inspect")]
fn fixtures_dir() -> PathBuf {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest
.join("../../integration-tests/fixtures/models")
.canonicalize()
.expect("fixtures/models/ directory must exist")
}
#[cfg(feature = "onnx-inspect")]
fn copy_fixture_to_temp(fixture_name: &str, files: &[&str]) -> Option<tempfile::TempDir> {
let src = fixtures_dir().join(fixture_name);
let tmp = tempfile::TempDir::new().expect("create temp dir");
for file in files {
let src_file = src.join(file);
if !src_file.exists() {
let is_model = file.ends_with(".onnx")
|| file.ends_with(".gguf")
|| file.ends_with(".safetensors");
if is_model {
eprintln!(
"Skipping test: {} not found (run ./integration-tests/download.sh {})",
src_file.display(),
fixture_name
);
return None;
}
continue;
}
std::fs::copy(&src_file, tmp.path().join(file))
.unwrap_or_else(|e| panic!("Failed to copy {}: {}", src_file.display(), e));
}
Some(tmp)
}
#[test]
#[cfg(feature = "onnx-inspect")]
fn test_mnist_fixture_generates_image_classification() {
let Some(tmp) = copy_fixture_to_temp("mnist", &["model.onnx"]) else {
return;
};
let (metadata, task_inference) =
xybrid_sdk::metadata_gen::inspect_and_generate(tmp.path(), "", None)
.expect("inspect_and_generate should succeed for MNIST");
match &metadata.execution_template {
ExecutionTemplate::Onnx { model_file } => {
assert_eq!(model_file, "model.onnx");
}
other => panic!("Expected Onnx template, got {:?}", other),
}
let task = metadata
.metadata
.get("task")
.and_then(|v| v.as_str())
.expect("task metadata should exist");
assert_eq!(task, "image-classification");
assert!(
metadata
.preprocessing
.iter()
.any(|s| matches!(s, PreprocessingStep::Normalize { .. })),
"Expected Normalize preprocessing, got: {:?}",
metadata.preprocessing
);
assert!(
metadata
.postprocessing
.iter()
.any(|s| matches!(s, PostprocessingStep::Softmax { .. })),
"Expected Softmax postprocessing, got: {:?}",
metadata.postprocessing
);
assert!(metadata.files.contains(&"model.onnx".to_string()));
assert!(!metadata.model_id.is_empty());
let ti = task_inference.expect("TaskInference should be Some for ONNX models");
assert_eq!(ti.task, "image-classification");
}
#[test]
fn test_gguf_fixture_generates_correct_template() {
use std::io::Write;
let tmp = tempfile::TempDir::new().unwrap();
let gguf_path = tmp.path().join("test-model-Q4_K_M.gguf");
let mut f = std::fs::File::create(&gguf_path).unwrap();
f.write_all(b"GGUF").unwrap();
f.write_all(&3u32.to_le_bytes()).unwrap();
f.write_all(&0u64.to_le_bytes()).unwrap();
f.write_all(&2u64.to_le_bytes()).unwrap();
write_gguf_string(&mut f, "general.architecture");
f.write_all(&8u32.to_le_bytes()).unwrap(); write_gguf_string(&mut f, "llama");
write_gguf_string(&mut f, "llama.context_length");
f.write_all(&4u32.to_le_bytes()).unwrap(); f.write_all(&4096u32.to_le_bytes()).unwrap();
drop(f);
let (metadata, _task_inference) =
xybrid_sdk::metadata_gen::inspect_and_generate(tmp.path(), "", None)
.expect("inspect_and_generate should succeed for GGUF");
match &metadata.execution_template {
ExecutionTemplate::Gguf {
model_file,
context_length,
..
} => {
assert_eq!(model_file, "test-model-Q4_K_M.gguf");
assert_eq!(*context_length, 4096);
}
other => panic!("Expected Gguf template, got {:?}", other),
}
assert!(
metadata.preprocessing.is_empty(),
"GGUF should have no preprocessing, got: {:?}",
metadata.preprocessing
);
assert!(
metadata.postprocessing.is_empty(),
"GGUF should have no postprocessing, got: {:?}",
metadata.postprocessing
);
assert_eq!(
metadata
.metadata
.get("architecture")
.and_then(|v| v.as_str()),
Some("llama")
);
assert_eq!(
metadata
.metadata
.get("quantization")
.and_then(|v| v.as_str()),
Some("Q4_K_M")
);
}
fn write_gguf_string(f: &mut std::fs::File, s: &str) {
use std::io::Write;
f.write_all(&(s.len() as u64).to_le_bytes()).unwrap();
f.write_all(s.as_bytes()).unwrap();
}
#[test]
#[cfg(feature = "onnx-inspect")]
fn test_all_minilm_fixture_generates_tokenize_and_meanpool() {
let Some(tmp) = copy_fixture_to_temp(
"all-minilm",
&["model.onnx", "tokenizer.json", "config.json", "vocab.txt"],
) else {
return;
};
let (metadata, task_inference) =
xybrid_sdk::metadata_gen::inspect_and_generate(tmp.path(), "", None)
.expect("inspect_and_generate should succeed for all-MiniLM");
match &metadata.execution_template {
ExecutionTemplate::Onnx { model_file } => {
assert_eq!(model_file, "model.onnx");
}
other => panic!("Expected Onnx template, got {:?}", other),
}
let has_tokenize = metadata
.preprocessing
.iter()
.any(|s| matches!(s, PreprocessingStep::Tokenize { .. }));
assert!(
has_tokenize,
"Expected Tokenize preprocessing, got: {:?}",
metadata.preprocessing
);
for step in &metadata.preprocessing {
if let PreprocessingStep::Tokenize {
vocab_file,
max_length,
..
} = step
{
assert!(
vocab_file == "tokenizer.json" || vocab_file == "vocab.txt",
"Unexpected vocab_file: {}",
vocab_file
);
assert_eq!(
*max_length,
Some(512),
"max_length should be 512 from config.json max_position_embeddings"
);
}
}
let has_meanpool = metadata
.postprocessing
.iter()
.any(|s| matches!(s, PostprocessingStep::MeanPool { .. }));
assert!(
has_meanpool,
"Expected MeanPool postprocessing, got: {:?}",
metadata.postprocessing
);
assert!(metadata.files.contains(&"model.onnx".to_string()));
assert!(metadata.files.contains(&"tokenizer.json".to_string()));
assert!(metadata.files.contains(&"config.json".to_string()));
let ti = task_inference.expect("TaskInference should be Some for ONNX models");
assert!(
ti.task == "feature-extraction" || ti.task == "sentence-similarity",
"Expected feature-extraction or sentence-similarity task, got: {}",
ti.task
);
}
#[test]
#[cfg(feature = "onnx-inspect")]
fn test_generate_metadata_writes_valid_json_for_mnist() {
use xybrid_core::execution::ModelMetadata;
let Some(tmp) = copy_fixture_to_temp("mnist", &["model.onnx"]) else {
return;
};
let (metadata, _) = xybrid_sdk::metadata_gen::generate_metadata(tmp.path(), "")
.expect("generate_metadata should succeed for MNIST");
let metadata_path = tmp.path().join("model_metadata.json");
assert!(
metadata_path.exists(),
"model_metadata.json should be written"
);
let json = std::fs::read_to_string(&metadata_path).unwrap();
let parsed: ModelMetadata =
serde_json::from_str(&json).expect("Written model_metadata.json should be valid JSON");
assert_eq!(parsed.model_id, metadata.model_id);
assert_eq!(parsed.version, metadata.version);
assert_eq!(parsed.files, metadata.files);
assert_eq!(parsed.preprocessing.len(), metadata.preprocessing.len());
assert_eq!(parsed.postprocessing.len(), metadata.postprocessing.len());
}
#[test]
fn test_generate_metadata_writes_valid_json_for_gguf() {
use std::io::Write;
use xybrid_core::execution::ModelMetadata;
let tmp = tempfile::TempDir::new().unwrap();
let gguf_path = tmp.path().join("model-Q8_0.gguf");
let mut f = std::fs::File::create(&gguf_path).unwrap();
f.write_all(b"GGUF").unwrap();
f.write_all(&3u32.to_le_bytes()).unwrap();
f.write_all(&0u64.to_le_bytes()).unwrap();
f.write_all(&2u64.to_le_bytes()).unwrap();
write_gguf_string(&mut f, "general.architecture");
f.write_all(&8u32.to_le_bytes()).unwrap();
write_gguf_string(&mut f, "qwen2");
write_gguf_string(&mut f, "qwen2.context_length");
f.write_all(&4u32.to_le_bytes()).unwrap();
f.write_all(&32768u32.to_le_bytes()).unwrap();
drop(f);
let (metadata, _) = xybrid_sdk::metadata_gen::generate_metadata(tmp.path(), "")
.expect("generate_metadata should succeed for GGUF");
let metadata_path = tmp.path().join("model_metadata.json");
assert!(metadata_path.exists());
let parsed: ModelMetadata =
serde_json::from_str(&std::fs::read_to_string(&metadata_path).unwrap())
.expect("Written model_metadata.json should parse");
match &parsed.execution_template {
ExecutionTemplate::Gguf { context_length, .. } => {
assert_eq!(*context_length, 32768);
}
other => panic!("Expected Gguf template, got {:?}", other),
}
assert!(parsed.preprocessing.is_empty());
assert!(parsed.postprocessing.is_empty());
assert_eq!(parsed.model_id, metadata.model_id);
}
#[test]
#[cfg(feature = "onnx-inspect")]
fn test_model_id_derived_from_directory_name() {
let src = fixtures_dir().join("mnist/model.onnx");
if !src.exists() {
eprintln!("Skipping test: mnist model.onnx not downloaded");
eprintln!("Run: ./integration-tests/download.sh mnist");
return;
}
let parent = tempfile::TempDir::new().unwrap();
let model_dir = parent.path().join("My Custom_Model.v2");
std::fs::create_dir_all(&model_dir).unwrap();
std::fs::copy(&src, model_dir.join("model.onnx")).unwrap();
let (metadata, _) = xybrid_sdk::metadata_gen::inspect_and_generate(&model_dir, "", None)
.expect("inspect_and_generate should succeed");
assert_eq!(metadata.model_id, "my-custom-model.v2");
}
#[test]
#[cfg(feature = "onnx-inspect")]
fn test_model_id_override() {
let Some(tmp) = copy_fixture_to_temp("mnist", &["model.onnx"]) else {
return;
};
let (metadata, _) =
xybrid_sdk::metadata_gen::inspect_and_generate(tmp.path(), "", Some("custom-id"))
.expect("inspect_and_generate with model_id override should succeed");
assert_eq!(metadata.model_id, "custom-id");
}
#[test]
fn test_empty_directory_returns_error() {
let tmp = tempfile::TempDir::new().unwrap();
std::fs::write(tmp.path().join("readme.txt"), "not a model").unwrap();
let result = xybrid_sdk::metadata_gen::inspect_and_generate(tmp.path(), "", None);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("No model files"),
"Expected 'No model files' error, got: {}",
err
);
}