Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
c6b35ab
Migrate Pester tests from v4 to v5
nohwnd Apr 17, 2026
48231f5
Fix duplicate Run key in PesterConfiguration hashtable
nohwnd Apr 17, 2026
b17e620
Fix UpdatableHelpSystem.Tests.ps1 discovery/runtime scope bugs
nohwnd Jun 10, 2026
cc9fd83
Fix Pester 5 scope bugs in 3 more test files
nohwnd Jun 10, 2026
61a39bd
Fix Pester 5 scope bugs and -ErrorId matching in more test files
nohwnd Jun 10, 2026
1eb8031
Fix Pester 5 discovery and skip patterns in 2 more test files
nohwnd Jun 10, 2026
5ceb729
Restore ErrorView squiggles and fix SemanticVersion BeforeAll/BeforeD…
nohwnd Jun 10, 2026
0f6115c
Fix Pester 5 discovery/runtime scope issues in remoting+misc tests
nohwnd Jun 10, 2026
bc3caeb
Pester 5: discovery-scope and TestCases fixes for two Windows-elevate…
nohwnd Jun 10, 2026
570cc51
Pester 5: top-level skip, $script: scope, and assertion operator fixe…
nohwnd Jun 11, 2026
1e34a35
Pester 5: round 9 - cross-platform scope and assertion fixes
nohwnd Jun 11, 2026
5bbbd5d
Pester 5: round 10 - Windows-elevated and helper-function fixes
nohwnd Jun 11, 2026
7d1665e
Pester 5: fix PSSessionConfiguration RegisterNewConfiguration null path
nohwnd Jun 11, 2026
8052c78
Pester 5: avoid Join-Path on TestDrive PSDrive during discovery
nohwnd Jun 11, 2026
11777d1
Pester 5: round 12 - top-level helper functions + runtime $script: re…
nohwnd Jun 11, 2026
afa59fc
Pester 5: round 13 - merge duplicate BeforeAll blocks (only last runs…
nohwnd Jun 11, 2026
d0fdf06
Pester 5: round 14 - move Describe-level skips out of BeforeAll, wrap…
nohwnd Jun 11, 2026
70abda3
Pester 5: round 15 - sweep anti-patterns #26 (skip-in-BeforeAll too l…
nohwnd Jun 11, 2026
5c306b0
Pester 5: round 16 - fix anti-pattern #28 (file-scope it:skip leaking…
nohwnd Jun 11, 2026
718f15a
Pester 5: round 17 - WSL-validated batch fix across 12 test files
nohwnd Jun 11, 2026
5845d4e
Pester 5: round 18 - redefine ConvertTo-Hashtable inside DefaultComma…
nohwnd Jun 11, 2026
5f4d6b5
test(pester5): r19 — anti-pattern #26 Linux/macOS skip batch
nohwnd Jun 11, 2026
fb1d344
Pester 5 migration round 20 - 5 files (next-tier residuals)
nohwnd Jun 11, 2026
8a8f4e7
Pester 5 migration round 21 - AP23 in 2 Windows-only test files
nohwnd Jun 11, 2026
320b503
Pester 5 migration round 22 - 5 file-targeted fixes
nohwnd Jun 11, 2026
aae995e
Pester 5 migration round 23 - WinRM / IsWindows discovery-time skips
nohwnd Jun 11, 2026
5c725d9
Pester 5 migration round 24 - Implicit.Remoting probe + product Pendi…
nohwnd Jun 11, 2026
a45cba5
Pester 5 migration round 25 - fix discovery regression from round 24
nohwnd Jun 11, 2026
e3b0eb3
Pester 5: round 26 local iteration fixes (5 files)
nohwnd Jun 11, 2026
9c71333
Pester 5 migration round 27 — Implicit.Remoting AP31 fix + WebCmdlets
nohwnd Jun 11, 2026
0ca4227
Pester 5 migration round 28 - skip platform-specific product issues
nohwnd Jun 12, 2026
39d6ef7
Pester 5 migration round 29 — drop redundant runtime skipTest guards …
nohwnd Jun 12, 2026
f501efc
Pester 5 migration round 30 — discovery-time skip & TestCases interpo…
nohwnd Jun 12, 2026
5612681
Pester 5: fix UsingAssembly $script: scope, restore LocalAccounts dis…
nohwnd Jun 12, 2026
e09ab40
Pester 5: restore AfterAll in Format-Custom DRT Describe to unbreak $…
nohwnd Jun 12, 2026
e5d2d32
Pester 5: fix WinCompat $script:desktopModuleToUse and symlink Before…
nohwnd Jun 12, 2026
e32628f
Pester 5: keep wsman: drive globally scoped after remove/re-add test
nohwnd Jun 12, 2026
d5fe310
Pester 5: fix Test-Path NUL TestCases truncating junit XML; clean up …
nohwnd Jun 12, 2026
f89fc06
Pester 5: shallow-project PassThru before Export-Clixml; skip 2 Get-H…
nohwnd Jun 12, 2026
df5d5a1
Clean up merge artifacts and global variable pollution
nohwnd Jun 16, 2026
4d940b4
Remove unnecessary platform skips, fix helpFunc12 syntax test
nohwnd Jun 16, 2026
dba8a6b
Fix NativeStreams and SuppressAnsi cross-file PSStyle pollution
nohwnd Jun 16, 2026
476192e
Fix ProxyCommand.GetHelpComments for environments with installed help
nohwnd Jun 16, 2026
016e0aa
Add AfterAll to restore OutputRendering after Allow Escape Sequences
nohwnd Jun 17, 2026
3eb09fe
Fix NativeStreams tests for corrupted ErrorRecord format view
nohwnd Jun 17, 2026
221cbc4
Replace $global: bridge with -ForEach in DefaultCommands.Tests
nohwnd Jun 17, 2026
48427fb
Use Start-Process for stderr redirect test to bypass ErrorRecord form…
nohwnd Jun 17, 2026
2ca6ba8
Address review: restore SemanticVersion invalid cases, dedupe data, f…
nohwnd Jun 29, 2026
e2a69a2
SemanticVersion: keep lenient-parse cases dropped, trim duplicate run…
nohwnd Jun 29, 2026
9d10f4c
Dedup ConvertTo-Hashtable in DefaultCommands.Tests.ps1
nohwnd Jun 29, 2026
47c6f7d
Reference tracking issue #27642 in macOS skip comments
nohwnd Jun 29, 2026
e2d1b65
Bump to Pester 6.0.0 and apply v6 compatibility fixes
nohwnd Jul 7, 2026
23875d1
Fix Pester 6 OOM in Encoding.Tests: pass command name, not CommandInfo
nohwnd Jul 7, 2026
f4cbb15
Convert It -Pending to -Skip for Pester 6
nohwnd Jul 7, 2026
ff81a91
Fix ErrorView ConciseView test for Pester 6 TestDrive naming
nohwnd Jul 7, 2026
e234b86
Skip unreliable untrusted-module import at discovery for Pester 6
nohwnd Jul 7, 2026
a5d608b
Use here-string when possible
daxian-dbw Jul 24, 2026
77bf011
Merge branch 'master' into pester6-migration
nohwnd Aug 27, 2026
bf61a59
Restore CI test output, pin the Pester version, fix two discovery-sco…
nohwnd Aug 27, 2026
4c00922
Fix Cdxml discovery broken by the master merge
nohwnd Aug 27, 2026
f0988ad
Fail the build on any Pester failure, not just on failed tests
nohwnd Aug 28, 2026
8d49836
Release the transcript file in the JEA transcript test
nohwnd Aug 28, 2026
c4bb2b5
Keep the JEA transcript out of TestDrive
nohwnd Aug 28, 2026
71b6e2b
Keep the unelevated run's console output
nohwnd Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
357 changes: 305 additions & 52 deletions build.psm1

Large diffs are not rendered by default.

525 changes: 268 additions & 257 deletions test/SSHRemoting/SSHRemoting.Basic.Tests.ps1

Large diffs are not rendered by default.

285 changes: 285 additions & 0 deletions test/infrastructure/pesterResults.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# Guards the build script against reporting a green run when Pester actually failed.
# Deciding on failure counts alone misses two cases: a file whose Discovery phase throws,
# and a failing AfterAll. Both leave FailedCount at 0 and are absent from the NUnit file.

Describe "Pester run result handling in build.psm1" -Tags CI {

BeforeAll {
Import-Module "$PSScriptRoot/../../build.psm1" -Force

# The child process has to use the same Pester as this run, otherwise the test would be
# asserting on whatever version happens to be newest on the machine.
$script:pesterModulePath = (Get-Module Pester).Path

$script:fixtureDir = Join-Path $TestDrive 'fixtures'
New-Item -ItemType Directory -Path $script:fixtureDir -Force | Out-Null

# One file per failure kind, plus the shapes that must stay green.
# Statements are separated by ';' on purpose: 'BeforeAll { } It "t" { }' with no
# separator parses as a single call to BeforeAll with four arguments.
$script:fixtures = @{
DiscoveryFails = 'Describe "d" { It "unreachable" -NoSuchParameter { $true | Should -BeTrue } }'
BeforeAllFails = 'Describe "d" { BeforeAll { throw "boom in BeforeAll" }; It "t" { $true | Should -BeTrue } }'
AfterAllFails = 'Describe "d" { AfterAll { throw "boom in AfterAll" }; It "t" { $true | Should -BeTrue } }'
TestFails = 'Describe "d" { It "t" { $false | Should -BeTrue } }'
AllGreen = 'Describe "d" { It "t" { $true | Should -BeTrue } }'
Inconclusive = 'Describe "d" { It "t" { Set-ItResult -Inconclusive -Because "flaky" } }'
Skipped = 'Describe "d" { It "t" -Skip { $true | Should -BeTrue } }'
NoTests = 'Describe "d" { }'
}
foreach ($name in $script:fixtures.Keys) {
Set-Content -Path (Join-Path $script:fixtureDir "$name.tests.ps1") -Value $script:fixtures[$name]
}

# Runs one fixture through the same projection that Start-PSPester splices into the
# child process, and round-trips it through CliXml exactly as the child does.
function Get-FixtureSummary {
param([string] $Name)

$resultsFile = Join-Path $TestDrive "$Name.xml"
$summaryFile = Get-PSPesterSummaryPath -TestResultsFile $resultsFile
Remove-Item $summaryFile -Force -ErrorAction SilentlyContinue

$script = @"
Import-Module '$script:pesterModulePath'
`$config = New-PesterConfiguration
`$config.Run.Path = '$(Join-Path $script:fixtureDir "$Name.tests.ps1")'
`$config.Run.PassThru = `$true
`$config.Run.FailOnNullOrEmptyForEach = `$false
`$config.TestResult.Enabled = `$true
`$config.TestResult.OutputPath = '$resultsFile'
`$config.TestResult.OutputFormat = 'NUnitXml'
`$config.Output.Verbosity = 'None'
Invoke-Pester -Configuration `$config $(Get-PSPesterSummaryProjection) | Export-Clixml -Path '$summaryFile' -Force
"@
# A separate process, so a fixture that throws cannot disturb the test run itself.
& (Get-Process -Id $PID).Path -NoProfile -Command $script *> $null

return Import-PSPesterSummary -TestResultsFile $resultsFile
}
}

Context "The fixtures are what they claim to be" {

It "'<Name>' is valid PowerShell" -TestCases @(
@{ Name = 'BeforeAllFails' }
@{ Name = 'AfterAllFails' }
@{ Name = 'TestFails' }
@{ Name = 'AllGreen' }
@{ Name = 'Inconclusive' }
@{ Name = 'Skipped' }
@{ Name = 'NoTests' }
) {
param($Name)

$errors = $null
$null = [System.Management.Automation.Language.Parser]::ParseFile(
(Join-Path $script:fixtureDir "$Name.tests.ps1"), [ref]$null, [ref]$errors)
$errors | Should -BeNullOrEmpty
}

It "only 'DiscoveryFails' fails discovery" -TestCases @(
@{ Name = 'BeforeAllFails' }
@{ Name = 'AfterAllFails' }
@{ Name = 'TestFails' }
@{ Name = 'AllGreen' }
) {
param($Name)

(Get-FixtureSummary -Name $Name).FailedContainersCount | Should -Be 0
}
}

Context "The projection survives serialization" {

It "produces a readable summary for '<Name>'" -TestCases @(
@{ Name = 'DiscoveryFails' }
@{ Name = 'BeforeAllFails' }
@{ Name = 'AfterAllFails' }
@{ Name = 'TestFails' }
@{ Name = 'AllGreen' }
) {
param($Name)

$summary = Get-FixtureSummary -Name $Name
$summary | Should -Not -BeNullOrEmpty -Because 'the projection has to round-trip through Import-Clixml'
$summary.Result | Should -Not -BeNullOrEmpty
}
}

Context "Result reflects every kind of failure" {

It "reports '<Name>' as <Expected>" -TestCases @(
@{ Name = 'DiscoveryFails'; Expected = 'Failed' }
@{ Name = 'BeforeAllFails'; Expected = 'Failed' }
@{ Name = 'AfterAllFails'; Expected = 'Failed' }
@{ Name = 'TestFails'; Expected = 'Failed' }
@{ Name = 'AllGreen'; Expected = 'Passed' }
@{ Name = 'Inconclusive'; Expected = 'Passed' }
@{ Name = 'Skipped'; Expected = 'Passed' }
@{ Name = 'NoTests'; Expected = 'Passed' }
) {
param($Name, $Expected)

(Get-FixtureSummary -Name $Name).Result | Should -BeExactly $Expected
}

It "keeps FailedCount at 0 for '<Name>', which is why counts alone are not enough" -TestCases @(
@{ Name = 'DiscoveryFails' }
@{ Name = 'AfterAllFails' }
) {
param($Name)

$summary = Get-FixtureSummary -Name $Name
$summary.FailedCount | Should -Be 0
$summary.Result | Should -BeExactly 'Failed'
}

It "records the error message for a failed discovery" {
$summary = Get-FixtureSummary -Name 'DiscoveryFails'
$summary.FailedContainersCount | Should -Be 1
($summary.TestResult | Where-Object Kind -eq 'Container').FailureMessage |
Should -Match 'NoSuchParameter'
}

# The exact wording Pester puts on a throw inside BeforeAll/AfterAll changes between
# versions, so this asserts the invariant that matters instead: the message lives on the
# block, and reporting only failed tests would print nothing useful.
It "puts the message on the block, not on the test, for a failed BeforeAll" {
$summary = Get-FixtureSummary -Name 'BeforeAllFails'

$block = @($summary.TestResult | Where-Object Kind -eq 'Block')
$block.Count | Should -Be 1
$block[0].FailureMessage | Should -Not -BeNullOrEmpty

$test = @($summary.TestResult | Where-Object Kind -eq 'Test')
$test.Count | Should -Be 1
$test[0].FailureMessage | Should -BeNullOrEmpty -Because 'this is why failed tests alone are not enough to report on'
}

It "records a block failure for a failed AfterAll, which produces no failed test at all" {
$summary = Get-FixtureSummary -Name 'AfterAllFails'

$summary.FailedCount | Should -Be 0
$block = @($summary.TestResult | Where-Object Kind -eq 'Block')
$block.Count | Should -Be 1
$block[0].FailureMessage | Should -Not -BeNullOrEmpty
}
}

Context "Assert-PSPesterRunPassed" {

It "throws for '<Name>'" -TestCases @(
@{ Name = 'DiscoveryFails' }
@{ Name = 'BeforeAllFails' }
@{ Name = 'AfterAllFails' }
@{ Name = 'TestFails' }
) {
param($Name)

{ Assert-PSPesterRunPassed -Summary (Get-FixtureSummary -Name $Name) -TestArea 'fixtures' } |
Should -Throw -ExpectedMessage '*fixtures*'
}

It "does not throw for '<Name>'" -TestCases @(
@{ Name = 'AllGreen' }
@{ Name = 'Inconclusive' }
@{ Name = 'Skipped' }
@{ Name = 'NoTests' }
) {
param($Name)

{ Assert-PSPesterRunPassed -Summary (Get-FixtureSummary -Name $Name) -TestArea 'fixtures' } |
Should -Not -Throw
}

It "falls back to failure counts when the summary predates the Result property" {
$legacy = [pscustomobject]@{
TotalCount = 1; FailedCount = 0; FailedBlocksCount = 0
FailedContainersCount = 1; TestResult = @()
}
{ Assert-PSPesterRunPassed -Summary $legacy -TestArea 'fixtures' } | Should -Throw
}

It "does not throw for a legacy summary with no failures at all" {
$legacy = [pscustomobject]@{
TotalCount = 1; FailedCount = 0; FailedBlocksCount = 0
FailedContainersCount = 0; TestResult = @()
}
{ Assert-PSPesterRunPassed -Summary $legacy -TestArea 'fixtures' } | Should -Not -Throw
}
}

Context "Test-PSPesterResults reads the summary next to the result file" {

It "throws for a failed discovery even though the NUnit file records no failure" {
$summary = Get-FixtureSummary -Name 'DiscoveryFails'
$summary | Should -Not -BeNullOrEmpty

$resultsFile = Join-Path $TestDrive 'DiscoveryFails.xml'
# The NUnit file is the reason this case used to pass: it records nothing.
([xml](Get-Content -Raw $resultsFile)).'test-results'.failures | Should -Be 0

{ Test-PSPesterResults -TestResultsFile $resultsFile -TestArea 'fixtures' } | Should -Throw
}

It "does not throw for a green run" {
$null = Get-FixtureSummary -Name 'AllGreen'
{ Test-PSPesterResults -TestResultsFile (Join-Path $TestDrive 'AllGreen.xml') -TestArea 'fixtures' } |
Should -Not -Throw
}
}

Context "The unelevated run keeps its console output" {

# Start-PSPester runs the unelevated child detached and tails a buffer file for its
# output. A redirection written after a pipeline binds to its last command only, so the
# pipeline has to be wrapped before '*>' or everything Pester prints is lost and CI shows
# a silent 16 minute gap. That happened, and nothing failed, hence this test.
It "wraps the pipeline so the redirection captures all of it" {
$command = Get-PSPesterRunCommand -Projection '| ForEach-Object { $_ }' `
-SummaryFile 'S.clixml' -BufferPath 'B.txt'

$command | Should -Match '^&\s*\{.*\}\s*\*>' -Because 'the redirection must apply to the whole pipeline'
$command | Should -Match '__UNELEVATED_TESTS_THE_END__'
}

It "does not redirect when there is no buffer, so the output streams back through the pipe" {
$command = Get-PSPesterRunCommand -Projection '| ForEach-Object { $_ }' -SummaryFile 'S.clixml'

$command | Should -Not -Match '\*>'
$command | Should -Not -Match '__UNELEVATED_TESTS_THE_END__'
}

It "actually captures host output and still writes the summary" {
$buffer = Join-Path $TestDrive 'buffer.txt'
$summary = Join-Path $TestDrive 'redirect-summary.clixml'

# Stands in for Invoke-Pester: writes to the host and emits one object.
$fake = '& { Write-Host "PESTER-CONSOLE-OUTPUT"; [pscustomobject]@{ Result = "Passed" } }'
$command = (Get-PSPesterRunCommand -Projection '| ForEach-Object { $_ }' `
-SummaryFile $summary -BufferPath $buffer) -replace
'Invoke-Pester -Configuration \$pesterConfig', $fake

& (Get-Process -Id $PID).Path -NoProfile -Command $command *> $null

(Get-Content $buffer -Raw) | Should -Match 'PESTER-CONSOLE-OUTPUT' -Because 'this is the whole point of the buffer file'
(Get-Content $buffer -Raw) | Should -Match '__UNELEVATED_TESTS_THE_END__'
(Import-Clixml $summary).Result | Should -BeExactly 'Passed'
}
}

Context "Get-PSPesterSummaryPath and Import-PSPesterSummary" {

It "puts the summary next to the result file" {
Get-PSPesterSummaryPath -TestResultsFile '/tmp/results.xml' | Should -BeExactly '/tmp/results.xml.summary.clixml'
}

It "returns null when there is no summary, so foreign result files still work" {
Import-PSPesterSummary -TestResultsFile (Join-Path $TestDrive 'does-not-exist.xml') | Should -BeNullOrEmpty
}
}
}
10 changes: 6 additions & 4 deletions test/powershell/Host/Base-Directory.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Describe "Configuration file locations" -tags "CI","Slow" {

Context "Default configuration file locations" {

BeforeDiscovery {
$ItArgs = @{}
}

BeforeAll {

if ($IsWindows) {
Expand All @@ -27,8 +31,6 @@ Describe "Configuration file locations" -tags "CI","Slow" {
$expectedProfile = [io.path]::Combine($env:HOME,".config","powershell",$profileName)
$expectedReadline = [IO.Path]::Combine($env:HOME, ".local", "share", "powershell", "PSReadLine", "ConsoleHost_history.txt")
}

$ItArgs = @{}
}

BeforeEach {
Expand Down Expand Up @@ -64,7 +66,7 @@ Describe "Configuration file locations" -tags "CI","Slow" {
}

Context "XDG Base Directory Specification is supported on Linux" {
BeforeAll {
BeforeDiscovery {
# Using It @ItArgs, we automatically skip on Windows for all these tests
if ($IsWindows) {
$ItArgs = @{ skip = $true }
Expand Down Expand Up @@ -130,7 +132,7 @@ Describe "Working directory on startup" -Tag "CI" {
}

# https://github.com/PowerShell/PowerShell/issues/5752
It "Can start in directory where name contains wildcard characters" -Pending {
It "Can start in directory where name contains wildcard characters" -Skip {
Set-Location -LiteralPath $testPath.FullName
if ($IsMacOS) {
# on macOS, /tmp is a symlink to /private so the real path is under /private/tmp
Expand Down
Loading
Loading