Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
248ccc2
Add artifact output directory action tests
MariusStorhaug Sep 10, 2026
5642454
Align artifact path test fixture
MariusStorhaug Sep 10, 2026
5602645
Add repository artifact output directory
MariusStorhaug Sep 10, 2026
89ce67d
Verify generated artifact paths directly
MariusStorhaug Sep 10, 2026
f5e3a9f
Load output directory configuration during execution
MariusStorhaug Sep 10, 2026
ac29239
Use outputs in artifact directory example
MariusStorhaug Sep 10, 2026
1a1d78a
Keep temporary Pester files separate from reports
MariusStorhaug Sep 10, 2026
d1b8697
Limit output directory to Pester reports
MariusStorhaug Sep 10, 2026
b404353
Require a unified framework artifact directory
MariusStorhaug Sep 10, 2026
b620620
Restore unified framework artifact directory
MariusStorhaug Sep 10, 2026
01ddc58
Require a separate report output directory
MariusStorhaug Sep 10, 2026
3401570
Keep temporary files outside report output directory
MariusStorhaug Sep 10, 2026
e0025d1
Restore unified framework artifact test
MariusStorhaug Sep 10, 2026
db5478b
Restore unified framework artifact directory
MariusStorhaug Sep 10, 2026
d99dce0
Test configured Pester report paths
MariusStorhaug Sep 10, 2026
282916d
Honor configured Pester report paths
MariusStorhaug Sep 10, 2026
dc9c065
Test Pester report path precedence
MariusStorhaug Sep 10, 2026
09a9d15
Correct report path test fixtures
MariusStorhaug Sep 10, 2026
d2c31fc
Isolate report path test configuration
MariusStorhaug Sep 10, 2026
29f8dc0
Flatten temporary Pester handoff directory
MariusStorhaug Sep 10, 2026
31f178d
Use the platform temporary directory
MariusStorhaug Sep 10, 2026
36a0285
Isolate invocation state and JSON reports
MariusStorhaug Sep 10, 2026
896d894
Fix lint findings in report path tests
MariusStorhaug Sep 10, 2026
c28dc99
Test JSON report path collisions
MariusStorhaug Sep 10, 2026
212d771
Prevent report artifact path collisions
MariusStorhaug Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions .github/workflows/Action-Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,130 @@ jobs:
Write-Host "This is a prescript"
Write-Host "We are running on $env:RUNNER_OS"

