From 248ccc2ead3cb00d679cc8e5e75767fd6c132967 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 13:48:11 +0200 Subject: [PATCH 01/25] Add artifact output directory action tests --- .github/workflows/Action-Test.yml | 41 +++++++++++++++++++++++++++ tests/Assert-ArtifactPaths.ps1 | 46 +++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/Assert-ArtifactPaths.ps1 diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index fb63f0c5..2ce3a14b 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -201,6 +201,17 @@ jobs: Write-Host "This is a prescript" Write-Host "We are running on $env:RUNNER_OS" + - name: Assert default artifact paths + shell: pwsh + env: + TEST_RESULT_OUTPUT_PATH: ${{ steps.action-test.outputs.TestResultOutputPath }} + CODE_COVERAGE_OUTPUT_PATH: ${{ steps.action-test.outputs.CodeCoverageOutputPath }} + run: | + tests/Assert-ArtifactPaths.ps1 ` + -Layout Default ` + -TestResultOutputPath $env:TEST_RESULT_OUTPUT_PATH ` + -CodeCoverageOutputPath $env:CODE_COVERAGE_OUTPUT_PATH + - name: Status shell: pwsh env: @@ -208,6 +219,35 @@ jobs: CONCLUSION: ${{ steps.action-test.conclusion }} run: tests/Show-Status.ps1 + ActionTestOutputDirectory: + name: Action-Test - [Output Directory] + runs-on: ubuntu-latest + + 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 Directory] + uses: ./ + id: action-test + with: + WorkingDirectory: tests/2-Standard + OutputDirectory: .PSModule + + - name: Assert .PSModule artifact paths + shell: pwsh + env: + TEST_RESULT_OUTPUT_PATH: ${{ steps.action-test.outputs.TestResultOutputPath }} + CODE_COVERAGE_OUTPUT_PATH: ${{ steps.action-test.outputs.CodeCoverageOutputPath }} + run: | + tests/Assert-ArtifactPaths.ps1 ` + -Layout PSModule ` + -TestResultOutputPath $env:TEST_RESULT_OUTPUT_PATH ` + -CodeCoverageOutputPath $env:CODE_COVERAGE_OUTPUT_PATH + ActionTest2StandardPrescriptFile: name: Action-Test - [2-Standard-PrescriptFile] runs-on: ubuntu-latest @@ -470,6 +510,7 @@ jobs: - ActionTest1SimpleFailureOnlyFailedSummary - ActionTest1SimpleExecutionFailure - ActionTest2Standard + - ActionTestOutputDirectory - ActionTest2StandardPrescriptFile - ActionTest2StandardNoSummary - ActionTest3Advanced diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 new file mode 100644 index 00000000..d039b4af --- /dev/null +++ b/tests/Assert-ArtifactPaths.ps1 @@ -0,0 +1,46 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidateSet('Default', 'PSModule')] + [string] $Layout, + + [Parameter(Mandatory)] + [string] $TestResultOutputPath, + + [Parameter(Mandatory)] + [string] $CodeCoverageOutputPath +) + +$outputDirectory = switch ($Layout) { + 'Default' { + Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' + } + 'PSModule' { + Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.PSModule' + } +} + +$expectedPaths = @{ + '.temp configuration' = Join-Path -Path $outputDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' + 'code coverage report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.xml' + 'code coverage JSON report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.json' + 'test result report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.xml' + 'test result JSON report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.json' +} + +$expectedTestResultOutputPath = Join-Path -Path $outputDirectory -ChildPath 'TestResult' +$expectedCodeCoverageOutputPath = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage' + +if ($TestResultOutputPath -ne $expectedTestResultOutputPath) { + throw "Expected TestResultOutputPath [$expectedTestResultOutputPath], but received [$TestResultOutputPath]." +} + +if ($CodeCoverageOutputPath -ne $expectedCodeCoverageOutputPath) { + throw "Expected CodeCoverageOutputPath [$expectedCodeCoverageOutputPath], but received [$CodeCoverageOutputPath]." +} + +foreach ($artifact in $expectedPaths.GetEnumerator()) { + if (-not (Test-Path -Path $artifact.Value -PathType Leaf)) { + throw "Expected $($artifact.Key) at [$($artifact.Value)]." + } +} From 5642454488795b3fe19cbcb1eb7ef4adaa0c0b50 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 13:49:53 +0200 Subject: [PATCH 02/25] Align artifact path test fixture --- tests/Assert-ArtifactPaths.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index d039b4af..99a97c2a 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -21,11 +21,11 @@ $outputDirectory = switch ($Layout) { } $expectedPaths = @{ - '.temp configuration' = Join-Path -Path $outputDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' - 'code coverage report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.xml' + '.temp configuration' = Join-Path -Path $outputDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' + 'code coverage report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.xml' 'code coverage JSON report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.json' - 'test result report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.xml' - 'test result JSON report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.json' + 'test result report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.xml' + 'test result JSON report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.json' } $expectedTestResultOutputPath = Join-Path -Path $outputDirectory -ChildPath 'TestResult' From 5602645363ba63c7ad7540cd7190383ae178dc2c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 13:49:53 +0200 Subject: [PATCH 03/25] Add repository artifact output directory --- README.md | 20 ++++++++++++++++++++ action.yml | 7 +++++++ src/init.ps1 | 40 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c2123da..48026f07 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | +| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | *(working directory)* | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | @@ -410,6 +411,25 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` +### Store Action-Generated Files Under `.PSModule` + +Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An +empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. + +```yaml +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run Pester tests + uses: PSModule/Invoke-Pester@v3 + with: + Path: './tests' + OutputDirectory: '.PSModule' +``` + ### Import a module before pester runs ```yaml diff --git a/action.yml b/action.yml index 99301eb6..ba473b24 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,12 @@ inputs: description: | Path to where tests are located or a configuration file. required: false + OutputDirectory: + description: | + Optional repository-relative directory for action-generated TestResult, CodeCoverage, and .temp files. + When empty, files are generated in the working directory for backward compatibility. + required: false + default: '' Version: description: | Version of the Pester module to install, using NuGet version-range syntax, e.g. '[6.0.0,7.0.0)' to allow any 6.x. @@ -306,6 +312,7 @@ runs: uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 env: PSMODULE_INVOKE_PESTER_INPUT_Path: ${{ inputs.Path }} + PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/init.ps1 b/src/init.ps1 index 23186d25..224ff1b4 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -25,6 +25,7 @@ LogGroup 'Init - Load inputs' { $inputs = @{ Path = $path + OutputDirectory = $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory Run_Path = $env:PSMODULE_INVOKE_PESTER_INPUT_Run_Path Run_ExcludePath = $env:PSMODULE_INVOKE_PESTER_INPUT_Run_ExcludePath @@ -172,6 +173,39 @@ LogGroup 'Init - Load configuration' { $configuration | Format-Hashtable | Out-String } +LogGroup 'Init - Resolve output directory' { + $outputDirectory = $pwd.Path + if (-not [string]::IsNullOrWhiteSpace($inputs.OutputDirectory)) { + if ([System.IO.Path]::IsPathRooted($inputs.OutputDirectory)) { + throw "OutputDirectory must be repository-relative: [$($inputs.OutputDirectory)]" + } + + if ([string]::IsNullOrWhiteSpace($env:GITHUB_WORKSPACE)) { + throw 'OutputDirectory requires the GITHUB_WORKSPACE environment variable.' + } + + $repositoryRoot = [System.IO.Path]::GetFullPath($env:GITHUB_WORKSPACE).TrimEnd( + [char[]]@( + [System.IO.Path]::DirectorySeparatorChar, + [System.IO.Path]::AltDirectorySeparatorChar + ) + ) + $outputDirectory = [System.IO.Path]::GetFullPath( + (Join-Path -Path $repositoryRoot -ChildPath $inputs.OutputDirectory) + ) + $repositoryRootWithSeparator = "$repositoryRoot$([System.IO.Path]::DirectorySeparatorChar)" + + if ( + $outputDirectory -ne $repositoryRoot -and + -not $outputDirectory.StartsWith($repositoryRootWithSeparator, [System.StringComparison]::Ordinal) + ) { + throw "OutputDirectory must remain within the repository: [$($inputs.OutputDirectory)]" + } + } + + Write-Output "Output directory: [$outputDirectory]" +} + LogGroup 'Init - Export containers' { $containers = @() $existingContainers = $configuration.Run.Container @@ -185,7 +219,7 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Create temp directory for container output - $path = New-Item -Path . -ItemType Directory -Name '.temp' -Force + $path = New-Item -Path $outputDirectory -ItemType Directory -Name '.temp' -Force # Process each input path foreach ($testDir in $inputs.Path) { @@ -243,8 +277,8 @@ LogGroup 'Init - Export containers' { LogGroup 'Init - Export configuration' { $artifactName = $configuration.TestResult.TestSuiteName ?? 'Pester' - $configuration.TestResult.OutputPath = "$pwd/TestResult/$artifactName-TestResult-Report.xml" - $configuration.CodeCoverage.OutputPath = "$pwd/CodeCoverage/$artifactName-CodeCoverage-Report.xml" + $configuration.TestResult.OutputPath = Join-Path -Path $outputDirectory -ChildPath "TestResult/$artifactName-TestResult-Report.xml" + $configuration.CodeCoverage.OutputPath = Join-Path -Path $outputDirectory -ChildPath "CodeCoverage/$artifactName-CodeCoverage-Report.xml" $configuration.Run.PassThru = $true Format-Hashtable -Hashtable $configuration From 89ce67dc8a77692e70980afda27cd2a3ed404775 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 13:54:20 +0200 Subject: [PATCH 04/25] Verify generated artifact paths directly --- .github/workflows/Action-Test.yml | 18 ++---------------- README.md | 2 +- tests/Assert-ArtifactPaths.ps1 | 19 +------------------ 3 files changed, 4 insertions(+), 35 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 2ce3a14b..d14d9b63 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -203,14 +203,7 @@ jobs: - name: Assert default artifact paths shell: pwsh - env: - TEST_RESULT_OUTPUT_PATH: ${{ steps.action-test.outputs.TestResultOutputPath }} - CODE_COVERAGE_OUTPUT_PATH: ${{ steps.action-test.outputs.CodeCoverageOutputPath }} - run: | - tests/Assert-ArtifactPaths.ps1 ` - -Layout Default ` - -TestResultOutputPath $env:TEST_RESULT_OUTPUT_PATH ` - -CodeCoverageOutputPath $env:CODE_COVERAGE_OUTPUT_PATH + run: tests/Assert-ArtifactPaths.ps1 -Layout Default - name: Status shell: pwsh @@ -239,14 +232,7 @@ jobs: - name: Assert .PSModule artifact paths shell: pwsh - env: - TEST_RESULT_OUTPUT_PATH: ${{ steps.action-test.outputs.TestResultOutputPath }} - CODE_COVERAGE_OUTPUT_PATH: ${{ steps.action-test.outputs.CodeCoverageOutputPath }} - run: | - tests/Assert-ArtifactPaths.ps1 ` - -Layout PSModule ` - -TestResultOutputPath $env:TEST_RESULT_OUTPUT_PATH ` - -CodeCoverageOutputPath $env:CODE_COVERAGE_OUTPUT_PATH + run: tests/Assert-ArtifactPaths.ps1 -Layout PSModule ActionTest2StandardPrescriptFile: name: Action-Test - [2-Standard-PrescriptFile] diff --git a/README.md b/README.md index 48026f07..304c7442 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | -| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | *(working directory)* | +| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | `''` | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 99a97c2a..c1dcb2ee 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -2,13 +2,7 @@ param( [Parameter(Mandatory)] [ValidateSet('Default', 'PSModule')] - [string] $Layout, - - [Parameter(Mandatory)] - [string] $TestResultOutputPath, - - [Parameter(Mandatory)] - [string] $CodeCoverageOutputPath + [string] $Layout ) $outputDirectory = switch ($Layout) { @@ -28,17 +22,6 @@ $expectedPaths = @{ 'test result JSON report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.json' } -$expectedTestResultOutputPath = Join-Path -Path $outputDirectory -ChildPath 'TestResult' -$expectedCodeCoverageOutputPath = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage' - -if ($TestResultOutputPath -ne $expectedTestResultOutputPath) { - throw "Expected TestResultOutputPath [$expectedTestResultOutputPath], but received [$TestResultOutputPath]." -} - -if ($CodeCoverageOutputPath -ne $expectedCodeCoverageOutputPath) { - throw "Expected CodeCoverageOutputPath [$expectedCodeCoverageOutputPath], but received [$CodeCoverageOutputPath]." -} - foreach ($artifact in $expectedPaths.GetEnumerator()) { if (-not (Test-Path -Path $artifact.Value -PathType Leaf)) { throw "Expected $($artifact.Key) at [$($artifact.Value)]." From f5e3a9fe9a9cac1069d61c7bca0a99f6fceb5fb2 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 13:58:15 +0200 Subject: [PATCH 05/25] Load output directory configuration during execution --- action.yml | 1 + src/exec.ps1 | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index ba473b24..4fe17385 100644 --- a/action.yml +++ b/action.yml @@ -377,6 +377,7 @@ runs: working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }} + PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 9810aa46..11f111da 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,7 +24,11 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$path = Join-Path -Path $pwd.Path -ChildPath '.temp' +$outputDirectory = $pwd.Path +if (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory)) { + $outputDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory +} +$path = Join-Path -Path $outputDirectory -ChildPath '.temp' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String From ac292395857d0e96483760ccde14729cb368117f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:03:47 +0200 Subject: [PATCH 06/25] Use outputs in artifact directory example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 304c7442..d3ba5c94 100644 --- a/README.md +++ b/README.md @@ -411,7 +411,7 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` -### Store Action-Generated Files Under `.PSModule` +### Store Action-Generated Files in a Custom Directory Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. @@ -427,7 +427,7 @@ jobs: uses: PSModule/Invoke-Pester@v3 with: Path: './tests' - OutputDirectory: '.PSModule' + OutputDirectory: 'outputs' ``` ### Import a module before pester runs From 1a1d78a95ae1aad7e48770cc423103b447e15f65 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:05:42 +0200 Subject: [PATCH 07/25] Keep temporary Pester files separate from reports --- tests/Assert-ArtifactPaths.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index c1dcb2ee..8aa650cd 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -14,8 +14,10 @@ $outputDirectory = switch ($Layout) { } } +$temporaryDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' + $expectedPaths = @{ - '.temp configuration' = Join-Path -Path $outputDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' + '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' 'code coverage report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.xml' 'code coverage JSON report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.json' 'test result report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.xml' From d1b86976c378f45dfd6333740bdf05f0e32d3742 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:06:07 +0200 Subject: [PATCH 08/25] Limit output directory to Pester reports --- README.md | 8 ++++---- action.yml | 5 ++--- src/exec.ps1 | 6 +----- src/init.ps1 | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d3ba5c94..f9fcc3b7 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | -| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | `''` | +| `OutputDirectory` | Repository-relative directory for generated `TestResult` and `CodeCoverage` files. | `''` | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | @@ -411,10 +411,10 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` -### Store Action-Generated Files in a Custom Directory +### Store Reports in a Custom Directory -Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An -empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. +Set `OutputDirectory` to place the action's `TestResult` and `CodeCoverage` directories under a repository-relative location. An empty value, +the default, preserves the existing behavior of placing reports in `WorkingDirectory`. Internal `.temp` files remain in `WorkingDirectory`. ```yaml jobs: diff --git a/action.yml b/action.yml index 4fe17385..541ac123 100644 --- a/action.yml +++ b/action.yml @@ -12,8 +12,8 @@ inputs: required: false OutputDirectory: description: | - Optional repository-relative directory for action-generated TestResult, CodeCoverage, and .temp files. - When empty, files are generated in the working directory for backward compatibility. + Optional repository-relative directory for generated TestResult and CodeCoverage files. + When empty, reports are generated in the working directory for backward compatibility. required: false default: '' Version: @@ -377,7 +377,6 @@ runs: working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }} - PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 11f111da..9810aa46 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,11 +24,7 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$outputDirectory = $pwd.Path -if (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory)) { - $outputDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory -} -$path = Join-Path -Path $outputDirectory -ChildPath '.temp' +$path = Join-Path -Path $pwd.Path -ChildPath '.temp' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index 224ff1b4..d4a6f8fe 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -219,7 +219,7 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Create temp directory for container output - $path = New-Item -Path $outputDirectory -ItemType Directory -Name '.temp' -Force + $path = New-Item -Path . -ItemType Directory -Name '.temp' -Force # Process each input path foreach ($testDir in $inputs.Path) { From b404353681a2a9171bed571043ccbe5b5f75a1d4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:10:02 +0200 Subject: [PATCH 09/25] Require a unified framework artifact directory --- tests/Assert-ArtifactPaths.ps1 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 8aa650cd..e50ad9ad 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -14,7 +14,7 @@ $outputDirectory = switch ($Layout) { } } -$temporaryDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' +$temporaryDirectory = $outputDirectory $expectedPaths = @{ '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' @@ -29,3 +29,18 @@ foreach ($artifact in $expectedPaths.GetEnumerator()) { throw "Expected $($artifact.Key) at [$($artifact.Value)]." } } + +if ($Layout -eq 'PSModule') { + $workingDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' + $legacyPaths = @( + (Join-Path -Path $workingDirectory -ChildPath '.temp'), + (Join-Path -Path $workingDirectory -ChildPath 'TestResult'), + (Join-Path -Path $workingDirectory -ChildPath 'CodeCoverage') + ) + + foreach ($legacyPath in $legacyPaths) { + if (Test-Path -Path $legacyPath) { + throw "Did not expect an action-generated path at [$legacyPath]." + } + } +} From b62062021d6f1182663f92f97de2cbcd75c08cfd Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:10:27 +0200 Subject: [PATCH 10/25] Restore unified framework artifact directory --- README.md | 8 ++++---- action.yml | 5 +++-- src/exec.ps1 | 6 +++++- src/init.ps1 | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f9fcc3b7..d3ba5c94 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | -| `OutputDirectory` | Repository-relative directory for generated `TestResult` and `CodeCoverage` files. | `''` | +| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | `''` | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | @@ -411,10 +411,10 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` -### Store Reports in a Custom Directory +### Store Action-Generated Files in a Custom Directory -Set `OutputDirectory` to place the action's `TestResult` and `CodeCoverage` directories under a repository-relative location. An empty value, -the default, preserves the existing behavior of placing reports in `WorkingDirectory`. Internal `.temp` files remain in `WorkingDirectory`. +Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An +empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. ```yaml jobs: diff --git a/action.yml b/action.yml index 541ac123..4fe17385 100644 --- a/action.yml +++ b/action.yml @@ -12,8 +12,8 @@ inputs: required: false OutputDirectory: description: | - Optional repository-relative directory for generated TestResult and CodeCoverage files. - When empty, reports are generated in the working directory for backward compatibility. + Optional repository-relative directory for action-generated TestResult, CodeCoverage, and .temp files. + When empty, files are generated in the working directory for backward compatibility. required: false default: '' Version: @@ -377,6 +377,7 @@ runs: working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }} + PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 9810aa46..11f111da 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,7 +24,11 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$path = Join-Path -Path $pwd.Path -ChildPath '.temp' +$outputDirectory = $pwd.Path +if (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory)) { + $outputDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory +} +$path = Join-Path -Path $outputDirectory -ChildPath '.temp' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index d4a6f8fe..224ff1b4 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -219,7 +219,7 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Create temp directory for container output - $path = New-Item -Path . -ItemType Directory -Name '.temp' -Force + $path = New-Item -Path $outputDirectory -ItemType Directory -Name '.temp' -Force # Process each input path foreach ($testDir in $inputs.Path) { From 01ddc58f3bd1200955354c946fd7f1d5f3e8161f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:23:13 +0200 Subject: [PATCH 11/25] Require a separate report output directory --- .github/workflows/Action-Test.yml | 2 +- tests/Assert-ArtifactPaths.ps1 | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index d14d9b63..cb581ba3 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -228,7 +228,7 @@ jobs: id: action-test with: WorkingDirectory: tests/2-Standard - OutputDirectory: .PSModule + OutputDirectory: .PSModule/outputs - name: Assert .PSModule artifact paths shell: pwsh diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index e50ad9ad..0ab52ba9 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -10,11 +10,11 @@ $outputDirectory = switch ($Layout) { Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' } 'PSModule' { - Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.PSModule' + Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.PSModule/outputs' } } -$temporaryDirectory = $outputDirectory +$temporaryDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' $expectedPaths = @{ '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' @@ -33,7 +33,6 @@ foreach ($artifact in $expectedPaths.GetEnumerator()) { if ($Layout -eq 'PSModule') { $workingDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' $legacyPaths = @( - (Join-Path -Path $workingDirectory -ChildPath '.temp'), (Join-Path -Path $workingDirectory -ChildPath 'TestResult'), (Join-Path -Path $workingDirectory -ChildPath 'CodeCoverage') ) From 3401570d382a845d82e1f56283e2b73436a9e309 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:23:45 +0200 Subject: [PATCH 12/25] Keep temporary files outside report output directory --- README.md | 10 +++++----- action.yml | 5 ++--- src/exec.ps1 | 6 +----- src/init.ps1 | 2 +- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d3ba5c94..8d894ec7 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | -| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | `''` | +| `OutputDirectory` | Repository-relative directory for generated `TestResult` and `CodeCoverage` files. | `''` | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | @@ -411,10 +411,10 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` -### Store Action-Generated Files in a Custom Directory +### Store Reports in a Custom Directory -Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An -empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. +Set `OutputDirectory` to place the action's `TestResult` and `CodeCoverage` directories under a repository-relative location. An empty value, +the default, preserves the existing behavior of placing reports in `WorkingDirectory`. Internal `.temp` files remain in `WorkingDirectory`. ```yaml jobs: @@ -427,7 +427,7 @@ jobs: uses: PSModule/Invoke-Pester@v3 with: Path: './tests' - OutputDirectory: 'outputs' + OutputDirectory: '.PSModule/outputs' ``` ### Import a module before pester runs diff --git a/action.yml b/action.yml index 4fe17385..541ac123 100644 --- a/action.yml +++ b/action.yml @@ -12,8 +12,8 @@ inputs: required: false OutputDirectory: description: | - Optional repository-relative directory for action-generated TestResult, CodeCoverage, and .temp files. - When empty, files are generated in the working directory for backward compatibility. + Optional repository-relative directory for generated TestResult and CodeCoverage files. + When empty, reports are generated in the working directory for backward compatibility. required: false default: '' Version: @@ -377,7 +377,6 @@ runs: working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }} - PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 11f111da..9810aa46 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,11 +24,7 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$outputDirectory = $pwd.Path -if (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory)) { - $outputDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory -} -$path = Join-Path -Path $outputDirectory -ChildPath '.temp' +$path = Join-Path -Path $pwd.Path -ChildPath '.temp' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index 224ff1b4..d4a6f8fe 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -219,7 +219,7 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Create temp directory for container output - $path = New-Item -Path $outputDirectory -ItemType Directory -Name '.temp' -Force + $path = New-Item -Path . -ItemType Directory -Name '.temp' -Force # Process each input path foreach ($testDir in $inputs.Path) { From e0025d1fed7dbc63fe7a85628fd0850acf502737 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:28:57 +0200 Subject: [PATCH 13/25] Restore unified framework artifact test --- .github/workflows/Action-Test.yml | 2 +- tests/Assert-ArtifactPaths.ps1 | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index cb581ba3..d14d9b63 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -228,7 +228,7 @@ jobs: id: action-test with: WorkingDirectory: tests/2-Standard - OutputDirectory: .PSModule/outputs + OutputDirectory: .PSModule - name: Assert .PSModule artifact paths shell: pwsh diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 0ab52ba9..e50ad9ad 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -10,11 +10,11 @@ $outputDirectory = switch ($Layout) { Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' } 'PSModule' { - Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.PSModule/outputs' + Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.PSModule' } } -$temporaryDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' +$temporaryDirectory = $outputDirectory $expectedPaths = @{ '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' @@ -33,6 +33,7 @@ foreach ($artifact in $expectedPaths.GetEnumerator()) { if ($Layout -eq 'PSModule') { $workingDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' $legacyPaths = @( + (Join-Path -Path $workingDirectory -ChildPath '.temp'), (Join-Path -Path $workingDirectory -ChildPath 'TestResult'), (Join-Path -Path $workingDirectory -ChildPath 'CodeCoverage') ) From db5478b58694fece2f0aebae28c925bb28de38be Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:29:21 +0200 Subject: [PATCH 14/25] Restore unified framework artifact directory --- README.md | 10 +++++----- action.yml | 5 +++-- src/exec.ps1 | 6 +++++- src/init.ps1 | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8d894ec7..71b62519 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | -| `OutputDirectory` | Repository-relative directory for generated `TestResult` and `CodeCoverage` files. | `''` | +| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | `''` | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | @@ -411,10 +411,10 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` -### Store Reports in a Custom Directory +### Store Action-Generated Files in a Custom Directory -Set `OutputDirectory` to place the action's `TestResult` and `CodeCoverage` directories under a repository-relative location. An empty value, -the default, preserves the existing behavior of placing reports in `WorkingDirectory`. Internal `.temp` files remain in `WorkingDirectory`. +Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An +empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. ```yaml jobs: @@ -427,7 +427,7 @@ jobs: uses: PSModule/Invoke-Pester@v3 with: Path: './tests' - OutputDirectory: '.PSModule/outputs' + OutputDirectory: '.PSModule' ``` ### Import a module before pester runs diff --git a/action.yml b/action.yml index 541ac123..4fe17385 100644 --- a/action.yml +++ b/action.yml @@ -12,8 +12,8 @@ inputs: required: false OutputDirectory: description: | - Optional repository-relative directory for generated TestResult and CodeCoverage files. - When empty, reports are generated in the working directory for backward compatibility. + Optional repository-relative directory for action-generated TestResult, CodeCoverage, and .temp files. + When empty, files are generated in the working directory for backward compatibility. required: false default: '' Version: @@ -377,6 +377,7 @@ runs: working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }} + PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 9810aa46..11f111da 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,7 +24,11 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$path = Join-Path -Path $pwd.Path -ChildPath '.temp' +$outputDirectory = $pwd.Path +if (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory)) { + $outputDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory +} +$path = Join-Path -Path $outputDirectory -ChildPath '.temp' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index d4a6f8fe..224ff1b4 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -219,7 +219,7 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Create temp directory for container output - $path = New-Item -Path . -ItemType Directory -Name '.temp' -Force + $path = New-Item -Path $outputDirectory -ItemType Directory -Name '.temp' -Force # Process each input path foreach ($testDir in $inputs.Path) { From d99dce04936d059074c6838304bec415511b543c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:37:11 +0200 Subject: [PATCH 15/25] Test configured Pester report paths --- .github/workflows/Action-Test.yml | 13 +++++++------ tests/Assert-ArtifactPaths.ps1 | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index d14d9b63..bc187be3 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -212,8 +212,8 @@ jobs: CONCLUSION: ${{ steps.action-test.conclusion }} run: tests/Show-Status.ps1 - ActionTestOutputDirectory: - name: Action-Test - [Output Directory] + ActionTestOutputPathInputs: + name: Action-Test - [Output Path Inputs] runs-on: ubuntu-latest steps: @@ -223,12 +223,13 @@ jobs: with: persist-credentials: false - - name: Action-Test [Output Directory] + - name: Action-Test [Output Path Inputs] uses: ./ id: action-test with: - WorkingDirectory: tests/2-Standard - OutputDirectory: .PSModule + Path: tests/2-Standard/Emoji.Configuration.ps1 + TestResult_OutputPath: .PSModule/TestResult/Standard-TestResult-Report.xml + CodeCoverage_OutputPath: .PSModule/CodeCoverage/Standard-CodeCoverage-Report.xml - name: Assert .PSModule artifact paths shell: pwsh @@ -496,7 +497,7 @@ jobs: - ActionTest1SimpleFailureOnlyFailedSummary - ActionTest1SimpleExecutionFailure - ActionTest2Standard - - ActionTestOutputDirectory + - ActionTestOutputPathInputs - ActionTest2StandardPrescriptFile - ActionTest2StandardNoSummary - ActionTest3Advanced diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index e50ad9ad..86d361ed 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -14,7 +14,7 @@ $outputDirectory = switch ($Layout) { } } -$temporaryDirectory = $outputDirectory +$temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' $expectedPaths = @{ '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' From 282916d4de0c6d9b4714ccd6d0297230aa616ae6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 14:37:59 +0200 Subject: [PATCH 16/25] Honor configured Pester report paths --- README.md | 10 ++++----- action.yml | 8 ------- src/exec.ps1 | 7 +++--- src/init.ps1 | 63 +++++++++++++++++++++------------------------------- 4 files changed, 33 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 71b62519..14af8e7b 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,6 @@ jobs: | **Input** | **Description** | **Default** | |--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| | `Path` | Path to where tests are located or a configuration file. | *(none)* | -| `OutputDirectory` | Repository-relative directory for action-generated `TestResult`, `CodeCoverage`, and `.temp` files. | `''` | | `Version` | Version of Pester to install (NuGet range, e.g. `[6.0.0,7.0.0)` for any 6.x). Empty installs the latest version. | *(none)* | | `Prerelease` | Allow installing prerelease versions of Pester. | `false` | | `Guid` | Optional module identity (GUID) the installed Pester must match; fails fast if a different module named Pester is loaded. Mirrors the GUID key of a `#Requires -Modules` pin. | *(none)* | @@ -411,10 +410,10 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` -### Store Action-Generated Files in a Custom Directory +### Store Reports in a Custom Directory -Set `OutputDirectory` to place the action's `TestResult`, `CodeCoverage`, and `.temp` directories under a repository-relative location. An -empty value, the default, preserves the existing behavior of placing them in `WorkingDirectory`. +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: @@ -427,7 +426,8 @@ jobs: uses: PSModule/Invoke-Pester@v3 with: Path: './tests' - OutputDirectory: '.PSModule' + TestResult_OutputPath: '.PSModule/TestResult/results.xml' + CodeCoverage_OutputPath: '.PSModule/CodeCoverage/coverage.xml' ``` ### Import a module before pester runs diff --git a/action.yml b/action.yml index 4fe17385..99301eb6 100644 --- a/action.yml +++ b/action.yml @@ -10,12 +10,6 @@ inputs: description: | Path to where tests are located or a configuration file. required: false - OutputDirectory: - description: | - Optional repository-relative directory for action-generated TestResult, CodeCoverage, and .temp files. - When empty, files are generated in the working directory for backward compatibility. - required: false - default: '' Version: description: | Version of the Pester module to install, using NuGet version-range syntax, e.g. '[6.0.0,7.0.0)' to allow any 6.x. @@ -312,7 +306,6 @@ runs: uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 env: PSMODULE_INVOKE_PESTER_INPUT_Path: ${{ inputs.Path }} - PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} @@ -377,7 +370,6 @@ runs: working-directory: ${{ inputs.WorkingDirectory }} env: PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson: ${{ inputs.ReportAsJson }} - PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory: ${{ inputs.OutputDirectory }} PSMODULE_INVOKE_PESTER_INPUT_Version: ${{ inputs.Version }} PSMODULE_INVOKE_PESTER_INPUT_Prerelease: ${{ inputs.Prerelease }} PSMODULE_INVOKE_PESTER_INPUT_Guid: ${{ inputs.Guid }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 11f111da..ee6e71a2 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,11 +24,10 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$outputDirectory = $pwd.Path -if (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory)) { - $outputDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory +if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { + throw 'RUNNER_TEMP is required to read temporary Pester files.' } -$path = Join-Path -Path $outputDirectory -ChildPath '.temp' +$path = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester/.temp' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index 224ff1b4..61d95077 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -25,7 +25,6 @@ LogGroup 'Init - Load inputs' { $inputs = @{ Path = $path - OutputDirectory = $env:PSMODULE_INVOKE_PESTER_INPUT_OutputDirectory Run_Path = $env:PSMODULE_INVOKE_PESTER_INPUT_Run_Path Run_ExcludePath = $env:PSMODULE_INVOKE_PESTER_INPUT_Run_ExcludePath @@ -173,39 +172,6 @@ LogGroup 'Init - Load configuration' { $configuration | Format-Hashtable | Out-String } -LogGroup 'Init - Resolve output directory' { - $outputDirectory = $pwd.Path - if (-not [string]::IsNullOrWhiteSpace($inputs.OutputDirectory)) { - if ([System.IO.Path]::IsPathRooted($inputs.OutputDirectory)) { - throw "OutputDirectory must be repository-relative: [$($inputs.OutputDirectory)]" - } - - if ([string]::IsNullOrWhiteSpace($env:GITHUB_WORKSPACE)) { - throw 'OutputDirectory requires the GITHUB_WORKSPACE environment variable.' - } - - $repositoryRoot = [System.IO.Path]::GetFullPath($env:GITHUB_WORKSPACE).TrimEnd( - [char[]]@( - [System.IO.Path]::DirectorySeparatorChar, - [System.IO.Path]::AltDirectorySeparatorChar - ) - ) - $outputDirectory = [System.IO.Path]::GetFullPath( - (Join-Path -Path $repositoryRoot -ChildPath $inputs.OutputDirectory) - ) - $repositoryRootWithSeparator = "$repositoryRoot$([System.IO.Path]::DirectorySeparatorChar)" - - if ( - $outputDirectory -ne $repositoryRoot -and - -not $outputDirectory.StartsWith($repositoryRootWithSeparator, [System.StringComparison]::Ordinal) - ) { - throw "OutputDirectory must remain within the repository: [$($inputs.OutputDirectory)]" - } - } - - Write-Output "Output directory: [$outputDirectory]" -} - LogGroup 'Init - Export containers' { $containers = @() $existingContainers = $configuration.Run.Container @@ -218,8 +184,12 @@ LogGroup 'Init - Export containers' { } Write-Output "Containers from configuration: [$($containers.Count)]" - # Create temp directory for container output - $path = New-Item -Path $outputDirectory -ItemType Directory -Name '.temp' -Force + # Keep transient configuration and container files outside the workspace. + if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { + throw 'RUNNER_TEMP is required to create temporary Pester files.' + } + $temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' + $path = New-Item -Path $temporaryDirectory -ItemType Directory -Name '.temp' -Force # Process each input path foreach ($testDir in $inputs.Path) { @@ -277,8 +247,25 @@ LogGroup 'Init - Export containers' { LogGroup 'Init - Export configuration' { $artifactName = $configuration.TestResult.TestSuiteName ?? 'Pester' - $configuration.TestResult.OutputPath = Join-Path -Path $outputDirectory -ChildPath "TestResult/$artifactName-TestResult-Report.xml" - $configuration.CodeCoverage.OutputPath = Join-Path -Path $outputDirectory -ChildPath "CodeCoverage/$artifactName-CodeCoverage-Report.xml" + $testResultPathIsConfigured = -not [string]::IsNullOrWhiteSpace($inputs.TestResult_OutputPath) -or + ($customConfig.ContainsKey('TestResult') -and -not [string]::IsNullOrWhiteSpace($customConfig.TestResult.OutputPath)) + $codeCoveragePathIsConfigured = -not [string]::IsNullOrWhiteSpace($inputs.CodeCoverage_OutputPath) -or + ($customConfig.ContainsKey('CodeCoverage') -and -not [string]::IsNullOrWhiteSpace($customConfig.CodeCoverage.OutputPath)) + + if (-not $testResultPathIsConfigured) { + $configuration.TestResult.OutputPath = Join-Path -Path $pwd.Path -ChildPath "TestResult/$artifactName-TestResult-Report.xml" + } + if (-not $codeCoveragePathIsConfigured) { + $configuration.CodeCoverage.OutputPath = Join-Path -Path $pwd.Path -ChildPath "CodeCoverage/$artifactName-CodeCoverage-Report.xml" + } + + foreach ($report in @('TestResult', 'CodeCoverage')) { + $outputPath = $configuration[$report].OutputPath + if (-not [System.IO.Path]::IsPathRooted($outputPath)) { + $outputPath = Join-Path -Path $pwd.Path -ChildPath $outputPath + } + $configuration[$report].OutputPath = [System.IO.Path]::GetFullPath($outputPath) + } $configuration.Run.PassThru = $true Format-Hashtable -Hashtable $configuration From dc9c06504851265e587e9da9f6d77288c74f2047 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 15:03:37 +0200 Subject: [PATCH 17/25] Test Pester report path precedence --- .github/workflows/Action-Test.yml | 68 ++++++++++++++++++++++---- tests/Assert-ArtifactPaths.ps1 | 74 +++++++++++++++++++---------- tests/ReportPaths.Configuration.ps1 | 21 ++++++++ 3 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 tests/ReportPaths.Configuration.ps1 diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index bc187be3..4707e97e 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -203,7 +203,10 @@ jobs: - name: Assert default artifact paths shell: pwsh - run: tests/Assert-ArtifactPaths.ps1 -Layout Default + run: | + tests/Assert-ArtifactPaths.ps1 ` + -TestResultPath 'tests/2-Standard/TestResult/Standard-TestResult-Report.xml' ` + -CodeCoveragePath 'tests/2-Standard/CodeCoverage/Standard-CodeCoverage-Report.xml' - name: Status shell: pwsh @@ -213,8 +216,52 @@ jobs: run: tests/Show-Status.ps1 ActionTestOutputPathInputs: - name: Action-Test - [Output Path Inputs] + 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/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/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: '' steps: # Need to check out as part of the test, as it's a local action @@ -223,17 +270,22 @@ jobs: with: persist-credentials: false - - name: Action-Test [Output Path Inputs] + - name: Action-Test [Output Paths - ${{ matrix.name }}] uses: ./ id: action-test with: - Path: tests/2-Standard/Emoji.Configuration.ps1 - TestResult_OutputPath: .PSModule/TestResult/Standard-TestResult-Report.xml - CodeCoverage_OutputPath: .PSModule/CodeCoverage/Standard-CodeCoverage-Report.xml + Path: ${{ matrix.path }} + TestResult_OutputPath: ${{ matrix.test_result_input }} + CodeCoverage_OutputPath: ${{ matrix.code_coverage_input }} - - name: Assert .PSModule artifact paths + - name: Assert artifact paths shell: pwsh - run: tests/Assert-ArtifactPaths.ps1 -Layout PSModule + run: | + tests/Assert-ArtifactPaths.ps1 ` + -TestResultPath '${{ matrix.expected_test_result }}' ` + -CodeCoveragePath '${{ matrix.expected_code_coverage }}' ` + -UnexpectedTestResultPath '${{ matrix.unexpected_test_result }}' ` + -UnexpectedCodeCoveragePath '${{ matrix.unexpected_code_coverage }}' ActionTest2StandardPrescriptFile: name: Action-Test - [2-Standard-PrescriptFile] diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 86d361ed..1a4bf581 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -1,27 +1,55 @@ [CmdletBinding()] param( [Parameter(Mandatory)] - [ValidateSet('Default', 'PSModule')] - [string] $Layout + [string] $TestResultPath, + + [Parameter(Mandatory)] + [string] $CodeCoveragePath, + + [string] $UnexpectedTestResultPath, + + [string] $UnexpectedCodeCoveragePath ) -$outputDirectory = switch ($Layout) { - 'Default' { - Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' - } - 'PSModule' { - Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath '.PSModule' +function Resolve-ArtifactPath { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $Path + ) + + if ([System.IO.Path]::IsPathRooted($Path)) { + return [System.IO.Path]::GetFullPath($Path) } + + $workspacePath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $Path + return [System.IO.Path]::GetFullPath($workspacePath) } -$temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' +function Get-ReportPath { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $Path + ) + $reportPath = Resolve-ArtifactPath -Path $Path + return @( + $reportPath + [System.IO.Path]::ChangeExtension($reportPath, '.json') + ) +} + +$temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' $expectedPaths = @{ - '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' - 'code coverage report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.xml' - 'code coverage JSON report' = Join-Path -Path $outputDirectory -ChildPath 'CodeCoverage/Standard-CodeCoverage-Report.json' - 'test result report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.xml' - 'test result JSON report' = Join-Path -Path $outputDirectory -ChildPath 'TestResult/Standard-TestResult-Report.json' + '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' +} + +foreach ($path in (Get-ReportPath -Path $TestResultPath)) { + $expectedPaths["test result report [$([System.IO.Path]::GetExtension($path))]"] = $path +} +foreach ($path in (Get-ReportPath -Path $CodeCoveragePath)) { + $expectedPaths["code coverage report [$([System.IO.Path]::GetExtension($path))]"] = $path } foreach ($artifact in $expectedPaths.GetEnumerator()) { @@ -30,17 +58,15 @@ foreach ($artifact in $expectedPaths.GetEnumerator()) { } } -if ($Layout -eq 'PSModule') { - $workingDirectory = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'tests/2-Standard' - $legacyPaths = @( - (Join-Path -Path $workingDirectory -ChildPath '.temp'), - (Join-Path -Path $workingDirectory -ChildPath 'TestResult'), - (Join-Path -Path $workingDirectory -ChildPath 'CodeCoverage') - ) +$unexpectedPaths = @( + $UnexpectedTestResultPath + $UnexpectedCodeCoveragePath +) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } - foreach ($legacyPath in $legacyPaths) { - if (Test-Path -Path $legacyPath) { - throw "Did not expect an action-generated path at [$legacyPath]." +foreach ($unexpectedPath in $unexpectedPaths) { + foreach ($path in (Get-ReportPath -Path $unexpectedPath)) { + if (Test-Path -Path $path) { + throw "Did not expect a report at [$path]." } } } diff --git a/tests/ReportPaths.Configuration.ps1 b/tests/ReportPaths.Configuration.ps1 new file mode 100644 index 00000000..b3b87c4b --- /dev/null +++ b/tests/ReportPaths.Configuration.ps1 @@ -0,0 +1,21 @@ +@{ + Run = @{ + Path = "$PSScriptRoot/2-Standard" + PassThru = $true + } + TestResult = @{ + Enabled = $true + TestSuiteName = 'Standard' + OutputPath = '.PSModule/Configuration/TestResult/Configured-TestResult-Report.xml' + } + CodeCoverage = @{ + Enabled = $true + Path = "$PSScriptRoot/2-Standard/Emoji.psm1" + OutputPath = '.PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml' + } + Output = @{ + CIFormat = 'Auto' + StackTraceVerbosity = 'Filtered' + Verbosity = 'Detailed' + } +} From 09a9d15ae72649c3e6adb233a7e72963a7bd50e4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 15:08:26 +0200 Subject: [PATCH 18/25] Correct report path test fixtures --- .github/workflows/Action-Test.yml | 4 ++-- .../ReportPaths.Configuration.ps1 | 4 ++-- tests/Assert-ArtifactPaths.ps1 | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 8 deletions(-) rename tests/{ => 2-Standard}/ReportPaths.Configuration.ps1 (83%) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 4707e97e..79876ff6 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -231,7 +231,7 @@ jobs: unexpected_test_result: '' unexpected_code_coverage: '' - name: Configuration file - path: tests/ReportPaths.Configuration.ps1 + path: tests/2-Standard/ReportPaths.Configuration.ps1 test_result_input: '' code_coverage_input: '' expected_test_result: .PSModule/Configuration/TestResult/Configured-TestResult-Report.xml @@ -239,7 +239,7 @@ jobs: unexpected_test_result: '' unexpected_code_coverage: '' - name: Action input precedence - path: tests/ReportPaths.Configuration.ps1 + path: tests/2-Standard/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 diff --git a/tests/ReportPaths.Configuration.ps1 b/tests/2-Standard/ReportPaths.Configuration.ps1 similarity index 83% rename from tests/ReportPaths.Configuration.ps1 rename to tests/2-Standard/ReportPaths.Configuration.ps1 index b3b87c4b..da393407 100644 --- a/tests/ReportPaths.Configuration.ps1 +++ b/tests/2-Standard/ReportPaths.Configuration.ps1 @@ -1,6 +1,6 @@ @{ Run = @{ - Path = "$PSScriptRoot/2-Standard" + Path = $PSScriptRoot PassThru = $true } TestResult = @{ @@ -10,7 +10,7 @@ } CodeCoverage = @{ Enabled = $true - Path = "$PSScriptRoot/2-Standard/Emoji.psm1" + Path = "$PSScriptRoot/Emoji.psm1" OutputPath = '.PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml' } Output = @{ diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 1a4bf581..8f07be62 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -12,6 +12,11 @@ param( ) function Resolve-ArtifactPath { + <# + .SYNOPSIS + Resolves an artifact path against the GitHub workspace. + #> + [OutputType([string])] [CmdletBinding()] param( [Parameter(Mandatory)] @@ -27,6 +32,11 @@ function Resolve-ArtifactPath { } function Get-ReportPath { + <# + .SYNOPSIS + Gets the configured report path and its JSON companion path. + #> + [OutputType([string])] [CmdletBinding()] param( [Parameter(Mandatory)] @@ -34,10 +44,8 @@ function Get-ReportPath { ) $reportPath = Resolve-ArtifactPath -Path $Path - return @( - $reportPath - [System.IO.Path]::ChangeExtension($reportPath, '.json') - ) + $reportPath + [System.IO.Path]::ChangeExtension($reportPath, '.json') } $temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' From d2c31fc7f73afa31ed73ec7e6c85c4abd88491b7 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 15:12:16 +0200 Subject: [PATCH 19/25] Isolate report path test configuration --- .github/workflows/Action-Test.yml | 4 ++-- tests/5-ReportPaths/Emoji.Container.ps1 | 8 ++++++++ .../ReportPaths.Configuration.ps1 | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 tests/5-ReportPaths/Emoji.Container.ps1 rename tests/{2-Standard => 5-ReportPaths}/ReportPaths.Configuration.ps1 (90%) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 79876ff6..8a02360b 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -231,7 +231,7 @@ jobs: unexpected_test_result: '' unexpected_code_coverage: '' - name: Configuration file - path: tests/2-Standard/ReportPaths.Configuration.ps1 + path: tests/5-ReportPaths/ReportPaths.Configuration.ps1 test_result_input: '' code_coverage_input: '' expected_test_result: .PSModule/Configuration/TestResult/Configured-TestResult-Report.xml @@ -239,7 +239,7 @@ jobs: unexpected_test_result: '' unexpected_code_coverage: '' - name: Action input precedence - path: tests/2-Standard/ReportPaths.Configuration.ps1 + 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 diff --git a/tests/5-ReportPaths/Emoji.Container.ps1 b/tests/5-ReportPaths/Emoji.Container.ps1 new file mode 100644 index 00000000..4e780e90 --- /dev/null +++ b/tests/5-ReportPaths/Emoji.Container.ps1 @@ -0,0 +1,8 @@ +@{ + Path = "$PSScriptRoot/../2-Standard/Emoji.Tests.ps1" + Data = @{ + Path = "$PSScriptRoot/../2-Standard/Emoji.psm1" + Debug = $false + Verbose = $false + } +} diff --git a/tests/2-Standard/ReportPaths.Configuration.ps1 b/tests/5-ReportPaths/ReportPaths.Configuration.ps1 similarity index 90% rename from tests/2-Standard/ReportPaths.Configuration.ps1 rename to tests/5-ReportPaths/ReportPaths.Configuration.ps1 index da393407..54314531 100644 --- a/tests/2-Standard/ReportPaths.Configuration.ps1 +++ b/tests/5-ReportPaths/ReportPaths.Configuration.ps1 @@ -10,7 +10,7 @@ } CodeCoverage = @{ Enabled = $true - Path = "$PSScriptRoot/Emoji.psm1" + Path = "$PSScriptRoot/../2-Standard/Emoji.psm1" OutputPath = '.PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml' } Output = @{ From 29f8dc032fc189f661bbc1ce8c2d3aa42684a03c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 15:14:56 +0200 Subject: [PATCH 20/25] Flatten temporary Pester handoff directory --- src/exec.ps1 | 2 +- src/init.ps1 | 3 +-- tests/Assert-ArtifactPaths.ps1 | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/exec.ps1 b/src/exec.ps1 index ee6e71a2..1d70a355 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -27,7 +27,7 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { throw 'RUNNER_TEMP is required to read temporary Pester files.' } -$path = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester/.temp' +$path = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index 61d95077..27d968ea 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -188,8 +188,7 @@ LogGroup 'Init - Export containers' { if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { throw 'RUNNER_TEMP is required to create temporary Pester files.' } - $temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' - $path = New-Item -Path $temporaryDirectory -ItemType Directory -Name '.temp' -Force + $path = New-Item -Path $env:RUNNER_TEMP -ItemType Directory -Name 'Invoke-Pester' -Force # Process each input path foreach ($testDir in $inputs.Path) { diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 8f07be62..f8bc12b2 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -50,7 +50,7 @@ function Get-ReportPath { $temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' $expectedPaths = @{ - '.temp configuration' = Join-Path -Path $temporaryDirectory -ChildPath '.temp/Invoke-Pester.Configuration.ps1' + 'temporary configuration' = Join-Path -Path $temporaryDirectory -ChildPath 'Invoke-Pester.Configuration.ps1' } foreach ($path in (Get-ReportPath -Path $TestResultPath)) { From 31f178d7a224be64a430e6da31603383e35ff407 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 15:20:12 +0200 Subject: [PATCH 21/25] Use the platform temporary directory --- src/exec.ps1 | 5 +---- src/init.ps1 | 6 ++---- tests/Assert-ArtifactPaths.ps1 | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/exec.ps1 b/src/exec.ps1 index 1d70a355..96ed0f12 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,10 +24,7 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { - throw 'RUNNER_TEMP is required to read temporary Pester files.' -} -$path = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' +$path = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'Invoke-Pester' Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String diff --git a/src/init.ps1 b/src/init.ps1 index 27d968ea..1090ca85 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -185,10 +185,8 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Keep transient configuration and container files outside the workspace. - if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { - throw 'RUNNER_TEMP is required to create temporary Pester files.' - } - $path = New-Item -Path $env:RUNNER_TEMP -ItemType Directory -Name 'Invoke-Pester' -Force + $temporaryRoot = [System.IO.Path]::GetTempPath() + $path = New-Item -Path $temporaryRoot -ItemType Directory -Name 'Invoke-Pester' -Force # Process each input path foreach ($testDir in $inputs.Path) { diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index f8bc12b2..1e875a72 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -48,7 +48,7 @@ function Get-ReportPath { [System.IO.Path]::ChangeExtension($reportPath, '.json') } -$temporaryDirectory = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'Invoke-Pester' +$temporaryDirectory = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'Invoke-Pester' $expectedPaths = @{ 'temporary configuration' = Join-Path -Path $temporaryDirectory -ChildPath 'Invoke-Pester.Configuration.ps1' } From 36a028504e8c35fa653d7c1856d36fcecb2032ea Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 15:47:06 +0200 Subject: [PATCH 22/25] Isolate invocation state and JSON reports --- .github/workflows/Action-Test.yml | 53 ++++++++++++++++++++++++++++++- README.md | 1 + action.yml | 14 ++++++++ src/exec.ps1 | 9 ++++-- src/init.ps1 | 7 ++-- tests/Assert-ArtifactPaths.ps1 | 31 +++++++++++++++--- 6 files changed, 105 insertions(+), 10 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 8a02360b..f4fe7e9b 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -206,7 +206,8 @@ jobs: run: | tests/Assert-ArtifactPaths.ps1 ` -TestResultPath 'tests/2-Standard/TestResult/Standard-TestResult-Report.xml' ` - -CodeCoveragePath 'tests/2-Standard/CodeCoverage/Standard-CodeCoverage-Report.xml' + -CodeCoveragePath 'tests/2-Standard/CodeCoverage/Standard-CodeCoverage-Report.xml' ` + -TempPath '${{ steps.action-test.outputs.TempPath }}' - name: Status shell: pwsh @@ -262,6 +263,14 @@ jobs: 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 + unexpected_test_result: '' + unexpected_code_coverage: '' steps: # Need to check out as part of the test, as it's a local action @@ -284,6 +293,7 @@ jobs: tests/Assert-ArtifactPaths.ps1 ` -TestResultPath '${{ matrix.expected_test_result }}' ` -CodeCoveragePath '${{ matrix.expected_code_coverage }}' ` + -TempPath '${{ steps.action-test.outputs.TempPath }}' ` -UnexpectedTestResultPath '${{ matrix.unexpected_test_result }}' ` -UnexpectedCodeCoveragePath '${{ matrix.unexpected_code_coverage }}' @@ -319,6 +329,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 @@ -550,6 +600,7 @@ jobs: - ActionTest1SimpleExecutionFailure - ActionTest2Standard - ActionTestOutputPathInputs + - ActionTestTempPathIsolation - ActionTest2StandardPrescriptFile - ActionTest2StandardNoSummary - ActionTest3Advanced diff --git a/README.md b/README.md index 14af8e7b..950c3281 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/action.yml b/action.yml index 99301eb6..e6d33b61 100644 --- a/action.yml +++ b/action.yml @@ -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 }} @@ -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 }} diff --git a/src/exec.ps1 b/src/exec.ps1 index 96ed0f12..f12fb8e7 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -24,7 +24,10 @@ $pesterModule = Get-Module -Name Pester | Sort-Object Version -Descending | Sele '::endgroup::' '::group::Exec - Info about environment' -$path = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'Invoke-Pester' +$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 @@ -144,13 +147,13 @@ $codeCoverageOutputFolderPath = $testResults.Configuration.CodeCoverage.OutputPa "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' + $jsonOutputPath = [System.IO.Path]::ChangeExtension($testResults.Configuration.TestResult.OutputPath.Value, '.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' + $jsonOutputPath = [System.IO.Path]::ChangeExtension($testResults.Configuration.CodeCoverage.OutputPath.Value, '.json') Write-Output "Exporting code coverage results to [$jsonOutputPath]" $testResults.CodeCoverage | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $jsonOutputPath } diff --git a/src/init.ps1 b/src/init.ps1 index 1090ca85..9fffe0b5 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -185,8 +185,11 @@ LogGroup 'Init - Export containers' { Write-Output "Containers from configuration: [$($containers.Count)]" # Keep transient configuration and container files outside the workspace. - $temporaryRoot = [System.IO.Path]::GetTempPath() - $path = New-Item -Path $temporaryRoot -ItemType Directory -Name 'Invoke-Pester' -Force + $tempPath = $env:PSMODULE_INVOKE_PESTER_INTERNAL_TempPath + if ([string]::IsNullOrWhiteSpace($tempPath)) { + throw 'A temporary path is required to create Pester handoff files.' + } + $path = New-Item -Path $tempPath -ItemType Directory -Force # Process each input path foreach ($testDir in $inputs.Path) { diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index 1e875a72..d7388016 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -6,6 +6,9 @@ param( [Parameter(Mandatory)] [string] $CodeCoveragePath, + [Parameter(Mandatory)] + [string] $TempPath, + [string] $UnexpectedTestResultPath, [string] $UnexpectedCodeCoveragePath @@ -48,15 +51,27 @@ function Get-ReportPath { [System.IO.Path]::ChangeExtension($reportPath, '.json') } -$temporaryDirectory = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'Invoke-Pester' +$tempRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'Invoke-Pester' +$resolvedTempRoot = [System.IO.Path]::GetFullPath($tempRoot).TrimEnd( + [System.IO.Path]::DirectorySeparatorChar, + [System.IO.Path]::AltDirectorySeparatorChar +) + [System.IO.Path]::DirectorySeparatorChar +$resolvedTempPath = [System.IO.Path]::GetFullPath($TempPath) +if (-not $resolvedTempPath.StartsWith($resolvedTempRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Expected invocation temporary path beneath [$resolvedTempRoot], but found [$resolvedTempPath]." +} + $expectedPaths = @{ - 'temporary configuration' = Join-Path -Path $temporaryDirectory -ChildPath 'Invoke-Pester.Configuration.ps1' + 'temporary configuration' = Join-Path -Path $resolvedTempPath -ChildPath 'Invoke-Pester.Configuration.ps1' } -foreach ($path in (Get-ReportPath -Path $TestResultPath)) { +$testResultPaths = @(Get-ReportPath -Path $TestResultPath) +$codeCoveragePaths = @(Get-ReportPath -Path $CodeCoveragePath) + +foreach ($path in $testResultPaths) { $expectedPaths["test result report [$([System.IO.Path]::GetExtension($path))]"] = $path } -foreach ($path in (Get-ReportPath -Path $CodeCoveragePath)) { +foreach ($path in $codeCoveragePaths) { $expectedPaths["code coverage report [$([System.IO.Path]::GetExtension($path))]"] = $path } @@ -66,6 +81,14 @@ foreach ($artifact in $expectedPaths.GetEnumerator()) { } } +foreach ($xmlReportPath in @($testResultPaths[0], $codeCoveragePaths[0])) { + try { + $null = [xml](Get-Content -Path $xmlReportPath -Raw) + } catch { + throw "Expected an XML report at [$xmlReportPath], but it did not contain valid XML." + } +} + $unexpectedPaths = @( $UnexpectedTestResultPath $UnexpectedCodeCoveragePath From 896d8948c79eeb10d194067c76ddeb79406438b9 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 16:28:59 +0200 Subject: [PATCH 23/25] Fix lint findings in report path tests Pass generated temporary paths through the step environment before using them in PowerShell, and align the documented TempPath output row. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Action-Test.yml | 8 ++++++-- README.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index f4fe7e9b..1eaecde8 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -203,11 +203,13 @@ jobs: - 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 '${{ steps.action-test.outputs.TempPath }}' + -TempPath $env:TEMP_PATH - name: Status shell: pwsh @@ -289,11 +291,13 @@ jobs: - 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 '${{ steps.action-test.outputs.TempPath }}' ` + -TempPath $env:TEMP_PATH ` -UnexpectedTestResultPath '${{ matrix.unexpected_test_result }}' ` -UnexpectedCodeCoveragePath '${{ matrix.unexpected_code_coverage }}' diff --git a/README.md b/README.md index 950c3281..f59e5b3e 100644 --- a/README.md +++ b/README.md @@ -339,7 +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 | +| `TempPath` | Invocation-specific action temporary directory | ## Examples From c28dc99c3a4d5dec3b9eca6b0ac8b97d60139799 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 16:36:07 +0200 Subject: [PATCH 24/25] Test JSON report path collisions --- .github/workflows/Action-Test.yml | 24 ++++++++++++++++++++++++ tests/Assert-ArtifactPaths.ps1 | 18 ++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index 1eaecde8..4bb11e35 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -271,6 +271,28 @@ jobs: 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: '' @@ -298,6 +320,8 @@ jobs: -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 }}' diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 index d7388016..17c2a1e5 100644 --- a/tests/Assert-ArtifactPaths.ps1 +++ b/tests/Assert-ArtifactPaths.ps1 @@ -9,6 +9,10 @@ param( [Parameter(Mandatory)] [string] $TempPath, + [string] $TestResultJsonPath, + + [string] $CodeCoverageJsonPath, + [string] $UnexpectedTestResultPath, [string] $UnexpectedCodeCoveragePath @@ -43,12 +47,18 @@ function Get-ReportPath { [CmdletBinding()] param( [Parameter(Mandatory)] - [string] $Path + [string] $Path, + + [string] $JsonPath ) $reportPath = Resolve-ArtifactPath -Path $Path $reportPath - [System.IO.Path]::ChangeExtension($reportPath, '.json') + if ([string]::IsNullOrWhiteSpace($JsonPath)) { + [System.IO.Path]::ChangeExtension($reportPath, '.json') + } else { + Resolve-ArtifactPath -Path $JsonPath + } } $tempRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'Invoke-Pester' @@ -65,8 +75,8 @@ $expectedPaths = @{ 'temporary configuration' = Join-Path -Path $resolvedTempPath -ChildPath 'Invoke-Pester.Configuration.ps1' } -$testResultPaths = @(Get-ReportPath -Path $TestResultPath) -$codeCoveragePaths = @(Get-ReportPath -Path $CodeCoveragePath) +$testResultPaths = @(Get-ReportPath -Path $TestResultPath -JsonPath $TestResultJsonPath) +$codeCoveragePaths = @(Get-ReportPath -Path $CodeCoveragePath -JsonPath $CodeCoverageJsonPath) foreach ($path in $testResultPaths) { $expectedPaths["test result report [$([System.IO.Path]::GetExtension($path))]"] = $path From 212d771aac0cfb17bef3a4b0625ea1873c7d8033 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 10 Sep 2026 16:38:02 +0200 Subject: [PATCH 25/25] Prevent report artifact path collisions --- src/exec.ps1 | 77 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/src/exec.ps1 b/src/exec.ps1 index f12fb8e7..c080f4fd 100644 --- a/src/exec.ps1 +++ b/src/exec.ps1 @@ -121,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 @@ -145,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 = [System.IO.Path]::ChangeExtension($testResults.Configuration.TestResult.OutputPath.Value, '.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 = [System.IO.Path]::ChangeExtension($testResults.Configuration.CodeCoverage.OutputPath.Value, '.json') - Write-Output "Exporting code coverage results to [$jsonOutputPath]" - $testResults.CodeCoverage | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $jsonOutputPath -} '::endgroup::' '::group::Exit'