Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/criterion_compat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ harness = false
[[bench]]
name = "test_benches"
harness = false

[[bench]]
name = "repro_custom_main"
harness = false
23 changes: 23 additions & 0 deletions crates/criterion_compat/benches/repro_custom_main.rs
Original file line number Diff line number Diff line change
@@ -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);
Comment on lines +15 to +22
}
23 changes: 23 additions & 0 deletions crates/criterion_compat/src/compat/criterion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,23 @@ impl<M: Measurement> Criterion<M> {
self.macro_group = macro_group.into();
}

#[track_caller]
pub fn bench_function<F>(&mut self, id: &str, f: F) -> &mut Criterion<M>
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<F, I>(&mut self, id: BenchmarkId, input: &I, f: F) -> &mut Criterion<M>
where
F: FnMut(&mut Bencher, &I),
{
self.fill_missing_file_from_caller();
let group_name = id.function_name.expect(
Comment on lines +95 to 112
"Cannot use BenchmarkId::from_parameter with Criterion::bench_with_input. \
Consider using a BenchmarkGroup or BenchmarkId::new instead.",
Expand All @@ -118,9 +122,28 @@ impl<M: Measurement> Criterion<M> {
self
}

#[track_caller]
pub fn benchmark_group<S: Into<String>>(&mut self, group_name: S) -> BenchmarkGroup<M> {
self.fill_missing_file_from_caller();
BenchmarkGroup::<M>::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
Expand Down
22 changes: 15 additions & 7 deletions crates/criterion_compat/src/compat/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = 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("::");
Comment on lines +65 to +79
if let Some(function_name) = id.function_name {
uri = format!("{uri}::{function_name}");
}
Expand Down
Loading