#![allow(dead_code)]
use embellama::{EngineConfig, NormalizationMode, PoolingStrategy};
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
#[must_use]
pub fn create_dummy_model() -> (TempDir, PathBuf) {
let dir = TempDir::new().expect("Failed to create temp dir");
let model_path = dir.path().join("test_model.gguf");
fs::write(&model_path, b"GGUF\x00\x00\x00\x04dummy_model_content")
.expect("Failed to write dummy model");
(dir, model_path)
}
#[must_use]
pub fn create_test_config(model_path: PathBuf) -> EngineConfig {
EngineConfig::builder()
.with_model_path(model_path)
.with_model_name("test-model")
.with_n_threads(2)
.with_normalization_mode(NormalizationMode::L2)
.with_pooling_strategy(PoolingStrategy::Mean)
.build()
.expect("Failed to create test config")
}
pub fn get_test_model_path() -> Option<PathBuf> {
std::env::var("EMBELLAMA_TEST_MODEL")
.ok()
.map(PathBuf::from)
.filter(|p| p.exists())
}
#[must_use]
pub fn should_run_model_tests() -> bool {
get_test_model_path().is_some()
}
pub fn init_test_logger() {
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("embellama=debug".parse().unwrap()),
)
.with_test_writer()
.init();
});
}
#[must_use]
pub fn generate_sample_texts(count: usize) -> Vec<String> {
(0..count)
.map(|i| format!("Sample text number {i} for testing embeddings"))
.collect()
}
pub fn assert_embeddings_equal(emb1: &[f32], emb2: &[f32], tolerance: f32) {
assert_eq!(
emb1.len(),
emb2.len(),
"Embeddings have different dimensions"
);
for (i, (a, b)) in emb1.iter().zip(emb2.iter()).enumerate() {
let diff = (a - b).abs();
assert!(
diff < tolerance,
"Embedding values differ at index {i}: {a} vs {b} (diff: {diff})"
);
}
}
#[must_use]
pub fn calculate_l2_norm(embedding: &[f32]) -> f32 {
embedding.iter().map(|x| x * x).sum::<f32>().sqrt()
}
#[must_use]
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
assert_eq!(a.len(), b.len(), "Vectors must have same dimension");
let dot: f32 = a.iter().zip(b).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot / (norm_a * norm_b)
}
#[must_use]
pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
assert_eq!(a.len(), b.len(), "Vectors must have same dimension");
a.iter().zip(b).map(|(x, y)| x * y).sum()
}
pub fn assert_embeddings_identical(a: &[f32], b: &[f32]) {
assert_eq!(a.len(), b.len(), "Embeddings have different dimensions");
for (i, (x, y)) in a.iter().zip(b).enumerate() {
assert_eq!(x, y, "Embeddings differ at index {}: {} != {}", i, x, y);
}
}
pub fn assert_normalized_strict(embedding: &[f32]) {
let norm = calculate_l2_norm(embedding);
const STRICT_TOLERANCE: f32 = 1e-6;
assert!(
(norm - 1.0).abs() < STRICT_TOLERANCE,
"Embedding not normalized: L2 norm = {} (expected 1.0 ± {})",
norm,
STRICT_TOLERANCE
);
}
#[must_use]
pub fn generate_text_with_approx_tokens(target_tokens: usize) -> String {
let chars_needed = target_tokens * 13 / 10;
"The quick brown fox jumps over the lazy dog. This is a test sentence for context size testing. "
.repeat(chars_needed / 95)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_dummy_model() {
let (_dir, model_path) = create_dummy_model();
assert!(model_path.exists());
assert!(
model_path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("gguf"))
);
}
#[test]
fn test_generate_sample_texts() {
let texts = generate_sample_texts(5);
assert_eq!(texts.len(), 5);
assert!(texts[0].contains("Sample text number 0"));
assert!(texts[4].contains("Sample text number 4"));
}
#[test]
fn test_l2_norm_calculation() {
let embedding = vec![0.6, 0.8]; let norm = calculate_l2_norm(&embedding);
assert!((norm - 1.0).abs() < 0.001);
}
#[test]
fn test_assert_embeddings_equal() {
let emb1 = vec![0.1, 0.2, 0.3];
let emb2 = vec![0.1001, 0.2001, 0.3001];
assert_embeddings_equal(&emb1, &emb2, 0.001);
}
#[test]
fn test_generate_text_with_approx_tokens() {
let text = generate_text_with_approx_tokens(1000);
assert!(text.len() > 1000, "Text should be at least 1000 chars");
assert!(text.len() < 2000, "Text should be less than 2000 chars");
}
#[test]
fn test_cosine_similarity() {
let a = vec![0.6, 0.8];
let b = vec![0.8, 0.6];
let sim = cosine_similarity(&a, &b);
assert!((sim - 0.96).abs() < 0.01);
let c = vec![1.0, 0.0, 0.0];
assert!((cosine_similarity(&c, &c) - 1.0).abs() < 0.001);
let d = vec![1.0, 0.0];
let e = vec![0.0, 1.0];
assert!(cosine_similarity(&d, &e).abs() < 0.001);
}
#[test]
fn test_dot_product() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let result = dot_product(&a, &b);
assert!((result - 32.0).abs() < 0.001); }
#[test]
fn test_dot_product_equals_cosine_for_normalized() {
let a = vec![0.6, 0.8]; let b = vec![0.8, 0.6];
let dot = dot_product(&a, &b);
let cos = cosine_similarity(&a, &b);
assert!((dot - cos).abs() < 0.001);
}
#[test]
fn test_assert_embeddings_identical_passes() {
let a = vec![0.1, 0.2, 0.3];
let b = vec![0.1, 0.2, 0.3];
assert_embeddings_identical(&a, &b); }
#[test]
#[should_panic(expected = "Embeddings differ")]
fn test_assert_embeddings_identical_fails() {
let a = vec![0.1, 0.2, 0.3];
let b = vec![0.1, 0.2, 0.30001];
assert_embeddings_identical(&a, &b); }
#[test]
fn test_assert_normalized_strict_passes() {
let embedding = vec![0.6, 0.8]; assert_normalized_strict(&embedding); }
#[test]
#[should_panic(expected = "not normalized")]
fn test_assert_normalized_strict_fails() {
let embedding = vec![1.0, 1.0]; assert_normalized_strict(&embedding); }
}