From 01581f60e0530dd7db728e0d0be73334dd143702 Mon Sep 17 00:00:00 2001 From: Jason Peacock Date: Tue, 30 Oct 2018 19:30:17 -0700 Subject: [PATCH] Add option to set the shell, closes #61 Allow the user to set the shell used for executing benchmark commands. The option (-S/--shell) can be overridden, when there are duplicate options the last option's value will be used. --- src/hyperfine/app.rs | 9 +++++++++ src/hyperfine/benchmark.rs | 30 +++++++++++++++++++++--------- src/hyperfine/shell.rs | 18 ++++++++++-------- src/hyperfine/types.rs | 10 ++++++++++ src/main.rs | 8 +++++--- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/hyperfine/app.rs b/src/hyperfine/app.rs index 0b98f2f..c486322 100644 --- a/src/hyperfine/app.rs +++ b/src/hyperfine/app.rs @@ -109,6 +109,15 @@ fn build_app() -> App<'static, 'static> { the colors without any interactive output.", ), ) + .arg( + Arg::with_name("shell") + .long("shell") + .short("S") + .takes_value(true) + .value_name("SHELL") + .overrides_with("shell") + .help("Set the shell to use (default: sh) for executing benchmarked commands."), + ) .arg( Arg::with_name("ignore-failure") .long("ignore-failure") diff --git a/src/hyperfine/benchmark.rs b/src/hyperfine/benchmark.rs index 0ff6e9a..5d7c1fd 100644 --- a/src/hyperfine/benchmark.rs +++ b/src/hyperfine/benchmark.rs @@ -39,6 +39,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, command: &Command, show_output: bool, failure_action: CmdFailureAction, @@ -51,7 +52,7 @@ pub fn time_shell_command( } else { (Stdio::null(), Stdio::null()) }; - let result = execute_and_time(stdout, stderr, &command.get_shell_command())?; + let result = execute_and_time(stdout, stderr, &command.get_shell_command(), shell)?; let mut time_user = result.user_time; let mut time_system = result.system_time; @@ -86,6 +87,7 @@ pub fn time_shell_command( /// Measure the average shell spawning time pub fn mean_shell_spawning_time( + shell: &str, style: &OutputStyleOption, show_output: bool, ) -> io::Result { @@ -99,6 +101,7 @@ pub fn mean_shell_spawning_time( for _ in 0..COUNT { // Just run the shell without any command let res = time_shell_command( + shell, &Command::new(""), show_output, CmdFailureAction::RaiseError, @@ -107,11 +110,17 @@ pub fn mean_shell_spawning_time( match res { Err(_) => { + let shell_cmd = if cfg!(windows) { + format!("{} /C \"\"", shell) + } else { + format!("{} -c \"\"", shell) + }; + return Err(io::Error::new( - io::ErrorKind::Other, - "Could not measure shell execution time. \ - Make sure you can run 'sh -c \"\"'.", - )) + io::ErrorKind::Other, + format!("Could not measure shell execution time. \ + Make sure you can run '{}'.", shell_cmd), + )) } Ok((r, _)) => { times_real.push(r.time_real); @@ -132,11 +141,12 @@ pub fn mean_shell_spawning_time( /// Run the command specified by `--prepare`. fn run_preparation_command( + shell: &str, command: &Option, show_output: bool, ) -> io::Result { if let &Some(ref cmd) = command { - let res = time_shell_command(cmd, show_output, CmdFailureAction::RaiseError, None); + let res = time_shell_command(shell, cmd, show_output, CmdFailureAction::RaiseError, None); if res.is_err() { return Err(io::Error::new( io::ErrorKind::Other, @@ -179,7 +189,7 @@ pub fn run_benchmark( ); for _ in 0..options.warmup_count { - let _ = time_shell_command(cmd, options.show_output, options.failure_action, None)?; + let _ = time_shell_command(&options.shell, cmd, options.show_output, options.failure_action, None)?; progress_bar.inc(1); } progress_bar.finish_and_clear(); @@ -200,10 +210,11 @@ pub fn run_benchmark( Some((param, value)) => Command::new_parametrized(preparation_command, param, value), None => Command::new(preparation_command), }); - let prepare_res = run_preparation_command(&prepare_cmd, options.show_output)?; + let prepare_res = run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?; // Initial timing run let (res, success) = time_shell_command( + &options.shell, cmd, options.show_output, options.failure_action, @@ -240,7 +251,7 @@ pub fn run_benchmark( // Gather statistics for _ in 0..count_remaining { - run_preparation_command(&prepare_cmd, options.show_output)?; + run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?; let msg = { let mean = format_duration(mean(×_real), None); @@ -249,6 +260,7 @@ pub fn run_benchmark( progress_bar.set_message(&msg); let (res, success) = time_shell_command( + &options.shell, cmd, options.show_output, options.failure_action, diff --git a/src/hyperfine/shell.rs b/src/hyperfine/shell.rs index 7389968..d069f17 100644 --- a/src/hyperfine/shell.rs +++ b/src/hyperfine/shell.rs @@ -19,8 +19,8 @@ pub struct ExecuteResult { /// Execute the given command and return a timing summary #[cfg(windows)] -pub fn execute_and_time(stdout: Stdio, stderr: Stdio, command: &str) -> io::Result { - let mut child = run_shell_command(stdout, stderr, command)?; +pub fn execute_and_time(stdout: Stdio, stderr: Stdio, command: &str, shell: &str) -> io::Result { + let mut child = run_shell_command(stdout, stderr, command, shell)?; let cpu_timer = get_cpu_timer(&child); let status = child.wait()?; @@ -34,10 +34,10 @@ pub fn execute_and_time(stdout: Stdio, stderr: Stdio, command: &str) -> io::Resu /// Execute the given command and return a timing summary #[cfg(not(windows))] -pub fn execute_and_time(stdout: Stdio, stderr: Stdio, command: &str) -> io::Result { +pub fn execute_and_time(stdout: Stdio, stderr: Stdio, command: &str, shell: &str) -> io::Result { let cpu_timer = get_cpu_timer(); - let status = run_shell_command(stdout, stderr, command)?; + let status = run_shell_command(stdout, stderr, command, shell)?; let (user_time, system_time) = cpu_timer.stop(); @@ -48,14 +48,15 @@ pub fn execute_and_time(stdout: Stdio, stderr: Stdio, command: &str) -> io::Resu }) } -/// Run a standard shell command +/// Run a standard shell command using `sh -c` #[cfg(not(windows))] fn run_shell_command( stdout: Stdio, stderr: Stdio, command: &str, + shell: &str, ) -> io::Result { - Command::new("sh") + Command::new(shell) .arg("-c") .arg(command) .stdin(Stdio::null()) @@ -64,14 +65,15 @@ fn run_shell_command( .status() } -/// Run a Windows shell command using cmd.exe +/// Run a Windows shell command using `cmd.exe /C` #[cfg(windows)] fn run_shell_command( stdout: Stdio, stderr: Stdio, command: &str, + shell: &str, ) -> io::Result { - Command::new("cmd.exe") + Command::new(shell) .arg("/C") .arg(command) .stdin(Stdio::null()) diff --git a/src/hyperfine/types.rs b/src/hyperfine/types.rs index 2092ff9..1754ea9 100644 --- a/src/hyperfine/types.rs +++ b/src/hyperfine/types.rs @@ -1,6 +1,12 @@ /// This module contains common internal types. use std::fmt; +#[cfg(not(windows))] +pub const DEFAULT_SHELL: &str = "sh"; + +#[cfg(windows)] +pub const DEFAULT_SHELL: &str = "cmd.exe"; + /// Type alias for unit of time pub type Second = f64; @@ -111,6 +117,9 @@ pub struct HyperfineOptions { /// What color mode to use for output pub output_style: OutputStyleOption, + /// The shell to use for executing commands. + pub shell: String, + /// Forward benchmark's stdout to hyperfine's stdout pub show_output: bool, } @@ -124,6 +133,7 @@ impl Default for HyperfineOptions { failure_action: CmdFailureAction::RaiseError, preparation_command: None, output_style: OutputStyleOption::Full, + shell: DEFAULT_SHELL.to_string(), show_output: false, } } diff --git a/src/main.rs b/src/main.rs index 72cf6dc..e9412a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,7 @@ pub fn error(message: &str) -> ! { /// Runs the benchmark for the given commands fn run(commands: &Vec, options: &HyperfineOptions) -> io::Result> { - let shell_spawning_time = mean_shell_spawning_time(&options.output_style, options.show_output)?; + let shell_spawning_time = mean_shell_spawning_time(&options.shell, &options.output_style, options.show_output)?; let mut timing_results = vec![]; @@ -123,7 +123,7 @@ fn build_hyperfine_options(matches: &ArgMatches) -> Result Result {} - } + }; + + options.shell = matches.value_of("shell").unwrap_or(&options.shell).to_string(); if matches.is_present("ignore-failure") { options.failure_action = CmdFailureAction::Ignore;