#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use crate::services::cache::unified::VectorizedCacheKey;
use proptest::prelude::*;
use std::collections::HashMap;
prop_compose! {
fn arb_cache_key()
(content in "[a-zA-Z0-9]{5,20}", suffix in 0u64..100) -> String
{
format!("{}-{}", content, suffix)
}
}
prop_compose! {
fn arb_cache_value()
(size in 10usize..100, seed in any::<u64>())
-> Vec<u8>
{
(0..size)
.map(|i| ((seed.wrapping_add(i as u64)) % 256) as u8)
.collect()
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn cache_get_put_consistency_fast(
operations in prop::collection::vec(
(arb_cache_key(), arb_cache_value()),
0..10 )
) {
let mut cache = HashMap::new();
let mut stats = TestCacheStats::default();
for (key, value) in operations {
cache.insert(key.clone(), value.clone());
let retrieved = cache.get(&key);
prop_assert!(retrieved.is_some(), "Value not found for key: {}", key);
prop_assert_eq!(retrieved.unwrap(), &value, "Retrieved value doesn't match");
stats.total_requests += 1;
stats.cache_hits += 1;
}
prop_assert_eq!(stats.cache_hits + stats.cache_misses, stats.total_requests);
}
#[test]
fn cache_eviction_maintains_invariants_fast(
entries in prop::collection::vec(
(arb_cache_key(), arb_cache_value()),
0..20 ),
max_size in 100usize..1000 ) {
let mut cache = SimpleEvictingCache::new(max_size);
for (key, value) in entries {
cache.put(key, value);
prop_assert!(cache.size_bytes() <= max_size,
"Cache size {} exceeds max {}", cache.size_bytes(), max_size);
}
let size_before = cache.size_bytes();
cache.evict_if_needed();
let size_after = cache.size_bytes();
prop_assert!(size_after <= size_before,
"Size increased after eviction: {} -> {}", size_before, size_after);
prop_assert!(size_after <= max_size,
"Size {} still exceeds max {} after eviction", size_after, max_size);
}
#[test]
fn cache_remove_consistency_fast(
initial_entries in prop::collection::vec(
(arb_cache_key(), arb_cache_value()),
0..10 ),
remove_indices in prop::collection::vec(any::<usize>(), 0..5) ) {
let mut cache = HashMap::new();
for (key, value) in &initial_entries {
cache.insert(key.clone(), value.clone());
}
let initial_len = cache.len();
let mut removed_count = 0;
for idx in remove_indices {
if !initial_entries.is_empty() {
let (key, _) = &initial_entries[idx % initial_entries.len()];
if cache.remove(key).is_some() {
removed_count += 1;
prop_assert!(!cache.contains_key(key),
"Removed key {} still present", key);
}
}
}
prop_assert_eq!(cache.len(), initial_len.saturating_sub(removed_count),
"Cache length inconsistent after removals");
}
#[test]
fn cache_clear_idempotent_fast(
entries in prop::collection::vec(
(arb_cache_key(), arb_cache_value()),
0..10 )
) {
let mut cache = HashMap::new();
for (key, value) in &entries {
cache.insert(key.clone(), value.clone());
}
cache.clear();
prop_assert_eq!(cache.len(), 0);
cache.clear();
prop_assert_eq!(cache.len(), 0);
for (key, _) in &entries {
prop_assert!(!cache.contains_key(key));
}
}
#[test]
fn vectorized_key_deterministic_fast(
data in prop::collection::vec(any::<u8>(), 0..100) ) {
let key1 = VectorizedCacheKey::from_bytes(&data);
let key2 = VectorizedCacheKey::from_bytes(&data);
prop_assert_eq!(&key1, &key2, "Keys not deterministic");
prop_assert_eq!(key1.hash_high, key2.hash_high);
prop_assert_eq!(key1.hash_low, key2.hash_low);
if !data.is_empty() {
let mut modified = data.clone();
modified[0] = modified[0].wrapping_add(1);
let key3 = VectorizedCacheKey::from_bytes(&modified);
prop_assert!(key1 != key3 || data.len() == 1,
"Different data produced same key");
}
}
}
struct SimpleEvictingCache {
entries: HashMap<String, Vec<u8>>,
max_size: usize,
}
impl SimpleEvictingCache {
fn new(max_size: usize) -> Self {
Self {
entries: HashMap::new(),
max_size,
}
}
fn put(&mut self, key: String, value: Vec<u8>) {
self.entries.insert(key, value);
self.evict_if_needed();
}
fn size_bytes(&self) -> usize {
self.entries.iter().map(|(k, v)| k.len() + v.len()).sum()
}
fn evict_if_needed(&mut self) {
while self.size_bytes() > self.max_size && !self.entries.is_empty() {
if let Some(key) = self.entries.keys().next().cloned() {
self.entries.remove(&key);
} else {
break;
}
}
}
}
#[derive(Default)]
struct TestCacheStats {
cache_hits: usize,
cache_misses: usize,
total_requests: usize,
}
}