Skip to content

Surface untested source files at 0% coverage in reports#9

Open
olivembo wants to merge 16 commits into
eclipse-score:mainfrom
etas-contrib:centralized-coverage
Open

Surface untested source files at 0% coverage in reports#9
olivembo wants to merge 16 commits into
eclipse-score:mainfrom
etas-contrib:centralized-coverage

Conversation

@olivembo

@olivembo olivembo commented Jun 23, 2026

Copy link
Copy Markdown

Summary

llvm-cov only reports files whose object files are linked into at least one test.

Source files that exist in the workspace but no cc_test pulls in silently disappear from the coverage report.

This PR adds a Bazel aspect that walks the dependency graph to collect all C/C++ sources, compares them against what llvm-cov actually reported, and augments the LCOV, HTML, and text outputs with synthetic 0%-coverage entries for the missing files. With that Feature Request #6 should be resolved.

The coverage pipeline (merger, reporter, justify, effective_coverage) was originally developed by Jan Schlosser in eclipse-score/communication#549.
This PR centralizes it in score_cpp_policies, adds the baseline-coverage aspect, and improves the reporter.

Approach

Why an aspect + manifest instead of patching --instrumentation_filter?

instrumentation_filter controls which targets get built with coverage instrumentation, but llvm-cov still only reports files whose .o is linked into a test binary. There is no Bazel-native way to surface the gap. The aspect walks deps/srcs/implementation_deps transitively and writes a manifest of all reachable .cpp/.cc/.cxx/.c files. The reporter then diffs manifest vs. LCOV output.

Why heuristic line counts instead of exact numbers?

Without running llvm-cov against actual instrumented object files (which don't exist for untested sources), we can't get exact instrumentable line counts. The heuristic (_count_instrumentable_lines) filters blank lines, comments, preprocessor directives, lone braces, and namespace declarations. All outputs explicitly label these as estimates
(~N, "estimated via heuristic") to avoid false precision. The TOTALS line in summary.txt is intentionally left untouched. Only a WARNING banner is appended.

Known limitations (tracked for follow-up)

  • Baseline coverage / rules_cc gap: Proper baseline generation requires upstream support in rules_cc first. Current heuristic line counts are estimates.
  • QNX: Coverage on QNX may pull in Rust coverage data; how to separate these is still an open question.
  • constexpr functions: These appear uncovered due to how llvm-cov handles them.
  • toolchains_llvm: Once the above are resolved, contributing upstream there is the intended next step.

What changed

coverage/defs.bzl: _collect_sources_aspect, score_instrumented_sources_manifest rule, instrumented_sources_manifest parameter on score_coverage_reporter macro
coverage/reporter.py: LCOV augmentation, HTML augmentation (top-banner + per-file pages + detail table), text summary banner, workspace-bounds check, HTML escaping including quotes
coverage/BUILD.bazel: reporter_lib py_library for testability
tests/coverage/: uncovered.cpp/.h fixture (library with no test), reporter_test.py with unit tests for all augmentation helpers including path-traversal rejection

Consumer usage

load("@score_cpp_policies//coverage:defs.bzl",
     "score_coverage_reporter", "score_instrumented_sources_manifest")

score_instrumented_sources_manifest(
    name = "instrumented_sources",
    targets = ["//src:mylib"],
)

score_coverage_reporter(
    name = "reporter_wrapper",
    llvm_cov = "@llvm_toolchain//:llvm-cov",
    llvm_profdata = "@llvm_toolchain//:llvm-profdata",
    instrumented_sources_manifest = ":instrumented_sources",
)

olivembo added 6 commits June 23, 2026 08:25
Introduces a reusable coverage toolchain based on llvm-cov:

- coverage/merger.py: per-test profraw -> profdata + object file packaging
- coverage/reporter.py: cross-test aggregation, HTML/LCOV/text reports
- coverage/effective_coverage.py: justification overlay + effective metrics
- coverage/justify.py: justification manifest resolution
- coverage/defs.bzl: score_coverage_reporter macro for consumer wiring
- coverage/coverage.bazelrc: shared coverage flags
- coverage/filter_regexes.txt: baseline source exclusions
- coverage/generate_coverage_html.sh: convenience entry point

Adds an end-to-end example under tests/coverage exercising the pipeline
with a small instrumented library, test, justification file, and consumer
filter regexes.

Known limitation: source files not linked into any cc_test are not yet
included in the report (no instrumented object file -> invisible to
llvm-cov).
llvm-cov only reports files linked into at least one test binary.
Sources that exist in the workspace but no test pulls in silently
disappear from the report, causing coverage to appear higher than
it actually is.

Three mechanisms work together to fix this:

Bazel aspect + manifest rule (coverage/defs.bzl):
  _collect_sources_aspect walks the dependency graph of all configured
  targets and collects C/C++ source files. score_instrumented_sources_manifest
  writes a workspace-relative path-per-line manifest.
  score_coverage_reporter gains an optional instrumented_sources_manifest
  parameter that passes the manifest to the reporter via
  --instrumented_sources_manifest.

Reporter augmentation (coverage/reporter.py):
  After llvm-cov export, the reporter compares the manifest against
  covered sources from the LCOV output. For each missing file a
  synthetic 0%-coverage LCOV record is appended (SF + DA per
  non-blank line + LF/LH). The llvm-cov text summary TOTALS line is
  updated in-place using re.finditer to preserve fixed-width column
  alignment. Per-file HTML pages and a "Not Linked Into Tests" index
  section are generated for visibility.

  Correctness and security fixes applied during review:
  - Use str.replace() instead of str.format() for HTML template
    rendering so that { and } in C++ source bodies do not crash the
    reporter with KeyError/ValueError.
  - Separate stderr from stdout for run_llvm_cov_export and
    run_llvm_cov_report (separate_stderr=True) so that llvm-cov
    warning messages are not mixed into LCOV/summary output.
  - Validate that resolved manifest paths stay within workspace_root
    via Path.is_relative_to() before reading files.
  - Extend _escape_html to cover ' (') and " (") so that
    file paths with apostrophes do not break HTML attributes.
  - Count only non-blank lines for LF in synthetic LCOV records
    to avoid inflating the denominator in aggregate metrics.

