diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml index fb63f0c5..4bb11e35 100644 --- a/.github/workflows/Action-Test.yml +++ b/.github/workflows/Action-Test.yml @@ -201,6 +201,16 @@ jobs: Write-Host "This is a prescript" Write-Host "We are running on $env:RUNNER_OS" + - name: Assert default artifact paths + shell: pwsh + env: + TEMP_PATH: ${{ steps.action-test.outputs.TempPath }} + run: | + tests/Assert-ArtifactPaths.ps1 ` + -TestResultPath 'tests/2-Standard/TestResult/Standard-TestResult-Report.xml' ` + -CodeCoveragePath 'tests/2-Standard/CodeCoverage/Standard-CodeCoverage-Report.xml' ` + -TempPath $env:TEMP_PATH + - name: Status shell: pwsh env: @@ -208,6 +218,113 @@ jobs: CONCLUSION: ${{ steps.action-test.conclusion }} run: tests/Show-Status.ps1 + ActionTestOutputPathInputs: + name: Action-Test - [Output Paths - ${{ matrix.name }}] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: Action inputs + path: tests/2-Standard/Emoji.Configuration.ps1 + test_result_input: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml + code_coverage_input: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml + expected_test_result: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml + expected_code_coverage: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml + unexpected_test_result: '' + unexpected_code_coverage: '' + - name: Configuration file + path: tests/5-ReportPaths/ReportPaths.Configuration.ps1 + test_result_input: '' + code_coverage_input: '' + expected_test_result: .PSModule/Configuration/TestResult/Configured-TestResult-Report.xml + expected_code_coverage: .PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml + unexpected_test_result: '' + unexpected_code_coverage: '' + - name: Action input precedence + path: tests/5-ReportPaths/ReportPaths.Configuration.ps1 + test_result_input: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml + code_coverage_input: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml + expected_test_result: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml + expected_code_coverage: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml + unexpected_test_result: .PSModule/Configuration/TestResult/Configured-TestResult-Report.xml + unexpected_code_coverage: .PSModule/Configuration/CodeCoverage/Configured-CodeCoverage-Report.xml + - name: Test result input only + path: tests/2-Standard/Emoji.Configuration.ps1 + test_result_input: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml + code_coverage_input: '' + expected_test_result: .PSModule/Inputs/TestResult/Input-TestResult-Report.xml + expected_code_coverage: CodeCoverage/Standard-CodeCoverage-Report.xml + unexpected_test_result: '' + unexpected_code_coverage: '' + - name: Code coverage input only + path: tests/2-Standard/Emoji.Configuration.ps1 + test_result_input: '' + code_coverage_input: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml + expected_test_result: TestResult/Standard-TestResult-Report.xml + expected_code_coverage: .PSModule/Inputs/CodeCoverage/Input-CodeCoverage-Report.xml + unexpected_test_result: '' + unexpected_code_coverage: '' + - name: Non-XML extensions + path: tests/2-Standard/Emoji.Configuration.ps1 + test_result_input: .PSModule/Extensions/TestResult/results.junit + code_coverage_input: .PSModule/Extensions/CodeCoverage/coverage.cobertura + expected_test_result: .PSModule/Extensions/TestResult/results.junit + expected_code_coverage: .PSModule/Extensions/CodeCoverage/coverage.cobertura + expected_test_result_json: '' + expected_code_coverage_json: '' + unexpected_test_result: '' + unexpected_code_coverage: '' + - name: JSON extensions + path: tests/2-Standard/Emoji.Configuration.ps1 + test_result_input: .PSModule/Collisions/TestResult/results.json + code_coverage_input: .PSModule/Collisions/CodeCoverage/coverage.json + expected_test_result: .PSModule/Collisions/TestResult/results.json + expected_code_coverage: .PSModule/Collisions/CodeCoverage/coverage.json + expected_test_result_json: .PSModule/Collisions/TestResult/results.json.json + expected_code_coverage_json: .PSModule/Collisions/CodeCoverage/coverage.json.json + unexpected_test_result: '' + unexpected_code_coverage: '' + - name: Shared stem + path: tests/2-Standard/Emoji.Configuration.ps1 + test_result_input: .PSModule/Collisions/Shared/report.xml + code_coverage_input: .PSModule/Collisions/Shared/report.cobertura + expected_test_result: .PSModule/Collisions/Shared/report.xml + expected_code_coverage: .PSModule/Collisions/Shared/report.cobertura + expected_test_result_json: .PSModule/Collisions/Shared/report.json + expected_code_coverage_json: .PSModule/Collisions/Shared/report.cobertura.json + unexpected_test_result: '' + unexpected_code_coverage: '' + + steps: + # Need to check out as part of the test, as it's a local action + - name: Checkout repo + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Action-Test [Output Paths - ${{ matrix.name }}] + uses: ./ + id: action-test + with: + Path: ${{ matrix.path }} + TestResult_OutputPath: ${{ matrix.test_result_input }} + CodeCoverage_OutputPath: ${{ matrix.code_coverage_input }} + + - name: Assert artifact paths + shell: pwsh + env: + TEMP_PATH: ${{ steps.action-test.outputs.TempPath }} + run: | + tests/Assert-ArtifactPaths.ps1 ` + -TestResultPath '${{ matrix.expected_test_result }}' ` + -CodeCoveragePath '${{ matrix.expected_code_coverage }}' ` + -TempPath $env:TEMP_PATH ` + -TestResultJsonPath '${{ matrix.expected_test_result_json }}' ` + -CodeCoverageJsonPath '${{ matrix.expected_code_coverage_json }}' ` + -UnexpectedTestResultPath '${{ matrix.unexpected_test_result }}' ` + -UnexpectedCodeCoveragePath '${{ matrix.unexpected_code_coverage }}' + ActionTest2StandardPrescriptFile: name: Action-Test - [2-Standard-PrescriptFile] runs-on: ubuntu-latest @@ -240,6 +357,46 @@ jobs: CONCLUSION: ${{ steps.action-test.conclusion }} run: tests/Show-Status.ps1 + ActionTestTempPathIsolation: + name: Action-Test - [Temporary Path Isolation] + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: First Invoke-Pester invocation + uses: ./ + id: first + with: + WorkingDirectory: tests/1-Simple + Path: Emoji.Tests.ps1 + + - name: Second Invoke-Pester invocation + uses: ./ + id: second + with: + WorkingDirectory: tests/1-Simple + Path: Cars.Tests.ps1 + + - name: Assert temporary path isolation + shell: pwsh + env: + FIRST_TEMP_PATH: ${{ steps.first.outputs.TempPath }} + SECOND_TEMP_PATH: ${{ steps.second.outputs.TempPath }} + run: | + if ($env:FIRST_TEMP_PATH -eq $env:SECOND_TEMP_PATH) { + throw "Expected invocation-specific temporary paths, but both used [$env:FIRST_TEMP_PATH]." + } + foreach ($tempPath in @($env:FIRST_TEMP_PATH, $env:SECOND_TEMP_PATH)) { + $configurationPath = Join-Path -Path $tempPath -ChildPath 'Invoke-Pester.Configuration.ps1' + if (-not (Test-Path -Path $configurationPath -PathType Leaf)) { + throw "Expected an invocation configuration at [$configurationPath]." + } + } + ActionTest2StandardNoSummary: name: Action-Test - [2-Standard-NoSummary] runs-on: ubuntu-latest @@ -470,6 +627,8 @@ jobs: - ActionTest1SimpleFailureOnlyFailedSummary - ActionTest1SimpleExecutionFailure - ActionTest2Standard + - ActionTestOutputPathInputs + - ActionTestTempPathIsolation - ActionTest2StandardPrescriptFile - ActionTest2StandardNoSummary - ActionTest3Advanced diff --git a/README.md b/README.md index 1c2123da..f59e5b3e 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 @@ -410,6 +411,26 @@ jobs: CodeCoverage_OutputFormat: 'JaCoCo' ``` +### Store Reports in a Custom Directory + +Use Pester's existing output-path inputs to configure the report locations. Direct action inputs take precedence over values from a Pester +configuration file. + +```yaml +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run Pester tests + uses: PSModule/Invoke-Pester@v3 + with: + Path: './tests' + TestResult_OutputPath: '.PSModule/TestResult/results.xml' + CodeCoverage_OutputPath: '.PSModule/CodeCoverage/coverage.xml' +``` + ### Import a module before pester runs ```yaml 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 9810aa46..c080f4fd 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 $pwd.Path -ChildPath '.temp' +$path = $env:PSMODULE_INVOKE_PESTER_INTERNAL_TempPath +if ([string]::IsNullOrWhiteSpace($path)) { + throw 'A temporary path is required to read Pester handoff files.' +} Test-Path -Path $path Get-ChildItem -Path $path -Recurse | Sort-Object FullName | Format-Table -AutoSize | Out-String @@ -118,20 +121,73 @@ if ($generateSummary) { '::endgroup::' '::group::Eval - Set outputs' -$testResultOutputFolderPath = $testResults.Configuration.TestResult.OutputPath.Value | Split-Path -Parent -$codeCoverageOutputFolderPath = $testResults.Configuration.CodeCoverage.OutputPath.Value | Split-Path -Parent +$testResultOutputPath = $testResults.Configuration.TestResult.OutputPath.Value +$codeCoverageOutputPath = $testResults.Configuration.CodeCoverage.OutputPath.Value +$reservedOutputPaths = [System.Collections.Generic.HashSet[string]]::new( + [System.StringComparer]::OrdinalIgnoreCase +) +$null = $reservedOutputPaths.Add($testResultOutputPath) +$null = $reservedOutputPaths.Add($codeCoverageOutputPath) + +function Get-JsonOutputPath { + <# + .SYNOPSIS + Selects a JSON companion path that cannot overwrite another configured report. + #> + [OutputType([string])] + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $ReportPath, + + [Parameter(Mandatory)] + [System.Collections.Generic.HashSet[string]] $ReservedPaths + ) + + $jsonOutputPath = [System.IO.Path]::ChangeExtension($ReportPath, '.json') + $suffix = '.json' + while (-not $ReservedPaths.Add($jsonOutputPath)) { + $jsonOutputPath = "$ReportPath$suffix" + $suffix += '.json' + } + return $jsonOutputPath +} + +$testResultJsonOutputPath = $null +$codeCoverageJsonOutputPath = $null +if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.TestResult.Enabled.Value) { + $testResultJsonOutputPath = Get-JsonOutputPath -ReportPath $testResultOutputPath -ReservedPaths $reservedOutputPaths + Write-Output "Exporting test results to [$testResultJsonOutputPath]" + $testResults | Get-PesterTestTree | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $testResultJsonOutputPath +} + +if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.CodeCoverage.Enabled.Value) { + $codeCoverageJsonOutputPath = Get-JsonOutputPath -ReportPath $codeCoverageOutputPath -ReservedPaths $reservedOutputPaths + Write-Output "Exporting code coverage results to [$codeCoverageJsonOutputPath]" + $testResults.CodeCoverage | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $codeCoverageJsonOutputPath +} + +$testResultArtifactPaths = @($testResultOutputPath) + @($testResultJsonOutputPath) | Where-Object { $_ } +$codeCoverageArtifactPaths = @($codeCoverageOutputPath) + @($codeCoverageJsonOutputPath) | Where-Object { $_ } [pscustomobject]@{ TestSuiteName = $testResults.Configuration.TestResult.TestSuiteName.Value TestResultEnabled = $testResults.Configuration.TestResult.Enabled.Value - TestResultOutputPath = $testResultOutputFolderPath + TestResultOutputPath = $testResultArtifactPaths CodeCoverageEnabled = $testResults.Configuration.CodeCoverage.Enabled.Value - CodeCoverageOutputPath = $codeCoverageOutputFolderPath + CodeCoverageOutputPath = $codeCoverageArtifactPaths } | Format-List | Out-String "TestSuiteName=$($testResults.Configuration.TestResult.TestSuiteName.Value)" >> $env:GITHUB_OUTPUT "TestResultEnabled=$($testResults.Configuration.TestResult.Enabled.Value)" >> $env:GITHUB_OUTPUT -"TestResultOutputPath=$($testResultOutputFolderPath)" >> $env:GITHUB_OUTPUT "CodeCoverageEnabled=$($testResults.Configuration.CodeCoverage.Enabled.Value)" >> $env:GITHUB_OUTPUT -"CodeCoverageOutputPath=$($codeCoverageOutputFolderPath)" >> $env:GITHUB_OUTPUT +foreach ($output in @{ + TestResultOutputPath = $testResultArtifactPaths + CodeCoverageOutputPath = $codeCoverageArtifactPaths + }.GetEnumerator()) { + $delimiter = "InvokePester_$([System.Guid]::NewGuid().ToString('N'))" + "$($output.Key)<<$delimiter" >> $env:GITHUB_OUTPUT + $output.Value >> $env:GITHUB_OUTPUT + $delimiter >> $env:GITHUB_OUTPUT +} "Executed=$($testResults.Executed)" >> $env:GITHUB_OUTPUT "Result=$($testResults.Result)" >> $env:GITHUB_OUTPUT "FailedCount=$($testResults.FailedCount)" >> $env:GITHUB_OUTPUT @@ -142,18 +198,6 @@ $codeCoverageOutputFolderPath = $testResults.Configuration.CodeCoverage.OutputPa "InconclusiveCount=$($testResults.InconclusiveCount)" >> $env:GITHUB_OUTPUT "NotRunCount=$($testResults.NotRunCount)" >> $env:GITHUB_OUTPUT "TotalCount=$($testResults.TotalCount)" >> $env:GITHUB_OUTPUT - -if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.TestResult.Enabled.Value) { - $jsonOutputPath = $testResults.Configuration.TestResult.OutputPath.Value -replace '\.xml$', '.json' - Write-Output "Exporting test results to [$jsonOutputPath]" - $testResults | Get-PesterTestTree | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $jsonOutputPath -} - -if ($env:PSMODULE_INVOKE_PESTER_INPUT_ReportAsJson -eq 'true' -and $testResults.Configuration.CodeCoverage.Enabled.Value) { - $jsonOutputPath = $testResults.Configuration.CodeCoverage.OutputPath.Value -replace '\.xml$', '.json' - Write-Output "Exporting code coverage results to [$jsonOutputPath]" - $testResults.CodeCoverage | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $jsonOutputPath -} '::endgroup::' '::group::Exit' diff --git a/src/init.ps1 b/src/init.ps1 index 23186d25..9fffe0b5 100644 --- a/src/init.ps1 +++ b/src/init.ps1 @@ -184,8 +184,12 @@ 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 + # Keep transient configuration and container files outside the workspace. + $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) { @@ -243,8 +247,25 @@ 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" + $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 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/5-ReportPaths/ReportPaths.Configuration.ps1 b/tests/5-ReportPaths/ReportPaths.Configuration.ps1 new file mode 100644 index 00000000..54314531 --- /dev/null +++ b/tests/5-ReportPaths/ReportPaths.Configuration.ps1 @@ -0,0 +1,21 @@ +@{ + Run = @{ + Path = $PSScriptRoot + 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' + } +} diff --git a/tests/Assert-ArtifactPaths.ps1 b/tests/Assert-ArtifactPaths.ps1 new file mode 100644 index 00000000..17c2a1e5 --- /dev/null +++ b/tests/Assert-ArtifactPaths.ps1 @@ -0,0 +1,113 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string] $TestResultPath, + + [Parameter(Mandatory)] + [string] $CodeCoveragePath, + + [Parameter(Mandatory)] + [string] $TempPath, + + [string] $TestResultJsonPath, + + [string] $CodeCoverageJsonPath, + + [string] $UnexpectedTestResultPath, + + [string] $UnexpectedCodeCoveragePath +) + +function Resolve-ArtifactPath { + <# + .SYNOPSIS + Resolves an artifact path against the GitHub workspace. + #> + [OutputType([string])] + [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) +} + +function Get-ReportPath { + <# + .SYNOPSIS + Gets the configured report path and its JSON companion path. + #> + [OutputType([string])] + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $Path, + + [string] $JsonPath + ) + + $reportPath = Resolve-ArtifactPath -Path $Path + $reportPath + 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' +$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 $resolvedTempPath -ChildPath 'Invoke-Pester.Configuration.ps1' +} + +$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 +} +foreach ($path in $codeCoveragePaths) { + $expectedPaths["code coverage report [$([System.IO.Path]::GetExtension($path))]"] = $path +} + +foreach ($artifact in $expectedPaths.GetEnumerator()) { + if (-not (Test-Path -Path $artifact.Value -PathType Leaf)) { + throw "Expected $($artifact.Key) at [$($artifact.Value)]." + } +} + +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 +) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + +foreach ($unexpectedPath in $unexpectedPaths) { + foreach ($path in (Get-ReportPath -Path $unexpectedPath)) { + if (Test-Path -Path $path) { + throw "Did not expect a report at [$path]." + } + } +}