From 3edfc492b0e7be06d3d7c0061b4be1e7a0255bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Sat, 5 Sep 2026 11:06:49 +0200 Subject: [PATCH] Fail when the timing assertions get something they cannot measure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Should-BeFasterThan and Should-BeSlowerThan handled [scriptblock] and [timespan], and both branches return. Anything else fell out of the bottom of the function, so the assertion returned having asserted nothing and the test passed. A string, a number, $null and an array all passed silently. That also made a CI flake unreadable. On Windows PS7 the test asserting that a 10ms sleep is slower than 1ms failed with "no assertion failure error was thrown", and the whole test took 3ms. The scriptblock was never run, which is why there was no sleep and no failure, but nothing said so, and it looked like a scriptblock that ran impossibly fast. The next time it happens the message names the type and value we were actually handed. 🤖 --- src/functions/assert/Time/Should-BeFasterThan.ps1 | 7 +++++++ src/functions/assert/Time/Should-BeSlowerThan.ps1 | 7 +++++++ .../assert/Time/Should-BeFasterThan.Tests.ps1 | 12 ++++++++++++ .../assert/Time/Should-BeSlowerThan.Tests.ps1 | 12 ++++++++++++ 4 files changed, 38 insertions(+) diff --git a/src/functions/assert/Time/Should-BeFasterThan.ps1 b/src/functions/assert/Time/Should-BeFasterThan.ps1 index 826f2b9dd..ffced49b3 100644 --- a/src/functions/assert/Time/Should-BeFasterThan.ps1 +++ b/src/functions/assert/Time/Should-BeFasterThan.ps1 @@ -76,4 +76,11 @@ } return } + + # Neither branch matched. Both branches above return, so reaching here means we were handed + # something we cannot measure or compare. Without this the assertion would return having done + # nothing and the test would pass, which is the worst way to fail. It also hid a CI flake: a + # test asserting that a 10ms sleep is slower than 1ms failed while the whole test took 3ms, + # because the scriptblock was never run and nothing said so. + $assert.Fail("Expected a [scriptblock] to measure or a [timespan] to compare, but got .", @{ Actual = $Actual; Because = $Because }) } diff --git a/src/functions/assert/Time/Should-BeSlowerThan.ps1 b/src/functions/assert/Time/Should-BeSlowerThan.ps1 index 2558597f2..dbc0c4d5a 100644 --- a/src/functions/assert/Time/Should-BeSlowerThan.ps1 +++ b/src/functions/assert/Time/Should-BeSlowerThan.ps1 @@ -83,4 +83,11 @@ } return } + + # Neither branch matched. Both branches above return, so reaching here means we were handed + # something we cannot measure or compare. Without this the assertion would return having done + # nothing and the test would pass, which is the worst way to fail. It also hid a CI flake: a + # test asserting that a 10ms sleep is slower than 1ms failed while the whole test took 3ms, + # because the scriptblock was never run and nothing said so. + $assert.Fail("Expected a [scriptblock] to measure or a [timespan] to compare, but got .", @{ Actual = $Actual; Because = $Because }) } diff --git a/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 b/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 index 11f1e09ce..22189b8b5 100644 --- a/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 +++ b/tst/functions/assert/Time/Should-BeFasterThan.Tests.ps1 @@ -60,6 +60,18 @@ Describe "Should-BeFasterThan" { $err.Exception.Message | Verify-Like '*because I said so*' } + It "Throws when actual is neither a scriptblock nor a timespan" -ForEach @( + @{ Actual = 'a string' } + @{ Actual = 42 } + @{ Actual = $null } + ) { + # Without this the assertion returned having done nothing and the test passed. That also + # made a CI flake unreadable: a scriptblock that was never run looked like a scriptblock + # that ran impossibly fast. + $err = { $Actual | Should-BeFasterThan -Expected 1ms } | Verify-AssertionFailed + $err.Exception.Message | Verify-Like '*Expected a `[scriptblock`] to measure or a `[timespan`] to compare*' + } + It "Requires Expected" { # Don't invoke with Expected missing to test this: a missing mandatory parameter makes # PowerShell prompt for it, which hangs an interactive test.ps1 run and the release build. diff --git a/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 b/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 index f2350678e..39d458195 100644 --- a/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 +++ b/tst/functions/assert/Time/Should-BeSlowerThan.Tests.ps1 @@ -35,6 +35,18 @@ Describe "Should-BeSlowerThan" { $err.Exception.Message | Verify-Like '*because I said so*' } + It "Throws when actual is neither a scriptblock nor a timespan" -ForEach @( + @{ Actual = 'a string' } + @{ Actual = 42 } + @{ Actual = $null } + ) { + # Without this the assertion returned having done nothing and the test passed. That also + # made a CI flake unreadable: a scriptblock that was never run looked like a scriptblock + # that ran impossibly fast. + $err = { $Actual | Should-BeSlowerThan -Expected 1ms } | Verify-AssertionFailed + $err.Exception.Message | Verify-Like '*Expected a `[scriptblock`] to measure or a `[timespan`] to compare*' + } + It "Requires Expected" { # Don't invoke with Expected missing to test this: a missing mandatory parameter makes # PowerShell prompt for it, which hangs an interactive test.ps1 run and the release build.