Test fixture (tests/coverage/uncovered.cpp, uncovered.h, BUILD.bazel):
  cc_library intentionally not linked into any cc_test. Verifies that
  the reporter surfaces the file at 0% coverage rather than omitting it.

Docs (coverage/README.md): new section 5a with usage example.
- Use heuristic to identify instrumentable lines instead of counting all
  non-blank lines. Filters comments, preprocessor directives, lone braces,
  namespace declarations, and access specifiers to avoid inflating LF values
  in synthetic LCOV records.
- Augment summary.txt and console output with untested file line counts so
  the visible TOTALS reflect the true coverage including 0%-files.
- Parse the llvm-cov column header to determine the Lines group index
  dynamically instead of hardcoding position 1.
- Add workspace-bounds check after resolve() in _find_untested_sources to
  prevent path traversal via symlinks.
- Escape single and double quotes in _escape_html to prevent attribute
  breakout in generated HTML pages.
- Narrow _NON_EXECUTABLE_RE block-comment pattern from `|\*.*` to
  `|\*(?:[/\s].*)?` so that pointer dereferences (`*ptr = value;`) are
  correctly classified as executable.
- Add py_test with unit tests for all reporter augmentation helpers:
  _is_likely_executable, _count_instrumentable_lines,
  _covered_sources_from_lcov, _find_untested_sources,
  _append_zero_coverage_lcov, _augment_text_summary, _escape_html.
  Includes a path-traversal rejection test for _find_untested_sources.
