Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement shell bypassing #429

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ fn build_app() -> App<'static> {
.overrides_with("shell")
.help("Set the shell to use for executing benchmarked commands."),
)
.arg(
Arg::new("no-shell")
.long("no-shell")
.help("Execute benchmarked commands without a shell."),
)
.arg(
Arg::new("ignore-failure")
.long("ignore-failure")
Expand Down
8 changes: 8 additions & 0 deletions src/benchmark/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ pub fn mean_shell_spawning_time(
style: OutputStyleOption,
show_output: bool,
) -> Result<TimingResult> {
if let Shell::None = shell {
return Ok(TimingResult {
time_real: 0.0,
time_user: 0.0,
time_system: 0.0,
});
}

const COUNT: u64 = 50;
let progress_bar = if style != OutputStyleOption::Disabled {
Some(get_progress_bar(
Expand Down
17 changes: 12 additions & 5 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum Shell {
Default(&'static str),
/// Custom shell command specified via --shell
Custom(Vec<String>),
/// No shell, each command is parsed (via `shell-words`) and directly executed
None,
}

impl Default for Shell {
Expand All @@ -37,6 +39,7 @@ impl fmt::Display for Shell {
match self {
Shell::Default(cmd) => write!(f, "{}", cmd),
Shell::Custom(cmdline) => write!(f, "{}", shell_words::join(cmdline)),
Shell::None => write!(f, "[none]"),
}
}
}
Expand All @@ -51,14 +54,15 @@ impl Shell {
Ok(Shell::Custom(v))
}

pub fn command(&self) -> Command {
pub fn command(&self) -> Option<Command> {
match self {
Shell::Default(cmd) => Command::new(cmd),
Shell::Default(cmd) => Some(Command::new(cmd)),
Shell::Custom(cmdline) => {
let mut c = Command::new(&cmdline[0]);
c.args(&cmdline[1..]);
c
Some(c)
}
Shell::None => None,
}
}
}
Expand Down Expand Up @@ -247,6 +251,9 @@ impl Options {
if let Some(shell) = matches.value_of("shell") {
options.shell = Shell::parse(shell)?;
}
if matches.is_present("no-shell") {
options.shell = Shell::None;
}

if matches.is_present("ignore-failure") {
options.failure_action = CmdFailureAction::Ignore;
Expand Down Expand Up @@ -281,7 +288,7 @@ fn test_shell_default_command() {
let s = format!("{}", shell);
assert_eq!(&s, DEFAULT_SHELL);

let cmd = shell.command();
let cmd = shell.command().unwrap();
// Command::get_program is not yet available in stable channel.
// https://doc.rust-lang.org/std/process/struct.Command.html#method.get_program
let s = format!("{:?}", cmd);
Expand All @@ -295,7 +302,7 @@ fn test_shell_parse_command() {
let s = format!("{}", shell);
assert_eq!(&s, "shell -x 'aaa bbb'");

let cmd = shell.command();
let cmd = shell.command().unwrap();
assert_eq!(cmd.get_program().to_string_lossy(), "shell");
assert_eq!(
cmd.get_args()
Expand Down
39 changes: 29 additions & 10 deletions src/shell.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::process::{ExitStatus, Stdio};
use std::process::{Command, ExitStatus, Stdio};

use crate::options::Shell;
use crate::timer::get_cpu_timer;

use anyhow::{Context, Result};
use anyhow::{Context, Error, Result};

/// Used to indicate the result of running a command
#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -67,10 +67,7 @@ fn run_shell_command(
command: &str,
shell: &Shell,
) -> Result<std::process::ExitStatus> {
shell
.command()
.arg("-c")
.arg(command)
prepare_shell_command(command, shell, &["-c"])?
.env(
"HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET",
"X".repeat(rand::random::<usize>() % 4096usize),
Expand All @@ -90,13 +87,35 @@ fn run_shell_command(
command: &str,
shell: &Shell,
) -> Result<std::process::Child> {
shell
.command()
.arg("/C")
.arg(command)
prepare_shell_command(command, shell, &["/C"])?
.stdin(Stdio::null())
.stdout(stdout)
.stderr(stderr)
.spawn()
.with_context(|| format!("Failed to run command '{}'", command))
}

fn prepare_shell_command(
command: &str,
shell: &Shell,
shell_extra_args: &[&str],
) -> Result<std::process::Command> {
match shell.command() {
Some(mut shell_command) => {
shell_command.args(shell_extra_args).arg(command);
Ok(shell_command)
}
None => {
let mut tokens = shell_words::split(command)
.with_context(|| format!("Failed to parse command '{}", command))?
.into_iter();
if let Some(exec) = tokens.next() {
let mut shell_command = Command::new(exec);
shell_command.args(tokens);
Ok(shell_command)
} else {
Err(Error::msg("Failed to parse empty command"))
}
}
}
}