use rust_supervisor::error::types::{TaskFailure, TaskFailureKind};
use rust_supervisor::id::types::{ChildId, ChildStartCount, Generation, SupervisorPath};
use rust_supervisor::role::adapter::worker::WorkerRoleAdapter;
use rust_supervisor::role::context::worker::WorkerContext;
use rust_supervisor::role::lifecycle::RoleLifecyclePhase;
use rust_supervisor::role::result::worker::{WorkerError, WorkerResult};
use rust_supervisor::role::traits::worker::WorkerRole;
use rust_supervisor::task::context::TaskContext;
use rust_supervisor::task::factory::{TaskFactory, TaskResult};
use std::sync::{Arc, Mutex};
fn task_context(id: &str) -> TaskContext {
let (ctx, _heartbeat) = TaskContext::new(
ChildId::new(id),
SupervisorPath::root().join(id),
Generation::initial(),
ChildStartCount::first(),
);
ctx
}
struct RecordingWorker {
events: Arc<Mutex<Vec<&'static str>>>,
}
impl WorkerRole for RecordingWorker {
async fn init(&mut self, ctx: &WorkerContext) -> WorkerResult<()> {
let _ = ctx;
self.events.lock().expect("events lock").push("init");
Ok(())
}
async fn work(&mut self, ctx: &WorkerContext) -> WorkerResult<()> {
let _ = ctx;
self.events.lock().expect("events lock").push("work");
Ok(())
}
async fn complete(&mut self, ctx: &WorkerContext) -> WorkerResult<()> {
let _ = ctx;
self.events.lock().expect("events lock").push("complete");
Ok(())
}
}
#[tokio::test]
async fn worker_role_adapter_runs_lifecycle_in_order() {
let events = Arc::new(Mutex::new(Vec::new()));
let worker = RecordingWorker {
events: events.clone(),
};
let adapter = WorkerRoleAdapter::new(worker);
let result = TaskFactory::build(&adapter, task_context("worker")).await;
assert_eq!(result, TaskResult::Succeeded);
assert_eq!(
*events.lock().expect("events lock"),
vec!["init", "work", "complete"]
);
}
struct CancellationAwareWorker {
seen_cancelled: Arc<Mutex<bool>>,
}
impl WorkerRole for CancellationAwareWorker {
async fn work(&mut self, ctx: &WorkerContext) -> WorkerResult<()> {
ctx.wait_cancelled().await;
*self.seen_cancelled.lock().expect("cancel lock") = ctx.is_cancelled();
Ok(())
}
}
#[tokio::test]
async fn worker_context_observes_runtime_cancellation() {
let seen_cancelled = Arc::new(Mutex::new(false));
let adapter = WorkerRoleAdapter::new(CancellationAwareWorker {
seen_cancelled: seen_cancelled.clone(),
});
let ctx = task_context("cancelled-worker");
ctx.cancel();
let result = TaskFactory::build(&adapter, ctx).await;
assert_eq!(result, TaskResult::Cancelled);
assert!(*seen_cancelled.lock().expect("cancel lock"));
}
struct FailingWorker;
impl WorkerRole for FailingWorker {
async fn work(&mut self, ctx: &WorkerContext) -> WorkerResult<()> {
Err(WorkerError::new(
ctx.child_id().clone(),
RoleLifecyclePhase::Work,
"worker body failed",
))
}
}
#[tokio::test]
async fn worker_error_maps_to_task_failure() {
let adapter = WorkerRoleAdapter::new(FailingWorker);
let result = TaskFactory::build(&adapter, task_context("failing-worker")).await;
match result {
TaskResult::Failed(TaskFailure {
kind,
category,
message,
}) => {
assert_eq!(kind, TaskFailureKind::Error);
assert_eq!(category, "worker_work");
assert!(message.contains("worker body failed"));
}
other => panic!("expected failed task result, got {other:?}"),
}
}