From 46ad27d3cc557f2b84ce30b2c4e27438526dc91d Mon Sep 17 00:00:00 2001 From: Chad Nedzlek Date: Fri, 30 Apr 2021 15:56:22 -0700 Subject: [PATCH] Unescape the XmlEscape method from XUnit (#7323) XUnit applies some custom escaping to this particular field. This code is the closest we can come to correctly reversing it so that it can render normally. It will produce incorrect results if a control character is followed by valid hex digits, but there is no way to avoid that, and it should be relatively rare for raw control characters to appear in error messages. --- .../azure-pipelines/reporter/formats/xunit.py | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/xunit.py b/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/xunit.py index d0d3f4025fc..0a5d09da677 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/xunit.py +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/xunit.py @@ -1,7 +1,31 @@ +import re import xml.etree.ElementTree + from .result_format import ResultFormat from defs import TestResult, TestResultAttachment +_unescape_char_map = { + 'r': '\r', + 'n': '\n', + 't': '\t', + '0': '\0', + 'a': '\a', + 'b': '\b', + 'v': '\v', + 'f': '\f', +} + +def _unescape_xunit_message(value): + # xunit does some escaping on the error message we need to do our + # best to turn back into something resembling the original message + # It only uses \x**, \x**** (indistinguishably), and then the items from __unescape_char_map + def bs(match): + grp = match.group(0) + sym = grp[1] + if sym == 'x': + return chr(int(grp[2:], 16)) + return _unescape_char_map.get(match[0][1]) or sym + return re.sub(r'\\x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]?[0-9a-fA-F]?|\\[^x]', bs, value) class XUnitFormat(ResultFormat): @@ -38,7 +62,7 @@ def read_results(self, path): exception_type = failure_element.get("exception-type") message_element = failure_element.find("message") if message_element is not None: - failure_message = message_element.text + failure_message = _unescape_xunit_message(message_element.text) stack_trace_element = failure_element.find("stack-trace") if stack_trace_element is not None: stack_trace = stack_trace_element.text