- name: Assert default artifact paths
shell: pwsh
env:
TEMP_PATH: ${{ steps.action-test.outputs.TempPath }}
run: |
tests/Assert-ArtifactPaths.ps1 `
-TestResultPath 'tests/2-Standard/TestResult/Standard-TestResult-Report.xml' `
-CodeCoveragePath 'tests/2-Standard/CodeCoverage/Standard-CodeCoverage-Report.xml' `
-TempPath $env:TEMP_PATH

- name: Status
shell: pwsh
env:
OUTCOME: ${{ steps.action-test.outcome }}
CONCLUSION: ${{ steps.action-test.conclusion }}
run: tests/Show-Status.ps1

ActionTestOutputPathInputs:
name: Action-Test - [Output Paths - ${{ matrix.name }}]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Action inputs
path: tests/2-Standard/Emoji.Configuration.ps1
test_result_input: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml
code_coverage_input: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml
expected_test_result: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml
expected_code_coverage: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml
unexpected_test_result: ''
unexpected_code_coverage: ''
- name: Configuration file
path: tests/5-ReportPaths/ReportPaths.Configuration.ps1
test_result_input: ''
code_coverage_input: ''
expected_test_result: .PSModule/Configuration/TestResult/Configured-TestResult-Report.xml
expected_code_coverage: .PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml
unexpected_test_result: ''
unexpected_code_coverage: ''
- name: Action input precedence
path: tests/5-ReportPaths/ReportPaths.Configuration.ps1
test_result_input: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml
code_coverage_input: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml
expected_test_result: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml
expected_code_coverage: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml
unexpected_test_result: .PSModule/Configuration/TestResult/Configured-TestResult-Report.xml
unexpected_code_coverage: .PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml
- name: Test result input only
path: tests/2-Standard/Emoji.Configuration.ps1
test_result_input: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml
code_coverage_input: ''
expected_test_result: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml
expected_code_coverage: CodeCoverage/Standard-CodeCoverage-Report.xml
unexpected_test_result: ''
unexpected_code_coverage: ''
- name: Code coverage input only
path: tests/2-Standard/Emoji.Configuration.ps1
test_result_input: ''
code_coverage_input: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml
expected_test_result: TestResult/Standard-TestResult-Report.xml
expected_code_coverage: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml
unexpected_test_result: ''
unexpected_code_coverage: ''
- name: Non-XML extensions
path: tests/2-Standard/Emoji.Configuration.ps1
test_result_input: .PSModule/Extensions/TestResult/results.junit
code_coverage_input: .PSModule/Extensions/CodeCoverage/coverage.cobertura
expected_test_result: .PSModule/Extensions/TestResult/results.junit
expected_code_coverage: .PSModule/Extensions/CodeCoverage/coverage.cobertura
expected_test_result_json: ''
expected_code_coverage_json: ''
unexpected_test_result: ''
unexpected_code_coverage: ''
- name: JSON extensions
path: tests/2-Standard/Emoji.Configuration.ps1
test_result_input: .PSModule/Collisions/TestResult/results.json
code_coverage_input: .PSModule/Collisions/CodeCoverage/coverage.json
expected_test_result: .PSModule/Collisions/TestResult/results.json
expected_code_coverage: .PSModule/Collisions/CodeCoverage/coverage.json
expected_test_result_json: .PSModule/Collisions/TestResult/results.json.json
expected_code_coverage_json: .PSModule/Collisions/CodeCoverage/coverage.json.json
unexpected_test_result: ''
unexpected_code_coverage: ''
- name: Shared stem
path: tests/2-Standard/Emoji.Configuration.ps1
test_result_input: .PSModule/Collisions/Shared/report.xml
code_coverage_input: .PSModule/Collisions/Shared/report.cobertura
expected_test_result: .PSModule/Collisions/Shared/report.xml
expected_code_coverage: .PSModule/Collisions/Shared/report.cobertura
expected_test_result_json: .PSModule/Collisions/Shared/report.json
expected_code_coverage_json: .PSModule/Collisions/Shared/report.cobertura.json
unexpected_test_result: ''
unexpected_code_coverage: ''

steps:
# Need to check out as part of the test, as it's a local action
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Action-Test [Output Paths - ${{ matrix.name }}]
uses: ./
id: action-test
with:
Path: ${{ matrix.path }}
TestResult_OutputPath: ${{ matrix.test_result_input }}
CodeCoverage_OutputPath: ${{ matrix.code_coverage_input }}

- name: Assert artifact paths
shell: pwsh
env:
TEMP_PATH: ${{ steps.action-test.outputs.TempPath }}
run: |
tests/Assert-ArtifactPaths.ps1 `
-TestResultPath '${{ matrix.expected_test_result }}' `
-CodeCoveragePath '${{ matrix.expected_code_coverage }}' `
-TempPath $env:TEMP_PATH `
-TestResultJsonPath '${{ matrix.expected_test_result_json }}' `
-CodeCoverageJsonPath '${{ matrix.expected_code_coverage_json }}' `
-UnexpectedTestResultPath '${{ matrix.unexpected_test_result }}' `
-UnexpectedCodeCoveragePath '${{ matrix.unexpected_code_coverage }}'

ActionTest2StandardPrescriptFile:
name: Action-Test - [2-Standard-PrescriptFile]
runs-on: ubuntu-latest
Expand Down Expand Up @@ -240,6 +357,46 @@ jobs:
CONCLUSION: ${{ steps.action-test.conclusion }}
run: tests/Show-Status.ps1

ActionTestTempPathIsolation:
name: Action-Test - [Temporary Path Isolation]
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: First Invoke-Pester invocation
uses: ./
id: first
with:
WorkingDirectory: tests/1-Simple
Path: Emoji.Tests.ps1

- name: Second Invoke-Pester invocation
uses: ./
id: second
with:
WorkingDirectory: tests/1-Simple
Path: Cars.Tests.ps1

- name: Assert temporary path isolation
shell: pwsh
env:
FIRST_TEMP_PATH: ${{ steps.first.outputs.TempPath }}
SECOND_TEMP_PATH: ${{ steps.second.outputs.TempPath }}
run: |
if ($env:FIRST_TEMP_PATH -eq $env:SECOND_TEMP_PATH) {
throw "Expected invocation-specific temporary paths, but both used [$env:FIRST_TEMP_PATH]."
}
foreach ($tempPath in @($env:FIRST_TEMP_PATH, $env:SECOND_TEMP_PATH)) {
$configurationPath = Join-Path -Path $tempPath -ChildPath 'Invoke-Pester.Configuration.ps1'
if (-not (Test-Path -Path $configurationPath -PathType Leaf)) {
throw "Expected an invocation configuration at [$configurationPath]."
}
}