- Add py_library target for reporter so unit tests can import it.
- Remove coverable_test from instrumented_sources manifest targets
  to avoid testonly dependency violation (test sources don't need to
  appear in the manifest — they're tested by definition).
The heuristic line count (_count_instrumentable_lines) cannot replicate
what llvm-cov would report for actually-instrumented objects. Rewriting
TOTALS with approximate numbers gives false precision.

- _augment_text_summary: no longer rewrites the TOTALS line; appends a
  clearly-labelled WARNING banner with ~N estimated lines instead.
- _inject_untested_section_into_index: injects a prominent banner right
  after <body> so it is the first thing reviewers see. Detail table uses
  ~N notation and includes a disclaimer about the heuristic.
- _append_zero_coverage_lcov docstring documents the approximation.
- Tests updated to match banner-only behavior.

@nradakovic nradakovic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest. I will need a lot of time (and coffee) to do proper review. Hopefully by Monday I will have feedback.

@olivembo

Copy link
Copy Markdown
Author

To be honest. I will need a lot of time (and coffee) to do proper review. Hopefully by Monday I will have feedback.

Thanks for the quick feedback Nikola.

In fact, I took most of the code from this PR in the communication repository. Additionally, I fixed that files without unittests are shown in the report so not a higher coverage is show that is actually the case.

Maybe for the review you seek help in the communication team.

Happy review and hopefully good ☕😉

@nradakovic

Copy link
Copy Markdown
Member

To be honest. I will need a lot of time (and coffee) to do proper review. Hopefully by Monday I will have feedback.

Thanks for the quick feedback Nikola.

In fact, I took most of the code from this PR in the communication repository. Additionally, I fixed that files without unittests are shown in the report so not a higher coverage is show that is actually the case.

Maybe for the review you seek help in the communication team.

Happy review and hopefully good ☕😉

And this is also another problem. I'll for sure have some questions why certain implementation blocks are done in such way, but since communication PR is already merged (and in use?) not sure if I want to dive into large discussion and diverge from original PR. Anyway, let's see once I have concrete feedback. If something is on critical path, I will request that communication change their implementation.

@nradakovic

Copy link
Copy Markdown
Member

So something that I see by just looking at. This implementation relies on a generated Bash wrapper to reconstruct Bazel runfiles and workspace paths before invoking the actual reporter. This feels a bit non-Bazel-native. Can we instead model this as an executable Starlark rule, or move runfiles/path resolution into the reporter binary using Bazel runfiles libraries?

@nradakovic nradakovic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For start I left 2 comments. It's enough to discuss Bazel implementation blocks (I still haven't check Python scripts) but I think they look OK. Have to do better look.

I see two paths for this PR to get merged:

  1. I will approve as is but only if we have plan to improve current implementation (tickets to track it with clear plan how to improve it). What communication guys did is on them to support it. This will be untilized by every single module in S-CORE so we do not want to find nasty surprises 2 days before release of S-CORE.
  2. We start now changing blocks but this will require some time and probably we will need to include more people who have more exp. with Bazel and code coverage.

@RSingh1511 Please provide your thoughts on this as well.

Comment thread coverage/defs.bzl
Comment thread coverage/coverage.bazelrc
@RSingh1511

Copy link
Copy Markdown
Contributor

Hello @olivembo , Thanks for this. Since you referenced communication, could you create an adoption PR in communication or logging showing how we can consume it? A real data example would help with verification and allow everyone to understand the use case.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the repo’s Bazel/llvm-cov coverage tooling so that C/C++ source files reachable from configured coverage roots—but not linked into any executed test binary—are still surfaced in coverage outputs as synthetic 0%-coverage entries (LCOV, HTML, and text summary). It does so by generating a manifest of reachable sources via a Bazel aspect and then augmenting the reporter outputs based on a manifest-vs-LCOV diff.

Changes:

  • Adds a Bazel aspect + score_instrumented_sources_manifest rule to enumerate transitive C/C++ sources and plumbs an optional instrumented_sources_manifest into score_coverage_reporter.
  • Implements report augmentation in coverage/reporter.py (LCOV + HTML index/pages + text summary banner) and adds unit tests for the augmentation helpers.
  • Adds coverage adoption docs/config (coverage/README.md, coverage.bazelrc, filter regex baseline) and a consumer smoke-test workspace under tests/coverage.

Reviewed changes

