mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-10-28 16:18:57 +00:00
Support list of exit codes in --ignore-failure
This commit is contained in:
1 parent
16348dcf35
commit
b5a6860d96
4 files changed
+137
-25
No files matched your search
+36
-18
@@ -79,23 +79,41 @@ fn run_command_and_measure_common(
|
||||
let result = execute_and_measure(command)
|
||||
.with_context(|| format!("Failed to run command '{command_name}'"))?;
|
||||
|
||||
if command_failure_action == CmdFailureAction::RaiseError && !result.status.success() {
|
||||
let when = match iteration {
|
||||
BenchmarkIteration::NonBenchmarkRun => "a non-benchmark run".to_string(),
|
||||
BenchmarkIteration::Warmup(0) => "the first warmup run".to_string(),
|
||||
BenchmarkIteration::Warmup(i) => format!("warmup iteration {i}"),
|
||||
BenchmarkIteration::Benchmark(0) => "the first benchmark run".to_string(),
|
||||
BenchmarkIteration::Benchmark(i) => format!("benchmark iteration {i}"),
|
||||
};
|
||||
bail!(
|
||||
"{cause} in {when}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \
|
||||
Alternatively, use the '--show-output' option to debug what went wrong.",
|
||||
cause=result.status.code().map_or(
|
||||
"The process has been terminated by a signal".into(),
|
||||
|c| format!("Command terminated with non-zero exit code {c}")
|
||||
if !result.status.success() {
|
||||
use crate::util::exit_code::extract_exit_code;
|
||||
|
||||
),
|
||||
);
|
||||
let should_fail = match command_failure_action {
|
||||
CmdFailureAction::RaiseError => true,
|
||||
CmdFailureAction::IgnoreAllFailures => false,
|
||||
CmdFailureAction::IgnoreSpecificFailures(ref codes) => {
|
||||
// Only fail if the exit code is not in the list of codes to ignore
|
||||
if let Some(exit_code) = extract_exit_code(result.status) {
|
||||
!codes.contains(&exit_code)
|
||||
} else {
|
||||
// If we can't extract an exit code, treat it as a failure
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if should_fail {
|
||||
let when = match iteration {
|
||||
BenchmarkIteration::NonBenchmarkRun => "a non-benchmark run".to_string(),
|
||||
BenchmarkIteration::Warmup(0) => "the first warmup run".to_string(),
|
||||
BenchmarkIteration::Warmup(i) => format!("warmup iteration {i}"),
|
||||
BenchmarkIteration::Benchmark(0) => "the first benchmark run".to_string(),
|
||||
BenchmarkIteration::Benchmark(i) => format!("benchmark iteration {i}"),
|
||||
};
|
||||
bail!(
|
||||
"{cause} in {when}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \
|
||||
Alternatively, use the '--show-output' option to debug what went wrong.",
|
||||
cause=result.status.code().map_or(
|
||||
"The process has been terminated by a signal".into(),
|
||||
|c| format!("Command terminated with non-zero exit code {c}")
|
||||
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
@@ -122,7 +140,7 @@ impl Executor for RawExecutor<'_> {
|
||||
let result = run_command_and_measure_common(
|
||||
command.get_command()?,
|
||||
iteration,
|
||||
command_failure_action.unwrap_or(self.options.command_failure_action),
|
||||
command_failure_action.unwrap_or_else(|| self.options.command_failure_action.clone()),
|
||||
&self.options.command_input_policy,
|
||||
output_policy,
|
||||
&command.get_command_line(),
|
||||
@@ -187,7 +205,7 @@ impl Executor for ShellExecutor<'_> {
|
||||
let mut result = run_command_and_measure_common(
|
||||
command_builder,
|
||||
iteration,
|
||||
command_failure_action.unwrap_or(self.options.command_failure_action),
|
||||
command_failure_action.unwrap_or_else(|| self.options.command_failure_action.clone()),
|
||||
&self.options.command_input_policy,
|
||||
output_policy,
|
||||
&command.get_command_line(),
|
||||
|
||||
+8
-2
@@ -226,9 +226,15 @@ fn build_command() -> Command {
|
||||
.arg(
|
||||
Arg::new("ignore-failure")
|
||||
.long("ignore-failure")
|
||||
.action(ArgAction::SetTrue)
|
||||
.action(ArgAction::Set)
|
||||
.value_name("MODE")
|
||||
.num_args(0..=1)
|
||||
.default_missing_value("all-non-zero")
|
||||
.require_equals(true)
|
||||
.short('i')
|
||||
.help("Ignore non-zero exit codes of the benchmarked programs."),
|
||||
.help("Ignore failures of the benchmarked programs. Without a value or with \
|
||||
'all-non-zero', all non-zero exit codes are ignored. You can also provide \
|
||||
a comma-separated list of exit codes to ignore (e.g., --ignore-failure=1,2)."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("style")
|
||||
|
||||
+21
-5
@@ -67,13 +67,16 @@ impl Shell {
|
||||
}
|
||||
|
||||
/// Action to take when an executed command fails.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CmdFailureAction {
|
||||
/// Exit with an error message
|
||||
RaiseError,
|
||||
|
||||
/// Simply ignore the non-zero exit code
|
||||
Ignore,
|
||||
/// Ignore all non-zero exit codes
|
||||
IgnoreAllFailures,
|
||||
|
||||
/// Ignore specific exit codes
|
||||
IgnoreSpecificFailures(Vec<i32>),
|
||||
}
|
||||
|
||||
/// Output style type option
|
||||
@@ -415,8 +418,21 @@ impl Options {
|
||||
}
|
||||
};
|
||||
|
||||
if matches.get_flag("ignore-failure") {
|
||||
options.command_failure_action = CmdFailureAction::Ignore;
|
||||
if let Some(mode) = matches.get_one::<String>("ignore-failure") {
|
||||
options.command_failure_action = match mode.as_str() {
|
||||
"all-non-zero" | "" => CmdFailureAction::IgnoreAllFailures,
|
||||
codes => {
|
||||
let exit_codes: Result<Vec<i32>, _> = codes
|
||||
.split(',')
|
||||
.map(|s| {
|
||||
s.trim()
|
||||
.parse::<i32>()
|
||||
.map_err(|e| OptionsError::IntParsingError("ignore-failure", e))
|
||||
})
|
||||
.collect();
|
||||
CmdFailureAction::IgnoreSpecificFailures(exit_codes?)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
options.time_unit = match matches.get_one::<String>("time-unit").map(|s| s.as_str()) {
|
||||
|
||||
@@ -277,6 +277,78 @@ fn can_run_failing_commands_with_ignore_failure_option() {
|
||||
.success();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn can_ignore_specific_exit_codes() {
|
||||
// Test that specifying exit code 1 ignores it
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=1")
|
||||
.arg("exit 1")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Test that other exit codes still fail
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=1")
|
||||
.arg("exit 2")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"Command terminated with non-zero exit code 2",
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn can_ignore_multiple_exit_codes() {
|
||||
// Test that all specified exit codes are ignored
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=1,2,3")
|
||||
.arg("exit 1")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=1,2,3")
|
||||
.arg("exit 2")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=1,2,3")
|
||||
.arg("exit 3")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Test that other exit codes still fail
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=1,2,3")
|
||||
.arg("exit 4")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"Command terminated with non-zero exit code 4",
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn ignore_failure_with_all_non_zero() {
|
||||
// Test explicit "all-non-zero" mode
|
||||
hyperfine()
|
||||
.arg("--runs=1")
|
||||
.arg("--ignore-failure=all-non-zero")
|
||||
.arg("exit 5")
|
||||
.assert()
|
||||
.success();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shows_output_of_benchmarked_command() {
|
||||
hyperfine()
|
||||
|
||||
Reference in new issue
Block a user