Skip to content

use llvm cov for coverage and justification#549

Merged
castler merged 4 commits into
eclipse-score:mainfrom
castler:js_use_llvm_cov_for_coverage_and_justification
Jun 18, 2026
Merged

use llvm cov for coverage and justification#549
castler merged 4 commits into
eclipse-score:mainfrom
castler:js_use_llvm_cov_for_coverage_and_justification

Conversation

@castler

@castler castler commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

No description provided.

- Move quality/coverage.bazelrc to quality/coverage/coverage.bazelrc
  for better organization alongside the coverage tooling.
- Enable user.bazelrc support via try-import in .bazelrc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@castler castler enabled auto-merge June 15, 2026 12:23

@LittleHuba LittleHuba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Really nice work!

# at the report level.

# Exclude mock files.
.*mock.*\.(h|hpp|cpp)$

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Better make it .*_mock.*\.(h|hpp|cpp)$. I'd prefer to start very strict and relax it when we find files that should be excluded.

.*mock.*\.(h|hpp|cpp)$

# Exclude external dependencies (anything under external/).
.*/external/.*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not a big fan of the starting .*. What is the base path? Could we live with external/.*?

Comment thread quality/coverage/llvm_cov/merger.py Outdated
return object_files


def find_llvm_bin() -> Path:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Uff... not really sure whether this is a good idea.
This could silently break without us being aware.

Two problems I see:

  • This might become unhermetic if we ever end up with a llvm-toolchain that does not provide llvm-cov for some reason
  • globbing through all of "external" invites usage of an llvm-cov that ends up there for a different reason (e.g. sysroot for a different tool)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

maybe try to hard code it - ,aybe look at it next.

Comment thread quality/coverage/llvm_cov/merger.py Outdated
return llvm_cov.resolve().parent

# Search from ROOT.
root = Path(os.environ.get("ROOT", "."))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Again, not sure whether . is a good fallback here. The fallback also makes no sense since it results in the same logic as ll254.

Comment on lines +134 to +139
# Also remove any other symlinks pointing into sandbox paths.
for entry in directory.iterdir():
if entry.is_symlink():
target = os.readlink(entry)
if "sandbox" in target:
entry.unlink()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we at least issue a warning for such symlinks?
Better to know which ones point in and then add them here explicitly than just removing everything that is there blindly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

maybe log into undeclared test outputs (maybe do later)

Comment on lines +33 to +49
┌───────────────────────────┐
│ generate_coverage_html.sh │ ◄── bazel run //quality/coverage:generate_coverage_html
└────┬──────────────────────┘
│ Extracts html_report/ → cpp_coverage/
┌─────────────┐ coverage_justifications.yaml
│ justify.py │ ◄── + source files (COV_JUSTIFIED markers)
└────┬────────┘
│ manifest.json: {file → {line → justification}}
┌───────────────────────┐
│ effective_coverage.py │ ◄── manifest.json + html_report/
└────┬──────────────────┘
│ • Modifies HTML in-place (restyled justified lines/branches)
│ • report.json + summary.txt
Console output: effective coverage summary

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it would help to argue why we do not call this from reporter.py.
If it is because we want to retain the original data without the justifications applied, we could just output the modified report next to the original report.


## Requirements

The coverage pipeline was built to satisfy the following requirements:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we put those directly into TRLC? Nit-pick can be done in a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can do in an other iteration

Comment thread quality/coverage/README.md
Comment thread quality/quality.md Outdated
Code coverage is generated using LLVM's source-based coverage instrumentation. The instrumentation filter is configured in [`quality/coverage/coverage.bazelrc`](coverage/coverage.bazelrc) to cover `//score/message_passing` and `//score/mw/com` while excluding test and benchmark code.

### Running Coverage
HTML reports are generated directly by `llvm-cov show` — no intermediate LCOV conversion or `genhtml` is needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please just drop everything after the dash. It just confuses.

Comment thread quality/quality.md Outdated
Comment on lines +158 to +197
#### Adding a Justification

1. Add an entry to `coverage_justifications.yaml`:

```yaml
justifications:
- id: my-justification-id
category: defensive_programming # or: tool_false_positive, platform_specific, other
reason: >
Explanation of why these lines cannot be covered.
locations:
- file: score/mw/com/impl/some_file.cpp
line_start: 42
line_end: 45
```

2. Alternatively, reference the justification from code (no `locations` needed in YAML):

