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); }