diff --git a/src/Format2.ps1 b/src/Format2.ps1 index 38348fb0d..202df6d8a 100644 --- a/src/Format2.ps1 +++ b/src/Format2.ps1 @@ -46,10 +46,25 @@ function Format-Object2 ($Value, $Property, [switch]$Pretty) { } function Format-String2 ($Value) { - if ('' -eq $Value) { + # Use .Length instead of '' -eq $Value because PowerShell's -eq operator + # considers some control characters (NUL, BEL, BS, ESC) equal to empty string. + if ($null -eq $Value -or $Value.Length -eq 0) { return '' } + # Escape control characters so they are visible in error messages. + # Without this, chars like ESC (0x1B used in ANSI sequences) are invisible + # and make error output confusing. See https://github.com/pester/Pester/issues/2561 + $Value = $Value ` + -replace "`0", '␀' ` + -replace "`a", '␇' ` + -replace "`b", '␈' ` + -replace "`t", '␉' ` + -replace "`f", '␌' ` + -replace "`r", '␍' ` + -replace "`n", '␊' ` + -replace "`e", '␛' + "'$Value'" } diff --git a/tst/Format2.Tests.ps1 b/tst/Format2.Tests.ps1 index ecb4b9ee2..487a46bc4 100644 --- a/tst/Format2.Tests.ps1 +++ b/tst/Format2.Tests.ps1 @@ -212,5 +212,65 @@ InPesterModuleScope { It "Formats string to be sorrounded by quotes" { Format-String2 -Value "abc" | Verify-Equal "'abc'" } + + # Regression tests for https://github.com/pester/Pester/issues/2561 + # Control characters must be escaped to Unicode control pictures so they are + # visible in error messages instead of being invisible or breaking output. + It "Escapes null character to control picture" { + Format-String2 -Value "`0" | Verify-Equal "'`u{2400}'" + } + + It "Escapes bell character to control picture" { + Format-String2 -Value "`a" | Verify-Equal "'`u{2407}'" + } + + It "Escapes backspace character to control picture" { + Format-String2 -Value "`b" | Verify-Equal "'`u{2408}'" + } + + It "Escapes tab character to control picture" { + Format-String2 -Value "`t" | Verify-Equal "'`u{2409}'" + } + + It "Escapes form feed character to control picture" { + Format-String2 -Value "`f" | Verify-Equal "'`u{240C}'" + } + + It "Escapes carriage return character to control picture" { + Format-String2 -Value "`r" | Verify-Equal "'`u{240D}'" + } + + It "Escapes newline character to control picture" { + Format-String2 -Value "`n" | Verify-Equal "'`u{240A}'" + } + + It "Escapes ESC character to control picture" { + Format-String2 -Value "$([char]27)" | Verify-Equal "'`u{241B}'" + } + + It "Leaves normal strings unchanged" { + Format-String2 -Value "hello" | Verify-Equal "'hello'" + } + + It "Escapes ANSI sequence making escape char visible" { + # ESC[31m is a common ANSI red color code; the ESC byte should become ␛ + $ansi = "$([char]27)[31m" + $result = Format-String2 -Value $ansi + $result | Verify-Equal "'`u{241B}[31m'" + } + + It "Escapes multiple control chars in one string" { + $value = "a`t`nb" + $result = Format-String2 -Value $value + $result | Verify-Equal "'a`u{2409}`u{240A}b'" + } + + It "Escaped output contains no actual control characters" { + # Round-trip: the escaped output should be a clean displayable string + $value = "`0`a`b`t`f`r`n$([char]27)" + $result = Format-String2 -Value $value + # The result should not contain any of the original control characters + $result | Should -Not -Match '[\x00-\x1F]' + } } }