use core::future::{Future, poll_fn};
use core::pin::Pin;
use std::time::Duration;
use futures_io::AsyncWrite;
use super::{Command, Stdio};
async fn within<F: Future>(fut: F) -> F::Output {
crate::time::timeout(Duration::from_secs(30), fut)
.await
.expect("nothing came within thirty seconds")
}
async fn write_all(w: &mut (impl AsyncWrite + Unpin), mut buf: &[u8]) {
while !buf.is_empty() {
let n = poll_fn(|cx| Pin::new(&mut *w).poll_write(cx, buf))
.await
.unwrap();
buf = &buf[n..];
}
}
#[test]
fn into_std_hands_the_command_over() {
let mut command = Command::new("prog");
command.arg("a").current_dir("/");
let std = command.into_std();
assert_eq!(std.get_program(), "prog");
assert_eq!(std.get_args().collect::<Vec<_>>(), ["a"]);
assert_eq!(std.get_current_dir(), Some(std::path::Path::new("/")));
}
#[cfg(unix)]
mod unix {
use std::os::unix::process::ExitStatusExt;
use std::time::Instant;
use super::*;
fn sh(script: &str) -> Command {
let mut command = Command::new("sh");
command.args(["-c", script]);
command
}
fn reaped(pid: u32) -> bool {
let mut info: libc::siginfo_t = unsafe { core::mem::zeroed() };
let r = unsafe {
libc::waitid(
libc::P_PID,
pid as libc::id_t,
&mut info,
libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
)
};
r != 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::ECHILD)
}
fn exited(pid: u32) -> bool {
let mut info: libc::siginfo_t = unsafe { core::mem::zeroed() };
let r = unsafe {
libc::waitid(
libc::P_PID,
pid as libc::id_t,
&mut info,
libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
)
};
r != 0 || crate::process::exit::status_of(&info).is_some()
}
#[test]
fn exit_codes_come_back() {
crate::block_on(async {
assert_eq!(within(sh("exit 7").status()).await.unwrap().code(), Some(7));
assert!(
within(Command::new("true").status())
.await
.unwrap()
.success()
);
assert_eq!(
within(Command::new("false").status()).await.unwrap().code(),
Some(1)
);
});
}
#[test]
fn a_killed_child_reports_its_signal() {
crate::block_on(async {
let mut child = Command::new("sleep").arg("30").spawn().unwrap();
assert!(child.try_wait().unwrap().is_none(), "it runs");
child.start_kill().unwrap();
let status = within(child.wait()).await.unwrap();
assert_eq!(status.signal(), Some(libc::SIGKILL));
assert_eq!(within(child.wait()).await.unwrap(), status);
child.start_kill().unwrap();
});
}
#[test]
fn output_collects_both_streams_until_the_exit() {
crate::block_on(async {
let output = within(sh("printf out; printf err >&2; exit 3").output())
.await
.unwrap();
assert_eq!(output.status.code(), Some(3));
assert_eq!(output.stdout, b"out");
assert_eq!(output.stderr, b"err");
});
}
#[test]
fn stdin_reaches_the_child_and_its_end_ends_it() {
crate::block_on(async {
let mut child = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
write_all(&mut stdin, b"hello, child").await;
drop(stdin);
let output = within(child.wait_with_output()).await.unwrap();
assert!(output.status.success());
assert_eq!(output.stdout, b"hello, child");
});
}
#[test]
fn a_child_is_waited_for_on_a_worker() {
let rt = crate::runtime::Runtime::builder()
.worker_threads(2)
.pin_workers(false)
.build()
.unwrap();
let task = rt.spawn(async { within(sh("exit 9").status()).await });
assert_eq!(rt.block_on(task).unwrap().code(), Some(9));
}
#[test]
fn a_thousand_live_children_each_report_their_exit() {
if !crate::alone::alone(
"process::tests::unix::a_thousand_live_children_each_report_their_exit",
) {
return;
}
unsafe {
let mut limit: libc::rlimit = core::mem::zeroed();
assert_eq!(libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit), 0);
limit.rlim_cur = limit.rlim_max;
assert_eq!(libc::setrlimit(libc::RLIMIT_NOFILE, &limit), 0);
}
crate::block_on(async {
let mut children: Vec<_> = (0..1000)
.map(|i| {
if i % 2 == 0 {
Command::new("sleep").arg("60").spawn().unwrap()
} else {
sh("read line; exit 3")
.stdin(Stdio::piped())
.spawn()
.unwrap()
}
})
.collect();
for (i, child) in children.iter_mut().enumerate() {
if i % 2 == 0 {
child.start_kill().unwrap();
} else {
drop(child.stdin.take());
}
}
for (i, child) in children.iter_mut().enumerate() {
let status = within(child.wait()).await.unwrap();
if i % 2 == 0 {
assert_eq!(
status.signal(),
Some(libc::SIGKILL),
"child {i}: {status:?}"
);
} else {
assert_eq!(status.code(), Some(3), "child {i}: {status:?}");
}
}
});
}
#[test]
fn kill_ends_the_child_and_waits_for_its_exit() {
crate::block_on(async {
let mut child = Command::new("sleep").arg("30").spawn().unwrap();
within(child.kill()).await.unwrap();
assert!(reaped(child.id()));
let status = child.try_wait().unwrap().expect("it exited");
assert_eq!(status.signal(), Some(libc::SIGKILL));
within(child.kill()).await.unwrap();
});
}
#[test]
fn kill_on_drop_ends_the_child_its_child_left() {
crate::block_on(async {
let mut command = Command::new("sleep");
command.arg("60");
assert!(!command.get_kill_on_drop());
let runs_on = command.spawn().unwrap();
let runs_on_pid = runs_on.id();
drop(runs_on);
let killed = command.kill_on_drop(true).spawn().unwrap();
assert!(command.get_kill_on_drop());
let pid = killed.id();
drop(killed);
let deadline = Instant::now() + Duration::from_secs(10);
while !reaped(pid) {
assert!(Instant::now() < deadline, "the child was not ended");
crate::time::sleep(Duration::from_millis(5)).await;
}
assert!(
!exited(runs_on_pid),
"the child without kill_on_drop runs on"
);
assert_eq!(
unsafe { libc::kill(runs_on_pid as libc::pid_t, libc::SIGKILL) },
0
);
});
}
#[test]
fn spawn_with_starts_the_child_its_own_way() {
crate::block_on(async {
let mut command = Command::new("sh");
let child = command
.stdout(Stdio::piped())
.spawn_with(|std| std.args(["-c", "echo hi; exit 3"]).spawn())
.unwrap();
let output = within(child.wait_with_output()).await.unwrap();
assert_eq!(output.status.code(), Some(3));
assert_eq!(output.stdout, b"hi\n");
let refused = command
.spawn_with(|_| Err(std::io::Error::other("refused")))
.unwrap_err();
assert_eq!(refused.to_string(), "refused");
});
}
#[test]
fn a_child_whose_child_went_is_reaped() {
crate::block_on(async {
let child = sh("sleep 0.1").spawn().unwrap();
let pid = child.id();
drop(child);
let deadline = Instant::now() + Duration::from_secs(30);
while !reaped(pid) {
assert!(Instant::now() < deadline, "the child was never reaped");
crate::time::sleep(Duration::from_millis(5)).await;
}
});
}
#[test]
fn a_child_that_exited_before_its_watch_is_reaped() {
crate::block_on(async {
let mut child = Command::new("true").spawn().unwrap();
let deadline = Instant::now() + Duration::from_secs(30);
while !exited(child.id()) {
assert!(Instant::now() < deadline, "the child never exited");
std::thread::yield_now();
}
assert!(within(child.wait()).await.unwrap().success());
assert!(reaped(child.id()));
});
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn the_sigchld_reaper_takes_children_without_a_pidfd() {
crate::process::exit::NO_PIDFD.with(|no| no.set(true));
crate::block_on(async {
assert_eq!(within(sh("exit 5").status()).await.unwrap().code(), Some(5));
let mut child = Command::new("sleep").arg("30").spawn().unwrap();
child.start_kill().unwrap();
assert_eq!(
within(child.wait()).await.unwrap().signal(),
Some(libc::SIGKILL)
);
});
crate::process::exit::NO_PIDFD.with(|no| no.set(false));
}
#[cfg(ring)]
#[test]
fn a_ring_without_waitid_polls_the_pidfd() {
crate::rt::shard::ring::exits::NO_WAITID.with(|no| no.set(true));
crate::block_on(async {
assert_eq!(within(sh("exit 4").status()).await.unwrap().code(), Some(4));
});
crate::rt::shard::ring::exits::NO_WAITID.with(|no| no.set(false));
}
}
#[cfg(windows)]
mod windows {
use super::*;
fn cmd(script: &str) -> Command {
let mut command = Command::new("cmd");
command.args(["/C", script]);
command
}
#[test]
fn exit_codes_come_back() {
crate::block_on(async {
assert_eq!(
within(cmd("exit 7").status()).await.unwrap().code(),
Some(7)
);
assert!(within(cmd("exit 0").status()).await.unwrap().success());
});
}
#[test]
fn a_killed_child_reports_its_end() {
crate::block_on(async {
let mut child = Command::new("ping")
.args(["-n", "30", "127.0.0.1"])
.stdout(Stdio::null())
.spawn()
.unwrap();
assert!(child.try_wait().unwrap().is_none(), "it runs");
child.start_kill().unwrap();
let status = within(child.wait()).await.unwrap();
assert_eq!(status.code(), Some(1));
child.start_kill().unwrap();
});
}
#[test]
fn kill_ends_the_child_and_waits_for_its_exit() {
crate::block_on(async {
let mut child = Command::new("ping")
.args(["-n", "30", "127.0.0.1"])
.stdout(Stdio::null())
.spawn()
.unwrap();
within(child.kill()).await.unwrap();
let status = child.try_wait().unwrap().expect("it exited");
assert_eq!(status.code(), Some(1));
within(child.kill()).await.unwrap();
});
}
#[test]
fn kill_on_drop_ends_the_child_its_child_left() {
use crate::io::AsyncReadExt;
crate::block_on(async {
let mut child = Command::new("ping")
.args(["-n", "60", "127.0.0.1"])
.stdout(Stdio::piped())
.kill_on_drop(true)
.spawn()
.unwrap();
let mut stdout = child.stdout.take().unwrap();
drop(child);
let mut out = Vec::new();
crate::time::timeout(Duration::from_secs(10), stdout.read_to_end(&mut out))
.await
.expect("the child was not ended")
.unwrap();
});
}
#[test]
fn output_collects_both_streams_until_the_exit() {
crate::block_on(async {
let output = within(cmd("(echo out)& (echo err)1>&2& exit /B 3").output())
.await
.unwrap();
assert_eq!(output.status.code(), Some(3));
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "out");
assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), "err");
});
}
#[test]
fn stdin_reaches_the_child_and_its_end_ends_it() {
crate::block_on(async {
let mut child = Command::new("sort")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
write_all(&mut stdin, b"b\r\na\r\n").await;
drop(stdin);
let output = within(child.wait_with_output()).await.unwrap();
assert!(output.status.success());
assert_eq!(String::from_utf8_lossy(&output.stdout), "a\r\nb\r\n");
});
}
#[test]
fn many_children_each_report_their_exit() {
crate::block_on(async {
let mut children: Vec<_> = (0..100)
.map(|i| cmd(&format!("exit {i}")).spawn().unwrap())
.collect();
for (i, child) in children.iter_mut().enumerate() {
assert_eq!(within(child.wait()).await.unwrap().code(), Some(i as i32));
}
});
}
}