Repository navigation
Description
Part of #120 (Phase 3 — API improvements). Blocks #83.
Question
When a consumer supplies their own [PesterConfiguration] object via $PSBPreference.Test.Configuration, what wins — their object, or the existing $PSBPreference.Test.* settings?
#83 raises this and leaves it open. It is the one genuinely undesigned item left in v1.0.0, and it defines public API surface that freezes at 1.0.0 — getting the precedence wrong is a breaking change to undo later.
Note: #120 previously pointed at "joshooaj's design in #80". That pointer is stale — #80 is a different, already-closed request about
Output.VerbosityandRun.SkipRemainingOnFailure. There is no existing design to implement.
Open questions
- Precedence. Does the supplied object act as a base that
$PSBPreference.Test.*overrides, or as a final say that PowerShellBuild only fills gaps in? A consumer who sets both and gets the opposite of what they expected is the failure mode to design against. Run.Path. PowerShellBuild computes the test path today. If the consumer setsRun.Pathon their object, is it honored, ignored, or an error? The advanced surface permits multiple paths, which the current single-path model has no answer for.- Opinionated defaults. Which settings does PowerShellBuild insist on regardless (code coverage output location, result file paths, CI-detection behavior) because the surrounding build tasks depend on them?
- Discoverability. If a supplied setting is silently overridden, how does the consumer find out? Silent override is the worst outcome — worse than an error.
- Pester 5 and 6.
Test-PSBuildPestersupports both majors and the test: Add Test-PSBuildPester integration tests and fix latent bugs #137 matrix verifies both. Does the configuration object surface differ between them in ways that affect the answer?
How to resolve
Conversation, not code — /grilling plus /domain-modeling. @joshooaj should be in it: they raised #83 and the design questions are theirs. A prototype of the merge logic is reasonable if the discussion stalls on something concrete.
Done when
The precedence contract is written down here in enough detail that #83 can be implemented without further design decisions.
Activity
tablackburn commentedon Aug 26, 2026
Evidence brief (2026-08-26)
Assembled so the design conversation starts from facts. This decides nothing — every option
below is left open with its trade-off, and questions 1–5 still need @joshooaj and @tablackburn.
The finding that changes the shape of the question
Pester already ships the merge primitive, and it is identical in both supported majors.
The override wins only for options whose
IsModifiedis$true; untouched options fallthrough to the base; neither input is mutated. Pester uses it on itself in
src/Main.ps1:So no merge logic needs writing. The decision is which object is base, which is override, and
what gets re-asserted afterward.
The constraint that rules out the obvious answer
Test-PSBuildPester.ps1:95-100assigns six properties unconditionally, and Pester marks anoption
IsModifiedon any assignment — even assigning its existing default. Meanwhilebuild.properties.ps1:47-109gives every$PSBPreference.Test.*key a concrete value; there is nounset state.
Therefore "
$PSBPreference.Test.*overrides the supplied object" would override a consumer'sOutput.Verbositywith'Detailed'even when they set nothing else — precisely the failuremode question 1 says to design against. That option only works alongside giving those preferences a
$null/unset state, which changes the documented default table inREADME.md:105-119.Question 3 — the premise needs correcting
The issue asks which settings the surrounding build tasks depend on. Reading
psakeFile.ps1andIB.tasks.ps1end to end: they depend on none. Neither reads a test-result or coverage fileafterward.
Task Testis an empty aggregator.Every hard dependency is internal to
Test-PSBuildPester, and there are four:Run.PassThru = $true$testResult, line 116 gates on$testResult.Result. There is noSet-StrictModeanywhere in the module, so a consumer setting$falsemakes$null.Result -eq 'Failed'false — a build with failing tests passes silentlyCodeCoverage.OutputPathCodeCoverage.OutputFormatCodeCoverage.EnabledTestResult.*is not structurally required — nothing readstestResults.xml. It can beconsumer-overridable at zero cost.
There is also no CI detection anywhere in the module, so question 3's "CI-detection behavior"
has no current implementation to preserve.
Worth noting for scoping: the coverage gate could read
$testResult.CodeCoverageinstead ofre-parsing the XML — this repository's own root
psakeFile.ps1:89-95already does exactlythat — which would shrink the frozen 1.0.0 contract from four properties to one. Bigger change,
much smaller public contract.
Question 2 —
Run.Pathis not set at allTest-PSBuildPesternever assignsRun.Path. It relies onPush-Location -LiteralPath $Path(line 92, popped at 155) plus Pester's default
Run.Path = '.'.Two consequences: a consumer's relative
Run.Pathwould resolve againstTest.RootDirrather thanthe project root, and honoring
Run.Pathproperly means removing thePush-Locationpair — whichalso moves where the coverage file at line 122 resolves. It cannot be bolted onto a merge-only
implementation.
The single-path limit is ours, not Pester's:
Run.Pathis alreadyStringArrayOptionin bothmajors. Widening
[string]$Pathto[string[]]is not a breaking change for callers.Question 4 — nothing today
Test-PSBuildPester.ps1contains zeroWrite-Warningand zeroWrite-Verbose. No prior art wasfound for warning on override — Pester, Maester, and PSModule/Invoke-Pester all override silently.
IsModifiedmakes a precise warning cheap if scoped to the reserved set: you can enumerateexactly what the consumer actually set. Scoped to everything PowerShellBuild assigns, it would fire
on all six unconditional properties every build.
Question 5 — measured, and the answer is encouraging
New-PesterConfigurationcompared in separate processes, Pester 5.7.1 vs 6.1.0:CodeCoverage.UseBreakpoints(5:True→ 6:False)Run.Parallel,Run.Shuffle,Should.DisableV5A version-agnostic contract is feasible, provided it is expressed generically —
::Mergeplus anamed reserved list — rather than as an enumerated property table.
Prior art
Maester is the closest analog, and its answer to question 2 is worth reading
(
Invoke-Maester.ps1:286-304):consumer object is the base; the tool overrides unconditionally only the two it structurally
needs — including
Run.PassThru, the same property we hardcode — and conditionally, only when itsown parameter was supplied, for path and filters. Silently.
Pester itself merges then corrects individual settings afterward (
src/functions/Output.ps1special-cases
-not $PesterPreference.Output.Verbosity.IsModifiedto raise verbosity under CI).Two things found on the way, both separate from this design
IB.tasks.ps1:100reads$PSBPreference.Test.CodeCoverage.OutputFormat, butbuild.properties.ps1:102definesOutputFileFormat. Invoke-Build users splat$null.psakeFile.ps1:161has it right. Filed separately.New-PesterConfigurationlanded in5.2 and
Run.SkipRemainingOnFailure— assigned unconditionally at line 97 — in 5.3. Worth raisingto 5.3.0 as part of whatever ships here.
Also:
tests/Test-PSBuildPester.tests.ps1contains no references toPesterConfiguration, sonothing existing pins the current assignments. A merge change has no test to break — which cuts both
ways.
tablackburn commentedon Aug 28, 2026
Deferring to 1.1.0 along with #83, which this exists to unblock.
The design questions here are real and none of them are answered — that is exactly the problem. They need @joshooaj, who raised #83, and the conversation has not started. Meanwhile the finished 1.0.0 break surface (PlatyPS 1.x, psake 5.0.4, Pester 6.0.0, the PowerShell 5.1 floor) has been sitting unreleased since 0.8.2 waiting behind it.
Nothing about this design gets harder by shipping 1.0.0 first. The surface precedence has to interact with,
$PSBPreference.Test.*, has been frozen since 0.8.2 either way — the version number on the next release does not constrain it.Everything already written here stands; the five open questions are still the right five. This is a scheduling change, not a change of scope or intent. See #83 for the fuller reasoning.