diff --git a/.github/actions/Get-PesterCodeCoverage/README.md b/.github/actions/Get-PesterCodeCoverage/README.md index 689d4452..38c3b64d 100644 --- a/.github/actions/Get-PesterCodeCoverage/README.md +++ b/.github/actions/Get-PesterCodeCoverage/README.md @@ -9,8 +9,9 @@ This GitHub Action is a part of the [PSModule framework](https://github.com/PSMo ## Features - Combines multiple code coverage reports from parallel test runs -- Generates markdown/HTML tables showing missed & executed commands +- Generates Markdown/HTML tables showing missed & executed commands - Displays analyzed files and coverage statistics +- Generates a generic missed-path report (Markdown + JSON) - Configurable step summary sections - Threshold enforcement for minimum code coverage @@ -40,6 +41,10 @@ This GitHub Action is a part of the [PSModule framework](https://github.com/PSMo ## Outputs +| Name | Description | +| ---- | ----------- | +| `MissedPathReportPath` | Folder path containing `CodeCoverage-MissedPaths.md` and `CodeCoverage-MissedPaths.json` | + ### GitHub Step Summary The action generates a detailed summary visible in the GitHub Actions UI: @@ -53,6 +58,7 @@ The action generates a detailed summary visible in the GitHub Actions UI: - **Missed Commands**: HTML table with code snippets - **Executed Commands**: HTML table with code snippets - **Analyzed Files**: List of covered files + - **Missed Paths**: Aggregated missed-command counts grouped by file path Example summary: diff --git a/.github/actions/Get-PesterCodeCoverage/action.yml b/.github/actions/Get-PesterCodeCoverage/action.yml index ee1fc33b..29bc7496 100644 --- a/.github/actions/Get-PesterCodeCoverage/action.yml +++ b/.github/actions/Get-PesterCodeCoverage/action.yml @@ -35,6 +35,11 @@ inputs: description: The target for code coverage. required: false +outputs: + MissedPathReportPath: + description: Path to the generated missed-path report folder. + value: ${{ steps.Get-PesterCodeCoverage.outputs.MissedPathReportPath }} + runs: using: composite steps: diff --git a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 index dc3fc9d6..995a5825 100644 --- a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 +++ b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 @@ -158,6 +158,73 @@ $success = $coveragePercent -ge $coveragePercentTarget $statusIcon = $success ? '✅' : '❌' $stats | Format-Table -AutoSize | Out-String +$missedPathGroups = $codeCoverage.CommandsMissed | + Where-Object { -not [string]::IsNullOrWhiteSpace($_.File) } | + Group-Object -Property File | + Sort-Object -Property @( + @{ Expression = 'Count'; Descending = $true }, + @{ Expression = 'Name'; Descending = $false } + ) + +$missedPaths = foreach ($group in $missedPathGroups) { + $lines = $group.Group | + Where-Object { $_.Line -is [ValueType] } | + ForEach-Object { [int]$_.Line } | + Sort-Object -Unique + $missedLines = if ($lines.Count -eq 0) { '' } else { $lines -join ', ' } + [pscustomobject]@{ + Path = $group.Name + MissedCommands = [int]$group.Count + MissedLines = $missedLines + } +} + +$missedPathReportPath = 'CodeCoverage-MissedPaths' +$null = New-Item -Path $missedPathReportPath -ItemType Directory -Force + +$missedPathReport = [ordered]@{ + GeneratedAtUtc = [DateTime]::UtcNow.ToString('o') + CoveragePercent = [double]$coveragePercent + CoverageTarget = [double]$coveragePercentTarget + MissedPaths = @($missedPaths) +} + +$missedPathReportJsonPath = Join-Path -Path $missedPathReportPath -ChildPath 'CodeCoverage-MissedPaths.json' +$missedPathReportMarkdownPath = Join-Path -Path $missedPathReportPath -ChildPath 'CodeCoverage-MissedPaths.md' +$missedPathReport | ConvertTo-Json -Depth 10 | Set-Content -Path $missedPathReportJsonPath -Encoding utf8NoBOM + +$missedPathMarkdown = [System.Collections.Generic.List[string]]::new() +$null = $missedPathMarkdown.Add('# Code Coverage Missed Paths') +$null = $missedPathMarkdown.Add('') +$null = $missedPathMarkdown.Add("| Coverage | Target | Missed Paths | Missed Commands |") +$null = $missedPathMarkdown.Add("| --- | --- | --- | --- |") +$coverageSummaryRow = '| {0}% | {1}% | {2} | {3} |' -f ( + [Math]::Round($coveragePercent, 2) +), ( + [Math]::Round($coveragePercentTarget, 2) +), $missedPaths.Count, $codeCoverage.CommandsMissedCount +$null = $missedPathMarkdown.Add($coverageSummaryRow) +$null = $missedPathMarkdown.Add('') +$null = $missedPathMarkdown.Add('## Paths') +$null = $missedPathMarkdown.Add('| Path | Missed Commands | Missed Lines |') +$null = $missedPathMarkdown.Add('| --- | ---: | --- |') +if ($missedPaths.Count -eq 0) { + $null = $missedPathMarkdown.Add('| _None_ | 0 | |') +} else { + foreach ($pathEntry in $missedPaths) { + $safePath = ([string]$pathEntry.Path).Replace('|', '\|') + $safeLines = ([string]$pathEntry.MissedLines).Replace('|', '\|') + $null = $missedPathMarkdown.Add("| $safePath | $($pathEntry.MissedCommands) | $safeLines |") + } +} + +$missedPathMarkdown | Set-Content -Path $missedPathReportMarkdownPath -Encoding utf8NoBOM +Write-Output 'Missed path report generated:' +$missedPaths | Select-Object -First 20 | Format-Table -Property Path, MissedCommands, MissedLines -AutoSize | Out-String +if ($env:GITHUB_OUTPUT) { + "MissedPathReportPath=$missedPathReportPath" >> $env:GITHUB_OUTPUT +} + # Build HTML table for 'missed' commands $tableheader = @'