Skip to content

Don't let reporting a failed parallel worker abort the rest of the run - #3004

Merged
nohwnd merged 1 commit into
mainfrom
nohwnd-fix-flakes
Aug 24, 2026
Merged

Don't let reporting a failed parallel worker abort the rest of the run#3004
nohwnd merged 1 commit into
mainfrom
nohwnd-fix-flakes

Conversation

@nohwnd

@nohwnd nohwnd commented Aug 22, 2026

Copy link
Copy Markdown
Member

Two flaky tests, one of which turned out to be a real bug in Run.Parallel.

Reporting a failed worker aborted the run

Invoke-InRunspacePool reports a worker that threw with Write-Error and keeps going, because the remaining files still have results worth reporting. Write-Error obeys the caller's $ErrorActionPreference though, so a caller running with Stop gets a terminating error instead, the loop aborts on the first failing file, and the results of everything that already finished are dropped. CI scripts commonly run with Stop.

$ErrorActionPreference = 'Stop'
Invoke-InRunspacePool -InputObject @(1,2,3) -ThrottleLimit 3 -ScriptBlock {
    param($item) if (2 -eq $item) { throw 'worker blew up' }; $item
}
# before: THREW, nothing returned
# after : 1,3 returned and the error reported

Both Write-Error calls are now explicitly -ErrorAction Continue, with a test that sets Stop itself so it stays fixed regardless of the harness.

Pester's own test.ps1 sets Stop on line 43, so this reliably killed the P phase locally and with it the entire RSpec phase after it. With the fix the full local suite completes: 2915 passed, 0 failed, 3 skipped.

Timing assertions

Should-BeFasterThan.Does not throw when actual is faster than expected failed on PS7 - macOS latest on both #2993 and #2995 today and needed manual reruns.

Both flaky assertions bound the measured time from above, and measuring a scriptblock times compiling it, GC, and whatever else the machine is doing, which has no upper bound on a shared runner. Widened both from 100ms/1000ms to 30s, with a comment recording the asymmetry so they do not get tightened again: a 10ms sleep that takes 30 seconds means the machine is broken, and the opposite direction needs no margin because a sleep never finishes early.

🤖

Invoke-InRunspacePool reports a worker that threw with Write-Error and keeps going,
the remaining files still have results worth reporting. Write-Error obeys the
caller's $ErrorActionPreference though, so a caller running with 'Stop', which is
what a CI script typically does, gets a terminating error instead and the loop
aborts on the first failing file, dropping the results of everything that already
finished. Both Write-Error calls are now explicitly -ErrorAction Continue.

Pester's own test.ps1 sets 'Stop' on line 43, so this reliably killed the P phase
locally and with it the whole RSpec phase after it.

Also widens two timing assertions. Both bound the measured time from above, and
measuring a scriptblock times compiling it, GC, and whatever else the machine is
doing, which has no upper bound on a shared runner. "Does not throw when actual is
faster than expected" failed on macOS on two PRs today. A 10ms sleep that takes 30
seconds means the machine is broken, not that the assertion is wrong. The opposite
direction needs no margin, a sleep never finishes early.

🤖
@nohwnd
nohwnd merged commit 942da19 into main Aug 24, 2026
20 checks passed
@nohwnd
nohwnd deleted the nohwnd-fix-flakes branch August 24, 2026 19:01
nohwnd added a commit that referenced this pull request Aug 24, 2026
Four files went into a Run.Parallel run on CI and three came back. The missing
file was not failed, skipped or errored, it was not reported at all, and the run
looked green with one test fewer than it should have had.

Invoke-TestInParallel filters worker results with a Where-Object that keeps only
well formed result objects, to guard against stray pipeline output. That filter
cannot tell stray output from a worker that died before returning its result, and
nothing afterwards compared the number of results to the number of files sent. So
a lost file was dropped as quietly as a stray string, and Sort-Object put the
survivors in discovery order, which made the output look like a normal shorter run.

Compare the counts after the filter and throw, naming the files that went missing.
The worker's own error is already surfaced by Invoke-InRunspacePool, this says
which file it cost us. Losing results silently is worse than failing.

This is independent of why the worker died. Any worker that dies for any reason
costs a whole test file, and #3004 only stopped one failing worker from aborting
the results of the others.

Added a test that hands Invoke-TestInParallel a runspace pool runner which drops
one file's result, and asserts the run throws and names that file.

Fix #3011

🤖
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant