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 + } + } + } }