Skip to content

Commit fbe22e8

Browse files
🌟 [Major]: Introducing Get-PesterCodeCoverage (#1)
## Description This pull request introduces the `Get-PesterCodeCoverage` GitHub Action that aggregates Pester code coverage reports and generates a detailed summary with coverage statistics. ### Core functionality * `scripts/main.ps1`: Added functionality to aggregate Pester code coverage reports, normalize file paths, and compute aggregate coverage statistics. The action now enforces a coverage threshold and generates detailed GitHub step summaries with expandable sections for missed and executed commands. ### Tests * `.github/workflows/Action-Test.yml`: Updated the `Action-Test.yml` workflow to upload multiple code coverage artifacts for macOS and Windows environments and added new parameters (`StepSummary_Mode` and `CodeCoveragePercentTarget`) to control reporting behavior. * `tests/*`: Added tests reports to use for the Action-Test workflow. ### Documentation * `README.md`: Updated README to reflect the new functionality, including detailed usage instructions, input descriptions, and example workflows. Added a comprehensive explanation of outputs and behavior. ### Action Metadata * `action.yml`: Updated to align with the new functionality. Added new inputs (`StepSummary_Mode`, `CodeCoveragePercentTarget`) for customization. ## Type of change <!-- Use the check-boxes [x] on the options that are relevant. --> - [ ] 📖 [Docs] - [ ] 🪲 [Fix] - [ ] 🩹 [Patch] - [ ] ⚠️ [Security fix] - [ ] 🚀 [Feature] - [x] 🌟 [Breaking change] ## Checklist <!-- Use the check-boxes [x] on the options that are relevant. --> - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas
1 parent e628e43 commit fbe22e8

19 files changed

+3060
-40
lines changed

Diff for: .github/workflows/Action-Test.yml

+51-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,57 @@ jobs:
2525
- name: Checkout repo
2626
uses: actions/checkout@v4
2727

28+
# Upload artifact from tests:
29+
- name: Upload artifact [Environments-macOS-CodeCoverage]
30+
uses: actions/upload-artifact@v4
31+
with:
32+
name: Environments-macOS-CodeCoverage
33+
path: ./tests/CodeCoverage/Environments-macOS-CodeCoverage
34+
retention-days: 1
35+
if-no-files-found: error
36+
37+
- name: Upload artifact [Environments-Windows-CodeCoverage]
38+
uses: actions/upload-artifact@v4
39+
with:
40+
name: Environments-Windows-CodeCoverage
41+
path: ./tests/CodeCoverage/Environments-Windows-CodeCoverage
42+
retention-days: 1
43+
if-no-files-found: error
44+
45+
- name: Upload artifact [Module-macOS-CodeCoverage]
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: Module-macOS-CodeCoverage
49+
path: ./tests/CodeCoverage/Module-macOS-CodeCoverage
50+
retention-days: 1
51+
if-no-files-found: error
52+
53+
- name: Upload artifact [Module-Windows-CodeCoverage]
54+
uses: actions/upload-artifact@v4
55+
with:
56+
name: Module-Windows-CodeCoverage
57+
path: ./tests/CodeCoverage/Module-Windows-CodeCoverage
58+
retention-days: 1
59+
if-no-files-found: error
60+
61+
- name: Upload artifact [MyTests-macOS-CodeCoverage]
62+
uses: actions/upload-artifact@v4
63+
with:
64+
name: MyTests-macOS-CodeCoverage
65+
path: ./tests/CodeCoverage/MyTests-macOS-CodeCoverage
66+
retention-days: 1
67+
if-no-files-found: error
68+
69+
- name: Upload artifact [MyTests-Windows-CodeCoverage]
70+
uses: actions/upload-artifact@v4
71+
with:
72+
name: MyTests-Windows-CodeCoverage
73+
path: ./tests/CodeCoverage/MyTests-Windows-CodeCoverage
74+
retention-days: 1
75+
if-no-files-found: error
76+
2877
- name: Action-Test
2978
uses: ./
3079
with:
31-
working-directory: ./tests
32-
subject: PSModule
80+
StepSummary_Mode: Full
81+
CodeCoveragePercentTarget: 50

Diff for: .github/workflows/Auto-Release.yml

-2
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,3 @@ jobs:
3030

3131
- name: Auto-Release
3232
uses: PSModule/Auto-Release@v1
33-
env:
34-
GITHUB_TOKEN: ${{ github.token }}

Diff for: README.md

+101-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,112 @@
1-
# Template-Action
1+
# Get-PesterCodeCoverage
22

3-
A template repository for GitHub Actions
3+
A GitHub Action that aggregates Pester code coverage reports and generates a detailed summary with coverage statistics.
4+
Fails the workflow if coverage falls below specified targets.
5+
6+
This GitHub Action is a part of the [PSModule framework](https://github.com/PSModule). It is recommended to use the
7+
[Process-PSModule workflow](https://github.com/PSModule/Process-PSModule) to automate the whole process of managing the PowerShell module.
8+
9+
## Features
10+
11+
- Combines multiple code coverage reports from parallel test runs
12+
- Generates markdown/HTML tables showing missed & executed commands
13+
- Displays analyzed files and coverage statistics
14+
- Configurable step summary sections
15+
- Threshold enforcement for minimum code coverage
416

517
## Usage
618

719
### Inputs
820

9-
### Secrets
21+
| Name | Description | Required | Default |
22+
|------|-------------|----------|---------|
23+
| `Debug` | Enable debug output | No | `false` |
24+
| `Verbose` | Enable verbose output | No | `false` |
25+
| `Version` | Exact version of GitHub module to install | No | Latest |
26+
| `Prerelease` | Allow prerelease versions | No | `false` |
27+
| `WorkingDirectory` | Working directory for the action | No | `.` |
28+
| `StepSummary_Mode` | Controls which sections to show in the GitHub step summary. Use 'Full' for all sections, 'None' to disable, or a comma-separated list of 'Missed, Executed, Files'. | No | `Missed, Files` |
29+
| `CodeCoveragePercentTarget` | Target code coverage percentage | No | Max target from individual reports |
30+
31+
### Example Workflow
32+
33+
```yaml
34+
- name: Process Code Coverage
35+
uses: PSModule/Get-PesterCodeCoverage@v1
36+
with:
37+
StepSummary_Mode: Full
38+
CodeCoveragePercentTarget: 80
39+
```
40+
41+
## Outputs
42+
43+
### GitHub Step Summary
44+
45+
The action generates a detailed summary visible in the GitHub Actions UI:
46+
47+
1. **Coverage Overview Table**
48+
- Coverage percentage vs target
49+
- Analyzed/executed/missed command counts
50+
- Number of files analyzed
51+
52+
2. **Expandable Sections**
53+
- **Missed Commands**: HTML table with code snippets
54+
- **Executed Commands**: HTML table with code snippets
55+
- **Analyzed Files**: List of covered files
56+
57+
Example summary:
58+
59+
```markdown
60+
✅ Code Coverage Report
61+
62+
Summary:
63+
| Coverage | Target | Analyzed | Executed | Missed | Files |
64+
|----------|--------|---------------|---------------|---------------|---------------|
65+
| 85% | 80% | 1000 commands | 850 commands | 150 commands | 15 files |
66+
67+
▶️ Missed commands [150] (click to expand)
68+
▶️ Executed commands [850] (click to expand)
69+
▶️ Files analyzed [15] (click to expand)
70+
```
71+
72+
## Requirements
73+
74+
1. **Pester Code Coverage Reports**
75+
Preceding steps must generate JSON coverage reports named `*-CodeCoverage*.json`
76+
77+
2. **GitHub CLI**
78+
The action uses `gh run download` to fetch artifacts from the current workflow run
79+
80+
## Behavior
81+
82+
1. **Coverage Calculation**
83+
- Combines multiple coverage reports
84+
- Removes duplicate entries
85+
- Calculates aggregate coverage percentage
86+
87+
2. **Threshold Enforcement**
88+
Fails the workflow if coverage is below either:
89+
- Explicitly specified `CodeCoveragePercentTarget`
90+
- Highest target from individual reports (if no target specified)
91+
92+
3. **Output Control**
93+
Configure visibility of sections using `StepSummary_Mode`:
94+
```yaml
95+
# Show all sections
96+
StepSummary_Mode: Full
97+
98+
# Disable summary
99+
StepSummary_Mode: None
10100

11-
### Outputs
101+
# Custom selection
102+
StepSummary_Mode: Missed, Files
103+
```
12104
13-
### Example
105+
## Troubleshooting
14106
107+
Enable debugging by setting inputs:
15108
```yaml
16-
Example here
109+
with:
110+
Debug: true
111+
Verbose: true
17112
```

Diff for: action.yml

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
name: {{ NAME }}
2-
description: {{ DESCRIPTION }}
1+
name: Get-PesterCodeCoverage
2+
description: A GitHub Action that is used to gather Code Coverage for the PSModule process.
33
author: PSModule
44
branding:
55
icon: upload-cloud
66
color: white
77

88
inputs:
9-
subject:
10-
description: The subject to greet
11-
required: false
12-
default: World
139
Debug:
1410
description: Enable debug output.
1511
required: false
@@ -28,21 +24,31 @@ inputs:
2824
WorkingDirectory:
2925
description: The working directory where the script will run from.
3026
required: false
31-
default: ${{ github.workspace }}
27+
default: '.'
28+
StepSummary_Mode:
29+
description: |
30+
Controls which sections to show in the GitHub step summary.
31+
Use 'Full' for all sections, 'None' to disable, or a comma-separated list of 'Missed, Executed, Files'.
32+
required: false
33+
default: Missed, Files
34+
CodeCoveragePercentTarget:
35+
description: The target for code coverage.
36+
required: false
3237

3338
runs:
3439
using: composite
3540
steps:
36-
- name: {{ NAME }}
41+
- name: Get-PesterCodeCoverage
3742
uses: PSModule/GitHub-Script@v1
3843
env:
39-
{{ ORG }}_{{ NAME }}_INPUT_subject: ${{ inputs.subject }}
44+
PSMODULE_GET_PESTERCODECOVERAGE_INPUT_StepSummary_Mode: ${{ inputs.StepSummary_Mode }}
45+
PSMODULE_GET_PESTERCODECOVERAGE_INPUT_CodeCoveragePercentTarget: ${{ inputs.CodeCoveragePercentTarget }}
4046
with:
47+
Name: Get-PesterCodeCoverage
4148
Debug: ${{ inputs.Debug }}
4249
Prerelease: ${{ inputs.Prerelease }}
4350
Verbose: ${{ inputs.Verbose }}
4451
Version: ${{ inputs.Version }}
4552
WorkingDirectory: ${{ inputs.WorkingDirectory }}
46-
Script: |
47-
# {{ NAME }}
48-
${{ github.action_path }}/scripts/main.ps1
53+
ShowInfo: false
54+
Script: ${{ github.action_path }}/scripts/main.ps1

Diff for: scripts/Helpers.psm1

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
function Normalize-IndentationExceptFirst {
2+
<#
3+
.SYNOPSIS
4+
Normalizes the indentation of a multi-line string, except for the first line.
5+
6+
.DESCRIPTION
7+
This function takes a multi-line string and normalizes the indentation of all lines except the first one.
8+
It removes the minimum leading whitespace from all subsequent lines.
9+
10+
.PARAMETER Code
11+
The multi-line string to normalize.
12+
13+
.OUTPUTS
14+
Returns the normalized multi-line string.
15+
s#>
16+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '', Scope = 'Function', Justification = 'Function isnt exported.')]
17+
[OutputType([string])]
18+
[CmdletBinding()]
19+
param(
20+
[Parameter(Mandatory)]
21+
[string]$Code
22+
)
23+
24+
# Split the code into lines
25+
$lines = $Code -split "`r?`n"
26+
27+
# If there's 0 or 1 line, there's nothing special to do
28+
if ($lines.Count -le 1) {
29+
return $Code
30+
}
31+
32+
# The first line stays as-is; we skip it for indentation measurement
33+
$firstLine = $lines[0]
34+
$subsequentLines = $lines[1..($lines.Count - 1)]
35+
36+
# Find the minimum leading indentation among the *subsequent* lines
37+
$minIndent = ($subsequentLines | Where-Object { $_ -match '\S' } | ForEach-Object {
38+
# If the line starts with whitespace, capture how many characters
39+
if ($_ -match '^(\s+)') {
40+
$matches[1].Length
41+
} else {
42+
0
43+
}
44+
} | Measure-Object -Minimum).Minimum
45+
46+
# Remove that leading indentation from each subsequent line
47+
for ($i = 0; $i -lt $subsequentLines.Count; $i++) {
48+
$line = $subsequentLines[$i]
49+
50+
# Only attempt to remove indentation if we actually have some whitespace
51+
if ($line -match '^(\s+)(.*)$') {
52+
# $matches[1] = leading whitespace; $matches[2] = the rest
53+
$leading = $matches[1]
54+
$rest = $matches[2]
55+
56+
# If we have enough whitespace to remove $minIndent worth, do it
57+
if ($leading.Length -ge $minIndent) {
58+
$leading = $leading.Substring($minIndent)
59+
}
60+
# Recombine
61+
$subsequentLines[$i] = $leading + $rest
62+
}
63+
}
64+
65+
$newLine = [Environment]::NewLine
66+
# Reconstruct the final code: first line + adjusted subsequent lines
67+
return ($firstLine + $newLine + ($subsequentLines -join $newLine))
68+
}

0 commit comments

Comments
 (0)