use std::collections::VecDeque;
use std::hash::Hash;
use std::ptr::NonNull;
use std::sync::Arc;
use rustc_hash::FxHashMap;
#[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::prelude::ReadOnlyCache;
use crate::traits::{CoreCache, LrukCacheTrait, MutableCache};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Segment {
Cold,
Hot,
}
#[repr(C)]
struct Node<K, V> {
prev: Option<NonNull<Node<K, V>>>,
next: Option<NonNull<Node<K, V>>>,
segment: Segment,
history: VecDeque<u64>,
key: K,
value: Arc<V>,
}
pub struct LrukCache<K, V> {
k: usize,
capacity: usize,
map: FxHashMap<K, NonNull<Node<K, V>>>,
cold_head: Option<NonNull<Node<K, V>>>,
cold_tail: Option<NonNull<Node<K, V>>>,
cold_len: usize,
hot_head: Option<NonNull<Node<K, V>>>,
hot_tail: Option<NonNull<Node<K, V>>>,
hot_len: usize,
tick: u64,
#[cfg(feature = "metrics")]
metrics: LruKMetrics,
}
unsafe impl<K: Send, V: Send> Send for LrukCache<K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for LrukCache<K, V> {}
impl<K, V> LrukCache<K, V> {
#[inline(always)]
fn pop_cold_tail_inner(&mut self) -> Option<Box<Node<K, V>>> {
self.cold_tail.map(|tail_ptr| unsafe {
let node = Box::from_raw(tail_ptr.as_ptr());
self.cold_tail = node.prev;
match self.cold_tail {
Some(mut t) => t.as_mut().next = None,
None => self.cold_head = None,
}
self.cold_len -= 1;
node
})
}
#[inline(always)]
fn pop_hot_tail_inner(&mut self) -> Option<Box<Node<K, V>>> {
self.hot_tail.map(|tail_ptr| unsafe {
let node = Box::from_raw(tail_ptr.as_ptr());
self.hot_tail = node.prev;
match self.hot_tail {
Some(mut t) => t.as_mut().next = None,
None => self.hot_head = None,
}
self.hot_len -= 1;
node
})
}
}
impl<K, V> LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[must_use]
#[inline]
pub fn new(capacity: usize) -> Self {
Self::with_k(capacity, 2)
}
#[must_use]
#[inline]
pub fn with_k(capacity: usize, k: usize) -> Self {
let k = k.max(1);
LrukCache {
k,
capacity,
map: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
cold_head: None,
cold_tail: None,
cold_len: 0,
hot_head: None,
hot_tail: None,
hot_len: 0,
tick: 0,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
}
}
#[inline(always)]
fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ref();
let prev = node.prev;
let next = node.next;
let segment = node.segment;
let (head, tail, len) = match segment {
Segment::Cold => (&mut self.cold_head, &mut self.cold_tail, &mut self.cold_len),
Segment::Hot => (&mut self.hot_head, &mut self.hot_tail, &mut self.hot_len),
};
match prev {
Some(mut p) => p.as_mut().next = next,
None => *head = next,
}
match next {
Some(mut n) => n.as_mut().prev = prev,
None => *tail = prev,
}
*len -= 1;
}
}
#[inline(always)]
fn attach_cold_front(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.cold_head;
node.segment = Segment::Cold;
match self.cold_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.cold_tail = Some(node_ptr),
}
self.cold_head = Some(node_ptr);
self.cold_len += 1;
}
}
#[inline(always)]
fn attach_hot_front(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_mut();
node.prev = None;
node.next = self.hot_head;
node.segment = Segment::Hot;
match self.hot_head {
Some(mut h) => h.as_mut().prev = Some(node_ptr),
None => self.hot_tail = Some(node_ptr),
}
self.hot_head = Some(node_ptr);
self.hot_len += 1;
}
}
#[inline(always)]
fn record_access(&mut self, node_ptr: NonNull<Node<K, V>>) -> usize {
self.tick = self.tick.saturating_add(1);
unsafe {
let node = &mut *node_ptr.as_ptr();
node.history.push_back(self.tick);
if node.history.len() > self.k {
node.history.pop_front();
}
node.history.len()
}
}
#[inline(always)]
fn move_hot_to_front(&mut self, node_ptr: NonNull<Node<K, V>>) {
let is_hot = unsafe { node_ptr.as_ref().segment == Segment::Hot };
if !is_hot {
return;
}
self.detach(node_ptr);
self.attach_hot_front(node_ptr);
}
#[inline(always)]
fn promote_if_needed(&mut self, node_ptr: NonNull<Node<K, V>>) {
let (is_cold, history_len) = unsafe {
let node = node_ptr.as_ref();
(node.segment == Segment::Cold, node.history.len())
};
if !is_cold || history_len < self.k {
return;
}
self.detach(node_ptr);
self.attach_hot_front(node_ptr);
}
#[inline]
fn evict_candidate(&mut self) -> Option<Box<Node<K, V>>> {
let node = if self.cold_len > 0 {
self.pop_cold_tail_inner()?
} else {
self.pop_hot_tail_inner()?
};
self.map.remove(&node.key);
Some(node)
}
#[inline]
fn peek_candidate(&self) -> Option<NonNull<Node<K, V>>> {
if self.cold_len > 0 {
self.cold_tail
} else {
self.hot_tail
}
}
}
impl<K, V> ReadOnlyCache<K, V> for LrukCache<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
#[inline]
fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
#[inline]
fn len(&self) -> usize {
self.map.len()
}
#[inline]
fn capacity(&self) -> usize {
self.capacity
}
}
impl<K, V> CoreCache<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[inline]
fn insert(&mut self, key: K, value: V) -> Option<V> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if self.capacity == 0 {
return None;
}
if let Some(&node_ptr) = self.map.get(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
let old_value = unsafe {
let node = &mut *node_ptr.as_ptr();
let old = std::mem::replace(&mut node.value, Arc::new(value));
(*old).clone()
};
self.record_access(node_ptr);
self.promote_if_needed(node_ptr);
self.move_hot_to_front(node_ptr);
return Some(old_value);
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
if self.map.len() >= self.capacity {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if self.evict_candidate().is_some() {
#[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);
let node = Box::new(Node {
prev: None,
next: None,
segment: Segment::Cold,
history,
key: key.clone(),
value: Arc::new(value),
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_cold_front(node_ptr);
None
}
#[inline]
fn get(&mut self, key: &K) -> Option<&V> {
let node_ptr = match self.map.get(key) {
Some(&ptr) => ptr,
None => {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
return None;
},
};
self.record_access(node_ptr);
self.promote_if_needed(node_ptr);
self.move_hot_to_front(node_ptr);
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
unsafe { Some((*node_ptr.as_ptr()).value.as_ref()) }
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
while self.pop_cold_tail_inner().is_some() {}
while self.pop_hot_tail_inner().is_some() {}
self.map.clear();
self.tick = 0;
}
}
impl<K, V> MutableCache<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[inline]
fn remove(&mut self, key: &K) -> Option<V> {
let node_ptr = self.map.remove(key)?;
self.detach(node_ptr);
let node = unsafe { Box::from_raw(node_ptr.as_ptr()) };
Some((*node.value).clone())
}
}
impl<K, V> LrukCacheTrait<K, V> for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
#[inline]
fn pop_lru_k(&mut self) -> Option<(K, V)> {
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_k_call();
let node = self.evict_candidate()?;
#[cfg(feature = "metrics")]
self.metrics.record_pop_lru_k_found();
Some((node.key, (*node.value).clone()))
}
#[inline]
fn peek_lru_k(&self) -> Option<(&K, &V)> {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_k_call();
let node_ptr = self.peek_candidate()?;
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lru_k_found();
unsafe {
let node = node_ptr.as_ref();
Some((&node.key, node.value.as_ref()))
}
}
#[inline]
fn k_value(&self) -> usize {
self.k
}
fn access_history(&self, key: &K) -> Option<Vec<u64>> {
let node_ptr = self.map.get(key)?;
unsafe {
let node = node_ptr.as_ref();
Some(node.history.iter().rev().copied().collect()) }
}
#[inline]
fn access_count(&self, key: &K) -> Option<usize> {
let node_ptr = self.map.get(key)?;
unsafe { Some(node_ptr.as_ref().history.len()) }
}
#[inline]
fn k_distance(&self, key: &K) -> Option<u64> {
#[cfg(feature = "metrics")]
(&self.metrics).record_k_distance_call();
let result = self.map.get(key).and_then(|node_ptr| unsafe {
let node = node_ptr.as_ref();
if node.history.len() >= self.k {
node.history.front().copied()
} else {
None
}
});
#[cfg(feature = "metrics")]
if result.is_some() {
(&self.metrics).record_k_distance_found();
}
result
}
#[inline]
fn touch(&mut self, key: &K) -> bool {
#[cfg(feature = "metrics")]
self.metrics.record_touch_call();
let node_ptr = match self.map.get(key) {
Some(&ptr) => ptr,
None => return false,
};
self.record_access(node_ptr);
self.promote_if_needed(node_ptr);
self.move_hot_to_front(node_ptr);
#[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.map.contains_key(key) {
return None;
}
let mut items_with_distances: Vec<(bool, u64)> = Vec::new();
for node_ptr in self.map.values() {
let history = unsafe { &node_ptr.as_ref().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_node = self.map.get(key)?;
let target_history = unsafe { &target_node.as_ref().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();
})
}
}
impl<K, V> Drop for LrukCache<K, V> {
fn drop(&mut self) {
while self.pop_cold_tail_inner().is_some() {}
while self.pop_hot_tail_inner().is_some() {}
}
}
impl<K, V> std::fmt::Debug for LrukCache<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LrukCache")
.field("k", &self.k)
.field("capacity", &self.capacity)
.field("len", &self.map.len())
.field("cold_len", &self.cold_len)
.field("hot_len", &self.hot_len)
.finish_non_exhaustive()
}
}
impl<K, V> Default for LrukCache<K, V> {
fn default() -> Self {
LrukCache {
k: 2,
capacity: 0,
map: FxHashMap::default(),
cold_head: None,
cold_tail: None,
cold_len: 0,
hot_head: None,
hot_tail: None,
hot_len: 0,
tick: 0,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
}
}
}
impl<K, V> Clone for LrukCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn clone(&self) -> Self {
let mut new = LrukCache {
k: self.k,
capacity: self.capacity,
map: FxHashMap::with_capacity_and_hasher(self.map.len(), Default::default()),
cold_head: None,
cold_tail: None,
cold_len: 0,
hot_head: None,
hot_tail: None,
hot_len: 0,
tick: self.tick,
#[cfg(feature = "metrics")]
metrics: LruKMetrics::default(),
};
let mut current = self.cold_head;
while let Some(ptr) = current {
unsafe {
let src = ptr.as_ref();
let node = Box::new(Node {
prev: new.cold_tail,
next: None,
segment: Segment::Cold,
history: src.history.clone(),
key: src.key.clone(),
value: Arc::clone(&src.value),
});
let np = NonNull::new_unchecked(Box::into_raw(node));
match new.cold_tail {
Some(mut t) => t.as_mut().next = Some(np),
None => new.cold_head = Some(np),
}
new.cold_tail = Some(np);
new.cold_len += 1;
new.map.insert(src.key.clone(), np);
current = src.next;
}
}
let mut current = self.hot_head;
while let Some(ptr) = current {
unsafe {
let src = ptr.as_ref();
let node = Box::new(Node {
prev: new.hot_tail,
next: None,
segment: Segment::Hot,
history: src.history.clone(),
key: src.key.clone(),
value: Arc::clone(&src.value),
});
let np = NonNull::new_unchecked(Box::into_raw(node));
match new.hot_tail {
Some(mut t) => t.as_mut().next = Some(np),
None => new.hot_head = Some(np),
}
new.hot_tail = Some(np);
new.hot_len += 1;
new.map.insert(src.key.clone(), np);
current = src.next;
}
}
new
}
}
#[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.map.len(),
capacity: self.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);
}
}
}