Skip to content

Commit f923523

Browse files
nohwndCopilot
andcommitted
Fix #2561: Escape control characters in assertion error messages
Format-String2 now escapes control characters as Unicode control pictures so they are visible in error messages. Characters like ESC (used in ANSI sequences) now display as their Unicode symbol instead of being invisible. Copilot-generated fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d0be71a commit f923523

2 files changed

Lines changed: 91 additions & 3 deletions

File tree

src/Format2.ps1

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
function Format-Collection2 ($Value, [switch]$Pretty) {
1+
# Control characters 0x00..0x1F that Format-String2 escapes to the Unicode
2+
# "Control Pictures" block so they are visible in error messages. Defined once
3+
# at module load time so the fast-path IndexOfAny check stays cheap.
4+
[char[]] $script:Format2ControlChars = foreach ($ch in 0..0x1F) { [char]$ch }
5+
6+
function Format-Collection2 ($Value, [switch]$Pretty) {
27
$length = 0
38
$o = foreach ($v in $Value) {
49
$formatted = Format-Nicely2 -Value $v -Pretty:$Pretty
@@ -46,11 +51,32 @@ function Format-Object2 ($Value, $Property, [switch]$Pretty) {
4651
}
4752

4853
function Format-String2 ($Value) {
49-
if ('' -eq $Value) {
54+
# Use .Length instead of '' -eq $Value because PowerShell's -eq operator
55+
# considers some control characters (NUL, BEL, BS, ESC) equal to empty string.
56+
if ($null -eq $Value -or $Value.Length -eq 0) {
5057
return '<empty>'
5158
}
5259

53-
"'$Value'"
60+
# Fast path: special chars are uncommon, so quote-and-return without rebuilding
61+
# the string when none are present. IndexOfAny is a single native scan.
62+
if (0 -gt $Value.IndexOfAny($script:Format2ControlChars)) {
63+
return "'$Value'"
64+
}
65+
66+
# Slow path: rebuild the string once. Convert to char[] (one O(n) C-side
67+
# copy), mutate in place, then re-create the string. Control chars in the
68+
# range 0x00..0x1F are mapped to the Unicode "Control Pictures" block
69+
# (U+2400..U+241F) by adding 0x2400, so they are visible in error messages
70+
# instead of being invisible or breaking output. See
71+
# https://github.com/pester/Pester/issues/2561
72+
$chars = [char[]] $Value
73+
$len = $chars.Length
74+
for ($i = 0; $i -lt $len; $i++) {
75+
if ([int] $chars[$i] -lt 0x20) {
76+
$chars[$i] = [char]([int] $chars[$i] + 0x2400)
77+
}
78+
}
79+
"'" + [string]::new($chars) + "'"
5480
}
5581

5682
function Format-Null2 {

tst/Format2.Tests.ps1

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,67 @@ InPesterModuleScope {
241241
It "Formats string to be sorrounded by quotes" {
242242
Format-String2 -Value "abc" | Verify-Equal "'abc'"
243243
}
244+
245+
# Regression tests for https://github.com/pester/Pester/issues/2561
246+
# Control characters must be escaped to Unicode control pictures so they are
247+
# visible in error messages instead of being invisible or breaking output.
248+
# Note: control picture chars (U+2400-U+241B) are written as literal Unicode in
249+
# single-quoted strings so the tests parse on PowerShell 5.1 too (`u{XXXX} is PS 6+).
250+
It "Escapes null character to control picture" {
251+
Format-String2 -Value "`0" | Verify-Equal "'␀'"
252+
}
253+
254+
It "Escapes bell character to control picture" {
255+
Format-String2 -Value "`a" | Verify-Equal "'␇'"
256+
}
257+
258+
It "Escapes backspace character to control picture" {
259+
Format-String2 -Value "`b" | Verify-Equal "'␈'"
260+
}
261+
262+
It "Escapes tab character to control picture" {
263+
Format-String2 -Value "`t" | Verify-Equal "'␉'"
264+
}
265+
266+
It "Escapes form feed character to control picture" {
267+
Format-String2 -Value "`f" | Verify-Equal "'␌'"
268+
}
269+
270+
It "Escapes carriage return character to control picture" {
271+
Format-String2 -Value "`r" | Verify-Equal "'␍'"
272+
}
273+
274+
It "Escapes newline character to control picture" {
275+
Format-String2 -Value "`n" | Verify-Equal "'␊'"
276+
}
277+
278+
It "Escapes ESC character to control picture" {
279+
Format-String2 -Value "$([char]27)" | Verify-Equal "'␛'"
280+
}
281+
282+
It "Leaves normal strings unchanged" {
283+
Format-String2 -Value "hello" | Verify-Equal "'hello'"
284+
}
285+
286+
It "Escapes ANSI sequence making escape char visible" {
287+
# ESC[31m is a common ANSI red color code; the ESC byte should become ␛
288+
$ansi = "$([char]27)[31m"
289+
$result = Format-String2 -Value $ansi
290+
$result | Verify-Equal "'␛[31m'"
291+
}
292+
293+
It "Escapes multiple control chars in one string" {
294+
$value = "a`t`nb"
295+
$result = Format-String2 -Value $value
296+
$result | Verify-Equal "'a␉␊b'"
297+
}
298+
299+
It "Escaped output contains no actual control characters" {
300+
# Round-trip: the escaped output should be a clean displayable string
301+
$value = "`0`a`b`t`f`r`n$([char]27)"
302+
$result = Format-String2 -Value $value
303+
# The result should not contain any of the original control characters
304+
$result | Should -Not -Match '[\x00-\x1F]'
305+
}
244306
}
245307
}

0 commit comments

Comments
 (0)