#[path = "../../common/mod.rs"]
mod common;
use common::test_containers::RedisContainer;
use oxcache::backend::CacheConnector;
use oxcache::backend::RedisBackend;
use oxcache::features::dist_lock::DistLockBuilder;
use std::sync::Arc;
use std::time::Duration;
async fn setup() -> Option<Arc<RedisBackend>> {
let container = RedisContainer::start().await.ok()?;
container.wait_ready().await.ok()?;
let backend = RedisBackend::new(&container.url()).await.ok()?;
backend.health_check().await.ok()?;
Some(Arc::new(backend))
}
macro_rules! ok_or_skip {
($expr:expr) => {
match $expr {
Ok(v) => v,
Err(_) => return, }
};
}
#[tokio::test]
async fn test_dist_lock_acquire_release() {
let Some(backend) = setup().await else { return };
let mut lock = DistLockBuilder::new(backend, "test:acquire-release".into())
.ttl(Duration::from_secs(5))
.watchdog_enabled(false)
.build();
let acquired = ok_or_skip!(lock.acquire().await);
assert!(acquired, "first acquire should succeed");
assert!(ok_or_skip!(lock.is_held().await));
ok_or_skip!(lock.release().await);
assert!(!ok_or_skip!(lock.is_held().await));
}
#[tokio::test]
async fn test_dist_lock_ttl_expiry() {
let Some(backend) = setup().await else { return };
let mut lock = DistLockBuilder::new(backend, "test:ttl-expiry".into())
.ttl(Duration::from_millis(200))
.watchdog_enabled(false)
.build();
ok_or_skip!(lock.acquire().await);
assert!(ok_or_skip!(lock.is_held().await));
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(!ok_or_skip!(lock.is_held().await));
}
#[tokio::test]
async fn test_dist_lock_reentrant() {
let Some(backend) = setup().await else { return };
let mut lock = DistLockBuilder::new(backend, "test:reentrant".into())
.ttl(Duration::from_secs(5))
.watchdog_enabled(false)
.build();
let first = ok_or_skip!(lock.acquire().await);
assert!(first, "first acquire should return true");
let second = ok_or_skip!(lock.acquire().await);
assert!(!second, "reentrant acquire should return false");
ok_or_skip!(lock.release().await);
assert!(ok_or_skip!(lock.is_held().await));
ok_or_skip!(lock.release().await);
assert!(!ok_or_skip!(lock.is_held().await));
}
#[tokio::test]
async fn test_dist_lock_watchdog_renew() {
let Some(backend) = setup().await else { return };
let mut lock = DistLockBuilder::new(backend, "test:watchdog".into())
.ttl(Duration::from_millis(300))
.watchdog_enabled(true)
.build();
ok_or_skip!(lock.acquire().await);
tokio::time::sleep(Duration::from_millis(500)).await;
assert!(
ok_or_skip!(lock.is_held().await),
"watchdog should have renewed the lock"
);
ok_or_skip!(lock.release().await);
}
#[tokio::test]
async fn test_dist_lock_contention() {
let Some(backend) = setup().await else { return };
let mut lock1 = DistLockBuilder::new(backend.clone(), "test:contention".into())
.ttl(Duration::from_secs(5))
.watchdog_enabled(false)
.build();
ok_or_skip!(lock1.acquire().await);
let mut lock2 = DistLockBuilder::new(backend, "test:contention".into())
.ttl(Duration::from_secs(5))
.watchdog_enabled(false)
.build();
let result = lock2.acquire().await;
assert!(result.is_err(), "second lock should fail to acquire");
ok_or_skip!(lock1.release().await);
let acquired = ok_or_skip!(lock2.acquire().await);
assert!(acquired);
ok_or_skip!(lock2.release().await);
}