use std::io;
use std::process::{Child, ChildStdin, Command, ExitStatus, Output, Stdio};
use super::registry;
pub struct ScopedChild {
inner: Option<Child>,
id: Option<u64>,
}
impl ScopedChild {
pub fn spawn(command: &mut Command) -> io::Result<Self> {
let child = command.spawn()?;
let id = registry::register(child.id());
Ok(Self {
inner: Some(child),
id: Some(id),
})
}
#[cfg_attr(
not(unix),
allow(
dead_code,
reason = "only consumed by the cfg(unix) signal_test_helper in main.rs; the lint does not consistently fire on Windows under -D warnings so allow is safer than expect"
)
)]
pub fn id(&self) -> u32 {
self.inner.as_ref().map_or(0, Child::id)
}
pub fn take_stdin(&mut self) -> Option<ChildStdin> {
self.inner.as_mut().and_then(|c| c.stdin.take())
}
pub fn wait_with_output(mut self) -> io::Result<Output> {
let child = self.inner.take().expect("inner already taken");
let id = self.id.take();
let result = child.wait_with_output();
if let Some(id) = id {
registry::deregister(id);
}
result
}
pub fn wait(mut self) -> io::Result<ExitStatus> {
let mut child = self.inner.take().expect("inner already taken");
let id = self.id.take();
let result = child.wait();
if let Some(id) = id {
registry::deregister(id);
}
result
}
}
impl Drop for ScopedChild {
fn drop(&mut self) {
if let Some(id) = self.id.take() {
registry::deregister(id);
}
if let Some(mut child) = self.inner.take() {
let _ = child.try_wait();
}
}
}
pub fn status(command: &mut Command) -> io::Result<ExitStatus> {
let scoped = ScopedChild::spawn(command)?;
scoped.wait()
}
pub fn output(command: &mut Command) -> io::Result<Output> {
command
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
ScopedChild::spawn(command)?.wait_with_output()
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[cfg(unix)]
fn assert_deregistered(id: u64) {
registry::deregister(id);
}
#[test]
#[cfg(unix)]
fn scoped_child_drop_deregisters() {
let mut cmd = Command::new("true");
let child = ScopedChild::spawn(&mut cmd).expect("spawn true");
let id = child.id.expect("freshly spawned wrapper has an id");
assert!(id > 0);
drop(child);
assert_deregistered(id);
}
#[test]
#[cfg(unix)]
fn scoped_child_wait_deregisters_and_succeeds() {
let mut cmd = Command::new("true");
let child = ScopedChild::spawn(&mut cmd).expect("spawn true");
let id = child.id.expect("freshly spawned wrapper has an id");
let status = child.wait().expect("wait true");
assert!(status.success());
assert_deregistered(id);
}
#[test]
#[cfg(unix)]
fn output_helper_collects_stdout() {
let mut cmd = Command::new("echo");
cmd.arg("hello")
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let output = output(&mut cmd).expect("echo");
assert!(output.status.success());
assert_eq!(output.stdout, b"hello\n");
}
}