From faa85d6c83891919946f1b66f2faba0fcf420c9e Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 11 Dec 2025 13:51:02 -0800 Subject: [PATCH 1/2] Add Reason to block and test results (#2049) This adds a reason property onto Block and Test results. Currently if you Skip a block using `-Skip` you cannot provide a reason for your skip. While if you use `Set-ItResult` to skip a test you're able to provide a reason for the result you're setting. Unfortunately with a Skip result, that reason is not bubbled up to the test results files. --- src/Pester.Runtime.ps1 | 22 ++++++++++++++++------ src/csharp/Pester/Block.cs | 1 + src/csharp/Pester/Test.cs | 1 + src/functions/Context.ps1 | 5 +++-- src/functions/Describe.ps1 | 5 +++-- src/functions/It.ps1 | 10 +++++----- src/functions/Set-ItResult.ps1 | 3 +-- src/functions/TestResults.NUnit25.ps1 | 10 ++++------ 8 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Pester.Runtime.ps1 b/src/Pester.Runtime.ps1 index 40ef93a56..85a579908 100644 --- a/src/Pester.Runtime.ps1 +++ b/src/Pester.Runtime.ps1 @@ -166,6 +166,7 @@ function New-ParametrizedBlock { [HashTable] $FrameworkData = @{ }, [Switch] $Focus, [Switch] $Skip, + [String] $Reason, $Data ) @@ -176,7 +177,7 @@ function New-ParametrizedBlock { foreach ($d in @($Data)) { # shallow clone to give every block it's own copy $fmwData = $FrameworkData.Clone() - New-Block -GroupId $groupId -Name $Name -ScriptBlock $ScriptBlock -StartLine $StartLine -Tag $Tag -FrameworkData $fmwData -Focus:$Focus -Skip:$Skip -Data $d + New-Block -GroupId $groupId -Name $Name -ScriptBlock $ScriptBlock -StartLine $StartLine -Tag $Tag -FrameworkData $fmwData -Focus:$Focus -Skip:$Skip -Reason:$Reason -Data $d } } @@ -194,6 +195,7 @@ function New-Block { [Switch] $Focus, [String] $GroupId, [Switch] $Skip, + [String] $Reason, $Data ) @@ -232,6 +234,7 @@ function New-Block { $block.Focus = $Focus $block.GroupId = $GroupId $block.Skip = $Skip + $block.Reason = $Reason $block.Data = $Data # we attach the current block to the parent, and put it to the parent @@ -479,7 +482,8 @@ function New-Test { $Data, [String] $GroupId, [Switch] $Focus, - [Switch] $Skip + [Switch] $Skip, + [String] $Reason ) if ($PesterPreference.Debug.WriteDebugMessages.Value) { @@ -513,6 +517,7 @@ function New-Test { $test.Tag = $Tag $test.Focus = $Focus $test.Skip = $Skip + $test.Reason = $Reason $test.Data = $Data $test.FrameworkData.Runtime.Phase = 'Discovery' @@ -687,6 +692,7 @@ function Invoke-TestItem { } else { $Test.Skipped = $true + $Test.Reason = $result.ErrorRecord.Exception.Message } } else { @@ -2120,6 +2126,7 @@ function PostProcess-DiscoveredBlock { } $t.Skip = $true + $t.Reason = $b.Reason } } } @@ -2186,12 +2193,14 @@ function PostProcess-DiscoveredBlock { if ($PesterPreference.Debug.WriteDebugMessages.Value) { if ($b.IsRoot) { Write-PesterDebugMessage -Scope Skip "($($b.BlockContainer)) Container will be skipped because all included children are marked as skipped." - } else { + } + else { Write-PesterDebugMessage -Scope Skip "($($b.Path -join '.')) Block will be skipped because all included children are marked as skipped." } } $b.Skip = $true - } elseif ($b.Skip -and -not $shouldSkipBasedOnChildren) { + } + elseif ($b.Skip -and -not $shouldSkipBasedOnChildren) { if ($PesterPreference.Debug.WriteDebugMessages.Value) { Write-PesterDebugMessage -Scope Skip "($($b.Path -join '.')) Block was marked as skipped, but one or more children are explicitly requested to be run, so the block itself will not be skipped." } @@ -2544,14 +2553,15 @@ function New-ParametrizedTest () { # do not use [hashtable[]] because that throws away the order if user uses [ordered] hashtable [object[]] $Data, [Switch] $Focus, - [Switch] $Skip + [Switch] $Skip, + [String] $Reason ) # using the position of It as Id for the the test so we can join multiple testcases together, this should be unique enough because it only needs to be unique for the current block. # TODO: Id is used by NUnit2.5 and 3 testresults to group. A better way to solve this? $groupId = "${StartLine}:${StartColumn}" foreach ($d in $Data) { - New-Test -GroupId $groupId -Name $Name -Tag $Tag -ScriptBlock $ScriptBlock -StartLine $StartLine -Data $d -Focus:$Focus -Skip:$Skip + New-Test -GroupId $groupId -Name $Name -Tag $Tag -ScriptBlock $ScriptBlock -StartLine $StartLine -Data $d -Focus:$Focus -Skip:$Skip -Reason:$Reason } } diff --git a/src/csharp/Pester/Block.cs b/src/csharp/Pester/Block.cs index 3146107d8..7103c70ee 100644 --- a/src/csharp/Pester/Block.cs +++ b/src/csharp/Pester/Block.cs @@ -46,6 +46,7 @@ public Block() public List Tag { get; set; } public bool Focus { get; set; } public bool Skip { get; set; } + public string Reason { get; set; } public string ItemType { get; } = "Block"; diff --git a/src/csharp/Pester/Test.cs b/src/csharp/Pester/Test.cs index 8b7952a09..c3e7d20c6 100644 --- a/src/csharp/Pester/Test.cs +++ b/src/csharp/Pester/Test.cs @@ -48,6 +48,7 @@ public Test() public List Tag { get; set; } public bool Focus { get; set; } public bool Skip { get; set; } + public string Reason { get; set; } // IDictionary to allow users use [ordered] public object Block { get; set; } diff --git a/src/functions/Context.ps1 b/src/functions/Context.ps1 index 6f6e506a2..ec16f4f71 100644 --- a/src/functions/Context.ps1 +++ b/src/functions/Context.ps1 @@ -89,6 +89,7 @@ # [Switch] $Focus, [Switch] $Skip, + [String] $Reason, [Switch] $AllowNullOrEmptyForEach, [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', '', Justification = 'ForEach is not used in Foreach-Object loop')] @@ -122,10 +123,10 @@ return } - New-ParametrizedBlock -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -StartColumn $MyInvocation.OffsetInLine -Tag $Tag -FrameworkData @{ CommandUsed = 'Context'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip -Data $ForEach + New-ParametrizedBlock -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -StartColumn $MyInvocation.OffsetInLine -Tag $Tag -FrameworkData @{ CommandUsed = 'Context'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip -Reason:$Reason -Data $ForEach } else { - New-Block -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -Tag $Tag -FrameworkData @{ CommandUsed = 'Context'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip + New-Block -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -Tag $Tag -FrameworkData @{ CommandUsed = 'Context'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip -Reason:$Reason } } else { diff --git a/src/functions/Describe.ps1 b/src/functions/Describe.ps1 index d46bb190c..ff97a7a79 100644 --- a/src/functions/Describe.ps1 +++ b/src/functions/Describe.ps1 @@ -97,6 +97,7 @@ # [Switch] $Focus, [Switch] $Skip, + [String] $Reason, [Switch] $AllowNullOrEmptyForEach, [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', '', Justification = 'ForEach is not used in Foreach-Object loop')] @@ -130,10 +131,10 @@ return } - New-ParametrizedBlock -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -StartColumn $MyInvocation.OffsetInLine -Tag $Tag -FrameworkData @{ CommandUsed = 'Describe'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip -Data $ForEach + New-ParametrizedBlock -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -StartColumn $MyInvocation.OffsetInLine -Tag $Tag -FrameworkData @{ CommandUsed = 'Describe'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip -Reason:$Reason -Data $ForEach } else { - New-Block -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -Tag $Tag -FrameworkData @{ CommandUsed = 'Describe'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip + New-Block -Name $Name -ScriptBlock $Fixture -StartLine $MyInvocation.ScriptLineNumber -Tag $Tag -FrameworkData @{ CommandUsed = 'Describe'; WrittenToScreen = $false } -Focus:$Focus -Skip:$Skip -Reason:$Reason } } else { diff --git a/src/functions/It.ps1 b/src/functions/It.ps1 index 1efa922f1..735e3d69c 100644 --- a/src/functions/It.ps1 +++ b/src/functions/It.ps1 @@ -133,10 +133,10 @@ [Parameter(ParameterSetName = 'Skip')] [Switch] $Skip, - [Switch] $AllowNullOrEmptyForEach + [Switch] $AllowNullOrEmptyForEach, - # [Parameter(ParameterSetName = 'Skip')] - # [String] $SkipBecause, + [Parameter(ParameterSetName = 'Skip')] + [String] $Reason # [Switch]$Focus ) @@ -163,9 +163,9 @@ return } - New-ParametrizedTest -Name $Name -ScriptBlock $Test -StartLine $MyInvocation.ScriptLineNumber -StartColumn $MyInvocation.OffsetInLine -Data $ForEach -Tag $Tag -Focus:$Focus -Skip:$Skip + New-ParametrizedTest -Name $Name -ScriptBlock $Test -StartLine $MyInvocation.ScriptLineNumber -StartColumn $MyInvocation.OffsetInLine -Data $ForEach -Tag $Tag -Focus:$Focus -Skip:$Skip -Reason:$Reason } else { - New-Test -Name $Name -ScriptBlock $Test -StartLine $MyInvocation.ScriptLineNumber -Tag $Tag -Focus:$Focus -Skip:$Skip + New-Test -Name $Name -ScriptBlock $Test -StartLine $MyInvocation.ScriptLineNumber -Tag $Tag -Focus:$Focus -Skip:$Skip -Reason:$Reason } } diff --git a/src/functions/Set-ItResult.ps1 b/src/functions/Set-ItResult.ps1 index d7f2cf672..7485fde8f 100644 --- a/src/functions/Set-ItResult.ps1 +++ b/src/functions/Set-ItResult.ps1 @@ -81,8 +81,7 @@ } if ($Because) { - [String]$formatted = Format-Because $Because - [String]$message += ",$($formatted.SubString(0, $formatted.Length - 1))" + [String]$message = $Because } throw [Pester.Factory]::CreateErrorRecord( diff --git a/src/functions/TestResults.NUnit25.ps1 b/src/functions/TestResults.NUnit25.ps1 index a0b8f74ff..5f731aebf 100644 --- a/src/functions/TestResults.NUnit25.ps1 +++ b/src/functions/TestResults.NUnit25.ps1 @@ -317,10 +317,9 @@ function Write-NUnitTestCaseAttributes { $XmlWriter.WriteAttributeString('result', 'Ignored') $XmlWriter.WriteAttributeString('executed', 'False') - # TODO: This doesn't work, FailureMessage comes from Get-ErrorForXmlReport which isn't called - if ($TestResult.FailureMessage) { + if ($TestResult.Reason) { $XmlWriter.WriteStartElement('reason') - $xmlWriter.WriteElementString('message', $TestResult.FailureMessage) + $xmlWriter.WriteElementString('message', $TestResult.Reason) $XmlWriter.WriteEndElement() # Close reason tag } @@ -331,10 +330,9 @@ function Write-NUnitTestCaseAttributes { $XmlWriter.WriteAttributeString('result', 'Inconclusive') $XmlWriter.WriteAttributeString('executed', 'True') - # TODO: This doesn't work, FailureMessage comes from Get-ErrorForXmlReport which isn't called - if ($TestResult.FailureMessage) { + if ($TestResult.Reason) { $XmlWriter.WriteStartElement('reason') - $xmlWriter.WriteElementString('message', $TestResult.DisplayErrorMessage) + $xmlWriter.WriteElementString('message', $TestResult.Reason) $XmlWriter.WriteEndElement() # Close reason tag } From 7194fd964660baac308740d8515c5fc052f5ca7c Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Thu, 11 Dec 2025 14:36:24 -0800 Subject: [PATCH 2/2] (TESTS) Add some tests for proving out the change This add some tests for what was run manually for testing this out. It is expected that this commit will not be included when the PR is merged, but it is valuable for getting to the point of being able to merge. --- 2049-testing/2049.tests.ps1 | 68 +++++++++++++++++++++++++++++++++++++ 2049-testing/mytests.ps1 | 11 ++++++ 2 files changed, 79 insertions(+) create mode 100644 2049-testing/2049.tests.ps1 create mode 100644 2049-testing/mytests.ps1 diff --git a/2049-testing/2049.tests.ps1 b/2049-testing/2049.tests.ps1 new file mode 100644 index 000000000..483a53988 --- /dev/null +++ b/2049-testing/2049.tests.ps1 @@ -0,0 +1,68 @@ + +Describe 'All The Tests' { + Context 'Reasons' { + It 'Skips...' { + Set-ItResult -Skipped -Because 'I am skipped' + } + It 'Does not skip' { + $true | Should -BeTrue + } + It 'is Inconclusive' { + Set-ItResult -Inconclusive -Because 'I am inconclusive!' + } + It 'is Failed!' { + $true | Should -BeFalse -Because 'I am failed test' + } + } + + Context 'No Reasons' { + It 'Skips...' { + Set-ItResult -Skipped + } + It 'Does not skip' { + $true | Should -BeTrue + } + It 'is Inconclusive' { + Set-ItResult -Inconclusive + } + It 'is Failed!' { + $true | Should -BeFalse + } + } + + Context 'It Reasons' { + It 'Skips' -Skip -Reason 'I am Skipped' { + $true | Should -BeTrue + } + } + + Context 'It No Reasons' { + It 'Skips' -Skip { + $true | Should -BeTrue + } + } + + Context 'Context Reasons' -Skip -Reason 'I am Skipped' { + It 'Skips' { + $true | Should -BeTrue + } + } + + Context 'Context No Reasons' -Skip { + It 'Skips' { + $true | Should -BeTrue + } + } + + Describe 'Describe Reasons' -Skip -Reason 'I am Skipped' { + It 'Skips' { + $true | Should -BeTrue + } + } + + Describe 'Describe No Reasons' -Skip { + It 'Skips' { + $true | Should -BeTrue + } + } +} diff --git a/2049-testing/mytests.ps1 b/2049-testing/mytests.ps1 new file mode 100644 index 000000000..24447651b --- /dev/null +++ b/2049-testing/mytests.ps1 @@ -0,0 +1,11 @@ +$pesterConfig = [PesterConfiguration]::Default +$pesterConfig.TestResult.Enabled = $true +$pesterConfig.Run.Path = '.\2049.tests.ps1' +$pesterConfig.Run.PassThru = $true +$pesterConfig.Debug.WriteDebugMessages = $true + +foreach ($fmt in 'NUnitXml NUnit2.5 NUnit3 JUnitXml'.split(' ')) { + $pesterConfig.TestResult.OutputFormat = $fmt + $pesterConfig.TestResult.OutputPath = "results.$fmt.xml" + Invoke-Pester -Configuration $pesterConfig +}