Copilot reviewed 5 out of 27 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/MODULE.bazel Adds rules_python + Python toolchain setup for the test workspace.
tests/coverage/uncovered.h Adds an intentionally untested header fixture for smoke-testing “0% untested file” surfacing.
tests/coverage/uncovered.cpp Adds an intentionally untested source fixture for smoke-testing “0% untested file” surfacing.
tests/coverage/reporter_test.py Adds unit tests for coverage reporter augmentation helpers (LCOV/HTML/text/escaping/path checks).
tests/coverage/coverage_justifications.yaml Adds a sample justification DB used by the coverage smoke test.
tests/coverage/coverage_filter_regexes.txt Adds smoke-test consumer-side extra ignore regexes.
tests/coverage/coverable.h Adds a small API exercised by the coverage smoke test (with an intentionally uncovered branch).
tests/coverage/coverable.cpp Adds the implementation for the smoke-test library.
tests/coverage/coverable_test.cpp Adds a test that intentionally leaves one branch uncovered for report validation.
tests/coverage/BUILD.bazel Wires the smoke test targets, manifest generation, and reporter wrapper.
tests/BUILD.bazel Exports MODULE.bazel so the reporter wrapper can resolve workspace root at runtime.
tests/.bazelrc Imports coverage bazelrc and adds coverage smoke-test settings for the tests workspace.
README.md Updates top-level README to document coverage tooling availability and link to coverage guide.
MODULE.bazel Adds rules_python, rules_shell, Python toolchain, and pip hub wiring for coverage tooling.
coverage/requirements.in Adds pyyaml as the direct dependency for justification processing.
coverage/requirements_lock.txt Adds a locked pyyaml pin with hashes for Bazel pip integration.
coverage/reporter.py Implements final report generation + augmentation with synthetic 0%-coverage entries for untested sources.
coverage/README.md Adds an adoption guide for consumers (setup, configs, optional untested-files manifest).
coverage/merger.py Adds per-test merger that packages profdata + object file metadata for final reporting.
coverage/justify.py Adds YAML + in-source marker processing to generate a justification manifest.
coverage/generate_coverage_html.sh Adds a consumer-facing driver to extract reports, run justification/effective-coverage, and optionally archive artifacts.
coverage/filter_regexes.txt Adds baseline ignore regexes (tests/mocks/fakes/external/benchmarks).
coverage/effective_coverage.py Adds HTML post-processing + effective coverage computation incorporating justifications.
coverage/defs.bzl Adds the manifest aspect/rule and updates score_coverage_reporter to pass the manifest into the reporter.
coverage/coverage.bazelrc Adds generic Bazel coverage flags/config to be imported by consumers.
coverage/BUILD.bazel Adds Bazel targets for merger/reporter/justify/effective_coverage and the driver script.
.gitignore Ignores generated cpp_coverage/ output directories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
…e_reporter

The previous implementation used a genrule + heredoc to generate the
reporter wrapper shell script. This required three-level escaping (\\$$)
to handle Bazel make-variable and bash variable expansion in the same
string which makes the code fragile
and hard to debug.

Replace with a custom Starlark rule (`_score_coverage_reporter_rule`) that
uses `ctx.actions.write()` to produce the wrapper script directly. A new
`_rlocation_path()` helper computes `Runfiles.Rlocation()`-compatible paths
from File objects, eliminating all genrule make-variable gymnastics.

The `--workspace_root` argument (previously computed via `readlink -f` in
bash) is replaced by `--module_bazel` (a plain rlocation string). The
reporter resolves it with `r.Rlocation()` and derives the workspace root
in Python.

The generated wrapper is now a readable 7-line script with no escaping.
`rules_shell` is no longer a dependency of defs.bzl.

Addresses review comment: eclipse-score#9 (comment)
@nradakovic nradakovic added enhancement New feature or request wip Work in progress p3 Medium/Low - handle it within normal process code coverage Issue or pull request for code coverage labels Jun 26, 2026
@olivembo

Copy link
Copy Markdown
Author

Hello @olivembo , Thanks for this. Since you referenced communication, could you create an adoption PR in communication or logging showing how we can consume it? A real data example would help with verification and allow everyone to understand the use case.

--> eclipse-score/communication#604

@FScholPer

Copy link
Copy Markdown

Gentleman still wait or proceed with merge?

@castler

castler commented Jun 27, 2026

Copy link
Copy Markdown

Two cents from the guy where this was copied over from.

