use crate::utils::shell::shell_pair;
#[test]
fn shell_pair_matches_platform() {
let (program, flag) = shell_pair();
if cfg!(target_os = "windows") {
assert_eq!((program, flag), ("cmd", "/C"));
} else {
assert_eq!((program, flag), ("sh", "-c"));
}
}
#[test]
fn shell_pair_runs_a_command_on_this_platform() {
let (program, flag) = shell_pair();
let out = std::process::Command::new(program)
.arg(flag)
.arg("echo shell_pair_probe_ok")
.output()
.expect("spawn platform shell");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success(),
"shell {program} {flag} failed: {stdout}{}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
stdout.contains("shell_pair_probe_ok"),
"expected probe token in stdout, got: {stdout}"
);
}
#[tokio::test]
async fn push_shell_command_passes_quoted_command_verbatim() {
use crate::utils::shell::PushShellCommand;
let (shell, flag) = shell_pair();
let mut cmd = tokio::process::Command::new(shell);
cmd.push_shell_command(flag, "echo \"push_shell_verbatim_ok\"");
let out = cmd.output().await.expect("spawn platform shell");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("push_shell_verbatim_ok"),
"expected probe token in stdout, got: {stdout:?}"
);
assert!(
!stdout.contains("\\\""),
"quoted command was mangled through MSVC arg escaping: {stdout:?}"
);
}
#[cfg(windows)]
#[test]
fn kill_process_tree_terminates_the_tree() {
use crate::utils::shell::PushShellCommand;
let (shell, flag) = shell_pair();
let mut cmd = std::process::Command::new(shell);
cmd.push_shell_command(flag, "ping -n 30 127.0.0.1 > NUL");
let child = cmd.spawn().expect("spawn ping tree");
let pid = child.id();
std::thread::sleep(std::time::Duration::from_millis(400));
crate::utils::shell::kill_process_tree(pid);
std::thread::sleep(std::time::Duration::from_millis(400));
let out = std::process::Command::new("tasklist")
.args(["/FI", &format!("PID eq {pid}")])
.output()
.expect("run tasklist");
let listing = String::from_utf8_lossy(&out.stdout);
assert!(
!listing.contains(&pid.to_string()),
"pid {pid} (or its tree) survived kill_process_tree: {listing}"
);
}
#[cfg(not(windows))]
#[test]
fn kill_process_tree_sweeps_the_children_on_unix() {
fn children_of(pid: u32) -> String {
let out = std::process::Command::new("pgrep")
.args(["-P", &pid.to_string()])
.output()
.expect("run pgrep");
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
use crate::utils::shell::PushShellCommand;
let (shell, flag) = shell_pair();
let mut cmd = std::process::Command::new(shell);
cmd.push_shell_command(flag, "sleep 37 & wait");
let mut child = cmd.spawn().expect("spawn sleep tree");
let pid = child.id();
std::thread::sleep(std::time::Duration::from_millis(500));
let before = children_of(pid);
assert!(
!before.is_empty(),
"precondition: the shell should own a child to sweep, found none"
);
crate::utils::shell::kill_process_tree(pid);
std::thread::sleep(std::time::Duration::from_millis(500));
let after = children_of(pid);
if let Err(e) = child.kill() {
eprintln!("could not kill the test shell pid {pid}: {e}");
}
if let Err(e) = child.wait() {
eprintln!("could not reap the test shell pid {pid}: {e}");
}
assert!(
after.is_empty(),
"children of pid {pid} survived kill_process_tree: before={before:?} after={after:?}"
);
}