ci(coverage): collect code coverage and publish a summary (refs #15) #370

Merged
timothy merged 3 commits from feat/15-ci-coverage into main 2026-07-16 19:29:35 +02:00
Owner

What

Wires code-coverage collection into the Build & test (.NET) CI job and publishes a human-readable summary. This is a bounded increment of #15 (scope item 4: "Coverage — collect with coverlet and publish a summary; decide on a floor later"). #15 stays open as an increments tracker; this PR refs it, it does not close it.

Changes

  • ErsatzTV.Core.Tests, ErsatzTV.Tests — add coverlet.collector (already referenced by the other four *.Tests projects) so coverage is uniform across all six.
  • .gitea/workflows/docker-build.yml — the Test step now runs --collect:"XPlat Code Coverage" --results-directory ./coverage; a new Coverage summary step merges the per-project Cobertura reports with ReportGenerator (TextSummary → log, MarkdownSummaryGithub → job step summary). continue-on-error: true keeps it purely informational — a missing report or transient tool install never blocks a build. No coverage floor is enforced yet.
  • .gitignore — ignore /coverage/.
  • docs/ci-cd.md — document the coverage step under the test job.

Verification (local, worktree)

  • dotnet build of both changed test projects: 0 errors.
  • Ran dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage on a test project → Cobertura emitted; reportgenerator … -reporttypes:TextSummary;MarkdownSummaryGithub produced both summaries. Full flow proven end-to-end (bash, matching the CI shell).

Scope notes

  • No .cs changed → the fix-as-you-touch formatting gate (#311) is a no-op here.
  • Pure CI/tooling + docs; no server-state effect, so per the independent-review skip rubric an independent review pass is skippable — I'll still run a cold-context review over the diff before merge.
  • Rule promotions (suggestion→warning/error) remain out of scope for this increment (higher-risk; see #25 for the razor Sonar burn-down).

refs #15

## What Wires code-coverage collection into the `Build & test (.NET)` CI job and publishes a human-readable summary. This is a **bounded increment of #15** (scope item 4: "Coverage — collect with coverlet and publish a summary; decide on a floor later"). #15 stays open as an increments tracker; this PR **refs** it, it does not close it. ## Changes - **`ErsatzTV.Core.Tests`, `ErsatzTV.Tests`** — add `coverlet.collector` (already referenced by the other four `*.Tests` projects) so coverage is uniform across all six. - **`.gitea/workflows/docker-build.yml`** — the `Test` step now runs `--collect:"XPlat Code Coverage" --results-directory ./coverage`; a new **Coverage summary** step merges the per-project Cobertura reports with ReportGenerator (`TextSummary` → log, `MarkdownSummaryGithub` → job step summary). `continue-on-error: true` keeps it purely informational — a missing report or transient tool install never blocks a build. **No coverage floor is enforced yet.** - **`.gitignore`** — ignore `/coverage/`. - **`docs/ci-cd.md`** — document the coverage step under the `test` job. ## Verification (local, worktree) - `dotnet build` of both changed test projects: **0 errors**. - Ran `dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage` on a test project → Cobertura emitted; `reportgenerator … -reporttypes:TextSummary;MarkdownSummaryGithub` produced both summaries. Full flow proven end-to-end (bash, matching the CI shell). ## Scope notes - **No `.cs` changed** → the fix-as-you-touch formatting gate (#311) is a no-op here. - Pure CI/tooling + docs; **no server-state effect**, so per the independent-review skip rubric an independent review pass is skippable — I'll still run a cold-context review over the diff before merge. - Rule promotions (suggestion→warning/error) remain out of scope for this increment (higher-risk; see #25 for the razor Sonar burn-down). refs #15
timothy added 1 commit 2026-07-16 18:46:09 +02:00
ci(coverage): collect code coverage and publish a summary (refs #15)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m29s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (pull_request) Has been cancelled
2090f7865c
#15 scope item 4 ("collect with coverlet and publish a summary; decide on
a floor later"). coverlet.collector was already referenced by four of the
six test projects but the CI Test step never passed --collect and nothing
summarized the result.

- Add coverlet.collector to ErsatzTV.Core.Tests and ErsatzTV.Tests so
  coverage is uniform across all *.Tests projects.
- CI Test step now runs --collect:"XPlat Code Coverage" --results-directory
  ./coverage, then a best-effort Coverage summary step merges the per-project
  Cobertura reports with ReportGenerator (TextSummary to the log,
  MarkdownSummaryGithub to the job step summary). No floor is enforced yet;
  continue-on-error keeps it purely informational.
- gitignore /coverage/; document the step in docs/ci-cd.md (test job).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timothy added 1 commit 2026-07-16 18:49:30 +02:00
ci(coverage): make ReportGenerator install idempotent + pinned (review follow-up)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 3m59s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Failing after 7m55s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 8m38s
a886fd2824
Cold-review should-fix on PR #370: `dotnet tool install` errors under set -e
if the tool is already present (retried step / cached runner image), which
would silently degrade the continue-on-error summary step to a no-op. Use
`dotnet tool update` (install-or-update) pinned to 5.5.10 for reproducible
output. Also order coverlet.collector after CliWrap to keep the ItemGroup
alphabetical (nit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Independent cold-context review (two passes, review-only scope):

  • Initial diff (2090f786): MERGEABLE with one should-fix (idempotent + pinned ReportGenerator install) and a cosmetic nit — both applied in the follow-up commit a886fd28.
  • Fix commit (2090f786..a886fd28): re-reviewed. dotnet tool update … --version 5.5.10 confirmed idempotent (installs-if-absent, exits 0 when already present — unlike install under set -e); 5.5.10 verified as a real published NuGet version; csproj reorder purely cosmetic, CPM preserved (no inline Version=); YAML parses clean.

Independent-review skip rubric: this is a pure CI/tooling + docs leaf with no server-state effect, so an independent pass was technically skippable — ran it anyway (cheap) and it earned its keep by catching the install-idempotency gap.

Review-verdict: MERGEABLE @ a886fd2824

Independent cold-context review (two passes, review-only scope): - **Initial diff** (`2090f786`): MERGEABLE with one should-fix (idempotent + pinned ReportGenerator install) and a cosmetic nit — both applied in the follow-up commit `a886fd28`. - **Fix commit** (`2090f786..a886fd28`): re-reviewed. `dotnet tool update … --version 5.5.10` confirmed idempotent (installs-if-absent, exits 0 when already present — unlike `install` under `set -e`); `5.5.10` verified as a real published NuGet version; csproj reorder purely cosmetic, CPM preserved (no inline `Version=`); YAML parses clean. Independent-review skip rubric: this is a pure CI/tooling + docs leaf with **no server-state effect**, so an independent pass was technically skippable — ran it anyway (cheap) and it earned its keep by catching the install-idempotency gap. Review-verdict: MERGEABLE @ a886fd2824db97439a52d75440b1efd7c99423d9
timothy added 1 commit 2026-07-16 19:03:57 +02:00
ci(coverage): exclude generated EF migrations to stop coverage OOM (exit 137)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 7s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 8s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m26s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 16s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 6m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 3m46s
e78aa01c05
The shared Build & test job OOM-killed (exit 137) with coverage enabled: the
whole-solution Architecture.Tests process instruments both EF migration folders
(~2.59M generated lines vs ~200k authored), ballooning coverlet's in-memory
hit-count arrays past the runner's limit.

Add coverlet.runsettings excluding **/Migrations/*.cs (+ generated/compiler
attributes) and pass it via --settings on the Test step. This cuts the
instrumented surface ~126x (2,510,185 -> 19,963 coverable lines in the
Architecture.Tests process, verified locally) and makes the reported percentage
reflect authored code. Documented in docs/ci-cd.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Update: fixed an OOM the initial coverage change introduced

The CI monitor (armed at PR-open) caught the gating Build & test job OOM-killing (exit 137) on the previous head. Root cause: coverage instrumentation of the whole-solution ErsatzTV.Architecture.Tests process was covering both EF migration folders — ~2.59M generated lines vs ~200k authored — blowing coverlet's in-memory hit-count arrays past the shared runner's limit (intermittent: passed on one run, OOM'd on another for the same sha under memory pressure).

Fix (e78aa01c): added coverlet.runsettings excluding **/Migrations/*.cs + generated/compiler attributes + [*]*.Migrations.*, passed via --settings on the Test step. Verified locally: coverable lines in the Architecture.Tests process dropped 2,510,185 → 19,963 (~126×). Bonus: the reported percentage now reflects authored code, not generated migrations.

Independent cold re-review of the fix commit: verified valid runsettings XML (xmllint), all 1100 migration files sit flat under Migrations/ (glob catches them), no hand-written code lives there (exclusion doesn't hide authored code), and --collect … --settings … is a valid combination.

Review-verdict: MERGEABLE @ e78aa01c05

## Update: fixed an OOM the initial coverage change introduced The CI monitor (armed at PR-open) caught the gating **Build & test** job OOM-killing (`exit 137`) on the previous head. Root cause: coverage instrumentation of the whole-solution `ErsatzTV.Architecture.Tests` process was covering both EF migration folders — **~2.59M generated lines vs ~200k authored** — blowing coverlet's in-memory hit-count arrays past the shared runner's limit (intermittent: passed on one run, OOM'd on another for the same sha under memory pressure). **Fix** (`e78aa01c`): added `coverlet.runsettings` excluding `**/Migrations/*.cs` + generated/compiler attributes + `[*]*.Migrations.*`, passed via `--settings` on the Test step. Verified locally: coverable lines in the Architecture.Tests process dropped **2,510,185 → 19,963 (~126×)**. Bonus: the reported percentage now reflects authored code, not generated migrations. Independent cold re-review of the fix commit: verified valid runsettings XML (`xmllint`), all 1100 migration files sit flat under `Migrations/` (glob catches them), no hand-written code lives there (exclusion doesn't hide authored code), and `--collect … --settings …` is a valid combination. Review-verdict: MERGEABLE @ e78aa01c05552d2e403662e248674df00920862e
timothy merged commit 3654fad163 into main 2026-07-16 19:29:35 +02:00
timothy deleted branch feat/15-ci-coverage 2026-07-16 19:29:35 +02:00
Sign in to join this conversation.