ActionTest2StandardNoSummary:
name: Action-Test - [2-Standard-NoSummary]
runs-on: ubuntu-latest
Expand Down Expand Up @@ -470,6 +627,8 @@ jobs:
- ActionTest1SimpleFailureOnlyFailedSummary
- ActionTest1SimpleExecutionFailure
- ActionTest2Standard
- ActionTestOutputPathInputs
- ActionTestTempPathIsolation
- ActionTest2StandardPrescriptFile
- ActionTest2StandardNoSummary
- ActionTest3Advanced
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ The action provides the following outputs:
| `InconclusiveCount` | Number of inconclusive tests |
| `NotRunCount` | Number of tests not run |
| `TotalCount` | Total count of tests |
| `TempPath` | Invocation-specific action temporary directory |

## Examples

Expand Down Expand Up @@ -410,6 +411,26 @@ jobs:
CodeCoverage_OutputFormat: 'JaCoCo'
```

### Store Reports in a Custom Directory

Use Pester's existing output-path inputs to configure the report locations. Direct action inputs take precedence over values from a Pester
configuration file.

```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run Pester tests
uses: PSModule/Invoke-Pester@v3
with:
Path: './tests'
TestResult_OutputPath: '.PSModule/TestResult/results.xml'
CodeCoverage_OutputPath: '.PSModule/CodeCoverage/coverage.xml'
```

### Import a module before pester runs

```yaml
Expand Down
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,26 @@ outputs:
description: |
Total count of tests.
value: ${{ steps.test.outputs.TotalCount }}
TempPath:
description: |
Path containing action-private temporary files for this invocation.
value: ${{ steps.temp.outputs.Path }}

runs:
using: composite
steps:
- name: Initialize temporary path
id: temp
shell: pwsh
run: |
$tempPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "Invoke-Pester/$([System.IO.Path]::GetRandomFileName())"
Write-Output "Temporary path: [$tempPath]"
"Path=$tempPath" >> $env:GITHUB_OUTPUT

- name: Invoke-Pester (init)
uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0
env:
PSMODULE_INVOKE_PESTER_INTERNAL_TempPath: ${{ steps.temp.outputs.Path }}
PSMODULE_INVOKE_PESTER_INPUT_Path: ${{ inputs.Path }}
PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }}
PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }}
Expand Down Expand Up @@ -369,6 +382,7 @@ runs:
continue-on-error: true
working-directory: ${{ inputs.WorkingDirectory }}
env:
PSMODULE_INVOKE_PESTER_INTERNAL_TempPath: ${{ steps.temp.outputs.Path }}
PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }}
PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }}
PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }}
Expand Down
82 changes: 63 additions & 19 deletions src/exec.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele
'::endgroup::'

'::group::Exec - Info about environment'
$path = Join-Path -Path $pwd.Path -ChildPath '.temp'
$path = $env:PSMODULE_INVOKE_PESTER_INTERNAL_TempPath
if ([string]::IsNullOrWhiteSpace($path)) {
throw 'A temporary path is required to read Pester handoff files.'
}
Test-Path -Path $path
Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String

Expand Down Expand Up @@ -118,20 +121,73 @@ if ($generateSummary) {
'::endgroup::'

'::group::Eval - Set outputs'
$testResultOutputFolderPath = $testResults.Configuration.TestResult.OutputPath.Value | Split-Path -Parent
$codeCoverageOutputFolderPath = $testResults.Configuration.CodeCoverage.OutputPath.Value | Split-Path -Parent
$testResultOutputPath = $testResults.Configuration.TestResult.OutputPath.Value
$codeCoverageOutputPath = $testResults.Configuration.CodeCoverage.OutputPath.Value
$reservedOutputPaths = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
$null = $reservedOutputPaths.Add($testResultOutputPath)
$null = $reservedOutputPaths.Add($codeCoverageOutputPath)

function Get-JsonOutputPath {
<#
.SYNOPSIS
Selects a JSON companion path that cannot overwrite another configured report.
#>
[OutputType([string])]
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string] $ReportPath,

[Parameter(Mandatory)]
[System.Collections.Generic.HashSet[string]] $ReservedPaths
)

