Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 25 additions & 13 deletions src/Format2.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function Format-Collection2 ($Value, [switch]$Pretty) {
function Format-Collection2 ($Value, [switch]$Pretty, [int]$Depth = 0) {
$length = 0
$o = foreach ($v in $Value) {
$formatted = Format-Nicely2 -Value $v -Pretty:$Pretty
$formatted = Format-Nicely2 -Value $v -Pretty:$Pretty -Depth ($Depth + 1)
$length += $formatted.Length + 1 # 1 is for the separator
$formatted
}
Expand All @@ -19,7 +19,7 @@
}
}

function Format-Object2 ($Value, $Property, [switch]$Pretty) {
function Format-Object2 ($Value, $Property, [switch]$Pretty, [int]$Depth = 0) {
if ($null -eq $Property) {
$Property = foreach ($p in $Value.PSObject.Properties) { $p.Name }
}
Expand All @@ -31,7 +31,7 @@ function Format-Object2 ($Value, $Property, [switch]$Pretty) {
$valueType = Get-ShortType $Value
$items = foreach ($p in $orderedProperty) {
$v = ([PSObject]$Value.$p)
$f = Format-Nicely2 -Value $v -Pretty:$Pretty
$f = Format-Nicely2 -Value $v -Pretty:$Pretty -Depth ($Depth + 1)
"$p=$f"
}

Expand Down Expand Up @@ -81,31 +81,31 @@ function Format-Number2 ($Value) {
[string]$Value
}

function Format-Hashtable2 ($Value) {
function Format-Hashtable2 ($Value, [int]$Depth = 0) {
$head = '@{'
$tail = '}'

$entries = foreach ($v in $Value.Keys | & $SafeCommands['Sort-Object']) {
$formattedValue = Format-Nicely2 $Value.$v
$formattedValue = Format-Nicely2 -Value $Value.$v -Depth ($Depth + 1)
"$v=$formattedValue"
}

$head + ( $entries -join '; ') + $tail
}

function Format-Dictionary2 ($Value) {
function Format-Dictionary2 ($Value, [int]$Depth = 0) {
$head = 'Dictionary{'
$tail = '}'

$entries = foreach ($v in $Value.Keys | & $SafeCommands['Sort-Object'] ) {
$formattedValue = Format-Nicely2 $Value.$v
$formattedValue = Format-Nicely2 -Value $Value.$v -Depth ($Depth + 1)
"$v=$formattedValue"
}

$head + ( $entries -join '; ') + $tail
}

function Format-Nicely2 ($Value, [switch]$Pretty) {
function Format-Nicely2 ($Value, [switch]$Pretty, [int]$Depth = 0) {
if ($null -eq $Value) {
return Format-Null2 -Value $Value
}
Expand All @@ -130,27 +130,39 @@ function Format-Nicely2 ($Value, [switch]$Pretty) {
return Format-ScriptBlock2 -Value $Value
}

# Deeply nested or self-referential objects (e.g. SMO stubs or DirectoryInfo, whose
# Parent/Root point back up the tree) would otherwise recurse until PowerShell throws
# "The script failed due to call depth overflow" (#2828, #2474). Once we are past a sane
# nesting depth stop expanding and just print the value's type, which is enough for a
# diagnostic message and cannot recurse further. Scalars above are always fully formatted;
# only the container/object branches below recurse, so the guard sits in front of them.
# A depth of 10 is never useful in an assertion message and is well below PowerShell's own
# call-depth limit, so it is fixed here rather than exposed as a configurable variable.
if ($Depth -ge 10) {
return Get-ShortType2 -Value $Value
}

if (Is-Collection -Value $Value) {
return Format-Collection2 -Value $Value -Pretty:$Pretty
return Format-Collection2 -Value $Value -Pretty:$Pretty -Depth $Depth
}

if (Is-Value -Value $Value) {
return $Value
}

if (Is-Hashtable -Value $Value) {
return Format-Hashtable2 -Value $Value
return Format-Hashtable2 -Value $Value -Depth $Depth
}

if (Is-Dictionary -Value $Value) {
return Format-Dictionary2 -Value $Value
return Format-Dictionary2 -Value $Value -Depth $Depth
}

if ((Is-DataTable -Value $Value) -or (Is-DataRow -Value $Value)) {
return Format-DataTable2 -Value $Value -Pretty:$Pretty
}

Format-Object2 -Value $Value -Property (Get-DisplayProperty2 $Value.GetType()) -Pretty:$Pretty
Format-Object2 -Value $Value -Property (Get-DisplayProperty2 $Value.GetType()) -Pretty:$Pretty -Depth $Depth
}

function Format-NicelyForTemplate ($Value) {
Expand Down
24 changes: 24 additions & 0 deletions tst/Format2.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,30 @@ InPesterModuleScope {
$job | Remove-Job -Force
$result | Should -Not -BeNullOrEmpty
}

# Regression test for https://github.com/pester/Pester/issues/2828
# A self-referential object (Self points back at the same instance, like SMO stubs or
# DirectoryInfo.Parent/Root) used to recurse until PowerShell threw a call depth overflow.
It "Stops expanding a self-referential object instead of overflowing the call stack" {
$o = [PSCustomObject]@{ Name = 'x' }
$o | Add-Member -NotePropertyName Self -NotePropertyValue $o

# Formatting completes at all (no ScriptCallDepthException) and the back-reference is
# cut off with a type-only marker once the max depth is reached, not expanded forever.
$formatted = Format-Nicely2 -Value $o
$formatted.Contains('Self=[PSObject]') | Verify-True
}

It "Truncates values nested past the max depth to their type" {
# Build a chain deeper than the max depth. The leaf sits below the cut-off, so it must
# never be reached, and the deepest shown value is a type-only marker instead.
$node = [PSCustomObject]@{ Leaf = 'bottom' }
foreach ($i in 1..20) { $node = [PSCustomObject]@{ Child = $node } }

$formatted = Format-Nicely2 -Value $node
$formatted.Contains("'bottom'") | Verify-False
$formatted.Contains('[PSObject]') | Verify-True
}
}

Describe "Get-DisplayProperty2" {
Expand Down
11 changes: 11 additions & 0 deletions tst/functions/assert/General/Should-HaveType.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ Describe "Should-HaveType" {
) {
Should-HaveType -Actual $Value -Expected ([string[]])
}

# Regression test for https://github.com/pester/Pester/issues/2828
# Formatting a self-referential actual value for the failure message used to recurse until
# PowerShell threw "The script failed due to call depth overflow", hiding the real result.
It "Reports a normal assertion failure for a self-referential value instead of overflowing" {
$o = [PSCustomObject]@{ Name = 'x' }
$o | Add-Member -NotePropertyName Self -NotePropertyValue $o

$err = { Should-HaveType -Actual $o -Expected ([string]) } | Verify-AssertionFailed
$err.Exception.Message | Verify-Like '*Expected value to have type*'
}
}

Describe "Should-HaveType input hint" {
Expand Down
Loading