From 2842cfe701f7639248c448457f88328f9a64efde Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 16 Jun 2026 18:51:23 +0200 Subject: [PATCH] Fix #2571: Expand data-driven block name before BeforeAll execution Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Pester.Runtime.ps1 | 47 +++++++++++++++++++--------------- src/functions/Output.ps1 | 5 ++-- tst/Pester.RSpec.Output.ts.ps1 | 27 +++++++++++++++++++ tst/Pester.RSpec.ts.ps1 | 26 ++++++++++++++++--- 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/src/Pester.Runtime.ps1 b/src/Pester.Runtime.ps1 index 4055085dd..d62635bcb 100644 --- a/src/Pester.Runtime.ps1 +++ b/src/Pester.Runtime.ps1 @@ -377,32 +377,37 @@ function Invoke-Block ($previousBlock) { $sessionStateInternal = $script:ScriptBlockSessionStateInternalProperty.GetValue($block.ScriptBlock, $null) $script:ScriptBlockSessionStateInternalProperty.SetValue($sb, $SessionStateInternal) + $expandCurrentBlockName = if (-not $Block.IsRoot) { + # expand block name by evaluating the <> templates, only match templates that have at least 1 character and are not escaped by ` + # avoid using variables so we don't run into conflicts + $sb = { + + $____Pester.CurrentBlock.ExpandedName = if ($____Pester.CurrentBlock.Name -like "*<*") { & ([ScriptBlock]::Create(('"' + ($____Pester.CurrentBlock.Name -replace '\$', '`$' -replace '"', '`"' -replace '(?^`]+)>', '$$($$$1)') + '"'))) } else { $____Pester.CurrentBlock.Name } + + $____Pester.CurrentBlock.ExpandedPath = if ($____Pester.CurrentBlock.Parent.IsRoot) { + # to avoid including Root name in the path + $____Pester.CurrentBlock.ExpandedName + } + else { + "$($____Pester.CurrentBlock.Parent.ExpandedPath).$($____Pester.CurrentBlock.ExpandedName)" + } + } + + $SessionStateInternal = $script:ScriptBlockSessionStateInternalProperty.GetValue($State.CurrentBlock.ScriptBlock, $null) + $script:ScriptBlockSessionStateInternalProperty.SetValue($sb, $SessionStateInternal) + + $sb + } + $result = Invoke-ScriptBlock ` -ScriptBlock $sb ` -OuterSetup @( $(if (-not (Is-Discovery) -and (-not $Block.Skip)) { - @($previousBlock.EachBlockSetup) + @($block.OneTimeTestSetup) + @($previousBlock.EachBlockSetup) }) - $(if (-not $Block.IsRoot) { - # expand block name by evaluating the <> templates, only match templates that have at least 1 character and are not escaped by ` - # avoid using variables so we don't run into conflicts - $sb = { - - $____Pester.CurrentBlock.ExpandedName = if ($____Pester.CurrentBlock.Name -like "*<*") { & ([ScriptBlock]::Create(('"' + ($____Pester.CurrentBlock.Name -replace '\$', '`$' -replace '"', '`"' -replace '(?^`]+)>', '$$($$$1)') + '"'))) } else { $____Pester.CurrentBlock.Name } - - $____Pester.CurrentBlock.ExpandedPath = if ($____Pester.CurrentBlock.Parent.IsRoot) { - # to avoid including Root name in the path - $____Pester.CurrentBlock.ExpandedName - } - else { - "$($____Pester.CurrentBlock.Parent.ExpandedPath).$($____Pester.CurrentBlock.ExpandedName)" - } - } - - $SessionStateInternal = $script:ScriptBlockSessionStateInternalProperty.GetValue($State.CurrentBlock.ScriptBlock, $null) - $script:ScriptBlockSessionStateInternalProperty.SetValue($sb, $SessionStateInternal) - - $sb + $expandCurrentBlockName + $(if (-not (Is-Discovery) -and (-not $Block.Skip)) { + @($block.OneTimeTestSetup) }) ) ` -OuterTeardown $( if (-not (Is-Discovery) -and (-not $Block.Skip)) { diff --git a/src/functions/Output.ps1 b/src/functions/Output.ps1 index cb88ae6af..d600d5b5d 100644 --- a/src/functions/Output.ps1 +++ b/src/functions/Output.ps1 @@ -298,7 +298,7 @@ function Write-PesterReport { if (0 -lt $RunResult.FailedBlocksCount) { Write-PesterHostMessage ('BeforeAll \ AfterAll failed: {0}' -f $RunResult.FailedBlocksCount) -Foreground $ReportTheme.Fail - Write-PesterHostMessage ($(foreach ($b in $RunResult.FailedBlocks) { " - $($b.Path -join '.')" }) -join [Environment]::NewLine) -Foreground $ReportTheme.Fail + Write-PesterHostMessage ($(foreach ($b in $RunResult.FailedBlocks) { " - $(if (-not [string]::IsNullOrWhiteSpace($b.ExpandedPath)) { $b.ExpandedPath } else { $b.Path -join '.' })" }) -join [Environment]::NewLine) -Foreground $ReportTheme.Fail } if (0 -lt $RunResult.FailedContainersCount) { @@ -773,7 +773,8 @@ function Get-WriteScreenPlugin ($Verbosity) { foreach ($e in $Context.Block.ErrorRecord) { ConvertTo-FailureLines $e } - $errorHeader = "[-] $($Context.Block.FrameworkData.CommandUsed) $($Context.Block.Path -join ".") failed" + $blockPath = if (-not [string]::IsNullOrWhiteSpace($Context.Block.ExpandedPath)) { $Context.Block.ExpandedPath } else { $Context.Block.Path -join "." } + $errorHeader = "[-] $($Context.Block.FrameworkData.CommandUsed) $blockPath failed" $formatErrorParams = @{ Err = $Context.Block.ErrorRecord diff --git a/tst/Pester.RSpec.Output.ts.ps1 b/tst/Pester.RSpec.Output.ts.ps1 index e3727ea34..dc44c585c 100644 --- a/tst/Pester.RSpec.Output.ts.ps1 +++ b/tst/Pester.RSpec.Output.ts.ps1 @@ -248,6 +248,33 @@ i -PassThru:$PassThru { @($describe1).Count | Verify-Equal 1 @($describe2).Count | Verify-Equal 1 } + + t 'BeforeAll failures use expanded names for each data item' { + $sb = { + $PesterPreference = [PesterConfiguration]::Default + $PesterPreference.Output.Verbosity = 'Detailed' + $PesterPreference.Output.RenderMode = 'ConsoleColor' + + $container = New-PesterContainer -ScriptBlock { + Describe 'Test <_>' -ForEach @('a', 'b') { + BeforeAll { throw 'something' } + It 'is a or b' { + $_ | Should -Match '[ab]' + } + } + } + Invoke-Pester -Container $container + } + + $output = Invoke-InNewProcess $sb + $null, $run = $output -join "`n" -split 'Running tests.' + $run | Write-Host + + @($output | Select-String -Pattern '\[-\] Describe Test a failed\s*$').Count | Verify-Equal 1 + @($output | Select-String -Pattern '\[-\] Describe Test b failed\s*$').Count | Verify-Equal 1 + @($output | Select-String -Pattern ' - Test a\s*$').Count | Verify-Equal 1 + @($output | Select-String -Pattern ' - Test b\s*$').Count | Verify-Equal 1 + } } b 'Output for container names' { diff --git a/tst/Pester.RSpec.ts.ps1 b/tst/Pester.RSpec.ts.ps1 index 6cea4cf90..305220633 100644 --- a/tst/Pester.RSpec.ts.ps1 +++ b/tst/Pester.RSpec.ts.ps1 @@ -2076,9 +2076,29 @@ i -PassThru:$PassThru { $container = New-PesterContainer -ScriptBlock $sb $r = Invoke-Pester -Container $container -PassThru $r.Containers[0].Blocks[0].Blocks[0].Result | Verify-Equal 'Failed' - $r.Containers[0].Blocks[0].Blocks[0].ExpandedName | Verify-Equal 'd2 <_>' - # ExpandedPath is updated as far as possible (parent block) before failure in block - $r.Containers[0].Blocks[0].Blocks[0].ExpandedPath | Verify-Equal 'd 1.d2 <_>' + $r.Containers[0].Blocks[0].Blocks[0].ExpandedName | Verify-Equal 'd2 1' + $r.Containers[0].Blocks[0].Blocks[0].ExpandedPath | Verify-Equal 'd 1.d2 1' + } + + t "Data-driven Describe names are expanded before BeforeAll throws" { + $sb = { + Describe 'Test <_>' -ForEach @('a', 'b') { + BeforeAll { throw 'something' } + It 'is a or b' { + $_ | Should -Match '[ab]' + } + } + } + + $r = Invoke-Pester -Configuration ([PesterConfiguration]@{ + Run = @{ ScriptBlock = $sb; PassThru = $true } + Output = @{ Verbosity = 'Detailed' } + }) + + $r.Containers[0].Blocks[0].ExpandedName | Verify-Equal 'Test a' + $r.Containers[0].Blocks[0].ExpandedPath | Verify-Equal 'Test a' + $r.Containers[0].Blocks[1].ExpandedName | Verify-Equal 'Test b' + $r.Containers[0].Blocks[1].ExpandedPath | Verify-Equal 'Test b' } t "ExpandedPath is expanded for parent blocks when test is skipped or fails in BeforeEach" {