use retainer::*;
use std::time::{Duration, Instant};
fn expired_instant() -> Instant {
Instant::now().checked_sub(Duration::from_secs(1)).unwrap()
}
#[tokio::test]
async fn test_cache_default_starts_empty() {
let cache = Cache::<u8, u8>::default();
assert!(cache.is_empty().await);
}
#[tokio::test]
async fn test_cache_is_empty_tracks_entries() {
let cache = Cache::<u8, u8>::new();
assert!(cache.is_empty().await);
cache.insert(1, 1, CacheExpiration::none()).await;
assert!(!cache.is_empty().await);
assert_eq!(cache.remove(&1).await, Some(1));
assert!(cache.is_empty().await);
}
#[tokio::test]
async fn test_cache_is_not_empty_when_entry_is_expired() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 1, expired_instant()).await;
assert_eq!(cache.expired().await, 1);
assert!(!cache.is_empty().await);
}
#[tokio::test]
async fn test_cache_counts_entries_by_expiration() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 2, CacheExpiration::none()).await;
cache.insert(2, 2, CacheExpiration::none()).await;
cache.insert(3, 3, expired_instant()).await;
assert_eq!(cache.len().await, 3);
assert_eq!(cache.expired().await, 1);
assert_eq!(cache.unexpired().await, 2);
}
#[tokio::test]
async fn test_cache_clear_removes_all_entries() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 1, CacheExpiration::none()).await;
cache.insert(2, 2, expired_instant()).await;
assert_eq!(cache.len().await, 2);
assert_eq!(cache.unexpired().await, 1);
assert_eq!(cache.expired().await, 1);
cache.clear().await;
assert!(cache.is_empty().await);
assert_eq!(cache.len().await, 0);
assert_eq!(cache.unexpired().await, 0);
assert_eq!(cache.expired().await, 0);
}
#[tokio::test]
async fn test_cache_update_changes_value() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 1, CacheExpiration::none()).await;
assert_eq!(cache.get(&1).await.unwrap().value(), &1);
cache.update(&1, |value| *value = 5).await;
assert_eq!(cache.get(&1).await.unwrap().value(), &5);
}
#[tokio::test]
async fn test_cache_borrow_types() {
let key = "key".to_string();
let cache = Cache::<String, bool>::new();
cache
.insert(key.clone(), true, CacheExpiration::none())
.await;
let lookup: &str = &key;
assert!(cache.get(lookup).await.unwrap().value());
}
#[tokio::test]
async fn test_cache_update_ignores_missing_entry() {
let cache = Cache::<u8, u8>::new();
let mut called = false;
assert!(cache.is_empty().await);
cache.update(&1, |_| called = true).await;
assert!(!called);
}
#[tokio::test]
async fn test_cache_update_preserves_expiration() {
let cache = Cache::<u8, u8>::new();
let expiration = Instant::now().checked_add(Duration::from_secs(60)).unwrap();
cache.insert(1, 1, expiration).await;
{
let guard = cache.get(&1).await.unwrap();
assert_eq!(*guard, 1);
assert_eq!(guard.expiration().instant(), &Some(expiration));
}
cache.update(&1, |value| *value = 2).await;
let guard = cache.get(&1).await.unwrap();
assert_eq!(*guard, 2);
assert_eq!(guard.expiration().instant(), &Some(expiration));
}
#[tokio::test]
async fn test_cache_get_rejects_missing_entry() {
let cache = Cache::<u8, u8>::new();
assert!(cache.get(&1).await.is_none());
}
#[tokio::test]
async fn test_cache_get_rejects_expired_entry_without_removing_it() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, expired_instant()).await;
assert_eq!(cache.len().await, 1);
assert_eq!(cache.expired().await, 1);
assert!(cache.get(&1).await.is_none());
assert_eq!(cache.len().await, 1);
assert_eq!(cache.expired().await, 1);
}
#[tokio::test]
async fn test_cache_insert_returns_unexpired_replaced_value() {
let cache = Cache::<u8, u8>::new();
assert_eq!(cache.insert(1, 10, CacheExpiration::none()).await, None);
assert_eq!(cache.insert(1, 20, CacheExpiration::none()).await, Some(10));
}
#[tokio::test]
async fn test_cache_insert_discards_expired_replaced_value() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 30, expired_instant()).await;
assert_eq!(cache.len().await, 1);
assert_eq!(cache.expired().await, 1);
assert_eq!(cache.insert(1, 40, CacheExpiration::none()).await, None);
assert_eq!(*cache.get(&1).await.unwrap(), 40);
}
#[tokio::test]
async fn test_cache_remove_discards_expired_value() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, expired_instant()).await;
assert_eq!(cache.len().await, 1);
assert_eq!(cache.expired().await, 1);
assert_eq!(cache.remove(&1).await, None);
assert!(cache.is_empty().await);
}
#[tokio::test]
async fn test_cache_remove_returns_unexpired_value() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, CacheExpiration::none()).await;
assert_eq!(*cache.get(&1).await.unwrap(), 10);
assert_eq!(cache.remove(&1).await, Some(10));
assert!(cache.is_empty().await);
}
#[tokio::test]
async fn test_cache_remove_missing_entry_returns_none() {
let cache = Cache::<u8, u8>::new();
assert!(cache.is_empty().await);
assert_eq!(cache.remove(&1).await, None);
}
#[tokio::test]
async fn test_cache_get_mut_changes_value() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, CacheExpiration::none()).await;
assert_eq!(*cache.get(&1).await.unwrap(), 10);
*cache.get_mut(&1).await.unwrap() = 20;
assert_eq!(*cache.get(&1).await.unwrap(), 20);
}
#[tokio::test]
async fn test_cache_get_mut_changes_expiration() {
let cache = Cache::<u8, u8>::new();
let initial = Instant::now().checked_add(Duration::from_secs(30)).unwrap();
let updated = Instant::now().checked_add(Duration::from_secs(60)).unwrap();
cache.insert(1, 10, initial).await;
assert_eq!(
cache.get(&1).await.unwrap().expiration().instant(),
&Some(initial)
);
*cache.get_mut(&1).await.unwrap().expiration_mut() = updated.into();
assert_eq!(
cache.get(&1).await.unwrap().expiration().instant(),
&Some(updated)
);
}
#[tokio::test]
async fn test_cache_get_mut_removes_expiration() {
let cache = Cache::<u8, u8>::new();
let expiration = Instant::now().checked_add(Duration::from_secs(30)).unwrap();
cache.insert(1, 10, expiration).await;
assert_eq!(
cache.get(&1).await.unwrap().expiration().instant(),
&Some(expiration)
);
*cache.get_mut(&1).await.unwrap().expiration_mut() = CacheExpiration::none();
assert_eq!(cache.get(&1).await.unwrap().expiration().instant(), &None);
}
#[tokio::test]
async fn test_cache_get_mut_rejects_missing_entry() {
let cache = Cache::<u8, u8>::new();
assert!(cache.get_mut(&1).await.is_none());
}
#[tokio::test]
async fn test_cache_get_mut_rejects_expired_entry() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, expired_instant()).await;
assert_eq!(cache.expired().await, 1);
assert!(cache.get_mut(&1).await.is_none());
assert_eq!(cache.expired().await, 1);
}
#[tokio::test]
async fn test_cache_get_mut_accepts_borrowed_key() {
let cache = Cache::<String, u8>::new();
cache
.insert("key".to_owned(), 10, CacheExpiration::none())
.await;
assert_eq!(*cache.get("key").await.unwrap(), 10);
*cache.get_mut("key").await.unwrap() = 20;
assert_eq!(*cache.get("key").await.unwrap(), 20);
}
#[tokio::test]
async fn test_cache_update_ignores_expired_entry() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, expired_instant()).await;
assert_eq!(cache.len().await, 1);
assert_eq!(cache.expired().await, 1);
let mut called = false;
cache.update(&1, |_| called = true).await;
assert!(!called);
}
#[tokio::test]
async fn test_cache_get_reports_expiration_metadata() {
let cache = Cache::<u8, u8>::new();
let expiration = Instant::now().checked_add(Duration::from_secs(60)).unwrap();
cache.insert(1, 10, expiration).await;
assert_eq!(cache.len().await, 1);
let guard = cache.get(&1).await.unwrap();
assert_eq!(guard.expiration().instant(), &Some(expiration));
assert!(guard.expiration().remaining().unwrap() <= Duration::from_secs(60));
assert!(!guard.expiration().is_expired());
}
#[tokio::test]
async fn test_cache_purge_removes_all_expired_entries_with_oversized_sample() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, expired_instant()).await;
cache.insert(2, 20, expired_instant()).await;
cache.insert(3, 30, CacheExpiration::none()).await;
assert_eq!(cache.expired().await, 2);
assert_eq!(cache.unexpired().await, 1);
cache.purge(25, 0.25).await;
assert_eq!(cache.len().await, 1);
assert_eq!(cache.expired().await, 0);
assert_eq!(*cache.get(&3).await.unwrap(), 30);
}
#[tokio::test]
async fn test_cache_purge_removes_only_one_partial_sample() {
let cache = Cache::<u8, u8>::new();
for key in 0..5 {
cache.insert(key, key, expired_instant()).await;
}
assert_eq!(cache.len().await, 5);
assert_eq!(cache.expired().await, 5);
cache.purge(2, 1.0).await;
assert_eq!(cache.len().await, 3);
assert_eq!(cache.expired().await, 3);
}
#[tokio::test]
async fn test_cache_purge_accepts_empty_cache() {
let cache = Cache::<u8, u8>::new();
assert!(cache.is_empty().await);
cache.purge(25, 0.25).await;
assert!(cache.is_empty().await);
}
#[tokio::test]
async fn test_cache_purge_accepts_zero_threshold() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, CacheExpiration::none()).await;
assert_eq!(cache.unexpired().await, 1);
tokio::time::timeout(Duration::from_secs(1), cache.purge(1, 0.0))
.await
.expect("zero-threshold purge did not terminate");
assert_eq!(cache.unexpired().await, 1);
}
#[tokio::test]
async fn test_cache_purge_accepts_one_threshold() {
let cache = Cache::<u8, u8>::new();
cache.insert(1, 10, expired_instant()).await;
assert_eq!(cache.expired().await, 1);
cache.purge(1, 1.0).await;
assert!(cache.is_empty().await);
}
#[tokio::test]
#[should_panic(expected = "sample must be > 0")]
async fn test_cache_purge_rejects_zero_sample() {
let cache = Cache::<u8, u8>::new();
cache.purge(0, 0.25).await;
}
#[tokio::test]
#[should_panic(expected = "threshold must be 0..=1")]
async fn test_cache_purge_rejects_negative_threshold() {
let cache = Cache::<u8, u8>::new();
cache.purge(1, -0.1).await;
}
#[tokio::test]
#[should_panic(expected = "threshold must be 0..=1")]
async fn test_cache_purge_rejects_threshold_above_one() {
let cache = Cache::<u8, u8>::new();
cache.purge(1, 1.1).await;
}
#[tokio::test]
#[should_panic(expected = "threshold must be 0..=1")]
async fn test_cache_purge_rejects_non_finite_threshold() {
let cache = Cache::<u8, u8>::new();
cache.purge(1, f64::NAN).await;
}
#[tokio::test]
#[should_panic(expected = "frequency must be > 0")]
async fn test_cache_monitor_rejects_zero_frequency() {
let cache = Cache::<u8, u8>::new();
cache.monitor(1, 0.25, Duration::from_secs(0)).await;
}
#[test]
fn test_cache_expiration_from_milliseconds() {
let started = Instant::now();
let expiration = CacheExpiration::from(1_000_u64);
let finished = Instant::now();
let expiration = expiration.instant().unwrap();
let earliest = started.checked_add(Duration::from_secs(1)).unwrap();
let latest = finished.checked_add(Duration::from_secs(1)).unwrap();
assert!(expiration >= earliest);
assert!(expiration <= latest);
}
#[test]
fn test_cache_expiration_from_duration() {
let started = Instant::now();
let expiration = CacheExpiration::from(Duration::from_secs(1));
let finished = Instant::now();
let expiration = expiration.instant().unwrap();
let earliest = started.checked_add(Duration::from_secs(1)).unwrap();
let latest = finished.checked_add(Duration::from_secs(1)).unwrap();
assert!(expiration >= earliest);
assert!(expiration <= latest);
}
#[test]
fn test_cache_expiration_from_range() {
let range_started = Instant::now();
let expiration = CacheExpiration::from(1_000_u64..2_000_u64);
let range_finished = Instant::now();
let expiration = expiration.instant().unwrap();
let earliest_range_expiration = range_started
.checked_add(Duration::from_millis(1_000))
.unwrap();
let latest_range_expiration = range_finished
.checked_add(Duration::from_millis(2_000))
.unwrap();
assert!(expiration >= earliest_range_expiration);
assert!(expiration < latest_range_expiration);
}
#[test]
fn test_cache_expiration_none_never_expires() {
let expiration = CacheExpiration::none();
assert_eq!(expiration.instant(), &None);
assert_eq!(expiration.remaining(), None);
assert!(!expiration.is_expired());
}
#[test]
fn test_cache_expiration_preserves_exact_instant() {
let instant = Instant::now().checked_add(Duration::from_secs(60)).unwrap();
assert_eq!(CacheExpiration::new(instant).instant(), &Some(instant));
assert_eq!(CacheExpiration::from(instant).instant(), &Some(instant));
}
#[test]
fn test_expired_cache_expiration_has_no_remaining_time() {
let expiration = CacheExpiration::new(expired_instant());
assert_eq!(expiration.remaining(), Some(Duration::from_secs(0)));
}