use bytes::Bytes;
use hitbox_backend::Backend;
use hitbox_backend::composition::policy::{
CompositionReadPolicy, ParallelReadPolicy, RaceReadPolicy, SequentialReadPolicy,
};
use hitbox_core::{BoxContext, CacheContext, CacheKey, CacheValue};
use crate::common::{ErrorBackend, TestBackend, TestOffloadManager};
fn default_ctx() -> BoxContext {
CacheContext::default().boxed()
}
#[tokio::test]
async fn test_sequential_l1_hit() {
let policy = SequentialReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l1"), None, None);
l1.write(&key, value.clone()).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l1"));
}
#[tokio::test]
async fn test_sequential_l2_hit() {
let policy = SequentialReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l2"), None, None);
l2.write(&key, value.clone()).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_sequential_both_miss() {
let policy = SequentialReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_none());
}
#[tokio::test]
async fn test_sequential_l1_error_l2_hit() {
let policy = SequentialReadPolicy::new();
let l1 = ErrorBackend;
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l2"), None, None);
l2.write(&key, value.clone()).await.unwrap();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_race_l1_hit() {
let policy = RaceReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l1"), None, None);
l1.write(&key, value.clone()).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l1"));
}
#[tokio::test]
async fn test_race_l2_hit() {
let policy = RaceReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l2"), None, None);
l2.write(&key, value.clone()).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_race_both_miss() {
let policy = RaceReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_none());
}
#[tokio::test]
async fn test_race_l1_error_l2_hit() {
let policy = RaceReadPolicy::new();
let l1 = ErrorBackend;
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l2"), None, None);
l2.write(&key, value.clone()).await.unwrap();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_parallel_both_hit_prefer_l1() {
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
l1.write(&key, CacheValue::new(Bytes::from("from_l1"), None, None))
.await
.unwrap();
l2.write(&key, CacheValue::new(Bytes::from("from_l2"), None, None))
.await
.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l1"));
}
#[tokio::test]
async fn test_parallel_l1_miss_l2_hit() {
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l2"), None, None);
l2.write(&key, value.clone()).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_parallel_both_miss() {
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_none());
}
#[tokio::test]
async fn test_parallel_l1_error_l2_hit() {
let policy = ParallelReadPolicy::new();
let l1 = ErrorBackend;
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let value = CacheValue::new(Bytes::from("from_l2"), None, None);
l2.write(&key, value.clone()).await.unwrap();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_parallel_both_hit_l2_fresher_ttl() {
use chrono::Utc;
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let now = Utc::now();
let l1_value = CacheValue::new(
Bytes::from("from_l1"),
Some(now + chrono::Duration::seconds(10)),
None,
);
let l2_value = CacheValue::new(
Bytes::from("from_l2"),
Some(now + chrono::Duration::seconds(60)),
None,
);
l1.write(&key, l1_value).await.unwrap();
l2.write(&key, l2_value).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_parallel_both_hit_l1_fresher_ttl() {
use chrono::Utc;
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let now = Utc::now();
let l1_value = CacheValue::new(
Bytes::from("from_l1"),
Some(now + chrono::Duration::seconds(60)),
None,
);
let l2_value = CacheValue::new(
Bytes::from("from_l2"),
Some(now + chrono::Duration::seconds(10)),
None,
);
l1.write(&key, l1_value).await.unwrap();
l2.write(&key, l2_value).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l1"));
}
#[tokio::test]
async fn test_parallel_both_hit_equal_ttl() {
use chrono::Utc;
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let now = Utc::now();
let expiry = now + chrono::Duration::seconds(30);
let l1_value = CacheValue::new(Bytes::from("from_l1"), Some(expiry), None);
let l2_value = CacheValue::new(Bytes::from("from_l2"), Some(expiry), None);
l1.write(&key, l1_value).await.unwrap();
l2.write(&key, l2_value).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l1"));
}
#[tokio::test]
async fn test_parallel_both_hit_l2_no_expiry() {
use chrono::Utc;
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let now = Utc::now();
let l1_value = CacheValue::new(
Bytes::from("from_l1"),
Some(now + chrono::Duration::seconds(60)),
None,
);
let l2_value = CacheValue::new(Bytes::from("from_l2"), None, None);
l1.write(&key, l1_value).await.unwrap();
l2.write(&key, l2_value).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l2"));
}
#[tokio::test]
async fn test_parallel_both_hit_l1_no_expiry() {
use chrono::Utc;
let policy = ParallelReadPolicy::new();
let l1 = TestBackend::new();
let l2 = TestBackend::new();
let offload = TestOffloadManager;
let key = CacheKey::from_str("test", "key1");
let now = Utc::now();
let l1_value = CacheValue::new(Bytes::from("from_l1"), None, None);
let l2_value = CacheValue::new(
Bytes::from("from_l2"),
Some(now + chrono::Duration::seconds(60)),
None,
);
l1.write(&key, l1_value).await.unwrap();
l2.write(&key, l2_value).await.unwrap();
let l1_clone = l1.clone();
let l2_clone = l2.clone();
let read_l1 = |k: CacheKey| async move { (l1_clone.read(&k).await, default_ctx()) };
let read_l2 = |k: CacheKey| async move { (l2_clone.read(&k).await, default_ctx()) };
let read_result = policy
.execute_with(key, read_l1, read_l2, &offload)
.await
.unwrap();
assert!(read_result.value.is_some());
assert_eq!(read_result.value.unwrap().data(), &Bytes::from("from_l1"));
}