Repository navigation
[Bug]: Code coverage metric is misleading — generated interop code dominates the denominator #630
Description
Describe the bug
The code coverage produced by the test run (dotnet run --project WinApp.Cli.Tests ... --coverage --coverage-output-format cobertura, as invoked by scripts/build-cli.ps1) reports a wildly misleading overall number.
Measured on main:
- Reported overall line-rate: ~18% (
lines-covered 18,358 / lines-valid 101,587). - The denominator (101,587 lines) is dominated by auto-generated interop code emitted into
obj\**, none of which is meaningful to cover:Generated\CsWin32\Windows.Win32.NativeMethods.g.cs(thousands of lines of P/Invoke thunks)Microsoft.Interop.ComInterfaceGenerator\...COM interface shims (Direct3D11ID3D11DeviceContext/ID3D11Device, UIAutomationIUIAutomation*,IImageList,IMoniker,IAccessible, …)System.Text.RegularExpressions.Generator\...RegexGenerator.g.cs- Both the
DebugandReleaseobj\trees are counted, double-counting the generated files.
When the generated obj\** code is excluded, the real, hand-written source measures:
- ~48.8% line coverage — 176 files, 21,127 valid lines, 10,306 covered.
So the headline metric under-reports true coverage by ~30 points and can't be used as a quality gate or trend signal. It also silently double-counts generated files across build configurations.
Note on hardware/COM/GPU code: ~2,300 lines of the hand-written source (the real UiAutomationService, WgcCapture screen capture, and the SendInput mouse/keyboard wrappers) are currently exercised only by a separate PowerShell E2E harness, not the MSTest unit suite, so they show as uncovered. These are not to be excluded from the denominator — they are real product code and will be brought under test (unit-testable logic seams plus an in-process UI test that drives the real service against the WinUI sample app). The only legitimate exclusion is the generated code above.
To Reproduce
Steps to reproduce the behavior:
- From a clean checkout, build the test project:
dotnet build src/winapp-CLI/WinApp.Cli.Tests/WinApp.Cli.Tests.csproj -c Release - Run the suite with coverage exactly as
scripts/build-cli.ps1does:dotnet run --project src/winapp-CLI/WinApp.Cli.Tests/WinApp.Cli.Tests.csproj -c Release --no-build --coverage --coverage-output-format cobertura - Open the produced
*.cobertura.xmland read the rootline-rate/lines-valid/lines-coveredattributes. - Observe
line-rate ≈ 0.18withlines-valid ≈ 101,587; inspect the per-<class>filenameattributes and see the largest entries are files underobj\...\Generated\and...Generator\.
Expected behavior
- Coverage should be measured against hand-written product source only. Generated files (
obj\**,*.g.cs, CsWin32 / ComInterfaceGenerator / RegexGenerator output) must be excluded from both numerator and denominator via a checked-in coverage configuration (e.g. a.runsettingsSources/ModulePathsexclude, consumed by the--coveragerun andbuild-cli.ps1). - No hand-written service or logic is excluded. The hardware/COM/GPU interop that the unit suite currently fakes (real
UiAutomationService,WgcCapture, keyboard/mouseSendInput) stays in the denominator and is covered by real tests: unit tests for its logic seams, plus an in-process UI test that launches the WinUI sample app and drives the genuine service (inspect/search/invoke/set-value/wait-for/screenshot + real type/click) so--coverageinstruments those paths. - The reported overall number should reflect real coverage and be stable across
Debug/Release(no double-counting), so it can be used as a trend signal / gate.
Screenshots
N/A
OS Version and details
Windows 11 / Windows Server (net10.0-windows10.0.19041.0). Not OS-specific.
Update — second cause: the Release build itself under-counts line coverage
Excluding generated code (above) is necessary but not sufficient. The --coverage run in build-cli.ps1 also collected coverage on an optimized Release build. On Release, the C# compiler often drops or merges the sequence points for standalone block-brace lines ({/}) and duplicate return statements, so the Microsoft coverage engine reports many brace-only lines as hits=0 even when the enclosing code fully executes. This systematically deflates line coverage and caps control-flow-heavy files at ~70–80% regardless of test quality.
Measured with the same 1,637 tests, generated code already excluded:
| Overall (hand-written) | ManifestService.cs |
|
|---|---|---|
| Release (understated) | ~48.8% | 62–71% |
| Debug (accurate) | ~59.5% | 81.7% |
ManifestService.cs reads 81.7% in Debug on main (no new tests) — already above the ~73% "ceiling" it appeared to hit in Release, proving the ceiling was a measurement artifact, not un-coverable code.
The fix is to collect coverage on a Debug build (the standard configuration for .NET coverage), which maps every line faithfully — no brace-filtering heuristic needed. Fixed in #637: coverage-report.ps1 defaults to -Configuration Debug, and build-cli.ps1 runs the test/coverage pass in Debug (passing -p:TreatWarningsAsErrors=true to preserve the Release-only warning gate). The shipped CLI is still the Release dotnet publish.
The honest baseline is therefore ~59.5%, not the ~48.8% first reported here.
Activity
Collect coverage on a Debug build for accurate line coverage (#630) (#…
test(services): crash-dump / XAML-triage / debug-output coverage to 9…
test(services): raise cert/sign/nuget/registration/lifecycle coverage…
test(ui): raise UI-command logic coverage to >=95% per file (#645)
Tests: non-UI CLI commands coverage to =>95% (#640)
test: add real in-process UI Automation coverage (#630)
Tests: Project / workspace / setup services coverage to =>95% (#639)
Test coverage: telemetry, tools & prompt mop-up to >=95% (#650)
Test coverage: UI-automation input helpers to >=95% (#649)
Test coverage: UI Automation service and capture stack (issue #630) (#…
Test coverage: ConsoleTasks infra + small missed files (issue #630) (#…
Address pr-review MEDIUMs: extract MapPenPressure + DRY ambient-conso…
Test coverage: NuGet/update/install files reverted by #654 (issue #630)…
Test coverage: UI touch/pen commands + pointer support (issue #630) (#…
Fix misleading code coverage measurement (microsoft#630) (microsoft#632)