use crate::{
Command, ExecResult, ExitStatus, OpaqueOsExitStatus, OutputMapping, UnexpectedExitStatus,
};
use std::{io, process};
pub(super) fn actual_exec_exec_replacement_callback<O, E>(
cmd: Command<O, E>,
return_settings: &dyn OutputMapping<Output = O, Error = E>,
) -> Result<ExecResult, io::Error>
where
E: From<io::Error> + From<UnexpectedExitStatus>,
{
let mut sys_cmd = process::Command::new(cmd.program());
sys_cmd.args(cmd.arguments());
sys_cmd.env_clear();
sys_cmd.envs(cmd.create_expected_env_iter());
if let Some(wd_override) = cmd.working_directory_override() {
sys_cmd.current_dir(wd_override);
}
let capture_stdout = return_settings.capture_stdout();
let capture_stderr = return_settings.capture_stderr();
if capture_stdout {
sys_cmd.stdout(process::Stdio::piped());
}
if capture_stderr {
sys_cmd.stderr(process::Stdio::piped());
}
let child = sys_cmd.spawn()?;
let process::Output {
stdout,
stderr,
status: exit_status,
} = child.wait_with_output()?;
let exit_status = map_std_exit_status(exit_status);
let stdout = if capture_stdout {
Some(stdout)
} else {
debug_assert!(stdout.is_empty());
None
};
let stderr = if capture_stderr {
Some(stderr)
} else {
debug_assert!(stderr.is_empty());
None
};
Ok(ExecResult {
exit_status,
stdout,
stderr,
})
}
fn map_std_exit_status(exit_status: std::process::ExitStatus) -> ExitStatus {
if let Some(code) = exit_status.code() {
let code = cast_exit_code(code);
ExitStatus::Code(code)
} else {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
let signal = exit_status.signal().unwrap_or(0x7F);
let signal = signal;
return OpaqueOsExitStatus::from_signal_number(signal).into();
}
#[cfg(not(unix))]
unreachable!("run on unsupported target family, please open issue on github");
}
}
fn cast_exit_code(code: i32) -> i64 {
#[cfg(windows)]
{
windows_cast_exit_code(code)
}
#[cfg(not(windows))]
{
code as i64
}
}
#[cfg(any(windows, test))]
fn windows_cast_exit_code(code: i32) -> i64 {
code as u32 as i64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ReturnStderr, ReturnStdout};
use proptest::prelude::*;
#[cfg(target_os = "linux")]
#[test]
fn with_arguments() {
let cap = Command::new("echo", ReturnStdout)
.with_arguments(vec!["hy", "there"])
.run()
.unwrap();
assert_eq!(String::from_utf8_lossy(&*cap), "hy there\n");
}
#[cfg(target_os = "linux")]
#[test]
fn can_run_failing_program_without_failing() {
let cap = Command::new("cp", ReturnStderr)
.with_arguments(vec!["/"])
.with_expected_exit_status(1)
.run()
.unwrap();
assert!(!cap.is_empty());
}
#[cfg(target_os = "linux")]
#[test]
fn with_env() {
let out = Command::new("bash", ReturnStdout)
.with_arguments(&["-c", "echo $MAPPED_COMMAND_ENV_TEST"])
.with_inherit_env(false)
.with_env_update("MAPPED_COMMAND_ENV_TEST", "yoyo")
.run()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out), "yoyo\n");
}
#[cfg(target_os = "linux")]
#[test]
fn with_working_dir() {
let out = Command::new("pwd", ReturnStdout)
.with_working_directory_override(Some("/"))
.run()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out), "/\n");
}
#[test]
fn special_windows_exit_code_cast() {
assert_eq!(windows_cast_exit_code(-1), u32::MAX as i64);
}
#[test]
#[cfg(unix)]
fn on_non_windows_cast_exit_code_normally() {
assert_eq!(cast_exit_code(-1), -1);
}
#[test]
#[cfg(not(unix))]
fn on_windows_cast_exit_code_specially() {
assert_eq!(cast_exit_code(-1), u32::MAX as i64);
}
proptest! {
#[test]
#[cfg(windows)]
fn mapping_windows_exit_code_to_status(
exit_code in any::<u32>()
) {
use std::os::windows::process::ExitStatusExt;
let exit_status = std::process::ExitStatus::from_raw(exit_code);
let exit_status = map_std_exit_status(exit_status);
assert_eq!(exit_status, ExitStatus::Code(exit_code.into()));
}
#[test]
#[cfg(unix)]
fn mapping_unix_exit_code_to_status(
raw_status in any::<i32>()
) {
use std::os::unix::process::ExitStatusExt;
let exit_status = std::process::ExitStatus::from_raw(raw_status);
let exit_status = map_std_exit_status(exit_status);
if libc::WIFEXITED(raw_status) {
let code= libc::WEXITSTATUS(raw_status);
assert_eq!(exit_status, ExitStatus::Code(code as i64));
} else {
let signal = libc::WTERMSIG(raw_status);
assert_eq!(exit_status, ExitStatus::OsSpecific(OpaqueOsExitStatus::from_signal_number(signal).into()));
}
}
}
}