Skip to content

Organized and added new Pester tests. #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/PSParallelPipeline/Commands/InvokeParallelCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private void ProcessOutput(PSOutputData data)
WriteVerbose((string)data.Output);
break;

case Type.Wraning:
case Type.Warning:
WriteWarning((string)data.Output);
break;
}
Expand Down
8 changes: 4 additions & 4 deletions src/PSParallelPipeline/PSOutputStreams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal sealed class PSOutputStreams : IDisposable

internal PSDataCollection<VerboseRecord> Verbose { get; } = [];

internal PSDataCollection<WarningRecord> Wraning { get; } = [];
internal PSDataCollection<WarningRecord> Warning { get; } = [];

private readonly Worker _worker;

Expand Down Expand Up @@ -86,11 +86,11 @@ private static void SetStreams(PSOutputStreams outputStreams)
}
};

outputStreams.Wraning.DataAdded += (s, e) =>
outputStreams.Warning.DataAdded += (s, e) =>
{
foreach (WarningRecord warning in outputStreams.Wraning.ReadAll())
foreach (WarningRecord warning in outputStreams.Warning.ReadAll())
{
outputStreams.AddOutput(new PSOutputData(Type.Wraning, warning.Message));
outputStreams.AddOutput(new PSOutputData(Type.Warning, warning.Message));
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/PSParallelPipeline/PSTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private static void HookStreams(PSTask ps, PSOutputStreams outputStreams)
ps._streams.Information = outputStreams.Information;
ps._streams.Progress = outputStreams.Progress;
ps._streams.Verbose = outputStreams.Verbose;
ps._streams.Warning = outputStreams.Wraning;
ps._streams.Warning = outputStreams.Warning;
}

private static Task InvokePowerShellAsync(
Expand Down
2 changes: 1 addition & 1 deletion src/PSParallelPipeline/Records.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal enum Type
Information,
Progress,
Verbose,
Wraning
Warning
}

internal record struct PSOutputData(Type Type, object Output);
Expand Down
264 changes: 183 additions & 81 deletions tests/PSParallelPipeline.tests.ps1
Original file line number Diff line number Diff line change
@@ -1,139 +1,241 @@
$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
using namespace System.IO
using namespace System.Management.Automation
using namespace System.Diagnostics
using namespace System.Collections.Concurrent

$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)

Import-Module $manifestPath
Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'common.psm1'))
Import-Module ([Path]::Combine($PSScriptRoot, 'common.psm1'))

