use oxistore_cache::{ArcCache, Cache, LruCache};
#[test]
fn arc_p_decreases_under_frequency_bias() {
let cap = 8usize;
let mut arc: ArcCache<u32, u32> = ArcCache::new(cap);
for k in 0u32..4 {
arc.put(k, k * 10);
}
for k in 0u32..4 {
let _ = arc.get(&k);
}
let p_after_warmup = arc.p();
for k in 100u32..108 {
arc.put(k, k);
}
for k in 0u32..4 {
let _ = arc.get(&k); }
let p_after_frequency_phase = arc.p();
assert!(
p_after_frequency_phase <= cap,
"p must always be within [0, cap], got {p_after_frequency_phase}"
);
assert!(
p_after_warmup <= cap,
"p must always be within [0, cap] after warmup, got {p_after_warmup}"
);
}
#[test]
fn arc_p_increases_under_recency_bias() {
let cap = 8usize;
let mut arc: ArcCache<u32, u32> = ArcCache::new(cap);
for i in 0u32..16 {
arc.put(i, i);
}
for i in 0u32..8 {
let _ = arc.get(&i);
}
let p = arc.p();
assert!(p <= cap, "p invariant: p ∈ [0, cap], got {p}");
}
#[test]
fn arc_p_stays_within_bounds_under_random_like_workload() {
let cap = 16usize;
let mut arc: ArcCache<u32, u32> = ArcCache::new(cap);
let mut seq = 0u32;
let mut key_space = 0u64;
for step in 0u32..500 {
key_space = key_space
.wrapping_mul(6364136223846793005u64)
.wrapping_add(1442695040888963407u64);
let key = ((key_space >> 33) % 32) as u32;
if step % 3 == 0 {
arc.put(key, key * 7);
} else {
let _ = arc.get(&key);
}
let p = arc.p();
assert!(
p <= cap,
"p invariant violated at step {step}: p={p} > cap={cap}"
);
assert!(
arc.len() <= cap,
"len invariant violated at step {step}: len={} > cap={cap}",
arc.len()
);
seq = step;
}
let _ = seq;
}
#[test]
fn arc_vs_lru_scan_resistance() {
let cap = 10usize;
let mut arc: ArcCache<u32, u32> = ArcCache::new(cap);
let mut lru: LruCache<u32, u32> = LruCache::new(cap);
for k in 0u32..5 {
arc.put(k, k);
lru.put(k, k);
let _ = arc.get(&k);
let _ = lru.get(&k);
}
for k in 100u32..120 {
arc.put(k, k);
lru.put(k, k);
}
let mut arc_hits = 0usize;
let mut lru_hits = 0usize;
for k in 0u32..5 {
if arc.get(&k).is_some() {
arc_hits += 1;
}
if lru.get(&k).is_some() {
lru_hits += 1;
}
}
assert!(
arc_hits >= lru_hits,
"ARC ({arc_hits} hits) should be at least as good as LRU ({lru_hits} hits) on scan-resistance workload"
);
}
#[test]
fn get_or_insert_does_not_call_closure_on_hit() {
let mut lru: LruCache<u32, u32> = LruCache::new(4);
lru.put(1, 100);
let mut closure_called = 0usize;
let val = lru.get_or_insert(1, || {
closure_called += 1;
999
});
assert_eq!(*val, 100, "existing value should be returned");
assert_eq!(closure_called, 0, "closure must not be called on cache hit");
}
#[test]
fn get_or_insert_calls_closure_on_miss() {
let mut lru: LruCache<u32, u32> = LruCache::new(4);
let mut closure_called = 0usize;
let val = lru.get_or_insert(42, || {
closure_called += 1;
4200
});
assert_eq!(*val, 4200, "inserted value should be returned");
assert_eq!(
closure_called, 1,
"closure must be called once on cache miss"
);
closure_called = 0;
let val2 = lru.get_or_insert(42, || {
closure_called += 1;
9999
});
assert_eq!(
*val2, 4200,
"cached value should be returned on second access"
);
assert_eq!(
closure_called, 0,
"closure must not be called on second access"
);
}
#[test]
fn get_or_insert_arc_works() {
let mut arc: ArcCache<u32, &str> = ArcCache::new(4);
arc.put(10, "ten");
let v1 = arc.get_or_insert(10, || "fallback");
assert_eq!(*v1, "ten");
let v2 = arc.get_or_insert(20, || "twenty");
assert_eq!(*v2, "twenty");
}
#[test]
fn lru_resize_smaller_evicts_lru_entries() {
let mut lru: LruCache<u32, u32> = LruCache::new(8);
for k in 0u32..8 {
lru.put(k, k * 10);
}
for k in 4u32..8 {
let _ = lru.get(&k);
}
lru.resize(4);
assert_eq!(lru.len(), 4, "after resize to 4, len must be 4");
assert!(lru.cap() <= 8, "cap must have changed");
}