Fix Enter-PSHostProcess tests flakiness (#9007)

This commit is contained in:
Tyler James Leonhardt
2019-03-11 10:41:06 -07:00
committed by Aditya Patwardhan
parent 6c049d0cb3
commit a26d639a47
6 changed files with 172 additions and 94 deletions
@@ -62,4 +62,4 @@ else
$IP | Where-Object {$_ -ne '127.0.0.1'}
```
[run-ps]:https://windowsitpro.com/powershell/running-powershell-scripts-easy-1-2-3
[run-ps]:https://www.itprotoday.com/powershell/running-powershell-scripts-easy-1-2-3
@@ -334,7 +334,7 @@ For more details, see [Create and Run PowerShell Script Guide][create-run-script
[create-ps-module]:https://www.business.com/articles/powershell-modules/
[remoting]:https://channel9.msdn.com/Series/GetStartedPowerShell3/06
[in-depth]: https://channel9.msdn.com/events/MMS/2012/SV-B406
[remote-mgmt]:https://windowsitpro.com/powershell/powershell-basics-remote-management
[remote-mgmt]:https://www.itprotoday.com/powershell/powershell-basics-remote-management
[remote-commands]:https://docs.microsoft.com/powershell/scripting/core-powershell/running-remote-commands?view=powershell-6
[examples]:https://examples.oreilly.com/9780596528492/
[examples-ps-module]:https://msdn.microsoft.com/library/dd878340%28v=vs.85%29.aspx
@@ -119,6 +119,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" {
{ & $powershell -input blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter"
}
}
Context "CommandLine" {
It "simple -args" {
& $powershell -noprofile { $args[0] } -args "hello world" | Should -Be "hello world"
@@ -611,6 +612,26 @@ foo
}
}
}
Context "CustomPipeName startup tests" {
It "Should create pipe file if CustomPipeName is specified" {
$pipeName = [System.IO.Path]::GetRandomFileName()
$pipePath = Get-PipePath $pipeName
# The pipePath should be created by the time the -Command is executed.
& $powershell -CustomPipeName $pipeName -Command "Test-Path '$pipePath'" | Should -BeTrue
}
It "Should throw if CustomPipeName is too long on Linux or macOS" -Skip:($IsWindows) {
# Generate a string that is larger than the max pipe name length.
$longPipeName = [string]::new("A", 200)
"`$pid" | & $powershell -CustomPipeName $longPipeName -c -
# 64 is the ExitCode for BadCommandLineParameter
$LASTEXITCODE | Should -Be 64
}
}
}
Describe "WindowStyle argument" -Tag Feature {
@@ -1,134 +1,181 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
$powershell = Join-Path -Path $PsHome -ChildPath "pwsh"
function Wait-JobPid {
param (
$Job
)
# This is to prevent hanging in the test.
$startTime = [DateTime]::Now
$TimeoutInMilliseconds = 10000
# This will receive the pid of the Job process and nothing more since that was the only thing written to the pipeline.
do {
Start-Sleep -Seconds 1
$pwshId = Receive-Job $Job
if (([DateTime]::Now - $startTime).TotalMilliseconds -gt $timeoutInMilliseconds) {
throw "Unable to receive PowerShell process id."
}
} while (!$pwshId)
$pwshId
}
# Executes the Enter/Exit PSHostProcess script that returns the pid of the process that's started.
function Invoke-PSHostProcessScript {
param (
[string] $ArgumentString,
[int] $Id,
[int] $Retry = 5 # Default retry of 5 times
)
$sb = {
# use $i as an incrementally growing pause based on the attempt number
# so that it's more likely to succeed.
$commandStr = @'
Start-Sleep -Seconds {0}
Enter-PSHostProcess {1} -ErrorAction Stop
$pid
Exit-PSHostProcess
'@ -f $i, $ArgumentString
($commandStr | & $powershell -c -) -eq $Id
}
$result = $false
$failures = 0
foreach ($i in 1..$Retry) {
if ($sb.Invoke()) {
$result = $true
break
}
$failures++
}
if($failures) {
Write-Warning "Enter-PSHostProcess script failed $i out of $Retry times."
}
$result
}
Describe "Enter-PSHostProcess tests" -Tag Feature {
Context "By Process Id" {
BeforeAll {
$params = @{
FilePath = "pwsh"
PassThru = $true
RedirectStandardOutput = "TestDrive:\pwsh_out.log"
RedirectStandardError = "TestDrive:\pwsh_err.log"
}
$pwsh = Start-Process @params
if ($IsWindows) {
$params = @{
FilePath = "powershell"
PassThru = $true
RedirectStandardOutput = "TestDrive:\powershell_out.log"
RedirectStandardError = "TestDrive:\powershell_err.log"
BeforeEach {
# Start a normal job where the first thing it does is return $pid. After that, spin forever.
# We will use this job as the target process for Enter-PSHostProcess
$pwshJob = Start-Job {
$pid
while ($true) {
Start-Sleep -Seconds 30 | Out-Null
}
$powershell = Start-Process @params
}
$pwshId = Wait-JobPid $pwshJob
}
AfterAll {
$pwsh | Stop-Process
AfterEach {
$pwshJob | Stop-Job -PassThru | Remove-Job
}
if ($IsWindows) {
$powershell | Stop-Process
It "Can enter, exit, and re-enter another PSHost" {
Wait-UntilTrue { [bool](Get-PSHostProcessInfo -Id $pwshId) }
# This will enter and exit another process
Invoke-PSHostProcessScript -ArgumentString "-Id $pwshId" -Id $pwshId |
Should -BeTrue -Because "The script was able to enter another process and grab the pid of '$pwshId'."
# Re-enter and exit the other process
Invoke-PSHostProcessScript -ArgumentString "-Id $pwshId" -Id $pwshId |
Should -BeTrue -Because "The script was able to re-enter another process and grab the pid of '$pwshId'."
}
It "Can enter, exit, and re-enter another Windows PowerShell PSHost" -Skip:(!$IsWindows) {
# Start a Windows PowerShell job where the first thing it does is return $pid. After that, spin forever.
# We will use this job as the target process for Enter-PSHostProcess
$powershellJob = Start-Job -PSVersion 5.1 {
$pid
while ($true) {
Start-Sleep -Seconds 30 | Out-Null
}
}
}
It "Can enter and exit another PSHost" -Pending:$true {
Wait-UntilTrue { (Get-PSHostProcessInfo -Id $pwsh.Id) -ne $null }
$powershellId = Wait-JobPid $powershellJob
"Enter-PSHostProcess -Id $($pwsh.Id) -ErrorAction Stop
`$pid
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
}
It "Can enter and exit another Windows PowerShell PSHost" -Skip:(!$IsWindows) {
Wait-UntilTrue { (Get-PSHostProcessInfo -Id $powershell.Id) -ne $null }
"Enter-PSHostProcess -Id $($powershell.Id) -ErrorAction Stop
`$pid
Exit-PSHostProcess" | pwsh -c - | Should -Be $powershell.Id
}
It "Can enter using NamedPipeConnectionInfo" -Pending:$true {
try {
$npInfo = [System.Management.Automation.Runspaces.NamedPipeConnectionInfo]::new($pwsh.Id)
Wait-UntilTrue { [bool](Get-PSHostProcessInfo -Id $powershellId) }
# This will enter and exit another process
Invoke-PSHostProcessScript -ArgumentString "-Id $powershellId" -Id $powershellId |
Should -BeTrue -Because "The script was able to enter another process and grab the pid of '$powershellId'."
# Re-enter and exit the other process
Invoke-PSHostProcessScript -ArgumentString "-Id $powershellId" -Id $powershellId |
Should -BeTrue -Because "The script was able to re-enter another process and grab the pid of '$powershellId'."
} finally {
$powershellJob | Stop-Job -PassThru | Remove-Job
}
}
It "Can enter using NamedPipeConnectionInfo" {
try {
Wait-UntilTrue { [bool](Get-PSHostProcessInfo -Id $pwshId) }
$npInfo = [System.Management.Automation.Runspaces.NamedPipeConnectionInfo]::new($pwshId)
$rs = [runspacefactory]::CreateRunspace($npInfo)
$rs.Open()
$ps = [powershell]::Create()
$ps.Runspace = $rs
$ps.AddScript('$pid').Invoke() | Should -Be $pwsh.Id
$ps.AddScript('$pid').Invoke() | Should -Be $pwshId
} finally {
$rs.Dispose()
$ps.Dispose()
}
}
}
Context "By CustomPipeName" {
BeforeAll {
# Helper function to get the correct path for the named pipe.
function Get-PipePath {
param (
$PipeName
)
if ($IsWindows) {
return "\\.\pipe\$PipeName"
}
"$([System.IO.Path]::GetTempPath())CoreFxPipe_$PipeName"
}
It "Can enter, exit, and re-enter using CustomPipeName" {
$pipeName = [System.IO.Path]::GetRandomFileName()
$params = @{
FilePath = "pwsh"
ArgumentList = @("-CustomPipeName",$pipeName)
PassThru = $true
RedirectStandardOutput = "TestDrive:\pwsh_out.log"
RedirectStandardError = "TestDrive:\pwsh_err.log"
}
$pwsh = Start-Process @params
$pipePath = Get-PipePath -PipeName $pipeName
}
AfterAll {
$pwsh | Stop-Process
}
# Start a job where the first thing it does is set the custom pipe name, then return $pid.
# After that, spin forever.
# We will use this job as the target process for Enter-PSHostProcess
$pwshJob = Start-Job -ArgumentList $pipeName {
[System.Management.Automation.Remoting.RemoteSessionNamedPipeServer]::CreateCustomNamedPipeServer($args[0])
$pid
while ($true) { Start-Sleep -Seconds 30 | Out-Null }
}
It "Can enter using CustomPipeName" -Pending:$true {
Wait-UntilTrue { Test-Path $pipePath }
$pwshId = Wait-JobPid $pwshJob
"Enter-PSHostProcess -CustomPipeName $pipeName -ErrorAction Stop
`$pid
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
}
try {
Wait-UntilTrue { Test-Path $pipePath }
It "Can enter, exit, and re-enter using CustomPipeName" -Pending:$true {
Wait-UntilTrue { Test-Path $pipePath }
# This will enter and exit another process
Invoke-PSHostProcessScript -ArgumentString "-CustomPipeName $pipeName" -Id $pwshId |
Should -BeTrue -Because "The script was able to enter another process and grab the pipe of '$pipeName'."
"Enter-PSHostProcess -CustomPipeName $pipeName -ErrorAction Stop
`$pid
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
# Re-enter and exit the other process
Invoke-PSHostProcessScript -ArgumentString "-CustomPipeName $pipeName" -Id $pwshId |
Should -BeTrue -Because "The script was able to re-enter another process and grab the pipe of '$pipeName'."
"Enter-PSHostProcess -CustomPipeName $pipeName -ErrorAction Stop
`$pid
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
} finally {
$pwshJob | Stop-Job -PassThru | Remove-Job
}
}
It "Should throw if CustomPipeName does not exist" {
Wait-UntilTrue { Test-Path $pipePath }
{ Enter-PSHostProcess -CustomPipeName badpipename } | Should -Throw -ExpectedMessage "No named pipe was found with CustomPipeName: badpipename."
}
It "Should throw if CustomPipeName is too long on Linux or macOS" {
$longPipeName = "DoggoipsumwaggywagssmolborkingdoggowithalongsnootforpatsdoingmeafrightenporgoYapperporgolongwatershoobcloudsbigolpupperlengthboy"
if (!$IsWindows) {
"`$pid" | pwsh -CustomPipeName $longPipeName -c -
# 64 is the ExitCode for BadCommandLineParameter
$LASTEXITCODE | Should -Be 64
} else {
"`$pid" | pwsh -CustomPipeName $longPipeName -c -
$LASTEXITCODE | Should -Be 0
}
}
}
}
@@ -16,6 +16,6 @@ Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'
Description = 'Temporary module for remoting tests'
FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession', 'New-RemoteRunspacePool'
FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession', 'New-RemoteRunspacePool', 'Get-PipePath'
}
@@ -248,3 +248,13 @@ function Connect-RemoteSession
$parameters = CreateParameters -ComputerName $ComputerName -Name $Name -Session $Session -ConfigurationName $ConfigurationName
Connect-PSSession @parameters
}
function Get-PipePath {
param (
$PipeName
)
if ($IsWindows) {
return "\\.\pipe\$PipeName"
}
"$([System.IO.Path]::GetTempPath())CoreFxPipe_$PipeName"
}