Harness the power of the Powershell Core foreach-object parallel feature
1. Harness the power of the Powershell Core foreach-object parallel feature
Hi Guys,
I'm trying to utilize the parallelism feature in PowerCore. https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/ but I'm getting this error..
Any Ideas?
InvalidOperation: /root/.local/share/powershell/Modules/Pester/5.0.4/Pester.psm1:898
Line |
898 | $state.CurrentBlock.Tests.Add($Test)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| You cannot call a method on a null-valued expression.
2. Describe Your Environment
Pester (5.0.4)
Name Value
---- -----
PSVersion 7.0.3
PSEdition Core
GitCommitId 7.0.3
OS Linux 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
3. Expected Behavior

- The first Test passes as expected
Describing Shopping Basket Test - Sequential
[+] Basket contains 🍎 171ms (126ms|45ms)
[+] Basket contains 🍌 8ms (5ms|3ms)
[+] Basket contains 🍐 8ms (5ms|3ms)
[+] Basket contains 🍍
- The second Test with the Parallel feature enabled blows up Pester
InvalidOperation: /root/.local/share/powershell/Modules/Pester/5.0.4/Pester.psm1:898
Line |
898 | $state.CurrentBlock.Tests.Add($Test)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| You cannot call a method on a null-valued expression.
4. Tests
Describe "Shopping Basket Test - Sequential" -Tags @("fruit") {
$basket = @("🍎", "🍌", "🍐", "🍍", "🍑", "🍓", "🍈", "🍇")
$fruit = @("🍎", "🍌", "🍐", "🍍")
$fruit | ForEach-Object {
It "Basket contains $_ " -TestCases @{Fruit = $_; Basket = $basket } {
$basket.contains($Fruit) | Should -Be $true -Because "The Basket should contain the fruit '$_'"
}
}
}
Describe "Shopping Basket Test - Parallel" -Tags @("fruit") {
$basket = @("🍎", "🍌", "🍐", "🍍", "🍑", "🍓", "🍈", "🍇")
$fruit = @("🍎", "🍌", "🍐", "🍍")
$fruit | ForEach-Object -Parallel {
It "Basket contains $_" -TestCases @{Fruit = $_; Basket = $basket } {
$basket.contains($Fruit) | Should -Be $true -Because "The Basket should contain the fruit '$_'"
}
} -ThrottleLimit 10
}
The internals are not expecting to have more than 1 test being discovered at the same time. You are either putting the discoverer in a bad state, or don't have the correct state in the other thread where the code is running.
I understand you thinking, but in Pester 5 the tests don't even run at this place, you'd be gaining very little by parallelizing discovery. To implement this properly you'd need to build the capability in Pester directly (in Invoke-Test and Invoke-TestItem), but consider the stuff discussed here first #1270
I like the emojis in your tests, but your code would need a lot of changes to work correctly with Pester 5. Did you try running it with Pester 4? Looks like it might almost work.
Hi @nohwnd ,
I'm running my suite using Pester (5.0.4).
The first test executes with no issues but introducing the modified (2nd test) with the '-Parallel' parameter, a bomb is set off.
Yes I understand, but the test does not run at that point in time, it is only discovered. You are wrapping the logic that runs during test discovery into parallelism. Discovery does almost nothing so this would be pointless, even if it worked. 🙂
I also tried to use PowerShell Core's Foreach-Object parallel feature to run Pester tests in parallel. See the following gist with an example.
But when trying to store the Pester objects in a new Pester Object not all the properties can be updated. The following cannot be updated.
- Configuration
- ExecutedAt
- PluginConfiguration
- PluginData
- Plugins
See screenshot below.

This does not impact the end result but I want to understand why these properties cannot be updated. The Pester Tests being run in parallel are stored in a variable Result of the type Pester.Run and the new Pester Object is also of the same type.
Stefan
Right @stefanstranger:
-
error 1, configurations can't be added (mathematically added I mean, as in 1+1 = 2.) and it does not make sense to implement addition operator for them (I think) because what would be the result of config1 + config2 = ? We do have a function to merge them, which is like addition but is not cumulative (config1 + config2 is not the same as config2 + config1). So in this case it would not make much sense to do addition. I think this would be nicely solved by adding a RunCollection which would be a List<Run>, IRun. The properties would then be the ones that can be summarized, and the ones that that can't be summarized we would take the first one.
-
ExecutedAt is a date time. Adding them together is not supported and it would not make sense either. When you have two runs both invokes in year 2021, the summary should not show the executedAt is 4042.
-
PluginConfiguration and PluginData are HashTables, and adding them does not make sense the same way adding configurations does not make sense. You can safely set them to @{}, I think they are empty anyway unless you use the raw object.
-
There are also Plugins. That is a list, so you could create a new list that would concatenate both of them, it is just not implemented as the += addition operator. But I think those are also empty list on both sides.