From c5d3a81fc571172d20896daff9e953054e9e1168 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 5 Oct 2026 17:02:34 +0200 Subject: [PATCH] Include raw commands in JSON exports when names differ --- src/benchmark/benchmark_result.rs | 6 +++- src/benchmark/mod.rs | 7 +++- src/benchmark/relative_speed.rs | 1 + src/export/csv.rs | 2 ++ src/export/json.rs | 2 ++ src/export/tests.rs | 8 +++++ tests/integration_tests.rs | 55 +++++++++++++++++++++++++++++++ 7 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/benchmark/benchmark_result.rs b/src/benchmark/benchmark_result.rs index 1012d31..7e32d15 100644 --- a/src/benchmark/benchmark_result.rs +++ b/src/benchmark/benchmark_result.rs @@ -15,9 +15,13 @@ pub struct Parameter { /// Meta data and performance metrics for a single benchmark #[derive(Debug, Default, Clone, Serialize, PartialEq)] pub struct BenchmarkResult { - /// The full command line of the program that is being benchmarked + /// The display name of the command being benchmarked pub command: String, + /// The expanded command line, when it differs from the display name + #[serde(skip_serializing_if = "Option::is_none")] + pub command_raw: Option, + /// Performance metric measurements and exit codes for each run #[serde(flatten)] pub measurements: Measurements, diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs index c473f9b..5c074b2 100644 --- a/src/benchmark/mod.rs +++ b/src/benchmark/mod.rs @@ -475,8 +475,13 @@ impl<'a> Benchmark<'a> { self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?; + let command = self.command.get_name(); + let command_raw = self.command.get_command_line(); + let command_raw = (command_raw != command).then_some(command_raw); + Ok(BenchmarkResult { - command: self.command.get_name(), + command, + command_raw, measurements, parameters: self .command diff --git a/src/benchmark/relative_speed.rs b/src/benchmark/relative_speed.rs index 2390564..333814c 100644 --- a/src/benchmark/relative_speed.rs +++ b/src/benchmark/relative_speed.rs @@ -158,6 +158,7 @@ fn create_result(name: &str, mean: f64) -> BenchmarkResult { BenchmarkResult { command: name.into(), + command_raw: None, measurements: Measurements { measurements: vec![Measurement { time_wall_clock: Time::new::(mean), diff --git a/src/export/csv.rs b/src/export/csv.rs index 16071ee..31f783f 100644 --- a/src/export/csv.rs +++ b/src/export/csv.rs @@ -81,6 +81,7 @@ fn test_csv() { let results = vec![ BenchmarkResult { command: String::from("command_a"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(7.0), @@ -125,6 +126,7 @@ fn test_csv() { }, BenchmarkResult { command: String::from("command_b"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(17.0), diff --git a/src/export/json.rs b/src/export/json.rs index 2abae92..5d93d02 100644 --- a/src/export/json.rs +++ b/src/export/json.rs @@ -182,6 +182,7 @@ fn test_json_optional_memory() { ] { let result = BenchmarkResult { command: "example".into(), + command_raw: None, measurements: Measurements::new(vec![Measurement { peak_memory_usage: memory, ..Measurement::default() @@ -249,6 +250,7 @@ fn test_json_summaries_for_parameterized_results() { .into_iter() .map(|(size, times)| BenchmarkResult { command: "example".into(), + command_raw: None, parameters: [( "size".into(), Parameter { diff --git a/src/export/tests.rs b/src/export/tests.rs index eeb0804..ab21be3 100644 --- a/src/export/tests.rs +++ b/src/export/tests.rs @@ -28,6 +28,7 @@ fn test_markup_export_auto_ms() { let results = [ BenchmarkResult { command: String::from("sleep 0.1"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(0.09), @@ -55,6 +56,7 @@ fn test_markup_export_auto_ms() { }, BenchmarkResult { command: String::from("sleep 2"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(2.0), @@ -127,6 +129,7 @@ fn test_markup_export_auto_s() { let results = [ BenchmarkResult { command: String::from("sleep 2"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(2.1), @@ -154,6 +157,7 @@ fn test_markup_export_auto_s() { }, BenchmarkResult { command: String::from("sleep 0.1"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(0.1), @@ -226,6 +230,7 @@ fn test_markup_export_manual_ms() { let timing_results = [ BenchmarkResult { command: String::from("sleep 2"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(2.1), @@ -253,6 +258,7 @@ fn test_markup_export_manual_ms() { }, BenchmarkResult { command: String::from("sleep 0.1"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(0.1), @@ -324,6 +330,7 @@ fn test_markup_export_manual_s() { let results = [ BenchmarkResult { command: String::from("sleep 2"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(2.01), @@ -351,6 +358,7 @@ fn test_markup_export_manual_s() { }, BenchmarkResult { command: String::from("sleep 0.1"), + command_raw: None, measurements: Measurements::new(vec![ Measurement { time_wall_clock: Time::new::(0.11), diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index d497a15..58290c7 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -42,6 +42,61 @@ fn one_run_is_supported() { .success(); } +#[test] +fn json_includes_raw_command_only_when_different() { + for (args, expected) in [ + (vec!["sleep 0.01"], vec![("sleep 0.01", None)]), + ( + vec!["--command-name=example", "sleep 0.01"], + vec![("example", Some("sleep 0.01"))], + ), + ( + vec!["--command-name=sleep 0.01", "sleep 0.01"], + vec![("sleep 0.01", None)], + ), + ( + vec![ + "-L", + "delay", + "0.01,0.02", + "--command-name=delay {delay}", + "sleep {delay}", + ], + vec![ + ("delay 0.01", Some("sleep 0.01")), + ("delay 0.02", Some("sleep 0.02")), + ], + ), + ( + vec![ + "-L", + "delay", + "0.01,0.02", + "--command-name=sleep {delay}", + "sleep {delay}", + ], + vec![("sleep 0.01", None), ("sleep 0.02", None)], + ), + ] { + let output = hyperfine_debug() + .args(["--runs=1", "--style=none", "--export-json=-"]) + .args(args) + .output() + .unwrap(); + assert!(output.status.success()); + let export: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + let results = export["results"].as_array().unwrap(); + assert_eq!(results.len(), expected.len()); + for (result, (command, command_raw)) in results.iter().zip(expected) { + assert_eq!(result["command"], command); + assert_eq!( + result.get("command_raw"), + command_raw.map(serde_json::Value::from).as_ref() + ); + } + } +} + #[test] fn json_memory_availability_matches_platform() { let output = hyperfine_raw_command()