Parse --shell option value as shell command

This commit is contained in:
rhysd authored and David Peter committed 2021-10-17 17:03:36 +02:00
1 parent 5c6879e4c1
commit e2fdaceb9f
7 files changed
+96 -20

No files matched your search

Generated
+7
View File
@@ -243,6 +243,7 @@ dependencies = [
"rust_decimal", "rust_decimal",
"serde", "serde",
"serde_json", "serde_json",
"shell-words",
"statistical", "statistical",
"tempfile", "tempfile",
"winapi", "winapi",
@@ -680,6 +681,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shell-words"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
[[package]] [[package]]
name = "statistical" name = "statistical"
version = "1.0.0" version = "1.0.0"
+1
View File
@@ -21,6 +21,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
rust_decimal = "1.16" rust_decimal = "1.16"
rand = "0.8" rand = "0.8"
shell-words = "1.0"
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
libc = "0.2" libc = "0.2"
+6 -6
View File
@@ -9,7 +9,7 @@ use crate::benchmark_result::BenchmarkResult;
use crate::command::Command; use crate::command::Command;
use crate::format::{format_duration, format_duration_unit}; use crate::format::{format_duration, format_duration_unit};
use crate::min_max::{max, min}; 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::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};
use crate::progress_bar::get_progress_bar; use crate::progress_bar::get_progress_bar;
use crate::shell::execute_and_time; 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 /// Run the given shell command and measure the execution time
pub fn time_shell_command( pub fn time_shell_command(
shell: &str, shell: &Shell,
command: &Command<'_>, command: &Command<'_>,
show_output: bool, show_output: bool,
failure_action: CmdFailureAction, failure_action: CmdFailureAction,
@@ -98,7 +98,7 @@ pub fn time_shell_command(
/// Measure the average shell spawning time /// Measure the average shell spawning time
pub fn mean_shell_spawning_time( pub fn mean_shell_spawning_time(
shell: &str, shell: &Shell,
style: OutputStyleOption, style: OutputStyleOption,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> io::Result<TimingResult> {
@@ -168,7 +168,7 @@ pub fn mean_shell_spawning_time(
} }
fn run_intermediate_command( fn run_intermediate_command(
shell: &str, shell: &Shell,
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
error_output: &'static str, error_output: &'static str,
@@ -187,7 +187,7 @@ fn run_intermediate_command(
/// Run the command specified by `--prepare`. /// Run the command specified by `--prepare`.
fn run_preparation_command( fn run_preparation_command(
shell: &str, shell: &Shell,
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> io::Result<TimingResult> {
@@ -199,7 +199,7 @@ fn run_preparation_command(
/// Run the command specified by `--cleanup`. /// Run the command specified by `--cleanup`.
fn run_cleanup_command( fn run_cleanup_command(
shell: &str, shell: &Shell,
command: &Option<Command<'_>>, command: &Option<Command<'_>>,
show_output: bool, show_output: bool,
) -> io::Result<TimingResult> { ) -> io::Result<TimingResult> {
+6
View File
@@ -62,6 +62,8 @@ pub enum OptionsError<'a> {
TooManyCommandNames(usize), TooManyCommandNames(usize),
UnexpectedCommandNameCount(usize, usize), UnexpectedCommandNameCount(usize, usize),
NumericParsingError(&'a str, ParseIntError), NumericParsingError(&'a str, ParseIntError),
EmptyShell,
ShellParseError(shell_words::ParseError),
} }
impl<'a> fmt::Display for OptionsError<'a> { impl<'a> fmt::Display for OptionsError<'a> {
@@ -85,6 +87,10 @@ impl<'a> fmt::Display for OptionsError<'a> {
cmd = cmd, cmd = cmd,
err = err 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)
}
} }
} }
} }
+4 -5
View File
@@ -33,7 +33,7 @@ use benchmark_result::BenchmarkResult;
use command::Command; use command::Command;
use error::OptionsError; use error::OptionsError;
use export::{ExportManager, ExportType}; use export::{ExportManager, ExportType};
use options::{CmdFailureAction, HyperfineOptions, OutputStyleOption}; use options::{CmdFailureAction, HyperfineOptions, OutputStyleOption, Shell};
use parameter_range::get_parameterized_commands; use parameter_range::get_parameterized_commands;
use tokenize::tokenize; use tokenize::tokenize;
use types::ParameterValue; use types::ParameterValue;
@@ -229,10 +229,9 @@ fn build_hyperfine_options<'a>(
OutputStyleOption::Disabled => {} OutputStyleOption::Disabled => {}
}; };
options.shell = matches if let Some(shell) = matches.value_of("shell") {
.value_of("shell") options.shell = Shell::parse(shell)?;
.unwrap_or(&options.shell) }
.to_string();
if matches.is_present("ignore-failure") { if matches.is_present("ignore-failure") {
options.failure_action = CmdFailureAction::Ignore; options.failure_action = CmdFailureAction::Ignore;
+62 -2
View File
@@ -1,3 +1,7 @@
use std::fmt;
use std::process::Command;
use crate::error::OptionsError;
use crate::units::{Second, Unit}; use crate::units::{Second, Unit};
#[cfg(not(windows))] #[cfg(not(windows))]
@@ -6,6 +10,62 @@ pub const DEFAULT_SHELL: &str = "sh";
#[cfg(windows)] #[cfg(windows)]
pub const DEFAULT_SHELL: &str = "cmd.exe"; 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. /// Action to take when an executed command fails.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum CmdFailureAction { pub enum CmdFailureAction {
@@ -74,7 +134,7 @@ pub struct HyperfineOptions {
pub output_style: OutputStyleOption, pub output_style: OutputStyleOption,
/// The shell to use for executing commands. /// The shell to use for executing commands.
pub shell: String, pub shell: Shell,
/// Forward benchmark's stdout to hyperfine's stdout /// Forward benchmark's stdout to hyperfine's stdout
pub show_output: bool, pub show_output: bool,
@@ -99,7 +159,7 @@ impl Default for HyperfineOptions {
preparation_command: None, preparation_command: None,
cleanup_command: None, cleanup_command: None,
output_style: OutputStyleOption::Full, output_style: OutputStyleOption::Full,
shell: DEFAULT_SHELL.to_string(), shell: Shell::default(),
show_output: false, show_output: false,
time_unit: None, time_unit: None,
} }
+10 -7
View File
@@ -1,6 +1,7 @@
use std::io; use std::io;
use std::process::{Command, ExitStatus, Stdio}; use std::process::{ExitStatus, Stdio};
use crate::options::Shell;
use crate::timer::get_cpu_timer; use crate::timer::get_cpu_timer;
/// Used to indicate the result of running a command /// Used to indicate the result of running a command
@@ -22,7 +23,7 @@ pub fn execute_and_time(
stdout: Stdio, stdout: Stdio,
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &str, shell: &Shell,
) -> io::Result<ExecuteResult> { ) -> io::Result<ExecuteResult> {
let mut child = run_shell_command(stdout, stderr, command, shell)?; let mut child = run_shell_command(stdout, stderr, command, shell)?;
let cpu_timer = get_cpu_timer(&child); let cpu_timer = get_cpu_timer(&child);
@@ -42,7 +43,7 @@ pub fn execute_and_time(
stdout: Stdio, stdout: Stdio,
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &str, shell: &Shell,
) -> io::Result<ExecuteResult> { ) -> io::Result<ExecuteResult> {
let cpu_timer = get_cpu_timer(); let cpu_timer = get_cpu_timer();
@@ -63,9 +64,10 @@ fn run_shell_command(
stdout: Stdio, stdout: Stdio,
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &str, shell: &Shell,
) -> io::Result<std::process::ExitStatus> { ) -> io::Result<std::process::ExitStatus> {
Command::new(shell) shell
.command()
.arg("-c") .arg("-c")
.arg(command) .arg(command)
.env( .env(
@@ -84,9 +86,10 @@ fn run_shell_command(
stdout: Stdio, stdout: Stdio,
stderr: Stdio, stderr: Stdio,
command: &str, command: &str,
shell: &str, shell: &Shell,
) -> io::Result<std::process::Child> { ) -> io::Result<std::process::Child> {
Command::new(shell) shell
.command()
.arg("/C") .arg("/C")
.arg(command) .arg(command)
.stdin(Stdio::null()) .stdin(Stdio::null())