Background
A fleet survey on 2026-08-14 found that ETL-SqlBulkCopy is the only repo measuring the coverage of its own test code. Every other repo instruments the src assembly only, so uncovered test code — dead helpers, unreachable branches, fakes nobody calls — has never been visible anywhere in the fleet.
Policy: test code is held to 100% line coverage. Test code that never executes has no purpose; it should be exercised or deleted. This is line coverage — branch coverage legitimately can't hit 100% (compiler-generated async state-machine branches), and the gate reads the line-coverage column only.
[ExcludeFromCodeCoverage] is a last resort, in this order:
- Write a test.
- If that's not possible, refactor the code to make it testable — without hurting performance.
- Only if both fail, apply
[ExcludeFromCodeCoverage], and justify it in review.
Status in this repo
Test assemblies are not instrumented. coverlet.runsettings exists but lacks <IncludeTestAssembly>true</IncludeTestAssembly>, so Summary.txt contains src rows only and current test-code coverage is unknown.
Related defect: the Stage 1 coverage gate mis-parses
.github/workflows/pr.yaml's Stage 1 gate anchors its filter on ^[^ ] to skip ReportGenerator's indented per-class rows, but the loop reads with a bare while read -r line. read strips leading IFS whitespace, so the indentation is gone before grep sees it and every class row matches — classes get gated as if they were projects.
This stayed invisible fleet-wide precisely because test assemblies aren't instrumented: with no class rows in Summary.txt, there was nothing to mis-match. It surfaced on ETL-SqlBulkCopy's 0.7.0 release, where the gate failed the release on a test class at 89.4% while both real assemblies were above 97%.
Fix (one line):
- while read -r line; do
+ while IFS= read -r line; do
Stage 2's PowerShell parser is unaffected (Get-Content preserves indentation). Note .github/workflows/pr.yaml is a protected file, so that change needs a maintainer bypass merge.
Steps
Notes
- Do not soften the gate to unblock work. If enforcing 100% walls off PRs until the backlog is cleared, that is the intended behaviour.
- Steps 1–3 are per-repo. Step 4 is fleet-uniform and should land in
repo-template first, then fan out.
Update: fleet sweep, 2026-08-14
A follow-up sweep found a second, larger reason test coverage is invisible: most test projects don't reference coverlet.collector, so dotnet test --collect:"XPlat Code Coverage" silently emits nothing for them — no error, no warning, just a missing coverage file.
Fleet totals: 83 test projects — 51 emit coverage, 31 emit none, 1 excluded by attribute. It is systematic by suite type, not random: Unit and Integration projects almost always have the collector; the specialty suites (AotSmoke, Fuzz, Concurrency, DocExamples, Snapshots, Allocation) were scaffolded without it across every repo that has them.
This repo — 1 of 1 test projects emit coverage
| test project |
emits coverage? |
Wolfgang.Extensions.String.Tests.Unit |
✅ yes |
Revised step 1 — three parts, not one
- Add
coverlet.collector to every suite that should be measured.
- Explicitly decide (and write down) whether AotSmoke / SourceLink-style suites stay uninstrumented.
- Then add
<IncludeTestAssembly>true</IncludeTestAssembly> — it only has an effect on projects that emit coverage at all.
Steps 2–5 in the original list are unchanged, but note the measurement in step 2 is only meaningful once step 1 is complete.
Scope decision, 2026-08-14
Two calls from Chris, narrowing this issue:
1. AotSmoke needs no coverage. Settled — AotSmoke suites stay uninstrumented deliberately. This is now a recorded decision, not an accident of scaffolding, and no longer an open question on this issue.
2. Phase 1 is unit tests only. Get the unit test projects to 100% first. The other specialty suites — Concurrency, DocExamples, Fuzz, Snapshots, Allocation, SourceLink — and the Integration suite are deferred to a later phase. Adding their collectors is explicitly not part of this issue's immediate work.
Deferred does not mean dismissed: the understated-src-coverage consequence noted above still holds while those suites emit nothing, so don't treat the post-phase-1 src number as the true figure.
Phase 1 scope for this repo
| unit test project |
status |
Wolfgang.Extensions.String.Tests.Unit |
✅ already emits coverage — only needs IncludeTestAssembly |
Revised phase-1 checklist:
Background
A fleet survey on 2026-08-14 found that ETL-SqlBulkCopy is the only repo measuring the coverage of its own test code. Every other repo instruments the
srcassembly only, so uncovered test code — dead helpers, unreachable branches, fakes nobody calls — has never been visible anywhere in the fleet.Policy: test code is held to 100% line coverage. Test code that never executes has no purpose; it should be exercised or deleted. This is line coverage — branch coverage legitimately can't hit 100% (compiler-generated async state-machine branches), and the gate reads the line-coverage column only.
[ExcludeFromCodeCoverage]is a last resort, in this order:[ExcludeFromCodeCoverage], and justify it in review.Status in this repo
Test assemblies are not instrumented.
coverlet.runsettingsexists but lacks<IncludeTestAssembly>true</IncludeTestAssembly>, soSummary.txtcontainssrcrows only and current test-code coverage is unknown.Related defect: the Stage 1 coverage gate mis-parses
.github/workflows/pr.yaml's Stage 1 gate anchors its filter on^[^ ]to skip ReportGenerator's indented per-class rows, but the loop reads with a barewhile read -r line.readstrips leadingIFSwhitespace, so the indentation is gone beforegrepsees it and every class row matches — classes get gated as if they were projects.This stayed invisible fleet-wide precisely because test assemblies aren't instrumented: with no class rows in
Summary.txt, there was nothing to mis-match. It surfaced on ETL-SqlBulkCopy's 0.7.0 release, where the gate failed the release on a test class at 89.4% while both real assemblies were above 97%.Fix (one line):
Stage 2's PowerShell parser is unaffected (
Get-Contentpreserves indentation). Note.github/workflows/pr.yamlis a protected file, so that change needs a maintainer bypass merge.Steps
coverlet.runsettings:xml <IncludeTestAssembly>true</IncludeTestAssembly>Not a protected file, so this is an ordinary PR. Expect this PR to fail if test-code line coverage is below 90%. The Stage 1 gate already enforces
CODECOV_MINIMUMagainst every assembly row, and enabling instrumentation adds the test assembly as a new row — so the threshold bites immediately, without step 4. That failure IS the measurement. (Confirmed in ETL-SqlBulkCopy's gate output: it checksWolfgang.Etl.SqlBulkCopy.Tests.Unitas a module; it only passes because that assembly is at 99.5%.) Noterelease.yamldoes NOT pass--settings coverlet.runsettings, so it will not instrument test assemblies even after this change — PR CI becomes stricter than release CI until step 5.CoverageReport/Summary.txt. Record the starting number here.srcassemblies stay at the existing 90% (CODECOV_MINIMUM).release.yamlagree — it runsdotnet testwithout--settings coverlet.runsettings, so release-time coverage excludes different code than PR-time. Harmless today; once a 100% rule exists it means a release can fail a gate the PR passed.Notes
repo-templatefirst, then fan out.Update: fleet sweep, 2026-08-14
A follow-up sweep found a second, larger reason test coverage is invisible: most test projects don't reference
coverlet.collector, sodotnet test --collect:"XPlat Code Coverage"silently emits nothing for them — no error, no warning, just a missing coverage file.Fleet totals: 83 test projects — 51 emit coverage, 31 emit none, 1 excluded by attribute. It is systematic by suite type, not random: Unit and Integration projects almost always have the collector; the specialty suites (AotSmoke, Fuzz, Concurrency, DocExamples, Snapshots, Allocation) were scaffolded without it across every repo that has them.
This repo — 1 of 1 test projects emit coverage
Wolfgang.Extensions.String.Tests.UnitRevised step 1 — three parts, not one
coverlet.collectorto every suite that should be measured.<IncludeTestAssembly>true</IncludeTestAssembly>— it only has an effect on projects that emit coverage at all.Steps 2–5 in the original list are unchanged, but note the measurement in step 2 is only meaningful once step 1 is complete.
Scope decision, 2026-08-14
Two calls from Chris, narrowing this issue:
1. AotSmoke needs no coverage. Settled — AotSmoke suites stay uninstrumented deliberately. This is now a recorded decision, not an accident of scaffolding, and no longer an open question on this issue.
2. Phase 1 is unit tests only. Get the unit test projects to 100% first. The other specialty suites — Concurrency, DocExamples, Fuzz, Snapshots, Allocation, SourceLink — and the Integration suite are deferred to a later phase. Adding their collectors is explicitly not part of this issue's immediate work.
Deferred does not mean dismissed: the understated-src-coverage consequence noted above still holds while those suites emit nothing, so don't treat the post-phase-1 src number as the true figure.
Phase 1 scope for this repo
Wolfgang.Extensions.String.Tests.UnitIncludeTestAssemblyRevised phase-1 checklist:
Add— not needed, the unit test project(s) already emit coverage.coverlet.collector<IncludeTestAssembly>true</IncludeTestAssembly>tocoverlet.runsettings. Expect the PR to fail if unit-test line coverage is under 90% — the gate enforcesCODECOV_MINIMUMagainst every assembly row. That failure is the measurement.[ExcludeFromCodeCoverage].repo-templatefirst, then fans out.