use super::{cmd, Expression};
use std;
use std::collections::HashMap;
use std::env;
use std::env::consts::EXE_EXTENSION;
use std::ffi::OsString;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str;
use std::sync::{Arc, Once};
use tempdir::TempDir;
pub fn sh(command: &'static str) -> Expression {
let argv = shell_command_argv(command.into());
cmd(&argv[0], &argv[1..])
}
#[cfg(unix)]
fn shell_command_argv(command: OsString) -> Vec<OsString> {
vec!["/bin/sh".into(), "-c".into(), command]
}
#[cfg(windows)]
fn shell_command_argv(command: OsString) -> Vec<OsString> {
let comspec = std::env::var_os("COMSPEC").unwrap_or_else(|| "cmd.exe".into());
vec![comspec, "/C".into(), command]
}
pub fn path_to_exe(name: &str) -> PathBuf {
static CARGO_BUILD_ONCE: Once = Once::new();
CARGO_BUILD_ONCE.call_once(|| {
let build_status = Command::new("cargo")
.arg("build")
.arg("--quiet")
.status()
.unwrap();
assert!(
build_status.success(),
"Cargo failed to build associated binaries."
);
});
Path::new("target")
.join("debug")
.join(name)
.with_extension(EXE_EXTENSION)
}
pub fn true_cmd() -> Expression {
cmd!(path_to_exe("status"), "0")
}
fn false_cmd() -> Expression {
cmd!(path_to_exe("status"), "1")
}
#[test]
fn test_cmd() {
let output = cmd!(path_to_exe("echo"), "hi").read().unwrap();
assert_eq!("hi", output);
}
#[test]
fn test_sh() {
let output = sh("echo hi").read().unwrap();
assert_eq!("hi", output);
}
#[test]
fn test_start() {
let handle1 = cmd!(path_to_exe("echo"), "hi")
.stdout_capture()
.start()
.unwrap();
let handle2 = cmd!(path_to_exe("echo"), "lo")
.stdout_capture()
.start()
.unwrap();
let output1 = handle1.wait().unwrap();
let output2 = handle2.wait().unwrap();
assert_eq!("hi", str::from_utf8(&output1.stdout).unwrap().trim());
assert_eq!("lo", str::from_utf8(&output2.stdout).unwrap().trim());
}
#[test]
fn test_error() {
let result = false_cmd().run();
if let Err(err) = result {
assert_eq!(err.kind(), io::ErrorKind::Other);
} else {
panic!("Expected a status error.");
}
}
#[test]
fn test_unchecked() {
let unchecked_false = false_cmd().unchecked();
let output = unchecked_false
.pipe(cmd!(path_to_exe("echo"), "waa"))
.stdout_capture()
.run()
.unwrap();
assert_eq!(1, output.status.code().unwrap());
assert_eq!("waa", String::from_utf8_lossy(&output.stdout).trim());
}
#[test]
fn test_unchecked_in_pipe() {
let zero = cmd!(path_to_exe("status"), "0");
let one = cmd!(path_to_exe("status"), "1");
let two = cmd!(path_to_exe("status"), "2");
let output = one.pipe(two.clone()).unchecked().run().unwrap();
assert_eq!(2, output.status.code().unwrap());
let output = one.pipe(two.unchecked()).unchecked().run().unwrap();
assert_eq!(1, output.status.code().unwrap());
let output = one
.unchecked()
.pipe(two.unchecked())
.unchecked()
.run()
.unwrap();
assert_eq!(2, output.status.code().unwrap());
let output = one
.unchecked()
.pipe(zero.unchecked())
.unchecked()
.run()
.unwrap();
assert_eq!(1, output.status.code().unwrap());
let output = one.unchecked().pipe(zero).unchecked().run().unwrap();
assert_eq!(1, output.status.code().unwrap());
}
#[test]
fn test_pipe() {
let output = sh("echo xxx")
.pipe(cmd!(path_to_exe("x_to_y")))
.read()
.unwrap();
assert_eq!("yyy", output);
let result = true_cmd().pipe(false_cmd()).run();
assert!(result.is_err());
let result = false_cmd().pipe(true_cmd()).run();
assert!(result.is_err());
}
#[test]
fn test_pipe_with_kill() {
let sleep_cmd = cmd!(path_to_exe("sleep"), "1000000");
let handle = sleep_cmd.pipe(sleep_cmd.clone()).start().unwrap();
handle.kill().unwrap();
handle.wait().unwrap_err();
}
#[test]
fn test_pipe_start() {
let nonexistent_cmd = cmd!(path_to_exe("nonexistent!!!"));
let sleep_cmd = cmd!(path_to_exe("sleep"), "1000000");
nonexistent_cmd.pipe(&sleep_cmd).start().unwrap_err();
sleep_cmd.pipe(nonexistent_cmd).start().unwrap_err();
}
#[test]
fn test_multiple_threads() {
let sleep_cmd = cmd!(path_to_exe("sleep"), "1000000");
let handle = Arc::new(sleep_cmd.unchecked().start().unwrap());
let arc_clone = handle.clone();
let wait_thread = std::thread::spawn(move || {
arc_clone.wait().unwrap();
});
handle.kill().unwrap();
wait_thread.join().unwrap();
}
#[test]
fn test_nonblocking_waits() {
let sleep_cmd = cmd!(path_to_exe("sleep"), "1000000");
let handle = sleep_cmd.pipe(&sleep_cmd).unchecked().start().unwrap();
assert!(handle.try_wait().unwrap().is_none());
handle.kill().unwrap();
}
#[test]
fn test_input() {
let expr = cmd!(path_to_exe("x_to_y")).stdin_bytes("xxx");
let output = expr.read().unwrap();
assert_eq!("yyy", output);
}
#[test]
fn test_stderr() {
let (mut reader, writer) = ::os_pipe::pipe().unwrap();
sh("echo hi>&2").stderr_file(writer).run().unwrap();
let mut s = String::new();
reader.read_to_string(&mut s).unwrap();
assert_eq!(s.trim(), "hi");
}
#[test]
fn test_null() {
let expr = cmd!(path_to_exe("cat"))
.stdin_null()
.stdout_null()
.stderr_null();
let output = expr.read().unwrap();
assert_eq!("", output);
}
#[test]
fn test_path() {
let dir = TempDir::new("test_path").unwrap();
let input_file = dir.path().join("input_file");
let output_file = dir.path().join("output_file");
File::create(&input_file)
.unwrap()
.write_all(b"xxx")
.unwrap();
let expr = cmd!(path_to_exe("x_to_y"))
.stdin_path(&input_file)
.stdout_path(&output_file);
let output = expr.read().unwrap();
assert_eq!("", output);
let mut file_output = String::new();
File::open(&output_file)
.unwrap()
.read_to_string(&mut file_output)
.unwrap();
assert_eq!("yyy", file_output);
}
#[test]
fn test_swapping() {
let output = sh("echo hi")
.stdout_to_stderr()
.stderr_capture()
.run()
.unwrap();
let stderr = str::from_utf8(&output.stderr).unwrap().trim();
assert_eq!("hi", stderr);
let output = sh("echo hi>&2").stderr_to_stdout().read().unwrap();
assert_eq!("hi", output);
}
#[test]
fn test_file() {
let dir = TempDir::new("test_file").unwrap();
let file = dir.path().join("file");
File::create(&file).unwrap().write_all(b"example").unwrap();
let expr = cmd!(path_to_exe("cat")).stdin_file(File::open(&file).unwrap());
let output = expr.read().unwrap();
assert_eq!(output, "example");
}
#[test]
fn test_ergonomics() {
let mystr = "owned string".to_owned();
let mypathbuf = Path::new("a/b/c").to_owned();
let myvec = vec![1, 2, 3];
let _ = sh("true")
.stdin_path(&*mystr)
.stdin_bytes(&*myvec)
.stdout_path(&*mypathbuf);
let _ = sh("true")
.stdin_path(mystr)
.stdin_bytes(myvec)
.stdout_path(mypathbuf);
}
#[test]
fn test_capture_both() {
let output = sh("echo hi && echo lo>&2")
.stdout_capture()
.stderr_capture()
.run()
.unwrap();
assert_eq!("hi", str::from_utf8(&output.stdout).unwrap().trim());
assert_eq!("lo", str::from_utf8(&output.stderr).unwrap().trim());
}
#[test]
fn test_dir() {
let pwd_path = path_to_exe("pwd");
assert!(pwd_path.is_relative());
let pwd = cmd!(pwd_path);
let pwd_output = pwd.read().unwrap();
let pwd_path = Path::new(&pwd_output);
assert_eq!(pwd_path, env::current_dir().unwrap());
let dir = TempDir::new("duct_test").unwrap();
let pwd_output = pwd.dir(dir.path()).read().unwrap();
let pwd_path = Path::new(&pwd_output);
assert_eq!(
pwd_path.canonicalize().unwrap(),
dir.path().canonicalize().unwrap()
);
}
#[test]
fn test_env() {
let output = cmd!(path_to_exe("print_env"), "foo")
.env("foo", "bar")
.read()
.unwrap();
assert_eq!("bar", output);
}
#[test]
fn test_full_env() {
let var_name = "TEST_FULL_ENV";
let clean_env: HashMap<String, String> = env::vars().collect();
assert!(
!clean_env.contains_key(var_name),
"why is this variable set?"
);
let clean_child = cmd!(path_to_exe("print_env"), var_name).full_env(clean_env);
env::set_var(var_name, "junk1");
let dirty_child = clean_child.env(var_name, "junk2");
let output = dirty_child.read().unwrap();
assert_eq!("", output);
}
#[test]
fn test_env_remove() {
let var_name = "TEST_ENV_REMOVE";
env::set_var(var_name, "junk2");
let output1 = cmd!(path_to_exe("print_env"), var_name).read().unwrap();
assert_eq!("junk2", output1);
let output2 = cmd!(path_to_exe("print_env"), var_name)
.env_remove(var_name)
.read()
.unwrap();
assert_eq!("", output2);
}
#[test]
fn test_env_remove_case_sensitivity() {
let var_name = "TEST_ENV_REMOVE_CASE_SENSITIVITY";
env::set_var(var_name, "abc123");
let output1 = cmd!(path_to_exe("print_env"), var_name)
.env_remove(var_name.to_lowercase())
.read()
.unwrap();
env::remove_var(var_name.to_lowercase());
let output2 = cmd!(path_to_exe("print_env"), var_name).read().unwrap();
assert_eq!(output1, output2, "failed to match platform behavior!!!");
if cfg!(windows) {
assert_eq!(output1, "");
} else {
assert_eq!(output1, "abc123");
}
}
#[test]
fn test_broken_pipe() {
let myvec = vec![0; 1_000_000];
true_cmd().stdin_bytes(myvec).run().unwrap();
}
#[test]
fn test_silly() {
crate::IoValue::Null.try_clone().unwrap();
}
#[test]
fn test_path_sanitization() {
cmd!(path_to_exe("exe_in_dir"), path_to_exe("status"), "0")
.run()
.unwrap();
}
#[test]
fn test_before_spawn_hook() {
let (reader, mut writer) = os_pipe::pipe().unwrap();
let expr = cmd!(path_to_exe("cat")).before_spawn(move |cmd| {
let reader_clone = reader.try_clone()?;
cmd.stdin(reader_clone);
Ok(())
});
writer.write_all(b"foobar").unwrap();
drop(writer);
let output = expr.read().unwrap();
assert_eq!("foobar", output);
}
#[test]
fn test_trailing_comma() {
let output = cmd!(path_to_exe("echo"), "trailing",).read().unwrap();
assert_eq!("trailing", output);
}
#[test]
fn test_no_argument() {
let output = cmd!(path_to_exe("echo")).read().unwrap();
assert_eq!("", output);
}
#[test]
fn test_dropping_reader() {
let (mut stderr_reader, stderr_writer) = os_pipe::pipe().unwrap();
let mut reader_handle = cmd!(path_to_exe("sleep"), "1000000")
.stdout_file(stderr_writer)
.reader()
.unwrap();
let n = reader_handle.read(&mut []).unwrap();
assert_eq!(n, 0);
let output = reader_handle.try_wait().unwrap();
assert!(output.is_none());
drop(reader_handle);
let mut stderr = Vec::new();
let n = stderr_reader.read_to_end(&mut stderr).unwrap();
assert_eq!(n, 0);
}
#[test]
fn test_kill_with_grandchild() -> io::Result<()> {
let mut reader = cmd!(path_to_exe("child_grandchild"))
.stderr_capture()
.reader()?;
let mut started_read = [0; 7];
reader.read_exact(&mut started_read)?;
assert_eq!(&started_read, b"started");
reader.kill()
}
#[test]
fn test_debug_format() {
let e = cmd!("foo", "bar", "baz").pipe(cmd!("bing", "bong"));
assert_eq!(
format!("{:?}", e),
r#"Pipe(Cmd(["foo", "bar", "baz"]), Cmd(["bing", "bong"]))"#,
);
}
#[test]
fn test_reader_try_wait() -> io::Result<()> {
let bytes = vec![42; 1_000_000];
let mut cat_reader = cmd!(path_to_exe("cat"))
.stdin_bytes(bytes.clone())
.reader()?;
assert!(cat_reader.try_wait()?.is_none());
let mut output = Vec::new();
cat_reader.read_to_end(&mut output)?;
assert_eq!(output, bytes);
let output = cat_reader.try_wait()?.expect("is some");
assert!(output.status.success());
assert!(output.stdout.is_empty());
assert!(output.stderr.is_empty());
Ok(())
}
#[test]
fn test_pids() -> io::Result<()> {
let handle = true_cmd().start()?;
let pids = handle.pids();
assert_eq!(pids.len(), 1);
handle.wait()?;
let reader = true_cmd().reader()?;
let pids = reader.pids();
assert_eq!(pids.len(), 1);
std::io::copy(&mut &reader, &mut std::io::sink())?;
let handle = true_cmd()
.pipe(true_cmd().stdout_null().pipe(true_cmd()))
.start()?;
let pids = handle.pids();
assert_eq!(pids.len(), 3);
handle.wait()?;
let reader = true_cmd()
.pipe(true_cmd().stdout_null().pipe(true_cmd()))
.reader()?;
let pids = reader.pids();
assert_eq!(pids.len(), 3);
std::io::copy(&mut &reader, &mut std::io::sink())?;
Ok(())
}