#[cfg(windows)]
fn shell_cmd(cmd: &str) -> (&'static str, Vec<String>) {
("cmd", vec!["/C".to_string(), cmd.to_string()])
}
#[cfg(unix)]
fn shell_cmd(cmd: &str) -> (&'static str, Vec<String>) {
("sh", vec!["-c".to_string(), cmd.to_string()])
}
#[cfg(feature = "native")]
mod native_tests {
use super::shell_cmd;
use dtact_util::process::{DtactChild, DtactCommand};
use std::future::Future;
use std::process::Stdio;
fn block_on<F: Future>(fut: F) -> F::Output {
use std::pin::pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake};
struct NoopWaker;
impl Wake for NoopWaker {
fn wake(self: Arc<Self>) {}
}
let waker = Arc::new(NoopWaker).into();
let mut cx = Context::from_waker(&waker);
let mut fut = pin!(fut);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => std::thread::yield_now(),
}
}
}
#[test]
fn spawn_and_wait_success() {
dtact_util::process::init(2);
let (prog, args) = shell_cmd("exit 0");
let mut cmd = DtactCommand::new(prog);
cmd.args(args);
let child = cmd.spawn().unwrap();
let status = block_on(child.wait()).unwrap();
assert!(status.success());
}
#[test]
fn wait_with_output_captures_stdout() {
dtact_util::process::init(2);
let (prog, args) = shell_cmd("echo hello-dtact-process");
let mut cmd = DtactCommand::new(prog);
cmd.args(args).stdout(Stdio::piped());
let child = cmd.spawn().unwrap();
let output = block_on(child.wait_with_output()).unwrap();
assert!(output.status.success());
let text = String::from_utf8_lossy(&output.stdout);
assert!(text.contains("hello-dtact-process"), "got: {text:?}");
}
#[test]
fn stdin_stdout_pipe_roundtrip() {
dtact_util::process::init(2);
#[cfg(unix)]
let (prog, args): (&str, Vec<String>) = ("cat", vec![]);
#[cfg(windows)]
let (prog, args): (&str, Vec<String>) =
("findstr", vec!["/R".to_string(), ".".to_string()]);
let mut cmd = DtactCommand::new(prog);
cmd.args(args).stdin(Stdio::piped()).stdout(Stdio::piped());
let mut child = cmd.spawn().unwrap();
let mut stdin = child.take_stdin().unwrap();
let mut stdout = child.take_stdout().unwrap();
block_on(async {
stdin.write(b"roundtrip-payload\n".to_vec()).await.unwrap();
stdin.close();
let mut collected = Vec::new();
loop {
let (n, buf) = stdout.read(vec![0u8; 64]).await.unwrap();
if n == 0 {
break;
}
collected.extend_from_slice(&buf[..n]);
if collected.len() >= "roundtrip-payload".len() {
break;
}
}
let text = String::from_utf8_lossy(&collected);
assert!(text.contains("roundtrip-payload"), "got: {text:?}");
});
let _ = block_on(child.wait());
}
#[test]
fn spawn_nonexistent_binary_errors() {
dtact_util::process::init(2);
let mut cmd = DtactCommand::new("this-binary-definitely-does-not-exist-xyz123");
let result = cmd.spawn();
assert!(
result.is_err(),
"spawning a nonexistent program must return Err, not panic or hang"
);
if let Err(e) = result {
assert_eq!(
e.kind(),
std::io::ErrorKind::NotFound,
"expected NotFound, got {e:?}"
);
}
}
#[test]
fn nonzero_exit_code_is_reported() {
dtact_util::process::init(2);
let (prog, args) = shell_cmd("exit 3");
let mut cmd = DtactCommand::new(prog);
cmd.args(args);
let child = cmd.spawn().unwrap();
let status = block_on(child.wait()).unwrap();
assert!(!status.success());
assert_eq!(
status.code(),
Some(3),
"expected the child's exact exit code to be reported"
);
}
#[test]
fn kill_terminates_a_long_running_child() {
dtact_util::process::init(2);
#[cfg(unix)]
let (prog, args) = ("sleep", vec!["30".to_string()]);
#[cfg(windows)]
let (prog, args) = shell_cmd("timeout /T 30");
let mut cmd = DtactCommand::new(prog);
cmd.args(args);
let mut child: DtactChild = cmd.spawn().unwrap();
child.kill().unwrap();
let status = block_on(child.wait()).unwrap();
assert!(!status.success(), "killed child should not report success");
}
}
#[cfg(all(feature = "tokio", not(feature = "native")))]
mod tokio_tests {
use super::shell_cmd;
use dtact_util::process::DtactCommand;
#[tokio::test]
async fn spawn_and_wait_success() {
let (prog, args) = shell_cmd("exit 0");
let mut cmd = DtactCommand::new(prog);
cmd.args(args);
let mut child = cmd.spawn().unwrap();
let status = child.wait().await.unwrap();
assert!(status.success());
}
}