use fluxmap::db::Database;
use fluxmap::error::FluxError;
use fluxmap::mem::{EvictionPolicy, MemSize};
use futures::StreamExt;
use std::sync::Arc;
#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
#[allow(dead_code)]
struct TestVal(String, u64);
impl MemSize for TestVal {
fn mem_size(&self) -> usize {
self.0.mem_size() + std::mem::size_of::<u64>() + std::mem::size_of::<Self>()
}
}
#[tokio::test]
async fn test_eviction_on_memory_limit() {
fastrand::seed(0);
let db: Arc<Database<String, i32>> = Arc::new(
Database::builder()
.max_memory(450)
.eviction_policy(EvictionPolicy::Lru)
.build()
.await
.unwrap(),
);
let handle = db.handle();
handle.insert("key1".to_string(), 1).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
handle.insert("key2".to_string(), 2).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
assert!(handle.get(&"key1".to_string()).unwrap().is_some());
assert!(handle.get(&"key2".to_string()).unwrap().is_some());
handle.insert("key3".to_string(), 3).await.unwrap();
assert!(
handle.get(&"key1".to_string()).unwrap().is_none(),
"key1 should have been evicted"
);
assert!(
handle.get(&"key2".to_string()).unwrap().is_some(),
"key2 should still exist"
);
assert!(
handle.get(&"key3".to_string()).unwrap().is_some(),
"key3 should exist"
);
let _ = handle.get(&"key2".to_string()).unwrap();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
handle.insert("key4".to_string(), 4).await.unwrap();
assert!(
handle.get(&"key3".to_string()).unwrap().is_none(),
"key3 should have been evicted"
);
assert!(
handle.get(&"key2".to_string()).unwrap().is_some(),
"key2 should still exist because it was accessed"
);
assert!(
handle.get(&"key4".to_string()).unwrap().is_some(),
"key4 should exist"
);
}
#[tokio::test]
async fn test_eviction_on_memory_limit_lfu() {
fastrand::seed(0);
let db: Arc<Database<String, i32>> = Arc::new(
Database::builder()
.max_memory(450) .eviction_policy(EvictionPolicy::Lfu)
.build()
.await
.unwrap(),
);
let handle = db.handle();
handle.insert("key_frequent".to_string(), 1).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
handle
.insert("key_infrequent".to_string(), 2)
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
for _ in 0..5 {
let _ = handle.get(&"key_frequent".to_string()).unwrap();
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
}
handle.insert("key_new".to_string(), 3).await.unwrap();
assert!(
handle.get(&"key_infrequent".to_string()).unwrap().is_none(),
"key_infrequent should have been evicted"
);
assert!(
handle.get(&"key_frequent".to_string()).unwrap().is_some(),
"key_frequent should still exist"
);
assert!(
handle.get(&"key_new".to_string()).unwrap().is_some(),
"key_new should exist"
);
}
#[tokio::test]
async fn test_eviction_on_memory_limit_random() {
fastrand::seed(0);
let db: Arc<Database<String, i32>> = Arc::new(
Database::builder()
.max_memory(450) .eviction_policy(EvictionPolicy::Random)
.build()
.await
.unwrap(),
);
let handle = db.handle();
handle.insert("key1".to_string(), 1).await.unwrap();
handle.insert("key2".to_string(), 2).await.unwrap();
assert!(handle.get(&"key1".to_string()).unwrap().is_some());
assert!(handle.get(&"key2".to_string()).unwrap().is_some());
handle.insert("key3".to_string(), 3).await.unwrap();
let key1_exists = handle.get(&"key1".to_string()).unwrap().is_some();
let key2_exists = handle.get(&"key2".to_string()).unwrap().is_some();
let key3_exists = handle.get(&"key3".to_string()).unwrap().is_some();
let keys_present = [key1_exists, key2_exists, key3_exists]
.iter()
.filter(|&&x| x)
.count();
assert_eq!(keys_present, 2, "Exactly one key should have been evicted");
assert!(
key3_exists,
"The most recently inserted key should always exist post-eviction"
);
}
#[tokio::test]
async fn test_eviction_on_memory_limit_arc() {
fastrand::seed(0);
let db: Arc<Database<String, i32>> = Arc::new(
Database::builder()
.max_memory(450)
.eviction_policy(EvictionPolicy::Arc)
.build()
.await
.unwrap(),
);
let handle = db.handle();
handle.insert("key1".to_string(), 1).await.unwrap();
handle.insert("key2".to_string(), 2).await.unwrap();
assert!(handle.get(&"key1".to_string()).unwrap().is_some());
handle.insert("key3".to_string(), 3).await.unwrap();
assert!(
handle.get(&"key1".to_string()).unwrap().is_some(),
"key1 should exist as it was promoted to T2"
);
assert!(
handle.get(&"key2".to_string()).unwrap().is_none(),
"key2 should have been evicted from T1"
);
assert!(
handle.get(&"key3".to_string()).unwrap().is_some(),
"key3 should exist as it was just inserted"
);
}
#[tokio::test]
async fn test_manual_eviction_flow() {
let db: Arc<Database<String, i32>> = Arc::new(
Database::builder()
.max_memory(450) .eviction_policy(EvictionPolicy::Manual)
.build()
.await
.unwrap(),
);
let handle = db.handle();
handle.insert("key1".to_string(), 1).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
handle.insert("key2".to_string(), 2).await.unwrap();
let result = handle.insert("key3".to_string(), 3).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err(), FluxError::MemoryLimitExceeded);
let mut victims: Vec<fluxmap::db::KeyMetadata<String>> = handle.scan_metadata().collect().await;
victims.sort_by_key(|v| v.last_accessed);
assert!(!victims.is_empty());
let victim_key = victims.first().unwrap().key.clone();
let evicted = handle.evict(&victim_key).await.unwrap();
assert!(evicted);
assert_eq!(victim_key, "key1");
handle.insert("key3".to_string(), 3).await.unwrap();
assert!(
handle.get(&"key1".to_string()).unwrap().is_none(),
"key1 should be evicted"
);
assert!(
handle.get(&"key2".to_string()).unwrap().is_some(),
"key2 should exist"
);
assert!(
handle.get(&"key3".to_string()).unwrap().is_some(),
"key3 should now exist"
);
let info = db.memory_info();
assert_eq!(info.max_bytes, Some(450));
assert!(info.current_bytes <= info.max_bytes.unwrap());
}
#[tokio::test]
async fn test_eviction_on_memory_limit_ttl() {
fastrand::seed(0);
let ttl_duration = std::time::Duration::from_secs(2);
let db: Arc<Database<String, i32>> = Arc::new(
Database::builder()
.max_memory(450)
.eviction_policy(EvictionPolicy::Ttl)
.eviction_ttl(ttl_duration)
.build()
.await
.unwrap(),
);
let handle = db.handle();
handle.insert("key_ephemeral".to_string(), 1).await.unwrap();
tokio::time::sleep(ttl_duration / 2).await;
handle.insert("key_permanent".to_string(), 2).await.unwrap();
tokio::time::sleep(ttl_duration / 2 + std::time::Duration::from_millis(500)).await;
handle.insert("key_trigger".to_string(), 3).await.unwrap();
assert!(
handle.get(&"key_ephemeral".to_string()).unwrap().is_none(),
"key_ephemeral should have been evicted due to TTL"
);
assert!(
handle.get(&"key_permanent".to_string()).unwrap().is_some(),
"key_permanent should still exist"
);
assert!(
handle.get(&"key_trigger".to_string()).unwrap().is_some(),
"key_trigger should exist"
);
}