$jsonOutputPath = [System.IO.Path]::ChangeExtension($ReportPath, '.json')
$suffix = '.json'
while (-not $ReservedPaths.Add($jsonOutputPath)) {
$jsonOutputPath = "$ReportPath$suffix"
$suffix += '.json'
}
return $jsonOutputPath
}

$testResultJsonOutputPath = $null
$codeCoverageJsonOutputPath = $null
if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.TestResult.Enabled.Value) {
$testResultJsonOutputPath = Get-JsonOutputPath -ReportPath $testResultOutputPath -ReservedPaths $reservedOutputPaths
Write-Output "Exporting test results to [$testResultJsonOutputPath]"
$testResults | Get-PesterTestTree | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $testResultJsonOutputPath
}

if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.CodeCoverage.Enabled.Value) {
$codeCoverageJsonOutputPath = Get-JsonOutputPath -ReportPath $codeCoverageOutputPath -ReservedPaths $reservedOutputPaths
Write-Output "Exporting code coverage results to [$codeCoverageJsonOutputPath]"
$testResults.CodeCoverage | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $codeCoverageJsonOutputPath
}

$testResultArtifactPaths = @($testResultOutputPath) + @($testResultJsonOutputPath) | Where-Object { $_ }
$codeCoverageArtifactPaths = @($codeCoverageOutputPath) + @($codeCoverageJsonOutputPath) | Where-Object { $_ }
[pscustomobject]@{
TestSuiteName = $testResults.Configuration.TestResult.TestSuiteName.Value
TestResultEnabled = $testResults.Configuration.TestResult.Enabled.Value
TestResultOutputPath = $testResultOutputFolderPath
TestResultOutputPath = $testResultArtifactPaths
CodeCoverageEnabled = $testResults.Configuration.CodeCoverage.Enabled.Value
CodeCoverageOutputPath = $codeCoverageOutputFolderPath
CodeCoverageOutputPath = $codeCoverageArtifactPaths
} | Format-List | Out-String
"TestSuiteName=$($testResults.Configuration.TestResult.TestSuiteName.Value)" >> $env:GITHUB_OUTPUT
"TestResultEnabled=$($testResults.Configuration.TestResult.Enabled.Value)" >> $env:GITHUB_OUTPUT
"TestResultOutputPath=$($testResultOutputFolderPath)" >> $env:GITHUB_OUTPUT
"CodeCoverageEnabled=$($testResults.Configuration.CodeCoverage.Enabled.Value)" >> $env:GITHUB_OUTPUT
"CodeCoverageOutputPath=$($codeCoverageOutputFolderPath)" >> $env:GITHUB_OUTPUT
foreach ($output in @{
TestResultOutputPath = $testResultArtifactPaths
CodeCoverageOutputPath = $codeCoverageArtifactPaths
}.GetEnumerator()) {
$delimiter = "InvokePester_$([System.Guid]::NewGuid().ToString('N'))"
"$($output.Key)<<$delimiter" >> $env:GITHUB_OUTPUT
$output.Value >> $env:GITHUB_OUTPUT
$delimiter >> $env:GITHUB_OUTPUT
}
"Executed=$($testResults.Executed)" >> $env:GITHUB_OUTPUT
"Result=$($testResults.Result)" >> $env:GITHUB_OUTPUT
"FailedCount=$($testResults.FailedCount)" >> $env:GITHUB_OUTPUT
Expand All @@ -142,18 +198,6 @@ $codeCoverageOutputFolderPath = $testResults.Configuration.CodeCoverage.OutputPa
"InconclusiveCount=$($testResults.InconclusiveCount)" >> $env:GITHUB_OUTPUT
"NotRunCount=$($testResults.NotRunCount)" >> $env:GITHUB_OUTPUT
"TotalCount=$($testResults.TotalCount)" >> $env:GITHUB_OUTPUT

if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.TestResult.Enabled.Value) {
$jsonOutputPath = $testResults.Configuration.TestResult.OutputPath.Value -replace '\.xml$', '.json'
Write-Output "Exporting test results to [$jsonOutputPath]"
$testResults | Get-PesterTestTree | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $jsonOutputPath
}

if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.CodeCoverage.Enabled.Value) {
$jsonOutputPath = $testResults.Configuration.CodeCoverage.OutputPath.Value -replace '\.xml$', '.json'
Write-Output "Exporting code coverage results to [$jsonOutputPath]"
$testResults.CodeCoverage | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $jsonOutputPath
}
'::endgroup::'

'::group::Exit'
Expand Down
Loading
Loading