diff --git a/src/functions/TestResults.NUnit3.ps1 b/src/functions/TestResults.NUnit3.ps1 index 687e20e00..75db6f9cc 100644 --- a/src/functions/TestResults.NUnit3.ps1 +++ b/src/functions/TestResults.NUnit3.ps1 @@ -1,5 +1,7 @@ # NUnit3 schema docs: https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html +[char[]] $script:invalidCDataChars = foreach ($ch in (0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F)) { [char]$ch } + function Write-NUnit3Report([Pester.Run] $Result, [System.Xml.XmlWriter] $XmlWriter) { # Write the XML Declaration $XmlWriter.WriteStartDocument($false) @@ -537,14 +539,39 @@ function Write-NUnit3TestCaseAttributes { } function Write-NUnit3OutputElement ($Output, [System.Xml.XmlWriter] $XmlWriter) { - $outputString = @(foreach ($o in $Output) { - if ($null -eq $o) { - [string]::Empty - } else { - $o.ToString() + # The characters in the range 0x01 to 0x20 are invalid for CData + # (with the exception of the characters 0x09, 0x0A and 0x0D) + # We convert each of these using the unicode printable version, + # which is obtained by adding 0x2400 + [int]$unicodeControlPictures = 0x2400 + + # Avoid indexing into an enumerable, such as a `string`, when there is only one item in the + # output array. + $out = @($Output) + $linesCount = $out.Length + $o = for ($i = 0; $i -lt $linesCount; $i++) { + # The input is array of objects, convert them to strings. + $line = if ($null -eq $out[$i]) { [String]::Empty } else { $out[$i].ToString() } + + if (0 -gt $line.IndexOfAny($script:invalidCDataChars)) { + # No special chars that need replacing. + $line + } + else { + $chars = [char[]]$line; + $charCount = $chars.Length + for ($j = 0; $j -lt $charCount; $j++) { + $char = $chars[$j] + if ($char -in $script:invalidCDataChars) { + $chars[$j] = [char]([int]$char + $unicodeControlPictures) + } + } + + $chars -join '' } - }) -join [System.Environment]::NewLine + } + $outputString = $o -join [Environment]::NewLine $XmlWriter.WriteStartElement('output') $XmlWriter.WriteCData($outputString) $XmlWriter.WriteEndElement() diff --git a/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 b/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 index 27dbb5951..d000a4e4e 100644 --- a/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 +++ b/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 @@ -349,6 +349,34 @@ i -PassThru:$PassThru { $xmlResult.Validate({ throw $args[1].Exception }) } + t 'replaces virtual terminal escape sequences with their printable representations' { + $sb = { + Describe 'Describe VT Sequences' { + It "Successful" { + $esc = [char][int]0x1B + $bell = [char][int]0x07 + + # write escape sequences to output + "$esc[32mHello`tWorld$esc[0m" + "Ring the bell$bell" + } + } + } + $r = Invoke-Pester -Configuration ([PesterConfiguration]@{ Run = @{ ScriptBlock = $sb; PassThru = $true }; Output = @{ Verbosity = 'None' } }) + + $xmlResult = [xml] ($r | ConvertTo-NUnitReport -Format NUnit3) + $xmlResult.Schemas.XmlResolver = New-Object System.Xml.XmlUrlResolver + $xmlResult.Schemas.Add($null, $schemaPath) > $null + $xmlResult.Validate({ throw $args[1].Exception }) + $xmlDescribe = $xmlResult.'test-run'.'test-suite'.'test-suite' + $xmlTest = $xmlDescribe.'test-case' + $message = $xmlTest.output.'#cdata-section' -split "`n" + + # message has the escape sequences replaced with their printable representations + $message[0] | Verify-Equal "␛[32mHello`tWorld␛[0m" + $message[1] | Verify-Equal "Ring the bell␇" + } + t 'should use TestResult.TestSuiteName configuration value as name-attribute for run and root Assembly test-suite' { $sb = { Describe 'Describe' {