#[cfg(test)]
mod tests {
use crate::cook::signal_handler::*;
use std::process::{Command, Stdio};
use std::time::Duration;
#[test]
fn test_setup_simple_interrupt_handler_doesnt_panic() {
let result = setup_simple_interrupt_handler();
assert!(result.is_ok());
}
#[cfg(unix)]
#[test]
fn test_signal_terminates_process() {
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
let mut child = Command::new("sleep")
.arg("60")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to spawn child process");
let child_pid = child.id() as i32;
std::thread::sleep(Duration::from_millis(100));
signal::kill(Pid::from_raw(child_pid), Signal::SIGINT).unwrap();
let exit_status = child.wait().expect("Failed to wait for child");
assert!(
!exit_status.success(),
"Process should not exit successfully after SIGINT"
);
}
#[cfg(unix)]
#[test]
fn test_process_group_termination() {
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
let mut parent = Command::new("sleep")
.arg("0.5")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to spawn parent process");
let parent_pid = parent.id() as i32;
std::thread::sleep(Duration::from_millis(50));
let _ = signal::kill(Pid::from_raw(parent_pid), Signal::SIGTERM);
let wait_result = parent.wait();
match wait_result {
Ok(status) => {
assert!(
status.code().is_none() || !status.success(),
"Process should be terminated by signal"
);
}
Err(e) => panic!("Failed to wait for process: {}", e),
}
}
}
#[cfg(unix)]
#[cfg(test)]
mod integration_tests {
use crate::cook::signal_handler::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
#[test]
fn test_multiple_signal_handlers_dont_conflict() {
let result1 = setup_simple_interrupt_handler();
assert!(result1.is_ok());
let result2 = setup_simple_interrupt_handler();
assert!(result2.is_ok());
}
#[test]
fn test_signal_handler_thread_spawns() {
let handler_started = Arc::new(AtomicBool::new(false));
let handler_clone = handler_started.clone();
thread::spawn(move || {
handler_clone.store(true, Ordering::SeqCst);
thread::sleep(Duration::from_millis(10));
});
thread::sleep(Duration::from_millis(50));
assert!(
handler_started.load(Ordering::SeqCst),
"Handler thread should start"
);
}
}
#[cfg(unix)]
#[cfg(test)]
mod subprocess_tests {
use crate::subprocess::runner::TokioProcessRunner;
use crate::subprocess::{ProcessCommand, ProcessRunner};
use std::collections::HashMap;
use std::time::Duration;
#[tokio::test]
async fn test_subprocess_with_process_group() {
let runner = TokioProcessRunner;
let command = ProcessCommand {
program: "sh".to_string(),
args: vec![
"-c".to_string(),
"echo 'parent'; sleep 0.1; echo 'done'".to_string(),
],
env: HashMap::new(),
working_dir: None,
timeout: Some(Duration::from_secs(1)),
stdin: None,
suppress_stderr: false,
};
let result = runner.run(command).await;
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.stdout.contains("parent"));
assert!(output.stdout.contains("done"));
}
#[tokio::test]
async fn test_subprocess_timeout_kills_process_group() {
let runner = TokioProcessRunner;
let command = ProcessCommand {
program: "sh".to_string(),
args: vec!["-c".to_string(), "sleep 2 & sleep 2 & wait".to_string()],
env: HashMap::new(),
working_dir: None,
timeout: Some(Duration::from_millis(100)),
stdin: None,
suppress_stderr: false,
};
let result = runner.run(command).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("Timeout") || e.to_string().contains("timed out"));
}
}
}
#[cfg(test)]
mod mock_tests {
use crate::cook::execution::command::CommandType;
use crate::cook::execution::process::{ProcessManager, UnifiedProcess};
use std::process::Stdio;
use tokio::process::Command;
#[tokio::test]
async fn test_unified_process_kill_terminates_immediately() {
let mut command = Command::new("sleep");
command.arg("60");
command.stdout(Stdio::null());
command.stderr(Stdio::null());
let child = command.spawn().unwrap();
let mut process = UnifiedProcess::new(child, CommandType::Shell);
let pid = process.id();
assert!(pid.0 > 0);
let kill_result = process.kill().await;
assert!(kill_result.is_ok(), "Kill should succeed");
}
#[tokio::test]
async fn test_process_manager_cleanup_after_kill() {
let manager = ProcessManager::new();
let process_id = crate::cook::execution::process::ProcessId(12345);
let result = manager.cleanup_process(process_id).await;
assert!(result.is_ok());
}
}