Parse --shell option value as shell command

This commit is contained in:
rhysd 2021-10-04 22:34:19 +09:00 committed by David Peter
parent 5c6879e4c1
commit e2fdaceb9f
7 changed files with 96 additions and 20 deletions

7
Cargo.lock generated
View File

@ -243,6 +243,7 @@ dependencies = [
"rust_decimal",
"serde",
"serde_json",
"shell-words",
"statistical",
"tempfile",
"winapi",
@ -680,6 +681,12 @@ dependencies = [
"serde",
]
[[package]]
name = "shell-words"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
[[package]]
name = "statistical"
version = "1.0.0"

View File

@ -21,6 +21,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rust_decimal = "1.16"
rand = "0.8"
shell-words = "1.0"
[target.'cfg(not(windows))'.dependencies]
libc = "0.2"

View File

@ -9,7 +9,7 @@ use crate::benchmark_result::BenchmarkResult;
use crate::command::Command;
use crate::format::{format_duration, format_duration_unit};
use crate::min_max::{max, min};
use crate::options::{CmdFailureAction, HyperfineOptions, OutputStyleOption};
use crate::options::{CmdFailureAction, HyperfineOptions, OutputStyleOption, Shell};
use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};
use crate::progress_bar::get_progress_bar;
use crate::shell::execute_and_time;
@ -45,7 +45,7 @@ fn subtract_shell_spawning_time(time: Second, shell_spawning_time: Second) -> Se
/// Run the given shell command and measure the execution time
pub fn time_shell_command(
shell: &str,
shell: &Shell,
command: &Command<'_>,
show_output: bool,
failure_action: CmdFailureAction,
@ -98,7 +98,7 @@ pub fn time_shell_command(
/// Measure the average shell spawning time
pub fn mean_shell_spawning_time(
shell: &str,
shell: &Shell,
style: OutputStyleOption,
show_output: bool,
) -> io::Result<TimingResult> {
@ -168,7 +168,7 @@ pub fn mean_shell_spawning_time(
}
fn run_intermediate_command(
shell: &str,
shell: &Shell,
command: &Option<Command<'_>>,
show_output: bool,
error_output: &'static str,
@ -187,7 +187,7 @@ fn run_intermediate_command(
/// Run the command specified by `--prepare`.
fn run_preparation_command(
shell: &str,
shell: &Shell,
command: &Option<Command<'_>>,
show_output: bool,
) -> io::Result<TimingResult> {
@ -199,7 +199,7 @@ fn run_preparation_command(
/// Run the command specified by `--cleanup`.
fn run_cleanup_command(
shell: &str,
shell: &Shell,
command: &Option<Command<'_>>,
show_output: bool,
) -> io::Result<TimingResult> {

View File

@ -62,6 +62,8 @@ pub enum OptionsError<'a> {
TooManyCommandNames(usize),
UnexpectedCommandNameCount(usize, usize),
NumericParsingError(&'a str, ParseIntError),
EmptyShell,
ShellParseError(shell_words::ParseError),
}
impl<'a> fmt::Display for OptionsError<'a> {
@ -85,6 +87,10 @@ impl<'a> fmt::Display for OptionsError<'a> {
cmd = cmd,
err = err
),
OptionsError::EmptyShell => write!(f, "Empty command at --shell option"),
OptionsError::ShellParseError(ref err) => {
write!(f, "Could not parse --shell value as command line: {}", err)
}
}
}
}

View File

@ -33,7 +33,7 @@ use benchmark_result::BenchmarkResult;
use command::Command;
use error::OptionsError;
use export::{ExportManager, ExportType};
use options::{CmdFailureAction, HyperfineOptions, OutputStyleOption};
use options::{CmdFailureAction, HyperfineOptions, OutputStyleOption, Shell};
use parameter_range::get_parameterized_commands;
use tokenize::tokenize;
use types::ParameterValue;
@ -229,10 +229,9 @@ fn build_hyperfine_options<'a>(
OutputStyleOption::Disabled => {}
};
options.shell = matches
.value_of("shell")
.unwrap_or(&options.shell)
.to_string();
if let Some(shell) = matches.value_of("shell") {
options.shell = Shell::parse(shell)?;
}
if matches.is_present("ignore-failure") {
options.failure_action = CmdFailureAction::Ignore;

View File

@ -1,3 +1,7 @@
use std::fmt;
use std::process::Command;
use crate::error::OptionsError;
use crate::units::{Second, Unit};
#[cfg(not(windows))]
@ -6,6 +10,62 @@ pub const DEFAULT_SHELL: &str = "sh";
#[cfg(windows)]
pub const DEFAULT_SHELL: &str = "cmd.exe";
/// Shell to use for executing benchmarked commands
pub enum Shell {
/// Default shell command
Default(&'static str),
/// Custom shell command specified via --shell
Custom(Vec<String>),
}
impl Default for Shell {
fn default() -> Self {
Shell::Default(DEFAULT_SHELL)
}
}
impl fmt::Display for Shell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Shell::Default(cmd) => write!(f, "{}", cmd),
Shell::Custom(cmdline) => {
let mut first = true;
for s in cmdline.iter() {
if first {
first = false;
write!(f, "{}", s)?;
} else {
write!(f, " {}", s)?;
}
}
Ok(())
}
}
}
}
impl Shell {
/// Parse given string as shell command line
pub fn parse<'a>(s: &str) -> Result<Self, OptionsError<'a>> {
let v = shell_words::split(s.as_ref()).map_err(OptionsError::ShellParseError)?;
if v.is_empty() {
return Err(OptionsError::EmptyShell);
}
Ok(Shell::Custom(v))
}
pub fn command(&self) -> Command {
match self {
Shell::Default(cmd) => Command::new(cmd),
Shell::Custom(cmdline) => {
let mut c = Command::new(&cmdline[0]);
c.args(&cmdline[1..]);
c
}
}
}
}
/// Action to take when an executed command fails.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CmdFailureAction {
@ -74,7 +134,7 @@ pub struct HyperfineOptions {
pub output_style: OutputStyleOption,
/// The shell to use for executing commands.
pub shell: String,
pub shell: Shell,
/// Forward benchmark's stdout to hyperfine's stdout
pub show_output: bool,
@ -99,7 +159,7 @@ impl Default for HyperfineOptions {
preparation_command: None,
cleanup_command: None,
output_style: OutputStyleOption::Full,
shell: DEFAULT_SHELL.to_string(),
shell: Shell::default(),
show_output: false,
time_unit: None,
}

View File

@ -1,6 +1,7 @@
use std::io;
use std::process::{Command, ExitStatus, Stdio};
use std::process::{ExitStatus, Stdio};
use crate::options::Shell;
use crate::timer::get_cpu_timer;
/// Used to indicate the result of running a command
@ -22,7 +23,7 @@ pub fn execute_and_time(
stdout: Stdio,
stderr: Stdio,
command: &str,
shell: &str,
shell: &Shell,
) -> io::Result<ExecuteResult> {
let mut child = run_shell_command(stdout, stderr, command, shell)?;
let cpu_timer = get_cpu_timer(&child);
@ -42,7 +43,7 @@ pub fn execute_and_time(
stdout: Stdio,
stderr: Stdio,
command: &str,
shell: &str,
shell: &Shell,
) -> io::Result<ExecuteResult> {
let cpu_timer = get_cpu_timer();
@ -63,9 +64,10 @@ fn run_shell_command(
stdout: Stdio,
stderr: Stdio,
command: &str,
shell: &str,
shell: &Shell,
) -> io::Result<std::process::ExitStatus> {
Command::new(shell)
shell
.command()
.arg("-c")
.arg(command)
.env(
@ -84,9 +86,10 @@ fn run_shell_command(
stdout: Stdio,
stderr: Stdio,
command: &str,
shell: &str,
shell: &Shell,
) -> io::Result<std::process::Child> {
Command::new(shell)
shell
.command()
.arg("/C")
.arg(command)
.stdin(Stdio::null())