Describe PSParallelPipeline {
Context 'Invoke-Parallel' -Tag 'Invoke-Parallel' {
It 'Should process all pipeline input' {
{ 0..10 | Invoke-Parallel { $_ } } |
Should -Not -Throw
Context 'Output Streams' {
It 'Success' {
1 | Invoke-Parallel { $_ } | Should -BeOfType ([int])
1 | Invoke-Parallel { $_ } | Should -BeExactly 1
}

$items = 0..10 | Invoke-Parallel { $_ } |
Sort-Object
It 'Error' {
1 | Invoke-Parallel { Write-Error $_ } 2>&1 |
Should -BeOfType ([ErrorRecord])
}

$items | Should -BeExactly (0..10)
$items | Should -HaveCount 11
It 'Warning' {
1 | Invoke-Parallel { Write-Warning $_ } 3>&1 |
Should -BeOfType ([WarningRecord])
}

It 'Should process in parallel' {
$timer = [System.Diagnostics.Stopwatch]::StartNew()
0..5 | Invoke-Parallel { Start-Sleep 1 } -ThrottleLimit 5
$timer.Elapsed | Should -BeLessOrEqual ([timespan]::FromSeconds(5))
$timer.Stop()
It 'Verbose' {
1 | Invoke-Parallel { Write-Verbose $_ -Verbose } -Verbose 4>&1 |
Should -BeOfType ([VerboseRecord])
}

It 'Should stop processing after a set timeout' {
$timer = [System.Diagnostics.Stopwatch]::StartNew()
It 'Debug' {
if ($IsCoreCLR) {
1 | Invoke-Parallel { Write-Debug $_ -Debug } -Debug 5>&1 |
Should -BeOfType ([DebugRecord])
return
}

{ 0..5 | Invoke-Parallel { Start-Sleep 10 } -TimeoutSeconds 2 -ErrorAction Stop } |
Should -Throw -ExceptionType ([System.TimeoutException])
# Debug is weird in PowerShell 5.1. Needs a different test.
$DebugPreference = 'Continue'
1 | Invoke-Parallel { & { [CmdletBinding()]param() $PSCmdlet.WriteDebug(123) } -Debug } 5>&1 |
Should -BeOfType ([DebugRecord])
}

$timer.Elapsed | Should -BeLessOrEqual ([timespan]::FromSeconds(4))
$timer.Stop()
It 'Information' {
1 | Invoke-Parallel { Write-Host $_ } 6>&1 |
Should -BeOfType ([InformationRecord])
}

It 'Allows $using: statements' {
$message = 'Hello world from {0:D2}'
$items = 0..10 | Invoke-Parallel { $using:message -f $_ } |
Sort-Object
It 'Progress' {
$ProgressPreference = 'SilentlyContinue'

$items | Should -BeExactly @(
0..10 | ForEach-Object { 'Hello world from {0:D2}' -f $_ }
)
$null | Invoke-Parallel {
1..10 | ForEach-Object {
Write-Progress -Activity 'Progress Output' -PercentComplete (10 * $_)
Start-Sleep -Milliseconds 200
}
Write-Progress -Completed -Activity 'Progress Output'
} | Should -BeNullOrEmpty
}
}

It 'Can make variables available through the -Variables parameter' {
$invokeParallelSplat = @{
Variables = @{ message = 'Hello world from {0:D2}' }
ScriptBlock = { $message -f $_ }
}
Context 'Common Parameters' {
It 'Supports ActionPreference' {
{ 1 | Invoke-Parallel { Write-Error $_ } -ErrorAction Stop } |
Should -Throw

$items = 0..10 | Invoke-Parallel @invokeParallelSplat |
Sort-Object
{ 1 | Invoke-Parallel { Write-Warning $_ } -WarningAction Stop 3>$null } |
Should -Throw

$items | Should -BeExactly @(
0..10 | ForEach-Object { 'Hello world from {0:D2}' -f $_ }
)
{ 1 | Invoke-Parallel { Write-Host $_ } -InformationAction Stop 6>$null } |
Should -Throw

1 | Invoke-Parallel { Write-Error $_ } -ErrorAction Ignore 2>&1 |
Should -BeNullOrEmpty

1 | Invoke-Parallel { Write-Warning $_ } -WarningAction Ignore 2>&1 |
Should -BeNullOrEmpty

1 | Invoke-Parallel { Write-Host $_ } -InformationAction Ignore 2>&1 |
Should -BeNullOrEmpty
}

It 'Supports PipelineVariable' {
1 | Invoke-Parallel { $_ } -PipelineVariable pipe |
ForEach-Object { Get-Variable pipe -ValueOnly } |
Should -BeExactly 1
}
}

Context 'UseNewRunspace Parameter' {
It 'Should reuse runspaces by default' {
0..10 | Invoke-Parallel { [runspace]::DefaultRunspace.InstanceId } -ThrottleLimit 5 |
Select-Object -Unique |
0..10 | Invoke-Parallel { [runspace]::DefaultRunspace } |
Select-Object -ExpandProperty InstanceId -Unique |
Should -HaveCount 5
}

It 'Should use a new runspace when the -UseNewRunspace parameter is used' {
0..10 | Invoke-Parallel { [runspace]::DefaultRunspace.InstanceId } -UseNewRunspace |
Select-Object -Unique |
It 'Should use a new runspace when the -UseNewRunspace is used' {
0..10 | Invoke-Parallel { [runspace]::DefaultRunspace } -UseNewRunspace |
Select-Object -ExpandProperty InstanceId -Unique |
Should -HaveCount 11
}
}

It 'Can add items to a single thread instance' {
$dict = [System.Collections.Concurrent.ConcurrentDictionary[string, object]]::new()

Get-Process | Invoke-Parallel { ($using:dict).TryAdd($_.Id, $_) } |
Should -Contain $true
Context 'Variables Parameter' {
It 'Makes variables available in the parallel scope' {
$items = 0..10 | Invoke-Parallel { $message -f $_ } -Variables @{
message = 'Hello world from {0:D2}'
} | Sort-Object

$dict[$PID].ProcessName | Should -Be (Get-Process -Id $PID).ProcessName
$shouldBe = 0..10 | ForEach-Object { 'Hello world from {0:D2}' -f $_ }
$items | Should -BeExactly $shouldBe
}
}

It 'Should add functions to the parallel scope with -Functions parameter' {
$invokeParallelSplat = @{
Functions = 'Test-Function'
ScriptBlock = { Test-Function $_ }
}

0..10 | Invoke-Parallel @invokeParallelSplat |
Context 'Functions Parameter' {
It 'Makes functions available in the parallel scope' {
0..10 | Invoke-Parallel { Test-Function $_ } -Functions Test-Function |
Sort-Object |
Should -BeExactly @(0..10 | ForEach-Object { Test-Function $_ })
}
}

It 'Should autocomplete existing commands in the caller scope' {
$result = TabExpansion2 -inputScript ($s = 'Invoke-Parallel -Function Get-') -cursorColumn $s.Length
$result.CompletionMatches.Count | Should -BeGreaterThan 0
$result.CompletionMatches.ListItemText | Should -Match '^Get-'
}
Context 'ThrottleLimit Parameter' {
It 'Defines the degree of parallelism' {
Measure-Command {
0..10 | Invoke-Parallel { Start-Sleep 1 }
} | ForEach-Object TotalSeconds | Should -BeGreaterOrEqual 3

It 'Should throw a terminating error' {
{ $null | Invoke-Parallel { Write-Error 'Error' } -ErrorAction Stop } |
Should -Throw
Measure-Command {
0..10 | Invoke-Parallel { Start-Sleep 1 } -ThrottleLimit 11
} | ForEach-Object TotalSeconds | Should -BeLessOrEqual 1.5
}
}

It 'Should write to the Error Stream' {
$null | Invoke-Parallel { Write-Error 'Error' } 2>&1 |
Should -BeOfType ([System.Management.Automation.ErrorRecord])
Context 'TimeoutSeconds Parameter' {
It 'Stops processing after the specified seconds' {
$timer = [Stopwatch]::StartNew()
{ 0..5 | Invoke-Parallel { Start-Sleep 10 } -TimeoutSeconds 2 -ErrorAction Stop } |
Should -Throw -ExceptionType ([TimeoutException])
$timer.Stop()
$timer.Elapsed | Should -BeLessOrEqual ([timespan]::FromSeconds(2.1))
}
}

Context 'CommandCompleter' {
It 'Should autocomplete existing commands in the caller scope' {
Complete 'Invoke-Parallel -Functions Compl' |
Should -Not -BeNullOrEmpty

It 'Should throw if passing a scriptblock with using: scope modifier' {
{
$sb = { }
1..1 | Invoke-Parallel { $using:sb }
} | Should -Throw
Complete 'Invoke-Parallel -Functions Compl' |
ForEach-Object ListItemText |
Should -Contain 'Complete'
}
}

It 'Should throw if passing a scriptblock with the -Variables parameter' {
{
$sb = { }
1..1 | Invoke-Parallel { $sb } -Variables @{ sb = $sb }
} | Should -Throw
Context '$using: keyword Support' {
It 'Allows passed-in variables through $using: keyword' {
$message = 'Hello world from {0:D2}'
$items = 0..10 | Invoke-Parallel { $using:message -f $_ } |
Sort-Object

$shouldBe = 0..10 | ForEach-Object { 'Hello world from {0:D2}' -f $_ }
$items | Should -BeExactly $shouldBe
}

It 'Allows indexing on using: statements' {
It 'Allows indexing on $using: passed-in variables' {
$arr = 0..10; $hash = @{ foo = 'bar' }
1 | Invoke-Parallel { $using:arr[-1] } | Should -BeExactly 10
1 | Invoke-Parallel { $using:hash['FOO'] } | Should -BeExactly 'bar'
}

It 'Allows member accessing on using: statemets' {
It 'Allows member access on $using: passed-in variables' {
$hash = @{
foo = @{
bar = [pscustomobject]@{ Index = 0..10 }
}
}

1 | Invoke-Parallel { $using:hash['foo']['bar'].Index[5] } | Should -BeExactly 5
1 | Invoke-Parallel { $using:hash['foo']['bar'].Index[5] } |
Should -BeExactly 5
}
}

Context 'Script Block Assertions' {
It 'Should throw on passed-in Script Block via $using: keyword' {
{ $sb = { }; 1..1 | Invoke-Parallel { $using:sb } } |
Should -Throw -ExceptionType ([PSArgumentException])
}

It 'Should throw on passed-in Script Block via -Variables parameter' {
{ $sb = { }; 1..1 | Invoke-Parallel { $sb } -Variables @{ sb = $sb } } |
Should -Throw -ExceptionType ([PSArgumentException])
}

It 'Should throw on passed-in Script Block via input object' {
{ { 1 + 1 } | Invoke-Parallel { & $_ } } |
Should -Throw -ExceptionType ([PSArgumentException])
}
}

Context 'Invoke-Parallel' {
It 'Process in parallel' {
$timer = [Stopwatch]::StartNew()
1..5 | Invoke-Parallel { Start-Sleep 1 }
$timer.Stop()
$timer.Elapsed | Should -BeLessOrEqual ([timespan]::FromSeconds(1.5))
}

It 'Supports streaming output' {
Measure-Command {
0..10 | Invoke-Parallel {
0..10 | ForEach-Object {
Start-Sleep 1
$_
}
} | Select-Object -First 5 |
Should -HaveCount 5
} | ForEach-Object TotalSeconds |
Should -BeLessThan 2

Measure-Command {
0..10 | Invoke-Parallel { Start-Sleep 1; $_ } -ThrottleLimit 2 |
Select-Object -First 10 |
Should -HaveCount 10
} | ForEach-Object TotalSeconds |
Should -BeLessThan 6
}

It 'Can add items to a single thread instance' {
$dict = [ConcurrentDictionary[string, object]]::new()

Get-Process | Invoke-Parallel { ($using:dict).TryAdd($_.Id, $_) } |
Should -Contain $true

$dict[$PID].ProcessName | Should -Be (Get-Process -Id $PID).ProcessName
}
}
}
7 changes: 0 additions & 7 deletions tests/common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,3 @@ function Complete {
$null).CompletionMatches
}
}

function Test-Completer {
param(
[ArgumentCompleter([PSParallelPipeline.CommandCompleter])]
[string] $Test
)
}