use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
use std::sync::Arc;
use crate::ds::{IntrusiveList, SlotArena, SlotId};
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::LruKMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::LruKMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{
CoreMetricsRecorder, LruKMetricsReadRecorder, LruKMetricsRecorder, LruMetricsRecorder,
MetricsSnapshotProvider,
};
use crate::store::hashmap::HashMapStore;
use crate::store::traits::{StoreCore, StoreMut};
use crate::traits::{CoreCache, LRUKCacheTrait, MutableCache};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Segment {
Cold,
Hot,
}
#[derive(Debug)]
struct Entry<K> {
key: K,
history: VecDeque<u64>,
segment: Segment,
list_node: Option<SlotId>,
}
#[derive(Debug)]
pub struct LRUKCache<K, V>
where
K: Eq + Hash + Clone,
{
k: usize,
store: HashMapStore<K, V>,
entries: SlotArena<Entry<K>>,
index: HashMap<K, SlotId>,
cold: IntrusiveList<SlotId>,
hot: IntrusiveList<SlotId>,
tick: u64,
#[cfg(feature = "metrics")]
metrics: LruKMetrics,
}
impl<K, V> LRUKCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
pub fn new(capacity: usize) -> Self {
Self::with_k(capacity, 2)
}
pub fn with_k(capacity: usize, k: usize) -> Self {
let k = k.max(1);
LRUKCache {
k,
store: HashMapStore::new(capacity),
entries: SlotArena::with_capacity(capacity),
index: HashMap::with_capacity(capacity),
cold: IntrusiveList::with_capacity(capacity),
hot: IntrusiveList::with_capacity(capacity),
tick: 0,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
}
}
fn record_access(&mut self, id: SlotId) -> usize {
self.tick = self.tick.saturating_add(1);
let entry = self.entries.get_mut(id).expect("lru-k entry missing");
entry.history.push_back(self.tick);
if entry.history.len() > self.k {
entry.history.pop_front();
}
entry.history.len()
}
fn move_hot_to_front(&mut self, id: SlotId) {
let is_hot = self
.entries
.get(id)
.map(|entry| entry.segment == Segment::Hot)
.unwrap_or(false);
if !is_hot {
return;
}
let node_id = match self.entries.get(id).and_then(|entry| entry.list_node) {
Some(node_id) => node_id,
None => return,
};
self.hot.move_to_front(node_id);
}
fn promote_if_needed(&mut self, id: SlotId) {
let promote = self
.entries
.get(id)
.map(|entry| entry.segment == Segment::Cold && entry.history.len() >= self.k)
.unwrap_or(false);
if !promote {
return;
}
if let Some(node_id) = self.entries.get(id).and_then(|entry| entry.list_node) {
let _ = self.cold.remove(node_id);
}
if let Some(entry) = self.entries.get_mut(id) {
entry.segment = Segment::Hot;
entry.list_node = None;
}
let node_id = self.hot.push_front(id);
if let Some(entry) = self.entries.get_mut(id) {
entry.list_node = Some(node_id);
}
}
fn attach_to_list(
entries: &mut SlotArena<Entry<K>>,
list: &mut IntrusiveList<SlotId>,
id: SlotId,
) {
let node_id = list.push_front(id);
if let Some(entry) = entries.get_mut(id) {
entry.list_node = Some(node_id);
}
}
fn detach_from_list(
entries: &mut SlotArena<Entry<K>>,
list: &mut IntrusiveList<SlotId>,
id: SlotId,
) {
let node_id = match entries.get(id).and_then(|entry| entry.list_node) {
Some(node_id) => node_id,
None => return,
};
let _ = list.remove(node_id);
if let Some(entry) = entries.get_mut(id) {
entry.list_node = None;
}
}
fn evict_candidate(&mut self) -> Option<(K, V)> {
let id = if !self.cold.is_empty() {
self.cold.pop_back()?
} else {
self.hot.pop_back()?
};
if let Some(entry) = self.entries.get_mut(id) {
entry.list_node = None;
}
let entry = self.entries.remove(id).expect("lru-k entry missing");
self.index.remove(&entry.key);
self.store.record_eviction();
let value = self.store.remove(&entry.key).map(|arc| (*arc).clone())?;
Some((entry.key, value))
}
fn peek_candidate(&self) -> Option<SlotId> {
if !self.cold.is_empty() {
self.cold.back().copied()
} else {
self.hot.back().copied()
}
}
}
impl<K, V> CoreCache<K, V> for LRUKCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if self.store.capacity() == 0 {
return None;
}
if let Some(&idx) = self.index.get(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
let old_value = self
.store
.try_insert(key.clone(), Arc::new(value))
.ok()
.flatten()
.map(|arc| (*arc).clone());
self.record_access(idx);
self.promote_if_needed(idx);
self.move_hot_to_front(idx);
return old_value;
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
if self.index.len() >= self.store.capacity() && !self.index.is_empty() {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if let Some((_key, _value)) = self.evict_candidate() {
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
}
}
self.tick = self.tick.saturating_add(1);
let mut history = VecDeque::with_capacity(self.k);
history.push_back(self.tick);
if self.store.try_insert(key.clone(), Arc::new(value)).is_err() {
return None;
}
let entry = Entry {
key: key.clone(),
history,
segment: Segment::Cold,
list_node: None,
};
let id = self.entries.insert(entry);
self.index.insert(key, id);
Self::attach_to_list(&mut self.entries, &mut self.cold, id);
None
}
fn get(&mut self, key: &K) -> Option<&V> {
let idx = match self.index.get(key) {
Some(idx) => *idx,
None => {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
let _ = self.store.get_ref(key);
return None;
},
};
self.record_access(idx);
self.promote_if_needed(idx);
self.move_hot_to_front(idx);
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
self.store.get_ref(key).map(|value| value.as_ref())
}
fn contains(&self, key: &K) -> bool {
self.store.contains(key)
}
fn len(&self) -> usize {
self.store.len()
}
fn capacity(&self) -> usize {
self.store.capacity()
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
self.store.clear();
self.index.clear();
self.entries.clear();
self.cold.clear();
self.hot.clear();
self.tick = 0;
}
}
impl<K, V> MutableCache<K, V> for LRUKCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn remove(&mut self, key: &K) -> Option<V> {
let id = self.index.remove(key)?;
let segment = self.entries.get(id).expect("lru-k entry missing").segment;
if segment == Segment::Cold {
Self::detach_from_list(&mut self.entries, &mut self.cold, id);
} else {
Self::detach_from_list(&mut self.entries, &mut self.hot, id);
}
let entry = self.entries.remove(id).expect("lru-k entry missing");
self.store.remove(&entry.key).map(|arc| (*arc).clone())
}
}
impl<K, V> LRUKCacheTrait<K, V> for LRUKCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn pop_lru_k(&mut self) -> Option<(K, V)> {
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_k_call();
let result = self.evict_candidate();
#[cfg(feature = "metrics")]
if result.is_some() {
self.metrics.record_pop_lru_k_found();
}
result
}
fn peek_lru_k(&self) -> Option<(&K, &V)> {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_k_call();
let idx = self.peek_candidate()?;
let entry = self.entries.get(idx)?;
let value = self.store.peek_ref(&entry.key)?;
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_k_found();
Some((&entry.key, value.as_ref()))
}
fn k_value(&self) -> usize {
self.k
}
fn access_history(&self, key: &K) -> Option<Vec<u64>> {
let id = self.index.get(key)?;
self.entries.get(*id).map(|entry| {
entry.history.iter().rev().copied().collect() })
}
fn access_count(&self, key: &K) -> Option<usize> {
let id = self.index.get(key)?;
self.entries.get(*id).map(|entry| entry.history.len())
}
fn k_distance(&self, key: &K) -> Option<u64> {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_call();
let result = self
.index
.get(key)
.and_then(|id| self.entries.get(*id))
.and_then(|entry| {
if entry.history.len() >= self.k {
entry.history.front().copied()
} else {
None
}
});
#[cfg(feature = "metrics")]
if result.is_some() {
(&self.metrics).record_k_distance_found();
}
result
}
fn touch(&mut self, key: &K) -> bool {
#[cfg(feature = "metrics")]
self.metrics.record_touch_call();
let idx = match self.index.get(key) {
Some(idx) => *idx,
None => return false,
};
self.record_access(idx);
self.promote_if_needed(idx);
self.move_hot_to_front(idx);
#[cfg(feature = "metrics")]
self.metrics.record_touch_found();
true
}
fn k_distance_rank(&self, key: &K) -> Option<usize> {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_rank_call();
if !self.index.contains_key(key) {
return None;
}
let mut items_with_distances: Vec<(bool, u64)> = Vec::new();
for idx in self.index.values() {
let history = &self.entries.get(*idx).expect("lru-k entry missing").history;
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_rank_scan_step();
let num_accesses = history.len();
if num_accesses < self.k {
let earliest = history.front().copied().unwrap_or(u64::MAX);
items_with_distances.push((false, earliest)); } else {
let k_distance = history.front().copied().unwrap_or(u64::MAX);
items_with_distances.push((true, k_distance)); }
}
items_with_distances.sort_by(|a, b| {
match (a.0, b.0) {
(false, false) => a.1.cmp(&b.1), (true, true) => a.1.cmp(&b.1), (false, true) => std::cmp::Ordering::Less, (true, false) => std::cmp::Ordering::Greater, }
});
let target_idx = *self.index.get(key)?;
let target_history = &self
.entries
.get(target_idx)
.expect("lru-k entry missing")
.history;
let target_num_accesses = target_history.len();
let target_value = if target_num_accesses < self.k {
(false, target_history.front().copied().unwrap_or(u64::MAX))
} else {
(true, target_history.front().copied().unwrap_or(u64::MAX))
};
items_with_distances
.iter()
.position(|item| item == &target_value)
.inspect(|_| {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_rank_found();
})
}
}
#[cfg(feature = "metrics")]
impl<K, V> LRUKCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
pub fn metrics_snapshot(&self) -> LruKMetricsSnapshot {
LruKMetricsSnapshot {
get_calls: self.metrics.get_calls,
get_hits: self.metrics.get_hits,
get_misses: self.metrics.get_misses,
insert_calls: self.metrics.insert_calls,
insert_updates: self.metrics.insert_updates,
insert_new: self.metrics.insert_new,
evict_calls: self.metrics.evict_calls,
evicted_entries: self.metrics.evicted_entries,
pop_lru_calls: self.metrics.pop_lru_calls,
pop_lru_found: self.metrics.pop_lru_found,
peek_lru_calls: self.metrics.peek_lru_calls,
peek_lru_found: self.metrics.peek_lru_found,
touch_calls: self.metrics.touch_calls,
touch_found: self.metrics.touch_found,
recency_rank_calls: self.metrics.recency_rank_calls,
recency_rank_found: self.metrics.recency_rank_found,
recency_rank_scan_steps: self.metrics.recency_rank_scan_steps,
pop_lru_k_calls: self.metrics.pop_lru_k_calls,
pop_lru_k_found: self.metrics.pop_lru_k_found,
peek_lru_k_calls: self.metrics.peek_lru_k_calls.get(),
peek_lru_k_found: self.metrics.peek_lru_k_found.get(),
k_distance_calls: self.metrics.k_distance_calls.get(),
k_distance_found: self.metrics.k_distance_found.get(),
k_distance_rank_calls: self.metrics.k_distance_rank_calls.get(),
k_distance_rank_found: self.metrics.k_distance_rank_found.get(),
k_distance_rank_scan_steps: self.metrics.k_distance_rank_scan_steps.get(),
cache_len: self.store.len(),
capacity: self.store.capacity(),
}
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<LruKMetricsSnapshot> for LRUKCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn snapshot(&self) -> LruKMetricsSnapshot {
self.metrics_snapshot()
}
}
#[cfg(test)]
mod tests {
mod basic_behavior {
use std::thread;
use std::time::Duration;
use super::super::*;
#[test]
fn test_basic_lru_k_insertion_and_retrieval() {
let mut cache = LRUKCache::new(2);
cache.insert(1, "one");
assert_eq!(cache.get(&1), Some(&"one"));
cache.insert(2, "two");
assert_eq!(cache.get(&2), Some(&"two"));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_lru_k_eviction_order() {
let mut cache = LRUKCache::with_k(3, 2);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
thread::sleep(Duration::from_millis(2));
cache.insert(3, 30);
thread::sleep(Duration::from_millis(2));
cache.insert(4, 40);
assert!(!cache.contains(&1), "1 should be evicted");
assert!(cache.contains(&2));
assert!(cache.contains(&3));
assert!(cache.contains(&4));
thread::sleep(Duration::from_millis(2));
cache.get(&2);
cache.insert(5, 50);
assert!(!cache.contains(&3), "3 should be evicted");
assert!(cache.contains(&2));
assert!(cache.contains(&4));
assert!(cache.contains(&5));
}
#[test]
fn test_capacity_enforcement() {
let mut cache = LRUKCache::new(2);
cache.insert(1, 1);
thread::sleep(Duration::from_millis(1));
cache.insert(2, 2);
assert_eq!(cache.len(), 2);
cache.insert(3, 3);
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&1));
assert!(cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_update_existing_key() {
let mut cache = LRUKCache::new(2);
cache.insert(1, 10);
cache.insert(1, 20);
assert_eq!(cache.get(&1), Some(&20));
assert_eq!(cache.access_count(&1), Some(2));
}
#[test]
fn test_access_history_tracking() {
let mut cache = LRUKCache::with_k(2, 3); cache.insert(1, 10); thread::sleep(Duration::from_millis(2));
cache.get(&1); thread::sleep(Duration::from_millis(2));
cache.get(&1); thread::sleep(Duration::from_millis(2));
assert_eq!(cache.access_count(&1), Some(3));
cache.get(&1); assert_eq!(cache.access_count(&1), Some(3));
let history = cache.access_history(&1).unwrap();
assert_eq!(history.len(), 3);
assert!(history[0] > history[1]);
assert!(history[1] > history[2]);
}
#[test]
fn test_k_value_behavior() {
let cache = LRUKCache::<i32, i32>::with_k(10, 5);
assert_eq!(cache.k_value(), 5);
}
#[test]
fn test_key_operations_consistency() {
let mut cache = LRUKCache::new(2);
cache.insert(1, 10);
assert!(cache.contains(&1));
assert_eq!(cache.get(&1), Some(&10));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_timestamp_ordering() {
let mut cache = LRUKCache::with_k(2, 1); cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
let dist1 = cache.k_distance(&1).unwrap();
let dist2 = cache.k_distance(&2).unwrap();
assert!(dist2 > dist1);
}
}
mod edge_cases {
use std::thread;
use std::time::Duration;
use super::super::*;
#[test]
fn test_empty_cache_operations() {
let mut cache = LRUKCache::<i32, i32>::new(5);
assert_eq!(cache.get(&1), None);
assert_eq!(cache.remove(&1), None);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&1));
}
#[test]
fn test_single_item_cache() {
let mut cache = LRUKCache::new(1);
cache.insert(1, 10);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&1), Some(&10));
cache.insert(2, 20);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&2), Some(&20));
assert!(!cache.contains(&1));
}
#[test]
fn test_zero_capacity_cache() {
let mut cache = LRUKCache::new(0);
cache.insert(1, 10);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&1));
}
#[test]
fn test_k_equals_one() {
let mut cache = LRUKCache::with_k(2, 1);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
thread::sleep(Duration::from_millis(2));
cache.get(&1);
thread::sleep(Duration::from_millis(2));
cache.insert(3, 30);
assert!(cache.contains(&1));
assert!(!cache.contains(&2)); assert!(cache.contains(&3));
}
#[test]
fn test_k_larger_than_capacity() {
let mut cache = LRUKCache::with_k(2, 5);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(1)); cache.insert(2, 20);
cache.get(&1);
cache.get(&2);
cache.insert(3, 30);
assert!(!cache.contains(&1));
assert!(cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_same_key_rapid_accesses() {
let mut cache = LRUKCache::with_k(5, 3);
cache.insert(1, 10);
for _ in 0..10 {
cache.get(&1);
}
assert_eq!(cache.access_count(&1), Some(3)); }
#[test]
fn test_duplicate_key_insertion() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
cache.insert(1, 20);
assert_eq!(cache.get(&1), Some(&20));
assert_eq!(cache.len(), 1);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_large_cache_operations() {
let mut cache = LRUKCache::new(100);
cache.insert(0, 0);
thread::sleep(Duration::from_millis(1));
for i in 1..100 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 100);
cache.insert(100, 100);
assert_eq!(cache.len(), 100);
assert!(!cache.contains(&0)); }
#[test]
fn test_access_history_overflow() {
let mut cache = LRUKCache::with_k(2, 3); cache.insert(1, 10);
cache.get(&1);
cache.get(&1);
cache.get(&1);
cache.get(&1);
let history = cache.access_history(&1).unwrap();
assert_eq!(history.len(), 3);
}
}
mod lru_k_operations {
use std::thread;
use std::time::Duration;
use super::super::*;
#[test]
fn test_pop_lru_k_basic() {
let mut cache = LRUKCache::with_k(3, 2);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
let popped = cache.pop_lru_k();
assert_eq!(popped, Some((1, 10)));
assert!(!cache.contains(&1));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_peek_lru_k_basic() {
let mut cache = LRUKCache::with_k(3, 2);
cache.insert(1, 10);
thread::sleep(Duration::from_millis(2));
cache.insert(2, 20);
let peeked = cache.peek_lru_k();
assert_eq!(peeked, Some((&1, &10)));
assert!(cache.contains(&1));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_k_value_retrieval() {
let cache = LRUKCache::<i32, i32>::with_k(10, 4);
assert_eq!(cache.k_value(), 4);
}
#[test]
fn test_access_history_retrieval() {
let mut cache = LRUKCache::with_k(10, 3);
cache.insert(1, 10);
cache.get(&1);
let history = cache.access_history(&1).unwrap();
assert_eq!(history.len(), 2);
}
#[test]
fn test_access_count() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
assert_eq!(cache.access_count(&1), Some(1));
cache.get(&1);
assert_eq!(cache.access_count(&1), Some(2));
}
#[test]
fn test_k_distance() {
let mut cache = LRUKCache::with_k(5, 2);
cache.insert(1, 10);
assert_eq!(cache.k_distance(&1), None);
cache.get(&1);
assert!(cache.k_distance(&1).is_some());
}
#[test]
fn test_touch_functionality() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
assert!(cache.touch(&1));
assert_eq!(cache.access_count(&1), Some(2));
assert!(!cache.touch(&999)); }
#[test]
fn test_k_distance_rank() {
let mut cache = LRUKCache::with_k(5, 2);
cache.insert(1, 10); thread::sleep(Duration::from_millis(2));
cache.insert(2, 20); thread::sleep(Duration::from_millis(2));
assert_eq!(cache.k_distance_rank(&1), Some(0));
assert_eq!(cache.k_distance_rank(&2), Some(1));
cache.get(&1);
assert_eq!(cache.k_distance_rank(&2), Some(0));
assert_eq!(cache.k_distance_rank(&1), Some(1));
}
#[test]
fn test_pop_lru_k_empty_cache() {
let mut cache = LRUKCache::<i32, i32>::new(5);
assert_eq!(cache.pop_lru_k(), None);
}
#[test]
fn test_peek_lru_k_empty_cache() {
let cache = LRUKCache::<i32, i32>::new(5);
assert_eq!(cache.peek_lru_k(), None);
}
#[test]
fn test_lru_k_tie_breaking() {
let mut cache = LRUKCache::with_k(5, 2);
cache.insert(1, 10);
cache.insert(2, 20);
assert!(cache.peek_lru_k().is_some());
}
#[test]
fn test_access_history_after_removal() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
cache.remove(&1);
assert!(!cache.contains(&1));
assert_eq!(cache.access_count(&1), None);
}
#[test]
fn test_access_history_after_clear() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.access_count(&1), None);
}
}
mod state_consistency {
use super::super::*;
#[test]
fn test_cache_access_history_consistency() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
assert!(cache.access_history(&1).is_some());
cache.remove(&1);
assert!(cache.access_history(&1).is_none());
}
#[test]
fn test_len_consistency() {
let mut cache = LRUKCache::new(5);
assert_eq!(cache.len(), 0);
cache.insert(1, 10);
assert_eq!(cache.len(), 1);
cache.insert(2, 20);
assert_eq!(cache.len(), 2);
cache.remove(&1);
assert_eq!(cache.len(), 1);
cache.clear();
assert_eq!(cache.len(), 0);
}
#[test]
fn test_capacity_consistency() {
let mut cache = LRUKCache::new(2);
assert_eq!(cache.capacity(), 2);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.len(), 2); }
#[test]
fn test_clear_resets_all_state() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
cache.insert(2, 20);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&1));
assert!(!cache.contains(&2));
assert!(cache.access_history(&1).is_none());
}
#[test]
fn test_remove_consistency() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
let removed = cache.remove(&1);
assert_eq!(removed, Some(10));
assert!(!cache.contains(&1));
assert!(cache.access_history(&1).is_none());
let removed_again = cache.remove(&1);
assert_eq!(removed_again, None);
}
#[test]
fn test_eviction_consistency() {
let mut cache = LRUKCache::new(1);
cache.insert(1, 10);
cache.insert(2, 20);
assert!(!cache.contains(&1));
assert!(cache.contains(&2));
assert!(cache.access_history(&1).is_none());
assert!(cache.access_history(&2).is_some());
}
#[test]
fn test_access_history_update_on_get() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
let count_before = cache.access_count(&1).unwrap();
cache.get(&1);
let count_after = cache.access_count(&1).unwrap();
assert_eq!(count_after, count_before + 1);
}
#[test]
fn test_invariants_after_operations() {
let mut cache = LRUKCache::with_k(2, 2);
cache.insert(1, 10);
cache.insert(2, 20);
assert!(cache.len() <= cache.capacity());
let h1 = cache.access_history(&1).unwrap();
assert!(h1.len() <= 2);
cache.get(&1);
cache.get(&1);
let h1_new = cache.access_history(&1).unwrap();
assert!(h1_new.len() <= 2);
}
#[test]
fn test_k_distance_calculation_consistency() {
let mut cache = LRUKCache::with_k(5, 2);
cache.insert(1, 10);
assert_eq!(cache.k_distance(&1), None);
cache.get(&1); assert!(cache.k_distance(&1).is_some());
}
#[test]
fn test_timestamp_consistency() {
let mut cache = LRUKCache::new(5);
cache.insert(1, 10);
let history = cache.access_history(&1).unwrap();
let ts1 = history[0];
std::thread::sleep(std::time::Duration::from_millis(1));
cache.get(&1);
let history_new = cache.access_history(&1).unwrap();
let ts2 = history_new[0];
assert!(ts2 > ts1);
}
}
}