Skip to content
Merged
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
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cargo-codspeed/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ itertools = { workspace = true }
anstyle = "1.0.8"
serde = { workspace = true }
serde_json = { workspace = true }
toml = "0.8"
codspeed = { path = "../codspeed", version = "=4.0.5" }

[dev-dependencies]
Expand Down
76 changes: 76 additions & 0 deletions crates/cargo-codspeed/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use crate::{
measurement_mode::MeasurementMode,
prelude::*,
};
use anyhow::Context;
use cargo_metadata::{camino::Utf8PathBuf, Message, Metadata, TargetKind};
use std::collections::HashMap;
use std::process::{exit, Command, Stdio};

struct BuildOptions<'a> {
Expand All @@ -31,6 +33,50 @@ pub struct BuildConfig {
pub passthrough_flags: Vec<String>,
}

fn get_bench_harness_value(
manifest_path: &Utf8PathBuf,
bench_name: &str,
cache: &mut HashMap<Utf8PathBuf, toml::Table>,
) -> Result<bool> {
let manifest_table = if let Some(table) = cache.get(manifest_path) {
table
} else {
// Read and parse the Cargo.toml file
let manifest_content = std::fs::read_to_string(manifest_path)
.with_context(|| format!("Failed to read manifest at {manifest_path}"))?;
let table: toml::Table = toml::from_str(&manifest_content)
.with_context(|| format!("Failed to parse TOML in {manifest_path}"))?;
cache.insert(manifest_path.clone(), table);
cache.get(manifest_path).unwrap()
};

// Look for [[bench]] sections
let Some(benches) = manifest_table.get("bench").and_then(|v| v.as_array()) else {
// If no [[bench]] sections, it's not an error, benches present in <root>/benches/<name>.rs
// are still collected with harness = true
return Ok(true);
};

// Find the bench entry with matching name
let matching_bench = benches
.iter()
.filter_map(|bench| bench.as_table())
.find(|bench_table| {
bench_table
.get("name")
.and_then(|v| v.as_str())
.is_some_and(|name| name == bench_name)
});

// Check if harness is enabled (defaults to true)
let harness_enabled = matching_bench
.and_then(|bench_table| bench_table.get("harness"))
.and_then(|v| v.as_bool())
.unwrap_or(true);

Ok(harness_enabled)
}

impl BuildOptions<'_> {
/// Builds the benchmarks by invoking cargo
/// Returns a list of built benchmarks, with path to associated executables
Expand Down Expand Up @@ -60,6 +106,8 @@ impl BuildOptions<'_> {
);

let mut built_benches = Vec::new();
let mut bench_targets_with_default_harness = Vec::new();
let mut manifest_cache = HashMap::new();

let package_names = self
.package_filters
Expand Down Expand Up @@ -95,6 +143,15 @@ impl BuildOptions<'_> {
let add_bench_to_codspeed_dir = package_names.iter().contains(&package.name);

