Speed up the legacy Should assertion pipeline#2914
Open
nohwnd wants to merge 1 commit into
Open
Conversation
Invoke-Assertion is a 12-parameter function with Mandatory and Validate attributes, and its body is 2 lines. Binding those parameters plus building the splat hashtable costs tens of microseconds on every assertion, so its body is inlined into the three call sites in Should's end block. Test-AssertionResult keeps reading file/lineNumber/lineText/shouldThrow/ addErrorCallback through dynamic scoping exactly as it read the equally named parameters before. Invoke-Assertion itself stays defined. ArraysAreEqual built four arrays and made four helper calls to compare two single values. Added a fast path for the case where each side is a single non-null value that PowerShell does not enumerate, bare or wrapped in a one-element object[], which is what the pipeline always hands to Be. Anything enumerable still takes the full path. 1 | Should -Be 1 goes from 243us to 194us per call, measured in the same process on the inline build. 🤖
This was referenced Jul 19, 2026
johlju
reviewed
Jul 19, 2026
| # checks cannot match (one non-null element), the counts are both 1, and IsArray is false for | ||
| # both elements, ending in $Second -eq $First (-ceq when case sensitive) - which is what we do | ||
| # here without the @() allocations and four helper-function calls. | ||
| $singleFirst = if ($First -is [object[]] -and 1 -eq $First.Length) { $First[0] } else { $First } |
Contributor
There was a problem hiding this comment.
Can $First be [object[]] when the parameter declaration has [object]?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Profiling
1 | Should -Be 1showed that most of the time is not in the comparison, it is in the plumbing around it. Two changes:Invoke-Assertionis a 12-parameter function withMandatoryandValidateattributes, and its body is 2 lines. Binding those parameters plus building the splat hashtable costs tens of microseconds on every assertion. I inlined the body into the three call sites inShould'sendblock.Test-AssertionResultkeeps reading$file/$lineNumber/$lineText/$shouldThrow/$addErrorCallbackthrough dynamic scoping exactly as it read the equally namedInvoke-Assertionparameters before, the locals are typed[string]/[int]to mirror the old parameter coercion (a$nullScriptNamestill becomes'').Invoke-Assertionitself stays defined.ArraysAreEqualbuilt four arrays and made four helper calls to compare two single values. I added a fast path for the common case where each side is a single non-null value that PowerShell does not enumerate, bare or wrapped in a one-elementobject[], which is what the pipeline always hands toBe. Anything enumerable still takes the full path, including a one-element array that wraps another collection.1 | Should -Be 1goes from 243us to 194us per call, measured in the same process on the inline build. On an assertion-heavy workload (400 tests, 2 assertions each) the whole run drops about 15%.Verification: full suite green on macOS with both the inline and the dot-sourced build, all
*.ts.ps1files pass in fresh processes, PSScriptAnalyzer finding count is unchanged. The risky part is the dynamic scoping intoTest-AssertionResult, that is covered bytst/functions/assertions/*.Tests.ps1and by custom-operator tests inPester.CustomAssertion.ts.ps1.Related perf PRs: #2915, #2916, #2917, #2918. Measured together they cut ~21% from four representative workloads (inline build, macOS).
🤖