use process::{Command, ExitStatus};
use std::{io, path::Path, process, time::Duration};
use thiserror::Error;
use wait_timeout::ChildExt;
pub fn binary_kind(binary_path: &Path) -> BinaryKind {
let exe_parent = binary_path.parent();
let parent_dir_name = exe_parent
.and_then(|p| p.file_name())
.and_then(|name| name.to_str());
match parent_dir_name {
Some("deps") => BinaryKind::Test,
Some(name) if name.starts_with("rustdoctest") => BinaryKind::DocTest,
_other => BinaryKind::Other,
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum BinaryKind {
Test,
DocTest,
Other,
}
impl BinaryKind {
pub fn is_test(&self) -> bool {
match self {
BinaryKind::Test | BinaryKind::DocTest => true,
BinaryKind::Other => false,
}
}
}
pub fn run_with_timeout(command: &mut Command, timeout: Duration) -> Result<ExitStatus, RunError> {
let mut child = command.spawn().map_err(|error| RunError::Io {
context: IoErrorContext::Command {
command: format!("{:?}", command),
},
error,
})?;
match child
.wait_timeout(timeout)
.map_err(context(IoErrorContext::WaitWithTimeout))?
{
None => {
child.kill().map_err(context(IoErrorContext::KillProcess))?;
child
.wait()
.map_err(context(IoErrorContext::WaitForProcess))?;
Err(RunError::TimedOut)
}
Some(exit_status) => Ok(exit_status),
}
}
#[derive(Debug, Error)]
pub enum RunError {
#[error("Command timed out")]
TimedOut,
#[error("I/O error: {context}")]
Io {
context: IoErrorContext,
#[source]
error: io::Error,
},
}
#[derive(Debug, Error)]
pub enum IoErrorContext {
#[error("Failed to execute command `{command}`")]
Command {
command: String,
},
#[error("Failed to wait with timeout")]
WaitWithTimeout,
#[error("Failed to kill process after timeout")]
KillProcess,
#[error("Failed to wait for process after killing it after timeout")]
WaitForProcess,
}
fn context(context: IoErrorContext) -> impl FnOnce(io::Error) -> RunError {
|error| RunError::Io { context, error }
}