mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Implement ForEach-Object -Parallel feature (#10229)
This commit is contained in:
committed by
Aditya Patwardhan
parent
ccc791c0a3
commit
fbfc57a00c
@@ -108,6 +108,9 @@ namespace System.Management.Automation
|
||||
new ExperimentalFeature(
|
||||
name: "PSCommandNotFoundSuggestion",
|
||||
description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"),
|
||||
new ExperimentalFeature(
|
||||
name: "PSForEachObjectParallel",
|
||||
description: "New parameter set for ForEach-Object to run script blocks in parallel")
|
||||
};
|
||||
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -230,13 +230,21 @@ namespace System.Management.Automation
|
||||
/// </remarks>
|
||||
internal sealed class DlrScriptCommandProcessor : ScriptCommandProcessorBase
|
||||
{
|
||||
private new ScriptBlock _scriptBlock;
|
||||
private readonly ArrayList _input = new ArrayList();
|
||||
private readonly object _dollarUnderbar = AutomationNull.Value;
|
||||
private new ScriptBlock _scriptBlock;
|
||||
private MutableTuple _localsTuple;
|
||||
private bool _runOptimizedCode;
|
||||
private bool _argsBound;
|
||||
private FunctionContext _functionContext;
|
||||
|
||||
internal DlrScriptCommandProcessor(ScriptBlock scriptBlock, ExecutionContext context, bool useNewScope, CommandOrigin origin, SessionStateInternal sessionState, object dollarUnderbar)
|
||||
: base(scriptBlock, context, useNewScope, origin, sessionState)
|
||||
{
|
||||
Init();
|
||||
_dollarUnderbar = dollarUnderbar;
|
||||
}
|
||||
|
||||
internal DlrScriptCommandProcessor(ScriptBlock scriptBlock, ExecutionContext context, bool useNewScope, CommandOrigin origin, SessionStateInternal sessionState)
|
||||
: base(scriptBlock, context, useNewScope, origin, sessionState)
|
||||
{
|
||||
@@ -513,6 +521,10 @@ namespace System.Management.Automation
|
||||
{
|
||||
_localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnderbar, _context);
|
||||
}
|
||||
else if (_dollarUnderbar != AutomationNull.Value)
|
||||
{
|
||||
_localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, _dollarUnderbar, _context);
|
||||
}
|
||||
|
||||
if (inputToProcess != AutomationNull.Value)
|
||||
{
|
||||
|
||||
@@ -173,6 +173,13 @@ namespace System.Management.Automation.Runspaces
|
||||
get { return _useLocalScope; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets DollarUnderbar ($_) value to be used with script command.
|
||||
/// This is used by foreach-object -parallel where each piped input ($_) is associated
|
||||
/// with a parallel running script block.
|
||||
/// </summary>
|
||||
internal object DollarUnderbar { get; set; } = AutomationNull.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current command marks the end of a statement (see PowerShell.AddStatement())
|
||||
/// </summary>
|
||||
@@ -495,7 +502,8 @@ namespace System.Management.Automation.Runspaces
|
||||
commandProcessorBase = new DlrScriptCommandProcessor(scriptBlock,
|
||||
executionContext, _useLocalScope ?? false,
|
||||
origin,
|
||||
executionContext.EngineSessionState);
|
||||
executionContext.EngineSessionState,
|
||||
DollarUnderbar);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -165,4 +165,20 @@
|
||||
<data name="ValueNotSpecifiedForWhereObject" xml:space="preserve">
|
||||
<value>The specified operator requires both the -Property and -Value parameters. Provide values for both parameters, and then try the command again.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="PSTaskStreamWriterWrongThread" xml:space="preserve">
|
||||
<value>This method cannot be run on the current thread. It can only be called on the cmdlet thread.</value>
|
||||
</data>
|
||||
<data name="ParallelUsingVariableCannotBeScriptBlock" xml:space="preserve">
|
||||
<value>A ForEach-Object -Parallel using variable cannot be a script block. Passed-in script block variables are not supported with ForEach-Object -Parallel, and can result in undefined behavior.</value>
|
||||
</data>
|
||||
<data name="ParallelPipedInputObjectCannotBeScriptBlock" xml:space="preserve">
|
||||
<value>A ForEach-Object -Parallel piped input object cannot be a script block. Passed-in script block variables are not supported with ForEach-Object -Parallel, and can result in undefined behavior.</value>
|
||||
</data>
|
||||
<data name="ParallelCannotUseTimeoutWithJob" xml:space="preserve">
|
||||
<value>The 'TimeoutSeconds' parameter cannot be used with the 'AsJob' parameter.</value>
|
||||
</data>
|
||||
<data name="ParallelCommonParametersNotSupported" xml:space="preserve">
|
||||
<value>The following common parameters are not currently supported in the Parallel parameter set:
|
||||
ErrorAction, WarningAction, InformationAction, PipelineVariable</value>
|
||||
</data>
|
||||
</root>
|
||||
+37
@@ -878,6 +878,43 @@ try
|
||||
}
|
||||
}
|
||||
|
||||
Describe "ForEach-Object -Parallel Constrained Language Tests" -Tags 'Feature','RequireAdminOnWindows' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
$skipTest = -not $EnabledExperimentalFeatures.Contains('PSForEachObjectParallel')
|
||||
if ($skipTest) {
|
||||
Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSForEachObjectParallel' to be enabled." -Verbose
|
||||
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = $true
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
if ($skipTest) {
|
||||
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
||||
}
|
||||
}
|
||||
|
||||
It 'Foreach-Object -Parallel must run in ConstrainedLanguage mode under system lock down' {
|
||||
|
||||
try
|
||||
{
|
||||
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
|
||||
Invoke-LanguageModeTestingSupportCmdlet -SetLockdownMode
|
||||
|
||||
$results = 1..1 | ForEach-Object -Parallel { $ExecutionContext.SessionState.LanguageMode }
|
||||
}
|
||||
finally
|
||||
{
|
||||
Invoke-LanguageModeTestingSupportCmdlet -RevertLockdownMode -EnableFullLanguageMode
|
||||
}
|
||||
|
||||
$results | Should -BeExactly "ConstrainedLanguage"
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Dot sourced script block functions from trusted script files should not run FullLanguage in ConstrainedLanguage context" -Tags 'Feature','RequireAdminOnWindows' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
Describe 'ForEach-Object -Parallel Basic Tests' -Tags 'CI' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
$skipTest = -not $EnabledExperimentalFeatures.Contains('PSForEachObjectParallel')
|
||||
if ($skipTest) {
|
||||
Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSForEachObjectParallel' to be enabled." -Verbose
|
||||
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = $true
|
||||
}
|
||||
else {
|
||||
$sb = { "Hello!" }
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
if ($skipTest) {
|
||||
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
||||
}
|
||||
}
|
||||
|
||||
It "Verifies dollar underbar variable" {
|
||||
|
||||
$expected = 1..10
|
||||
$result = $expected | ForEach-Object -Parallel { $_ }
|
||||
$result.Count | Should -BeExactly $expected.Count
|
||||
$result | Should -Contain 1
|
||||
$result | Should -Contain 10
|
||||
}
|
||||
|
||||
It 'Verifies using variables' {
|
||||
|
||||
$var = "Hello"
|
||||
$varArray = "Hello","There"
|
||||
$result = 1..1 | ForEach-Object -Parallel { $using:var; $using:varArray[1] }
|
||||
$result.Count | Should -BeExactly 2
|
||||
$result[0] | Should -BeExactly $var
|
||||
$result[1] | Should -BeExactly $varArray[1]
|
||||
}
|
||||
|
||||
It 'Verifies terminating error streaming' {
|
||||
|
||||
$result = 1..1 | ForEach-Object -Parallel { throw 'Terminating Error!'; "Hello" } 2>&1
|
||||
$result.Count | Should -BeExactly 1
|
||||
$result.ToString() | Should -BeExactly 'Terminating Error!'
|
||||
$result.FullyQualifiedErrorId | Should -BeExactly 'PSTaskException'
|
||||
}
|
||||
|
||||
It 'Verifies terminating error in multiple iterations' {
|
||||
|
||||
$results = 1..2 | ForEach-Object -Parallel {
|
||||
if ($_ -eq 1) {
|
||||
throw 'Terminating Error!'
|
||||
"Hello!"
|
||||
}
|
||||
else {
|
||||
"Goodbye!"
|
||||
}
|
||||
} 2>&1
|
||||
|
||||
$resultStrings = $results | ForEach-Object { $_.ToString() }
|
||||
$resultStrings | Should -Not -Contain "Hello!"
|
||||
$resultStrings | Should -Contain "Goodbye!"
|
||||
$resultStrings | Should -Contain "Terminating Error!"
|
||||
}
|
||||
|
||||
It 'Verifies non-terminating error streaming' {
|
||||
|
||||
$actualError = 1..1 | ForEach-Object -Parallel { Write-Error "Error!" } 2>&1
|
||||
$actualError.ToString() | Should -BeExactly 'Error!'
|
||||
$actualError.FullyQualifiedErrorId | Should -BeExactly 'Microsoft.PowerShell.Commands.WriteErrorException'
|
||||
}
|
||||
|
||||
It 'Verifies warning data streaming' {
|
||||
|
||||
$actualWarning = 1..1 | ForEach-Object -Parallel { Write-Warning "Warning!" } 3>&1
|
||||
$actualWarning.Message | Should -BeExactly 'Warning!'
|
||||
}
|
||||
|
||||
It 'Verifies verbose data streaming' {
|
||||
|
||||
$actualVerbose = 1..1 | ForEach-Object -Parallel { Write-Verbose "Verbose!" -Verbose } -Verbose 4>&1
|
||||
$actualVerbose.Message | Should -BeExactly 'Verbose!'
|
||||
}
|
||||
|
||||
It 'Verifies debug data streaming' {
|
||||
|
||||
$actualDebug = 1..1 | ForEach-Object -Parallel { Write-Debug "Debug!" -Debug } -Debug 5>&1
|
||||
$actualDebug.Message | Should -BeExactly 'Debug!'
|
||||
}
|
||||
|
||||
It 'Verifies information data streaming' {
|
||||
|
||||
$actualInformation = 1..1 | ForEach-Object -Parallel { Write-Information "Information!" } 6>&1
|
||||
$actualInformation.MessageData | Should -BeExactly 'Information!'
|
||||
}
|
||||
|
||||
It 'Verifies error for using script block variable' {
|
||||
|
||||
{ 1..1 | ForEach-Object -Parallel { $using:sb } } | Should -Throw -ErrorId 'ParallelUsingVariableCannotBeScriptBlock,Microsoft.PowerShell.Commands.ForEachObjectCommand'
|
||||
}
|
||||
|
||||
It 'Verifies error for script block piped variable' {
|
||||
|
||||
$actualError = $sb | ForEach-Object -Parallel { "Hello" } 2>&1
|
||||
$actualError.FullyQualifiedErrorId | Should -BeExactly 'ParallelPipedInputObjectCannotBeScriptBlock,Microsoft.PowerShell.Commands.ForEachObjectCommand'
|
||||
}
|
||||
|
||||
It 'Verifies that parallel script blocks run in FullLanguage mode by default' {
|
||||
|
||||
$results = 1..1 | ForEach-Object -Parallel { $ExecutionContext.SessionState.LanguageMode }
|
||||
$results | Should -BeExactly 'FullLanguage'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'ForEach-Object -Parallel common parameters' -Tags 'CI' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
# Test cases
|
||||
$TestCasesNotSupportedCommonParameters = @(
|
||||
@{
|
||||
testName = 'Verifies that ErrorAction common parameter is not supported'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { "Hello" } -ErrorAction Stop }
|
||||
},
|
||||
@{
|
||||
testName = 'Verifies that WarningAction common parameter is not supported'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { "Hello" } -WarningAction SilentlyContinue }
|
||||
},
|
||||
@{
|
||||
testName = 'Verifies that InformationAction common parameter is not supported'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { "Hello" } -InformationAction SilentlyContinue }
|
||||
},
|
||||
@{
|
||||
testName = 'Verifies that PipelineVariable common parameter is not supported'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { "Hello" } -PipelineVariable pipeVar }
|
||||
}
|
||||
)
|
||||
|
||||
$TestCasesForSupportedCommonParameters = @(
|
||||
@{
|
||||
testName = 'Verifies ErrorVariable common parameter'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { Write-Error "Error:$_" } -ErrorVariable global:actualVariable }
|
||||
expectedResult = 'Error:1'
|
||||
},
|
||||
@{
|
||||
testName = 'Verifies WarningVarible common parameter'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { Write-Warning "Warning:$_" } -WarningVariable global:actualVariable }
|
||||
expectedResult = 'Warning:1'
|
||||
},
|
||||
@{
|
||||
testName = 'Verifies InformationVariable common parameter'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel { Write-Information "Information:$_"} -InformationVariable global:actualVariable }
|
||||
expectedResult = 'Information:1'
|
||||
},
|
||||
@{
|
||||
testName = 'Verifies OutVariable common parameter'
|
||||
scriptBlock = { 1..1 | ForEach-Object -Parallel {Write-Output "Output:$_"} -OutVariable global:actualVariable }
|
||||
expectedResult = 'Output:1'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
BeforeEach {
|
||||
|
||||
$global:actualVariable = $null
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
$global:actualVariable = $null
|
||||
}
|
||||
|
||||
It "<testName>" -TestCases $TestCasesNotSupportedCommonParameters {
|
||||
|
||||
param ($scriptBlock)
|
||||
|
||||
{ & $scriptBlock } | Should -Throw -ErrorId 'ParallelCommonParametersNotSupported,Microsoft.PowerShell.Commands.ForEachObjectCommand'
|
||||
}
|
||||
|
||||
It "<testName>" -TestCases $TestCasesForSupportedCommonParameters {
|
||||
|
||||
param ($scriptBlock, $expectedResult)
|
||||
|
||||
& $scriptBlock *>$null
|
||||
$global:actualVariable[0].ToString() | Should -BeExactly $expectedResult
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'ForEach-Object -Parallel -AsJob Basic Tests' -Tags 'CI' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
$skipTest = -not $EnabledExperimentalFeatures.Contains('PSForEachObjectParallel')
|
||||
if ($skipTest) {
|
||||
Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSForEachObjectParallel' to be enabled." -Verbose
|
||||
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = $true
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
if ($skipTest) {
|
||||
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
||||
}
|
||||
}
|
||||
|
||||
It 'Verifies TimeoutSeconds parameter is excluded from AsJob' {
|
||||
|
||||
{ 1..1 | ForEach-Object -AsJob -Parallel { "Hello" } -TimeoutSeconds 60 } | Should -Throw -ErrorId 'ParallelCannotUseTimeoutWithJob,Microsoft.PowerShell.Commands.ForEachObjectCommand'
|
||||
}
|
||||
|
||||
It 'Verifies ForEach-Object -Parallel jobs appear in job repository' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { "Hello" }
|
||||
Get-Job | Should -Contain $job
|
||||
$job | Wait-Job | Remove-Job
|
||||
}
|
||||
|
||||
It 'Verifies dollar underbar variable' {
|
||||
|
||||
$expected = 1..10
|
||||
$job = $expected | ForEach-Object -AsJob -Parallel { $_ }
|
||||
$result = $job | Wait-Job | Receive-Job
|
||||
$job | Remove-Job
|
||||
$result.Count | Should -BeExactly $expected.Count
|
||||
$result | Should -Contain 1
|
||||
$result | Should -Contain 10
|
||||
}
|
||||
|
||||
It 'Verifies using variables' {
|
||||
|
||||
$Var1 = "Hello"
|
||||
$Var2 = "Goodbye"
|
||||
$Var3 = 105
|
||||
$Var4 = "One","Two","Three"
|
||||
$job = 1..1 | Foreach-Object -AsJob -Parallel {
|
||||
Write-Output $using:Var1
|
||||
Write-Output $using:Var2
|
||||
Write-Output $using:Var3
|
||||
Write-Output @(,$using:Var4)
|
||||
Write-Output $using:Var4[1]
|
||||
}
|
||||
$results = $job | Wait-Job | Receive-Job
|
||||
$job | Remove-Job
|
||||
|
||||
$results[0] | Should -BeExactly $Var1
|
||||
$results[1] | Should -BeExactly $Var2
|
||||
$results[2] | Should -BeExactly $Var3
|
||||
$results[3] | Should -BeExactly $Var4
|
||||
$results[4] | Should -BeExactly $Var4[1]
|
||||
}
|
||||
|
||||
It 'Verifies terminating error in single iteration' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { throw "Terminating Error!"; "Hello" }
|
||||
$results = $job | Wait-Job | Receive-Job 2>$null
|
||||
$results.Count | Should -BeExactly 0
|
||||
$job.State | Should -BeExactly 'Failed'
|
||||
$job.ChildJobs[0].JobStateInfo.State | Should -BeExactly 'Failed'
|
||||
$job.ChildJobs[0].JobStateInfo.Reason.Message | Should -BeExactly 'Terminating Error!'
|
||||
$job | Remove-Job
|
||||
}
|
||||
|
||||
It 'Verifies terminating error in double iteration' {
|
||||
|
||||
$job = 1..2 | ForEach-Object -AsJob -Parallel {
|
||||
if ($_ -eq 1) {
|
||||
throw "Terminating Error!"
|
||||
"Goodbye!"
|
||||
}
|
||||
else {
|
||||
"Hello!"
|
||||
}
|
||||
}
|
||||
$results = $job | Wait-Job | Receive-Job 2>$null
|
||||
$results | Should -Contain 'Hello!'
|
||||
$results | Should -Not -Contain 'Goodbye!'
|
||||
$job.JobStateInfo.State | Should -BeExactly 'Failed'
|
||||
$job.ChildJobs[0].JobStateInfo.State | Should -BeExactly 'Failed'
|
||||
$job.ChildJobs[0].JobStateInfo.Reason.Message | Should -BeExactly 'Terminating Error!'
|
||||
$job.ChildJobs[1].JobStateInfo.State | Should -BeExactly 'Completed'
|
||||
$job | Remove-Job
|
||||
}
|
||||
|
||||
It 'Verifies non-terminating error' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { Write-Error "Error:$_" }
|
||||
$results = $job | Wait-Job | Receive-Job 2>&1
|
||||
$job | Remove-Job
|
||||
$results.ToString() | Should -BeExactly "Error:1"
|
||||
}
|
||||
|
||||
It 'Verifies warning data' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { Write-Warning "Warning:$_" }
|
||||
$results = $job | Wait-Job | Receive-Job 3>&1
|
||||
$job | Remove-Job
|
||||
$results.Message | Should -BeExactly "Warning:1"
|
||||
}
|
||||
|
||||
It 'Verifies verbose data' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { Write-Verbose "Verbose:$_" -Verbose }
|
||||
$results = $job | Wait-Job | Receive-Job -Verbose 4>&1
|
||||
$job | Remove-Job
|
||||
$results.Message | Should -BeExactly "Verbose:1"
|
||||
}
|
||||
|
||||
It 'Verifies debug data' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { Write-Debug "Debug:$_" -Debug }
|
||||
$results = $job | Wait-Job | Receive-Job -Debug 5>&1
|
||||
$job | Remove-Job
|
||||
$results.Message | Should -BeExactly "Debug:1"
|
||||
}
|
||||
|
||||
It 'Verifies information data' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel { Write-Information "Information:$_" }
|
||||
$results = $job | Wait-Job | Receive-Job 6>&1
|
||||
$job | Remove-Job
|
||||
$results.MessageData | Should -BeExactly "Information:1"
|
||||
}
|
||||
|
||||
It 'Verifies job Command property' {
|
||||
|
||||
$job = 1..1 | ForEach-Object -AsJob -Parallel {"Hello"}
|
||||
$job.Command | Should -BeExactly '"Hello"'
|
||||
$job.ChildJobs[0].Command | Should -BeExactly '"Hello"'
|
||||
$job | Wait-Job | Remove-Job
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'ForEach-Object -Parallel Functional Tests' -Tags 'Feature' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
$skipTest = -not $EnabledExperimentalFeatures.Contains('PSForEachObjectParallel')
|
||||
if ($skipTest) {
|
||||
Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSForEachObjectParallel' to be enabled." -Verbose
|
||||
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = $true
|
||||
}
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
|
||||
if ($skipTest) {
|
||||
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
||||
}
|
||||
}
|
||||
|
||||
It 'Verifies job queuing and throttle limit' {
|
||||
|
||||
# Run four job tasks, two in parallel at a time.
|
||||
$job = 1..4 | ForEach-Object -Parallel { Start-Sleep 60 } -AsJob -ThrottleLimit 2
|
||||
|
||||
# Wait for child job 2 to begin running for up to ten seconds
|
||||
if ( !(Wait-UntilTrue -TimeoutInMilliseconds 10000 -IntervalInMilliseconds 250 `
|
||||
{ $job.ChildJobs[1].JobStateInfo.State -eq 'Running' }))
|
||||
{
|
||||
throw "ForEach-Object -Parallel child job 2 did not start"
|
||||
}
|
||||
|
||||
# Two job tasks should be running and two waiting to run
|
||||
$job.ChildJobs[0].JobStateInfo.State | Should -BeExactly 'Running'
|
||||
$job.ChildJobs[1].JobStateInfo.State | Should -BeExactly 'Running'
|
||||
$job.ChildJobs[2].JobStateInfo.State | Should -BeExactly 'NotStarted'
|
||||
$job.ChildJobs[3].JobStateInfo.State | Should -BeExactly 'NotStarted'
|
||||
|
||||
$job | Remove-Job -Force
|
||||
}
|
||||
|
||||
It 'Verifies jobs work with Receive-Job -AutoRemove parameter' {
|
||||
|
||||
$job = 1..4 | ForEach-Object -AsJob -Parallel { "Hello:$_" }
|
||||
$null = $job | Receive-Job -Wait -AutoRemoveJob
|
||||
Get-Job | Should -Not -Contain $job
|
||||
}
|
||||
|
||||
It 'Verifies parallel task queuing' {
|
||||
|
||||
$results = 10..1 | ForEach-Object -Parallel { Start-Sleep 1; $_ } -ThrottleLimit 5
|
||||
$results[0] | Should -BeGreaterThan 5
|
||||
$results[1] | Should -BeGreaterThan 5
|
||||
$results[2] | Should -BeGreaterThan 5
|
||||
$results[3] | Should -BeGreaterThan 5
|
||||
$results[4] | Should -BeGreaterThan 5
|
||||
}
|
||||
|
||||
It 'Verifies timeout and throttle parameters' {
|
||||
|
||||
# With ThrottleLimit set to 1, the two 60 second long script blocks will run sequentially,
|
||||
# until the timeout in 5 seconds.
|
||||
$results = 1..2 | ForEach-Object -Parallel { "Output $_"; Start-Sleep -Seconds 60 } -TimeoutSeconds 5 -ThrottleLimit 1 2>&1
|
||||
$results.Count | Should -BeExactly 2
|
||||
$results[0] | Should -BeExactly 'Output 1'
|
||||
$results[1].FullyQualifiedErrorId | Should -BeExactly 'PSTaskException'
|
||||
$results[1].Exception | Should -BeOfType [System.Management.Automation.PipelineStoppedException]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ExperimentalFeatures": {
|
||||
"Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints": ["test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1"],
|
||||
"ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ]
|
||||
"ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ],
|
||||
"PSForEachObjectParallel": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1", "test/powershell/Modules/Microsoft.PowerShell.Security/ConstrainedLanguageRestriction.Tests.ps1" ]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user