Skip to content

Commit 7a101ce

Browse files
authored
Reattach TestDrive and TestRegistry after running inner Invoke-Pester (#2341)
1 parent 9227418 commit 7a101ce

File tree

6 files changed

+290
-66
lines changed

6 files changed

+290
-66
lines changed

src/Main.ps1

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,10 @@ function Invoke-Pester {
675675
# todo: move mock cleanup to BeforeAllBlockContainer when there is any?
676676
Remove-MockFunctionsAndAliases -SessionState $PSCmdlet.SessionState
677677
}
678+
else {
679+
# this will inherit to child scopes and affect behavior of ex. TestDrive/TestRegistry
680+
$runningPesterInPester = $true
681+
}
678682

679683
# this will inherit to child scopes and allow Pester to run in Pester, not checking if this is
680684
# already defined because we want a clean state for this Invoke-Pester even if it runs inside another
@@ -1121,7 +1125,7 @@ function Invoke-Pester {
11211125
Invoke-PluginStep -Plugins $Plugins -Step Start -Context @{
11221126
Containers = $containers
11231127
Configuration = $pluginConfiguration
1124-
GlobalPluginData = $state.PluginData
1128+
GlobalPluginData = $pluginData
11251129
WriteDebugMessages = $PesterPreference.Debug.WriteDebugMessages.Value
11261130
Write_PesterDebugMessage = if ($PesterPreference.Debug.WriteDebugMessages.Value) { $script:SafeCommands['Write-PesterDebugMessage'] }
11271131
} -ThrowOnFailure
@@ -1165,8 +1169,9 @@ function Invoke-Pester {
11651169
$steps = $Plugins.End
11661170
if ($null -ne $steps -and 0 -lt @($steps).Count) {
11671171
Invoke-PluginStep -Plugins $Plugins -Step End -Context @{
1168-
TestRun = $run
1169-
Configuration = $pluginConfiguration
1172+
TestRun = $run
1173+
Configuration = $pluginConfiguration
1174+
GlobalPluginData = $pluginData
11701175
} -ThrowOnFailure
11711176
}
11721177

src/functions/Environment.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,16 @@ function Get-TempDirectory {
3333
}
3434

3535
function Get-TempRegistry {
36+
# The Pester root key is created once and then stays in place.
37+
# In TestDrive we use system Temp folder, but such key exists for registry so we create our own.
38+
# Removing it would cleanup remaining keys from cancelled runs, but could break parallell or nested runs, so leaving it
39+
3640
$pesterTempRegistryRoot = 'Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Pester'
3741
try {
3842
# Test-Path returns true and doesn't throw access denied when path exists but user missing permission unless -PathType Container is used
3943
if (-not (& $script:SafeCommands['Test-Path'] $pesterTempRegistryRoot -PathType Container -ErrorAction Stop)) {
44+
# Don't use -Force parameter here because that deletes the folder and creates a race condition see
45+
# https://github.com/pester/Pester/issues/1181
4046
$null = & $SafeCommands['New-Item'] -Path $pesterTempRegistryRoot -ErrorAction Stop
4147
}
4248
}

src/functions/TestDrive.ps1

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
function Get-TestDrivePlugin {
2-
New-PluginObject -Name "TestDrive" -Start {
2+
$p = @{
3+
Name = 'TestDrive'
4+
}
5+
6+
$p.Start = {
7+
param($Context)
8+
39
if (& $script:SafeCommands['Test-Path'] TestDrive:\) {
4-
& $SafeCommands['Remove-Item'] (& $SafeCommands['Get-PSDrive'] TestDrive -ErrorAction Stop).Root -Force -Recurse -Confirm:$false
10+
$existingDrive = & $SafeCommands['Get-PSDrive'] TestDrive -ErrorAction Stop
11+
$existingDriveRoot = "$($existingDrive.Provider)::$($existingDrive.Root)"
12+
13+
if ($runningPesterInPester) {
14+
# If nested run, store location and only remove PSDrive so we can re-attach it during End-step
15+
$Context.GlobalPluginData.TestDrive = @{
16+
ExistingTestDrivePath = $existingDriveRoot
17+
}
18+
}
19+
else {
20+
& $SafeCommands['Remove-Item'] $existingDriveRoot -Force -Recurse -Confirm:$false
21+
}
522
& $SafeCommands['Remove-PSDrive'] TestDrive
623
}
7-
} -EachBlockSetupStart {
24+
}
25+
26+
$p.EachBlockSetupStart = {
827
param($Context)
928

1029
if ($Context.Block.IsRoot) {
@@ -24,39 +43,76 @@
2443
TestDrivePath = $testDrivePath
2544
})
2645
}
27-
} -EachBlockTearDownEnd {
46+
}
47+
48+
$p.EachBlockTearDownEnd = {
2849
param($Context)
2950

51+
# Remap drive and variable if missing/wrong? Ex. if nested run was cancelled and didn't re-attach this drive
52+
if (-not (& $script:SafeCommands['Test-Path'] TestDrive:\)) {
53+
New-TestDrive -Path $Context.Block.PluginData.TestDrive.TestDrivePath
54+
}
55+
3056
if ($Context.Block.IsRoot) {
3157
# this is top-level block remove test drive
3258
Remove-TestDrive -TestDrivePath $Context.Block.PluginData.TestDrive.TestDrivePath
3359
}
3460
else {
35-
Clear-TestDrive -TestDrivePath $Context.Block.PluginData.TestDrive.TestDrivePath -Exclude ( $Context.Block.PluginData.TestDrive.TestDriveContent )
61+
Clear-TestDrive -TestDrivePath $Context.Block.PluginData.TestDrive.TestDrivePath -Exclude ($Context.Block.PluginData.TestDrive.TestDriveContent)
3662
}
3763
}
64+
65+
$p.End = {
66+
param($Context)
67+
68+
if ($Context.GlobalPluginData.TestDrive.ExistingTestDrivePath) {
69+
# If nested run, reattach previous TestDrive PSDrive and variable
70+
New-TestDrive -Path $Context.GlobalPluginData.TestDrive.ExistingTestDrivePath
71+
}
72+
}
73+
74+
New-PluginObject @p
3875
}
3976

40-
function New-TestDrive ([Switch]$PassThru, [string] $Path) {
41-
$directory = New-RandomTempDirectory
42-
$DriveName = "TestDrive"
43-
$null = & $SafeCommands['New-PSDrive'] -Name $DriveName -PSProvider FileSystem -Root $directory -Scope Global -Description "Pester test drive"
77+
function New-TestDrive {
78+
param(
79+
[string] $Path
80+
)
4481

45-
#publish the global TestDrive variable used in few places within the module
46-
if (-not (& $SafeCommands['Test-Path'] "Variable:Global:$DriveName")) {
47-
& $SafeCommands['New-Variable'] -Name $DriveName -Scope Global -Value $directory
82+
if ($Path -notmatch '\S') {
83+
$directory = New-RandomTempDirectory
4884
}
85+
else {
86+
# We have a path, so probably a remap after losing the PSDrive (ex. cancelled nested Pester run)
87+
if (-not (& $SafeCommands['Test-Path'] -Path $Path)) {
88+
# If this runs, something deleted the container-specific folder, so we create a new folder
89+
$null = & $SafeCommands['New-Item'] -Path $Path -ItemType Directory -ErrorAction Stop
90+
}
91+
92+
$directory = & $SafeCommands['Get-Item'] $Path
93+
}
94+
95+
$DriveName = 'TestDrive'
96+
$null = & $SafeCommands['New-PSDrive'] -Name $DriveName -PSProvider FileSystem -Root $directory -Scope Global -Description 'Pester test drive'
97+
98+
# publish the global TestDrive variable used in few places within the module.
99+
# using Set-Variable to support new variable + override existing (remap)
100+
& $SafeCommands['Set-Variable'] -Name $DriveName -Scope Global -Value $directory
49101

50102
$directory
51103
}
52104

53105

54-
function Clear-TestDrive ([String[]]$Exclude, [string]$TestDrivePath) {
106+
function Clear-TestDrive {
107+
param(
108+
[String[]] $Exclude,
109+
[string] $TestDrivePath
110+
)
55111
if ([IO.Directory]::Exists($TestDrivePath)) {
56112

57113
Remove-TestDriveSymbolicLinks -Path $TestDrivePath
58114

59-
foreach ($i in [IO.Directory]::GetFileSystemEntries($TestDrivePath, "*.*", [System.IO.SearchOption]::AllDirectories)) {
115+
foreach ($i in [IO.Directory]::GetFileSystemEntries($TestDrivePath, '*.*', [System.IO.SearchOption]::AllDirectories)) {
60116
if ($Exclude -contains $i) {
61117
continue
62118
}
@@ -77,7 +133,7 @@ function New-RandomTempDirectory {
77133

78134
function Get-TestDriveChildItem ($TestDrivePath) {
79135
if ([IO.Directory]::Exists($TestDrivePath)) {
80-
[IO.Directory]::GetFileSystemEntries($TestDrivePath, "*.*", [System.IO.SearchOption]::AllDirectories)
136+
[IO.Directory]::GetFileSystemEntries($TestDrivePath, '*.*', [System.IO.SearchOption]::AllDirectories)
81137
}
82138
}
83139

@@ -93,23 +149,23 @@ function Remove-TestDriveSymbolicLinks ([String] $Path) {
93149

94150
# issue 621 was fixed before PowerShell 6.1
95151
# now there is an issue with calling the Delete method in recent (6.1) builds of PowerShell
96-
if ( (GetPesterPSVersion) -ge 6) {
152+
if ((GetPesterPSVersion) -ge 6) {
97153
return
98154
}
99155

100156
# powershell 2-compatible
101157
$reparsePoint = [System.IO.FileAttributes]::ReparsePoint
102-
& $SafeCommands["Get-ChildItem"] -Recurse -Path $Path |
158+
& $SafeCommands['Get-ChildItem'] -Recurse -Path $Path |
103159
& $SafeCommands['Where-Object'] { ($_.Attributes -band $reparsePoint) -eq $reparsePoint } |
104160
& $SafeCommands['Foreach-Object'] { $_.Delete() }
105161
}
106162

107163
function Remove-TestDrive ($TestDrivePath) {
108-
$DriveName = "TestDrive"
164+
$DriveName = 'TestDrive'
109165
$Drive = & $SafeCommands['Get-PSDrive'] -Name $DriveName -ErrorAction Ignore
110166
$Path = ($Drive).Root
111167

112-
if ($pwd -like "$DriveName*" ) {
168+
if ($pwd -like "$DriveName*") {
113169
#will staying in the test drive cause issues?
114170
#TODO: review this
115171
& $SafeCommands['Write-Warning'] -Message "Your current path is set to ${pwd}:. You should leave ${DriveName}:\ before leaving Describe."

0 commit comments

Comments
 (0)