The //coverage:combined_report target generates a single HTML coverage report
for all Rust and Python tools in the repository using Bazel's built-in
coverage support (bazel coverage) and genhtml.
bazel run //coverage:combined_reportThis runs bazel coverage --config=coverage for //plantuml/...,
//validation/... and //manual_analysis/..., merges all LCOV data, and
renders the report to <workspace>/coverage-html/index.html.
Custom output directory:
bazel run //coverage:combined_report -- --out-dir /tmp/my-coverageCustom target set:
bazel run //coverage:combined_report -- --targets "//plantuml/... //validation/core/..."bazel coverage --config=coveragecompiles Rust with-Cinstrument-coverageand wraps Python tests withcoverage.py(viarules_python's built-inconfigure_coverage_tool).- Bazel merges all per-test LCOV files into one
_coverage_report.dat(controlled by--combined_report=lcov). --instrumentation_filterlimits instrumentation to the three tool packages, excluding external dependencies and generated code.- Test infrastructure files (
integration_test/,tests/) are excluded from instrumentation via--instrumentation_filter; external Python files are removed vialcov --remove. - The HTML report uses a high-coverage threshold of 95 % (green) and the default medium threshold of 75 % (yellow).
genhtmlandlcovare downloaded hermetically via thedownload_utilsBazel module (@lcov_deb) — no system installation oflcovis required.
The coverage:coverage config in .bazelrc provides the required flags:
coverage:coverage --combined_report=lcov
coverage:coverage --instrumentation_filter=//plantuml,//validation,//manual_analysis,-//plantuml/parser/integration_test,-//validation/core/integration_test
coverage:coverage --@rules_rust//rust/settings:extra_rustc_flag=-Clink-dead-code
coverage:coverage --@rules_rust//rust/settings:extra_rustc_flag=-Ccodegen-units=1
You can also run bazel coverage directly without the script (requires genhtml
from the system lcov package):
bazel coverage --config=coverage //plantuml/... //validation/... //manual_analysis/...
genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat" \
--output-directory coverage-html/This directory provides the Ferrocene Rust coverage workflow for Bazel-based
projects. It uses Ferrocene's symbol-report and blanket tools to generate
HTML coverage reports from .profraw files produced by Rust tests.
The workflow is intentionally split:
- Tests produce
.profrawfiles (can run on host or target hardware). - Reports are generated later on a host machine.
This makes it easy to collect coverage from cross-compiled tests or from hardware-in-the-loop runs.
- Run tests with coverage enabled:
bazel test --config=ferrocene-x86_64-linux --config=ferrocene-coverage \
--nocache_test_results \
//path/to:rust_tests- Generate coverage reports:
bazel run //:rust_coverage -- --min-line-coverage 80The default report directory is:
$(bazel info bazel-bin)/coverage/rust-tests/<target>/blanket/index.html
The script prints per-target line coverage plus an overall summary line.
Add score_tooling and score_toolchains_rust as dependencies:
bazel_dep(name = "score_tooling", version = "1.0.0")
bazel_dep(name = "score_toolchains_rust", version = "0.4.0")Add a Ferrocene coverage config. Names are examples; choose names that fit your repo:
# Ferrocene toolchain for host execution
build:ferrocene-x86_64-linux --host_platform=@score_bazel_platforms//:x86_64-linux
build:ferrocene-x86_64-linux --platforms=@score_bazel_platforms//:x86_64-linux
build:ferrocene-x86_64-linux --extra_toolchains=@score_toolchains_rust//toolchains/ferrocene:ferrocene_x86_64_unknown_linux_gnu
# Coverage flags for rustc
build:ferrocene-coverage --@rules_rust//rust/settings:extra_rustc_flag=-Cinstrument-coverage
build:ferrocene-coverage --@rules_rust//rust/settings:extra_rustc_flag=-Clink-dead-code
build:ferrocene-coverage --@rules_rust//rust/settings:extra_rustc_flag=-Ccodegen-units=1
build:ferrocene-coverage --@rules_rust//rust/settings:extra_rustc_flag=-Cdebuginfo=2
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Cinstrument-coverage
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Clink-dead-code
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Ccodegen-units=1
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Cdebuginfo=2
test:ferrocene-coverage --run_under=@score_tooling//coverage:llvm_profile_wrapper
In a root BUILD file:
load("@score_tooling//coverage:coverage.bzl", "rust_coverage_report")
rust_coverage_report(
name = "rust_coverage",
bazel_configs = [
"ferrocene-x86_64-linux",
"ferrocene-coverage",
],
query = 'kind("rust_test", //...)',
min_line_coverage = "80",
)Run it with:
bazel run //:rust_coveragequery = 'kind("rust_test", //...) except //path/to:tests',If tests run on target hardware, copy the .profraw files back to the host
and point the report generator to the directory:
bazel run //:rust_coverage -- --profraw-dir /path/to/profrawYou can invoke the report generator from a top-level integration repo (for example, reference_integration) while targeting tests that live in external modules. Use a query that references external labels and run the wrapper target from the integration repo:
bazel run //images/linux_x86_64:per_rust_coverage --config=ferrocene-coverage -- \
--query 'kind("rust_test", @score_persistency//src/rust/...)'If the .profraw files were produced in that same workspace, the reporter
auto-discovers them under bazel-testlogs/ (including
bazel-testlogs/external/<repo>+ for external labels), so you do not need
to pass --profraw-dir. If they were copied from elsewhere, pass
--profraw-dir to point to the directory containing the .profraw files.
External source paths are resolved via Bazel's output_base so
external/<repo>/... paths are handled.
--min-line-coverage applies per target. If any target is below the minimum,
the script exits non-zero so CI can fail the job. An overall summary is printed
for visibility but does not change gating behavior.
- "running 0 tests": The Rust test harness found no
#[test]functions, so coverage is 0%. Add tests or exclude the target from the query. - "couldn't find source file" warnings: Usually path remapping or crate
mapping issues. Check that
crateattributes inrust_testtargets point to the library crate (or exclude the target). - Cached test results: Use
--nocache_test_resultsif you need to re-run tests and regenerate.profrawfiles.
- Verify the target contains real
#[test]functions. A rust_test target with no tests will run but report 0% coverage. - Ensure you ran tests with
--config=ferrocene-coverageso.profrawfiles exist. - If the test binary is cached, use
--nocache_test_results.
- Check
cratemapping onrust_testtargets. Ifcrate = "name"is used, ensure it refers to the library crate in the same package. - Confirm the reported paths exist in the workspace. Path remapping is required
so
blanketcan resolve files under--ferrocene-src.
- Ensure
test:ferrocene-coveragesets--run_under=@score_tooling//coverage:llvm_profile_wrapper. - Re-run tests with
--nocache_test_results. - If tests ran on target hardware, copy the
.profrawfiles back and pass--profraw-dir.
- The gate is per-target. A single target below the threshold fails the job.
- Use a stricter query (exclude known-zero targets) or add tests.
Keep coverage generation separate from docs:
-
Coverage workflow:
- run
bazel run //:rust_coverage - upload
bazel-bin/coverage/rust-testsas an artifact
- run
-
Docs workflow:
- download the artifact
- copy into the docs output (e.g.
docs/_static/coverage/) - publish Sphinx docs to GitHub Pages