use std::collections::VecDeque;
use std::hash::Hash;
#[cfg(feature = "concurrency")]
use std::sync::Arc;
use crate::prelude::ReadOnlyCache;
use crate::store::hashmap::HashMapStore;
use crate::store::traits::{StoreCore, StoreMut};
#[cfg(feature = "concurrency")]
use crate::traits::ConcurrentCache;
use crate::traits::{CoreCache, FifoCacheTrait};
#[cfg(feature = "concurrency")]
use parking_lot::RwLock;
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::CacheMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::CacheMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{
CoreMetricsRecorder, FifoMetricsReadRecorder, FifoMetricsRecorder, MetricsSnapshotProvider,
};
#[must_use]
#[derive(Debug)]
pub struct FifoCache<K, V> {
inner: FifoCacheInner<K, V>,
}
#[derive(Debug)]
struct FifoCacheInner<K, V> {
store: HashMapStore<K, V>,
insertion_order: VecDeque<K>,
#[cfg(feature = "metrics")]
metrics: CacheMetrics,
}
impl<K, V> FifoCacheInner<K, V>
where
K: Clone + Eq + Hash,
{
fn new(capacity: usize) -> Self {
Self {
store: HashMapStore::new(capacity),
insertion_order: VecDeque::with_capacity(capacity),
#[cfg(feature = "metrics")]
metrics: CacheMetrics::default(),
}
}
}
impl<K, V> Clone for FifoCache<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
fn clone(&self) -> Self {
Self {
inner: FifoCacheInner {
store: self.inner.store.clone(),
insertion_order: self.inner.insertion_order.clone(),
#[cfg(feature = "metrics")]
metrics: CacheMetrics::default(),
},
}
}
}
impl<K, V> Default for FifoCache<K, V>
where
K: Eq + Hash,
{
fn default() -> Self {
Self {
inner: FifoCacheInner {
store: HashMapStore::new(0),
insertion_order: VecDeque::new(),
#[cfg(feature = "metrics")]
metrics: CacheMetrics::default(),
},
}
}
}
impl<K, V> FifoCache<K, V>
where
K: Clone + Eq + Hash,
{
pub fn new(capacity: usize) -> Self {
Self {
inner: FifoCacheInner::new(capacity),
}
}
pub fn try_new(capacity: usize) -> Result<Self, crate::error::ConfigError> {
if capacity == 0 {
return Err(crate::error::ConfigError::new(
"cache capacity must be greater than zero",
));
}
Ok(Self::new(capacity))
}
pub fn queue_len(&self) -> usize {
self.inner.insertion_order.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.inner
.insertion_order
.iter()
.filter_map(|k| self.inner.store.peek(k).map(|v| (k, v)))
}
pub fn keys_by_age(&self) -> impl Iterator<Item = &K> {
self.inner.insertion_order.iter()
}
#[cfg(test)]
pub fn remove_from_cache_only(&mut self, key: &K) -> Option<V> {
self.inner.store.remove(key)
}
#[cfg(test)]
pub fn cache_capacity(&self) -> usize {
self.inner.store.map_capacity()
}
#[cfg(test)]
pub fn insertion_order_capacity(&self) -> usize {
self.inner.insertion_order.capacity()
}
fn evict_oldest(&mut self) {
while let Some(oldest_key) = self.inner.insertion_order.pop_front() {
#[cfg(feature = "metrics")]
self.inner.metrics.record_evict_scan_step();
if self.inner.store.contains(&oldest_key) {
self.inner.store.remove(&oldest_key);
self.inner.store.record_eviction();
#[cfg(feature = "metrics")]
self.inner.metrics.record_evicted_entry();
break;
}
#[cfg(feature = "metrics")]
self.inner.metrics.record_stale_skip();
}
}
}
#[cfg(feature = "concurrency")]
#[derive(Clone, Debug)]
pub struct ConcurrentFifoCache<K, V> {
inner: Arc<RwLock<FifoCache<K, V>>>,
}
#[cfg(feature = "concurrency")]
impl<K, V> Default for ConcurrentFifoCache<K, V>
where
K: Eq + Hash,
{
fn default() -> Self {
Self {
inner: Arc::new(RwLock::new(FifoCache::default())),
}
}
}
#[cfg(feature = "concurrency")]
impl<K, V> ConcurrentFifoCache<K, V>
where
K: Clone + Eq + Hash,
{
pub fn new(capacity: usize) -> Self {
Self {
inner: Arc::new(RwLock::new(FifoCache::new(capacity))),
}
}
pub fn insert(&self, key: K, value: V) -> Option<V> {
let mut cache = self.inner.write();
cache.insert(key, value)
}
pub fn get(&self, key: &K) -> Option<V>
where
V: Clone,
{
let mut cache = self.inner.write();
cache.get(key).cloned()
}
pub fn peek(&self, key: &K) -> Option<V>
where
V: Clone,
{
let cache = self.inner.read();
cache.inner.store.peek(key).cloned()
}
pub fn contains(&self, key: &K) -> bool {
let cache = self.inner.read();
cache.contains(key)
}
pub fn len(&self) -> usize {
let cache = self.inner.read();
cache.len()
}
pub fn is_empty(&self) -> bool {
let cache = self.inner.read();
cache.is_empty()
}
pub fn capacity(&self) -> usize {
let cache = self.inner.read();
cache.capacity()
}
pub fn clear(&self) {
let mut cache = self.inner.write();
cache.clear();
}
pub fn pop_oldest(&self) -> Option<(K, V)> {
let mut cache = self.inner.write();
cache.pop_oldest()
}
pub fn peek_oldest(&self) -> Option<(K, V)>
where
K: Clone,
V: Clone,
{
let cache = self.inner.read();
cache.peek_oldest().map(|(k, v)| (k.clone(), v.clone()))
}
pub fn pop_oldest_batch(&self, count: usize) -> Vec<(K, V)> {
let mut cache = self.inner.write();
cache.pop_oldest_batch(count)
}
pub fn age_rank(&self, key: &K) -> Option<usize> {
let cache = self.inner.read();
cache.age_rank(key)
}
}
#[cfg(feature = "concurrency")]
unsafe impl<K, V> ConcurrentCache for ConcurrentFifoCache<K, V>
where
K: Clone + Eq + Hash + Send + Sync,
V: Send + Sync,
{
}
impl<K, V> ReadOnlyCache<K, V> for FifoCache<K, V>
where
K: Eq + Hash,
{
fn contains(&self, key: &K) -> bool {
self.inner.store.contains(key)
}
fn len(&self) -> usize {
self.inner.store.len()
}
fn capacity(&self) -> usize {
self.inner.store.capacity()
}
}
impl<K, V> CoreCache<K, V> for FifoCache<K, V>
where
K: Clone + Eq + Hash,
{
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
self.inner.metrics.record_insert_call();
if self.inner.store.capacity() == 0 {
return None;
}
if self.inner.store.contains(&key) {
#[cfg(feature = "metrics")]
self.inner.metrics.record_insert_update();
if let Ok(previous) = self.inner.store.try_insert(key, value) {
return previous;
}
return None;
}
#[cfg(feature = "metrics")]
self.inner.metrics.record_insert_new();
if self.inner.store.len() >= self.inner.store.capacity() {
#[cfg(feature = "metrics")]
self.inner.metrics.record_evict_call();
self.evict_oldest();
}
let key_for_queue = key.clone();
if self.inner.store.try_insert(key, value).is_ok() {
self.inner.insertion_order.push_back(key_for_queue);
}
None
}
fn get(&mut self, key: &K) -> Option<&V> {
match self.inner.store.get(key) {
Some(value) => {
#[cfg(feature = "metrics")]
self.inner.metrics.record_get_hit();
Some(value)
},
None => {
#[cfg(feature = "metrics")]
self.inner.metrics.record_get_miss();
None
},
}
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.inner.metrics.record_clear();
self.inner.store.clear();
self.inner.insertion_order.clear();
}
}
impl<K, V> FifoCacheTrait<K, V> for FifoCache<K, V>
where
K: Clone + Eq + Hash,
{
fn pop_oldest(&mut self) -> Option<(K, V)> {
#[cfg(feature = "metrics")]
self.inner.metrics.record_pop_oldest_call();
while let Some(oldest_key) = self.inner.insertion_order.pop_front() {
if let Some(value) = self.inner.store.remove(&oldest_key) {
self.inner.store.record_eviction();
#[cfg(feature = "metrics")]
self.inner.metrics.record_pop_oldest_found();
return Some((oldest_key, value));
}
}
#[cfg(feature = "metrics")]
self.inner.metrics.record_pop_oldest_empty_or_stale();
None
}
fn peek_oldest(&self) -> Option<(&K, &V)> {
#[cfg(feature = "metrics")]
(&self.inner.metrics).record_peek_oldest_call();
for key in &self.inner.insertion_order {
if let Some(value) = self.inner.store.peek(key) {
#[cfg(feature = "metrics")]
(&self.inner.metrics).record_peek_oldest_found();
return Some((key, value));
}
}
None
}
fn pop_oldest_batch_into(&mut self, count: usize, out: &mut Vec<(K, V)>) {
out.reserve(count.min(self.len()));
for _ in 0..count {
match self.pop_oldest() {
Some(entry) => out.push(entry),
None => break,
}
}
}
fn age_rank(&self, key: &K) -> Option<usize> {
#[cfg(feature = "metrics")]
(&self.inner.metrics).record_age_rank_call();
let mut rank = 0;
for insertion_key in &self.inner.insertion_order {
#[cfg(feature = "metrics")]
(&self.inner.metrics).record_age_rank_scan_step();
if self.inner.store.contains(insertion_key) {
if insertion_key == key {
#[cfg(feature = "metrics")]
(&self.inner.metrics).record_age_rank_found();
return Some(rank);
}
rank += 1;
}
}
None
}
}
#[cfg(feature = "metrics")]
impl<K, V> FifoCache<K, V>
where
K: Clone + Eq + Hash,
{
pub fn metrics_snapshot(&self) -> CacheMetricsSnapshot {
CacheMetricsSnapshot {
get_calls: self.inner.metrics.get_calls,
get_hits: self.inner.metrics.get_hits,
get_misses: self.inner.metrics.get_misses,
insert_calls: self.inner.metrics.insert_calls,
insert_updates: self.inner.metrics.insert_updates,
insert_new: self.inner.metrics.insert_new,
evict_calls: self.inner.metrics.evict_calls,
evicted_entries: self.inner.metrics.evicted_entries,
stale_skips: self.inner.metrics.stale_skips,
evict_scan_steps: self.inner.metrics.evict_scan_steps,
pop_oldest_calls: self.inner.metrics.pop_oldest_calls,
pop_oldest_found: self.inner.metrics.pop_oldest_found,
pop_oldest_empty_or_stale: self.inner.metrics.pop_oldest_empty_or_stale,
peek_oldest_calls: self.inner.metrics.peek_oldest_calls.get(),
peek_oldest_found: self.inner.metrics.peek_oldest_found.get(),
age_rank_calls: self.inner.metrics.age_rank_calls.get(),
age_rank_found: self.inner.metrics.age_rank_found.get(),
age_rank_scan_steps: self.inner.metrics.age_rank_scan_steps.get(),
cache_len: self.inner.store.len(),
insertion_order_len: self.inner.insertion_order.len(),
capacity: self.inner.store.capacity(),
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<CacheMetricsSnapshot> for FifoCache<K, V>
where
K: Clone + Eq + Hash,
{
fn snapshot(&self) -> CacheMetricsSnapshot {
self.metrics_snapshot()
}
}
#[cfg(all(feature = "metrics", feature = "concurrency"))]
impl<K, V> ConcurrentFifoCache<K, V>
where
K: Clone + Eq + Hash + Send + Sync,
V: Send + Sync,
{
pub fn metrics_snapshot(&self) -> CacheMetricsSnapshot {
let cache = self.inner.read();
cache.metrics_snapshot()
}
}
#[cfg(all(feature = "metrics", feature = "concurrency"))]
impl<K, V> MetricsSnapshotProvider<CacheMetricsSnapshot> for ConcurrentFifoCache<K, V>
where
K: Clone + Eq + Hash + Send + Sync,
V: Send + Sync,
{
fn snapshot(&self) -> CacheMetricsSnapshot {
self.metrics_snapshot()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use crate::policy::fifo::FifoCache;
use crate::traits::{CoreCache, FifoCacheTrait, ReadOnlyCache};
fn _assert_send<T: Send>() {}
fn _assert_sync<T: Sync>() {}
#[test]
fn fifo_cache_is_send_and_sync() {
_assert_send::<FifoCache<String, String>>();
_assert_sync::<FifoCache<String, String>>();
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_fifo_cache_is_send_and_sync() {
use crate::policy::fifo::ConcurrentFifoCache;
_assert_send::<ConcurrentFifoCache<String, String>>();
_assert_sync::<ConcurrentFifoCache<String, String>>();
}
mod basic_behavior {
use super::*;
#[test]
fn test_basic_fifo_insertion_and_retrieval() {
let mut cache = FifoCache::new(3);
assert_eq!(cache.insert("key1", "value1"), None);
assert_eq!(cache.insert("key2", "value2"), None);
assert_eq!(cache.insert("key3", "value3"), None);
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.get(&"key2"), Some(&"value2"));
assert_eq!(cache.get(&"key3"), Some(&"value3"));
assert_eq!(cache.len(), 3);
}
#[test]
fn test_fifo_eviction_order() {
let mut cache = FifoCache::new(2);
cache.insert("first", "value1");
cache.insert("second", "value2");
assert_eq!(cache.len(), 2);
cache.insert("third", "value3");
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&"first")); assert!(cache.contains(&"second"));
assert!(cache.contains(&"third"));
}
#[test]
fn test_capacity_enforcement() {
let mut cache = FifoCache::new(3);
for i in 1..=5 {
cache.insert(format!("key{}", i), format!("value{}", i));
}
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&"key1".to_string()));
assert!(!cache.contains(&"key2".to_string()));
assert!(cache.contains(&"key3".to_string()));
assert!(cache.contains(&"key4".to_string()));
assert!(cache.contains(&"key5".to_string()));
}
#[test]
fn test_update_existing_key() {
let mut cache = FifoCache::new(3);
cache.insert("key1", "original");
cache.insert("key2", "value2");
let old_value = cache.insert("key1", "updated");
assert_eq!(old_value, Some("original"));
assert_eq!(cache.get(&"key1"), Some(&"updated"));
assert_eq!(cache.len(), 2); }
#[test]
fn test_insertion_order_preservation() {
let mut cache = FifoCache::new(4);
cache.insert("first", 1);
cache.insert("second", 2);
cache.insert("third", 3);
cache.insert("fourth", 4);
assert_eq!(cache.peek_oldest(), Some((&"first", &1)));
assert_eq!(cache.age_rank(&"first"), Some(0)); assert_eq!(cache.age_rank(&"second"), Some(1));
assert_eq!(cache.age_rank(&"third"), Some(2));
assert_eq!(cache.age_rank(&"fourth"), Some(3));
assert_eq!(cache.pop_oldest(), Some(("first", 1)));
assert_eq!(cache.peek_oldest(), Some((&"second", &2)));
assert_eq!(cache.age_rank(&"second"), Some(0)); assert_eq!(cache.age_rank(&"third"), Some(1));
assert_eq!(cache.age_rank(&"fourth"), Some(2));
cache.insert("fifth", 5);
assert_eq!(cache.age_rank(&"second"), Some(0));
assert_eq!(cache.age_rank(&"third"), Some(1));
assert_eq!(cache.age_rank(&"fourth"), Some(2));
assert_eq!(cache.age_rank(&"fifth"), Some(3));
cache.insert("sixth", 6);
assert!(!cache.contains(&"second"));
assert_eq!(cache.peek_oldest(), Some((&"third", &3)));
assert_eq!(cache.age_rank(&"third"), Some(0)); assert_eq!(cache.age_rank(&"fourth"), Some(1));
assert_eq!(cache.age_rank(&"fifth"), Some(2));
assert_eq!(cache.age_rank(&"sixth"), Some(3)); }
#[test]
fn test_key_operations_consistency() {
let mut cache = FifoCache::new(3);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key1"));
assert_eq!(cache.get(&"key1"), None);
cache.insert("key1", "value1");
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"key1"));
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert!(!cache.contains(&"key2"));
assert_eq!(cache.get(&"key2"), None);
cache.insert("key2", "value2");
assert_eq!(cache.len(), 2);
assert!(cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.get(&"key2"), Some(&"value2"));
let old_value = cache.insert("key1", "updated_value1");
assert_eq!(old_value, Some("value1"));
assert_eq!(cache.len(), 2); assert!(cache.contains(&"key1"));
assert_eq!(cache.get(&"key1"), Some(&"updated_value1"));
assert_eq!(cache.get(&"key2"), Some(&"value2"));
cache.insert("key3", "value3");
assert_eq!(cache.len(), 3);
assert_eq!(cache.capacity(), 3);
assert!(cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
assert!(cache.contains(&"key3"));
cache.insert("key4", "value4");
assert_eq!(cache.len(), 3); assert!(!cache.contains(&"key1")); assert!(cache.contains(&"key2")); assert!(cache.contains(&"key3"));
assert!(cache.contains(&"key4"));
assert_eq!(cache.get(&"key1"), None);
assert_eq!(cache.get(&"key4"), Some(&"value4"));
let popped = cache.pop_oldest();
assert_eq!(popped, Some(("key2", "value2")));
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&"key2"));
assert_eq!(cache.get(&"key2"), None);
assert!(cache.contains(&"key3"));
assert!(cache.contains(&"key4"));
cache.clear();
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key2"));
assert!(!cache.contains(&"key3"));
assert!(!cache.contains(&"key4"));
assert_eq!(cache.get(&"key2"), None);
assert_eq!(cache.get(&"key3"), None);
assert_eq!(cache.get(&"key4"), None);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.pop_oldest(), None);
}
}
mod edge_cases {
use super::*;
#[test]
fn test_empty_cache_operations() {
let mut cache: FifoCache<String, String> = FifoCache::new(5);
assert_eq!(cache.get(&"nonexistent".to_string()), None);
assert!(!cache.contains(&"nonexistent".to_string()));
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 5);
cache.clear();
assert_eq!(cache.len(), 0);
}
#[test]
fn test_single_item_cache() {
let mut cache = FifoCache::new(1);
cache.insert("only", "value1");
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&"only"), Some(&"value1"));
cache.insert("second", "value2");
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"only"));
assert!(cache.contains(&"second"));
}
#[test]
fn test_zero_capacity_cache() {
let mut cache = FifoCache::new(0);
cache.insert("key", "value");
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key"));
assert_eq!(cache.get(&"key"), None);
}
#[test]
fn test_clear_operation() {
let mut cache = FifoCache::new(3);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key1"));
assert!(!cache.contains(&"key2"));
assert_eq!(cache.get(&"key1"), None);
}
#[test]
fn test_duplicate_key_handling() {
let mut cache = FifoCache::new(3);
assert_eq!(cache.insert("key1", "value1"), None);
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"key1"));
assert_eq!(cache.get(&"key1"), Some(&"value1"));
assert_eq!(cache.insert("key1", "value1_updated"), Some("value1"));
assert_eq!(cache.len(), 1); assert_eq!(cache.get(&"key1"), Some(&"value1_updated"));
cache.insert("key2", "value2");
cache.insert("key3", "value3");
assert_eq!(cache.len(), 3);
assert_eq!(cache.insert("key2", "value2_v2"), Some("value2"));
assert_eq!(cache.insert("key2", "value2_v3"), Some("value2_v2"));
assert_eq!(cache.len(), 3); assert_eq!(cache.get(&"key2"), Some(&"value2_v3"));
assert_eq!(cache.age_rank(&"key1"), Some(0)); assert_eq!(cache.age_rank(&"key2"), Some(1));
assert_eq!(cache.age_rank(&"key3"), Some(2));
cache.insert("key4", "value4");
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&"key1")); assert!(cache.contains(&"key2"));
assert!(cache.contains(&"key3"));
assert!(cache.contains(&"key4"));
assert_eq!(cache.insert("key2", "value2_final"), Some("value2_v3"));
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&"key2"), Some(&"value2_final"));
let mut found_keys = HashSet::new();
let mut count = 0;
while let Some((key, _)) = cache.pop_oldest() {
assert!(found_keys.insert(key), "Duplicate key found: {}", key);
count += 1;
}
assert_eq!(count, 3); }
#[test]
fn test_boundary_conditions() {
let mut cache = FifoCache::new(2);
cache.insert("key1", "value1");
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"key2"));
cache.insert("key2", "value2");
assert_eq!(cache.len(), 2); assert_eq!(cache.capacity(), 2);
assert!(cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
assert_eq!(cache.peek_oldest(), Some((&"key1", &"value1")));
assert_eq!(cache.age_rank(&"key1"), Some(0));
assert_eq!(cache.age_rank(&"key2"), Some(1));
cache.insert("key3", "value3");
assert_eq!(cache.len(), 2); assert!(!cache.contains(&"key1")); assert!(cache.contains(&"key2"));
assert!(cache.contains(&"key3"));
assert_eq!(cache.insert("key2", "value2_updated"), Some("value2"));
assert_eq!(cache.len(), 2); assert_eq!(cache.get(&"key2"), Some(&"value2_updated"));
assert_eq!(cache.pop_oldest(), Some(("key2", "value2_updated")));
assert_eq!(cache.len(), 1);
assert_eq!(cache.pop_oldest(), Some(("key3", "value3")));
assert_eq!(cache.len(), 0);
assert_eq!(cache.pop_oldest(), None);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.len(), 0);
cache.insert("new1", "new_val1");
cache.insert("new2", "new_val2");
assert_eq!(cache.len(), 2);
let batch = cache.pop_oldest_batch(3); assert_eq!(batch.len(), 2); assert_eq!(cache.len(), 0); }
#[test]
fn test_empty_to_full_transition() {
let mut cache = FifoCache::new(4);
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 4);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.pop_oldest(), None);
cache.insert("item1", 1);
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"item1"));
assert_eq!(cache.peek_oldest(), Some((&"item1", &1)));
assert_eq!(cache.age_rank(&"item1"), Some(0));
cache.insert("item2", 2);
assert_eq!(cache.len(), 2);
assert!(cache.contains(&"item1"));
assert!(cache.contains(&"item2"));
assert_eq!(cache.peek_oldest(), Some((&"item1", &1))); assert_eq!(cache.age_rank(&"item1"), Some(0));
assert_eq!(cache.age_rank(&"item2"), Some(1));
cache.insert("item3", 3);
assert_eq!(cache.len(), 3);
assert_eq!(cache.peek_oldest(), Some((&"item1", &1)));
assert_eq!(cache.age_rank(&"item1"), Some(0));
assert_eq!(cache.age_rank(&"item2"), Some(1));
assert_eq!(cache.age_rank(&"item3"), Some(2));
cache.insert("item4", 4);
assert_eq!(cache.len(), 4); assert_eq!(cache.capacity(), 4);
assert!(cache.contains(&"item1"));
assert_eq!(cache.get(&"item1"), Some(&1));
assert_eq!(cache.age_rank(&"item1"), Some(0));
assert!(cache.contains(&"item2"));
assert_eq!(cache.get(&"item2"), Some(&2));
assert_eq!(cache.age_rank(&"item2"), Some(1));
assert!(cache.contains(&"item3"));
assert_eq!(cache.get(&"item3"), Some(&3));
assert_eq!(cache.age_rank(&"item3"), Some(2));
assert!(cache.contains(&"item4"));
assert_eq!(cache.get(&"item4"), Some(&4));
assert_eq!(cache.age_rank(&"item4"), Some(3));
assert_eq!(cache.peek_oldest(), Some((&"item1", &1)));
cache.insert("item5", 5);
assert_eq!(cache.len(), 4); assert!(!cache.contains(&"item1")); assert!(cache.contains(&"item5")); assert_eq!(cache.peek_oldest(), Some((&"item2", &2))); }
#[test]
fn test_full_to_empty_transition() {
let create_test_cache = || {
let mut cache = FifoCache::new(3);
cache.insert("item1", 1);
cache.insert("item2", 2);
cache.insert("item3", 3);
assert_eq!(cache.len(), 3);
assert_eq!(cache.capacity(), 3);
cache
};
let mut emptying_cache = create_test_cache();
assert_eq!(emptying_cache.pop_oldest(), Some(("item1", 1)));
assert_eq!(emptying_cache.len(), 2);
assert!(!emptying_cache.contains(&"item1"));
assert!(emptying_cache.contains(&"item2"));
assert!(emptying_cache.contains(&"item3"));
assert_eq!(emptying_cache.peek_oldest(), Some((&"item2", &2)));
assert_eq!(emptying_cache.pop_oldest(), Some(("item2", 2)));
assert_eq!(emptying_cache.len(), 1);
assert!(!emptying_cache.contains(&"item2"));
assert!(emptying_cache.contains(&"item3"));
assert_eq!(emptying_cache.peek_oldest(), Some((&"item3", &3)));
assert_eq!(emptying_cache.pop_oldest(), Some(("item3", 3)));
assert_eq!(emptying_cache.len(), 0);
assert!(!emptying_cache.contains(&"item3"));
assert_eq!(emptying_cache.peek_oldest(), None);
assert_eq!(emptying_cache.pop_oldest(), None);
assert_eq!(emptying_cache.len(), 0);
let mut batch_cache = create_test_cache();
let batch = batch_cache.pop_oldest_batch(3);
assert_eq!(batch.len(), 3);
assert_eq!(batch[0], ("item1", 1));
assert_eq!(batch[1], ("item2", 2));
assert_eq!(batch[2], ("item3", 3));
assert_eq!(batch_cache.len(), 0);
assert_eq!(batch_cache.peek_oldest(), None);
let mut clear_cache = create_test_cache();
clear_cache.clear();
assert_eq!(clear_cache.len(), 0);
assert_eq!(clear_cache.capacity(), 3); assert_eq!(clear_cache.peek_oldest(), None);
assert_eq!(clear_cache.pop_oldest(), None);
for mut test_cache in [emptying_cache, batch_cache, clear_cache] {
test_cache.insert("new1", 100);
assert_eq!(test_cache.len(), 1);
assert!(test_cache.contains(&"new1"));
assert_eq!(test_cache.peek_oldest(), Some((&"new1", &100)));
}
let mut partial_cache = FifoCache::new(4);
partial_cache.insert("a", 1);
partial_cache.insert("b", 2);
partial_cache.insert("c", 3);
partial_cache.insert("d", 4);
let _ = partial_cache.pop_oldest(); let _ = partial_cache.pop_oldest(); assert_eq!(partial_cache.len(), 2);
partial_cache.insert("e", 5);
partial_cache.insert("f", 6);
assert_eq!(partial_cache.len(), 4);
assert_eq!(partial_cache.peek_oldest(), Some((&"c", &3))); assert_eq!(partial_cache.age_rank(&"c"), Some(0));
assert_eq!(partial_cache.age_rank(&"d"), Some(1));
assert_eq!(partial_cache.age_rank(&"e"), Some(2));
assert_eq!(partial_cache.age_rank(&"f"), Some(3));
}
}
mod trait_methods {
use super::*;
#[test]
fn test_pop_oldest() {
let mut cache = FifoCache::new(3);
cache.insert("first", "value1");
cache.insert("second", "value2");
cache.insert("third", "value3");
assert_eq!(cache.pop_oldest(), Some(("first", "value1")));
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&"first"));
assert_eq!(cache.pop_oldest(), Some(("second", "value2")));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_peek_oldest() {
let mut cache = FifoCache::new(3);
cache.insert("first", "value1");
cache.insert("second", "value2");
assert_eq!(cache.peek_oldest(), Some((&"first", &"value1")));
assert_eq!(cache.len(), 2); assert!(cache.contains(&"first"));
assert_eq!(cache.peek_oldest(), Some((&"first", &"value1")));
}
#[test]
fn test_age_rank() {
let mut cache = FifoCache::new(4);
cache.insert("first", "value1"); cache.insert("second", "value2"); cache.insert("third", "value3"); cache.insert("fourth", "value4");
assert_eq!(cache.age_rank(&"first"), Some(0));
assert_eq!(cache.age_rank(&"second"), Some(1));
assert_eq!(cache.age_rank(&"third"), Some(2));
assert_eq!(cache.age_rank(&"fourth"), Some(3));
assert_eq!(cache.age_rank(&"nonexistent"), None);
}
#[test]
fn test_pop_oldest_batch() {
let mut cache = FifoCache::new(5);
for i in 1..=5 {
cache.insert(format!("key{}", i), format!("value{}", i));
}
let batch = cache.pop_oldest_batch(3);
assert_eq!(batch.len(), 3);
assert_eq!(batch[0], ("key1".to_string(), "value1".to_string()));
assert_eq!(batch[1], ("key2".to_string(), "value2".to_string()));
assert_eq!(batch[2], ("key3".to_string(), "value3".to_string()));
assert_eq!(cache.len(), 2);
assert!(cache.contains(&"key4".to_string()));
assert!(cache.contains(&"key5".to_string()));
}
#[test]
fn test_pop_oldest_batch_more_than_available() {
let mut cache = FifoCache::new(3);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
let batch = cache.pop_oldest_batch(5);
assert_eq!(batch.len(), 2); assert_eq!(cache.len(), 0); }
#[test]
fn test_pop_oldest_empty_cache() {
let mut cache: FifoCache<String, String> = FifoCache::new(5);
assert_eq!(cache.pop_oldest(), None);
assert_eq!(cache.len(), 0);
assert_eq!(cache.pop_oldest(), None);
assert_eq!(cache.pop_oldest(), None);
assert_eq!(cache.len(), 0);
cache.insert("key1".to_string(), "value1".to_string());
assert_eq!(cache.len(), 1);
let popped = cache.pop_oldest();
assert_eq!(popped, Some(("key1".to_string(), "value1".to_string())));
assert_eq!(cache.len(), 0);
assert_eq!(cache.pop_oldest(), None);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_peek_oldest_empty_cache() {
let cache: FifoCache<String, String> = FifoCache::new(5);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.len(), 0);
let mut test_cache = FifoCache::new(3);
test_cache.insert("key1".to_string(), "value1".to_string());
test_cache.insert("key2".to_string(), "value2".to_string());
assert!(test_cache.peek_oldest().is_some());
assert_eq!(test_cache.len(), 2);
test_cache.clear();
assert_eq!(test_cache.peek_oldest(), None);
assert_eq!(test_cache.len(), 0);
}
#[test]
fn test_age_rank_after_eviction() {
let mut cache = FifoCache::new(3);
cache.insert("first", 1);
cache.insert("second", 2);
cache.insert("third", 3);
assert_eq!(cache.age_rank(&"first"), Some(0));
assert_eq!(cache.age_rank(&"second"), Some(1));
assert_eq!(cache.age_rank(&"third"), Some(2));
cache.insert("fourth", 4);
assert_eq!(cache.age_rank(&"first"), None); assert_eq!(cache.age_rank(&"second"), Some(0)); assert_eq!(cache.age_rank(&"third"), Some(1));
assert_eq!(cache.age_rank(&"fourth"), Some(2));
cache.insert("fifth", 5);
assert_eq!(cache.age_rank(&"first"), None); assert_eq!(cache.age_rank(&"second"), None); assert_eq!(cache.age_rank(&"third"), Some(0)); assert_eq!(cache.age_rank(&"fourth"), Some(1));
assert_eq!(cache.age_rank(&"fifth"), Some(2));
let popped = cache.pop_oldest();
assert_eq!(popped, Some(("third", 3)));
assert_eq!(cache.age_rank(&"third"), None); assert_eq!(cache.age_rank(&"fourth"), Some(0)); assert_eq!(cache.age_rank(&"fifth"), Some(1));
cache.insert("sixth", 6);
cache.insert("seventh", 7);
assert_eq!(cache.len(), 3);
let batch = cache.pop_oldest_batch(2);
assert_eq!(batch.len(), 2);
assert_eq!(cache.len(), 1);
assert_eq!(cache.age_rank(&"fourth"), None);
assert_eq!(cache.age_rank(&"fifth"), None);
assert_eq!(cache.age_rank(&"sixth"), None);
assert_eq!(cache.age_rank(&"seventh"), Some(0)); }
#[test]
fn test_batch_operations_edge_cases() {
let mut cache = FifoCache::new(3);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
let batch = cache.pop_oldest_batch(0);
assert_eq!(batch.len(), 0);
assert_eq!(cache.len(), 2); assert!(cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
let mut empty_cache: FifoCache<String, String> = FifoCache::new(5);
let empty_batch = empty_cache.pop_oldest_batch(3);
assert_eq!(empty_batch.len(), 0);
assert_eq!(empty_cache.len(), 0);
let batch_one = cache.pop_oldest_batch(1);
assert_eq!(batch_one.len(), 1);
assert_eq!(batch_one[0], ("key1", "value1"));
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"key1"));
assert!(cache.contains(&"key2"));
cache.insert("key3", "value3");
cache.insert("key4", "value4");
assert_eq!(cache.len(), 3);
let all_batch = cache.pop_oldest_batch(3);
assert_eq!(all_batch.len(), 3);
assert_eq!(all_batch[0], ("key2", "value2"));
assert_eq!(all_batch[1], ("key3", "value3"));
assert_eq!(all_batch[2], ("key4", "value4"));
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_oldest(), None);
assert_eq!(cache.pop_oldest(), None);
cache.insert("only", "item");
let large_batch = cache.pop_oldest_batch(100);
assert_eq!(large_batch.len(), 1);
assert_eq!(large_batch[0], ("only", "item"));
assert_eq!(cache.len(), 0);
cache.insert("a", "1");
cache.insert("b", "2");
cache.clear();
let post_clear_batch = cache.pop_oldest_batch(5);
assert_eq!(post_clear_batch.len(), 0);
assert_eq!(cache.len(), 0);
}
}
mod stale_entries {
use super::*;
#[test]
fn test_stale_entry_skipping_during_eviction() {
let mut cache = FifoCache::new(3);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
cache.insert("key3", "value3");
assert_eq!(cache.len(), 3);
cache.remove_from_cache_only(&"key1");
assert_eq!(cache.len(), 2);
assert_eq!(cache.queue_len(), 3);
assert!(!cache.contains(&"key1")); assert!(cache.contains(&"key2")); assert!(cache.contains(&"key3"));
cache.insert("temp", "temp_value");
assert_eq!(cache.len(), 3);
cache.insert("key4", "value4");
assert_eq!(cache.len(), 3); assert!(!cache.contains(&"key1")); assert!(!cache.contains(&"key2")); assert!(cache.contains(&"key3")); assert!(cache.contains(&"temp")); assert!(cache.contains(&"key4"));
cache.insert("key5", "value5");
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&"key3")); assert!(cache.contains(&"temp"));
assert!(cache.contains(&"key4"));
assert!(cache.contains(&"key5"));
}
#[test]
fn test_insertion_order_consistency_with_stale_entries() {
let mut cache = FifoCache::new(4);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.insert("d", 4);
assert_eq!(cache.age_rank(&"a"), Some(0));
assert_eq!(cache.age_rank(&"b"), Some(1));
assert_eq!(cache.age_rank(&"c"), Some(2));
assert_eq!(cache.age_rank(&"d"), Some(3));
cache.remove_from_cache_only(&"a");
cache.remove_from_cache_only(&"c");
assert_eq!(cache.len(), 2); assert_eq!(cache.queue_len(), 4);
assert_eq!(cache.age_rank(&"a"), None); assert_eq!(cache.age_rank(&"b"), Some(0)); assert_eq!(cache.age_rank(&"c"), None); assert_eq!(cache.age_rank(&"d"), Some(1));
assert_eq!(cache.peek_oldest(), Some((&"b", &2)));
assert_eq!(cache.pop_oldest(), Some(("b", 2)));
assert_eq!(cache.len(), 1);
assert_eq!(cache.age_rank(&"d"), Some(0));
assert_eq!(cache.peek_oldest(), Some((&"d", &4)));
cache.insert("e", 5);
cache.insert("f", 6);
assert_eq!(cache.age_rank(&"d"), Some(0)); assert_eq!(cache.age_rank(&"e"), Some(1));
assert_eq!(cache.age_rank(&"f"), Some(2));
}
#[test]
fn test_lazy_deletion_behavior() {
let mut cache = FifoCache::new(3);
cache.insert("temp1", "value1");
cache.insert("temp2", "value2");
cache.insert("keep", "value_keep");
cache.remove_from_cache_only(&"temp1");
cache.remove_from_cache_only(&"temp2");
assert_eq!(cache.len(), 1); assert_eq!(cache.queue_len(), 3);
assert_eq!(cache.peek_oldest(), Some((&"keep", &"value_keep")));
assert_eq!(cache.age_rank(&"keep"), Some(0));
assert!(!cache.contains(&"temp1"));
assert!(!cache.contains(&"temp2"));
assert!(cache.contains(&"keep"));
cache.insert("new1", "value_new1");
cache.insert("new2", "value_new2");
assert_eq!(cache.len(), 3);
assert_eq!(cache.queue_len(), 5);
assert_eq!(cache.pop_oldest(), Some(("keep", "value_keep")));
assert_eq!(cache.len(), 2);
cache.insert("trigger_eviction", "value_trigger");
assert_eq!(cache.len(), 3);
assert!(cache.contains(&"new1"));
assert!(cache.contains(&"new2"));
assert!(cache.contains(&"trigger_eviction"));
cache.clear();
cache.insert("stale1", "v1");
cache.insert("stale2", "v2");
cache.insert("stale3", "v3");
cache.insert("valid", "valid_value");
cache.remove_from_cache_only(&"stale1");
cache.remove_from_cache_only(&"stale2");
cache.remove_from_cache_only(&"stale3");
assert_eq!(cache.len(), 1);
assert_eq!(cache.peek_oldest(), Some((&"valid", &"valid_value")));
assert_eq!(cache.pop_oldest(), Some(("valid", "valid_value")));
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_oldest(), None);
cache.insert("new_after_stale", "new_value");
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"new_after_stale"));
}
#[test]
fn test_stale_entry_cleanup_during_operations() {
let mut cache = FifoCache::new(4);
cache.insert("will_be_stale1", "stale1");
cache.insert("will_be_stale2", "stale2");
cache.insert("valid1", "value1");
cache.insert("valid2", "value2");
cache.remove_from_cache_only(&"will_be_stale1");
cache.remove_from_cache_only(&"will_be_stale2");
assert_eq!(cache.len(), 2); assert_eq!(cache.queue_len(), 4);
assert_eq!(cache.pop_oldest(), Some(("valid1", "value1")));
assert_eq!(cache.len(), 1);
cache.insert("new1", "new_value1");
cache.insert("new2", "new_value2");
cache.insert("new3", "new_value3");
cache.remove_from_cache_only(&"new1");
cache.remove_from_cache_only(&"new3");
let batch = cache.pop_oldest_batch(2);
assert_eq!(batch.len(), 2);
assert!(batch.contains(&("valid2", "value2")));
assert!(batch.contains(&("new2", "new_value2")));
cache.insert("test1", "test_value1");
cache.insert("test2", "test_value2");
cache.insert("test3", "test_value3");
cache.remove_from_cache_only(&"test2");
assert_eq!(cache.age_rank(&"test1"), Some(0)); assert_eq!(cache.age_rank(&"test2"), None); assert_eq!(cache.age_rank(&"test3"), Some(1));
cache.remove_from_cache_only(&"test1");
assert_eq!(cache.peek_oldest(), Some((&"test3", &"test_value3")));
assert_eq!(cache.len(), 1);
cache.insert("fill1", "f1");
cache.insert("fill2", "f2");
cache.insert("fill3", "f3");
cache.remove_from_cache_only(&"fill1");
cache.remove_from_cache_only(&"fill2");
assert_eq!(cache.len(), 2);
cache.insert("temp1", "t1");
cache.insert("temp2", "t2");
assert_eq!(cache.len(), 4);
cache.insert("trigger", "trigger_value");
assert_eq!(cache.len(), 4); assert!(cache.contains(&"trigger"));
cache.insert("final", "final_value");
assert_eq!(cache.len(), 4); assert!(cache.contains(&"final"));
assert_eq!(cache.capacity(), 4);
let oldest = cache.peek_oldest();
assert!(oldest.is_some());
let popped = cache.pop_oldest();
assert!(popped.is_some()); assert_eq!(cache.len(), 3);
}
}
}