use rust_supervisor::error::types::{TaskFailure, TaskFailureKind};
use rust_supervisor::id::types::{ChildId, ChildStartCount, Generation, SupervisorPath};
use rust_supervisor::role::adapter::service::ServiceRoleAdapter;
use rust_supervisor::role::context::service::ServiceContext;
use rust_supervisor::role::lifecycle::RoleLifecyclePhase;
use rust_supervisor::role::result::service::{ServiceError, ServiceResult};
use rust_supervisor::role::traits::service::ServiceRole;
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 RecordingService {
events: Arc<Mutex<Vec<&'static str>>>,
}
impl ServiceRole for RecordingService {
async fn init(&mut self, ctx: &ServiceContext) -> ServiceResult<()> {
let _ = ctx;
self.events.lock().expect("events lock").push("init");
Ok(())
}
async fn run(&mut self, ctx: &ServiceContext) -> ServiceResult<()> {
let _ = ctx;
self.events.lock().expect("events lock").push("run");
Ok(())
}
async fn shutdown(&mut self, ctx: &ServiceContext) -> ServiceResult<()> {
let _ = ctx;
self.events.lock().expect("events lock").push("shutdown");
Ok(())
}
}
#[tokio::test]
async fn service_role_adapter_runs_lifecycle_in_order() {
let events = Arc::new(Mutex::new(Vec::new()));
let service = RecordingService {
events: events.clone(),
};
let adapter = ServiceRoleAdapter::new(service);
let result = TaskFactory::build(&adapter, task_context("service")).await;
assert_eq!(result, TaskResult::Succeeded);
assert_eq!(
*events.lock().expect("events lock"),
vec!["init", "run", "shutdown"]
);
}
struct ShutdownAwareService {
seen_shutdown: Arc<Mutex<bool>>,
}
impl ServiceRole for ShutdownAwareService {
async fn run(&mut self, ctx: &ServiceContext) -> ServiceResult<()> {
ctx.wait_shutdown().await;
*self.seen_shutdown.lock().expect("shutdown lock") = ctx.is_shutdown_requested();
Ok(())
}
}
#[tokio::test]
async fn service_context_observes_runtime_shutdown() {
let seen_shutdown = Arc::new(Mutex::new(false));
let adapter = ServiceRoleAdapter::new(ShutdownAwareService {
seen_shutdown: seen_shutdown.clone(),
});
let ctx = task_context("shutdown-service");
ctx.cancel();
let result = TaskFactory::build(&adapter, ctx).await;
assert_eq!(result, TaskResult::Cancelled);
assert!(*seen_shutdown.lock().expect("shutdown lock"));
}
struct FailingService;
impl ServiceRole for FailingService {
async fn run(&mut self, ctx: &ServiceContext) -> ServiceResult<()> {
Err(ServiceError::new(
ctx.child_id().clone(),
RoleLifecyclePhase::Run,
"service body failed",
))
}
}
#[tokio::test]
async fn service_error_maps_to_task_failure() {
let adapter = ServiceRoleAdapter::new(FailingService);
let result = TaskFactory::build(&adapter, task_context("failing-service")).await;
match result {
TaskResult::Failed(TaskFailure {
kind,
category,
message,
}) => {
assert_eq!(kind, TaskFailureKind::Error);
assert_eq!(category, "service_run");
assert!(message.contains("service body failed"));
}
other => panic!("expected failed task result, got {other:?}"),
}
}