#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
pub trait Quiet {
fn quiet(&mut self) -> &mut Self;
}
impl Quiet for std::process::Command {
fn quiet(&mut self) -> &mut Self {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt as _;
self.creation_flags(CREATE_NO_WINDOW);
}
self
}
}
impl Quiet for tokio::process::Command {
fn quiet(&mut self) -> &mut Self {
#[cfg(windows)]
{
self.creation_flags(CREATE_NO_WINDOW);
}
self
}
}
#[must_use]
pub fn pid_alive(pid: u32) -> bool {
#[cfg(unix)]
{
match std::process::Command::new("kill")
.arg("-0")
.arg(pid.to_string())
.output()
{
Ok(o) if o.status.success() => true,
Ok(o) => {
let stderr = String::from_utf8_lossy(&o.stderr).to_lowercase();
!stderr.contains("no such process")
}
Err(_) => true,
}
}
#[cfg(windows)]
{
let out = std::process::Command::new("tasklist")
.quiet()
.args(["/FI", &format!("PID eq {pid}"), "/NH", "/FO", "CSV"])
.output();
match out {
Ok(o) if o.status.success() => {
String::from_utf8_lossy(&o.stdout).contains(&format!("\"{pid}\""))
}
_ => true,
}
}
#[cfg(not(any(unix, windows)))]
{
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(windows)]
#[test]
fn the_flag_hides_a_console_rather_than_removing_or_creating_one() {
assert_eq!(CREATE_NO_WINDOW, 0x0800_0000);
assert_ne!(CREATE_NO_WINDOW, 0x0000_0008, "DETACHED_PROCESS");
assert_ne!(CREATE_NO_WINDOW, 0x0000_0010, "CREATE_NEW_CONSOLE");
}
#[test]
fn quiet_leaves_the_command_it_was_handed_intact() {
let mut cmd = tokio::process::Command::new("git");
cmd.args(["status", "--short"]).quiet();
let built = cmd.as_std();
assert_eq!(built.get_program(), "git");
let args: Vec<_> = built.get_args().collect();
assert_eq!(args, ["status", "--short"]);
}
#[test]
fn every_spawn_in_the_crate_is_quiet_or_documented_as_exempt() {
let src_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut offenders = Vec::new();
for entry in std::fs::read_dir(&src_dir).expect("read src dir") {
let path = entry.expect("dir entry").path();
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let file_name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_owned();
if file_name == "tui.rs" {
continue;
}
let text = std::fs::read_to_string(&path).expect("read source file");
let lines: Vec<&str> = text.lines().collect();
let spawn_at: Vec<usize> = lines
.iter()
.enumerate()
.filter(|(_, l)| l.contains("Command::new("))
.map(|(i, _)| i)
.collect();
for (pos, &start) in spawn_at.iter().enumerate() {
let end = spawn_at.get(pos + 1).copied().unwrap_or(lines.len());
let block = lines[start..end].join("\n");
if block.contains(".quiet()") {
continue;
}
if block.contains("DETACHED_PROCESS") {
continue;
}
let preceding = lines[start.saturating_sub(5)..start].join("\n");
if preceding.contains("#[cfg(unix)]") {
continue;
}
offenders.push(format!("{file_name}:{}", start + 1));
}
}
assert!(
offenders.is_empty(),
"Command::new without .quiet() and no documented exemption: {offenders:?}"
);
}
#[test]
fn this_process_is_alive_and_a_pid_nothing_ever_reuses_is_not() {
assert!(pid_alive(std::process::id()), "this test is running");
assert!(!pid_alive(999_999_999));
}
}