mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-03 09:23:00 +00:00
Use a shell for lifecycle commands and improve hook errors
This commit is contained in:
1 parent
63209515a3
commit
445b6130f7
6 files changed
+61
-68
No files matched your search
@@ -108,7 +108,7 @@ hyperfine -S 'my-command > output-${HYPERFINE_ITERATION}.log'
|
|||||||
|
|
||||||
### Intermediate shell
|
### Intermediate shell
|
||||||
|
|
||||||
By default, commands are executed directly, without an intermediate shell (`--shell=none`).
|
By default, benchmarked commands, including warmup runs, are executed directly, without an intermediate shell (`--shell=none`).
|
||||||
Arguments are split using shell-like quoting, so quoted arguments containing spaces are supported.
|
Arguments are split using shell-like quoting, so quoted arguments containing spaces are supported.
|
||||||
Shell syntax such as pipes, redirections, environment-variable expansion, `*`, and `~` is not interpreted.
|
Shell syntax such as pipes, redirections, environment-variable expansion, `*`, and `~` is not interpreted.
|
||||||
This avoids shell startup overhead and the noise from correcting for it, especially for fast commands
|
This avoids shell startup overhead and the noise from correcting for it, especially for fast commands
|
||||||
@@ -125,10 +125,11 @@ You can also select a specific shell with `--shell <SHELL>`:
|
|||||||
hyperfine --shell zsh 'for i in {1..10000}; do echo test; done'
|
hyperfine --shell zsh 'for i in {1..10000}; do echo test; done'
|
||||||
```
|
```
|
||||||
|
|
||||||
The shell setting applies to all commands, including `--setup`, `--prepare`, `--conclude`, and `--cleanup`.
|
The `--setup`, `--prepare`, `--conclude`, and `--cleanup` commands always run through a shell.
|
||||||
If any of these commands need shell syntax, enable a shell explicitly.
|
They use the shell selected by `--shell`, or the platform shell (`sh` on Unix, `cmd.exe` on Windows)
|
||||||
|
when `--shell` is omitted or set to `none`.
|
||||||
|
|
||||||
When a shell is enabled, hyperfine *corrects for the shell spawning time*. It runs the shell with an
|
When benchmarked commands use a shell, hyperfine *corrects for the shell spawning time*. It runs the shell with an
|
||||||
empty command multiple times to measure its startup time, then subtracts this time from each
|
empty command multiple times to measure its startup time, then subtracts this time from each
|
||||||
time measurement.
|
time measurement.
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -194,14 +194,16 @@ possible parameter combinations.
|
|||||||
.HP
|
.HP
|
||||||
\fB\-\-shell\fR \fISHELL\fP
|
\fB\-\-shell\fR \fISHELL\fP
|
||||||
.IP
|
.IP
|
||||||
Set the shell to use for executing commands (default: none). This can be
|
Set the shell to use for executing benchmarked commands, including warmup runs
|
||||||
the name or the path to the shell executable, or a full command line
|
(default: none). This can be the name or the path to the shell executable,
|
||||||
|
or a full command line
|
||||||
like "bash \fB\-\-norc\fR". It can also be set to "default" to explicitly
|
like "bash \fB\-\-norc\fR". It can also be set to "default" to explicitly
|
||||||
select the platform shell (sh on Unix, cmd.exe on Windows). Finally, this can also be
|
select the platform shell (sh on Unix, cmd.exe on Windows). Finally, this can also be
|
||||||
set to "none" to disable the shell. In this case, commands will be
|
set to "none" to disable the shell. In this case, benchmarked commands will be
|
||||||
executed directly. They can still have arguments, but more complex
|
executed directly. They can still have arguments, but more complex
|
||||||
things like "sleep 0.1; sleep 0.2" are not possible without a shell.
|
things like "sleep 0.1; sleep 0.2" are not possible without a shell.
|
||||||
This option also applies to setup, prepare, conclude, and cleanup commands.
|
Setup, prepare, conclude, and cleanup commands always use a shell: the selected
|
||||||
|
shell, or the platform shell when this option is omitted or set to "none".
|
||||||
.HP
|
.HP
|
||||||
\fB\-S\fR
|
\fB\-S\fR
|
||||||
.IP
|
.IP
|
||||||
|
|||||||
@@ -40,6 +40,21 @@ pub trait Executor {
|
|||||||
output_policy: &CommandOutputPolicy,
|
output_policy: &CommandOutputPolicy,
|
||||||
) -> Result<Measurement>;
|
) -> Result<Measurement>;
|
||||||
|
|
||||||
|
/// Run a lifecycle command through a shell, always treating failures as errors.
|
||||||
|
fn run_intermediate_command_and_measure(
|
||||||
|
&self,
|
||||||
|
command: &Command<'_>,
|
||||||
|
iteration: BenchmarkIteration,
|
||||||
|
output_policy: &CommandOutputPolicy,
|
||||||
|
) -> Result<Measurement> {
|
||||||
|
self.run_command_and_measure(
|
||||||
|
command,
|
||||||
|
iteration,
|
||||||
|
Some(CmdFailureAction::RaiseError),
|
||||||
|
output_policy,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a calibration of this executor. For example,
|
/// Perform a calibration of this executor. For example,
|
||||||
/// when running commands through a shell, we need to
|
/// when running commands through a shell, we need to
|
||||||
/// measure the shell spawning time separately in order
|
/// measure the shell spawning time separately in order
|
||||||
@@ -153,6 +168,18 @@ impl Executor for RawExecutor<'_> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_intermediate_command_and_measure(
|
||||||
|
&self,
|
||||||
|
command: &Command<'_>,
|
||||||
|
iteration: BenchmarkIteration,
|
||||||
|
output_policy: &CommandOutputPolicy,
|
||||||
|
) -> Result<Measurement> {
|
||||||
|
// Keep shell startup in the hook's duration: raw benchmarks have no
|
||||||
|
// calibrated shell overhead to add back when estimating the run count.
|
||||||
|
ShellExecutor::new(&Shell::platform_default(), self.options)
|
||||||
|
.run_intermediate_command_and_measure(command, iteration, output_policy)
|
||||||
|
}
|
||||||
|
|
||||||
fn calibrate(&mut self) -> Result<()> {
|
fn calibrate(&mut self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-18
@@ -11,9 +11,7 @@ use crate::benchmark::benchmark_result::Parameter;
|
|||||||
use crate::benchmark::executor::BenchmarkIteration;
|
use crate::benchmark::executor::BenchmarkIteration;
|
||||||
use crate::benchmark::measurement::{Measurement, Measurements};
|
use crate::benchmark::measurement::{Measurement, Measurements};
|
||||||
use crate::command::Command;
|
use crate::command::Command;
|
||||||
use crate::options::{
|
use crate::options::{CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption};
|
||||||
CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,
|
|
||||||
};
|
|
||||||
use crate::outlier_detection::OUTLIER_THRESHOLD;
|
use crate::outlier_detection::OUTLIER_THRESHOLD;
|
||||||
use crate::output::console_writeln;
|
use crate::output::console_writeln;
|
||||||
use crate::output::progress_bar::{
|
use crate::output::progress_bar::{
|
||||||
@@ -25,7 +23,7 @@ use crate::parameter::ParameterNameAndValue;
|
|||||||
use crate::quantity::{self, const_time_from_seconds, Time, Zero};
|
use crate::quantity::{self, const_time_from_seconds, Time, Zero};
|
||||||
use benchmark_result::BenchmarkResult;
|
use benchmark_result::BenchmarkResult;
|
||||||
|
|
||||||
use anyhow::{ensure, Context, Result};
|
use anyhow::{anyhow, ensure, Result};
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
|
||||||
use self::executor::Executor;
|
use self::executor::Executor;
|
||||||
@@ -64,22 +62,14 @@ impl<'a> Benchmark<'a> {
|
|||||||
iteration: executor::BenchmarkIteration,
|
iteration: executor::BenchmarkIteration,
|
||||||
) -> Result<Measurement> {
|
) -> Result<Measurement> {
|
||||||
self.executor
|
self.executor
|
||||||
.run_command_and_measure(
|
.run_intermediate_command_and_measure(command, iteration, output_policy)
|
||||||
command,
|
.map_err(|error| {
|
||||||
iteration,
|
let hint = if cfg!(windows) {
|
||||||
Some(CmdFailureAction::RaiseError),
|
"Append ' || exit /b 0' to the command if this failure can be ignored."
|
||||||
output_policy,
|
|
||||||
)
|
|
||||||
.with_context(|| {
|
|
||||||
let ignore_failure = if cfg!(windows) {
|
|
||||||
" || exit /b 0"
|
|
||||||
} else {
|
} else {
|
||||||
" || true"
|
"Append ' || true' to the command if this failure can be ignored."
|
||||||
};
|
};
|
||||||
format!(
|
anyhow!("The {kind} command failed: {error:#} {hint}")
|
||||||
"The {kind} command failed. If this failure can be ignored, \
|
|
||||||
use '-S'/'--shell=default' and append '{ignore_failure}' to the command"
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -236,14 +236,15 @@ fn build_command() -> Command {
|
|||||||
.value_name("SHELL")
|
.value_name("SHELL")
|
||||||
.overrides_with("shell")
|
.overrides_with("shell")
|
||||||
.value_hint(ValueHint::CommandString)
|
.value_hint(ValueHint::CommandString)
|
||||||
.help("Set the shell to use for executing commands (default: none). This can be the \
|
.help("Set the shell to use for executing benchmarked commands, including warmup runs \
|
||||||
name or the path to the shell executable, or a full command line \
|
(default: none). This can be the name or the path to the shell executable, or a full command line \
|
||||||
like \"bash --norc\". It can also be set to \"default\" to explicitly select \
|
like \"bash --norc\". It can also be set to \"default\" to explicitly select \
|
||||||
the platform shell (sh on Unix, cmd.exe on Windows). It can also be set to \
|
the platform shell (sh on Unix, cmd.exe on Windows). It can also be set to \
|
||||||
\"none\" to disable the shell. In this case, commands will be executed \
|
\"none\" to disable the shell. In this case, benchmarked commands will be executed \
|
||||||
directly. They can still have arguments, but more complex things like \
|
directly. They can still have arguments, but more complex things like \
|
||||||
\"sleep 0.1; sleep 0.2\" are not possible without a shell. This option also \
|
\"sleep 0.1; sleep 0.2\" are not possible without a shell. Setup, prepare, conclude, \
|
||||||
applies to setup, prepare, conclude, and cleanup commands.")
|
and cleanup commands always use a shell: the selected shell, or the platform \
|
||||||
|
shell when this option is omitted or set to \"none\".")
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("default-shell")
|
Arg::new("default-shell")
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ fn fails_for_unknown_setup_command() {
|
|||||||
.arg("echo test")
|
.arg("echo test")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(predicate::str::contains("The setup command failed."));
|
.stderr(predicate::str::contains("The setup command failed:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -330,7 +330,7 @@ fn fails_for_unknown_cleanup_command() {
|
|||||||
.arg("echo test")
|
.arg("echo test")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(predicate::str::contains("The cleanup command failed."));
|
.stderr(predicate::str::contains("The cleanup command failed:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -341,7 +341,7 @@ fn fails_for_unknown_prepare_command() {
|
|||||||
.arg("echo test")
|
.arg("echo test")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(predicate::str::contains("The preparation command failed."));
|
.stderr(predicate::str::contains("The preparation command failed:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -352,40 +352,7 @@ fn fails_for_unknown_conclude_command() {
|
|||||||
.arg("echo test")
|
.arg("echo test")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(predicate::str::contains("The conclusion command failed."));
|
.stderr(predicate::str::contains("The conclusion command failed:"));
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[test]
|
|
||||||
fn hook_errors_preserve_causes_and_explain_shell_syntax() {
|
|
||||||
for hook in ["--setup", "--prepare", "--conclude", "--cleanup"] {
|
|
||||||
for (command, cause) in [
|
|
||||||
(
|
|
||||||
"some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577",
|
|
||||||
"No such file or directory",
|
|
||||||
),
|
|
||||||
("printf 'unclosed", "missing closing quote"),
|
|
||||||
("false", "terminated with non-zero exit code 1"),
|
|
||||||
] {
|
|
||||||
let assert = hyperfine()
|
|
||||||
.args(["--runs=1", hook, command, "true"])
|
|
||||||
.assert()
|
|
||||||
.failure()
|
|
||||||
.stderr(predicate::str::contains(command))
|
|
||||||
.stderr(predicate::str::contains(cause))
|
|
||||||
.stderr(predicate::str::contains("'-S'/'--shell=default'"))
|
|
||||||
.stderr(predicate::str::contains("append ' || true'"))
|
|
||||||
.stderr(predicate::str::contains("--ignore-failure").not());
|
|
||||||
if command != "false" {
|
|
||||||
assert.stderr(predicate::str::contains("non-zero exit code").not());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hyperfine()
|
|
||||||
.args(["-S", "--runs=1", hook, "false || true", "true"])
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -505,9 +472,14 @@ fn runs_commands_using_user_defined_shell() {
|
|||||||
.arg("--show-output")
|
.arg("--show-output")
|
||||||
.arg("--shell")
|
.arg("--shell")
|
||||||
.arg("echo 'custom_shell' '--shell-arg'")
|
.arg("echo 'custom_shell' '--shell-arg'")
|
||||||
|
.arg("--setup=echo setup")
|
||||||
|
.arg("--prepare=echo prepare")
|
||||||
|
.arg("--conclude=echo conclude")
|
||||||
|
.arg("--cleanup=echo cleanup")
|
||||||
.arg("echo benchmark")
|
.arg("echo benchmark")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
.stdout(predicate::str::contains("custom_shell --shell-arg").count(5))
|
||||||
.stdout(
|
.stdout(
|
||||||
predicate::str::contains("custom_shell --shell-arg -c echo benchmark").or(
|
predicate::str::contains("custom_shell --shell-arg -c echo benchmark").or(
|
||||||
predicate::str::contains("custom_shell --shell-arg /C echo benchmark"),
|
predicate::str::contains("custom_shell --shell-arg /C echo benchmark"),
|
||||||
|
|||||||
Reference in new issue
Block a user