Skip to content

feat!: Raise the Pester floor to 6.0.0 and drop the 5.x matrix - #182

Merged
tablackburn merged 2 commits into
mainfrom
feature/172-pester-6-floor
Aug 27, 2026
Merged

feat!: Raise the Pester floor to 6.0.0 and drop the 5.x matrix#182
tablackburn merged 2 commits into
mainfrom
feature/172-pester-6-floor

Conversation

@tablackburn

Copy link
Copy Markdown
Contributor

Closes #172. Part of #120 (Phase 3).

Breaking. RequiredModules now requires Pester 6.0.0 or newer, previously 5.6.1, and
Test-PSBuildPester refuses to run under anything older. With this, every dependency floor matches
what CI actually tests.

Why

Same principle as the psake floor in #181: the declared minimum should be what is tested.
Nothing exercised a Pester 5 consumer except the matrix leg that existed to keep the claim honest.

Dependency Floor before Floor now Tested
BuildHelpers 2.0.16 2.0.16 2.0.16
Pester 5.6.1 6.0.0 6.1.0
psake 5.0.4 5.0.4 5.0.4

Two real defects fixed alongside

Test-PSBuildPester declared a floor it could never honor. Verified by downloading the version
in question:

Pester 5.0.0 exports New-PesterConfiguration: False
5.0.0 mentions SkipRemainingOnFailure:        False

The function calls both. So the guard admitted versions that could not work, and the caller got a
CommandNotFoundException instead of the clear "version not supported" error the guard exists to
produce.

The requirements.psd1 pin moves 6.0.0 → 6.1.0. Worth being explicit that this issue's
original premise was wrong
, and it is corrected on #172: CI does honor the pin, running exactly
Pester v6.0.0 on all four legs. I had generalized from my own machine, which has 6.1.0 installed
from elsewhere and autoloads it. What survives is narrower and latent — the pin holds only because
6.0.0 is currently the highest Pester on the runner, and a newer one in a future image would
silently override it.

This removes machinery rather than adding it

requirements.pester-matrix.psd1 deleted — it existed solely to install Pester 5 for the matrix
build.ps1 second Invoke-PSDepend call deleted
tests/Test-PSBuildPester.tests.ps1 matrix collapses from two majors to one
Test-PSBuildPester.ps1 three lines — the entirety of version-conditional shipped code

The discovery loop is deliberately kept rather than hardcoded to 6, so a second supported
major can return without rebuilding it. It now also throws when no supported major is installed,
instead of silently producing zero matrix legs — the same silent-nothing failure mode #174 was
about.

Consumer cost, which is smaller than a major bump implies

  • Should -Be and the v5 assertions still work. Pester 6's Should.DisableV5 defaults to
    $false, so existing suites are not forced onto the Should-* family. Verified directly.
  • Run.FailOnNullOrEmptyForEach now defaults to $true, where Pester 5 skipped an empty
    -ForEach silently. That can turn a green suite red — usually by surfacing a test that never ran
    and never said so. -AllowNullOrEmptyForEach is the escape for collections that can legitimately
    be empty.
  • Pester 6 supports Windows PowerShell 5.1 and PowerShell 7.4+, matching PowerShellBuild's own
    floor, so it adds no engine constraint.

The honest argument against, recorded so it is weighed rather than buried: Pester 6.0.0 is seven
weeks old, and Pester 5.9.1 shipped two weeks ago — upstream still maintains 5.x. This requires a
recent major of the tool that runs consumers' own test suites, which is a larger imposition than a
build task runner. That was the substance of #120's July decision to keep ≥ 5.x, and this reverses
it deliberately.

Verification

  • Full suite: 463 passed, 0 failed. The drop from 472 is the removed Pester 5 matrix leg, not a
    regression — the matrix now reports one context, with inner Pester 6.1.0
  • Test-ModuleManifest validates against the raised floor
  • The single analyzer finding on Test-PSBuildPester is pre-existing on main (confirmed by
    stashing and re-running)
  • Migration-guide entry and changelog entry added; repository-specific.instructions.md corrected,
    since it still described the old 5.0.0 contract

Closes #172. Part of #120 (Phase 3).

BREAKING CHANGE: Pester 5.x is no longer supported. RequiredModules is
enforced at import, so a consumer with only Pester 5.x installed cannot
import the module, and Test-PSBuildPester refuses to run under an
already-loaded 5.x.

Same principle as the psake floor in #181: the declared minimum should be
what CI actually tests. Nothing exercised a Pester 5 consumer except the
matrix leg, which existed only to keep that claim honest.

Two real defects go with it.

Test-PSBuildPester declared a 5.0.0 minimum it could never honor. Verified
by downloading that version: Pester 5.0.0 does not export
New-PesterConfiguration and has no Run.SkipRemainingOnFailure, and the
function calls both. The guard admitted versions that then failed with a
CommandNotFoundException instead of the clear error it exists to produce.

The requirements.psd1 pin moves 6.0.0 -> 6.1.0. Note the original #172
premise was wrong and is corrected on the issue: CI does honor the pin,
running exactly 6.0.0 on all four legs. What is true is narrower -- the pin
holds only because 6.0.0 is the highest Pester on the runner, and a newer
one in a future image would silently override it.

Dropping 5.x support removes machinery rather than adding it:
requirements.pester-matrix.psd1 is deleted, build.ps1 loses its second
Invoke-PSDepend call, and the Test-PSBuildPester matrix collapses from two
majors to one. The discovery loop is kept rather than hardcoded, so a
second supported major can return without rebuilding it, and it now throws
when no supported major is installed instead of silently producing no
tests.

