Skip to content

Collect and report code coverage in CI#52

Merged
KaliCZ merged 10 commits into
mainfrom
claude/add-coverage-reporting-KPt1w
Apr 21, 2026
Merged

Collect and report code coverage in CI#52
KaliCZ merged 10 commits into
mainfrom
claude/add-coverage-reporting-KPt1w

Conversation

@KaliCZ

@KaliCZ KaliCZ commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

Adds coverage collection + reporting to PRs via two parallel channels so we can compare and pick one:

  1. Custom sticky commentscripts/coverage-summary.py renders Cobertura into a markdown summary grouped by project → folder with nested collapsibles, absolute counts alongside percentages, and a Files changed in this PR section scoped to the PR diff. Also fixes generic-heavy totals (Cobertura emits one <class> per constructed generic; we dedupe by (filename, line number) so a 200-line file isn't counted as 140,000).
  2. Codecovcodecov/codecov-action@v5 uploads the merged Cobertura to codecov.io, with codecov.yml mapping paths to one component per project. Both project and patch statuses are informational: true so codecov can't fail the PR while we're evaluating.

Also splits the workflow: build.yml runs on push/PR in Debug (so coverage sequence points line up 1:1 with source); the new release.yml runs on release events in Release, tests the shipping binaries, and only then pushes to nuget.org.

How it works

Collection. Each test project references Microsoft.Testing.Extensions.CodeCoverage. The Test step forwards coverage flags through the -- separator:

dotnet test --no-restore --no-build --configuration Debug -- --coverage --coverage-output-format cobertura

Merge. dotnet-reportgenerator-globaltool consolidates per-project *.cobertura.xml into one merged XML plus a drill-down HTML report.

Rendering (custom). scripts/coverage-summary.py reads the merged XML and emits a markdown document with:

  • overall line / branch percentages
  • a Files changed in this PR table driven by gh pr view --json files
  • one <details> per project with project-level totals in the summary line
  • nested <details open> per folder inside each project, with per-folder totals
  • source-generator output under src/<proj>/obj/**/*.g.cs folds into a generated/ folder, with arity backticks ( `1) translated to <T> so the filename stays inside its markdown code span

Rendering (codecov). codecov.yml sets comment: layout: header, diff, components, tree so the codecov comment shows overall delta, the @@ Coverage Diff @@ block, a per-component table (one row per project), and a folder-grouped file tree.

Visibility. Coverage surfaces four ways:

  • Job summary (custom markdown appended to $GITHUB_STEP_SUMMARY)
  • Custom sticky PR comment (header coverage, edits in place via marocchino/sticky-pull-request-comment@v2)
  • Codecov PR comment (separate sticky)
  • HTML artifact (coverage-report, 14-day retention) for offline drill-down

Safety. Every downstream step uses !cancelled() + hashFiles guards so a build failure leaves no misleading red steps.

Workflow split

Event Workflow Config Purpose
push to main, pull_request build.yml Debug test + coverage + PR comments
release published release.yml Release build + test + pack + publish + attach

Debug-in-CI matters for coverage fidelity: Release merges sequence points and can fold constant branches, slightly distorting the numbers. Shipping binaries are still validated in Release via release.yml's Test step before nuget push.

Test plan

  • build workflow runs green on this PR
  • Job summary shows the coverage table
  • Custom sticky coverage comment appears with the project/folder breakdown
  • Codecov comment appears with the per-component table
  • coverage-report artifact is attached and contains the HTML drill-down
  • Re-running the workflow updates both comments in place rather than stacking
  • Generic-heavy files show realistic line counts (not the old inflated totals)

Adds Microsoft.Testing.Extensions.CodeCoverage to each test project and
wires ReportGenerator into the build workflow so every run emits a
GitHub-flavored markdown summary. The summary is appended to the job
summary and, on pull requests, posted as a sticky comment so authors
see at a glance which areas they touched without tests.

https://claude.ai/code/session_01FjeHRA2Wauszz7JaEE8dAR
@KaliCZ KaliCZ self-assigned this Apr 21, 2026
@github-actions

github-actions Bot commented Apr 21, 2026

Copy link
Copy Markdown

Coverage

Lines: 1835 / 14439 (12.7%)    Branches: 642 / 9339 (6.9%)

Files changed in this PR

No coverage-instrumented files changed in this PR.

StrongTypes — lines 9.2% (1278/13841), branches 5.5% (504/9167)
Collections — lines 91.5% (257/281), branches 82.5% (94/114)
File Lines Branches
IEnumerableExtensions.cs 7 / 7 (100.0%) 6 / 6 (100.0%)
IEnumerableExtensions_Types.cs 0 / 8 (0.0%) 0 / 6 (0.0%)
NonEmptyEnumerable.cs 59 / 65 (90.8%) 30 / 38 (78.9%)
NonEmptyEnumerableDebugView.cs 0 / 2 (0.0%) 0 / 0 (n/a)
NonEmptyEnumerableExtensions.cs 144 / 149 (96.6%) 37 / 38 (97.4%)
NonEmptyEnumerableJsonConverter.cs 47 / 50 (94.0%) 21 / 26 (80.8%)
Collections_Old — lines 1.6% (40/2504), branches 1.6% (17/1080)
File Lines Branches
IEnumerableExtensions_Concatenating_Old.cs 16 / 19 (84.2%) 9 / 10 (90.0%)
IEnumerableExtensions_Coproducts_Old.cs 24 / 1817 (1.3%) 8 / 1004 (0.8%)
IEnumerableExtensions_Emptiness_Old.cs 0 / 20 (0.0%) 0 / 0 (n/a)
IEnumerableExtensions_Flattening_Old.cs 0 / 3 (0.0%) 0 / 2 (0.0%)
IEnumerableExtensions_Null_Old.cs 0 / 15 (0.0%) 0 / 10 (0.0%)
IEnumerableExtensions_Partition_Old.cs 0 / 15 (0.0%) 0 / 4 (0.0%)
IEnumerableExtensions_Try_Old.cs 0 / 46 (0.0%) 0 / 12 (0.0%)
IEnumerableExtensions_Tuples_Old.cs 0 / 532 (0.0%) 0 / 38 (0.0%)
ReadOnlyList_Old.cs 0 / 37 (0.0%) 0 / 0 (n/a)
Coproduct_Old — lines 2.4% (87/3580), branches 0.7% (23/3123)
File Lines Branches
Coproduct_Old.cs 87 / 3531 (2.5%) 23 / 3115 (0.7%)
ICoproductExtensions_Old.cs 0 / 46 (0.0%) 0 / 8 (0.0%)
Nothing_Old.cs 0 / 3 (0.0%) 0 / 0 (n/a)
Digits — lines 97.4% (74/76), branches 90.9% (20/22)
File Lines Branches
Digit.cs 67 / 69 (97.1%) 16 / 18 (88.9%)
DigitExtensions.cs 7 / 7 (100.0%) 4 / 4 (100.0%)
Enums — lines 100.0% (58/58), branches 95.5% (21/22)
File Lines Branches
EnumExtensions.cs 58 / 58 (100.0%) 21 / 22 (95.5%)
Exceptions — lines 78.9% (15/19), branches 50.0% (7/14)
File Lines Branches
ExceptionEnumerableExtensions.cs 15 / 19 (78.9%) 7 / 14 (50.0%)
Extensions_Old — lines 2.0% (120/6061), branches 1.5% (58/3990)
File Lines Branches
ActionExtensions_Old.cs 0 / 112 (0.0%) 0 / 0 (n/a)
BooleanExtensions_Old.cs 7 / 49 (14.3%) 2 / 14 (14.3%)
ObjectExtensions_Coproduct_Old.cs 42 / 1443 (2.9%) 27 / 1172 (2.3%)
ObjectExtensions_Generic_Old.cs 44 / 95 (46.3%) 17 / 40 (42.5%)
ObjectExtensions_MappingToCollections_Old.cs 0 / 200 (0.0%) 0 / 80 (0.0%)
ObjectExtensions_ValueMatch_Old.cs 27 / 4150 (0.7%) 12 / 2680 (0.4%)
TypeExtensions_Old.cs 0 / 12 (0.0%) 0 / 4 (0.0%)
Maybe — lines 92.1% (152/165), branches 84.2% (96/114)
File Lines Branches
Maybe.cs 58 / 59 (98.3%) 40 / 44 (90.9%)
MaybeCollectionExtensions.cs 41 / 45 (91.1%) 28 / 30 (93.3%)
MaybeExtensions.cs 10 / 15 (66.7%) 12 / 20 (60.0%)
MaybeJsonConverter.cs 43 / 46 (93.5%) 16 / 20 (80.0%)
Numbers — lines 100.0% (79/79), branches 94.4% (17/18)
File Lines Branches
Negative.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
NonNegative.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
NonPositive.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
NumberExtensions.cs 12 / 12 (100.0%) 4 / 4 (100.0%)
NumericStrongTypeJsonConverterFactory.cs 37 / 37 (100.0%) 5 / 6 (83.3%)
NumericWrapperAttribute.cs 2 / 2 (100.0%) 0 / 0 (n/a)
Positive.cs 7 / 7 (100.0%) 2 / 2 (100.0%)
Product_Old — lines 57.1% (12/21), branches n/a (0/0)
File Lines Branches
Unit_Old.cs 4 / 13 (30.8%) 0 / 0 (n/a)
UnitJsonConverter_Old.cs 8 / 8 (100.0%) 0 / 0 (n/a)
Strings — lines 47.3% (69/146), branches 54.8% (34/62)
File Lines Branches
NonEmptyString.cs 23 / 76 (30.3%) 12 / 30 (40.0%)
NonEmptyStringExtensions.cs 13 / 27 (48.1%) 0 / 0 (n/a)
NonEmptyStringJsonConverter.cs 12 / 15 (80.0%) 4 / 6 (66.7%)
StringExtensions.cs 21 / 28 (75.0%) 18 / 26 (69.2%)
Try — lines 100.0% (10/10), branches 75.0% (3/4)
File Lines Branches
TryExtensions.cs 10 / 10 (100.0%) 3 / 4 (75.0%)
Try_Old — lines 29.1% (161/553), branches 13.3% (56/420)
File Lines Branches
Try_Old.cs 72 / 414 (17.4%) 24 / 366 (6.6%)
TryConverterFactory_Old.cs 55 / 67 (82.1%) 16 / 24 (66.7%)
TryExtensions_Old.cs 34 / 72 (47.2%) 16 / 30 (53.3%)
generated — lines 50.0% (144/288), branches 31.5% (58/184)
File Lines Branches
Negative<T>.Extensions.g.cs 0 / 32 (0.0%) 0 / 20 (0.0%)
Negative<T>.g.cs 32 / 40 (80.0%) 13 / 26 (50.0%)
NonNegative<T>.Extensions.g.cs 9 / 32 (28.1%) 3 / 20 (15.0%)
NonNegative<T>.g.cs 31 / 40 (77.5%) 13 / 26 (50.0%)
NonPositive<T>.Extensions.g.cs 0 / 32 (0.0%) 0 / 20 (0.0%)
NonPositive<T>.g.cs 31 / 40 (77.5%) 13 / 26 (50.0%)
Positive<T>.Extensions.g.cs 9 / 32 (28.1%) 3 / 20 (15.0%)
Positive<T>.g.cs 32 / 40 (80.0%) 13 / 26 (50.0%)
StrongTypes.Analyzers — lines 91.7% (188/205), branches 86.6% (71/82)
(root) — lines 91.7% (188/205), branches 86.6% (71/82)
File Lines Branches
AddEfCorePackageCodeFixProvider.cs 48 / 52 (92.3%) 18 / 20 (90.0%)
MissingEfCorePackageAnalyzer.cs 140 / 153 (91.5%) 53 / 62 (85.5%)
StrongTypes.Api — lines 100.0% (226/226), branches 78.9% (30/38)
(root) — lines 100.0% (11/11), branches n/a (0/0)
File Lines Branches
Program.cs 11 / 11 (100.0%) 0 / 0 (n/a)
Controllers — lines 100.0% (122/122), branches 78.9% (30/38)
File Lines Branches
CollectionJsonController.cs 4 / 4 (100.0%) 0 / 0 (n/a)
EntityControllerBase.cs 9 / 9 (100.0%) 0 / 0 (n/a)
NegativeDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NegativeShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonEmptyStringEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonNegativeShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
NonPositiveShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveDecimalEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveDoubleEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveFloatEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveIntEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveLongEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
PositiveShortEntityController.cs 1 / 1 (100.0%) 0 / 0 (n/a)
ReferenceTypeEntityControllerBase.cs 42 / 42 (100.0%) 14 / 18 (77.8%)
StructTypeEntityControllerBase.cs 42 / 42 (100.0%) 16 / 20 (80.0%)
Data — lines 100.0% (52/52), branches n/a (0/0)
File Lines Branches
PostgreSqlDbContext.cs 26 / 26 (100.0%) 0 / 0 (n/a)
SqlServerDbContext.cs 26 / 26 (100.0%) 0 / 0 (n/a)
Entities — lines 100.0% (12/12), branches n/a (0/0)
File Lines Branches
EntityBase.cs 8 / 8 (100.0%) 0 / 0 (n/a)
IEntity.cs 4 / 4 (100.0%) 0 / 0 (n/a)
Models — lines 100.0% (29/29), branches n/a (0/0)
File Lines Branches
CollectionJsonModels.cs 20 / 20 (100.0%) 0 / 0 (n/a)
EntityModels.cs 9 / 9 (100.0%) 0 / 0 (n/a)
StrongTypes.EfCore — lines 88.7% (110/124), branches 71.2% (37/52)
(root) — lines 88.7% (110/124), branches 71.2% (37/52)
File Lines Branches
NonEmptyStringValueConverter.cs 3 / 3 (100.0%) 0 / 0 (n/a)
NumericStrongTypeValueConverter.cs 17 / 17 (100.0%) 0 / 0 (n/a)
StrongTypesConvention.cs 43 / 56 (76.8%) 25 / 38 (65.8%)
StrongTypesDbContextOptionsExtension.cs 18 / 19 (94.7%) 2 / 2 (100.0%)
UnwrapMethodCallTranslator.cs 29 / 29 (100.0%) 10 / 12 (83.3%)
StrongTypes.FsCheck — lines 76.7% (33/43), branches n/a (0/0)
(root) — lines 76.7% (33/43), branches n/a (0/0)
File Lines Branches
Generators.cs 33 / 43 (76.7%) 0 / 0 (n/a)

claude and others added 9 commits April 21, 2026 18:42
ReportGenerator's Markdown outputs group by class, which surfaces every
compiler-generated closure type as its own row — in extension-method-heavy
code like Maybe, a single MaybeExtensions.cs turns into MaybeExtensions,
MaybeExtensions<A, B>, MaybeExtensions<A, R>, MaybeExtensions<A, X, B>, …
rows, all with near-identical numbers.

Cobertura tags every <class> with its source filename, so we can group
on that attribute instead. Adds a small Python post-processor that
collapses the merged Cobertura.xml into one row per .cs file and
highlights files below 50% line coverage up top, so reviewers see the
"add tests here" nudge first.

https://claude.ai/code/session_01FjeHRA2Wauszz7JaEE8dAR
Cobertura emits one <class> per constructed generic, so summing <line>
entries naively multiplied physical lines by instantiation count. Dedupe
by (filename, line number) to count each source line once and take the
max of branch coverage across instantiations.

Also compact source-generator output paths to `generated/<name>`,
translate backtick arity to `<T>` so the filename stays inside its
markdown code span, and replace the "below 50%" section with a
PR-scoped "Files changed in this PR" table driven by `gh pr view`.
Each project gets its own collapsible <details> block with project-level
totals in the summary line; within a project, files are grouped under
bold folder headers with per-folder percentages. Source-generator output
folds into a `generated` folder under its originating project instead of
sitting at top level.
Each folder becomes its own <details open> block under its project, so
readers can collapse noisy areas (e.g. _Old folders) while keeping the
rest expanded. Folder headers now carry absolute covered/total counts
alongside percentages, matching the project summary line. A <br> after
the project summary adds vertical breathing room before the first folder.
Debug builds preserve per-statement sequence points, so coverage lines
up 1:1 with source. Release merges those points and folds constant
branches, which skews the coverage numbers slightly. Switch the PR/push
build to Debug and drop it on release events — the publish job now
Release-builds, runs the full test suite against those binaries, and
only pushes to nuget.org if they pass.
The two paths now share no steps and no dependencies: ci.yml runs on
PR/push, builds Debug, runs tests with coverage, posts the sticky PR
comment; release.yml runs on release-published, builds Release, runs
tests against shipping binaries, pushes to nuget.org, and attaches the
nupkgs to the release. Each file now has a single `on:` trigger and
no job-level conditionals, which reads much more cleanly than the
prior single-file fork.
Rename ci.yml back to build.yml (workflow name `build`, job name `build`)
so existing branch protections and status-check names keep working.
The new release.yml sits next to it, unchanged.
Upload the merged Cobertura.xml to codecov.io via the codecov-action and
configure components per project so the PR comment groups coverage by
StrongTypes / Analyzers / EfCore / Api / FsCheck. Left `informational: true`
on both project and patch statuses so codecov can't fail the PR while
we're evaluating. The custom sticky comment still posts — both appear on
the PR and we pick one afterwards.
@codecov-commenter

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@KaliCZ KaliCZ merged commit b8cafd1 into main Apr 21, 2026
1 check passed
@KaliCZ KaliCZ deleted the claude/add-coverage-reporting-KPt1w branch April 21, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants