use process_control::{ChildExt, Control};
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fs::read_to_string;
use std::io::{Error, ErrorKind, Result};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use crate::context::Context;
use crate::context::Shell;
/// Create a `PathBuf` from an absolute path, where the root directory will be mocked in test
#[cfg(not(test))]
#[inline]
#[allow(dead_code)]
pub fn context_path<S: AsRef<OsStr> + ?Sized>(_context: &Context, s: &S) -> PathBuf {
PathBuf::from(s)
}
/// Create a `PathBuf` from an absolute path, where the root directory will be mocked in test
#[cfg(test)]
#[allow(dead_code)]
pub fn context_path<S: AsRef<OsStr> + ?Sized>(context: &Context, s: &S) -> PathBuf {
let requested_path = PathBuf::from(s);
if requested_path.is_absolute() {
let mut path = PathBuf::from(context.root_dir.path());
path.extend(requested_path.components().skip(1));
path
} else {
requested_path
}
}
/// Return the string contents of a file
pub fn read_file<P: AsRef<Path> + Debug>(file_name: P) -> Result<String> {
log::trace!("Trying to read from {:?}", file_name);
let result = read_to_string(file_name);
if result.is_err() {
log::debug!("Error reading file: {:?}", result);
} else {
log::trace!("File read successfully");
};
result
}
/// Write a string to a file
#[cfg(test)]
pub fn write_file<P: AsRef<Path>, S: AsRef<str>>(file_name: P, text: S) -> Result<()> {
use std::io::Write;
let file_name = file_name.as_ref();
let text = text.as_ref();
log::trace!("Trying to write {text:?} to {file_name:?}");
let mut file = match std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(file_name)
{
Ok(file) => file,
Err(err) => {
log::warn!("Error creating file: {:?}", err);
return Err(err);
}
};
match file.write_all(text.as_bytes()) {
Ok(()) => {
log::trace!("File {file_name:?} written successfully");
}
Err(err) => {
log::warn!("Error writing to file: {err:?}");
return Err(err);
}
}
file.sync_all()
}
/// Reads command output from stderr or stdout depending on to which stream program streamed it's output
pub fn get_command_string_output(command: CommandOutput) -> String {
if command.stdout.is_empty() {
command.stderr
} else {
command.stdout
}
}
/// Attempt to resolve `binary_name` from and creates a new `Command` pointing at it
/// This allows executing cmd files on Windows and prevents running executable from cwd on Windows
/// This function also initializes std{err,out,in} to protect against processes changing the console mode
pub fn create_command<T: AsRef<OsStr>>(binary_name: T) -> Result<Command> {
let binary_name = binary_name.as_ref();
log::trace!("Creating Command for binary {:?}", binary_name);
let full_path = match which::which(binary_name) {
Ok(full_path) => {
log::trace!("Using {:?} as {:?}", full_path, binary_name);
full_path
}
Err(error) => {
log::trace!("Unable to find {:?} in PATH, {:?}", binary_name, error);
return Err(Error::new(ErrorKind::NotFound, error));
}
};
#[allow(clippy::disallowed_methods)]
let mut cmd = Command::new(full_path);
cmd.stderr(Stdio::piped())
.stdout(Stdio::piped())
.stdin(Stdio::null());
Ok(cmd)
}
#[derive(Debug, Clone)]
pub struct CommandOutput {
pub stdout: String,
pub stderr: String,
}
impl PartialEq for CommandOutput {
fn eq(&self, other: &Self) -> bool {
self.stdout == other.stdout && self.stderr == other.stderr
}
}
#[cfg(test)]
pub fn display_command<T: AsRef<OsStr> + Debug, U: AsRef<OsStr> + Debug>(
cmd: T,
args: &[U],
) -> String {
std::iter::once(cmd.as_ref())
.chain(args.iter().map(AsRef::as_ref))
.map(|i| i.to_string_lossy().into_owned())
.collect::<Vec<String>>()
.join(" ")
}
/// Execute a command and return the output on stdout and stderr if successful
pub fn exec_cmd<T: AsRef<OsStr> + Debug, U: AsRef<OsStr> + Debug>(
cmd: T,
args: &[U],
time_limit: Duration,
) -> Option<CommandOutput> {
log::trace!("Executing command {:?} with args {:?}", cmd, args);
#[cfg(test)]