#[cfg(feature = "cache")]
mod cache_tests {
use sdforge::cache::{DashMapCache, SharedCache, SyncCache};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
#[test]
fn test_cache_set_and_get() {
let cache = DashMapCache::new();
cache.set("name", b"Alice".to_vec());
assert_eq!(cache.get("name"), Some(b"Alice".to_vec()));
cache.set("age", b"30".to_vec());
assert_eq!(cache.get("age"), Some(b"30".to_vec()));
cache.set("user", br#"{"id":1,"name":"Bob"}"#.to_vec());
assert_eq!(
cache.get("user"),
Some(br#"{"id":1,"name":"Bob"}"#.to_vec())
);
cache.set("empty", b"".to_vec());
assert_eq!(cache.get("empty"), Some(b"".to_vec()));
}
#[test]
fn test_cache_get_nonexistent_key() {
let cache = DashMapCache::new();
assert_eq!(cache.get("nonexistent"), None);
assert!(cache.get("missing_key").is_none());
cache.set("temp", b"value".to_vec());
assert_eq!(cache.get("temp"), Some(b"value".to_vec()));
cache.delete("temp");
assert_eq!(cache.get("temp"), None);
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
cache.clear();
assert_eq!(cache.get("k1"), None);
assert_eq!(cache.get("k2"), None);
}
#[test]
fn test_cache_update_existing_key() {
let cache = DashMapCache::new();
cache.set("counter", b"1".to_vec());
assert_eq!(cache.get("counter"), Some(b"1".to_vec()));
cache.set("counter", b"2".to_vec());
assert_eq!(cache.get("counter"), Some(b"2".to_vec()));
cache.set("counter", b"100".to_vec());
assert_eq!(cache.get("counter"), Some(b"100".to_vec()));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_cache_delete_key() {
let cache = DashMapCache::new();
assert!(!cache.delete("nonexistent"));
assert_eq!(cache.len(), 0);
cache.set("key1", b"value1".to_vec());
cache.set("key2", b"value2".to_vec());
assert_eq!(cache.len(), 2);
assert!(cache.delete("key1"));
assert_eq!(cache.get("key1"), None);
assert_eq!(cache.get("key2"), Some(b"value2".to_vec()));
assert_eq!(cache.len(), 1);
assert!(cache.delete("key2"));
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert!(!cache.delete("key1"));
}
#[test]
fn test_cache_clear_all() {
let cache = DashMapCache::new();
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
cache.set("k3", b"v3".to_vec());
assert_eq!(cache.len(), 3);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
assert_eq!(cache.get("k1"), None);
assert_eq!(cache.get("k2"), None);
assert_eq!(cache.get("k3"), None);
cache.set("new_key", b"new_value".to_vec());
assert_eq!(cache.get("new_key"), Some(b"new_value".to_vec()));
}
#[test]
fn test_cache_ttl_expiration() {
use std::time::{SystemTime, UNIX_EPOCH};
let cache = DashMapCache::new();
let ttl_seconds: u64 = 1;
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let expiry_time = current_time + ttl_seconds;
cache.set("data", b"sensitive".to_vec());
cache.set("data_expiry", expiry_time.to_string().into_bytes());
assert_eq!(cache.get("data"), Some(b"sensitive".to_vec()));
let is_expired = |cache: &DashMapCache, key: &str| -> bool {
if let Some(expiry_bytes) = cache.get(&format!("{}_expiry", key)) {
if let Ok(expiry) = String::from_utf8(expiry_bytes)
.unwrap_or_default()
.parse::<u64>()
{
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
return now > expiry;
}
}
false
};
cache.set("data_expiry", (current_time - 1).to_string().into_bytes());
assert!(is_expired(&cache, "data"));
}
#[test]
fn test_cache_manual_invalidation() {
let cache = DashMapCache::new();
cache.set("session:user1", b"session_data_1".to_vec());
cache.set("session:user2", b"session_data_2".to_vec());
cache.set("token:123", b"auth_token".to_vec());
cache.delete("session:user1");
assert_eq!(cache.get("session:user1"), None);
assert_eq!(cache.get("session:user2"), Some(b"session_data_2".to_vec()));
cache.delete("session:user2");
cache.delete("token:123");
assert!(cache.is_empty());
}
#[test]
fn test_cache_refresh_before_expiry() {
use std::time::{SystemTime, UNIX_EPOCH};
let cache = DashMapCache::new();
cache.set("page:home", b"html_content".to_vec());
cache.set("page:home_last_refresh", b"0".to_vec());
let get_current_time = || {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
};
let initial_time = get_current_time();
cache.set("page:home", b"updated_html".to_vec());
cache.set(
"page:home_last_refresh",
get_current_time().to_string().into_bytes(),
);
assert_eq!(cache.get("page:home"), Some(b"updated_html".to_vec()));
let new_time_bytes = cache.get("page:home_last_refresh").unwrap();
let new_time: u64 = String::from_utf8(new_time_bytes).unwrap().parse().unwrap();
assert!(new_time >= initial_time);
}
#[test]
fn test_cache_concurrent_reads() {
let cache = DashMapCache::new();
cache.set("shared_data", b"important_content".to_vec());
let cache = Arc::new(cache);
let handles: Vec<_> = (0..10)
.map(|i| {
let cache = cache.clone();
thread::spawn(move || {
thread::sleep(Duration::from_micros(10));
let value = cache.get("shared_data");
assert_eq!(value, Some(b"important_content".to_vec()));
i })
})
.collect();
for handle in handles {
let _ = handle.join();
}
assert_eq!(cache.len(), 1);
assert_eq!(
cache.get("shared_data"),
Some(b"important_content".to_vec())
);
}
#[test]
fn test_cache_concurrent_writes() {
let cache = Arc::new(DashMapCache::new());
let num_threads = 20;
let keys_per_thread = 5;
let handles: Vec<_> = (0..num_threads)
.map(|thread_id| {
let cache = cache.clone();
thread::spawn(move || {
for i in 0..keys_per_thread {
let key = format!("thread_{}_key_{}", thread_id, i);
let value = format!("value_from_thread_{}", thread_id);
cache.set(&key, value.into_bytes());
}
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
assert_eq!(cache.len(), num_threads * keys_per_thread);
assert_eq!(
cache.get("thread_0_key_0"),
Some(b"value_from_thread_0".to_vec())
);
assert_eq!(
cache.get("thread_10_key_3"),
Some(b"value_from_thread_10".to_vec())
);
assert_eq!(
cache.get("thread_19_key_4"),
Some(b"value_from_thread_19".to_vec())
);
}
#[test]
fn test_cache_race_condition_prevention() {
let cache = Arc::new(DashMapCache::new());
let num_threads = 50;
cache.set("counter", b"0".to_vec());
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let cache = cache.clone();
thread::spawn(move || {
if let Some(value) = cache.get("counter") {
let current: u32 = String::from_utf8(value)
.unwrap_or_default()
.parse()
.unwrap_or(0);
let next = current + 1;
cache.set("counter", next.to_string().into_bytes());
}
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
let final_value_bytes = cache.get("counter").unwrap();
let final_value: u32 = String::from_utf8(final_value_bytes)
.unwrap_or_default()
.parse()
.unwrap_or(0);
assert!(final_value <= num_threads);
assert!(final_value > 0);
cache.delete("counter");
}
#[test]
fn test_cache_lru_eviction() {
let cache = DashMapCache::new();
cache.set("lru_list", b"key1,key2,key3".to_vec());
cache.set("key1", b"value1".to_vec());
cache.set("key2", b"value2".to_vec());
cache.set("key3", b"value3".to_vec());
cache.set("key4", b"value4".to_vec());
let evict_lru = |cache: &DashMapCache| {
if let Some(list_bytes) = cache.get("lru_list") {
let list = String::from_utf8(list_bytes).unwrap_or_default();
let keys: Vec<&str> = list.split(',').collect();
if let Some(lru_key) = keys.first() {
cache.delete(lru_key);
}
}
};
evict_lru(&cache);
assert_eq!(cache.get("key1"), None);
assert_eq!(cache.get("key2"), Some(b"value2".to_vec()));
assert_eq!(cache.get("key3"), Some(b"value3".to_vec()));
assert_eq!(cache.get("key4"), Some(b"value4".to_vec()));
assert_eq!(cache.get("lru_list"), Some(b"key1,key2,key3".to_vec()));
assert_eq!(cache.len(), 4);
}
#[test]
fn test_cache_memory_limit() {
let cache = DashMapCache::new();
cache.set("data", vec![0u8; 100]);
cache.set("data_size", b"100".to_vec());
let calculate_size = |cache: &DashMapCache| -> usize {
let mut total = 0;
for key in cache.find_keys_by_pattern("*") {
if let Some(value) = cache.get(&key) {
total += key.len();
total += value.len();
}
}
total
};
cache.set("large_data", vec![0u8; 200]);
cache.set("large_data_size", b"200".to_vec());
let current_size = calculate_size(&cache);
assert!(current_size > 300);
let memory_limit = 250;
let enforce_memory_limit = |cache: &DashMapCache| {
let mut iterations = 0;
while calculate_size(cache) > memory_limit && iterations < 10 {
iterations += 1;
let keys: Vec<String> = cache.find_keys_by_pattern("*");
if let Some(key_to_remove) = keys.iter().find(|k| !k.ends_with("_size")) {
cache.delete(key_to_remove);
} else {
break; }
}
};
enforce_memory_limit(&cache);
let final_size = calculate_size(&cache);
assert!(final_size <= memory_limit + 50); }
#[test]
fn test_cache_stats_tracking() {
let cache = DashMapCache::new();
cache.set("stats_hits", b"0".to_vec());
cache.set("stats_misses", b"0".to_vec());
let record_hit = |cache: &DashMapCache| {
if let Some(hits_bytes) = cache.get("stats_hits") {
let hits: u32 = String::from_utf8(hits_bytes)
.unwrap_or_default()
.parse()
.unwrap_or(0);
cache.set("stats_hits", (hits + 1).to_string().into_bytes());
}
};
let record_miss = |cache: &DashMapCache| {
if let Some(misses_bytes) = cache.get("stats_misses") {
let misses: u32 = String::from_utf8(misses_bytes)
.unwrap_or_default()
.parse()
.unwrap_or(0);
cache.set("stats_misses", (misses + 1).to_string().into_bytes());
}
};
cache.set("key1", b"value1".to_vec());
for _ in 0..5 {
if cache.get("key1").is_some() {
record_hit(&cache);
} else {
record_miss(&cache);
}
}
for _ in 0..3 {
if cache.get("nonexistent").is_some() {
record_hit(&cache);
} else {
record_miss(&cache);
}
}
let hits: u32 = String::from_utf8(cache.get("stats_hits").unwrap())
.unwrap()
.parse()
.unwrap();
let misses: u32 = String::from_utf8(cache.get("stats_misses").unwrap())
.unwrap()
.parse()
.unwrap();
assert_eq!(hits, 5);
assert_eq!(misses, 3);
let total = hits + misses;
let hit_rate = (hits as f64) / (total as f64);
assert!((hit_rate - 0.625).abs() < 0.001); }
#[test]
fn test_cache_get_many() {
let cache = DashMapCache::new();
cache.set("key1", b"value1".to_vec());
cache.set("key2", b"value2".to_vec());
cache.set("key3", b"value3".to_vec());
let keys = ["key1", "key2", "key3"];
let results = cache.get_many(&keys);
assert_eq!(results.len(), 3);
assert_eq!(results.get("key1"), Some(&b"value1".to_vec()));
assert_eq!(results.get("key2"), Some(&b"value2".to_vec()));
assert_eq!(results.get("key3"), Some(&b"value3".to_vec()));
let mixed_keys = ["key1", "nonexistent", "key3"];
let mixed_results = cache.get_many(&mixed_keys);
assert_eq!(mixed_results.len(), 2);
assert!(mixed_results.contains_key("key1"));
assert!(mixed_results.contains_key("key3"));
assert!(!mixed_results.contains_key("nonexistent"));
}
#[test]
fn test_cache_set_many() {
let cache = DashMapCache::new();
assert!(cache.is_empty());
let items = vec![
("k1".to_string(), b"v1".to_vec()),
("k2".to_string(), b"v2".to_vec()),
("k3".to_string(), b"v3".to_vec()),
];
cache.set_many(&items);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get("k1"), Some(b"v1".to_vec()));
assert_eq!(cache.get("k2"), Some(b"v2".to_vec()));
assert_eq!(cache.get("k3"), Some(b"v3".to_vec()));
let update_items = vec![
("k1".to_string(), b"v1_updated".to_vec()),
("k2".to_string(), b"v2_updated".to_vec()),
];
cache.set_many(&update_items);
assert_eq!(cache.len(), 3); assert_eq!(cache.get("k1"), Some(b"v1_updated".to_vec()));
assert_eq!(cache.get("k2"), Some(b"v2_updated".to_vec()));
assert_eq!(cache.get("k3"), Some(b"v3".to_vec())); }
#[test]
fn test_cache_delete_many() {
let cache = DashMapCache::new();
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
cache.set("k3", b"v3".to_vec());
cache.set("k4", b"v4".to_vec());
assert_eq!(cache.len(), 4);
let keys_to_delete = ["k1", "k3", "nonexistent"];
let deleted_count = cache.delete_many(&keys_to_delete);
assert_eq!(deleted_count, 2); assert_eq!(cache.len(), 2);
assert_eq!(cache.get("k1"), None);
assert_eq!(cache.get("k2"), Some(b"v2".to_vec()));
assert_eq!(cache.get("k3"), None);
assert_eq!(cache.get("k4"), Some(b"v4".to_vec()));
let remaining_keys = ["k2", "k4"];
let second_deleted_count = cache.delete_many(&remaining_keys);
assert_eq!(second_deleted_count, 2);
assert!(cache.is_empty());
}
#[test]
fn test_shared_cache_type() {
let cache: SharedCache = Arc::new(DashMapCache::new());
cache.set("shared_key", b"shared_value".to_vec());
assert_eq!(cache.get("shared_key"), Some(b"shared_value".to_vec()));
assert_eq!(cache.len(), 1);
let cache2 = cache.clone();
assert_eq!(cache2.get("shared_key"), Some(b"shared_value".to_vec()));
cache.set("another", b"value".to_vec());
assert_eq!(cache2.len(), 2);
}
#[test]
fn test_cache_special_key_characters() {
let cache = DashMapCache::new();
cache.set("key:with:colons", b"value".to_vec());
cache.set("key/with/slashes", b"value".to_vec());
cache.set("key with spaces", b"value".to_vec());
cache.set("key.with.dots", b"value".to_vec());
assert_eq!(cache.get("key:with:colons"), Some(b"value".to_vec()));
assert_eq!(cache.get("key/with/slashes"), Some(b"value".to_vec()));
assert_eq!(cache.get("key with spaces"), Some(b"value".to_vec()));
assert_eq!(cache.get("key.with.dots"), Some(b"value".to_vec()));
cache.set("Key", b"uppercase".to_vec());
cache.set("key", b"lowercase".to_vec());
assert_eq!(cache.get("Key"), Some(b"uppercase".to_vec()));
assert_eq!(cache.get("key"), Some(b"lowercase".to_vec()));
assert_eq!(cache.len(), 6); }
#[test]
fn test_cache_value_edge_cases() {
let cache = DashMapCache::new();
cache.set("empty", b"".to_vec());
assert_eq!(cache.get("empty"), Some(b"".to_vec()));
let binary_data: Vec<u8> = (0..=255).collect(); cache.set("binary", binary_data.clone());
assert_eq!(cache.get("binary"), Some(binary_data));
cache.set("unicode", "Hello, 世界! 🌍".as_bytes().to_vec());
assert_eq!(
cache.get("unicode"),
Some("Hello, 世界! 🌍".as_bytes().to_vec())
);
let large_value = vec![0x42u8; 1024 * 100]; cache.set("large", large_value.clone());
assert_eq!(cache.get("large"), Some(large_value));
}
#[test]
fn test_cache_contains() {
let cache = DashMapCache::new();
assert!(!cache.contains("any_key"));
cache.set("existing", b"value".to_vec());
assert!(cache.contains("existing"));
assert!(!cache.contains("nonexistent"));
cache.delete("existing");
assert!(!cache.contains("existing"));
cache.set("k1", b"v1".to_vec());
cache.set("k2", b"v2".to_vec());
cache.clear();
assert!(!cache.contains("k1"));
assert!(!cache.contains("k2"));
}
}