#![cfg(not(feature = "loom"))]
use dualcache_ff::{Config, DualCacheFF};
use dualcache_ff::unsafe_core::{BatchBuf};
use std::sync::atomic::Ordering;
use dualcache_ff::lossy_queue::LossyQueue;
use dualcache_ff::tls::TlsProvider;
use std::thread;
use std::time::Duration;
mod common;
use common::run_with_timeout;
#[test]
fn test_batch_buf_overflow() {
let mut buf = BatchBuf::<u64, u64>::new();
let mut flush_count = 0;
for i in 0..40 {
if buf.push((i, i, i, false)) {
buf.drain_to_vec();
flush_count += 1;
}
}
assert_eq!(flush_count, 1, "Should flush on exactly the 32nd item");
assert_eq!(buf.len(), 8);
let items = buf.drain_to_vec();
assert_eq!(items.len(), 8);
assert_eq!(buf.len(), 0);
assert!(buf.is_empty());
}
#[test]
fn test_lossy_queue_drop() {
let q = LossyQueue::<u64>::new(4);
assert!(q.try_send(1).is_ok());
assert!(q.try_send(2).is_ok());
assert!(q.try_send(3).is_ok());
assert!(q.try_send(4).is_ok());
let res = q.try_send(5);
assert!(res.is_err(), "Queue should reject when full");
assert_eq!(q.try_recv(), Some(1));
assert_eq!(q.try_recv(), Some(2));
assert_eq!(q.try_recv(), Some(3));
assert_eq!(q.try_recv(), Some(4));
assert_eq!(q.try_recv(), None);
}
#[test]
fn test_daemon_shutdown_during_active() {
run_with_timeout(Duration::from_secs(5), || {
let config = Config::new_expert(128, 64, 64, 200, 2);
let (cache, daemon) = DualCacheFF::new_headless(config);
let handle = thread::spawn(move || {
daemon.run();
});
for i in 0..1000 {
cache.insert(i, i);
}
drop(cache);
handle.join().unwrap();
});
}
#[test]
fn test_record_hit_out_of_bounds() {
run_with_timeout(Duration::from_secs(5), || {
let config = Config::new_expert(128, 64, 64, 200, 2);
let (cache, daemon) = DualCacheFF::<u64, u64>::new_headless(config);
let handle = thread::spawn(move || {
daemon.run();
});
let mut bad_batch = [usize::MAX; 64];
bad_batch[0] = 9999;
let _ = cache.hit_tx.try_send(bad_batch);
cache.sync();
drop(cache);
handle.join().unwrap();
});
}
#[test]
fn test_hash_collision_t1_t2() {
run_with_timeout(Duration::from_secs(5), || {
let config = Config::new_expert(128, 2, 2, 200, 2); let cache = DualCacheFF::new(config);
for i in 0..10 {
let session = cache.begin_cold_start_session();
session.warmup(i, i);
}
cache.sync();
for i in 0..10 {
let _ = cache.get(&i);
}
cache.sync();
});
}
struct DummyTls {
allow_read: std::sync::atomic::AtomicBool,
}
impl TlsProvider for DummyTls {
fn get_worker_id(&self) -> Option<usize> {
if self.allow_read.load(Ordering::Relaxed) { Some(0) } else { None }
}
fn with_hit_buf(&self, _f: &mut dyn FnMut(&mut ([usize; 64], usize))) {}
fn with_l1_filter(&self, _f: &mut dyn FnMut(&mut ([u8; 4096], usize))) {}
fn with_last_flush_tick(&self, _f: &mut dyn FnMut(&mut u64)) {}
fn with_warmup_state(&self, f: &mut dyn FnMut(&mut u8)) {
let mut state = 255;
f(&mut state);
}
}
#[test]
fn test_no_std_tls_fallback() {
run_with_timeout(Duration::from_secs(5), || {
let config = Config::new_expert(128, 64, 64, 200, 2);
let tls = DummyTls { allow_read: std::sync::atomic::AtomicBool::new(false) };
let (cache, daemon) = DualCacheFF::<u64, u64, ahash::RandomState, DummyTls>::new_headless_with_tls(config, tls);
let handle = thread::spawn(move || {
daemon.run();
});
cache.insert(1, 100);
cache.sync();
cache.tls.allow_read.store(true, Ordering::Relaxed);
assert_eq!(cache.get(&1), Some(100));
cache.sync();
drop(cache);
handle.join().unwrap();
});
}
#[test]
fn test_cold_start_session_pressure() {
run_with_timeout(Duration::from_secs(5), || {
let config = Config::new_expert(128, 64, 64, 200, 1024);
let cache = DualCacheFF::new(config);
for i in 0..200 {
let session = cache.begin_cold_start_session();
session.warmup(i, i * 10);
}
cache.sync();
let mut found = 0;
for i in 0..50 {
if cache.get(&i).is_some() {
found += 1;
}
}
assert!(found > 0, "The earliest inserted cold start items should be heavily protected and survive");
});
}
#[test]
fn test_config_memory_budget() {
let budget_mb = 10;
let config = Config::with_memory_budget(budget_mb, 60);
assert!(config.capacity > 10_000);
assert_eq!(config.capacity.count_ones(), 1, "Capacity must be a power of two");
assert_eq!(config.poll_us, 1000, "Default memory budget constructor should use 1000us");
}
#[test]
fn test_config_poll_clamping() {
let config1 = Config::with_memory_budget(1, 0).with_poll_us(0);
assert_eq!(config1.poll_us, 1_000, "Should clamp to min 1000us");
let config2 = Config::with_memory_budget(1, 0).with_poll_us(20000);
assert_eq!(config2.poll_us, 10_000, "Should clamp to max 10000us");
}