From 74da3b20b3fa15eb3c829e67f7a014b018d2c09a Mon Sep 17 00:00:00 2001 From: not-matthias Date: Fri, 13 Mar 2026 19:30:05 +0100 Subject: [PATCH] fix(criterion-compat): fix URI formatting when criterion_group!/criterion_main! are bypassed --- crates/criterion_compat/Cargo.toml | 4 ++++ .../benches/repro_custom_main.rs | 23 +++++++++++++++++++ .../criterion_compat/src/compat/criterion.rs | 23 +++++++++++++++++++ crates/criterion_compat/src/compat/group.rs | 22 ++++++++++++------ 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 crates/criterion_compat/benches/repro_custom_main.rs diff --git a/crates/criterion_compat/Cargo.toml b/crates/criterion_compat/Cargo.toml index 0044814c..b6ff74fa 100644 --- a/crates/criterion_compat/Cargo.toml +++ b/crates/criterion_compat/Cargo.toml @@ -56,3 +56,7 @@ harness = false [[bench]] name = "test_benches" harness = false + +[[bench]] +name = "repro_custom_main" +harness = false diff --git a/crates/criterion_compat/benches/repro_custom_main.rs b/crates/criterion_compat/benches/repro_custom_main.rs new file mode 100644 index 00000000..aaa580ca --- /dev/null +++ b/crates/criterion_compat/benches/repro_custom_main.rs @@ -0,0 +1,23 @@ +/// Reproducer for COD-2324: URI formatting issues when users bypass criterion_group!/criterion_main! +/// and use a custom main function (like the rtmalloc project does). +use codspeed_criterion_compat::{criterion_group, BenchmarkId, Criterion}; + +fn bench_with_group(c: &mut Criterion) { + let mut group = c.benchmark_group("my_group"); + group.bench_function("my_bench", |b| b.iter(|| 2 + 2)); + group.bench_function(BenchmarkId::new("parameterized", 42), |b| b.iter(|| 2 + 2)); + group.finish(); +} + +// Scenario 1: criterion_group! defined but NOT used — custom main calls bench functions directly +criterion_group!(benches, bench_with_group); + +fn main() { + // Pattern A: Using new_instrumented() but calling bench functions directly (not through criterion_group!) + let mut criterion = Criterion::new_instrumented(); + bench_with_group(&mut criterion); + + // Pattern B: Calling through criterion_group!-generated function (should work correctly) + let mut criterion2 = Criterion::new_instrumented(); + benches(&mut criterion2); +} diff --git a/crates/criterion_compat/src/compat/criterion.rs b/crates/criterion_compat/src/compat/criterion.rs index a8b85c1a..b2772572 100644 --- a/crates/criterion_compat/src/compat/criterion.rs +++ b/crates/criterion_compat/src/compat/criterion.rs @@ -92,19 +92,23 @@ impl Criterion { self.macro_group = macro_group.into(); } + #[track_caller] pub fn bench_function(&mut self, id: &str, f: F) -> &mut Criterion where F: FnMut(&mut Bencher), { + self.fill_missing_file_from_caller(); self.benchmark_group(id) .bench_function(BenchmarkId::no_function(), f); self } + #[track_caller] pub fn bench_with_input(&mut self, id: BenchmarkId, input: &I, f: F) -> &mut Criterion where F: FnMut(&mut Bencher, &I), { + self.fill_missing_file_from_caller(); let group_name = id.function_name.expect( "Cannot use BenchmarkId::from_parameter with Criterion::bench_with_input. \ Consider using a BenchmarkGroup or BenchmarkId::new instead.", @@ -118,9 +122,28 @@ impl Criterion { self } + #[track_caller] pub fn benchmark_group>(&mut self, group_name: S) -> BenchmarkGroup { + self.fill_missing_file_from_caller(); BenchmarkGroup::::new(self, group_name.into()) } + + /// When `current_file` wasn't set by `criterion_group!`, derive it from the caller location. + #[track_caller] + fn fill_missing_file_from_caller(&mut self) { + if !self.current_file.is_empty() { + return; + } + let caller = std::panic::Location::caller(); + if let Ok(workspace_root) = std::env::var("CODSPEED_CARGO_WORKSPACE_ROOT") { + self.current_file = std::path::PathBuf::from(workspace_root) + .join(caller.file()) + .to_string_lossy() + .into_owned(); + } else { + self.current_file = caller.file().to_string(); + } + } } // Dummy methods diff --git a/crates/criterion_compat/src/compat/group.rs b/crates/criterion_compat/src/compat/group.rs index 90e52877..499d5378 100644 --- a/crates/criterion_compat/src/compat/group.rs +++ b/crates/criterion_compat/src/compat/group.rs @@ -62,13 +62,21 @@ impl<'a, M: Measurement> BenchmarkGroup<'a, M> { F: FnMut(&mut Bencher, &I), I: ?Sized, { - let git_relative_file_path = get_git_relative_path(&self.current_file); - let mut uri = format!( - "{}::{}::{}", - git_relative_file_path.to_string_lossy(), - self.macro_group, - self.group_name, - ); + let mut uri_parts: Vec = Vec::new(); + + if !self.current_file.is_empty() { + let git_relative_file_path = get_git_relative_path(&self.current_file); + let file_str = git_relative_file_path.to_string_lossy(); + if !file_str.is_empty() { + uri_parts.push(file_str.into_owned()); + } + } + if !self.macro_group.is_empty() { + uri_parts.push(self.macro_group.clone()); + } + uri_parts.push(self.group_name.clone()); + + let mut uri = uri_parts.join("::"); if let Some(function_name) = id.function_name { uri = format!("{uri}::{function_name}"); }