use neocache::NeoCache;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn concurrent_inserts_all_visible() {
const THREADS: usize = 8;
const PER_THREAD: u64 = 1_000;
let map: Arc<NeoCache<u64, u64>> = Arc::new(NeoCache::new_unbounded());
let barrier = Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|t| {
let m = Arc::clone(&map);
let b = Arc::clone(&barrier);
let start = (t as u64) * PER_THREAD;
thread::spawn(move || {
b.wait();
for i in start..start + PER_THREAD {
m.insert(i, i * 2);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
for i in 0..THREADS as u64 * PER_THREAD {
assert_eq!(*map.get(&i).unwrap(), i * 2, "key {i} missing or wrong");
}
}
#[test]
fn concurrent_entry_increments() {
const THREADS: usize = 8;
const INCREMENTS: u64 = 500;
const KEY: &str = "counter";
let map: Arc<NeoCache<&'static str, u64>> = Arc::new(NeoCache::new_unbounded());
map.insert(KEY, 0u64);
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let m = Arc::clone(&map);
thread::spawn(move || {
for _ in 0..INCREMENTS {
m.entry(KEY).and_modify(|v| *v += 1);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let total = *map.get(&KEY).unwrap();
assert_eq!(total, THREADS as u64 * INCREMENTS);
}
#[test]
fn concurrent_readers_and_writer() {
const READERS: usize = 6;
const WRITES: usize = 200;
let map: Arc<NeoCache<u32, u32>> = Arc::new(NeoCache::new_unbounded());
for i in 0..100u32 {
map.insert(i, i);
}
let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
let reader_handles: Vec<_> = (0..READERS)
.map(|_| {
let m = Arc::clone(&map);
let s = Arc::clone(&stop);
thread::spawn(move || {
while !s.load(std::sync::atomic::Ordering::Relaxed) {
for i in 0..100u32 {
let _ = m.get(&i);
}
}
})
})
.collect();
for i in 0..WRITES as u32 {
map.insert(i % 100, i);
}
stop.store(true, std::sync::atomic::Ordering::Relaxed);
for h in reader_handles {
h.join().unwrap();
}
}
#[test]
fn capacity_contract_exact_divisor() {
const CAPACITY: usize = 64;
const SHARDS: usize = 4;
const SHARD_CAP: usize = CAPACITY / SHARDS;
let map: NeoCache<u64, u64> = NeoCache::with_shard_amount(CAPACITY, SHARDS);
for i in 0..(CAPACITY * 3) as u64 {
map.insert(i, i);
}
assert!(
map.len() <= CAPACITY,
"len {} exceeded capacity {}",
map.len(),
CAPACITY
);
let _ = SHARD_CAP; }
#[test]
fn unbounded_no_eviction() {
const N: usize = 1_000;
let map: NeoCache<u64, u64> = NeoCache::new_unbounded();
for i in 0..N as u64 {
map.insert(i, i);
}
assert_eq!(map.len(), N);
}
#[test]
fn hot_key_survives_eviction() {
let map: NeoCache<u64, u64> = NeoCache::with_shard_amount(32, 4);
let hot_key = 0u64;
map.insert(hot_key, 999);
for _ in 0..5 {
let _ = map.get(&hot_key);
}
for i in 1..200u64 {
map.insert(i, i);
}
assert!(
map.get(&hot_key).is_some(),
"hot key was evicted despite high frequency"
);
}
#[test]
fn ghost_set_promotes_on_reinsertion() {
let map: NeoCache<u64, u64> = NeoCache::with_shard_amount(8, 4);
for i in 0..40u64 {
map.insert(i, i);
}
map.insert(0, 42);
if let Some(v) = map.get(&0) {
assert_eq!(*v, 42);
}
}
#[test]
fn remove_then_reinsert() {
let map: NeoCache<u64, u64> = NeoCache::new_unbounded();
map.insert(1u64, 10);
assert_eq!(map.remove(&1u64).map(|(_, v)| v), Some(10));
assert!(map.get(&1u64).is_none());
map.insert(1u64, 20);
assert_eq!(*map.get(&1u64).unwrap(), 20);
}
#[test]
fn retain_correctness() {
let map: NeoCache<u64, u64> = NeoCache::new_unbounded();
for i in 0..20u64 {
map.insert(i, i);
}
map.retain(|_k, v| *v % 2 == 0);
assert_eq!(map.len(), 10);
for i in 0..20u64 {
if i % 2 == 0 {
assert!(map.contains_key(&i), "even key {i} missing");
} else {
assert!(!map.contains_key(&i), "odd key {i} present after retain");
}
}
}