diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs
index 7848a61b227..9cf6062e95d 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs
@@ -109,6 +109,16 @@ protected override void BeginProcessing()
///
protected override void ProcessRecord()
{
+ if (InternalTestHooks.ActivateSleepForStoppingTest != 0)
+ {
+ // We'll wait the verbose stream marker in tests and then call Stop().
+ // The cmdlet is very fast and likely to end before the test sees the marker
+ // so we have to slow down it by Sleep(50).
+ // We can not sleep for a long time because Stop() doesn't work on sleeping runspace.
+ WriteVerbose("ConvertTo-Json started");
+ System.Threading.Thread.Sleep(InternalTestHooks.ActivateSleepForStoppingTest);
+ }
+
if (InputObject != null)
{
_inputObjects.Add(InputObject);
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs
index 1fa3f9befa5..8f467b024c7 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs
@@ -131,6 +131,12 @@ protected override
void
ProcessRecord()
{
+ if (InternalTestHooks.ActivateSleepForStoppingTest != 0)
+ {
+ WriteVerbose("Export-Clixml started");
+ System.Threading.Thread.Sleep(InternalTestHooks.ActivateSleepForStoppingTest);
+ }
+
if (_serializer != null)
{
_serializer.Serialize(InputObject);
@@ -334,6 +340,12 @@ protected override void ProcessRecord()
{
foreach (string path in Path)
{
+ if (InternalTestHooks.ActivateSleepForStoppingTest != 0)
+ {
+ WriteVerbose("Import-Clixml started");
+ System.Threading.Thread.Sleep(InternalTestHooks.ActivateSleepForStoppingTest);
+ }
+
_helper = new ImportXmlHelper(path, this, _isLiteralPath);
_helper.Import();
}
@@ -423,6 +435,12 @@ protected override void BeginProcessing()
///
protected override void ProcessRecord()
{
+ if (InternalTestHooks.ActivateSleepForStoppingTest != 0)
+ {
+ WriteVerbose("ConvertTo-Xml started");
+ System.Threading.Thread.Sleep(InternalTestHooks.ActivateSleepForStoppingTest);
+ }
+
if (As.Equals("Stream", StringComparison.OrdinalIgnoreCase))
{
CreateMemoryStream();
diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs
index 5210755f0bd..9f0a44680c1 100644
--- a/src/System.Management.Automation/engine/Utils.cs
+++ b/src/System.Management.Automation/engine/Utils.cs
@@ -1366,6 +1366,7 @@ namespace System.Management.Automation.Internal
{
/// This class is used for internal test purposes.
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")]
+ [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")]
public static class InternalTestHooks
{
internal static bool BypassGroupPolicyCaching;
@@ -1395,6 +1396,9 @@ public static class InternalTestHooks
internal static bool ShowMarkdownOutputBypass;
+ // Slow down a cmdlet to predictably check the cmdlet stopping (that 'PowerShell.Stop()' or 'Ctrl-C' work).
+ internal static int ActivateSleepForStoppingTest;
+
/// This member is used for internal test purposes.
public static void SetTestHook(string property, object value)
{
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1
index 198cccf5964..f643fb901ce 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1
@@ -22,22 +22,12 @@ Describe 'ConvertTo-Json' -tags "CI" {
}
It "StopProcessing should succeed" {
- $ps = [PowerShell]::Create()
- $null = $ps.AddScript({
+ $sb = {
$obj = [PSCustomObject]@{P1 = ''; P2 = ''; P3 = ''; P4 = ''; P5 = ''; P6 = ''}
$obj.P1 = $obj.P2 = $obj.P3 = $obj.P4 = $obj.P5 = $obj.P6 = $obj
1..100 | Foreach-Object { $obj } | ConvertTo-Json -Depth 10 -Verbose
- # the conversion is expected to take some time, this throw is in case it doesn't
- throw "Should not have thrown exception"
- })
- $null = $ps.BeginInvoke()
- # wait for verbose message from ConvertTo-Json to ensure cmdlet is processing
- Wait-UntilTrue { $ps.Streams.Verbose.Count -gt 0 }
- $null = $ps.BeginStop($null, $null)
- # wait a bit to ensure state has changed, not using synchronous Stop() to avoid blocking Pester
- Start-Sleep -Milliseconds 100
- $ps.InvocationStateInfo.State | Should -BeExactly "Stopped"
- $ps.Dispose()
+ }
+ Test-Stopping $sb -IntervalInMilliseconds 50
}
It "The result string is packed in an array symbols when AsArray parameter is used." {
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Xml.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Xml.Tests.ps1
index 848167703ab..df94b523323 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Xml.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Xml.Tests.ps1
@@ -59,22 +59,16 @@ Describe "ConvertTo-Xml DRT Unit Tests" -Tags "CI" {
}
It "StopProcessing should work" {
- $ps = [PowerShell]::Create()
- $ps.AddCommand("Get-Process")
- $ps.AddCommand("ConvertTo-Xml")
- $ps.AddParameter("Depth", 2)
- $ps.BeginInvoke()
- $ps.Stop()
- $ps.InvocationStateInfo.State | Should -BeExactly "Stopped"
+ Test-Stopping { Get-Process | ConvertTo-Xml -Depth 2 -Verbose } -IntervalInMilliseconds 50
}
# these tests just cover aspects that aren't normally exercised being used as a cmdlet
- It "Can read back switch and parameter values using api" {
+ It "Can read back switch and parameter values using api" {
Add-Type -AssemblyName "${pshome}/Microsoft.PowerShell.Commands.Utility.dll"
- $cmd = [Microsoft.PowerShell.Commands.ConvertToXmlCommand]::new()
- $cmd.NoTypeInformation = $true
- $cmd.NoTypeInformation | Should -BeTrue
+ $cmd = [Microsoft.PowerShell.Commands.ConvertToXmlCommand]::new()
+ $cmd.NoTypeInformation = $true
+ $cmd.NoTypeInformation | Should -BeTrue
}
It "Serialize primitive type" {
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/XMLCommand.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/XMLCommand.Tests.ps1
index 2882d88f888..d9b1e3ae86d 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/XMLCommand.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/XMLCommand.Tests.ps1
@@ -2,11 +2,11 @@
# Licensed under the MIT License.
Describe "XmlCommand DRT basic functionality Tests" -Tags "CI" {
- BeforeAll {
- if(-not ('IsHiddenTestType' -as "type"))
- {
- Add-Type -TypeDefinition @"
- public class IsHiddenTestType
+ BeforeAll {
+ if(-not ('IsHiddenTestType' -as "type"))
+ {
+ Add-Type -TypeDefinition @"
+ public class IsHiddenTestType
{
public IsHiddenTestType()
{
@@ -24,139 +24,125 @@ Describe "XmlCommand DRT basic functionality Tests" -Tags "CI" {
public string Property2;
}
"@
- }
+ }
}
- BeforeEach {
- $testfile = Join-Path -Path $TestDrive -ChildPath "clixml-directive.xml"
- }
+ BeforeEach {
+ $testfile = Join-Path -Path $TestDrive -ChildPath "clixml-directive.xml"
+ }
AfterEach {
- remove-item $testfile -Force -ErrorAction SilentlyContinue
+ remove-item $testfile -Force -ErrorAction SilentlyContinue
}
- It "Import with CliXml directive should work" {
+ It "Import with CliXml directive should work" {
Get-Command export* -Type Cmdlet | Select-Object -First 3 | Export-Clixml -Path $testfile
- $results = Import-Clixml $testfile
- $results.Count | Should -BeExactly 3
+ $results = Import-Clixml $testfile
+ $results.Count | Should -BeExactly 3
$results[0].PSTypeNames[0] | Should -Be "Deserialized.System.Management.Automation.CmdletInfo"
}
- It "Import with Rehydration should work" {
- $property1 = 256
- $property2 = "abcdef"
- $isHiddenTestType = [IsHiddenTestType]::New($property1,$property2)
- $isHiddenTestType | Export-Clixml $testfile
- $results = Import-Clixml $testfile
- $results.Property1 | Should -Be $property1
- $results.Property2 | Should -Be $property2
+ It "Import with Rehydration should work" {
+ $property1 = 256
+ $property2 = "abcdef"
+ $isHiddenTestType = [IsHiddenTestType]::New($property1,$property2)
+ $isHiddenTestType | Export-Clixml $testfile
+ $results = Import-Clixml $testfile
+ $results.Property1 | Should -Be $property1
+ $results.Property2 | Should -Be $property2
+ }
+
+ It "Export-Clixml StopProcessing should succeed" {
+ $sb = { 1..20000 | Export-CliXml -Path $testfile -Verbose }
+ Test-Stopping $sb -IntervalInMilliseconds 50
+ }
+
+ It "Import-Clixml StopProcessing should succeed" {
+ 1..20000 | Export-Clixml -Path $testfile
+ $sb = { Import-CliXml -Path $testfile,$testfile,$testfile,$testfile,$testfile -Verbose }
+ Test-Stopping $sb -IntervalInMilliseconds 20
}
- It "Export-Clixml StopProcessing should succeed" {
- $ps = [PowerShell]::Create()
- $null = $ps.AddScript("1..10")
- $null = $ps.AddCommand("foreach-object")
- $null = $ps.AddParameter("Process", { $_; start-sleep 1 })
- $null = $ps.AddCommand("Export-CliXml")
- $null = $ps.AddParameter("Path", $testfile)
- $null = $ps.BeginInvoke()
- Start-Sleep 1
- $null = $ps.Stop()
- $ps.InvocationStateInfo.State | Should -Be "Stopped"
- $ps.Dispose()
- }
-
- It "Import-Clixml StopProcessing should succeed" {
- 1,2,3 | Export-Clixml -Path $testfile
- $ps = [PowerShell]::Create()
- $ps.AddCommand("Get-Process")
- $ps.AddCommand("Import-CliXml")
- $ps.AddParameter("Path", $testfile)
- $ps.BeginInvoke()
- $ps.Stop()
- $ps.InvocationStateInfo.State | Should -Be "Stopped"
- }
-
- It "Export-Clixml using -Depth should work" {
- class Three
- {
- [int] $num = 3;
- }
-
- class Two
- {
- [Three] $three = [Three]::New();
- [int] $value = 2;
- }
-
- class One
- {
- [Two] $two = [Two]::New();
- [int] $value = 1;
- }
-
- $one = [One]::New()
- $one | Export-Clixml -Depth 2 -Path $testfile
- $deserialized_one = Import-Clixml -Path $testfile
- $deserialized_one.Value | Should -Be 1
- $deserialized_one.two.Value | Should -Be 2
- $deserialized_one.two.Three | Should -Not -BeNullOrEmpty
- $deserialized_one.two.three.num | Should -BeNullOrEmpty
- }
-
- It "Import-Clixml should work with XML serialization from pwsh.exe" {
- # need to create separate process so that current powershell doesn't interpret clixml output
- Start-Process -FilePath $pshome\pwsh -RedirectStandardOutput $testfile -Args "-noprofile -nologo -outputformat xml -command get-command import-clixml" -Wait
- $out = Import-Clixml -Path $testfile
- $out.Name | Should -Be "Import-CliXml"
- $out.CommandType.ToString() | Should -Be "Cmdlet"
- $out.Source | Should -Be "Microsoft.PowerShell.Utility"
- }
-
- It "Import-Clixml -IncludeTotalCount always returns unknown total count" {
- # this cmdlets supports paging, but not this switch
- [PSCustomObject]@{foo=1;bar=@{hello="world"}} | Export-Clixml -Path $testfile
- $out = Import-Clixml -Path $testfile -IncludeTotalCount
- $out[0].ToString() | Should -BeExactly "Unknown total count"
- }
-
- It "Import-Clixml -First and -Skip work together for simple types" {
- "one","two","three","four" | Export-Clixml -Path $testfile
- $out = Import-Clixml -Path $testfile -First 2 -Skip 1
- $out.Count | Should -Be 2
- $out[0] | Should -BeExactly "two"
- $out[1] | Should -BeExactly "three"
- }
-
- It "Import-Clixml -First and -Skip work together for collections" {
- @{a=1;b=2;c=3;d=4} | Export-Clixml -Path $testfile
- # order not guaranteed, even with [ordered] so we have to be smart here and compare against the full result
- $out1 = Import-Clixml -Path $testfile # this results in a hashtable
- $out2 = Import-Clixml -Path $testfile -First 2 -Skip 1 # this results in a dictionary entry
- $out2.Count | Should -Be 2
+ It "Export-Clixml using -Depth should work" {
+ class Three
+ {
+ [int] $num = 3;
+ }
+
+ class Two
+ {
+ [Three] $three = [Three]::New();
+ [int] $value = 2;
+ }
+
+ class One
+ {
+ [Two] $two = [Two]::New();
+ [int] $value = 1;
+ }
+
+ $one = [One]::New()
+ $one | Export-Clixml -Depth 2 -Path $testfile
+ $deserialized_one = Import-Clixml -Path $testfile
+ $deserialized_one.Value | Should -Be 1
+ $deserialized_one.two.Value | Should -Be 2
+ $deserialized_one.two.Three | Should -Not -BeNullOrEmpty
+ $deserialized_one.two.three.num | Should -BeNullOrEmpty
+ }
+
+ It "Import-Clixml should work with XML serialization from pwsh.exe" {
+ # need to create separate process so that current powershell doesn't interpret clixml output
+ Start-Process -FilePath $pshome\pwsh -RedirectStandardOutput $testfile -Args "-noprofile -nologo -outputformat xml -command get-command import-clixml" -Wait
+ $out = Import-Clixml -Path $testfile
+ $out.Name | Should -Be "Import-CliXml"
+ $out.CommandType.ToString() | Should -Be "Cmdlet"
+ $out.Source | Should -Be "Microsoft.PowerShell.Utility"
+ }
+
+ It "Import-Clixml -IncludeTotalCount always returns unknown total count" {
+ # this cmdlets supports paging, but not this switch
+ [PSCustomObject]@{foo=1;bar=@{hello="world"}} | Export-Clixml -Path $testfile
+ $out = Import-Clixml -Path $testfile -IncludeTotalCount
+ $out[0].ToString() | Should -BeExactly "Unknown total count"
+ }
+
+ It "Import-Clixml -First and -Skip work together for simple types" {
+ "one","two","three","four" | Export-Clixml -Path $testfile
+ $out = Import-Clixml -Path $testfile -First 2 -Skip 1
+ $out.Count | Should -Be 2
+ $out[0] | Should -BeExactly "two"
+ $out[1] | Should -BeExactly "three"
+ }
+
+ It "Import-Clixml -First and -Skip work together for collections" {
+ @{a=1;b=2;c=3;d=4} | Export-Clixml -Path $testfile
+ # order not guaranteed, even with [ordered] so we have to be smart here and compare against the full result
+ $out1 = Import-Clixml -Path $testfile # this results in a hashtable
+ $out2 = Import-Clixml -Path $testfile -First 2 -Skip 1 # this results in a dictionary entry
+ $out2.Count | Should -Be 2
($out2.Name) -join ":" | Should -Be (@($out1.Keys)[1, 2] -join ":")
($out2.Value) -join ":" | Should -Be (@($out1.Values)[1, 2] -join ":")
- }
-
- # these tests just cover aspects that aren't normally exercised being used as a cmdlet
- It "Can read back switch and parameter values using api" {
- Add-Type -AssemblyName "${pshome}/Microsoft.PowerShell.Commands.Utility.dll"
-
- $cmd = [Microsoft.PowerShell.Commands.ExportClixmlCommand]::new()
- $cmd.LiteralPath = "foo"
- $cmd.LiteralPath | Should -BeExactly "foo"
- $cmd.NoClobber = $true
- $cmd.NoClobber | Should -BeTrue
-
- $cmd = [Microsoft.PowerShell.Commands.ImportClixmlCommand]::new()
- $cmd.LiteralPath = "bar"
- $cmd.LiteralPath | Should -BeExactly "bar"
-
- $cmd = [Microsoft.PowerShell.Commands.SelectXmlCommand]::new()
- $cmd.LiteralPath = "foo"
- $cmd.LiteralPath | Should -BeExactly "foo"
- $xml = [xml]""
- $cmd.Xml = $xml
- $cmd.Xml | Should -Be $xml
- }
+ }
+
+ # these tests just cover aspects that aren't normally exercised being used as a cmdlet
+ It "Can read back switch and parameter values using api" {
+ Add-Type -AssemblyName "${pshome}/Microsoft.PowerShell.Commands.Utility.dll"
+
+ $cmd = [Microsoft.PowerShell.Commands.ExportClixmlCommand]::new()
+ $cmd.LiteralPath = "foo"
+ $cmd.LiteralPath | Should -BeExactly "foo"
+ $cmd.NoClobber = $true
+ $cmd.NoClobber | Should -BeTrue
+
+ $cmd = [Microsoft.PowerShell.Commands.ImportClixmlCommand]::new()
+ $cmd.LiteralPath = "bar"
+ $cmd.LiteralPath | Should -BeExactly "bar"
+
+ $cmd = [Microsoft.PowerShell.Commands.SelectXmlCommand]::new()
+ $cmd.LiteralPath = "foo"
+ $cmd.LiteralPath | Should -BeExactly "foo"
+ $xml = [xml]""
+ $cmd.Xml = $xml
+ $cmd.Xml | Should -Be $xml
+ }
}
diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psd1
index 0dea69c59dc..7c65fc5d457 100644
--- a/test/tools/Modules/HelpersCommon/HelpersCommon.psd1
+++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psd1
@@ -30,6 +30,7 @@ FunctionsToExport = @(
'Test-IsVstsLinux'
'Test-IsVstsWindows'
'Test-TesthookIsSet'
+ 'Test-Stopping'
'Wait-FileToBePresent'
'Wait-UntilTrue'
)
diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1
index f3ca73a1fed..16d1e0591b4 100644
--- a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1
+++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1
@@ -1,5 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
+
function Wait-UntilTrue
{
[CmdletBinding()]
@@ -314,6 +315,34 @@ function Start-NativeExecution
}
}
+# Test if the scriptblock (cmdlet) can be stopped
+function Test-Stopping
+{
+ [CmdletBinding()]
+ param (
+ [ScriptBlock]$sb,
+ [int]$TimeoutInMilliseconds = 10000,
+ [int]$IntervalInMilliseconds = 100,
+ [int]$SlowDownCmdletInMilliseconds = 50
+ )
+
+ try {
+ $ps = [PowerShell]::Create()
+ $null = $ps.AddScript($sb)
+ ${Script:TesthookType}::SetTestHook('ActivateSleepForStoppingTest', $SlowDownCmdletInMilliseconds)
+ $null = $ps.BeginInvoke()
+ Wait-UntilTrue { $ps.Streams.Verbose.Count -gt 0 } -TimeoutInMilliseconds $TimeoutInMilliseconds -IntervalInMilliseconds $IntervalInMilliseconds
+ $null = $ps.BeginStop($null, $null)
+ Wait-UntilTrue { $ps.InvocationStateInfo.State -eq "Stopped" } -TimeoutInMilliseconds $TimeoutInMilliseconds -IntervalInMilliseconds $IntervalInMilliseconds
+
+ $ps.InvocationStateInfo.State | Should -Be "Stopped"
+
+ } finally {
+ $ps.Dispose()
+ ${Script:TesthookType}::SetTestHook('ActivateSleepForStoppingTest', 0)
+ }
+}
+
# Creates a new random hex string for use with things like test certificate passwords
function New-RandomHexString
{