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..f50488f4 --- /dev/null +++ b/crates/criterion_compat/benches/repro_custom_main.rs @@ -0,0 +1,30 @@ +/// 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(); +} + +criterion_group!(benches, bench_with_group); + +#[cfg(codspeed)] +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); +} + +#[cfg(not(codspeed))] +fn main() { + // Without codspeed, just run through upstream criterion directly + let mut criterion = Criterion::default().configure_from_args(); + bench_with_group(&mut criterion); +} diff --git a/crates/criterion_compat/src/compat/criterion.rs b/crates/criterion_compat/src/compat/criterion.rs index a8b85c1a..a388b529 100644 --- a/crates/criterion_compat/src/compat/criterion.rs +++ b/crates/criterion_compat/src/compat/criterion.rs @@ -92,6 +92,7 @@ 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), @@ -101,6 +102,7 @@ impl Criterion { self } + #[track_caller] pub fn bench_with_input(&mut self, id: BenchmarkId, input: &I, f: F) -> &mut Criterion where F: FnMut(&mut Bencher, &I), @@ -118,9 +120,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..b0aaf85d 100644 --- a/crates/criterion_compat/src/compat/group.rs +++ b/crates/criterion_compat/src/compat/group.rs @@ -62,13 +62,25 @@ 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 = String::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.push_str(&file_str); + } + } + if !self.macro_group.is_empty() { + if !uri.is_empty() { + uri.push_str("::"); + } + uri.push_str(&self.macro_group); + } + if !uri.is_empty() { + uri.push_str("::"); + } + uri.push_str(&self.group_name); if let Some(function_name) = id.function_name { uri = format!("{uri}::{function_name}"); }