Skip to content

Fix CI coverage aggregation by normalizing LCOV source paths#1614

Merged
SteveL-MSFT merged 6 commits into
mainfrom
stevel-msft-fix-ci-coverage-aggregation
Jul 9, 2026
Merged

Fix CI coverage aggregation by normalizing LCOV source paths#1614
SteveL-MSFT merged 6 commits into
mainfrom
stevel-msft-fix-ci-coverage-aggregation

Conversation

@SteveL-MSFT

@SteveL-MSFT SteveL-MSFT commented Jul 8, 2026

Copy link
Copy Markdown
Member

Problem

The CI coverage report shows ~42% code coverage, while running .\build.ps1 -codecoverage locally reports >80%. Three issues were identified:

  1. Pester coverage never generated: Export-PesterCodeCoverageReport searched bin/ for executables but they live in bin/debug/ (non-Release build). All 12 Pester jobs emitted "No executable binaries found".

  2. Unix binary detection broken: On Linux/macOS, the code checked $_.Mode -match 'x' but PowerShell's .Mode on Unix shows Windows-style attributes (-----), not Unix permissions. The execute bit is in .UnixMode.

  3. Cross-platform merging inflated totals: Merging LCOV from all 3 platforms inflates totalLines because Windows has ~4,500 extra platform-specific source lines (registry, service, DISM resources) that are only partially covered, dragging down the percentage.

Fix

  1. Workflow: Use Get-ArtifactDirectoryPath to resolve the correct bin/debug path for Pester coverage generation.

  2. Binary detection: On Unix, identify Rust binaries by absence of file extension (robust against artifact upload stripping execute permissions).

  3. Single-platform coverage: Use only Linux coverage data (Rust tests + Pester integration tests) which matches the local build.ps1 -codecoverage single-platform workflow. Path normalization is still applied to strip workspace prefixes.

  4. Path normalization: Added ConvertTo-NormalizedLcovSourcePath to strip CI workspace prefixes and normalize separators, ensuring correct LCOV merging regardless of source.

Verification

  • Downloaded all 14 CI coverage artifacts and tested merging locally
  • Linux-only merge (5 files): 81% (14,772/18,050) — matches local result
  • All-platform merge (14 files): 65% (15,189/23,237) — inflated by platform-specific code
  • This confirms single-platform is the correct approach

Testing

Added Pester tests in tests/helpers.build.coverage.tests.ps1 covering:

  • Path normalization for Linux, macOS, and Windows CI paths
  • Cross-platform consistency (same file → same normalized path)
  • Merge behavior: single entry produced, hit counts summed correctly
  • Non-overlapping files preserved as separate entries
  • CRLF-safe regex assertions for Windows compatibility

The Merge-LcovFile function was treating the same source file as different
entries when LCOV files came from different platforms (Linux, macOS, Windows)
because each platform uses different absolute paths:
- Linux:   /home/runner/work/DSC/DSC/dsc_lib/src/foo.rs
- macOS:   /Users/runner/work/DSC/DSC/dsc_lib/src/foo.rs
- Windows: D:\a\DSC\DSC\dsc_lib\src\foo.rs

This caused the same source lines to be counted 15+ times (3 platforms x
5 test groups) in the total, with Pester coverage runs contributing many
DA:line,0 entries that dragged the overall percentage down from ~80% to ~48%.

Fix: Add ConvertTo-NormalizedLcovSourcePath that strips platform-specific
workspace prefixes and normalizes path separators before using them as merge
keys. This ensures coverage data from all platforms and test groups is
correctly combined for each source file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 22:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses inaccurate CI code coverage aggregation by normalizing LCOV SF: (source file) paths so coverage from different platforms merges into a single entry per source file, matching local .\build.ps1 -codecoverage results more closely.

Changes:

  • Added ConvertTo-NormalizedLcovSourcePath to normalize LCOV SF: paths (workspace prefix stripping + separator normalization).
  • Updated Merge-LcovFile to merge coverage keyed by normalized source paths.
  • Added Pester tests validating path normalization and cross-platform LCOV merge behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
