Don't repeat the failure summary in the XML report body

The XML printer puts the summary of a test part result into the "message"
attribute of the <failure> or <skipped> element and the full message into
the CDATA body of that same element. The two only differ when the message
carries a stack trace, so in a build without stack trace support, and for
every skipped result since those never get a trace, the report contains
the exact same text twice.

Write the CDATA body only when it holds more than the attribute already
does. The "message" attribute keeps the summary, which is what JUnit
consumers read, and nothing is dropped from the report. The JSON printer
already reports the message just once.

Fixes #4785
This commit is contained in:
Nithin 2026-08-19 23:02:54 +05:30
parent 91c99b6ffe
commit 24ee021f44
No known key found for this signature in database
3 changed files with 86 additions and 29 deletions

View File

@ -2173,8 +2173,8 @@ could generate this report:
<testsuites tests="3" failures="1" errors="0" time="0.035" timestamp="2011-10-31T18:52:42" name="AllTests">
<testsuite name="MathTest" tests="2" failures="1" errors="0" time="0.015">
<testcase name="Addition" file="test.cpp" line="1" status="run" time="0.007" classname="">
<failure message="Value of: add(1, 1)&#x0A; Actual: 3&#x0A;Expected: 2" type="">...</failure>
<failure message="Value of: add(1, -1)&#x0A; Actual: 1&#x0A;Expected: 0" type="">...</failure>
<failure message="Value of: add(1, 1)&#x0A; Actual: 3&#x0A;Expected: 2" type=""/>
<failure message="Value of: add(1, -1)&#x0A; Actual: 1&#x0A;Expected: 0" type=""/>
</testcase>
<testcase name="Subtraction" file="test.cpp" line="2" status="run" time="0.005" classname="">
</testcase>

View File

