use embellama::cache::CacheStore;
use embellama::cache::embedding_cache::EmbeddingCache;
use embellama::{NormalizationMode, PoolingStrategy};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
#[test]
fn test_cache_basic_operations() {
let cache = EmbeddingCache::new(100, 3600);
let key = "test_key".to_string();
let embedding = vec![0.1, 0.2, 0.3, 0.4, 0.5];
cache.insert(key.clone(), embedding.clone());
let retrieved = cache.get(&key);
assert_eq!(retrieved, Some(embedding.clone()));
let stats = cache.stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 0);
let missing_key = "missing_key".to_string();
let result = cache.get(&missing_key);
assert_eq!(result, None);
let stats = cache.stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
}
#[test]
fn test_cache_key_computation() {
let text = "Hello, world!";
let model = "test-model";
let pooling = PoolingStrategy::Mean;
let normalization = NormalizationMode::L2;
let key1 = EmbeddingCache::compute_key(text, model, pooling, normalization);
let key2 = EmbeddingCache::compute_key(text, model, pooling, normalization);
assert_eq!(key1, key2);
let key3 = EmbeddingCache::compute_key("Different text", model, pooling, normalization);
assert_ne!(key1, key3);
let key4 = EmbeddingCache::compute_key(text, "different-model", pooling, normalization);
assert_ne!(key1, key4);
let key5 = EmbeddingCache::compute_key(text, model, PoolingStrategy::Cls, normalization);
assert_ne!(key1, key5);
let key6 = EmbeddingCache::compute_key(text, model, pooling, NormalizationMode::None);
assert_ne!(key1, key6);
}
#[test_log::test]
fn test_cache_clear() {
let cache = EmbeddingCache::new(100, 3600);
for i in 0..10 {
let key = format!("key_{}", i);
let embedding = vec![i as f32; 5];
cache.insert(key, embedding);
}
let stats_before = cache.stats();
assert!(stats_before.entry_count > 0);
cache.clear();
let stats_after = cache.stats();
assert_eq!(stats_after.entry_count, 0);
for i in 0..10 {
let key = format!("key_{}", i);
assert_eq!(cache.get(&key), None);
}
}
#[test]
fn test_cache_capacity_limit() {
let cache = EmbeddingCache::new(5, 3600);
for i in 0..10 {
let key = format!("key_{}", i);
let embedding = vec![i as f32; 5];
cache.insert(key, embedding);
}
let stats = cache.stats();
assert!(stats.entry_count <= 5);
}
#[test]
fn test_cache_warm_up() {
let cache = EmbeddingCache::new(100, 3600);
let entries: Vec<(String, Vec<f32>)> = (0..5)
.map(|i| (format!("warm_key_{}", i), vec![i as f32; 5]))
.collect();
cache.warm_cache(entries.clone());
for (key, expected_value) in entries {
let retrieved = cache.get(&key);
assert_eq!(retrieved, Some(expected_value));
}
}
#[test]
fn test_cache_thread_safety() {
let cache = Arc::new(EmbeddingCache::new(1000, 3600));
let mut handles = vec![];
for thread_id in 0..10 {
let cache_clone = Arc::clone(&cache);
let handle = thread::spawn(move || {
for i in 0..100 {
let key = format!("thread_{}_key_{}", thread_id, i);
let embedding = vec![thread_id as f32, i as f32];
cache_clone.insert(key.clone(), embedding.clone());
let retrieved = cache_clone.get(&key);
assert_eq!(retrieved, Some(embedding));
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread panicked");
}
let stats = cache.stats();
assert!(stats.entry_count > 0);
assert!(stats.hits > 0);
}
#[test]
fn test_cache_memory_tracking() {
let cache = EmbeddingCache::new(100, 3600);
let embedding_small = vec![0.1; 10]; let embedding_large = vec![0.2; 1000];
cache.insert("small".to_string(), embedding_small);
cache.insert("large".to_string(), embedding_large);
let stats = cache.stats();
assert!(stats.memory_bytes > 4040); }
#[test]
fn test_cache_stats_accuracy() {
let cache = EmbeddingCache::new(100, 3600);
cache.insert("key1".to_string(), vec![1.0]);
cache.insert("key2".to_string(), vec![2.0]);
cache.insert("key3".to_string(), vec![3.0]);
cache.get(&"key1".to_string());
cache.get(&"key2".to_string());
cache.get(&"missing1".to_string());
cache.get(&"missing2".to_string());
cache.get(&"missing3".to_string());
let stats = cache.stats();
assert_eq!(stats.hits, 2);
assert_eq!(stats.misses, 3);
assert!(stats.entry_count > 0);
let expected_hit_rate = 2.0 / 5.0; assert!((stats.hit_rate - expected_hit_rate).abs() < 0.001);
}
#[test]
fn test_embedding_cache_with_real_embeddings() {
let cache = EmbeddingCache::new(100, 3600);
let embedding_384 = vec![0.1; 384];
let embedding_768 = vec![0.2; 768];
let embedding_1024 = vec![0.3; 1024];
cache.insert("bert-base".to_string(), embedding_384.clone());
cache.insert("bert-large".to_string(), embedding_768.clone());
cache.insert("large-model".to_string(), embedding_1024.clone());
assert_eq!(cache.get(&"bert-base".to_string()), Some(embedding_384));
assert_eq!(cache.get(&"bert-large".to_string()), Some(embedding_768));
assert_eq!(cache.get(&"large-model".to_string()), Some(embedding_1024));
let stats = cache.stats();
let expected_min_memory = (384 + 768 + 1024) * 4; assert!(stats.memory_bytes >= expected_min_memory as u64);
}
#[test]
#[ignore = "Takes ~2 seconds due to sleep for TTL expiration"]
fn test_cache_ttl_expiration() {
let cache = EmbeddingCache::new(100, 1);
cache.insert("ephemeral".to_string(), vec![1.0, 2.0, 3.0]);
assert!(cache.get(&"ephemeral".to_string()).is_some());
thread::sleep(Duration::from_secs(2));
cache.inner_cache().run_pending_tasks();
assert!(
cache.inner_cache().get(&"ephemeral".to_string()).is_none(),
"Entry should have expired from shared cache after TTL"
);
}