From f3693b69fc2657f57fcc93d78b8c5a73fecf2571 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Mon, 4 May 2026 14:00:56 +0000 Subject: [PATCH 1/2] Respect --instrumentation_filter for Rust coverage Add ctx.coverage_instrumented() check so that only targets matching --instrumentation_filter get -Cinstrument-coverage, consistent with Bazel's recommended approach for rules. For rust_test targets with a crate attribute, also check if the underlying crate should be instrumented. Rust compiles the crate sources directly into the test binary, so the test must be built with -Cinstrument-coverage for the crate's code to produce coverage. Add --instrumentation_filter=^// and --instrument_test_targets to CI coverage tasks so all workspace targets (including tests) are instrumented while excluding external dependencies. --- .bazelci/presubmit.yml | 13 +++++++++++++ docs/src/SUMMARY.md | 1 + docs/src/coverage.md | 40 ++++++++++++++++++++++++++++++++++++++++ rust/private/rustc.bzl | 13 +++++++++++-- 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 docs/src/coverage.md diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index c2ab7dfbe0..745fb8b216 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -74,14 +74,20 @@ split_coverage_postprocessing_shell_commands: &split_coverage_postprocessing_she - echo "coverage --experimental_fetch_all_coverage_outputs" >> user.bazelrc - echo "coverage --experimental_split_coverage_postprocessing" >> user.bazelrc - echo "build --//rust/settings:experimental_use_coverage_metadata_files" >> user.bazelrc +coverage_flags: &coverage_flags + - "--instrumentation_filter=^//" + - "--instrument_test_targets" rbe_coverage_flags: &rbe_coverage_flags # https://github.com/bazelbuild/bazel/issues/20578 - "--strategy=CoverageReport=local" + - "--instrumentation_filter=^//" + - "--instrument_test_targets" tasks: ubuntu2204: build_targets: *default_linux_targets test_targets: *default_linux_targets coverage_targets: *default_linux_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands run_targets: - //test:query_test_binary @@ -99,6 +105,7 @@ tasks: build_targets: *default_macos_targets test_targets: *default_macos_targets coverage_targets: *default_macos_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands windows: build_targets: *default_windows_targets @@ -116,12 +123,14 @@ tasks: platform: ubuntu2204 shell_commands: *split_coverage_postprocessing_shell_commands coverage_targets: *default_linux_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands macos_split_coverage_postprocessing: name: Split Coverage Postprocessing platform: macos_arm64 shell_commands: *split_coverage_postprocessing_shell_commands coverage_targets: *default_macos_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands ubuntu2204_opt: name: Opt Mode @@ -157,6 +166,7 @@ tasks: build_targets: *default_linux_targets test_targets: *default_linux_targets coverage_targets: *default_linux_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands rbe_ubuntu2204_with_aspects: name: With Aspects @@ -191,6 +201,7 @@ tasks: build_targets: *default_macos_targets test_targets: *default_macos_targets coverage_targets: *default_macos_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands macos_rolling_with_aspects: name: "Macos Rolling Bazel Version With Aspects" @@ -199,6 +210,7 @@ tasks: build_targets: *default_macos_targets test_targets: *default_macos_targets coverage_targets: *default_macos_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands soft_fail: yes bazel: "rolling" @@ -358,6 +370,7 @@ tasks: build_targets: *default_linux_targets test_targets: *default_linux_targets coverage_targets: *default_linux_targets + coverage_flags: *coverage_flags post_shell_commands: *coverage_validation_post_shell_commands linux_docs: name: Docs diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 83897a6cd4..4d30988c07 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -19,6 +19,7 @@ - [Bzlmod](./rust_bzlmod.md) - [External Crates](./external_crates.md) - [crate_universe](crate_universe_bzlmod.md) +- [Code Coverage](./coverage.md) - [Upstream Tooling](./upstream_tooling.md) - [IDE Integrations](./ide_integrations.md) - [Extensions](./extensions.md) diff --git a/docs/src/coverage.md b/docs/src/coverage.md new file mode 100644 index 0000000000..ca168f1fea --- /dev/null +++ b/docs/src/coverage.md @@ -0,0 +1,40 @@ +# Code Coverage + +Rules Rust supports collecting code coverage data using `bazel coverage`, leveraging LLVM's source-based coverage instrumentation. + +## Basic Usage + +```sh +bazel coverage //my_project/... +``` + +This instruments all targets matching the default `--instrumentation_filter` and produces an LCOV report at `bazel-out/_coverage/_coverage_report.dat`. + +## Controlling Instrumentation + +Rules Rust respects Bazel's standard [`--instrumentation_filter`](https://bazel.build/reference/command-line-reference#flag--instrumentation_filter) flag to control which targets get compiled with `-Cinstrument-coverage`. Only targets whose label matches the filter are instrumented, consistent with how coverage works for other languages (C++, Java). + +### Recommended `.bazelrc` Settings + +For projects with vendored or third-party dependencies, restrict instrumentation to workspace targets to avoid unnecessary recompilation: + +```text +coverage --instrumentation_filter=^// +coverage --instrument_test_targets +``` + +The `--instrument_test_targets` flag is recommended because Rust test binaries (`rust_test`) often compile library source code directly into the test binary (e.g., when using the `crate` attribute). Without this flag, test targets are excluded from instrumentation by default, which means library code compiled into the test binary would not produce coverage data. + +To further exclude specific directories: + +```text +coverage --instrumentation_filter=^//,-^//third_party +``` + +### Flags Summary + +| Flag | Purpose | +|------|---------| +| `--instrumentation_filter=` | Controls which targets are instrumented. Default: `-/javatests[/:],-/test/java[/:]` | +| `--instrument_test_targets` | Also instrument test targets. Recommended for Rust. | +| `--combined_report=lcov` | Produce a combined LCOV report (set by default with `bazel coverage`). | diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 3f03e895f6..db779cfd92 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -1219,8 +1219,17 @@ def construct_arguments( rustc_flags.add("--extern") rustc_flags.add("proc_macro") - if toolchain.coverage_supported and ctx.configuration.coverage_enabled: - # https://doc.rust-lang.org/rustc/instrument-coverage.html + # Use Bazel's standard instrumentation filter (--instrumentation_filter) + # so that only targets matching the filter get instrumented, consistent + # with how coverage works for other languages (Java, C++). + # For rust_test targets with a `crate` attribute, also check if the + # underlying crate should be instrumented. Rust compiles the crate + # sources directly into the test binary, so the test must be built + # with -Cinstrument-coverage for the crate's code to produce coverage. + _coverage_instrumented = ctx.coverage_instrumented() + if not _coverage_instrumented and crate_info.is_test and hasattr(ctx.attr, "crate") and ctx.attr.crate: + _coverage_instrumented = ctx.coverage_instrumented(ctx.attr.crate) + if toolchain.coverage_supported and ctx.configuration.coverage_enabled and _coverage_instrumented: rustc_flags.add("--codegen=instrument-coverage") if toolchain._experimental_link_std_dylib: From 76c07871f6dee526251bdf17b208b829eec0e6c0 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 5 May 2026 07:29:03 +0000 Subject: [PATCH 2/2] Clarify --instrument_test_targets in coverage docs Remove --instrument_test_targets from recommended settings since it is not needed for the common case of rust_test with a crate attribute. Document the inconsistency where #[cfg(test)] code gets instrumented even without the flag. --- docs/src/coverage.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/src/coverage.md b/docs/src/coverage.md index ca168f1fea..ed2a62f2a2 100644 --- a/docs/src/coverage.md +++ b/docs/src/coverage.md @@ -20,21 +20,32 @@ For projects with vendored or third-party dependencies, restrict instrumentation ```text coverage --instrumentation_filter=^// -coverage --instrument_test_targets ``` -The `--instrument_test_targets` flag is recommended because Rust test binaries (`rust_test`) often compile library source code directly into the test binary (e.g., when using the `crate` attribute). Without this flag, test targets are excluded from instrumentation by default, which means library code compiled into the test binary would not produce coverage data. - To further exclude specific directories: ```text coverage --instrumentation_filter=^//,-^//third_party ``` +### `rust_test` with a `crate` attribute + +When a `rust_test` target uses the `crate` attribute, Rust compiles the library source code directly into the test binary. Rules Rust automatically checks whether the underlying crate should be instrumented, so library code compiled into the test binary produces coverage data without needing `--instrument_test_targets`. + +Note: because the entire crate (including `#[cfg(test)]` code) is compiled as one unit, test-specific code in the crate will also be instrumented. This is a known inconsistency with the usual Bazel convention where test code is only instrumented when `--instrument_test_targets` is set. + +### `--instrument_test_targets` + +By default, Bazel excludes test targets from instrumentation. If you want coverage of the test code itself (not just the libraries it exercises), add: + +```text +coverage --instrument_test_targets +``` + ### Flags Summary | Flag | Purpose | |------|---------| | `--instrumentation_filter=` | Controls which targets are instrumented. Default: `-/javatests[/:],-/test/java[/:]` | -| `--instrument_test_targets` | Also instrument test targets. Recommended for Rust. | +| `--instrument_test_targets` | Also instrument test targets. Not required for library coverage via `rust_test` with `crate`. | | `--combined_report=lcov` | Produce a combined LCOV report (set by default with `bazel coverage`). |