As I wrote already in the Slack, the proper generation of baseline coverage requires a deeper look. Especially, as rules_cc is missing this support today - and I believe for a proper generation of baseline coverage, we need to look there first. But, this will require quite some time.

Second, I am still in the workings to make the coverage also correct for QNX. First experiments also showed that the coverage for QNX will bring coverage results for rust included. Thus, I am also not sure yet how to best handle this and if this will be at the end an "cpp_policies" only thing, or might not include Rust also.

Third, we still have a problem with constexpr functions, as they appear not covered.

Fourth, my original plan would have been, once the three mentioned points are solved, to contribute this solution to toolchains_llvm, as obviously this can be reused by a way broader community.

Anyhow, it can be decided that this is "good enough" for now, and these problems are tackled in an incremental way.
Also just raising awareness for them.


On a personal note - while the licenses and copyrights that we utilize in this project fully allow a copying of the code to different places without touching the license + copyright, I still think it follows best practices to give at least some credit / blame for the original author. Feels quite bad if a topic where quite some time was invested is just copied over. I would have loved to see at least a clean commit with the original state and author, and then the improvements, refactorings on top of it. (I do not require to change this, but I would like to raise awareness).

@olivembo

Copy link
Copy Markdown
Author

Gentleman still wait or proceed with merge?

It's not approved yet

@olivembo

Copy link
Copy Markdown
Author

Two cents from the guy where this was copied over from.

As I wrote already in the Slack, the proper generation of baseline coverage requires a deeper look. Especially, as rules_cc is missing this support today - and I believe for a proper generation of baseline coverage, we need to look there first. But, this will require quite some time.

Second, I am still in the workings to make the coverage also correct for QNX. First experiments also showed that the coverage for QNX will bring coverage results for rust included. Thus, I am also not sure yet how to best handle this and if this will be at the end an "cpp_policies" only thing, or might not include Rust also.

Third, we still have a problem with constexpr functions, as they appear not covered.

Fourth, my original plan would have been, once the three mentioned points are solved, to contribute this solution to toolchains_llvm, as obviously this can be reused by a way broader community.

Anyhow, it can be decided that this is "good enough" for now, and these problems are tackled in an incremental way. Also just raising awareness for them.

On a personal note - while the licenses and copyrights that we utilize in this project fully allow a copying of the code to different places without touching the license + copyright, I still think it follows best practices to give at least some credit / blame for the original author. Feels quite bad if a topic where quite some time was invested is just copied over. I would have loved to see at least a clean commit with the original state and author, and then the improvements, refactorings on top of it. (I do not require to change this, but I would like to raise awareness).

Hi Jan, thanks for the detailed write-up. This is genuinely useful context.

On the technical points:

  • Baseline coverage / rules_cc: Agreed. The heuristic line counts in this PR are explicitly labeled as estimates precisely because we know proper baseline generation needs upstream work in rules_cc first. That's not something we tried to paper over.
  • QNX + Rust interleaving: That's not on our radar yet. Good to know it's an active problem. If the solution ends up language-agnostic, landing it there rather than in cpp_policies alone makes sense.
  • constexpr: Known and annoying. Nothing to add here.

On attribution: you're right to call this out, and I'll be direct: It should have been handled better from the start. A commit that establishes the original state with proper authorship, followed by the changes on top, would have been the clean way to do it. That ship has sailed for the commit history, but I've updated the PR description to explicitly credit your PR #549 as the origin of the pipeline. Not a substitute for getting the history right, but at least the record is there.

The plan: Merge this as "good enough for now", and track the three open items as follow-ups. Does that work for you?

olivembo added 3 commits July 2, 2026 09:45
score_instrumented_sources_manifest only emitted the manifest .txt
listing source paths, never propagating the actual source Files as
runfiles. Under the linux-sandbox strategy the reporter therefore
couldn't find those files on disk, so _find_untested_sources() silently
dropped every manifest entry and 0%-coverage files never appeared in
the report.

Even after fixing runfiles propagation, a second bug remained:
_find_untested_sources() resolved each manifest path and rejected it
if it fell outside workspace_root. Runfiles are frequently symlinks
that legitimately resolve to the real on-disk source tree outside the
sandboxed workspace_root, so this check rejected every legitimate
entry as a false-positive path traversal.