if add_bench_to_codspeed_dir {
if get_bench_harness_value(
&package.manifest_path,
&bench_target_name,
&mut manifest_cache,
)? {
bench_targets_with_default_harness
.push((package.name.to_string(), bench_target_name.clone()));
}

built_benches.push(BuiltBench {
package: package.name.to_string(),
bench: bench_target_name,
Expand All @@ -114,6 +171,25 @@ impl BuildOptions<'_> {
exit(status.code().expect("Could not get exit code"));
}

if !bench_targets_with_default_harness.is_empty() {
let targets_list = bench_targets_with_default_harness
.into_iter()
.map(|(package, bench)| format!(" - `{bench}` in package `{package}`"))
.join("\n");

bail!("\
CodSpeed will not work with the following benchmark targets:
{targets_list}

CodSpeed requires benchmark targets to disable the default test harness because benchmark frameworks handle harnessing themselves.

Either disable the default harness by adding `harness = false` to the corresponding \
`[[bench]]` section in the Cargo.toml, or specify which targets to build by using \
`cargo codspeed build -p package_name --bench first_target --bench second_target`.

See `cargo codspeed build --help` for more information.");
}

for built_bench in &built_benches {
eprintln!(
"Built benchmark `{}` in package `{}`",
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-codspeed/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ pub fn run_benches(

command
.status()
.map_err(|e| anyhow!("failed to execute the benchmark process: {}", e))
.map_err(|e| anyhow!("failed to execute the benchmark process: {e}"))
.and_then(|status| {
if status.success() {
Ok(())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
Cargo.lock
18 changes: 18 additions & 0 deletions crates/cargo-codspeed/tests/default_harness_error.in/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "default-harness-error-test"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
bencher = "0.1.5"
codspeed = { path = "../../../codspeed" }
codspeed-bencher-compat = { path = "../../../bencher_compat" }

[[bench]]
name = "bencher_example"
# Missing harness = false to trigger error

[[bench]]
name = "bencher_correct"
harness = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use codspeed::codspeed::black_box;
use codspeed_bencher_compat::{benchmark_group, benchmark_main, Bencher};

pub fn b(bench: &mut Bencher) {
bench.iter(|| (0..50).fold(0, |x, y| black_box(x + y)))
}

benchmark_group!(benches, b);
benchmark_main!(benches);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use codspeed::codspeed::black_box;
use codspeed_bencher_compat::{benchmark_group, benchmark_main, Bencher};

pub fn a(bench: &mut Bencher) {
bench.iter(|| (0..100).fold(0, |x, y| black_box(x + y)))
}

benchmark_group!(benches, a);
benchmark_main!(benches);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use codspeed::codspeed::black_box;
use codspeed_bencher_compat::{benchmark_group, benchmark_main, Bencher};

pub fn c(bench: &mut Bencher) {
bench.iter(|| (0..75).fold(0, |x, y| black_box(x + y)))
}

benchmark_group!(benches, c);
benchmark_main!(benches);
33 changes: 33 additions & 0 deletions crates/cargo-codspeed/tests/default_harness_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use predicates::prelude::*;
use predicates::str::contains;

mod helpers;
use helpers::*;

const DIR: &str = "tests/default_harness_error.in";

#[test]
fn test_default_harness_error() {
let dir = setup(DIR, Project::DefaultHarnessError);
cargo_codspeed(&dir)
.arg("build")
.assert()
.failure()
.stderr(contains(
"Error: CodSpeed will not work with the following benchmark targets:",
))
// Should report bencher_example (explicit bench with missing harness = false)
.stderr(contains(
"`bencher_example` in package `default-harness-error-test`",
))
// Should report bencher_no_section (no [[bench]] section means default harness)
.stderr(contains(
"`bencher_no_section` in package `default-harness-error-test`",
))
.stderr(contains(
"CodSpeed requires benchmark targets to disable the default test harness",
))
// Ensure the correct benchmark with harness = false is NOT reported
.stderr(contains("bencher_correct").not());
teardown(dir);
}
3 changes: 2 additions & 1 deletion crates/cargo-codspeed/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub enum Project {
Workspace,
PackageInDeps,
CratesWorkingDirectory,
DefaultHarnessError,
}

pub fn setup(dir: &str, project: Project) -> String {
Expand All @@ -38,7 +39,7 @@ pub fn setup(dir: &str, project: Project) -> String {
let package_root = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap();
let workspace_root = package_root.parent().unwrap().parent().unwrap();
match project {
Project::Simple | Project::Features => {
Project::Simple | Project::Features | Project::DefaultHarnessError => {
replace_in_file(
tmp_dir.join("Cargo.toml").to_str().unwrap(),
"../../..",
Expand Down
4 changes: 4 additions & 0 deletions crates/cargo-codspeed/tests/workspace.in/b/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ codspeed-bencher-compat = { path = "../../../../bencher_compat" }
[[bench]]
name = "bencher_example"
harness = false

[[bench]]
name = "another_bencher_example"
harness = false
8 changes: 4 additions & 4 deletions crates/criterion_compat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ repository = "https://github.com/CodSpeedHQ/codspeed-rust"
homepage = "https://codspeed.io"
license = "MIT OR Apache-2.0"
categories = [
"development-tools",
"development-tools::profiling",
"development-tools::testing",
"development-tools",
"development-tools::profiling",
"development-tools::testing",
]
keywords = ["codspeed", "benchmark", "criterion"]
[dependencies]
Expand All @@ -26,7 +26,7 @@ regex = { version = "1.5", default-features = false, features = ["std"] }
futures = { version = "0.3", default-features = false, optional = true }
smol = { version = "2.0", default-features = false, optional = true }
tokio = { version = "1.39", default-features = false, features = [
"rt",
"rt",
], optional = true }
async-std = { version = "1.12", optional = true }

Expand Down
6 changes: 3 additions & 3 deletions crates/divan_compat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ repository = "https://github.com/CodSpeedHQ/codspeed-rust"
homepage = "https://codspeed.io"
license = "MIT OR Apache-2.0"
categories = [
"development-tools",
"development-tools::profiling",
"development-tools::testing",
"development-tools",
"development-tools::profiling",
"development-tools::testing",
]
keywords = ["codspeed", "benchmark", "divan"]

Expand Down
Loading