helpers.build.psm1 Adds LCOV source-path normalization and uses it during LCOV merge to fix cross-platform aggregation.
tests/helpers.build.coverage.tests.ps1 Adds Pester coverage tests for normalization and merge aggregation correctness.

Comment thread helpers.build.psm1
Comment thread helpers.build.psm1 Outdated
Comment thread tests/helpers.build.coverage.tests.ps1 Outdated
Comment thread tests/helpers.build.coverage.tests.ps1
…nchor test assertions

- Remove generic ADO prefix patterns that could produce inconsistent keys
  for non-CI absolute paths across platforms.
- Expand the marker-based fallback to also handle drive-letter absolute
  paths (not just paths starting with /).
- Anchor test regex assertions with (?m)^ and $ to prevent false positives
  (e.g., DA:1,8 matching DA:1,80).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Code Coverage Report

No Rust files were changed in this PR.

🔵 Full Codebase Coverage

81% (good)

Metric Value
Total executable lines 18050
Lines covered by tests 14772
Coverage percentage 81%

Full codebase coverage measures all instrumented Rust lines across the project.

SteveL-MSFT and others added 3 commits July 8, 2026 17:49
The Export-PesterCodeCoverageReport function searches for binaries using
Get-ChildItem without -Recurse. Since CI builds without -Release, binaries
are placed in bin/debug/ (not bin/ directly). The workflow was passing
'Join-Path $PWD bin' which only searched the top-level bin/ directory,
causing 'No executable binaries found' on all platforms.

Fix: Use Get-ArtifactDirectoryPath (which returns bin/debug for non-Release
builds) to resolve the correct binary directory path. This ensures all 12
Pester coverage LCOV files are generated and included in the merge, adding
the integration test coverage that accounts for ~30% of the total.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
On Unix, PowerShell's .Mode property shows Windows-style attributes (e.g.,
'-----') rather than Unix permissions. The code checked $_.Mode -match 'x'
which always evaluates to false on Linux/macOS, causing 'No executable
binaries found' in all Unix Pester coverage jobs.

Fix: On Unix, identify Rust binaries by the absence of a file extension
(Rust produces extensionless binaries on Unix). Files with extensions are
only included if they have execute permission via .UnixMode. This approach
is robust against artifact upload/download stripping execute permissions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Merging coverage from all 3 platforms inflates the total line count because
each platform has platform-specific source files (Windows has ~4500 extra
lines from registry, service, DISM resources; macOS has some unique files).
These platform-specific lines are only partially covered by their respective
platform tests, dragging down the reported percentage.

Local build.ps1 -codecoverage runs on a single platform and reports ~81%.
The CI report should do the same. Use only Linux coverage data (Rust tests +
Pester integration tests) which matches the local workflow and accurately
reflects codebase coverage on the primary platform.

Also reduces the coverage-report job's dependencies to just linux-build and
linux-pester, allowing it to complete sooner without waiting for all platforms.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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

Comment thread tests/helpers.build.coverage.tests.ps1 Outdated
Comment thread tests/helpers.build.coverage.tests.ps1
Comment thread tests/helpers.build.coverage.tests.ps1
Comment thread .github/workflows/rust.yml
Comment thread .github/workflows/rust.yml
…ationale

- Add \r? before $ anchors in test regex assertions to handle CRLF line
  endings when tests run on Windows.
- Add workflow comment explaining why coverage uses Linux-only artifacts:
  cross-platform merging inflates total lines due to platform-specific
  source files, producing misleadingly low percentages vs local results.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SteveL-MSFT SteveL-MSFT merged commit cc66e8a into main Jul 9, 2026
24 checks passed
@SteveL-MSFT SteveL-MSFT deleted the stevel-msft-fix-ci-coverage-aggregation branch July 9, 2026 13:57
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.

2 participants