Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,10 @@ jobs:
with:
script: |
const fs = require('fs');
let body = fs.readFileSync('coverage-comment.md', 'utf8');
let body = fs.readFileSync('coverage-comment.md', 'utf8').trim();
const url = process.env.REPORT_URL;
if (url) {
body += `\n\n[Full annotated report](${url})`;
body = `[${body}](${url})`;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
Expand Down
2 changes: 1 addition & 1 deletion docs/source/user_guide/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ You can run these locally with `pre-commit run -a` after `pip install pre-commit
- **check-markup-links** (`check_markup_links.yml`) — validates links in documentation
- **linux / macosx / win** — build and test on each platform
- **test-gpu** — GPU-specific tests
- **coverage report** — a diff coverage summary is posted as a PR comment on each push, with a link to the full annotated report. This includes kernel-level branch coverage. See [Kernel code coverage](kernel_coverage.md) for details.
- **coverage report** — a one-line diff coverage summary is posted as a PR comment on each push, linking to the full annotated report. This includes kernel-level branch coverage. See [Kernel code coverage](kernel_coverage.md) for details.

### Line wrapping check (`check_wrapping.yml`)

Expand Down
6 changes: 0 additions & 6 deletions docs/source/user_guide/kernel_coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ One edge case: kernel calls inside a `qd.ad.Tape` with `validation=True` will no

Coverage probes change the compiled kernel, so the offline cache will see them as new kernels and recompile. This is expected and does not affect correctness, but the first run with coverage enabled will be slower if you normally rely on cached kernels.

## CI integration

The CI workflow posts a compact coverage summary as a PR comment on each push, showing per-file coverage percentages and missing line ranges. The full annotated report (with per-line hit/miss markers) is published as a GitHub Check — the PR comment includes a link to it. A **new comment** is created each time (rather than editing the previous one) so that the PR timeline shows a clear chronological sequence of commits and their corresponding coverage results.

The report covers only Python files (`.py`) and only lines added or modified in the PR diff — unchanged lines are not reported. C++ files are not included in the coverage report.

Comment thread
duburcqa marked this conversation as resolved.
## Under the hood

When `QD_KERNEL_COVERAGE=1` is set, quadrants rewrites the Python AST of each `@qd.kernel` and `@qd.func` before compilation. It inserts lightweight probe statements (`field[probe_id] = 1`) at each source line. These probes compile as ordinary field stores and execute on the device alongside your kernel code.
Expand Down
31 changes: 7 additions & 24 deletions tests/coverage_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,41 +186,24 @@ def output(self):


class _MarkdownSummaryRenderer(_Renderer):
"""Compact markdown summary (for PR comment, under 65K limit)."""
"""One-line summary for the PR comment (turned into a link by the workflow)."""

def __init__(self):
self._buf = []

def _print(self, text=""):
self._buf.append(text)
self._total_hit = self._total_miss = 0
self._total_pct = 0.0

def begin(self, total_hit, total_miss, total_pct):
self._total_hit, self._total_miss, self._total_pct = total_hit, total_miss, total_pct
self._files: list[tuple[str, float, list[int]]] = []
commit = _get_commit_hash()
heading = f"## Coverage Report (`{commit}`)\n" if commit else "## Coverage Report\n"
self._print(heading)

def begin_file(self, filename, pct, missing):
self._files.append((filename, pct, missing))
pass

def finish(self):
overall = _get_overall_coverage()
self._print("| File | Coverage | Missing |")
self._print("|------|----------|---------|")
for filename, pct, missing in self._files:
icon = "🟢" if pct >= 80 else "🔴"
missing_str = _format_ranges(missing) if missing else ""
self._print(f"| {icon} `{filename}` | {pct:.0f}% | {missing_str} |")
self._print()
parts = [f"**Diff coverage: {self._total_pct:.0f}%**"]
if overall:
parts.append(f"Overall: {overall}")
parts.append(f"{self._total_hit + self._total_miss} lines, {self._total_miss} missing")
self._print(" · ".join(parts))
pass

def output(self):
return "\n".join(self._buf)
total = self._total_hit + self._total_miss
return f"Diff coverage: {self._total_pct:.0f}% · {total} lines, {self._total_miss} missing"


_HTML_CSS = """\
Expand Down
Loading