use std::future::Future;
use std::io;
use std::path::PathBuf;
use std::process::{ExitStatus, Stdio};
pub trait BenchRunner {
fn run_benches(
&self,
argv: &[String],
env: &[(String, String)],
) -> impl Future<Output = io::Result<EngineStatus>>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EngineStatus {
pub success: bool,
pub code: Option<i32>,
}
impl EngineStatus {
fn from_exit(status: ExitStatus) -> Self {
Self {
success: status.success(),
code: status.code(),
}
}
}
#[derive(Clone, Debug)]
pub struct CommandOutput {
pub status: ExitStatus,
pub stdout: String,
}
#[derive(Clone, Debug, Default)]
pub struct TokioBenchRunner {
dir: Option<PathBuf>,
}
impl TokioBenchRunner {
#[must_use]
pub fn in_dir(dir: impl Into<PathBuf>) -> Self {
Self {
dir: Some(dir.into()),
}
}
}
impl BenchRunner for TokioBenchRunner {
async fn run_benches(
&self,
argv: &[String],
env: &[(String, String)],
) -> io::Result<EngineStatus> {
let mut command = engine_command(argv);
if let Some(dir) = self.dir.as_deref() {
command.current_dir(dir);
}
for (name, value) in env {
command.env(name, value);
}
Ok(EngineStatus::from_exit(command.status().await?))
}
}
pub async fn capture(program: &str, args: &[&str]) -> io::Result<CommandOutput> {
let output = tokio::process::Command::new(program)
.args(args)
.stdin(Stdio::null())
.output()
.await?;
Ok(CommandOutput {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
})
}
fn engine_command(argv: &[String]) -> tokio::process::Command {
let (program, args) = argv
.split_first()
.expect("engine argv is non-empty; build_bench_argv rejects empty commands");
let mut command = tokio::process::Command::new(program);
command.args(args);
command
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::path::PathBuf;
use super::{TokioBenchRunner, engine_command};
#[test]
fn in_dir_sets_the_working_directory() {
let runner = TokioBenchRunner::in_dir("some/worktree");
assert_eq!(runner.dir, Some(PathBuf::from("some/worktree")));
assert_eq!(TokioBenchRunner::default().dir, None);
}
#[test]
fn engine_command_runs_argv_directly() {
let argv = vec!["echo".to_owned(), "hi there".to_owned()];
let command = engine_command(&argv);
let std = command.as_std();
let program = std.get_program().to_string_lossy().into_owned();
let args: Vec<String> = std
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(program, "echo");
assert_eq!(args, vec!["hi there".to_owned()]);
}
}