```cpp
unreachable_code(); // COV_JUSTIFIED my-justification-id

// COV_JUSTIFIED_START my-justification-id
defensive_block();
more_defensive_code();
// COV_JUSTIFIED_STOP
```

#### Effective Coverage

The coverage pipeline calculates:
- **Raw line coverage**: actual lines hit / total instrumented lines
- **Effective line coverage**: (lines hit + justified lines) / total instrumented lines
- **Raw branch coverage**: actual branches hit / total branches
- **Effective branch coverage**: (branches hit + justified branches) / total branches

The index page shows a banner with overall effective coverage and updates per-file percentages. The `generate_coverage_html` script prints the full summary. Set `COVERAGE_THRESHOLD` to enforce a minimum (default: 100%):

```bash
COVERAGE_THRESHOLD=95 bazel run //quality/coverage:generate_coverage_html
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not really a huge fan of this duplication. Can we not just link to the actual readme for coverage?

# on PATH inside the run environment.
LCOV_DAT="${BUILD_WORKSPACE_DIRECTORY}/bazel-out/_coverage/_coverage_report.dat"
# Resolve OUTPUT_DIR to absolute path (relative to workspace root).
OUTPUT_DIR="${BUILD_WORKSPACE_DIRECTORY}/${OUTPUT_DIR}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What if OUTPUT_DIR is an absolute path?

"--show-region-summary=0",
]

cxxfilt = llvm_bin_path.parent / "llvm-cxxfilt"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
cxxfilt = llvm_bin_path.parent / "llvm-cxxfilt"
cxxfilt = r.Rlocation("llvm_toolchain/llvm-cxxfilt")


# Get llvm tools via runfiles.
r = Runfiles.Create()
llvm_bin_path = Path(r.Rlocation("llvm_toolchain/llvm-cov"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It feels hacky that in some places you use r.Rlocation to determine the path. And in others you do path traversal based on this path.

if [[ -z "\\$${RUNFILES_DIR:-}" ]]; then
if [[ -d "\\$$0.runfiles" ]]; then
export RUNFILES_DIR="\\$$0.runfiles"
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we have an else condition as a safeguard here?

castler and others added 3 commits June 18, 2026 16:01
…reports

Replace the genhtml/lcov-based coverage report pipeline with a native
llvm-cov implementation that produces HTML reports directly from profraw
data.

Key changes:
- Add merger.py as --coverage_output_generator (per-test profraw→profdata+HTML)
- Add reporter.py as --coverage_report_generator (aggregate all tests)
- Add filter_regexes.txt config file for configurable source filtering
- Update coverage.bazelrc with llvm-cov flags (--experimental_use_llvm_covmap,
  --dynamic_mode=off, continuous profiling mode)
- Update generate_coverage_html.sh to extract from the new zip format

The source filtering is configurable via filter_regexes.txt (loaded from
runfiles) and can be overridden via --filter_sources CLI argument.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a justification system that allows developers to argue lines that
cannot be covered by tests (defensive programming, tool false positives,
platform-specific code).

Components:
- justify.py: YAML loader + source scanner → resolved manifest JSON
- effective_coverage.py: HTML post-processor + effective coverage calculator
- coverage_justifications.yaml: initial justification database
- Integration into generate_coverage_html.sh (runs after report generation)

Justifications can be applied via:
- In-code markers: // COV_JUSTIFIED <id>
- YAML locations: file + line range (no code change needed)

Justified lines appear yellow in the HTML report, and effective coverage
is calculated as (covered + justified) / instrumented.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add quality/coverage/README.md with pipeline architecture overview
- Add quality/coverage/llvm_cov/README.md with tool-level documentation
- Update quality/quality.md to reflect new llvm-cov pipeline and
  link to the detailed documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@LittleHuba LittleHuba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving as remaining issues are nitpicks that can be resolved in a follow-up.

@castler castler added this pull request to the merge queue Jun 18, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 18, 2026
@castler castler added this pull request to the merge queue Jun 18, 2026
Merged via the queue into eclipse-score:main with commit b66f303 Jun 18, 2026
9 checks passed
@castler castler deleted the js_use_llvm_cov_for_coverage_and_justification branch June 18, 2026 15:09
@github-project-automation github-project-automation Bot moved this from Backlog to Done in COM - Communication FT Jun 18, 2026
@olivembo

Copy link
Copy Markdown

@castler Moved and adapted your implementation to the cpp_policies repo.

PR is: eclipse-score/score_cpp_policies#9

Can you check?

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants