|
| 1 | +# Coverage Infrastructure |
| 2 | + |
| 3 | +This directory contains the tooling to generate, post-process, and report C++ code coverage for the Score Communication project using LLVM's source-based coverage instrumentation (`llvm-cov`). |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +``` |
| 8 | +quality/coverage/ |
| 9 | +├── README.md ← You are here |
| 10 | +├── BUILD ← Bazel target for generate_coverage_html |
| 11 | +├── coverage.bazelrc ← Bazel coverage configuration flags |
| 12 | +├── coverage_justifications.yaml ← Central justification database |
| 13 | +├── generate_coverage_html.sh ← Orchestrator script (entry point) |
| 14 | +└── llvm_cov/ ← Python tools for coverage processing |
| 15 | + ├── README.md ← Detailed tool documentation |
| 16 | + ├── BUILD ← Bazel targets for Python tools |
| 17 | + ├── merger.py ← Per-test coverage output generator |
| 18 | + ├── reporter.py ← Final combined report generator |
| 19 | + ├── justify.py ← Justification resolver |
| 20 | + └── effective_coverage.py ← HTML post-processor & effective coverage calculator |
| 21 | +``` |
| 22 | + |
| 23 | +## Requirements |
| 24 | + |
| 25 | +The coverage pipeline was built to satisfy the following requirements: |
| 26 | + |
| 27 | +### REQ-COV-001: Native llvm-cov HTML Reports |
| 28 | + |
| 29 | +Coverage reports **must** be generated directly by `llvm-cov show` using LLVM's source-based coverage (`--experimental_use_llvm_covmap`). No intermediate LCOV-to-HTML conversion (genhtml) is used. This provides accurate source-level coverage including branch and expansion views. |
| 30 | + |
| 31 | +### REQ-COV-002: Instrumentation Filtering |
| 32 | + |
| 33 | +Only project source code under `//score/message_passing` and `//score/mw/com` shall be instrumented and reported. Tests, benchmarks, and external/third-party code must be excluded from the report. |
| 34 | + |
| 35 | +> **Note:** `--experimental_use_llvm_covmap` causes Bazel to instrument ALL targets regardless of `--instrumentation_filter`. Actual source filtering is enforced by `--ignore-filename-regex` in the merger and reporter. See `coverage.bazelrc` for details. |
| 36 | +
|
| 37 | +### REQ-COV-003: Coverage Justification Infrastructure |
| 38 | + |
| 39 | +A YAML-based justification system must allow developers to "argue" non-covered lines and branches to achieve 100% effective coverage. Justified lines must: |
| 40 | +- Be tracked in a central YAML file with unique IDs, categories, and rationale |
| 41 | +- Optionally be referenced from code via `COV_JUSTIFIED` markers |
| 42 | +- Appear visually distinct (yellow/orange) in the HTML report |
| 43 | +- Be reflected in both per-file and total coverage percentages |
| 44 | + |
| 45 | +### REQ-COV-004: Effective Coverage Calculation |
| 46 | + |
| 47 | +The system must calculate and display: |
| 48 | +- **Raw coverage**: actual lines/branches hit ÷ total instrumented lines/branches |
| 49 | +- **Effective coverage**: (hit + justified) ÷ total |
| 50 | + |
| 51 | +Both line and branch effective coverage must be shown in the summary, per-file index table, and totals row. |
| 52 | + |
| 53 | +### REQ-COV-005: Stale Justification Detection |
| 54 | + |
| 55 | +Justifications for lines/branches that are actually covered by tests must be detected and reported as stale warnings, enabling cleanup. |
| 56 | + |
| 57 | +### REQ-COV-006: Template Instantiation Handling |
| 58 | + |
| 59 | +For C++ templates with multiple instantiations, a line or branch is considered "covered" if ANY instantiation covers it (consistent with llvm-cov semantics). This prevents inflated totals from repeated template expansions. |
| 60 | + |
| 61 | +### REQ-COV-007: Threshold Enforcement |
| 62 | + |
| 63 | +The pipeline must support a configurable effective coverage threshold (default: 100%) and emit a warning when coverage falls below it. |
| 64 | + |
| 65 | +## Quick Start |
| 66 | + |
| 67 | +### 1. Run Coverage Collection |
| 68 | + |
| 69 | +```bash |
| 70 | +# Full project |
| 71 | +bazel coverage //... |
| 72 | + |
| 73 | +# Specific target |
| 74 | +bazel coverage //score/message_passing:client_connection_test_linux |
| 75 | +``` |
| 76 | + |
| 77 | +### 2. Generate the HTML Report |
| 78 | + |
| 79 | +```bash |
| 80 | +bazel run //quality/coverage:generate_coverage_html |
| 81 | +``` |
| 82 | + |
| 83 | +This extracts the HTML report to `cpp_coverage/`, runs justification processing, and prints the coverage summary. Open the report: |
| 84 | + |
| 85 | +```bash |
| 86 | +xdg-open cpp_coverage/index.html |
| 87 | +``` |
| 88 | + |
| 89 | +### 3. Create an Archive (CI) |
| 90 | + |
| 91 | +```bash |
| 92 | +bazel run //quality/coverage:generate_coverage_html -- --archive coverage-report |
| 93 | +``` |
| 94 | + |
| 95 | +Creates `coverage-report.zip` containing the HTML report, LCOV data, and JUnit XML test results. |
| 96 | + |
| 97 | +## Pipeline Architecture |
| 98 | + |
| 99 | +The coverage pipeline has two phases: |
| 100 | + |
| 101 | +### Phase 1: Bazel Coverage Collection |
| 102 | + |
| 103 | +Configured by `coverage.bazelrc`, Bazel runs tests with coverage instrumentation enabled: |
| 104 | + |
| 105 | +``` |
| 106 | +bazel coverage //... |
| 107 | + │ |
| 108 | + ├── Per-test: merger.py (--coverage_output_generator) |
| 109 | + │ • Receives .profraw files from test execution |
| 110 | + │ • Merges into .profdata via llvm-profdata |
| 111 | + │ • Packages profdata + metadata into a zip |
| 112 | + │ |
| 113 | + └── Final: reporter.py (--coverage_report_generator) |
| 114 | + • Merges all per-test profdata into one |
| 115 | + • Runs llvm-cov show → HTML report |
| 116 | + • Runs llvm-cov export → LCOV data |
| 117 | + • Runs llvm-cov report → text summary |
| 118 | + • Packages everything into _coverage_report.dat (zip) |
| 119 | +``` |
| 120 | + |
| 121 | +### Phase 2: Report Extraction & Justification |
| 122 | + |
| 123 | +``` |
| 124 | +bazel run //quality/coverage:generate_coverage_html |
| 125 | + │ |
| 126 | + └── generate_coverage_html.sh |
| 127 | + ├── Extract HTML from _coverage_report.dat → cpp_coverage/ |
| 128 | + ├── justify.py: YAML + code markers → manifest.json |
| 129 | + ├── effective_coverage.py: Post-process HTML + calculate effective % |
| 130 | + └── Print summary + threshold check |
| 131 | +``` |
| 132 | + |
| 133 | +## Configuration |
| 134 | + |
| 135 | +### coverage.bazelrc |
| 136 | + |
| 137 | +Key settings: |
| 138 | + |
| 139 | +| Flag | Purpose | |
| 140 | +|------|---------| |
| 141 | +| `--experimental_use_llvm_covmap` | Use LLVM source-based coverage (not gcov) | |
| 142 | +| `--instrumentation_filter` | Documents intended scope (not enforced by Bazel with covmap) | |
| 143 | +| `--coverage_output_generator` | Points to `merger.py` for per-test processing | |
| 144 | +| `--coverage_report_generator` | Points to `reporter.py` for final aggregation | |
| 145 | +| `--test_env=LLVM_PROFILE_CONTINUOUS_MODE=1` | Enables profiling of abnormal terminations | |
| 146 | +| `-mllvm -runtime-counter-relocation` | Required for continuous-mode profiling with LLVM | |
| 147 | + |
| 148 | +### Environment Variables |
| 149 | + |
| 150 | +| Variable | Default | Description | |
| 151 | +|----------|---------|-------------| |
| 152 | +| `COVERAGE_THRESHOLD` | `100` | Minimum effective line coverage % (warning if below) | |
| 153 | + |
| 154 | +## Coverage Justifications |
| 155 | + |
| 156 | +See [`coverage_justifications.yaml`](coverage_justifications.yaml) for the justification database and [`llvm_cov/README.md`](llvm_cov/README.md) for detailed documentation of the justification tools. |
| 157 | + |
| 158 | +### Adding a Justification |
| 159 | + |
| 160 | +1. **Via YAML** — add an entry to `coverage_justifications.yaml`: |
| 161 | + |
| 162 | +```yaml |
| 163 | +justifications: |
| 164 | + - id: my-unique-id # kebab-case, must be unique |
| 165 | + category: defensive_programming # or: tool_false_positive, platform_specific, other |
| 166 | + reason: > |
| 167 | + Explanation of why these lines cannot be covered by tests. |
| 168 | + locations: |
| 169 | + - file: score/mw/com/impl/some_file.cpp |
| 170 | + line_start: 42 |
| 171 | + line_end: 45 |
| 172 | +``` |
| 173 | +
|
| 174 | +2. **Via code markers** — reference the ID from source (no `locations` needed in YAML): |
| 175 | + |
| 176 | +```cpp |
| 177 | +unreachable_code(); // COV_JUSTIFIED my-unique-id |
| 178 | +
|
| 179 | +// COV_JUSTIFIED_START my-unique-id |
| 180 | +defensive_block(); |
| 181 | +more_defensive_code(); |
| 182 | +// COV_JUSTIFIED_STOP |
| 183 | +``` |
| 184 | + |
| 185 | +Both methods can be combined. A justification covers both the line and any branches on that line. |
| 186 | + |
| 187 | +We strongly suggest though to use the in-code marker where possible, as this better supports refactorings and avoids |
| 188 | +better that justifications get outdated. |
| 189 | + |
| 190 | +### Justification Categories |
| 191 | + |
| 192 | +| Category | Use Case | |
| 193 | +|----------|----------| |
| 194 | +| `defensive_programming` | Unreachable code kept as safety guard (e.g., default case in exhaustive switch) | |
| 195 | +| `tool_false_positive` | Coverage tool incorrectly marks line as uncovered | |
| 196 | +| `platform_specific` | Code path only reachable on platforms not under test | |
| 197 | +| `other` | Any other valid reason | |
| 198 | + |
| 199 | +### Visual Indicators in HTML Report |
| 200 | + |
| 201 | +| Color | Meaning | |
| 202 | +|-------|---------| |
| 203 | +| **Green** | Covered by tests | |
| 204 | +| **Red** | Not covered (needs tests or justification) | |
| 205 | +| **Yellow/Orange** | Justified — not covered but argued with rationale | |
| 206 | + |
| 207 | +The index page shows a banner with overall effective coverage and updates per-file percentages in the table to reflect justifications. |
0 commit comments