From 65358990c26ae95301127a73613f4b11867412a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 31 Jul 2026 21:04:10 +0200 Subject: [PATCH] Fix #2923: resolve code coverage ReportRoot and RepoRoot during configuration validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeCoverage.ReportRoot and Run.RepoRoot were resolved to absolute paths only in Get-ReportRoot, which runs when the report is written at the very end of the run. A test can change the current location (Set-Location), so a relative ReportRoot/RepoRoot resolved then would use whatever directory the last test left behind (#2921 comment). Resolve both to absolute in Resolve-CodeCoverageConfiguration, which runs during plugin setup while the current location still points at the directory Invoke-Pester was called from, next to the OutputPath resolution added in #2892. Paths already absolute are unaffected. 🤖 --- src/functions/Coverage.Plugin.ps1 | 12 +++++++ tst/functions/Coverage.Tests.ps1 | 54 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/functions/Coverage.Plugin.ps1 b/src/functions/Coverage.Plugin.ps1 index da5b00ce8..0a2aeaad1 100644 --- a/src/functions/Coverage.Plugin.ps1 +++ b/src/functions/Coverage.Plugin.ps1 @@ -246,4 +246,16 @@ function Resolve-CodeCoverageConfiguration { # test can change the current location (e.g. Set-Location), so a relative path would resolve # against the wrong directory, or one that no longer exists (#2641). $PesterPreference.CodeCoverage.OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PesterPreference.CodeCoverage.OutputPath.Value) + + # Resolve the report root to an absolute path now, for the same reason. Get-ReportRoot uses it + # (or its Run.RepoRoot fallback) to strip the prefix off the absolute file paths when the report + # is written. That also happens after all tests ran, so resolving a relative ReportRoot/RepoRoot + # only then would use the location the last test left behind (#2923). + if (-not [string]::IsNullOrEmpty($PesterPreference.CodeCoverage.ReportRoot.Value)) { + $PesterPreference.CodeCoverage.ReportRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PesterPreference.CodeCoverage.ReportRoot.Value) + } + + if (-not [string]::IsNullOrEmpty($PesterPreference.Run.RepoRoot.Value)) { + $PesterPreference.Run.RepoRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PesterPreference.Run.RepoRoot.Value) + } } diff --git a/tst/functions/Coverage.Tests.ps1 b/tst/functions/Coverage.Tests.ps1 index dda93ff78..34ae0db91 100644 --- a/tst/functions/Coverage.Tests.ps1 +++ b/tst/functions/Coverage.Tests.ps1 @@ -1440,4 +1440,58 @@ InPesterModuleScope { Get-RelativePath -Path $absFile -RelativeTo (Get-ReportRoot) | Should -Be $expected } } + + Describe 'Resolve-CodeCoverageConfiguration report root resolution (#2923)' { + # A relative ReportRoot (or its Run.RepoRoot fallback) must be captured against the + # location Invoke-Pester was called from, during configuration validation, not against + # whatever location a test leaves behind. The report is written after all tests ran, so + # resolving only then (in Get-ReportRoot) would break when a test changes the location. + BeforeAll { + $invocationDir = (New-Item -ItemType Directory -Path (Join-Path $TestDrive 'invocation-dir') -Force).FullName + $elsewhere = (New-Item -ItemType Directory -Path (Join-Path $TestDrive 'elsewhere') -Force).FullName + } + + It 'captures a relative CodeCoverage.ReportRoot at configuration time so a later location change does not move it' { + $PesterPreference = [PesterConfiguration]::Default + $PesterPreference.CodeCoverage.ReportRoot = '.' + + Push-Location -Path $invocationDir + try { + Resolve-CodeCoverageConfiguration + } + finally { + Pop-Location + } + + # A test changed the current location before the report is written. + Push-Location -Path $elsewhere + try { + Get-ReportRoot | Should -Be $invocationDir + } + finally { + Pop-Location + } + } + + It 'captures a relative Run.RepoRoot fallback at configuration time so a later location change does not move it' { + $PesterPreference = [PesterConfiguration]::Default + $PesterPreference.Run.RepoRoot = '.' + + Push-Location -Path $invocationDir + try { + Resolve-CodeCoverageConfiguration + } + finally { + Pop-Location + } + + Push-Location -Path $elsewhere + try { + Get-ReportRoot | Should -Be $invocationDir + } + finally { + Pop-Location + } + } + } }