#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#![deny(missing_docs)]
#![cfg_attr(all(doc, ENABLE_DOC_AUTO_CFG), feature(doc_cfg))]
#![forbid(unsafe_code)]
mod lfu;
mod lru;
mod mru;
mod queue;
#[cfg(feature = "rand")]
mod random;
mod r#ref;
mod sieve;
use std::hash::Hash;
use std::num::NonZeroUsize;
pub use lfu::LfuPolicy;
pub use lru::LruPolicy;
pub use mru::MruPolicy;
pub use queue::FifoPolicy;
pub use queue::LifoPolicy;
#[cfg(feature = "rand")]
pub use random::RandomPolicy;
pub use r#ref::Entry;
pub use sieve::SievePolicy;
use tether_map::Ptr;
use tether_map::linked_hash_map::LinkedHashMap;
#[cfg(not(feature = "ahash"))]
type RandomState = std::hash::RandomState;
#[cfg(feature = "ahash")]
type RandomState = ahash::RandomState;
mod private {
pub trait Sealed {}
}
#[doc(hidden)]
#[must_use]
pub enum InsertionResult<K, T> {
Inserted(Ptr, Option<T>),
Updated(Ptr),
FoundTouchedNoUpdate(Ptr),
NotFoundNoInsert(K, Option<T>),
}
impl<K, T> std::fmt::Debug for InsertionResult<K, T> {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match self {
InsertionResult::Inserted(ptr, _) => f.debug_tuple("Inserted").field(ptr).finish(),
InsertionResult::Updated(ptr) => f.debug_tuple("Updated").field(ptr).finish(),
InsertionResult::FoundTouchedNoUpdate(ptr) => {
f.debug_tuple("FoundTouchedNoUpdate").field(ptr).finish()
}
InsertionResult::NotFoundNoInsert(_, _) => f.debug_tuple("NotFoundNoInsert").finish(),
}
}
}
#[doc(hidden)]
pub enum InsertOrUpdateAction<T> {
InsertOrUpdate(T),
TouchNoUpdate,
NoInsert(Option<T>),
}
#[doc(hidden)]
pub trait EntryValue<T>: private::Sealed {
fn new(value: T) -> Self;
fn into_value(self) -> T;
fn value(&self) -> &T;
fn value_mut(&mut self) -> &mut T;
}
#[doc(hidden)]
pub trait Metadata<T>: Default + private::Sealed + Clone {
type EntryType: EntryValue<T>;
fn candidate_removal_index<K>(
&self,
queue: &LinkedHashMap<K, Self::EntryType, RandomState>,
) -> Option<Ptr>;
fn clone_from<K>(
&mut self,
other: &Self,
new_queue: &LinkedHashMap<K, Self::EntryType, RandomState>,
) {
let _ = new_queue;
<Self as Clone>::clone_from(self, other);
}
}
#[doc(hidden)]
pub trait Policy<T>: private::Sealed {
type MetadataType: Metadata<T>;
type IntoIter<K: Hash + Eq>: Iterator<Item = (K, T)>;
#[track_caller]
fn insert_or_update_entry<K: Hash + Eq>(
key: K,
make_room_on_insert: bool,
get_value: impl FnOnce(&K, /* is_insert */ bool) -> InsertOrUpdateAction<T>,
metadata: &mut Self::MetadataType,
queue: &mut LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> InsertionResult<K, T>;
#[track_caller]
fn touch_entry<K: Hash + Eq>(
ptr: Ptr,
metadata: &mut Self::MetadataType,
queue: &mut LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
);
#[inline]
#[track_caller]
fn evict_entry<K: Hash + Eq>(
metadata: &mut Self::MetadataType,
queue: &mut LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> Option<(K, <Self::MetadataType as Metadata<T>>::EntryType)> {
let removed = metadata.candidate_removal_index(queue)?;
Self::remove_entry(removed, metadata, queue).1
}
#[allow(clippy::type_complexity)]
#[track_caller]
fn remove_entry<K: Hash + Eq>(
ptr: Ptr,
metadata: &mut Self::MetadataType,
queue: &mut LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> (
Option<Ptr>,
Option<(K, <Self::MetadataType as Metadata<T>>::EntryType)>,
);
#[track_caller]
fn remove_key<K: Hash + Eq>(
key: &K,
metadata: &mut Self::MetadataType,
queue: &mut LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> Option<<Self::MetadataType as Metadata<T>>::EntryType>;
fn iter<'q, K>(
metadata: &'q Self::MetadataType,
queue: &'q LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> impl Iterator<Item = (&'q K, &'q T)>
where
T: 'q;
fn into_iter<K: Hash + Eq>(
metadata: Self::MetadataType,
queue: LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> Self::IntoIter<K>;
fn into_entries<K: Hash + Eq>(
metadata: Self::MetadataType,
queue: LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) -> impl Iterator<Item = (K, <Self::MetadataType as Metadata<T>>::EntryType)>;
#[cfg(all(debug_assertions, feature = "internal-debugging"))]
#[doc(hidden)]
fn debug_validate<K: Hash + Eq + std::fmt::Debug>(
metadata: &Self::MetadataType,
queue: &LinkedHashMap<K, <Self::MetadataType as Metadata<T>>::EntryType, RandomState>,
) where
T: std::fmt::Debug;
}
#[doc(alias = "LRU")]
pub type Lru<Key, Value> = Cache<Key, Value, LruPolicy>;
#[doc(hidden)]
pub type LRU<Key, Value> = Lru<Key, Value>;
#[doc(alias = "MRU")]
pub type Mru<Key, Value> = Cache<Key, Value, MruPolicy>;
#[doc(hidden)]
pub type MRU<Key, Value> = Mru<Key, Value>;
#[doc(alias = "LFU")]
pub type Lfu<Key, Value> = Cache<Key, Value, LfuPolicy>;
#[doc(hidden)]
pub type LFU<Key, Value> = Lfu<Key, Value>;
#[doc(alias = "FIFO")]
pub type Fifo<Key, Value> = Cache<Key, Value, FifoPolicy>;
#[doc(hidden)]
pub type FIFO<Key, Value> = Fifo<Key, Value>;
#[doc(alias = "LIFO")]
pub type Lifo<Key, Value> = Cache<Key, Value, LifoPolicy>;
#[doc(hidden)]
pub type LIFO<Key, Value> = Lifo<Key, Value>;
#[cfg(feature = "rand")]
pub type Random<Key, Value> = Cache<Key, Value, RandomPolicy>;
#[doc(alias = "SIEVE")]
pub type Sieve<Key, Value> = Cache<Key, Value, SievePolicy>;
#[doc(hidden)]
pub type SIEVE<Key, Value> = Sieve<Key, Value>;
#[derive(Clone, Debug)]
#[cfg(feature = "statistics")]
pub struct Statistics {
len: usize,
capacity: NonZeroUsize,
evictions: u64,
insertions: u64,
misses: u64,
hits: u64,
}
#[cfg(feature = "statistics")]
impl Statistics {
fn with_capacity(capacity: NonZeroUsize) -> Self {
Self {
len: 0,
capacity,
evictions: 0,
insertions: 0,
misses: 0,
hits: 0,
}
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn len(&self) -> usize {
self.len
}
pub fn residency(&self) -> f64 {
self.len as f64 / self.capacity.get() as f64 * 100.0
}
pub fn capacity(&self) -> usize {
self.capacity.get()
}
pub fn evictions(&self) -> u64 {
self.evictions
}
pub fn insertions(&self) -> u64 {
self.insertions
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn misses(&self) -> u64 {
self.misses
}
pub fn hit_rate(&self) -> f64 {
if self.hits + self.misses == 0 {
0.0
} else {
self.hits as f64 / (self.hits + self.misses) as f64 * 100.0
}
}
pub fn miss_rate(&self) -> f64 {
if self.hits + self.misses == 0 {
0.0
} else {
self.misses as f64 / (self.hits + self.misses) as f64 * 100.0
}
}
}
pub struct Cache<Key, Value, PolicyType: Policy<Value>> {
queue:
LinkedHashMap<Key, <PolicyType::MetadataType as Metadata<Value>>::EntryType, RandomState>,
capacity: NonZeroUsize,
metadata: PolicyType::MetadataType,
#[cfg(feature = "statistics")]
statistics: Statistics,
}
impl<Key, Value, PolicyType: Policy<Value>> std::fmt::Debug for Cache<Key, Value, PolicyType>
where
Key: std::fmt::Debug,
Value: std::fmt::Debug,
PolicyType::MetadataType: std::fmt::Debug,
<PolicyType::MetadataType as Metadata<Value>>::EntryType: std::fmt::Debug,
{
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
f.debug_struct("Cache")
.field("queue", &self.queue)
.field("capacity", &self.capacity)
.field("metadata", &self.metadata)
.finish()
}
}
impl<Key, Value, PolicyType: Policy<Value>> Clone for Cache<Key, Value, PolicyType>
where
Key: Clone + Hash + Eq,
Value: Clone,
<PolicyType::MetadataType as Metadata<Value>>::EntryType: Clone,
{
fn clone(&self) -> Self {
let mut metadata = <PolicyType::MetadataType as Default>::default();
<PolicyType::MetadataType as Metadata<Value>>::clone_from(
&mut metadata,
&self.metadata,
&self.queue,
);
Self {
queue: self.queue.clone(),
metadata,
capacity: self.capacity,
#[cfg(feature = "statistics")]
statistics: self.statistics.clone(),
}
}
}
impl<Key: Hash + Eq, Value, PolicyType: Policy<Value>> Cache<Key, Value, PolicyType> {
pub fn new(capacity: NonZeroUsize) -> Self {
Self {
queue: LinkedHashMap::with_capacity_and_hasher(capacity.get(), RandomState::default()),
capacity,
metadata: PolicyType::MetadataType::default(),
#[cfg(feature = "statistics")]
statistics: Statistics::with_capacity(capacity),
}
}
#[cfg(feature = "statistics")]
pub fn statistics(&self) -> Statistics {
Statistics {
len: self.queue.len(),
capacity: self.capacity,
..self.statistics
}
}
#[cfg(feature = "statistics")]
pub fn reset_statistics(&mut self) {
self.statistics = Statistics::with_capacity(self.capacity);
}
pub fn clear(&mut self) {
self.queue.clear();
self.metadata = PolicyType::MetadataType::default();
}
pub fn peek(
&self,
key: &Key,
) -> Option<&Value> {
self.queue.get(key).map(|entry| entry.value())
}
pub fn peek_mut<'q>(
&'q mut self,
key: &'q Key,
) -> Option<Entry<'q, Key, Value, PolicyType>> {
self.queue
.get_ptr(key)
.map(|ptr| Entry::new(ptr, key, self))
}
pub fn tail(&self) -> Option<(&Key, &Value)> {
let ptr = self.metadata.candidate_removal_index(&self.queue)?;
self.queue
.ptr_get_entry(ptr)
.map(|(key, value)| (key, value.value()))
}
pub fn contains_key(
&self,
key: &Key,
) -> bool {
self.queue.contains_key(key)
}
pub fn get_or_insert_with(
&mut self,
key: Key,
or_insert: impl FnOnce(&Key) -> Value,
) -> (&Value, Option<Value>) {
let (value, evicted) = self.get_or_insert_with_mut(key, or_insert);
(&*value, evicted)
}
pub fn get_or_insert_with_mut(
&mut self,
key: Key,
or_insert: impl FnOnce(&Key) -> Value,
) -> (&mut Value, Option<Value>) {
let will_evict = self.queue.len() == self.capacity.get();
let result = PolicyType::insert_or_update_entry(
key,
will_evict,
|value, is_insert| {
if is_insert {
InsertOrUpdateAction::InsertOrUpdate(or_insert(value))
} else {
InsertOrUpdateAction::TouchNoUpdate
}
},
&mut self.metadata,
&mut self.queue,
);
match result {
InsertionResult::Inserted(ptr, evicted) => {
#[cfg(feature = "statistics")]
{
self.statistics.insertions += 1;
self.statistics.misses += 1;
self.statistics.evictions += u64::from(will_evict);
}
(self.queue[ptr].value_mut(), evicted)
}
InsertionResult::FoundTouchedNoUpdate(ptr) => {
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
(self.queue[ptr].value_mut(), None)
}
_ => unreachable!("Unexpected insertion result: {:?}", result),
}
}
pub fn get_or_default(
&mut self,
key: Key,
) -> (&Value, Option<Value>)
where
Value: Default,
{
self.get_or_insert_with(key, |_| Value::default())
}
pub fn get_or_default_mut(
&mut self,
key: Key,
) -> (&mut Value, Option<Value>)
where
Value: Default,
{
self.get_or_insert_with_mut(key, |_| Value::default())
}
pub fn insert(
&mut self,
key: Key,
value: Value,
) -> Option<Value> {
let will_evict = self.queue.len() == self.capacity.get();
let result = PolicyType::insert_or_update_entry(
key,
will_evict,
|_, _| InsertOrUpdateAction::InsertOrUpdate(value),
&mut self.metadata,
&mut self.queue,
);
match result {
InsertionResult::Inserted(_, evicted) => {
#[cfg(feature = "statistics")]
{
self.statistics.insertions += 1;
self.statistics.evictions += u64::from(will_evict);
}
evicted
}
InsertionResult::Updated(_) => {
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
None
}
_ => unreachable!("Unexpected insertion result: {:?}", result),
}
}
pub fn insert_mut(
&mut self,
key: Key,
value: Value,
) -> (&mut Value, Option<Value>) {
let will_evict = self.queue.len() == self.capacity.get();
let result = PolicyType::insert_or_update_entry(
key,
will_evict,
|_, _| InsertOrUpdateAction::InsertOrUpdate(value),
&mut self.metadata,
&mut self.queue,
);
match result {
InsertionResult::Inserted(ptr, evicted) => {
#[cfg(feature = "statistics")]
{
self.statistics.insertions += 1;
self.statistics.evictions += u64::from(will_evict);
}
(self.queue[ptr].value_mut(), evicted)
}
InsertionResult::Updated(ptr) => {
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
(self.queue[ptr].value_mut(), None)
}
_ => unreachable!("Unexpected insertion result: {:?}", result),
}
}
pub fn try_insert_or_update(
&mut self,
key: Key,
value: Value,
) -> Result<&Value, (Key, Value)> {
self.try_insert_or_update_mut(key, value).map(|v| &*v)
}
pub fn try_insert_or_update_mut(
&mut self,
key: Key,
value: Value,
) -> Result<&mut Value, (Key, Value)> {
let will_evict = self.queue.len() == self.capacity.get();
let result = PolicyType::insert_or_update_entry(
key,
false,
|_, is_insert| {
if is_insert && will_evict {
InsertOrUpdateAction::NoInsert(Some(value))
} else {
InsertOrUpdateAction::InsertOrUpdate(value)
}
},
&mut self.metadata,
&mut self.queue,
);
match result {
InsertionResult::Inserted(ptr, _) => {
#[cfg(feature = "statistics")]
{
self.statistics.insertions += 1;
}
Ok(self.queue[ptr].value_mut())
}
InsertionResult::Updated(ptr) => {
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
Ok(self.queue[ptr].value_mut())
}
InsertionResult::NotFoundNoInsert(k, v) => Err((k, v.unwrap())),
_ => unreachable!("Unexpected insertion result: {:?}", result),
}
}
pub fn try_get_or_insert_with(
&mut self,
key: Key,
or_insert: impl FnOnce(&Key) -> Value,
) -> Result<&Value, Key> {
self.try_get_or_insert_with_mut(key, or_insert).map(|v| &*v)
}
pub fn try_get_or_insert_with_mut(
&mut self,
key: Key,
or_insert: impl FnOnce(&Key) -> Value,
) -> Result<&mut Value, Key> {
let will_evict = self.queue.len() == self.capacity.get();
let result = PolicyType::insert_or_update_entry(
key,
false,
|key, is_insert| {
if is_insert && !will_evict {
InsertOrUpdateAction::InsertOrUpdate(or_insert(key))
} else if !is_insert {
InsertOrUpdateAction::TouchNoUpdate
} else {
InsertOrUpdateAction::NoInsert(None)
}
},
&mut self.metadata,
&mut self.queue,
);
match result {
InsertionResult::Inserted(ptr, _) => {
#[cfg(feature = "statistics")]
{
self.statistics.insertions += 1;
}
Ok(self.queue[ptr].value_mut())
}
InsertionResult::Updated(ptr) => {
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
Ok(self.queue[ptr].value_mut())
}
InsertionResult::NotFoundNoInsert(k, _) => Err(k),
InsertionResult::FoundTouchedNoUpdate(ptr) => {
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
Ok(self.queue[ptr].value_mut())
}
}
}
pub fn get(
&mut self,
key: &Key,
) -> Option<&Value> {
self.get_mut(key).map(|v| &*v)
}
pub fn get_mut(
&mut self,
key: &Key,
) -> Option<&mut Value> {
if let Some(index) = self.queue.get_ptr(key) {
PolicyType::touch_entry(index, &mut self.metadata, &mut self.queue);
#[cfg(feature = "statistics")]
{
self.statistics.hits += 1;
}
Some(self.queue[index].value_mut())
} else {
#[cfg(feature = "statistics")]
{
self.statistics.misses += 1;
}
None
}
}
pub fn pop(&mut self) -> Option<(Key, Value)> {
PolicyType::evict_entry(&mut self.metadata, &mut self.queue).map(|(key, entry)| {
#[cfg(feature = "statistics")]
{
self.statistics.evictions += 1;
}
(key, entry.into_value())
})
}
pub fn remove(
&mut self,
key: &Key,
) -> Option<Value> {
PolicyType::remove_key(key, &mut self.metadata, &mut self.queue)
.map(|entry| entry.into_value())
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn capacity(&self) -> usize {
self.capacity.get()
}
pub fn set_capacity(
&mut self,
capacity: NonZeroUsize,
) -> Vec<(Key, Value)> {
assert!((capacity.get() as u32) < u32::MAX - 1, "Capacity too large");
let mut evicted = Vec::with_capacity(self.queue.len().saturating_sub(capacity.get()));
if capacity.get() < self.queue.len() {
while self.queue.len() > capacity.get()
&& let Some(kv) = self.pop()
{
evicted.push(kv);
}
}
self.capacity = capacity;
evicted
}
pub fn evict_to_size(
&mut self,
n: usize,
) -> Vec<(Key, Value)> {
let mut evicted = Vec::with_capacity(n);
while self.queue.len() > n
&& let Some(kv) = self.pop()
{
evicted.push(kv);
}
evicted
}
pub fn evict_n_entries(
&mut self,
n: usize,
) -> Vec<(Key, Value)> {
let mut evicted = Vec::with_capacity(n);
for _ in 0..n {
if let Some(kv) = self.pop() {
evicted.push(kv);
} else {
break; }
}
evicted
}
pub fn iter(&self) -> impl Iterator<Item = (&Key, &Value)> {
PolicyType::iter(&self.metadata, &self.queue)
}
pub fn keys(&self) -> impl Iterator<Item = &Key> {
PolicyType::iter(&self.metadata, &self.queue).map(|(k, _)| k)
}
pub fn values(&self) -> impl Iterator<Item = &Value> {
PolicyType::iter(&self.metadata, &self.queue).map(|(_, v)| v)
}
pub fn retain<F>(
&mut self,
mut f: F,
) where
F: FnMut(&Key, &mut Value) -> bool,
{
let mut next = self.queue.head_cursor_mut();
while let Some((k, v)) = next.current_mut() {
if !f(k, v.value_mut()) {
let (ptr, _) = PolicyType::remove_entry(
next.ptr().unwrap(),
&mut self.metadata,
&mut self.queue,
);
if let Some(ptr) = ptr {
next = self.queue.ptr_cursor_mut(ptr);
} else {
break;
}
} else {
next.move_next();
if next.at_head() {
break;
}
}
}
}
pub fn shrink_to_fit(&mut self) {
self.queue.shrink_to_fit();
}
}
impl<K: Hash + Eq + std::fmt::Debug, Value: std::fmt::Debug, PolicyType: Policy<Value>>
Cache<K, Value, PolicyType>
{
#[doc(hidden)]
#[cfg(all(debug_assertions, feature = "internal-debugging"))]
pub fn debug_validate(&self) {
PolicyType::debug_validate(&self.metadata, &self.queue);
}
}
impl<K: Hash + Eq, V, PolicyType: Policy<V>> IntoIterator for Cache<K, V, PolicyType> {
type IntoIter = PolicyType::IntoIter<K>;
type Item = (K, V);
fn into_iter(self) -> Self::IntoIter {
PolicyType::into_iter(self.metadata, self.queue)
}
}
impl<Key: Hash + Eq, Value, PolicyType: Policy<Value>> std::iter::FromIterator<(Key, Value)>
for Cache<Key, Value, PolicyType>
{
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = (Key, Value)>,
{
let mut queue: LinkedHashMap<
Key,
<PolicyType::MetadataType as Metadata<Value>>::EntryType,
RandomState,
> = LinkedHashMap::default();
let mut metadata = PolicyType::MetadataType::default();
#[cfg(feature = "statistics")]
let mut hits = 0;
#[cfg(feature = "statistics")]
let mut insertions = 0;
for (key, value) in iter {
let result = PolicyType::insert_or_update_entry(
key,
false,
|_, _| InsertOrUpdateAction::InsertOrUpdate(value),
&mut metadata,
&mut queue,
);
match result {
InsertionResult::Inserted(_, _) => {
#[cfg(feature = "statistics")]
{
insertions += 1;
}
}
InsertionResult::Updated(_) => {
#[cfg(feature = "statistics")]
{
hits += 1;
}
}
_ => unreachable!("Unexpected insertion result: {:?}", result),
}
}
let capacity = NonZeroUsize::new(queue.len().max(1)).unwrap();
Self {
#[cfg(feature = "statistics")]
statistics: Statistics {
hits,
insertions,
evictions: 0,
len: queue.len(),
capacity,
misses: 0,
},
queue,
capacity,
metadata,
}
}
}
impl<K: Hash + Eq, V, PolicyType: Policy<V>> Extend<(K, V)> for Cache<K, V, PolicyType> {
fn extend<I>(
&mut self,
iter: I,
) where
I: IntoIterator<Item = (K, V)>,
{
for (key, value) in iter {
self.insert(key, value);
}
}
}
#[cfg(test)]
mod tests {
use ntest::timeout;
#[test]
#[timeout(1000)]
fn test_lru_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::LruPolicy;
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
fn test_lfu_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::LfuPolicy;
let mut cache = Cache::<i32, String, LfuPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
fn test_mru_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::MruPolicy;
let mut cache = Cache::<i32, String, MruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
fn test_fifo_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::FifoPolicy;
let mut cache = Cache::<i32, String, FifoPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
fn test_lifo_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::LifoPolicy;
let mut cache = Cache::<i32, String, LifoPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
#[cfg(feature = "rand")]
fn test_random_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::RandomPolicy;
let mut cache = Cache::<i32, String, RandomPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
fn test_sieve_cache_clone() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::SievePolicy;
let mut cache = Cache::<i32, String, SievePolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let cloned_cache = cache.clone();
assert_eq!(cloned_cache.len(), 2);
assert_eq!(cloned_cache.peek(&1), Some(&"one".to_string()));
assert_eq!(cloned_cache.peek(&2), Some(&"two".to_string()));
}
#[test]
#[timeout(1000)]
fn test_lfu_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::LfuPolicy;
let mut cache = Cache::<i32, String, LfuPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
#[test]
#[timeout(1000)]
fn test_lru_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::LruPolicy;
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
#[test]
#[timeout(1000)]
fn test_mru_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::MruPolicy;
let mut cache = Cache::<i32, String, MruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
#[test]
#[timeout(1000)]
fn test_fifo_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::FifoPolicy;
let mut cache = Cache::<i32, String, FifoPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
#[test]
#[timeout(1000)]
fn test_lifo_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::LifoPolicy;
let mut cache = Cache::<i32, String, LifoPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
#[test]
#[timeout(1000)]
#[cfg(feature = "rand")]
fn test_random_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::RandomPolicy;
let mut cache = Cache::<i32, String, RandomPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
#[test]
#[timeout(1000)]
fn test_sieve_cache_debug() {
use std::num::NonZeroUsize;
use crate::Cache;
use crate::SievePolicy;
let mut cache = Cache::<i32, String, SievePolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let debug_str = format!("{:?}", cache);
assert!(debug_str.contains("Cache"));
assert!(debug_str.contains("\"one\""));
assert!(debug_str.contains("\"two\""));
}
}
#[cfg(all(test, feature = "statistics"))]
mod statistics_tests {
use ntest::timeout;
use super::*;
#[test]
#[timeout(1000)]
fn test_statistics_initialization() {
let cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
let stats = cache.statistics();
assert_eq!(stats.len, 0);
assert_eq!(stats.capacity.get(), 3);
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 0);
assert_eq!(stats.insertions, 0);
assert_eq!(stats.evictions, 0);
}
#[test]
#[timeout(1000)]
fn test_statistics_insertions_and_misses() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.insert(3, "three".to_string());
let stats = cache.statistics();
assert_eq!(stats.len, 3);
assert_eq!(stats.insertions, 3);
assert_eq!(stats.misses, 0);
assert_eq!(stats.hits, 0);
assert_eq!(stats.evictions, 0);
}
#[test]
#[timeout(1000)]
fn test_statistics_hits() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.get(&1);
cache.get(&2);
cache.get(&1);
let stats = cache.statistics();
assert_eq!(stats.hits, 3);
assert_eq!(stats.misses, 0);
assert_eq!(stats.insertions, 2);
}
#[test]
#[timeout(1000)]
fn test_statistics_misses_on_failed_get() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.get(&999);
cache.get(&888);
let stats = cache.statistics();
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 2);
assert_eq!(stats.insertions, 1);
}
#[test]
#[timeout(1000)]
fn test_statistics_evictions() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(2).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
let stats_before = cache.statistics();
assert_eq!(stats_before.evictions, 0);
cache.insert(3, "three".to_string());
let stats_after = cache.statistics();
assert_eq!(stats_after.evictions, 1);
assert_eq!(stats_after.len, 2);
assert_eq!(stats_after.insertions, 3);
}
#[test]
#[timeout(1000)]
fn test_statistics_with_get_or_insert_with() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.get_or_insert_with(1, |_| "one".to_string());
cache.get_or_insert_with(2, |_| "two".to_string());
let stats = cache.statistics();
assert_eq!(stats.insertions, 2);
assert_eq!(stats.misses, 2);
assert_eq!(stats.hits, 0);
cache.get_or_insert_with(1, |_| "one_new".to_string());
let stats = cache.statistics();
assert_eq!(stats.insertions, 2);
assert_eq!(stats.misses, 2);
assert_eq!(stats.hits, 1);
}
#[test]
#[timeout(1000)]
fn test_statistics_with_try_insert() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(2).unwrap());
assert!(cache.try_insert_or_update(1, "one".to_string()).is_ok());
assert!(cache.try_insert_or_update(2, "two".to_string()).is_ok());
let stats = cache.statistics();
assert_eq!(stats.insertions, 2);
assert_eq!(
cache.try_insert_or_update(1, "one_new".to_string()),
Ok(&"one_new".to_string())
);
let stats = cache.statistics();
assert_eq!(stats.insertions, 2); assert_eq!(stats.hits, 1); }
#[test]
#[timeout(1000)]
fn test_statistics_reset() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.get(&1);
cache.get(&999);
let stats_before = cache.statistics();
assert!(stats_before.hits > 0);
assert!(stats_before.misses > 0);
assert!(stats_before.insertions > 0);
cache.reset_statistics();
let stats_after = cache.statistics();
assert_eq!(stats_after.hits, 0);
assert_eq!(stats_after.misses, 0);
assert_eq!(stats_after.insertions, 0);
assert_eq!(stats_after.evictions, 0);
assert_eq!(stats_after.len, 2); assert_eq!(stats_after.capacity.get(), 3);
}
#[test]
#[timeout(1000)]
fn test_statistics_with_remove() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.insert(3, "three".to_string());
let stats_before = cache.statistics();
assert_eq!(stats_before.len, 3);
cache.remove(&2);
let stats_after = cache.statistics();
assert_eq!(stats_after.len, 2);
assert_eq!(stats_after.insertions, stats_before.insertions);
assert_eq!(stats_after.hits, stats_before.hits);
assert_eq!(stats_after.misses, stats_before.misses);
}
#[test]
#[timeout(1000)]
fn test_statistics_with_clear() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.get(&1);
let stats_before = cache.statistics();
assert!(stats_before.len > 0);
cache.clear();
let stats_after = cache.statistics();
assert_eq!(stats_after.len, 0);
assert_eq!(stats_after.insertions, stats_before.insertions);
assert_eq!(stats_after.hits, stats_before.hits);
assert_eq!(stats_after.misses, stats_before.misses);
}
#[test]
#[timeout(1000)]
fn test_statistics_with_set_capacity() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.insert(3, "three".to_string());
let stats_before = cache.statistics();
assert_eq!(stats_before.capacity.get(), 3);
assert_eq!(stats_before.evictions, 0);
let evicted = cache.set_capacity(NonZeroUsize::new(1).unwrap());
assert_eq!(evicted.len(), 2);
let stats_after = cache.statistics();
assert_eq!(stats_after.capacity.get(), 1);
assert_eq!(stats_after.len, 1);
assert_eq!(stats_after.evictions, 2); }
#[test]
#[timeout(1000)]
fn test_statistics_with_different_policies() {
let mut lfu_cache = Cache::<i32, String, LfuPolicy>::new(NonZeroUsize::new(2).unwrap());
lfu_cache.insert(1, "one".to_string());
lfu_cache.insert(2, "two".to_string());
lfu_cache.get(&1); lfu_cache.insert(3, "three".to_string());
let lfu_stats = lfu_cache.statistics();
assert_eq!(lfu_stats.evictions, 1);
assert_eq!(lfu_stats.hits, 1);
assert_eq!(lfu_stats.insertions, 3);
let mut mru_cache = Cache::<i32, String, MruPolicy>::new(NonZeroUsize::new(2).unwrap());
mru_cache.insert(1, "one".to_string());
mru_cache.insert(2, "two".to_string());
mru_cache.get(&1); mru_cache.insert(3, "three".to_string());
let mru_stats = mru_cache.statistics();
assert_eq!(mru_stats.evictions, 1);
assert_eq!(mru_stats.hits, 1);
assert_eq!(mru_stats.insertions, 3);
}
#[test]
#[timeout(1000)]
fn test_statistics_clone() {
let mut cache = Cache::<i32, String, LruPolicy>::new(NonZeroUsize::new(3).unwrap());
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.get(&1);
cache.get(&999);
let cloned_cache = cache.clone();
let original_stats = cache.statistics();
let cloned_stats = cloned_cache.statistics();
assert_eq!(original_stats.len, cloned_stats.len);
assert_eq!(original_stats.capacity, cloned_stats.capacity);
assert_eq!(original_stats.hits, cloned_stats.hits);
assert_eq!(original_stats.misses, cloned_stats.misses);
assert_eq!(original_stats.insertions, cloned_stats.insertions);
assert_eq!(original_stats.evictions, cloned_stats.evictions);
}
}