mod common;
use foxtive_supervisor::task_pool::{TaskPool, TaskPoolBuilder, LoadBalancingStrategy};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
#[tokio::test]
async fn test_task_pool_creation() {
struct WorkerTask {
id: &'static str,
#[allow(dead_code)]
worker_index: usize,
execution_count: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for WorkerTask {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
self.execution_count.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(())
}
}
let execution_count = Arc::new(AtomicUsize::new(0));
let supervisor = TaskPoolBuilder::new("worker-pool")
.with_size(3)
.with_strategy(LoadBalancingStrategy::RoundRobin)
.build_and_create(|index| {
let ids = ["worker-0", "worker-1", "worker-2"];
WorkerTask {
id: ids[index],
worker_index: index,
execution_count: execution_count.clone(),
}
});
let runtime = supervisor.start().await.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
runtime.shutdown().await;
let count = execution_count.load(Ordering::SeqCst);
assert_eq!(count, 3, "All 3 workers should have executed");
}
#[tokio::test]
async fn test_pool_with_different_strategies() {
#[allow(dead_code)]
struct SimpleTask {
id: &'static str,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for SimpleTask {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
tokio::time::sleep(Duration::from_millis(50)).await;
Ok(())
}
}
let pool_rr = TaskPool::new("rr-pool", 3, LoadBalancingStrategy::RoundRobin);
let info_rr = pool_rr.info();
assert_eq!(info_rr.pool_size, 3);
let pool_random = TaskPool::new("random-pool", 5, LoadBalancingStrategy::Random);
let info_random = pool_random.info();
assert_eq!(info_random.pool_size, 5);
let pool_ll = TaskPool::new("ll-pool", 2, LoadBalancingStrategy::LeastLoaded);
let info_ll = pool_ll.info();
assert_eq!(info_ll.pool_size, 2);
}
#[tokio::test]
async fn test_pool_load_distribution() {
struct CountingTask {
id: &'static str,
run_count: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for CountingTask {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
self.run_count.fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
let total_runs = Arc::new(AtomicUsize::new(0));
let pool_size = 5;
let supervisor = TaskPoolBuilder::new("counting-pool")
.with_size(pool_size)
.build_and_create(|index| {
let ids = ["counter-0", "counter-1", "counter-2", "counter-3", "counter-4"];
CountingTask {
id: ids[index],
run_count: total_runs.clone(),
}
});
let runtime = supervisor.start().await.unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
runtime.shutdown().await;
let runs = total_runs.load(Ordering::SeqCst);
assert_eq!(runs, pool_size, "Each worker in the pool should have run once");
}
#[tokio::test]
async fn test_pool_integration_with_groups() {
use foxtive_supervisor::Supervisor;
struct GroupedWorker {
id: &'static str,
group: &'static str,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for GroupedWorker {
fn id(&self) -> &'static str { self.id }
fn group_id(&self) -> Option<&'static str> {
Some(self.group)
}
async fn run(&self) -> anyhow::Result<()> {
tokio::time::sleep(Duration::from_millis(50)).await;
Ok(())
}
}
let mut supervisor = Supervisor::new();
supervisor = supervisor.add(GroupedWorker {
id: "pool-worker-0",
group: "worker-pool",
});
supervisor = supervisor.add(GroupedWorker {
id: "pool-worker-1",
group: "worker-pool",
});
supervisor = supervisor.add(GroupedWorker {
id: "pool-worker-2",
group: "worker-pool",
});
supervisor = supervisor.add(GroupedWorker {
id: "pool-worker-3",
group: "worker-pool",
});
let runtime = supervisor.start().await.unwrap();
let group_tasks = runtime.list_group_tasks("worker-pool");
assert_eq!(group_tasks.len(), 4, "All pool workers should be in the same group");
runtime.shutdown().await;
}
#[tokio::test]
async fn test_large_pool_stress() {
struct StressTask {
id: &'static str,
execution_count: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for StressTask {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
self.execution_count.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(5)).await;
Ok(())
}
}
let execution_count = Arc::new(AtomicUsize::new(0));
let pool_size = 20;
let mut supervisor = foxtive_supervisor::Supervisor::new();
for i in 0..pool_size {
let id: &'static str = Box::leak(format!("stress-worker-{}", i).into_boxed_str());
supervisor = supervisor.add(StressTask {
id,
execution_count: execution_count.clone(),
});
}
let runtime = supervisor.start().await.unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
runtime.shutdown().await;
let count = execution_count.load(Ordering::SeqCst);
assert_eq!(count, pool_size, "All {} workers should have executed", pool_size);
}
#[tokio::test]
async fn test_pool_concurrent_start_stop() {
struct ConcurrentTask {
id: &'static str,
started: Arc<AtomicUsize>,
stopped: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for ConcurrentTask {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
self.started.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_secs(60)).await;
Ok(())
}
async fn on_shutdown(&self) {
self.stopped.fetch_add(1, Ordering::SeqCst);
}
}
let started = Arc::new(AtomicUsize::new(0));
let stopped = Arc::new(AtomicUsize::new(0));
let mut supervisor = foxtive_supervisor::Supervisor::new();
for i in 0..10 {
let ids = ["conc-0", "conc-1", "conc-2", "conc-3", "conc-4",
"conc-5", "conc-6", "conc-7", "conc-8", "conc-9"];
supervisor = supervisor.add(ConcurrentTask {
id: ids[i],
started: started.clone(),
stopped: stopped.clone(),
});
}
let runtime = supervisor.start().await.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
assert_eq!(started.load(Ordering::SeqCst), 10);
runtime.shutdown().await;
assert_eq!(stopped.load(Ordering::SeqCst), 10, "All tasks should shutdown cleanly");
}
#[tokio::test]
async fn test_pool_with_failing_workers() {
struct FailingWorker {
id: &'static str,
worker_index: usize,
failure_count: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for FailingWorker {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
if self.worker_index % 2 == 0 {
self.failure_count.fetch_add(1, Ordering::SeqCst);
Err(anyhow::anyhow!("Worker {} intentionally failing", self.worker_index))
} else {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(())
}
}
fn restart_policy(&self) -> foxtive_supervisor::enums::RestartPolicy {
if self.worker_index % 2 == 0 {
foxtive_supervisor::enums::RestartPolicy::MaxAttempts(2)
} else {
foxtive_supervisor::enums::RestartPolicy::Always
}
}
}
let failure_count = Arc::new(AtomicUsize::new(0));
let supervisor = TaskPoolBuilder::new("mixed-pool")
.with_size(4)
.build_and_create(|index| {
let ids = ["fail-0", "ok-1", "fail-2", "ok-3"];
FailingWorker {
id: ids[index],
worker_index: index,
failure_count: failure_count.clone(),
}
});
let runtime = supervisor.start().await.unwrap();
tokio::time::sleep(Duration::from_millis(300)).await;
runtime.shutdown().await;
assert!(failure_count.load(Ordering::SeqCst) > 0);
}
#[tokio::test]
async fn test_pool_dynamic_scaling_simulation() {
use std::sync::Mutex;
struct ScalableTask {
id: &'static str,
active_tasks: Arc<Mutex<Vec<String>>>,
}
#[async_trait::async_trait]
impl foxtive_supervisor::contracts::SupervisedTask for ScalableTask {
fn id(&self) -> &'static str { self.id }
async fn run(&self) -> anyhow::Result<()> {
{
let mut active = self.active_tasks.lock().unwrap();
active.push(self.id.to_string());
}
tokio::time::sleep(Duration::from_millis(50)).await;
Ok(())
}
}
let active_tasks = Arc::new(Mutex::new(Vec::new()));
let mut supervisor = foxtive_supervisor::Supervisor::new();
let task_ids = ["scale-task-0", "scale-task-1", "scale-task-2"];
for id in &task_ids {
supervisor = supervisor.add(ScalableTask {
id: id,
active_tasks: active_tasks.clone(),
});
}
let runtime = supervisor.start().await.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
{
let active = active_tasks.lock().unwrap();
assert_eq!(active.len(), 3, "Initial pool should have 3 tasks");
}
runtime.shutdown().await;
}