- _instrumented_sources_manifest_impl: return runfiles for the source
  Files (not just the manifest file) via DefaultInfo, and expose the
  InstrumentedSourcesInfo provider.
- _score_coverage_reporter_impl: merge the manifest target's
  default_runfiles into the wrapper so the source files are present on
  disk when the reporter runs sandboxed.
- _find_untested_sources: reject path traversal based on the raw
  manifest-relative path (no ".." components) instead of
  resolve()-then-containment, so legitimate runfiles symlinks are no
  longer dropped. Returns (abs_path, rel_path) pairs so callers can
  display the clean manifest-relative name instead of the resolved
  on-disk path.
- _augment_html_with_untested: use the manifest-relative name for
  display instead of recomputing it from the resolved absolute path,
  which leaked the full host filesystem path once symlinks were
  followed.
- Add regression test covering manifest entries reached only through
  symlinks.
…s symlink

Clicking a file in the HTML coverage report, or opening a source referenced
in lcov.dat (IDE coverage gutters, SonarQube, ...), resulted in
file-not-found. The links pointed into a path that no longer existed once
the coverage-report-generator action's sandbox was torn down.

Root cause: workspace_root was computed by taking the parent of
Runfiles.Rlocation(MODULE.bazel) directly. Under linux-sandbox, Rlocation()
returns a path inside the runfiles tree, which is itself a symlink into the
reporter action's own (ephemeral) sandbox - not the real workspace. Since
workspace_root feeds --path-equivalence for both llvm-cov show and llvm-cov
export, every SF: entry and every per-file HTML link for normal (tested)
source files was built from that dead sandbox path (e.g.
.../reporter_wrapper.sh.runfiles/_main/...) instead of the real,
stable workspace directory.

Extract the computation into _resolve_workspace_root() and resolve the
symlink before taking its parent. Add a regression test that reproduces
the runfiles-symlink layout.
Sums LH/LF across the final LCOV report (real + synthetic 0% records) to surface a combined coverage estimate in both the text summary and HTML index, clearly labeled as a heuristic.
The synthetic "Not Linked Into Tests" pages dumped the whole file into a single <pre> block, so they never used the line-number/uncovered-line
classes style.css actually styles, next to a real llvm-cov page they looked broken/unstyled even though style.css loaded fine. Render one row per line instead, matching llvm-cov's own markup, and link control.js so keyboard navigation works on these pages too.
@nradakovic nradakovic linked an issue Jul 3, 2026 that may be closed by this pull request
10 tasks
@nradakovic

Copy link
Copy Markdown
Member

