diff --git a/src/functions/Pester.Parallel.ps1 b/src/functions/Pester.Parallel.ps1 index 91f65a503..e2c16dc98 100644 --- a/src/functions/Pester.Parallel.ps1 +++ b/src/functions/Pester.Parallel.ps1 @@ -175,7 +175,17 @@ function Invoke-TestInParallel { }) # Path to the currently loaded Pester so each worker imports the exact same build. - $modulePath = $ExecutionContext.SessionState.Module.Path + # Use the module manifest (.psd1), not the root module (.psm1) that Module.Path points to: + # importing the bare .psm1 loads Pester without its manifest metadata, so its ModuleVersion + # becomes 0.0.0.0. That then fails to satisfy any module a test imports whose manifest lists + # Pester in RequiredModules (e.g. @{ ModuleName = 'Pester'; ModuleVersion = '5.7.1' }), + # because the loaded 0.0.0.0 is below the required version (#2816). + $pesterModuleInfo = $ExecutionContext.SessionState.Module + $modulePath = $pesterModuleInfo.Path + $manifestPath = & $SafeCommands['Join-Path'] $pesterModuleInfo.ModuleBase "$($pesterModuleInfo.Name).psd1" + if (& $SafeCommands['Test-Path'] -LiteralPath $manifestPath -PathType Leaf) { + $modulePath = $manifestPath + } # Cap concurrency at Run.ParallelThrottleLimit when set (> 0); otherwise use all processors. $requestedThrottle = [int]$Configuration.Run.ParallelThrottleLimit.Value diff --git a/tst/Pester.RSpec.Parallel.ts.ps1 b/tst/Pester.RSpec.Parallel.ts.ps1 index 10963d6a4..a276433bd 100644 --- a/tst/Pester.RSpec.Parallel.ts.ps1 +++ b/tst/Pester.RSpec.Parallel.ts.ps1 @@ -193,6 +193,60 @@ Describe 'D' { It 'sees data' { $Module | Should -Be 'hello'; $Data.k | Should - } } + b "Run.Parallel module loading" { + t "imports a module that lists Pester in RequiredModules (#2816)" { + # Each parallel worker imports Pester so test bodies can use it. The worker must import + # Pester *via its manifest* so the loaded module keeps its real ModuleVersion. Importing + # the bare root module instead would load Pester as 0.0.0.0, and any module a test imports + # whose manifest lists Pester in RequiredModules (e.g. @{ ModuleName = 'Pester'; + # ModuleVersion = '5.0.0' }) would then fail to resolve that requirement against the + # loaded 0.0.0.0 Pester - the bug reported in #2816. + $folder = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid().Guid) + $null = New-Item -ItemType Directory -Path $folder -Force + try { + $moduleDir = Join-Path $folder 'RequiresPester' + $null = New-Item -ItemType Directory -Path $moduleDir -Force + Set-Content -Path (Join-Path $moduleDir 'RequiresPester.psm1') -Value 'function Get-RequiresPester { ''ok'' }' + Set-Content -Path (Join-Path $moduleDir 'RequiresPester.psd1') -Value @' +@{ + RootModule = 'RequiresPester.psm1' + ModuleVersion = '1.0.0' + GUID = 'b3c4d5e6-f7a8-4901-b2c3-d4e5f6a7b8c9' + RequiredModules = @( @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } ) + FunctionsToExport = @('Get-RequiresPester') +} +'@ + $manifest = Join-Path $moduleDir 'RequiresPester.psd1' + Set-Content -Path (Join-Path $folder 'Import.Tests.ps1') -Value @" +Describe 'Module import' { + It 'imports a module that requires Pester' { + { Import-Module '$manifest' -Force -ErrorAction Stop } | Should -Not -Throw + } +} +"@ + $c = [PesterConfiguration]::Default + $c.Run.Path = $folder + $c.Run.Parallel = $true + $c.Run.PassThru = $true + $c.Output.Verbosity = 'None' + + $r = Invoke-Pester -Configuration $c + + $r.PassedCount | Verify-Equal 1 + $r.FailedCount | Verify-Equal 0 + } + finally { + # Import.Tests.ps1 imports RequiresPester, which takes a dependency on Pester. When + # Run.Parallel falls back to sequential (e.g. Windows PowerShell 5.1) that import runs + # in this process, so the module leaks into the shared P-test session and the next + # *.ts.ps1 file's `Remove-Module Pester` fails with "required by 'RequiresPester'". + # Unload it first - this also releases the lock on its .psm1 so the folder can be removed. + Get-Module RequiresPester | Remove-Module -Force + Remove-Item -Path $folder -Recurse -Force + } + } + } + b "Run.BeforeContainer" { t "runs the repo-root Pester.BeforeContainer.ps1 before each file in a sequential run" { $folder = New-BeforeContainerTestFolder