@ -4372,11 +4372,20 @@ void XmlUnitTestResultPrinter::OutputXmlTestResult(::std::ostream* stream,
internal::FormatCompilerIndependentFileLocation(part.file_name(),
part.line_number());
const std::string summary = location + "\n" + part.summary();
*stream << " <failure message=\"" << EscapeXmlAttribute(summary)
<< "\" type=\"\">";
const std::string detail = location + "\n" + part.message();
OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str());
*stream << " <failure message=\"" << EscapeXmlAttribute(summary)
<< "\" type=\"\"";
// The "message" attribute already carries the summary, so only write a
// CDATA body when the full message adds something to it (a stack trace,
// for instance). Otherwise the exact same text is reported twice.
if (detail == summary) {
*stream << " />\n";
} else {
*stream << ">";
OutputXmlCDataSection(stream,
RemoveInvalidXmlCharacters(detail).c_str());
*stream << "</failure>\n";
}
} else if (part.skipped()) {
if (++skips == 1 && failures == 0) {
*stream << ">\n";
@ -4385,13 +4394,19 @@ void XmlUnitTestResultPrinter::OutputXmlTestResult(::std::ostream* stream,
internal::FormatCompilerIndependentFileLocation(part.file_name(),
part.line_number());
const std::string summary = location + "\n" + part.summary();
*stream << " <skipped message=\"" << EscapeXmlAttribute(summary)
<< "\">";
const std::string detail = location + "\n" + part.message();
OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str());
*stream << " <skipped message=\"" << EscapeXmlAttribute(summary)
<< "\"";
if (detail == summary) {
*stream << " />\n";
} else {
*stream << ">";
OutputXmlCDataSection(stream,
RemoveInvalidXmlCharacters(detail).c_str());
*stream << "</skipped>\n";
}
}
}
if (failures == 0 && skips == 0 && result.test_property_count() == 0) {
*stream << " />\n";

View File

@ -66,7 +66,27 @@ else:
# unittest.main() can't handle unknown flags
sys.argv.remove(NO_STACKTRACE_SUPPORT_FLAG)
EXPECTED_NON_EMPTY_XML = """<?xml version="1.0" encoding="UTF-8"?>
def RemoveRepeatedDetails(xml):
"""Drops the bodies of <failure> elements that only repeat their message.
GoogleTest writes the detail of a failure as a CDATA body only when it holds
more than the "message" attribute already does. Without stack trace support
the two are always identical, so the body is not written at all.
Args:
xml: an expected XML document, as a string.
Returns:
The document with such bodies replaced by empty elements.
"""
if SUPPORTS_STACK_TRACES:
return xml
return re.sub(r'><!\[CDATA\[.*?\]\]></failure>', ' />', xml, flags=re.DOTALL)
EXPECTED_NON_EMPTY_XML = RemoveRepeatedDetails(
"""<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="28" failures="5" disabled="2" errors="0" time="*" timestamp="*" name="AllTests">
<properties>
<property name="ad_hoc_property" value="42"/>
@ -115,23 +135,17 @@ Invalid characters in brackets []%(stack)s]]></failure>
</testsuite>
<testsuite name="SkippedTest" tests="3" failures="1" disabled="0" skipped="2" errors="0" time="*" timestamp="*">
<testcase name="Skipped" status="run" file="gtest_xml_output_unittest_.cc" line="75" result="skipped" time="*" timestamp="*" classname="SkippedTest">
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;&#x0A;"><![CDATA[gtest_xml_output_unittest_.cc:*
]]></skipped>
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;&#x0A;" />
</testcase>
<testcase name="SkippedWithMessage" file="gtest_xml_output_unittest_.cc" line="79" status="run" result="skipped" time="*" timestamp="*" classname="SkippedTest">
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;It is good practice to tell why you skip a test.&#x0A;"><![CDATA[gtest_xml_output_unittest_.cc:*
It is good practice to tell why you skip a test.
]]></skipped>
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;It is good practice to tell why you skip a test.&#x0A;" />
</testcase>
<testcase name="SkippedAfterFailure" file="gtest_xml_output_unittest_.cc" line="83" status="run" result="completed" time="*" timestamp="*" classname="SkippedTest">
<failure message="gtest_xml_output_unittest_.cc:*&#x0A;Expected equality of these values:&#x0A; 1&#x0A; 2%(stack_entity)s" type=""><![CDATA[gtest_xml_output_unittest_.cc:*
Expected equality of these values:
1
2%(stack)s]]></failure>
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;It is good practice to tell why you skip a test.&#x0A;"><![CDATA[gtest_xml_output_unittest_.cc:*
It is good practice to tell why you skip a test.
]]></skipped>
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;It is good practice to tell why you skip a test.&#x0A;" />
</testcase>
</testsuite>
@ -184,8 +198,7 @@ It is good practice to tell why you skip a test.
</testsuite>
<testsuite name="SetupFailTest" tests="1" failures="0" disabled="0" skipped="1" errors="0" time="*" timestamp="*">
<testcase name="NoopPassingTest" file="gtest_xml_output_unittest_.cc" line="172" status="run" result="skipped" time="*" timestamp="*" classname="SetupFailTest">
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;"><![CDATA[gtest_xml_output_unittest_.cc:*
]]></skipped>
<skipped message="gtest_xml_output_unittest_.cc:*&#x0A;" />
</testcase>
<testcase name="" status="run" result="completed" classname="" time="*" timestamp="*">
<failure message="gtest_xml_output_unittest_.cc:*&#x0A;Expected equality of these values:&#x0A; 1&#x0A; 2%(stack_entity)s" type=""><![CDATA[gtest_xml_output_unittest_.cc:*
@ -225,6 +238,7 @@ Expected equality of these values:
'stack': STACK_TRACE_TEMPLATE,
'stack_entity': STACK_TRACE_ENTITY_TEMPLATE,
}
)
EXPECTED_FILTERED_TEST_XML = """<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="1" failures="0" disabled="0" errors="0" time="*" timestamp="*" name="AllTests">
@ -263,7 +277,8 @@ EXPECTED_SHARDED_TEST_XML = """<?xml version="1.0" encoding="UTF-8"?>
</testsuite>
</testsuites>"""
EXPECTED_NO_TEST_XML = """<?xml version="1.0" encoding="UTF-8"?>
EXPECTED_NO_TEST_XML = RemoveRepeatedDetails(
"""<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="0" failures="0" disabled="0" errors="0" time="*"
timestamp="*" name="AllTests">
<testsuite name="NonTestSuiteFailure" tests="1" failures="1" disabled="0" skipped="0" errors="0" time="*" timestamp="*">
@ -278,6 +293,7 @@ Expected equality of these values:
'stack': STACK_TRACE_TEMPLATE,
'stack_entity': STACK_TRACE_ENTITY_TEMPLATE,
}
)
GTEST_PROGRAM_PATH = gtest_test_utils.GetTestExecutablePath(GTEST_PROGRAM_NAME)
@ -313,6 +329,32 @@ class GTestXMLOutputUnitTest(gtest_xml_test_utils.GTestXMLTestCase):
self._TestXmlOutput('gtest_no_test_unittest', EXPECTED_NO_TEST_XML, 0)
def testFailureDetailIsNotACopyOfTheMessage(self):
"""Verifies that a failure detail does not repeat the message attribute.
The "message" attribute of a <failure> or <skipped> element already holds
the summary of the failure, so the body of the element is only written when
it adds something to that summary.
"""
actual = self._GetXmlOutput(GTEST_PROGRAM_NAME, [], {}, 1)
elements = actual.getElementsByTagName(
'failure'
) + actual.getElementsByTagName('skipped')
self.assertTrue(elements, 'the test program reported no failure')
for element in elements:
detail = ''.join(
child.nodeValue
for child in element.childNodes
if child.nodeType == child.CDATA_SECTION_NODE
)
self.assertNotEqual(
element.getAttribute('message'),
detail,
'the detail of a <%s> element repeats its message attribute'
% element.tagName,
)
actual.unlink()
def testTimestampValue(self):
"""Checks whether the timestamp attribute in the XML output is valid.