Files
dirigent/crates/dirigent_process/tests/lifecycle.rs
T
2026-05-08 01:59:04 +02:00

150 lines
4.3 KiB
Rust

use dirigent_process::{create_manager, graceful_shutdown_sync};
use std::process::Command;
use std::time::Duration;
/// Build a long-running command that does not require a TTY on any platform.
///
/// On Windows, `timeout /t N /nobreak` fails when stdin is a pipe (no console),
/// so we use `ping -n N 127.0.0.1` which sleeps approximately N-1 seconds with
/// no TTY requirement.
///
/// On Unix, `sleep N` is the idiomatic choice.
#[cfg(windows)]
fn long_sleep_cmd(seconds: u32) -> Command {
let mut cmd = Command::new("ping");
cmd.args(["-n", &seconds.to_string(), "127.0.0.1"]);
cmd
}
#[cfg(unix)]
fn long_sleep_cmd(seconds: u32) -> Command {
let mut cmd = Command::new("sleep");
cmd.arg(seconds.to_string());
cmd
}
#[cfg(all(windows, feature = "tokio"))]
fn long_sleep_async_cmd(seconds: u32) -> tokio::process::Command {
let mut cmd = tokio::process::Command::new("ping");
cmd.args(["-n", &seconds.to_string(), "127.0.0.1"]);
cmd
}
#[cfg(all(unix, feature = "tokio"))]
fn long_sleep_async_cmd(seconds: u32) -> tokio::process::Command {
let mut cmd = tokio::process::Command::new("sleep");
cmd.arg(seconds.to_string());
cmd
}
#[test]
fn test_manager_init() {
let mgr = create_manager();
mgr.init().expect("init should succeed");
// Double init should also succeed (idempotent)
mgr.init().expect("double init should succeed");
}
#[test]
fn test_create_lifecycle() {
let mgr = create_manager();
mgr.init().expect("init failed");
let _lifecycle = mgr.create_lifecycle();
}
#[test]
fn test_configure_and_spawn() {
let mgr = create_manager();
mgr.init().expect("init failed");
let lifecycle = mgr.create_lifecycle();
let mut cmd = long_sleep_cmd(30);
lifecycle.configure_command(&mut cmd);
let mut child = cmd.spawn().expect("spawn failed");
let pid = child.id();
assert!(pid > 0);
// Register should succeed
lifecycle.register_child(pid).expect("register failed");
// Process should still be running
assert!(child.try_wait().expect("try_wait failed").is_none());
// Clean up
let _ = child.kill();
let _ = child.wait();
}
#[test]
fn test_graceful_shutdown_sync() {
let mgr = create_manager();
mgr.init().expect("init failed");
let lifecycle = mgr.create_lifecycle();
let mut cmd = long_sleep_cmd(60);
lifecycle.configure_command(&mut cmd);
let mut child = cmd.spawn().expect("spawn failed");
let pid = child.id();
lifecycle.register_child(pid).expect("register failed");
// Graceful shutdown with 3s timeout — process won't exit voluntarily,
// so it should be force-killed after timeout
let exited_gracefully = graceful_shutdown_sync(
lifecycle.as_ref(),
&mut child,
Duration::from_secs(3),
);
// Process should be dead now
assert!(child.try_wait().expect("try_wait failed").is_some());
// It was force-killed (ping/sleep don't handle SIGTERM/CTRL_BREAK)
assert!(!exited_gracefully);
}
#[test]
fn test_send_kill_signal() {
let mgr = create_manager();
mgr.init().expect("init failed");
let lifecycle = mgr.create_lifecycle();
let mut cmd = long_sleep_cmd(60);
lifecycle.configure_command(&mut cmd);
let mut child = cmd.spawn().expect("spawn failed");
let pid = child.id();
lifecycle.register_child(pid).expect("register failed");
// Direct kill signal
lifecycle.send_kill_signal(pid).expect("kill failed");
// Wait for process to die
let status = child.wait().expect("wait failed");
assert!(!status.success());
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_async_graceful_shutdown() {
use dirigent_process::graceful_shutdown_async;
let mgr = create_manager();
mgr.init().expect("init failed");
let lifecycle = mgr.create_lifecycle();
let mut cmd = long_sleep_async_cmd(60);
lifecycle.configure_async_command(&mut cmd);
let mut child = cmd.spawn().expect("spawn failed");
let pid = child.id().expect("no pid");
lifecycle.register_child(pid).expect("register failed");
let exited_gracefully = graceful_shutdown_async(
lifecycle.as_ref(),
&mut child,
Duration::from_secs(3),
)
.await;
assert!(child.try_wait().expect("try_wait failed").is_some());
assert!(!exited_gracefully);
}