Skip to content

fix(ci): wire coverage observability into CI pipelines (Phase 1)#1404

Merged
sergio-sisternes-epam merged 3 commits into
mainfrom
coverage/1398-phase1
May 19, 2026
Merged

fix(ci): wire coverage observability into CI pipelines (Phase 1)#1404
sergio-sisternes-epam merged 3 commits into
mainfrom
coverage/1398-phase1

Conversation

@sergio-sisternes-epam
Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam commented May 19, 2026

TL;DR

Adds test coverage reporting to both CI pipelines (unit and integration) as Phase 1 of the progressive coverage ratchet (#1398). Coverage summaries appear in GitHub Actions job summaries as markdown tables — no PR is blocked.

Note

Phase 1 is observability only — no fail_under gates. Phases 2–4 (#1401, #1402, #1400) will progressively tighten thresholds.

Problem (WHY)

Approach (WHAT)

# Fix
1 Add [tool.coverage.run] and [tool.coverage.report] to pyproject.toml with branch = true, parallel = true, and standard exclusion patterns
2 Wire --cov + JSON report into ci.yml unit test step; add coverage summary step
3 Wire --cov into integration shard PYTEST_EXTRA_ARGS; upload .coverage data files per shard with include-hidden-files: true
4 Fan-in job: coverage combinecoverage json → shared summary script for correct multi-shard merge
5 Create scripts/coverage-summary.py — stdlib-only shared renderer used by both pipelines
6 All coverage steps use continue-on-error: true / if: always() so coverage never blocks the required check

Implementation (HOW)

  • pyproject.toml — Adds [tool.coverage.run] (source, branch, parallel) and [tool.coverage.report] (exclusions for pragma: no cover, TYPE_CHECKING, NotImplementedError, @overload).
  • scripts/coverage-summary.py — New file. Reads coverage.json, renders a markdown summary (overall %, metrics table, collapsible lowest-coverage files). Writes to stdout and $GITHUB_STEP_SUMMARY. Exits 0 on missing input (soft-fail). Stdlib only, ASCII source.
  • .github/workflows/ci.yml — Adds --cov --cov-report=term-missing:skip-covered --cov-report=json:coverage.json to the pytest invocation. Calls the shared summary script.
  • .github/workflows/ci-integration.yml — Each shard produces a .coverage binary data file, renamed per-shard and uploaded with include-hidden-files: true. Fan-in job: checkout → Python → pip install coverage[toml] → download with merge-multiple: truecoverage combinecoverage json → summary script. Coverage summary runs before shard-result gate so observability is preserved even when shards fail.

Diagrams

Legend: Integration coverage data flow from shards to fan-in. The yellow region highlights the coverage combine merge path.

sequenceDiagram
    participant S as Shard Job 1..4
    participant A as Upload Artifact
    participant F as Fan-in Job

    S->>S: pytest --cov
    S->>S: mv .coverage .coverage.shard-N
    S->>A: upload .coverage.shard-N
    rect rgb(255, 247, 200)
        Note over F: NEW: proper coverage combine
        A-->>F: download all shards
        F->>F: coverage combine
        F->>F: coverage json
        F->>F: coverage-summary.py
    end
    F->>F: aggregate shard results
Loading

Trade-offs

  • coverage combine over JSON merge. coverage combine is the only correct way to aggregate parallel coverage data — it handles source-file deduplication and branch-level merging internally.
  • pip install coverage[toml] in fan-in, not full uv sync. The fan-in job only runs data processing (combine + json + summary), not tests. Installing just coverage keeps the step fast (~5s vs ~60s).
  • Coverage steps are soft-fail (continue-on-error). Trades coverage visibility for CI stability — if coverage collection breaks, the required check still passes. Acceptable for Phase 1 observability; will be revisited when gates are introduced in Phase 2+.
  • Scenario Evidence skip clause applies. This PR changes only CI workflow files and a standalone script — no CLI behaviour change, no user-facing code paths affected. Existing test suites run green without modification.

Benefits

  1. Coverage percentage visible in every CI run's job summary — contributors see impact before review.
  2. Lowest-coverage files surfaced in a collapsible table — identifies improvement targets without requiring local tooling.
  3. Integration coverage correctly merged across 4 shards via coverage combine.
  4. Shared summary script eliminates duplication — one renderer, consistent output for both pipelines.
  5. Coverage never blocks a PR — soft-fail design ensures observability does not become a gate.

Validation

Lint (ruff check + format)
$ uv run --extra dev ruff check src/ tests/
All checks passed!

$ uv run --extra dev ruff format --check src/ tests/
775 files already formatted

$ uv run --extra dev ruff check scripts/coverage-summary.py
All checks passed!
Coverage summary script — end-to-end test
$ uv run pytest tests/unit/test_apm_package.py --cov --cov-report=json:coverage-test.json -q
32 passed in 2.76s

$ python3 scripts/coverage-summary.py coverage-test.json --title "Test Run"
## Test Run

**Overall: 6%** (2,762/36,735 statements)

| Metric | Value |
|--------|-------|
| Statements | 36,735 |
| Covered | 2,762 |
| Missed | 33,973 |
| Coverage | 6% |
Missing-file edge case
$ python3 scripts/coverage-summary.py nonexistent.json
[!] coverage-summary: nonexistent.json not found -- skipping summary.
$ echo $?
0

How to test

  • Push this branch and observe the CI Build & Test (Linux) job summary — it should show a "Unit Test Coverage" markdown table with overall % and a collapsible lowest-coverage files section.
  • Enqueue a merge-queue run and observe the Integration Tests (Linux) fan-in job summary — it should show an "Integration Test Coverage" table with correctly merged shard data.
  • Verify no fail_under gate exists — coverage should never cause a red check.

Closes #1399

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

Copilot AI review requested due to automatic review settings May 19, 2026 16:37
@sergio-sisternes-epam sergio-sisternes-epam added area/ci-cd GitHub workflows, merge queue, gh-aw integrations, release pipeline. area/testing Test infrastructure, fixtures, e2e harness, coverage. quality labels May 19, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@sergio-sisternes-epam sergio-sisternes-epam marked this pull request as draft May 19, 2026 16:38
Sergio Sisternes and others added 2 commits May 19, 2026 17:39
Add [tool.coverage] config to pyproject.toml (branch coverage,
TYPE_CHECKING exclusions). Update ci.yml test step to collect
coverage and render a summary table to GITHUB_STEP_SUMMARY.

Add coverage collection to ci-integration.yml: each of the 4
shards uploads coverage.json as an artifact; the fan-in job
downloads all shards, merges totals, and writes an integration
coverage summary to GITHUB_STEP_SUMMARY.

No threshold gate -- this is observability-only to establish
a baseline.

Local unit baseline: 80% statement coverage (42905 stmts).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix critical bug: fan-in was summing coverage JSON totals across 4
  shards, quadruple-counting statements for files appearing in all shards.
  Now uploads .coverage binary data files and uses `coverage combine`
  for mathematically correct merge.

- Create scripts/coverage-summary.py: shared stdlib-only renderer that
  both CI pipelines call, producing markdown summary with overall %,
  metrics table, and collapsible lowest-coverage files section.

- Add include-hidden-files: true to shard artifact upload (dotfiles
  are excluded by default in actions/upload-artifact@v4).

- Make coverage summary soft-fail (continue-on-error) so missing or
  partial coverage data never blocks the required fan-in check.

- Reorder fan-in: coverage summary runs before shard-result gate so
  observability is preserved even when some shards fail.

Closes #1399

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sergio-sisternes-epam sergio-sisternes-epam marked this pull request as ready for review May 19, 2026 16:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 4 out of 4 changed files in this pull request and generated 5 comments.

Comment thread scripts/coverage-summary.py
Comment thread scripts/coverage-summary.py
Comment thread .github/workflows/ci-integration.yml
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci-integration.yml
Address review feedback: read covered_lines from the JSON totals
instead of computing it from num_statements - missing_lines. The
values are identical but using the explicit field is clearer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sergio-sisternes-epam sergio-sisternes-epam added this pull request to the merge queue May 19, 2026
Merged via the queue into main with commit c9d4797 May 19, 2026
44 checks passed
@sergio-sisternes-epam sergio-sisternes-epam deleted the coverage/1398-phase1 branch May 19, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci-cd GitHub workflows, merge queue, gh-aw integrations, release pipeline. area/testing Test infrastructure, fixtures, e2e harness, coverage. quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Coverage Phase 1: Wire observability into CI

3 participants