use cache_rs::config::{
GdsfCacheConfig, LfuCacheConfig, LfudaCacheConfig, LruCacheConfig, SlruCacheConfig,
};
use cache_rs::metrics::CacheMetrics;
use cache_rs::{GdsfCache, LfuCache, LfudaCache, LruCache, SlruCache};
use std::num::NonZeroUsize;
const OBJECT_SIZE: u64 = 1;
fn make_lru<K: std::hash::Hash + Eq + Clone, V: Clone>(cap: usize) -> LruCache<K, V> {
let config = LruCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
max_size: u64::MAX,
};
LruCache::init(config, None)
}
fn make_lru_with_limits<K: std::hash::Hash + Eq + Clone, V: Clone>(
cap: usize,
max_size: u64,
) -> LruCache<K, V> {
let config = LruCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
max_size,
};
LruCache::init(config, None)
}
fn make_lru_with_max_size<K: std::hash::Hash + Eq + Clone, V: Clone>(
max_size: u64,
) -> LruCache<K, V> {
let config = LruCacheConfig {
capacity: NonZeroUsize::new(16384).unwrap(),
max_size,
};
LruCache::init(config, None)
}
fn make_lfu<K: std::hash::Hash + Eq + Clone, V: Clone>(cap: usize) -> LfuCache<K, V> {
let config = LfuCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
max_size: u64::MAX,
};
LfuCache::init(config, None)
}
fn make_lfu_with_max_size<K: std::hash::Hash + Eq + Clone, V: Clone>(
max_size: u64,
) -> LfuCache<K, V> {
let config = LfuCacheConfig {
capacity: NonZeroUsize::new(16384).unwrap(),
max_size,
};
LfuCache::init(config, None)
}
fn make_lfuda<K: std::hash::Hash + Eq + Clone, V: Clone>(cap: usize) -> LfudaCache<K, V> {
let config = LfudaCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
initial_age: 0,
max_size: u64::MAX,
};
LfudaCache::init(config, None)
}
fn make_lfuda_with_max_size<K: std::hash::Hash + Eq + Clone, V: Clone>(
max_size: u64,
) -> LfudaCache<K, V> {
let config = LfudaCacheConfig {
capacity: NonZeroUsize::new(16384).unwrap(),
initial_age: 0,
max_size,
};
LfudaCache::init(config, None)
}
fn make_slru<K: std::hash::Hash + Eq + Clone, V: Clone>(
cap: usize,
protected_cap: usize,
) -> SlruCache<K, V> {
let config = SlruCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
protected_capacity: NonZeroUsize::new(protected_cap).unwrap(),
max_size: u64::MAX,
};
SlruCache::init(config, None)
}
fn make_slru_with_max_size<K: std::hash::Hash + Eq + Clone, V: Clone>(
max_size: u64,
) -> SlruCache<K, V> {
let config = SlruCacheConfig {
capacity: NonZeroUsize::new(16384).unwrap(),
protected_capacity: NonZeroUsize::new(3276).unwrap(), max_size,
};
SlruCache::init(config, None)
}
fn make_gdsf<K: std::hash::Hash + Eq + Clone, V: Clone>(cap: usize) -> GdsfCache<K, V> {
let config = GdsfCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
initial_age: 0.0,
max_size: u64::MAX,
};
GdsfCache::init(config, None)
}
#[test]
fn test_lru_evicts_least_recently_used() {
let mut cache = make_lru(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
assert!(cache.get(&1).is_some(), "Key 1 should be present");
assert!(cache.get(&2).is_some(), "Key 2 should be present");
assert!(cache.get(&3).is_some(), "Key 3 should be present");
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 should have been evicted (was LRU)"
);
assert!(cache.get(&2).is_some(), "Key 2 should remain");
assert!(cache.get(&3).is_some(), "Key 3 should remain");
assert!(cache.get(&4).is_some(), "Key 4 should be present");
cache.put(5, 50, 1);
assert!(
cache.get(&2).is_none(),
"Key 2 should have been evicted (was LRU)"
);
assert!(cache.get(&3).is_some(), "Key 3 should remain");
assert!(cache.get(&4).is_some(), "Key 4 should remain");
assert!(cache.get(&5).is_some(), "Key 5 should be present");
}
#[test]
fn test_lru_eviction_order_is_predictable() {
let mut cache = make_lru(5);
for i in 0..5 {
cache.put(i, i * 10, 1);
}
cache.put(5, 50, 1);
assert!(
cache.get(&0).is_none(),
"First eviction: Key 0 should be evicted"
);
cache.put(6, 60, 1);
assert!(
cache.get(&1).is_none(),
"Second eviction: Key 1 should be evicted"
);
cache.put(7, 70, 1);
assert!(
cache.get(&2).is_none(),
"Third eviction: Key 2 should be evicted"
);
assert!(cache.get(&3).is_some(), "Key 3 should remain");
assert!(cache.get(&4).is_some(), "Key 4 should remain");
assert!(cache.get(&5).is_some(), "Key 5 should remain");
assert!(cache.get(&6).is_some(), "Key 6 should remain");
assert!(cache.get(&7).is_some(), "Key 7 should remain");
}
#[test]
fn test_lru_get_updates_recency() {
let mut cache = make_lru(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
assert_eq!(cache.get(&1), Some(&10));
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_some(),
"Key 1 should survive due to recent access"
);
assert!(
cache.get(&2).is_none(),
"Key 2 should be evicted (was LRU after key 1 was accessed)"
);
assert!(cache.get(&3).is_some(), "Key 3 should remain");
assert!(cache.get(&4).is_some(), "Key 4 should be present");
}
#[test]
fn test_lfu_evicts_least_frequently_used() {
let mut cache = make_lfu(3);
cache.put(1, 10, 1); cache.put(2, 20, 1); cache.put(3, 30, 1);
cache.get(&1); cache.get(&1); cache.get(&2);
cache.put(4, 40, 1);
assert!(
cache.get(&3).is_none(),
"Key 3 should be evicted (lowest freq=1)"
);
assert!(cache.get(&1).is_some(), "Key 1 should remain (freq=3)");
assert!(cache.get(&2).is_some(), "Key 2 should remain (freq=2)");
assert!(cache.get(&4).is_some(), "Key 4 should be present");
}
#[test]
fn test_lfu_frequency_accumulates() {
let mut cache = make_lfu(3);
cache.put("hot", 1, 1);
cache.put("warm", 2, 1);
cache.put("cold", 3, 1);
for _ in 0..10 {
cache.get(&"hot");
}
for _ in 0..3 {
cache.get(&"warm");
}
cache.put("new", 4, 1);
assert!(
cache.get(&"cold").is_none(),
"cold should be evicted (lowest freq)"
);
assert!(cache.get(&"hot").is_some(), "hot should remain");
assert!(cache.get(&"warm").is_some(), "warm should remain");
assert!(cache.get(&"new").is_some(), "new should be present");
}
#[test]
fn test_lfu_same_frequency_uses_fifo() {
let mut cache = make_lfu(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (FIFO among same freq)"
);
cache.put(5, 50, 1);
assert!(
cache.get(&2).is_none(),
"Key 2 should be evicted (FIFO among same freq)"
);
}
#[test]
fn test_lfuda_evicts_lowest_priority() {
let mut cache = make_lfuda(3);
cache.put(1, 10, 1); cache.put(2, 20, 1); cache.put(3, 30, 1);
cache.get(&1); cache.get(&1); cache.get(&2);
cache.put(4, 40, 1);
assert!(
cache.get(&3).is_none(),
"Key 3 should be evicted (lowest priority)"
);
assert!(
cache.get(&1).is_some(),
"Key 1 should remain (high priority)"
);
assert!(cache.get(&2).is_some(), "Key 2 should remain");
assert!(cache.get(&4).is_some(), "Key 4 should be present");
}
#[test]
fn test_lfuda_aging_helps_new_items() {
let mut cache = make_lfuda(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
for _ in 0..5 {
cache.get(&1);
cache.get(&2);
cache.get(&3);
}
cache.put(4, 40, 1); cache.put(5, 50, 1);
assert_eq!(cache.len(), 3, "Cache should be at capacity");
}
#[test]
fn test_lfuda_basic_eviction() {
let mut cache = make_lfuda(4);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
for _ in 0..3 {
cache.get(&1);
cache.get(&2);
}
cache.put(5, 50, 1);
let key3_evicted = cache.get(&3).is_none();
let key4_evicted = cache.get(&4).is_none();
assert!(
key3_evicted || key4_evicted,
"One of the low-frequency items (3 or 4) should be evicted"
);
assert!(cache.get(&1).is_some(), "Key 1 should remain (high freq)");
assert!(cache.get(&2).is_some(), "Key 2 should remain (high freq)");
}
#[test]
fn test_slru_promotion_to_protected() {
let mut cache = make_slru(4, 2);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.get(&1);
cache.get(&1);
cache.get(&2);
cache.get(&2);
cache.put(5, 50, 1);
assert!(
cache.get(&3).is_none(),
"Key 3 should be evicted from probationary"
);
assert!(cache.get(&1).is_some(), "Key 1 should remain (protected)");
assert!(cache.get(&2).is_some(), "Key 2 should remain (protected)");
}
#[test]
fn test_slru_probationary_evicted_first() {
let mut cache = make_slru(4, 2);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.get(&3);
cache.get(&3);
cache.get(&4);
cache.get(&4);
cache.put(5, 50, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (LRU in probationary)"
);
assert!(cache.get(&3).is_some(), "Key 3 should remain (protected)");
assert!(cache.get(&4).is_some(), "Key 4 should remain (protected)");
assert!(
cache.get(&2).is_some(),
"Key 2 should remain (still in probationary)"
);
assert!(cache.get(&5).is_some(), "Key 5 should be present");
}
#[test]
fn test_slru_eviction_order_in_probationary() {
let mut cache = make_slru(4, 1);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.get(&4);
cache.get(&4);
cache.put(5, 50, 1);
assert!(
cache.get(&1).is_none(),
"First eviction: Key 1 should be evicted"
);
cache.put(6, 60, 1);
assert!(
cache.get(&2).is_none(),
"Second eviction: Key 2 should be evicted"
);
cache.put(7, 70, 1);
assert!(
cache.get(&3).is_none(),
"Third eviction: Key 3 should be evicted"
);
assert!(cache.get(&4).is_some(), "Key 4 should remain (protected)");
}
#[test]
fn test_gdsf_prefers_smaller_objects() {
let mut cache = make_gdsf(3);
cache.put(1, 10, 100); cache.put(2, 20, 1); cache.put(3, 30, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_none(),
"Large object (key 1) should be evicted first due to low priority"
);
assert!(cache.get(&2).is_some(), "Small object 2 should remain");
assert!(cache.get(&3).is_some(), "Small object 3 should remain");
assert!(
cache.get(&4).is_some(),
"New small object should be present"
);
}
#[test]
fn test_gdsf_frequency_matters() {
let mut cache = make_gdsf(3);
cache.put(1, 10, OBJECT_SIZE);
cache.put(2, 20, OBJECT_SIZE);
cache.put(3, 30, OBJECT_SIZE);
for _ in 0..10 {
cache.get(&1);
}
for _ in 0..3 {
cache.get(&2);
}
cache.put(4, 40, OBJECT_SIZE);
assert!(
cache.get(&3).is_none(),
"Lowest frequency item (key 3) should be evicted"
);
assert!(cache.get(&1).is_some(), "High freq item should remain");
assert!(cache.get(&2).is_some(), "Medium freq item should remain");
}
#[test]
fn test_gdsf_size_frequency_tradeoff() {
let mut cache = make_gdsf(3);
cache.put(1, 10, 100); for _ in 0..20 {
cache.get(&1); }
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_none(),
"Large object should be evicted despite high frequency (priority 0.21 < 1.0)"
);
assert!(cache.get(&2).is_some(), "Small object 2 should remain");
assert!(cache.get(&3).is_some(), "Small object 3 should remain");
assert!(cache.get(&4).is_some(), "New object should be present");
}
#[test]
fn test_gdsf_eviction_order_by_priority() {
let mut cache = make_gdsf(4);
cache.put(1, 10, 10); cache.put(2, 20, 5); cache.put(3, 30, 2); cache.put(4, 40, 1);
cache.put(5, 50, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 evicted first (priority 0.1)"
);
cache.put(6, 60, 1);
assert!(
cache.get(&2).is_none(),
"Key 2 evicted second (priority 0.2)"
);
cache.put(7, 70, 1);
assert!(
cache.get(&3).is_none(),
"Key 3 evicted third (priority 0.5)"
);
assert!(
cache.get(&4).is_some(),
"Key 4 should remain (highest priority 1.0)"
);
}
#[test]
fn test_all_caches_basic_operations() {
let mut lru = make_lru(10);
lru.put("key", 42, 1);
assert_eq!(lru.get(&"key"), Some(&42));
assert_eq!(lru.remove(&"key"), Some(42));
assert_eq!(lru.get(&"key"), None);
let mut lfu = make_lfu(10);
lfu.put("key", 42, 1);
assert_eq!(lfu.get(&"key"), Some(&42));
assert_eq!(lfu.remove(&"key"), Some(42));
assert_eq!(lfu.get(&"key"), None);
let mut lfuda = make_lfuda(10);
lfuda.put("key", 42, 1);
assert_eq!(lfuda.get(&"key"), Some(&42));
assert_eq!(lfuda.remove(&"key"), Some(42));
assert_eq!(lfuda.get(&"key"), None);
let mut slru = make_slru(10, 5);
slru.put("key", 42, 1);
assert_eq!(slru.get(&"key"), Some(&42));
assert_eq!(slru.remove(&"key"), Some(42));
assert_eq!(slru.get(&"key"), None);
let mut gdsf = make_gdsf(10);
gdsf.put("key", 42, 1);
assert_eq!(gdsf.get(&"key"), Some(42));
gdsf.clear();
assert_eq!(gdsf.get(&"key"), None);
}
#[test]
fn test_all_caches_capacity_enforcement() {
let mut lru = make_lru(3);
for i in 0..10 {
lru.put(i, i, 1);
}
assert_eq!(lru.len(), 3, "LRU should enforce capacity");
let mut lfu = make_lfu(3);
for i in 0..10 {
lfu.put(i, i, 1);
}
assert_eq!(lfu.len(), 3, "LFU should enforce capacity");
let mut lfuda = make_lfuda(3);
for i in 0..10 {
lfuda.put(i, i, 1);
}
assert_eq!(lfuda.len(), 3, "LFUDA should enforce capacity");
let mut slru = make_slru(3, 1);
for i in 0..10 {
slru.put(i, i, 1);
}
assert_eq!(slru.len(), 3, "SLRU should enforce capacity");
let mut gdsf = make_gdsf(3);
for i in 0..10 {
gdsf.put(i, i, 1);
}
assert_eq!(gdsf.len(), 3, "GDSF should enforce capacity");
}
#[test]
fn test_all_caches_update_existing_key() {
let mut lru = make_lru(3);
lru.put(1, 10, 1);
lru.put(2, 20, 1);
lru.put(1, 100, 1); assert_eq!(lru.len(), 2, "LRU: update should not increase len");
assert_eq!(lru.get(&1), Some(&100), "LRU: value should be updated");
let mut lfu = make_lfu(3);
lfu.put(1, 10, 1);
lfu.put(2, 20, 1);
lfu.put(1, 100, 1);
assert_eq!(lfu.len(), 2, "LFU: update should not increase len");
assert_eq!(lfu.get(&1), Some(&100), "LFU: value should be updated");
let mut lfuda = make_lfuda(3);
lfuda.put(1, 10, 1);
lfuda.put(2, 20, 1);
lfuda.put(1, 100, 1);
assert_eq!(lfuda.len(), 2, "LFUDA: update should not increase len");
assert_eq!(lfuda.get(&1), Some(&100), "LFUDA: value should be updated");
let mut slru = make_slru(3, 1);
slru.put(1, 10, 1);
slru.put(2, 20, 1);
slru.put(1, 100, 1);
assert_eq!(slru.len(), 2, "SLRU: update should not increase len");
assert_eq!(slru.get(&1), Some(&100), "SLRU: value should be updated");
let mut gdsf = make_gdsf(3);
gdsf.put(1, 10, 1);
gdsf.put(2, 20, 1);
gdsf.put(1, 100, 1);
assert_eq!(gdsf.len(), 2, "GDSF: update should not increase len");
assert_eq!(gdsf.get(&1), Some(100), "GDSF: value should be updated");
}
#[test]
fn test_all_caches_clear() {
let mut lru = make_lru(5);
for i in 0..5 {
lru.put(i, i, 1);
}
lru.clear();
assert_eq!(lru.len(), 0, "LRU: clear should empty cache");
assert!(
lru.get(&0).is_none(),
"LRU: get after clear should return None"
);
let mut lfu = make_lfu(5);
for i in 0..5 {
lfu.put(i, i, 1);
}
lfu.clear();
assert_eq!(lfu.len(), 0, "LFU: clear should empty cache");
let mut lfuda = make_lfuda(5);
for i in 0..5 {
lfuda.put(i, i, 1);
}
lfuda.clear();
assert_eq!(lfuda.len(), 0, "LFUDA: clear should empty cache");
let mut slru = make_slru(5, 2);
for i in 0..5 {
slru.put(i, i, 1);
}
slru.clear();
assert_eq!(slru.len(), 0, "SLRU: clear should empty cache");
let mut gdsf = make_gdsf(5);
for i in 0..5 {
gdsf.put(i, i, 1);
}
gdsf.clear();
assert_eq!(gdsf.len(), 0, "GDSF: clear should empty cache");
}
#[test]
fn test_lru_size_tracking() {
let mut cache = make_lru_with_max_size(100);
cache.put(1, "a", 30); assert_eq!(
cache.current_size(),
30,
"Size should be 30 after first insert"
);
cache.put(2, "b", 40); assert_eq!(
cache.current_size(),
70,
"Size should be 70 after second insert"
);
cache.put(3, "c", 20); assert_eq!(
cache.current_size(),
90,
"Size should be 90 after third insert"
);
assert!(cache.get(&1).is_some(), "Key 1 should be present");
assert!(cache.get(&2).is_some(), "Key 2 should be present");
assert!(cache.get(&3).is_some(), "Key 3 should be present");
}
#[test]
fn test_lru_size_tracking_accumulates() {
let mut cache = make_lru_with_max_size(1000);
for i in 0..10 {
cache.put(i, format!("value{}", i), 50);
}
assert_eq!(
cache.current_size(),
500,
"Total size should be 500 (10 * 50)"
);
assert_eq!(cache.len(), 10, "Should have 10 entries");
}
#[test]
fn test_lru_entry_count_eviction_updates_size() {
let mut cache = make_lru_with_limits(3, 1000);
cache.put(1, "a", 30);
cache.put(2, "b", 40);
cache.put(3, "c", 50);
assert_eq!(cache.current_size(), 120);
assert_eq!(cache.len(), 3);
cache.put(4, "d", 60);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (LRU, count limit)"
);
assert_eq!(cache.len(), 3, "Should still have 3 entries");
}
#[test]
fn test_lfu_size_tracking() {
let mut cache = make_lfu_with_max_size(1000);
cache.put(1, "a", 100);
cache.put(2, "b", 200);
cache.put(3, "c", 150);
assert_eq!(cache.current_size(), 450, "Total size should be 450");
assert_eq!(cache.len(), 3);
}
#[test]
fn test_lfuda_size_tracking() {
let mut cache = make_lfuda_with_max_size(1000);
cache.put(1, "a", 100);
cache.put(2, "b", 200);
assert_eq!(cache.current_size(), 300, "Total size should be 300");
}
#[test]
fn test_slru_size_tracking() {
let mut cache = make_slru_with_max_size(1000);
cache.put(1, "a", 100);
cache.put(2, "b", 200);
cache.put(3, "c", 150);
assert_eq!(cache.current_size(), 450, "Total size should be 450");
}
#[test]
fn test_size_reset_on_clear() {
let mut cache = make_lru_with_max_size(1000);
cache.put(1, "a", 30);
cache.put(2, "b", 40);
assert_eq!(cache.current_size(), 70);
cache.clear();
assert_eq!(cache.current_size(), 0, "Size should be 0 after clear");
assert_eq!(cache.len(), 0, "Length should be 0 after clear");
}
#[test]
fn test_max_size_getter() {
let cache: LruCache<i32, i32> = make_lru_with_max_size(500);
assert_eq!(
cache.max_size(),
500,
"max_size should return configured limit"
);
let cache2: LruCache<i32, i32> = make_lru(10);
assert_eq!(
cache2.max_size(),
u64::MAX,
"max_size should be u64::MAX for count-only cache"
);
}
#[test]
fn test_with_limits_constructor() {
let cache: LruCache<i32, i32> = make_lru_with_limits(100, 50000);
assert_eq!(cache.max_size(), 50000);
assert_eq!(cache.len(), 0);
assert_eq!(cache.current_size(), 0);
}
#[test]
fn test_lru_capacity_one() {
let mut cache = make_lru(1);
cache.put(1, 10, 1);
assert_eq!(cache.get(&1), Some(&10));
cache.put(2, 20, 1);
assert!(cache.get(&1).is_none(), "Key 1 should be evicted");
assert_eq!(cache.get(&2), Some(&20), "Key 2 should be present");
cache.put(3, 30, 1);
assert!(cache.get(&2).is_none(), "Key 2 should be evicted");
assert_eq!(cache.get(&3), Some(&30), "Key 3 should be present");
}
#[test]
fn test_lru_update_moves_to_mru() {
let mut cache = make_lru(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(1, 100, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&2).is_none(),
"Key 2 should be evicted (was LRU after update)"
);
assert_eq!(
cache.get(&1),
Some(&100),
"Key 1 should remain with updated value"
);
assert!(cache.get(&3).is_some(), "Key 3 should remain");
assert!(cache.get(&4).is_some(), "Key 4 should be present");
}
#[test]
fn test_lru_reverse_access_order() {
let mut cache = make_lru(5);
for i in 1..=5 {
cache.put(i, i * 10, 1);
}
for i in (1..=5).rev() {
cache.get(&i);
}
cache.put(6, 60, 1);
assert!(
cache.get(&5).is_none(),
"Key 5 should be evicted (was LRU after reverse access)"
);
assert!(cache.get(&1).is_some(), "Key 1 should remain (was MRU)");
}
#[test]
fn test_lru_remove_and_reinsert() {
let mut cache = make_lru(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
assert_eq!(cache.remove(&2), Some(20));
assert_eq!(cache.len(), 2);
cache.put(2, 200, 1);
cache.put(4, 40, 1);
assert!(cache.get(&1).is_none(), "Key 1 should be evicted");
assert_eq!(
cache.get(&2),
Some(&200),
"Key 2 should be present with new value"
);
}
#[test]
fn test_lfu_all_equal_frequency_fifo() {
let mut cache = make_lfu(4);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.put(5, 50, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (FIFO tiebreaker)"
);
cache.put(6, 60, 1);
assert!(
cache.get(&2).is_none(),
"Key 2 should be evicted (FIFO tiebreaker)"
);
cache.put(7, 70, 1);
assert!(
cache.get(&3).is_none(),
"Key 3 should be evicted (FIFO tiebreaker)"
);
}
#[test]
fn test_lfu_new_item_lowest_frequency() {
let mut cache = make_lfu(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
for _ in 0..10 {
cache.get(&1);
cache.get(&2);
cache.get(&3);
}
cache.put(4, 40, 1); let _first_evicted = (1..=3).find(|&i| cache.get(&i).is_none());
cache.put(5, 50, 1);
assert!(
cache.get(&4).is_none(),
"Key 4 should be evicted (lowest freq=1)"
);
assert!(cache.get(&5).is_some(), "Key 5 should be present");
}
#[test]
fn test_lfu_capacity_one() {
let mut cache = make_lfu(1);
cache.put(1, 10, 1);
for _ in 0..100 {
cache.get(&1);
}
cache.put(2, 20, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 must be evicted (capacity=1)"
);
assert_eq!(cache.get(&2), Some(&20));
}
#[test]
fn test_lfu_update_preserves_frequency() {
let mut cache = make_lfu(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
for _ in 0..10 {
cache.get(&1);
}
cache.put(1, 100, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_some(),
"Key 1 should remain (high freq preserved after update)"
);
assert_eq!(cache.get(&1), Some(&100), "Key 1 should have updated value");
}
#[test]
fn test_slru_all_in_probationary() {
let mut cache = make_slru(5, 3);
for i in 1..=5 {
cache.put(i, i * 10, 1);
}
cache.put(6, 60, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (LRU in probationary)"
);
cache.put(7, 70, 1);
assert!(cache.get(&2).is_none(), "Key 2 should be evicted");
}
#[test]
fn test_slru_protected_full_demotion() {
let mut cache = make_slru(4, 2);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.get(&1); cache.get(&1); cache.get(&2);
cache.get(&2);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.get(&3);
cache.get(&3);
cache.put(5, 50, 1);
assert!(cache.get(&2).is_some(), "Key 2 should remain (protected)");
assert!(cache.get(&3).is_some(), "Key 3 should remain (protected)");
}
#[test]
fn test_slru_access_in_protected_stays() {
let mut cache = make_slru(4, 2);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.get(&1);
cache.get(&1);
cache.get(&2);
cache.get(&2);
cache.get(&1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.get(&3);
cache.get(&3);
cache.put(5, 50, 1);
assert!(
cache.get(&1).is_some(),
"Key 1 should remain (MRU in protected)"
);
assert!(
cache.get(&3).is_some(),
"Key 3 should remain (just promoted)"
);
}
#[test]
fn test_slru_probationary_larger_than_protected() {
let mut cache = make_slru(5, 1);
for i in 1..=5 {
cache.put(i, i * 10, 1);
}
cache.get(&5);
cache.get(&5);
for i in 6..=9 {
cache.put(i, i * 10, 1);
}
assert!(cache.get(&5).is_some(), "Key 5 should remain (protected)");
for i in 1..=4 {
assert!(cache.get(&i).is_none(), "Key {} should be evicted", i);
}
}
#[test]
fn test_lfuda_all_equal_priority() {
let mut cache = make_lfuda(4);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
cache.put(5, 50, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (FIFO among equal priority)"
);
}
#[test]
fn test_lfuda_aging_reduces_priority_gap() {
let mut cache = make_lfuda(3);
cache.put(1, 10, 1);
for _ in 0..50 {
cache.get(&1);
}
cache.put(2, 20, 1);
cache.put(3, 30, 1);
for i in 100..120 {
cache.put(i, i, 1);
}
assert_eq!(cache.len(), 3);
}
#[test]
fn test_lfuda_capacity_one() {
let mut cache = make_lfuda(1);
cache.put(1, 10, 1);
for _ in 0..100 {
cache.get(&1);
}
cache.put(2, 20, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 must be evicted (capacity=1)"
);
}
#[test]
fn test_lfuda_cap_len_is_empty() {
let mut cache: LfudaCache<i32, i32> = make_lfuda(10);
assert_eq!(cache.cap().get(), 10);
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
assert!(!cache.is_empty());
assert_eq!(cache.len(), 2);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_lfuda_current_size_max_size() {
let mut cache: LfudaCache<i32, i32> = make_lfuda_with_max_size(1000);
assert_eq!(cache.max_size(), 1000);
assert_eq!(cache.current_size(), 0);
cache.put(1, 10, 100);
assert_eq!(cache.current_size(), 100);
cache.put(2, 20, 200);
assert_eq!(cache.current_size(), 300);
cache.put(1, 15, 150);
assert_eq!(cache.current_size(), 350);
cache.remove(&2);
assert_eq!(cache.current_size(), 150);
cache.clear();
assert_eq!(cache.current_size(), 0);
}
#[test]
fn test_lfuda_global_age_tracking() {
let mut cache: LfudaCache<i32, i32> = make_lfuda(3);
assert_eq!(cache.global_age(), 0);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
assert_eq!(cache.global_age(), 0);
cache.put(4, 40, 1);
let age_after_first_eviction = cache.global_age();
assert!(
age_after_first_eviction >= 1,
"Global age should increase after eviction"
);
cache.put(5, 50, 1);
let age_after_second = cache.global_age();
assert!(
age_after_second >= age_after_first_eviction,
"Global age should not decrease"
);
}
#[test]
fn test_lfuda_record_miss() {
use cache_rs::metrics::CacheMetrics;
let mut cache: LfudaCache<i32, i32> = make_lfuda(10);
cache.record_miss(100);
cache.record_miss(200);
cache.record_miss(50);
let metrics = cache.metrics();
assert!(
metrics.contains_key("cache_misses"),
"Metrics should track cache_misses"
);
let misses = metrics.get("cache_misses").unwrap();
assert_eq!(*misses, 3.0, "Should have 3 recorded misses");
}
#[test]
fn test_lfuda_get_mut_modifies_value() {
let mut cache: LfudaCache<&str, i32> = make_lfuda(10);
cache.put("a", 10, 1);
cache.put("b", 20, 1);
if let Some(val) = cache.get_mut(&"a") {
*val = 100;
}
assert_eq!(cache.get(&"a"), Some(&100));
assert!(cache.get_mut(&"missing").is_none());
}
#[test]
fn test_lfuda_get_mut_affects_priority() {
let mut cache: LfudaCache<i32, i32> = make_lfuda(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
for _ in 0..5 {
if let Some(val) = cache.get_mut(&1) {
*val += 1;
}
}
assert_eq!(cache.get(&1), Some(&15));
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_some(),
"Key 1 should not be evicted (high priority from get_mut accesses)"
);
}
#[test]
fn test_lfuda_size_eviction_multiple_items() {
let mut cache: LfudaCache<i32, String> = make_lfuda_with_max_size(100);
cache.put(1, "a".to_string(), 25);
cache.put(2, "b".to_string(), 25);
cache.put(3, "c".to_string(), 25);
cache.put(4, "d".to_string(), 25);
assert_eq!(cache.current_size(), 100);
assert_eq!(cache.len(), 4);
cache.put(5, "large".to_string(), 60);
assert!(cache.current_size() <= 100);
assert!(cache.len() < 4);
assert!(cache.get(&5).is_some(), "Large item should be in cache");
}
#[test]
fn test_lfuda_put_returns_evicted() {
let mut cache: LfudaCache<i32, i32> = make_lfuda(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
let evicted = cache.put(4, 40, 1);
assert!(evicted.is_some(), "Should return evicted item");
let (key, value) = evicted.unwrap().into_iter().next().unwrap();
assert!((1..=3).contains(&key));
assert!(value == 10 || value == 20 || value == 30);
}
#[test]
fn test_lfuda_update_existing_key() {
let mut cache: LfudaCache<i32, i32> = make_lfuda(10);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
let old = cache.put(1, 100, 1);
assert!(old.is_none());
assert_eq!(cache.get(&1), Some(&100));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_gdsf_all_same_size_and_frequency() {
let mut cache = make_gdsf(4);
for i in 1..=4 {
cache.put(i, i * 10, 10); }
cache.put(5, 50, 10);
assert!(
cache.get(&1).is_none(),
"Key 1 should be evicted (FIFO tiebreaker)"
);
}
#[test]
fn test_gdsf_tiny_vs_huge_size() {
let mut cache = make_gdsf(3);
cache.put(1, 10, 1000);
for _ in 0..10 {
cache.get(&1); }
cache.put(2, 20, 1); cache.put(3, 30, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_none(),
"Huge object should be evicted (priority 0.011 << 1.0)"
);
assert!(cache.get(&2).is_some(), "Tiny object 2 should remain");
assert!(cache.get(&3).is_some(), "Tiny object 3 should remain");
}
#[test]
fn test_gdsf_size_one_equals_lfu() {
let mut cache = make_gdsf(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
for _ in 0..10 {
cache.get(&1);
}
for _ in 0..5 {
cache.get(&2);
}
cache.put(4, 40, 1);
assert!(
cache.get(&3).is_none(),
"Key 3 should be evicted (lowest freq when size=1)"
);
}
#[test]
fn test_gdsf_frequency_can_overcome_size() {
let mut cache = make_gdsf(3);
cache.put(1, 10, 100);
for _ in 0..500 {
cache.get(&1); }
cache.put(2, 20, 1); cache.put(3, 30, 1);
cache.put(4, 40, 1);
assert!(
cache.get(&1).is_some(),
"Large frequent object should survive"
);
let small_evicted = cache.get(&2).is_none() || cache.get(&3).is_none();
assert!(small_evicted, "A small object should be evicted");
}
#[test]
fn test_gdsf_capacity_one() {
let mut cache = make_gdsf(1);
cache.put(1, 10, 1);
for _ in 0..100 {
cache.get(&1);
}
cache.put(2, 20, 1);
assert!(
cache.get(&1).is_none(),
"Key 1 must be evicted (capacity=1)"
);
}
#[test]
fn test_operations_on_empty_cache() {
let mut lru: LruCache<i32, i32> = make_lru(3);
assert_eq!(lru.get(&1), None);
assert_eq!(lru.remove(&1), None);
lru.clear();
assert_eq!(lru.len(), 0);
}
#[test]
fn test_remove_nonexistent_key() {
let mut cache = make_lru(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
assert_eq!(cache.remove(&99), None);
assert_eq!(cache.len(), 2, "Length should be unchanged");
assert!(cache.get(&1).is_some());
assert!(cache.get(&2).is_some());
}
#[test]
fn test_insert_after_clear() {
let mut cache = make_lru(3);
cache.put(1, 10, 1);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.clear();
assert_eq!(cache.len(), 0);
cache.put(4, 40, 1);
cache.put(5, 50, 1);
assert_eq!(cache.len(), 2);
assert_eq!(cache.get(&4), Some(&40));
assert_eq!(cache.get(&5), Some(&50));
}
#[test]
fn test_rapid_update_same_key() {
let mut cache = make_lru(3);
for i in 0..100 {
cache.put(1, i, 1);
}
assert_eq!(cache.len(), 1, "Should only have 1 entry");
assert_eq!(cache.get(&1), Some(&99), "Should have last value");
}
#[test]
fn test_alternating_keys() {
let mut cache = make_lru(2);
for i in 0..10 {
cache.put(i % 3, i, 1); }
assert_eq!(cache.len(), 2);
}
#[test]
fn test_lfu_get_does_not_exist() {
let mut cache = make_lfu(3);
cache.put(1, 10, 1);
assert_eq!(cache.get(&99), None);
assert_eq!(cache.get(&99), None);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
cache.put(4, 40, 1);
assert!(cache.get(&1).is_none(), "Key 1 should be evicted (FIFO)");
}
#[test]
fn test_gdsf_zero_size_handling() {
let mut cache = make_gdsf(3);
cache.put(1, 10, 0);
cache.put(2, 20, 1);
cache.put(3, 30, 1);
assert!(cache.len() <= 3);
cache.put(4, 40, 1);
assert!(cache.len() <= 3);
}
#[test]
fn test_metrics_size_tracking_no_underflow() {
let mut cache = make_lru_with_limits(2, 1000);
cache.put(1, "a", 1); cache.put(2, "b", 1);
assert_eq!(cache.current_size(), 2, "Should track 2 bytes");
cache.put(3, "c", 1);
assert_eq!(cache.len(), 2);
assert!(cache.get(&1).is_none(), "Key 1 should be evicted");
assert!(cache.get(&2).is_some(), "Key 2 should remain");
assert!(cache.get(&3).is_some(), "Key 3 should be present");
let size = cache.current_size();
assert!(size <= 1000, "Size should not have underflowed: {}", size);
}
#[test]
fn test_metrics_size_mismatch_on_eviction() {
let mut cache: LruCache<i32, String> = make_lru_with_limits(3, 10000);
cache.put(1, "hello world this is a long string".to_string(), 1);
cache.put(2, "another fairly long string value".to_string(), 1);
cache.put(3, "yet another long string for testing".to_string(), 1);
for i in 4..10 {
cache.put(i, format!("value number {}", i), 1);
}
assert_eq!(cache.len(), 3);
let size = cache.current_size();
assert!(size < 1000, "Size should not have underflowed: {}", size);
}
fn make_slru_with_limits<K: std::hash::Hash + Eq + Clone, V: Clone>(
cap: usize,
protected_cap: usize,
max_size: u64,
) -> SlruCache<K, V> {
let config = SlruCacheConfig {
capacity: NonZeroUsize::new(cap).unwrap(),
protected_capacity: NonZeroUsize::new(protected_cap).unwrap(),
max_size,
};
SlruCache::init(config, None)
}
#[test]
fn test_lru_max_size_triggers_eviction() {
let mut cache: LruCache<String, i32> = make_lru_with_limits(1000, 100);
cache.put("a".to_string(), 1, 30); cache.put("b".to_string(), 2, 30); cache.put("c".to_string(), 3, 30);
assert_eq!(cache.len(), 3, "Should have 3 items");
assert_eq!(cache.current_size(), 90, "Size should be 90");
cache.put("d".to_string(), 4, 20);
assert!(
cache.current_size() <= 100,
"LRU should respect max_size limit. current_size={}, max_size=100",
cache.current_size()
);
assert!(
cache.get(&"a".to_string()).is_none(),
"Item 'a' should be evicted when max_size exceeded"
);
assert!(
cache.get(&"b".to_string()).is_some(),
"Item 'b' should remain"
);
assert!(
cache.get(&"c".to_string()).is_some(),
"Item 'c' should remain"
);
assert!(
cache.get(&"d".to_string()).is_some(),
"Item 'd' should be inserted"
);
}
#[test]
fn test_slru_max_size_triggers_eviction() {
let mut cache: SlruCache<String, i32> = make_slru_with_limits(1000, 200, 100);
cache.put("a".to_string(), 1, 30); cache.put("b".to_string(), 2, 30); cache.put("c".to_string(), 3, 30);
assert_eq!(cache.len(), 3, "Should have 3 items");
assert_eq!(cache.current_size(), 90, "Size should be 90");
cache.put("d".to_string(), 4, 20);
assert!(
cache.current_size() <= 100,
"current_size {} exceeds max_size 100",
cache.current_size()
);
}
#[test]
fn test_lfu_max_size_triggers_eviction() {
let mut cache: LfuCache<String, i32> = make_lfu_with_max_size(100);
cache.put("a".to_string(), 1, 30);
cache.put("b".to_string(), 2, 30);
cache.put("c".to_string(), 3, 30);
assert_eq!(cache.current_size(), 90);
cache.put("d".to_string(), 4, 20);
assert!(
cache.current_size() <= 100,
"LFU should respect max_size limit. current_size={}, max_size=100",
cache.current_size()
);
}
#[test]
fn test_lfuda_max_size_triggers_eviction() {
let mut cache: LfudaCache<String, i32> = make_lfuda_with_max_size(100);
cache.put("a".to_string(), 1, 30);
cache.put("b".to_string(), 2, 30);
cache.put("c".to_string(), 3, 30);
assert_eq!(cache.current_size(), 90);
cache.put("d".to_string(), 4, 20);
assert!(
cache.current_size() <= 100,
"LFUDA should respect max_size limit. current_size={}, max_size=100",
cache.current_size()
);
}
#[test]
fn test_slru_max_size_should_evict_multiple_items() {
let mut cache: SlruCache<String, i32> = make_slru_with_limits(1000, 200, 100);
for i in 0..10 {
cache.put(format!("key{}", i), i, 10); }
assert_eq!(cache.len(), 10);
assert_eq!(cache.current_size(), 100, "Cache should be at max_size");
cache.put("big".to_string(), 999, 50);
assert!(
cache.current_size() <= 100,
"SLRU BUG: current_size {} exceeds max_size 100 after inserting large item. \
Multiple items should be evicted to make room.",
cache.current_size()
);
}
#[test]
fn test_lru_get_mut_updates_value() {
let mut cache: LruCache<&str, i32> = make_lru(3);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
if let Some(v) = cache.get_mut(&"b") {
*v = 20;
}
assert_eq!(cache.get(&"b"), Some(&20));
cache.put("d", 4, 1); assert!(cache.get(&"a").is_none());
assert_eq!(cache.get(&"b"), Some(&20));
}
#[test]
fn test_lfu_get_mut_updates_value() {
let mut cache: LfuCache<&str, i32> = make_lfu(3);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
if let Some(v) = cache.get_mut(&"b") {
*v = 20;
}
assert_eq!(cache.get(&"b"), Some(&20));
}
#[test]
fn test_lfuda_get_mut_updates_value() {
let mut cache: LfudaCache<&str, i32> = make_lfuda(3);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
if let Some(v) = cache.get_mut(&"b") {
*v = 20;
}
assert_eq!(cache.get(&"b"), Some(&20));
}
#[test]
fn test_slru_get_mut_updates_value() {
let mut cache: SlruCache<&str, i32> = make_slru(5, 2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
if let Some(v) = cache.get_mut(&"b") {
*v = 20;
}
assert_eq!(cache.get(&"b"), Some(&20));
}
#[test]
fn test_gdsf_get_mut_updates_value() {
let mut cache: GdsfCache<&str, i32> = make_gdsf(3);
cache.put("a", 1, OBJECT_SIZE);
cache.put("b", 2, OBJECT_SIZE);
cache.put("c", 3, OBJECT_SIZE);
if let Some(v) = cache.get_mut(&"b") {
*v = 20;
}
assert_eq!(cache.get(&"b"), Some(20));
}
#[test]
fn test_all_caches_contains() {
let mut lru: LruCache<&str, i32> = make_lru(3);
assert!(!lru.contains(&"a"));
lru.put("a", 1, 1);
assert!(lru.contains(&"a"));
lru.remove(&"a");
assert!(!lru.contains(&"a"));
let mut lfu: LfuCache<&str, i32> = make_lfu(3);
assert!(!lfu.contains(&"a"));
lfu.put("a", 1, 1);
assert!(lfu.contains(&"a"));
lfu.remove(&"a");
assert!(!lfu.contains(&"a"));
let mut lfuda: LfudaCache<&str, i32> = make_lfuda(3);
assert!(!lfuda.contains(&"a"));
lfuda.put("a", 1, 1);
assert!(lfuda.contains(&"a"));
lfuda.remove(&"a");
assert!(!lfuda.contains(&"a"));
let mut slru: SlruCache<&str, i32> = make_slru(5, 2);
assert!(!slru.contains(&"a"));
slru.put("a", 1, 1);
assert!(slru.contains(&"a"));
slru.remove(&"a");
assert!(!slru.contains(&"a"));
let mut gdsf: GdsfCache<&str, i32> = make_gdsf(3);
assert!(!gdsf.contains(&"a"));
gdsf.put("a", 1, OBJECT_SIZE);
assert!(gdsf.contains(&"a"));
gdsf.remove(&"a");
assert!(!gdsf.contains(&"a"));
}
#[test]
fn test_contains_after_eviction() {
let mut cache: LruCache<&str, i32> = make_lru(2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
assert!(cache.contains(&"a"));
cache.put("c", 3, 1); assert!(!cache.contains(&"a"));
assert!(cache.contains(&"b"));
assert!(cache.contains(&"c"));
}
#[test]
fn test_lru_peek_does_not_affect_eviction() {
let mut cache: LruCache<&str, i32> = make_lru(3);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
assert_eq!(cache.peek(&"a"), Some(&1));
cache.put("d", 4, 1);
assert!(
cache.get(&"a").is_none(),
"peek should NOT prevent eviction of LRU item"
);
}
#[test]
fn test_lfu_peek_does_not_affect_eviction() {
let mut cache: LfuCache<&str, i32> = make_lfu(3);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
cache.get(&"b");
cache.get(&"c");
assert_eq!(cache.peek(&"a"), Some(&1));
cache.put("d", 4, 1);
assert!(
cache.get(&"a").is_none(),
"peek should NOT increase frequency"
);
}
#[test]
fn test_lfuda_peek_does_not_affect_eviction() {
let mut cache: LfudaCache<&str, i32> = make_lfuda(3);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
cache.get(&"b");
cache.get(&"c");
assert_eq!(cache.peek(&"a"), Some(&1));
cache.put("d", 4, 1);
assert!(
cache.get(&"a").is_none(),
"peek should NOT increase priority in LFUDA"
);
}
#[test]
fn test_slru_peek_does_not_affect_eviction() {
let mut cache: SlruCache<&str, i32> = make_slru(5, 2);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
cache.put("d", 4, 1);
cache.put("e", 5, 1);
assert_eq!(cache.peek(&"a"), Some(&1));
cache.put("f", 6, 1);
assert!(
cache.get(&"a").is_none(),
"peek should NOT promote items in SLRU"
);
}
#[test]
fn test_gdsf_peek_does_not_affect_eviction() {
let mut cache: GdsfCache<&str, i32> = make_gdsf(3);
cache.put("a", 1, OBJECT_SIZE);
cache.put("b", 2, OBJECT_SIZE);
cache.put("c", 3, OBJECT_SIZE);
cache.get(&"b");
cache.get(&"c");
assert_eq!(cache.peek(&"a"), Some(&1));
cache.put("d", 4, OBJECT_SIZE);
assert!(
cache.get(&"a").is_none(),
"peek should NOT increase frequency in GDSF"
);
}
#[test]
fn test_peek_nonexistent_key() {
let mut cache: LruCache<&str, i32> = make_lru(3);
assert_eq!(cache.peek(&"missing"), None);
cache.put("a", 1, 1);
assert_eq!(cache.peek(&"missing"), None);
assert_eq!(cache.peek(&"a"), Some(&1));
}
#[test]
fn test_all_caches_cap() {
let lru: LruCache<&str, i32> = make_lru(10);
assert_eq!(lru.cap().get(), 10);
let lfu: LfuCache<&str, i32> = make_lfu(20);
assert_eq!(lfu.cap().get(), 20);
let lfuda: LfudaCache<&str, i32> = make_lfuda(30);
assert_eq!(lfuda.cap().get(), 30);
let slru: SlruCache<&str, i32> = make_slru(40, 10);
assert_eq!(slru.cap().get(), 40);
let gdsf: GdsfCache<&str, i32> = make_gdsf(50);
assert_eq!(gdsf.cap().get(), 50);
}
#[test]
fn test_all_caches_record_miss() {
let mut lru: LruCache<&str, i32> = make_lru(3);
lru.put("a", 1, 1);
lru.record_miss(100);
lru.record_miss(200);
assert_eq!(lru.len(), 1); let metrics = lru.metrics();
assert!(
*metrics.get("requests").unwrap_or(&0.0) >= 2.0,
"LRU should track miss requests"
);
let mut lfu: LfuCache<&str, i32> = make_lfu(3);
lfu.put("a", 1, 1);
lfu.record_miss(100);
assert_eq!(lfu.len(), 1);
let mut lfuda: LfudaCache<&str, i32> = make_lfuda(3);
lfuda.put("a", 1, 1);
lfuda.record_miss(100);
assert_eq!(lfuda.len(), 1);
let mut slru: SlruCache<&str, i32> = make_slru(5, 2);
slru.put("a", 1, 1);
slru.record_miss(100);
assert_eq!(slru.len(), 1);
let mut gdsf: GdsfCache<&str, i32> = make_gdsf(3);
gdsf.put("a", 1, OBJECT_SIZE);
gdsf.record_miss(100);
assert_eq!(gdsf.len(), 1);
}
#[test]
fn test_all_caches_algorithm_name() {
let lru: LruCache<&str, i32> = make_lru(3);
assert_eq!(lru.algorithm_name(), "LRU");
let lfu: LfuCache<&str, i32> = make_lfu(3);
assert_eq!(lfu.algorithm_name(), "LFU");
let lfuda: LfudaCache<&str, i32> = make_lfuda(3);
assert_eq!(lfuda.algorithm_name(), "LFUDA");
let slru: SlruCache<&str, i32> = make_slru(5, 2);
assert_eq!(slru.algorithm_name(), "SLRU");
let gdsf: GdsfCache<&str, i32> = make_gdsf(3);
assert_eq!(gdsf.algorithm_name(), "GDSF");
}
#[test]
fn test_all_caches_metrics_after_operations() {
let mut lru: LruCache<&str, i32> = make_lru(2);
lru.put("a", 1, 1);
lru.put("b", 2, 1);
lru.get(&"a"); lru.put("c", 3, 1);
let metrics = lru.metrics();
assert!(
metrics.contains_key("cache_hits"),
"LRU metrics should have 'cache_hits'"
);
assert!(
metrics.contains_key("evictions"),
"LRU metrics should have 'evictions'"
);
assert!(
*metrics.get("cache_hits").unwrap() >= 1.0,
"LRU should record at least 1 hit"
);
assert!(
*metrics.get("evictions").unwrap() >= 1.0,
"LRU should record at least 1 eviction"
);
let mut lfu: LfuCache<&str, i32> = make_lfu(2);
lfu.put("a", 1, 1);
lfu.put("b", 2, 1);
lfu.get(&"a");
lfu.put("c", 3, 1);
let metrics = lfu.metrics();
assert!(
metrics.contains_key("cache_hits"),
"LFU metrics should have 'cache_hits'"
);
let mut lfuda: LfudaCache<&str, i32> = make_lfuda(2);
lfuda.put("a", 1, 1);
lfuda.put("b", 2, 1);
lfuda.get(&"a");
lfuda.put("c", 3, 1);
let metrics = lfuda.metrics();
assert!(
metrics.contains_key("cache_hits"),
"LFUDA metrics should have 'cache_hits'"
);
let mut slru: SlruCache<&str, i32> = make_slru(3, 1);
slru.put("a", 1, 1);
slru.put("b", 2, 1);
slru.get(&"a");
let metrics = slru.metrics();
assert!(
metrics.contains_key("cache_hits"),
"SLRU metrics should have 'cache_hits'"
);
let mut gdsf: GdsfCache<&str, i32> = make_gdsf(2);
gdsf.put("a", 1, OBJECT_SIZE);
gdsf.put("b", 2, OBJECT_SIZE);
gdsf.get(&"a");
gdsf.put("c", 3, OBJECT_SIZE);
let metrics = gdsf.metrics();
assert!(
metrics.contains_key("cache_hits"),
"GDSF metrics should have 'cache_hits'"
);
}
#[test]
#[should_panic(expected = "not yet implemented")]
fn test_lru_iter_is_unimplemented() {
let mut cache: LruCache<&str, i32> = make_lru(3);
cache.put("a", 1, 1);
let _iter = cache.iter();
}
#[test]
#[should_panic(expected = "not yet implemented")]
fn test_lru_iter_mut_is_unimplemented() {
let mut cache: LruCache<&str, i32> = make_lru(3);
cache.put("a", 1, 1);
let _iter = cache.iter_mut();
}
#[test]
fn test_lfuda_global_age_increases_on_eviction() {
let mut cache: LfudaCache<&str, i32> = make_lfuda(2);
assert_eq!(cache.global_age(), 0);
cache.put("a", 1, 1);
cache.put("b", 2, 1);
cache.put("c", 3, 1);
let _age_after = cache.global_age();
}
#[test]
fn test_gdsf_global_age_increases_on_eviction() {
let mut cache: GdsfCache<&str, i32> = make_gdsf(2);
assert!((cache.global_age() - 0.0).abs() < f64::EPSILON);
cache.put("a", 1, OBJECT_SIZE);
cache.put("b", 2, OBJECT_SIZE);
cache.put("c", 3, OBJECT_SIZE);
let age_after = cache.global_age();
assert!(
age_after >= 0.0,
"GDSF global_age should be non-negative after eviction"
);
}
#[test]
fn test_slru_protected_max_size() {
let cache: SlruCache<&str, i32> = make_slru(10, 4);
assert_eq!(cache.protected_max_size().get(), 4);
let cache2: SlruCache<&str, i32> = make_slru(100, 25);
assert_eq!(cache2.protected_max_size().get(), 25);
}