OK, so it took me a while to understand implementation design here (still haven't cover everything), to follow up on every discussion made (and probably I missed ton of them) and finally to review the PR. To be fair, the drive of the topic is in mess. Several people across different working community groups, across different modules are involved, everyone has it's own 5 cents to present and when you put all of this into mix, no wonder why no one wants to be maintainer of it. Anyway, let's at least try to fix this.

  1. @olivembo @castler is there a single place where this topic is documented? I found issues and PRs in communication, reference integration and on CI workflows. This has to be consolidated and put in one place. I don't mind doing this work, but I need to know if there is something more than what I found so far. If not, all good, I will create overlord Epic here and we can swing it from there.
  2. As it seems, there are plans to move this beyond usage of S-CORE. I would like to have plan how to move forward with this and who will take this on him to finalize implementation.
    I will soon provide PR review for implementation.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 27 changed files in this pull request and generated no new comments.

@nradakovic nradakovic self-requested a review July 3, 2026 10:55
@olivembo

olivembo commented Jul 3, 2026

Copy link
Copy Markdown
Author

OK, so it took me a while to understand implementation design here (still haven't cover everything), to follow up on every discussion made (and probably I missed ton of them) and finally to review the PR. To be fair, the drive of the topic is in mess. Several people across different working community groups, across different modules are involved, everyone has it's own 5 cents to present and when you put all of this into mix, no wonder why no one wants to be maintainer of it. Anyway, let's at least try to fix this.

1. @olivembo @castler is there a single place where this topic is documented? I found issues and PRs in communication, reference integration and on CI workflows. This has to be consolidated and put in one place. I don't mind doing this work, but I need to know if there is something more than what I found so far. If not, all good, I will create overlord Epic here and we can swing it from there.

2. As it seems, there are plans to move this beyond usage of S-CORE. I would like to have plan how to move forward with this  and who will take this on him to finalize implementation.
   I will soon provide PR review for implementation.

Thanks for taking the time @nradakovic

I just got to know this PR: eclipse-score/baselibs#295

As mentioned: I thinks this PR at the moment is the most centralized one, but I also wrote with @castler and we agreed that a final solution should even be at a higher place (e.g. in rules_cpp), but that this is at least on step into the direction that we can provide a first coverage report to the modules do not fly blind and have no clue about there current coverage.

Comment thread coverage/coverage.bazelrc
Comment thread coverage/merger.py Outdated
profdata_dir.mkdir(exist_ok=True)
profdata_file = profdata_dir / "target.profdata"

llvm_profdata = os.environ.get("LLVM_PROFDATA")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be an issue if the consumer doesn't sets it. I see reporter already has label for it, so I wonder why we don't do it for merger as well. We create a small executable script on the fly that has default label for it and we wire it to the command coverage --coverage_output_generator=//tools/coverage:merger_wrapper. Make sense?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Addressed with #064db8173f96391cd4f1d68faa6f1eeb940d6a2

@4og

4og commented Jul 3, 2026

Copy link
Copy Markdown
Member

The eclipse-score/baselibs#295 is just to try things out before centralizing it.

The findings are captured in eclipse-score/baselibs#289 Unfortunately, llvm-cov doesn't perform well when it comes to computing line coverage per file. If we are to adopt it in S-CORE we should find a solution for it, ideally fixing it upstream.

I would also be interesting to see how this works together with lcov.

llvm-cov export to JSON --> llvm2lcov --> genhtml

Latest versions of genhtml have a differential view mode with git integration.

olivembo added 5 commits July 6, 2026 15:09
LLVM_PROFDATA is never set by default anywhere, so any consumer pointing
--coverage_output_generator directly at :merger had a silently broken
coverage collection step. Add score_coverage_merger, mirroring
score_coverage_reporter's pattern: it generates a wrapper script that
resolves llvm-profdata from its own runfiles and passes it explicitly,
falling back to LLVM_PROFDATA only for legacy direct-:merger consumers.

The generated wrapper self-derives RUNFILES_DIR from its own path rather
than trusting the env var value, since Bazel invokes
--coverage_output_generator as a nested tool inside the per-test coverage
action, which has its own (different) RUNFILES_DIR already set.
Same class of bug as the merger wrapper: if this wrapper is ever invoked as
a nested tool from inside another action, the ambient RUNFILES_DIR would
resolve paths against the wrong runfiles tree. reporter_wrapper is
currently only ever invoked standalone so this was dormant, but self-derive
from the script's own path first for consistency and defense-in-depth.
When coverage is built in a devcontainer (workspace at /workspace/...) and
consumed on the host (at /home/user/...), or when an archive is created and
unpacked elsewhere, absolute SF: paths in the LCOV file break IDE coverage
gutters, SonarQube ingestion, and other tooling.

Strip workspace_root prefix from all SF: entries so paths are relative to
the project root, making the report portable across environments.
llvm-cov embeds absolute paths in the source-name-title div of every
per-file coverage page. When a report is built in one environment (e.g. a
devcontainer at /workspace/...) and viewed in another (host, unpacked
archive), the displayed paths are misleading even though the intra-HTML
navigation links remain functional.

Post-process the HTML to strip workspace_root prefix from all
source-name-title entries, making the displayed paths match the relative
structure consumers see in their own environment.
@olivembo

Copy link
Copy Markdown
Author

Hi @nradakovic anything left to do from your side to get an approval?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code coverage Issue or pull request for code coverage enhancement New feature or request p3 Medium/Low - handle it within normal process wip Work in progress

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Improve Accuracy of Code Coverage Reports

8 participants