Consumer cost is smaller than a major bump implies: Pester 6 keeps the v5
Should -Be assertions, since Should.DisableV5 defaults to false. The one
behavioral change worth flagging is Run.FailOnNullOrEmptyForEach, which
now defaults to true.

Suite: 463 passed, 0 failed. The drop from 472 is the removed Pester 5
matrix leg, not a regression. Test-ModuleManifest validates against the
raised floor. The one analyzer finding on Test-PSBuildPester is
pre-existing on main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AKaM9i6NyMFDcJNeC34h5
Copilot AI lite review requested due to automatic review settings August 26, 2026 23:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

Test Results

    4 files  ± 0    744 suites  ±0   2m 27s ⏱️ - 3m 20s
  469 tests  -  4    466 ✅  -  5   3 💤 +1  0 ❌ ±0 
1 880 runs   - 16  1 815 ✅  - 20  65 💤 +4  0 ❌ ±0 

Results for commit e2da4d3. ± Comparison against base commit 8cb163c.

This pull request removes 16 and adds 12 tests. Note that renamed tests count towards both.
Test-PSBuildPester.with inner Pester 5.9.0.fails the build when a setup block throws
Test-PSBuildPester.with inner Pester 5.9.0.fails the build when a test fails
Test-PSBuildPester.with inner Pester 5.9.0.fails the build when a test file errors during discovery
Test-PSBuildPester.with inner Pester 5.9.0.fails when measured coverage is below the code coverage threshold
Test-PSBuildPester.with inner Pester 5.9.0.passes when measured coverage is above the code coverage threshold
Test-PSBuildPester.with inner Pester 5.9.0.succeeds for a healthy suite
Test-PSBuildPester.with inner Pester 5.9.0.writes code coverage in the requested format to the requested path
Test-PSBuildPester.with inner Pester 5.9.0.writes test results to the requested output path
Test-PSBuildPester.with inner Pester 6.0.0.fails the build when a setup block throws
Test-PSBuildPester.with inner Pester 6.0.0.fails the build when a test fails
…
Pester version floor.declares a Pester floor in the module manifest
Pester version floor.names the same floor in the message the guard throws
Pester version floor.states the floor in Test-PSBuildPester at least twice
Pester version floor.uses the manifest floor everywhere the function states it
Test-PSBuildPester.with inner Pester 6.1.0.fails the build when a setup block throws
Test-PSBuildPester.with inner Pester 6.1.0.fails the build when a test fails
Test-PSBuildPester.with inner Pester 6.1.0.fails the build when a test file errors during discovery
Test-PSBuildPester.with inner Pester 6.1.0.fails when measured coverage is below the code coverage threshold
Test-PSBuildPester.with inner Pester 6.1.0.passes when measured coverage is above the code coverage threshold
Test-PSBuildPester.with inner Pester 6.1.0.succeeds for a healthy suite
…
This pull request skips 1 test.
Test-PSBuildPester.Regressions independent of the inner Pester version.honors the Pester version that is already loaded

♻️ This comment has been updated with latest results.

…d guard

The floor is stated in three places -- RequiredModules, and twice inside
Test-PSBuildPester -- plus the message the guard throws. They drifted and
nobody noticed for months: the manifest said 5.6.1 while the function said
5.0.0, a version that cannot run the function at all. That is the defect
this pull request fixes, and nothing would have caught it recurring.

The alternative was to read the floor from the manifest at runtime.
Verified as workable and cheap -- Import-PowerShellDataFile on
<ModuleBase>/<Name>.psd1 returns 6.0.0, about 0.4 ms a call -- but
rejected. It adds a lookup that returns nothing when the module is loaded
from an unusual path, and a floor of $null makes "$version -lt $null"
false, so the guard would vanish silently. That is the same shape as
Get-Command -Module $null applying no filter (#174) and the empty
ExternalHelpFile aborting a MAML export. A hardcoded fallback would not
help either: the constant would still exist, alongside a new branch that
can pick the wrong one.

Note $m.RequiredModules is not a route to this at all -- it reports the
loaded version, not the declared minimum, so a guard built on it would
compare Pester against itself.

Asserting the agreement catches drift when it is introduced, in CI, with
no runtime coupling and no way to fail open. Proven to work: temporarily
desyncing the guard to 5.0.0 fails exactly one test, "uses the manifest
floor everywhere the function states it".

One of the four tests guards the test itself. If the function is
refactored so neither the -MinimumVersion nor the [version] literal
pattern matches, an empty match set would satisfy the comparison and prove
nothing, so the count is asserted first.

Also documents why "honors the Pester version that is already loaded" now
skips in CI. It needs two installed Pester versions to distinguish "used
the loaded one" from "imported the newest", and dropping the 5.x matrix
left one. Restoring it would mean reinstating install-only machinery this
pull request deletes, so it is skipped deliberately rather than by
oversight.

Suite: 467 passed, 0 failed. Analyzer clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AKaM9i6NyMFDcJNeC34h5
@tablackburn
tablackburn merged commit 4876653 into main Aug 27, 2026
9 checks passed
@tablackburn
tablackburn deleted the feature/172-pester-6-floor branch August 27, 2026 14:07
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.

Pester version floors do not match what is tested, and Test-PSBuildPester's guard admits versions that cannot work

2 participants