150 lines
4.3 KiB
Rust
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);
|
|
}
|