#[cfg(feature = "distributed")]
mod tests {
use foxtive_supervisor::distributed::{
CoordinationBackend, CoordinationConfig, RedisCoordination,
};
use std::sync::Arc;
use std::time::Duration;
fn test_config(instance_id: &str) -> CoordinationConfig {
CoordinationConfig::default()
.with_instance_id(instance_id)
.with_redis_url("redis://localhost:6379")
.with_leader_lease(10)
.with_heartbeat_interval(2)
}
async fn create_backend(instance_id: &str) -> foxtive_supervisor::SupervisorResult<RedisCoordination> {
let config = test_config(instance_id);
RedisCoordination::new(config).await
}
#[tokio::test]
#[ignore] async fn test_distributed_lock_acquire_and_release() {
let backend = create_backend("test-instance-1").await.unwrap();
let acquired = backend.try_acquire_lock("test-lock", 10).await.unwrap();
assert!(acquired, "Should acquire lock on first try");
let is_locked = backend.is_locked("test-lock").await.unwrap();
assert!(is_locked, "Lock should be held");
backend.release_lock("test-lock").await.unwrap();
let is_locked = backend.is_locked("test-lock").await.unwrap();
assert!(!is_locked, "Lock should be released");
}
#[tokio::test]
#[ignore] async fn test_distributed_lock_prevents_duplicate() {
let backend1 = create_backend("instance-1").await.unwrap();
let backend2 = create_backend("instance-2").await.unwrap();
let acquired1 = backend1.try_acquire_lock("shared-lock", 10).await.unwrap();
assert!(acquired1, "First instance should acquire lock");
let acquired2 = backend2.try_acquire_lock("shared-lock", 10).await.unwrap();
assert!(!acquired2, "Second instance should not acquire lock");
backend1.release_lock("shared-lock").await.unwrap();
}
#[tokio::test]
#[ignore] async fn test_leader_election_single_instance() {
let backend = create_backend("leader-candidate").await.unwrap();
let became_leader = backend
.try_become_leader("leader-candidate", 10)
.await
.unwrap();
assert!(became_leader, "Should become leader when no competition");
let is_leader = backend.is_leader("leader-candidate").await.unwrap();
assert!(is_leader, "Should be recognized as leader");
let current_leader = backend.get_current_leader().await.unwrap();
assert_eq!(current_leader, Some("leader-candidate".to_string()));
}
#[tokio::test]
#[ignore] async fn test_leader_election_with_competition() {
let backend1 = create_backend("candidate-1").await.unwrap();
let backend2 = create_backend("candidate-2").await.unwrap();
let leader1 = backend1.try_become_leader("candidate-1", 10).await.unwrap();
assert!(leader1, "First candidate should become leader");
let leader2 = backend2.try_become_leader("candidate-2", 10).await.unwrap();
assert!(!leader2, "Second candidate should not become leader");
let current_leader = backend1.get_current_leader().await.unwrap();
assert_eq!(current_leader, Some("candidate-1".to_string()));
}
#[tokio::test]
#[ignore] async fn test_heartbeat_mechanism() {
let backend = create_backend("heartbeat-tester").await.unwrap();
backend.heartbeat("heartbeat-tester", 10).await.unwrap();
let is_alive = backend.is_instance_alive("heartbeat-tester").await.unwrap();
assert!(is_alive, "Instance should be alive after heartbeat");
let is_alive = backend.is_instance_alive("non-existent").await.unwrap();
assert!(!is_alive, "Non-existent instance should not be alive");
}
#[tokio::test]
#[ignore] async fn test_heartbeat_expiration() {
let backend = create_backend("expiring-instance").await.unwrap();
backend.heartbeat("expiring-instance", 1).await.unwrap();
assert!(
backend
.is_instance_alive("expiring-instance")
.await
.unwrap()
);
tokio::time::sleep(Duration::from_secs(2)).await;
let is_alive = backend
.is_instance_alive("expiring-instance")
.await
.unwrap();
assert!(!is_alive, "Instance should be dead after TTL expires");
}
#[tokio::test]
#[ignore] async fn test_leader_lease_expiration() {
let backend = create_backend("lease-tester").await.unwrap();
backend.try_become_leader("lease-tester", 2).await.unwrap();
assert!(backend.is_leader("lease-tester").await.unwrap());
tokio::time::sleep(Duration::from_secs(3)).await;
let is_leader = backend.is_leader("lease-tester").await.unwrap();
assert!(!is_leader, "Should lose leadership after lease expires");
let backend2 = create_backend("new-leader").await.unwrap();
let became_leader = backend2.try_become_leader("new-leader", 10).await.unwrap();
assert!(
became_leader,
"New instance should become leader after old lease expires"
);
}
#[tokio::test]
#[ignore] async fn test_coordination_manager() {
use foxtive_supervisor::distributed::CoordinationManager;
let config = test_config("manager-test");
let backend: Arc<dyn foxtive_supervisor::distributed::CoordinationBackend> =
Arc::new(create_backend("manager-test").await.unwrap());
let manager = CoordinationManager::new(backend.clone(), config);
manager.start().await.unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
let _is_leader = manager.is_leader();
tokio::time::sleep(Duration::from_secs(3)).await;
assert!(backend.is_instance_alive("manager-test").await.unwrap());
}
#[tokio::test]
#[ignore] async fn test_multiple_instances_coordination() {
let num_instances = 5;
let mut backends = Vec::new();
for i in 0..num_instances {
let backend = create_backend(&format!("instance-{}", i)).await.unwrap();
backends.push(backend);
}
let mut leaders = 0;
for backend in &backends {
let instance_id = format!("instance-{}", leaders);
if backend.try_become_leader(&instance_id, 10).await.unwrap() {
leaders += 1;
}
}
assert_eq!(leaders, 1, "Exactly one instance should become leader");
for (i, backend) in backends.iter().enumerate().take(num_instances) {
let instance_id = format!("instance-{}", i);
backend.heartbeat(&instance_id, 10).await.unwrap();
assert!(backend.is_instance_alive(&instance_id).await.unwrap());
}
}
}