diff --git a/bazel/rules/BUILD.bazel b/bazel/rules/BUILD.bazel new file mode 100644 index 000000000000..218872c73c38 --- /dev/null +++ b/bazel/rules/BUILD.bazel @@ -0,0 +1,5 @@ +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "e2e_benchmark.bzl", +]) diff --git a/bazel/rules/e2e_benchmark.bzl b/bazel/rules/e2e_benchmark.bzl new file mode 100644 index 000000000000..035419adad1e --- /dev/null +++ b/bazel/rules/e2e_benchmark.bzl @@ -0,0 +1,56 @@ +load("@crates//:defs.bzl", "all_crate_deps") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//:defs.bzl", "workspace_root_test") + +_WORKSPACE_ROOT_MARKER = "//codex-rs/utils/cargo-bin:repo_root.marker" + +def codex_e2e_benchmark(name, binaries = [], data = [], deps = []): + """Defines a Bazel-only Divan end-to-end benchmark. + + The benchmark source lives at `e2e_benches/.rs`, with hyphens in + `name` replaced by underscores. `binaries` are runtime executables made + available through the same `CARGO_BIN_EXE_*` bridge used by Rust tests. + + Args: + name: Stem for the generated `-bench` target and benchmark. + binaries: Runtime executable labels that the benchmark spawns. + data: Additional runtime files needed by the benchmark. + deps: Additional Rust dependencies beyond the crate's Cargo deps. + """ + benchmark_name = name.replace("-", "_") + source = "e2e_benches/{}.rs".format(benchmark_name) + binary_name = name + "-bench-bin" + runfile_env = { + binary: "CARGO_BIN_EXE_" + native.package_relative_label(binary).name + for binary in binaries + } + + rust_binary( + name = binary_name, + testonly = True, + srcs = [source], + crate_name = benchmark_name + "_bench", + crate_root = source, + deps = all_crate_deps( + normal = True, + normal_dev = True, + ) + [ + "@crates//:divan", + ] + deps, + ) + + workspace_root_test( + name = name + "-bench", + args = [ + "--bench", + benchmark_name, + ], + data = data, + # Keep path resolution inside the wrapper so manifest-only runfiles + # work on every supported host platform. + runfile_env = runfile_env, + tags = ["manual"], + test_bin = ":" + binary_name, + visibility = ["//codex-rs:__pkg__"], + workspace_root_marker = _WORKSPACE_ROOT_MARKER, + ) diff --git a/codex-rs/cli/BUILD.bazel b/codex-rs/cli/BUILD.bazel index 97c868845b89..9938897f6ddc 100644 --- a/codex-rs/cli/BUILD.bazel +++ b/codex-rs/cli/BUILD.bazel @@ -1,5 +1,6 @@ load("//:defs.bzl", "MACOS_WEBRTC_RUSTC_LINK_FLAGS", "codex_rust_crate") load("//bazel/platforms:release_binaries.bzl", "multiplatform_binaries") +load("//bazel/rules:e2e_benchmark.bzl", "codex_e2e_benchmark") codex_rust_crate( name = "cli", @@ -13,3 +14,8 @@ codex_rust_crate( multiplatform_binaries( name = "codex", ) + +codex_e2e_benchmark( + name = "codex-help", + binaries = [":codex"], +) diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index 9a8312ced28f..46702be15997 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -104,3 +104,8 @@ predicates = { workspace = true } pretty_assertions = { workspace = true } sqlx = { workspace = true } wiremock = { workspace = true } + +[package.metadata.cargo-shear] +# These Rust sources are intentionally Bazel-only macrobenchmarks rather than +# Cargo targets. +ignored-paths = ["e2e_benches/*.rs"] diff --git a/codex-rs/cli/e2e_benches/codex_help.rs b/codex-rs/cli/e2e_benches/codex_help.rs new file mode 100644 index 000000000000..b304b23d94b5 --- /dev/null +++ b/codex-rs/cli/e2e_benches/codex_help.rs @@ -0,0 +1,26 @@ +#![allow(clippy::expect_used)] + +use std::process::Command; + +use divan::Bencher; + +fn main() { + divan::main(); +} + +/// Exercises the Bazel-backed end-to-end benchmark path with a cheap, +/// deterministic Codex invocation. Richer scenarios can add separate +/// benchmark binaries without making the shared harness depend on them. +#[divan::bench(sample_count = 20, sample_size = 1)] +fn codex_help(bencher: Bencher) { + let codex = codex_utils_cargo_bin::cargo_bin("codex") + .expect("codex binary should be available through Bazel runfiles"); + + bencher.bench_local(move || { + let output = Command::new(&codex) + .arg("--help") + .output() + .expect("codex --help should run"); + assert!(output.status.success(), "codex --help should succeed"); + }); +}