From b177870a4ff2a573b5f932612bcb51d2b0ae4cd6 Mon Sep 17 00:00:00 2001 From: alexchenai Date: Thu, 19 Mar 2026 11:24:26 +0000 Subject: [PATCH 1/3] fix: expose HYPERFINE_ITERATION env var to --prepare and --conclude commands Previously, the $HYPERFINE_ITERATION environment variable was only set for the main benchmark command, but not for --prepare or --conclude commands. This made it impossible to use iteration-specific file names in preparation and cleanup steps (e.g. creating and removing per-iteration data files). The fix passes the current BenchmarkIteration through to run_intermediate_command so that --prepare and --conclude commands receive the same HYPERFINE_ITERATION value as the corresponding benchmark command. Setup and cleanup commands (--setup/--cleanup) continue to use NonBenchmarkRun since they run once, not per-iteration. Fixes #781 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/benchmark/executor.rs | 1 + src/benchmark/mod.rs | 47 ++++++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/benchmark/executor.rs b/src/benchmark/executor.rs index 915b735b6..5e56fb9c0 100644 --- a/src/benchmark/executor.rs +++ b/src/benchmark/executor.rs @@ -16,6 +16,7 @@ use super::timing_result::TimingResult; use anyhow::{bail, Context, Result}; use statistical::mean; +#[derive(Clone, Copy)] pub enum BenchmarkIteration { NonBenchmarkRun, Warmup(u64), diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs index e3534a7bc..1bc416931 100644 --- a/src/benchmark/mod.rs +++ b/src/benchmark/mod.rs @@ -59,11 +59,12 @@ impl<'a> Benchmark<'a> { command: &Command<'_>, error_output: &'static str, output_policy: &CommandOutputPolicy, + iteration: executor::BenchmarkIteration, ) -> Result { self.executor .run_command_and_measure( command, - executor::BenchmarkIteration::NonBenchmarkRun, + iteration, Some(CmdFailureAction::RaiseError), output_policy, ) @@ -87,7 +88,14 @@ impl<'a> Benchmark<'a> { Append ' || true' to the command if you are sure that this can be ignored."; Ok(command - .map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy)) + .map(|cmd| { + self.run_intermediate_command( + &cmd, + error_output, + output_policy, + executor::BenchmarkIteration::NonBenchmarkRun, + ) + }) .transpose()? .unwrap_or_default()) } @@ -108,7 +116,14 @@ impl<'a> Benchmark<'a> { Append ' || true' to the command if you are sure that this can be ignored."; Ok(command - .map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy)) + .map(|cmd| { + self.run_intermediate_command( + &cmd, + error_output, + output_policy, + executor::BenchmarkIteration::NonBenchmarkRun, + ) + }) .transpose()? .unwrap_or_default()) } @@ -118,11 +133,12 @@ impl<'a> Benchmark<'a> { &self, command: &Command<'_>, output_policy: &CommandOutputPolicy, + iteration: executor::BenchmarkIteration, ) -> Result { let error_output = "The preparation command terminated with a non-zero exit code. \ Append ' || true' to the command if you are sure that this can be ignored."; - self.run_intermediate_command(command, error_output, output_policy) + self.run_intermediate_command(command, error_output, output_policy, iteration) } /// Run the command specified by `--conclude`. @@ -130,11 +146,12 @@ impl<'a> Benchmark<'a> { &self, command: &Command<'_>, output_policy: &CommandOutputPolicy, + iteration: executor::BenchmarkIteration, ) -> Result { let error_output = "The conclusion command terminated with a non-zero exit code. \ Append ' || true' to the command if you are sure that this can be ignored."; - self.run_intermediate_command(command, error_output, output_policy) + self.run_intermediate_command(command, error_output, output_policy, iteration) } /// Run the benchmark for a single command @@ -170,10 +187,10 @@ impl<'a> Benchmark<'a> { ) }); - let run_preparation_command = || { + let run_preparation_command = |iteration: BenchmarkIteration| { preparation_command .as_ref() - .map(|cmd| self.run_preparation_command(cmd, output_policy)) + .map(|cmd| self.run_preparation_command(cmd, output_policy, iteration)) .transpose() }; @@ -189,10 +206,10 @@ impl<'a> Benchmark<'a> { self.command.get_parameters().iter().cloned(), ) }); - let run_conclusion_command = || { + let run_conclusion_command = |iteration: BenchmarkIteration| { conclusion_command .as_ref() - .map(|cmd| self.run_conclusion_command(cmd, output_policy)) + .map(|cmd| self.run_conclusion_command(cmd, output_policy, iteration)) .transpose() }; @@ -211,14 +228,14 @@ impl<'a> Benchmark<'a> { }; for i in 0..self.options.warmup_count { - let _ = run_preparation_command()?; + let _ = run_preparation_command(BenchmarkIteration::Warmup(i))?; let _ = self.executor.run_command_and_measure( self.command, BenchmarkIteration::Warmup(i), None, output_policy, )?; - let _ = run_conclusion_command()?; + let _ = run_conclusion_command(BenchmarkIteration::Warmup(i))?; if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } @@ -239,7 +256,7 @@ impl<'a> Benchmark<'a> { None }; - let preparation_result = run_preparation_command()?; + let preparation_result = run_preparation_command(BenchmarkIteration::Benchmark(0))?; let preparation_overhead = preparation_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead()); @@ -252,7 +269,7 @@ impl<'a> Benchmark<'a> { )?; let success = status.success(); - let conclusion_result = run_conclusion_command()?; + let conclusion_result = run_conclusion_command(BenchmarkIteration::Benchmark(0))?; let conclusion_overhead = conclusion_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead()); @@ -295,7 +312,7 @@ impl<'a> Benchmark<'a> { // Gather statistics (perform the actual benchmark) for i in 0..count_remaining { - run_preparation_command()?; + run_preparation_command(BenchmarkIteration::Benchmark(i + 1))?; let msg = { let mean = format_duration(mean(×_real), self.options.time_unit); @@ -326,7 +343,7 @@ impl<'a> Benchmark<'a> { bar.inc(1) } - run_conclusion_command()?; + run_conclusion_command(BenchmarkIteration::Benchmark(i + 1))?; } if let Some(bar) = progress_bar.as_ref() { From 063294ab0d84244fd491cf7172a06e33d952d3a8 Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 02:25:46 +0800 Subject: [PATCH 2/3] test: verify HYPERFINE_ITERATION in prepare and conclude Add integration test for #781 and changelog entry. Based on #857 by @alexchenai. Co-authored-by: Cursor --- CHANGELOG.md | 1 + tests/integration_tests.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fda110bee..290018cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Expose `$HYPERFINE_ITERATION` in `--prepare` and `--conclude` commands (matching the benchmark run), see #781 (@alexchenai, @leno23) - Add `--reference-name` option to give a meaningful name to the reference command, see #808 (@niklasdewally) - The `--ignore-failure` option now supports a comma-separated list of exit codes to ignore (e.g., `--ignore-failure=1,2`), see #836 (@sharkdp) - Python scripts: Add `--time-unit` option to `advanced_statistics.py` (@sharkdp) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 2de3a5049..42fbd8716 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -705,6 +705,27 @@ fn speed_comparison_sort_order() { )); } +#[test] +fn hyperfine_iteration_env_in_prepare_and_conclude_commands() { + hyperfine() + .arg("--runs=2") + .arg("--warmup=0") + .arg("--show-output") + .arg("--prepare=echo prep-$HYPERFINE_ITERATION") + .arg("--conclude=echo done-$HYPERFINE_ITERATION") + .arg("echo run-$HYPERFINE_ITERATION") + .assert() + .success() + .stdout( + predicate::str::contains("prep-0") + .and(predicate::str::contains("prep-1")) + .and(predicate::str::contains("run-0")) + .and(predicate::str::contains("run-1")) + .and(predicate::str::contains("done-0")) + .and(predicate::str::contains("done-1")), + ); +} + #[cfg(windows)] #[test] fn windows_quote_args() { From a288e2ea073b748c25950299be7679dc0eb5461a Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 02:30:03 +0800 Subject: [PATCH 3/3] test: gate iteration env test to Unix (cmd.exe does not expand $VAR) Co-authored-by: Cursor --- tests/integration_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 42fbd8716..e694e96a0 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -705,6 +705,7 @@ fn speed_comparison_sort_order() { )); } +#[cfg(unix)] #[test] fn hyperfine_iteration_env_in_prepare_and_conclude_commands() { hyperfine()