use rust_supervisor::control::handle::SupervisorHandle;
use rust_supervisor::error::types::{TaskFailure, TaskFailureKind};
use rust_supervisor::id::types::{ChildId, ChildStartCount, Generation, SupervisorPath};
use rust_supervisor::role::adapter::supervisor::SupervisorRoleAdapter;
use rust_supervisor::role::context::supervisor::SupervisorContext;
use rust_supervisor::role::lifecycle::RoleLifecyclePhase;
use rust_supervisor::role::result::supervisor::{SupervisorResult, SupervisorRoleError};
use rust_supervisor::role::traits::supervisor::SupervisorRole;
use rust_supervisor::spec::supervisor::SupervisorSpec;
use rust_supervisor::task::context::TaskContext;
use rust_supervisor::task::factory::{TaskFactory, TaskResult};
use rust_supervisor::test_support::test_time::with_auto_clock_drive;
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 RecordingSupervisor {
events: Arc<Mutex<Vec<&'static str>>>,
}
impl SupervisorRole for RecordingSupervisor {
async fn build_tree(&mut self, ctx: &SupervisorContext) -> SupervisorResult<SupervisorSpec> {
let _ = ctx;
self.events.lock().expect("events lock").push("build_tree");
Ok(SupervisorSpec::root(Vec::new()))
}
async fn run(
&mut self,
ctx: &SupervisorContext,
handle: &SupervisorHandle,
) -> SupervisorResult<()> {
let _ = (ctx, handle);
self.events.lock().expect("events lock").push("run");
Ok(())
}
}
#[tokio::test(start_paused = true)]
async fn supervisor_role_adapter_calls_build_tree() {
let events = Arc::new(Mutex::new(Vec::new()));
let supervisor = RecordingSupervisor {
events: events.clone(),
};
let adapter = SupervisorRoleAdapter::new(supervisor);
let result = with_auto_clock_drive(TaskFactory::build(
&adapter,
task_context("nested-supervisor"),
))
.await;
assert_eq!(result, TaskResult::Succeeded);
assert_eq!(
*events.lock().expect("events lock"),
vec!["build_tree", "run"]
);
}
struct ShutdownAwareSupervisor {
seen_shutdown: Arc<Mutex<bool>>,
}
impl SupervisorRole for ShutdownAwareSupervisor {
async fn build_tree(&mut self, ctx: &SupervisorContext) -> SupervisorResult<SupervisorSpec> {
let _ = ctx;
Ok(SupervisorSpec::root(Vec::new()))
}
async fn run(
&mut self,
ctx: &SupervisorContext,
handle: &SupervisorHandle,
) -> SupervisorResult<()> {
let _ = handle;
ctx.wait_shutdown().await;
*self.seen_shutdown.lock().expect("shutdown lock") = ctx.is_shutdown_requested();
Ok(())
}
}
#[tokio::test(start_paused = true)]
async fn supervisor_context_observes_runtime_shutdown() {
let seen_shutdown = Arc::new(Mutex::new(false));
let adapter = SupervisorRoleAdapter::new(ShutdownAwareSupervisor {
seen_shutdown: seen_shutdown.clone(),
});
let ctx = task_context("shutdown-supervisor");
ctx.cancel();
let result = with_auto_clock_drive(TaskFactory::build(&adapter, ctx)).await;
assert_eq!(result, TaskResult::Cancelled);
assert!(*seen_shutdown.lock().expect("shutdown lock"));
}
struct FailingSupervisor;
impl SupervisorRole for FailingSupervisor {
async fn build_tree(&mut self, ctx: &SupervisorContext) -> SupervisorResult<SupervisorSpec> {
Err(SupervisorRoleError::new(
ctx.child_id().clone(),
RoleLifecyclePhase::BuildTree,
"nested tree could not be built",
))
}
}
#[tokio::test(start_paused = true)]
async fn supervisor_role_error_maps_to_task_failure() {
let adapter = SupervisorRoleAdapter::new(FailingSupervisor);
let result = TaskFactory::build(&adapter, task_context("failing-supervisor")).await;
match result {
TaskResult::Failed(TaskFailure {
kind,
category,
message,
}) => {
assert_eq!(kind, TaskFailureKind::Error);
assert_eq!(category, "supervisor_build_tree");
assert!(message.contains("nested tree could not be built"));
}
other => panic!("expected failed task result, got {other:?}"),
}
}
struct InvalidSpecSupervisor;
impl SupervisorRole for InvalidSpecSupervisor {
async fn build_tree(&mut self, ctx: &SupervisorContext) -> SupervisorResult<SupervisorSpec> {
let _ = ctx;
let mut spec = SupervisorSpec::root(Vec::new());
spec.control_channel_capacity = 0;
Ok(spec)
}
}
#[tokio::test(start_paused = true)]
async fn supervisor_start_error_maps_to_task_failure() {
let adapter = SupervisorRoleAdapter::new(InvalidSpecSupervisor);
let result = TaskFactory::build(&adapter, task_context("invalid-supervisor")).await;
match result {
TaskResult::Failed(TaskFailure {
kind,
category,
message,
}) => {
assert_eq!(kind, TaskFailureKind::Error);
assert_eq!(category, "supervisor_build_tree");
assert!(message.contains("control channel capacity"));
}
other => panic!("expected failed task result, got {other:?}"),
}
}