diff --git a/src/benchmark/executor.rs b/src/benchmark/executor.rs index ac6577b..e450d40 100644 --- a/src/benchmark/executor.rs +++ b/src/benchmark/executor.rs @@ -15,6 +15,7 @@ use super::measurement::{Measurement, Measurements}; use anyhow::{bail, Context, Result}; +#[derive(Clone, Copy)] pub enum BenchmarkIteration { NonBenchmarkRun, Warmup(u64), diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs index ecf1302..4fc75fb 100644 --- a/src/benchmark/mod.rs +++ b/src/benchmark/mod.rs @@ -58,11 +58,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, ) @@ -85,7 +86,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, + BenchmarkIteration::NonBenchmarkRun, + ) + }) .transpose()? .unwrap_or_default()) } @@ -106,7 +114,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, + BenchmarkIteration::NonBenchmarkRun, + ) + }) .transpose()? .unwrap_or_default()) } @@ -116,11 +131,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`. @@ -128,11 +144,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 @@ -165,10 +182,10 @@ impl<'a> Benchmark<'a> { ) }); - let run_preparation_command = || { + let run_preparation_command = |iteration: executor::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() }; @@ -184,10 +201,10 @@ impl<'a> Benchmark<'a> { self.command.get_parameters().iter().cloned(), ) }); - let run_conclusion_command = || { + let run_conclusion_command = |iteration: executor::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() }; @@ -206,14 +223,15 @@ impl<'a> Benchmark<'a> { }; for i in 0..self.options.warmup_count { - let _ = run_preparation_command()?; + let warmup_iteration = BenchmarkIteration::Warmup(i); + let _ = run_preparation_command(warmup_iteration)?; let _ = self.executor.run_command_and_measure( self.command, - BenchmarkIteration::Warmup(i), + warmup_iteration, None, output_policy, )?; - let _ = run_conclusion_command()?; + let _ = run_conclusion_command(warmup_iteration)?; if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } @@ -234,7 +252,8 @@ impl<'a> Benchmark<'a> { None }; - let preparation_result = run_preparation_command()?; + let benchmark_iteration = BenchmarkIteration::Benchmark(0); + let preparation_result = run_preparation_command(benchmark_iteration)?; let preparation_overhead = preparation_result.map_or(Time::zero(), |res| { res.time_wall_clock + self.executor.time_overhead() }); @@ -242,13 +261,13 @@ impl<'a> Benchmark<'a> { // Initial timing run let res = self.executor.run_command_and_measure( self.command, - BenchmarkIteration::Benchmark(0), + benchmark_iteration, None, output_policy, )?; let success = res.exit_status.success(); - let conclusion_result = run_conclusion_command()?; + let conclusion_result = run_conclusion_command(benchmark_iteration)?; let conclusion_overhead = conclusion_result.map_or(Time::zero(), |res| { res.time_wall_clock + self.executor.time_overhead() }); @@ -289,7 +308,8 @@ impl<'a> Benchmark<'a> { // Gather statistics (perform the actual benchmark) for i in 0..count_remaining { - run_preparation_command()?; + let benchmark_iteration = BenchmarkIteration::Benchmark(i + 1); + run_preparation_command(benchmark_iteration)?; let msg = { let t_wall_clock_mean = measurements.time_wall_clock_mean(); @@ -307,7 +327,7 @@ impl<'a> Benchmark<'a> { let res = self.executor.run_command_and_measure( self.command, - BenchmarkIteration::Benchmark(i + 1), + benchmark_iteration, None, output_policy, )?; @@ -321,7 +341,7 @@ impl<'a> Benchmark<'a> { bar.inc(1) } - run_conclusion_command()?; + run_conclusion_command(benchmark_iteration)?; } if let Some(bar) = progress_bar.as_ref() { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 826bace..e6832b2 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1037,3 +1037,45 @@ fn windows_quote_before_quote_args() { .assert() .success(); } + +#[test] +fn hyperfine_iteration_env_var_in_prepare_and_conclude_commands() { + let tempdir = tempfile::tempdir().unwrap(); + let output_path = tempdir.path().join("iteration output.txt"); + let iteration = if cfg!(windows) { + "%HYPERFINE_ITERATION%" + } else { + "$HYPERFINE_ITERATION" + }; + let command = |phase| format!(r#"echo {phase}:{iteration} >> "iteration output.txt""#); + + hyperfine() + .current_dir(tempdir.path()) + .arg("--runs=2") + .arg("--warmup=1") + .arg("--prepare") + .arg(command("prepare")) + .arg(command("benchmark")) + .arg("--conclude") + .arg(command("conclude")) + .assert() + .success(); + + let contents = std::fs::read_to_string(output_path).unwrap(); + // cmd.exe's echo includes the space before the redirection operator. + let lines: Vec<&str> = contents.lines().map(str::trim_end).collect(); + assert_eq!( + lines, + [ + "prepare:warmup-0", + "benchmark:warmup-0", + "conclude:warmup-0", + "prepare:0", + "benchmark:0", + "conclude:0", + "prepare:1", + "benchmark:1", + "conclude:1", + ] + ); +}