Skip to content

Commit 8e0ab01

Browse files
committed
Splits XML <failure> tags and escapes required XML characters
1 parent 992bb9c commit 8e0ab01

2 files changed

Lines changed: 24 additions & 15 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,17 @@ jobs:
4646
if: ${{ matrix.config.os != 'windows-latest' }}
4747
with:
4848
files: '${{github.workspace}}/build/*.xml'
49+
check_name: ${{ matrix.config.name }} Test Results
4950
comment_mode: off
50-
fail_on: nothing
51+
fail_on: errors
5152
check_run_annotations: none
5253

5354
- name: Report Windows
5455
uses: EnricoMi/publish-unit-test-result-action/composite@v1.31
5556
if: ${{ matrix.config.os == 'windows-latest' }}
5657
with:
5758
files: '${{github.workspace}}/build/*.xml'
59+
check_name: ${{ matrix.config.name }} Test Results
5860
comment_mode: off
59-
fail_on: nothing
61+
fail_on: errors
6062
check_run_annotations: none

src/XMLOutput.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,22 @@ XMLOutput::~XMLOutput() noexcept
2323
output.flush();
2424
}
2525

26-
static std::string escapeQuotes(std::string text)
26+
static std::string escapeXML(std::string text)
2727
{
2828
std::size_t pos = 0;
29-
while ((pos = text.find('"', pos)) != std::string::npos)
29+
while ((pos = text.find_first_of("\"'<>&", pos)) != std::string::npos)
3030
{
31-
text.replace(pos, 1, "\\\"");
32-
pos += 2;
31+
if(text[pos] == '"')
32+
text.replace(pos, 1, "&quot;");
33+
else if(text[pos] == '\'')
34+
text.replace(pos, 1, "&apos;");
35+
else if(text[pos] == '<')
36+
text.replace(pos, 1, "&lt;");
37+
else if(text[pos] == '>')
38+
text.replace(pos, 1, "&gt;");
39+
else if(text[pos] == '&')
40+
text.replace(pos, 1, "&amp;");
41+
pos += 4;
3342
}
3443
return text;
3544
}
@@ -42,7 +51,7 @@ void XMLOutput::finishSuite(const std::string& suiteName, unsigned int numTests,
4251
std::chrono::seconds seconds = std::chrono::duration_cast<std::chrono::seconds>(totalDuration);
4352
std::chrono::microseconds remainder = totalDuration - seconds;
4453
unsigned numErrors = std::count_if(currentSuite->methods.begin(), currentSuite->methods.end(), [] (const TestMethodInfo& method) { return !method.exceptionMessage.empty(); });
45-
output << "\t<testsuite name=\"" << suiteName << "\" tests=\"" << numTests << "\" failures=\""
54+
output << "\t<testsuite name=\"" << escapeXML(suiteName) << "\" tests=\"" << numTests << "\" failures=\""
4655
<< (numTests - numPositiveTests - numErrors) << "\" errors=\"" << numErrors << "\" time=\""
4756
<< seconds.count() << '.' << std::setfill('0') << std::setw(3) << remainder.count() << "\" timestamp=\""
4857
<< std::put_time(gmtime(&now), "%FT%T") << "\">\n";
@@ -52,24 +61,22 @@ void XMLOutput::finishSuite(const std::string& suiteName, unsigned int numTests,
5261
std::string name = stripMethodName(method.methodName);
5362
if(!method.argString.empty())
5463
name.append("(" + method.argString + ")");
55-
output << "\t\t<testcase classname=\"" << suiteName << "\" name=\"" << escapeQuotes(name) << "\">\n";
64+
output << "\t\t<testcase classname=\"" << escapeXML(suiteName) << "\" name=\"" << escapeXML(name) << "\">\n";
5665
if(!method.exceptionMessage.empty())
57-
output << "\t\t\t<error message=\"" << escapeQuotes(method.exceptionMessage) << "\" type=\"\"/>\n";
66+
output << "\t\t\t<error message=\"" << escapeXML(method.exceptionMessage) << "\" type=\"\"/>\n";
5867
else if(method.failedAssertions.empty() && method.passedAssertions.empty())
5968
output << "\t\t\t<skipped message=\"Test case has no assertions\" type=\"\"/>\n";
6069
else if(!method.failedAssertions.empty())
6170
{
62-
output << "\t\t\t<failure message=\"" << method.failedAssertions.size() << " assertions failed\" type=\"\">\n";
6371
for(const Assertion& assertion : method.failedAssertions)
6472
{
65-
output << "\t\t\t\tFailure: " << assertion.errorMessage << '\n';
66-
output << "\t\t\t\tFile: " << Private::getFileName(assertion.file) << '\n';
73+
output << "\t\t\t<failure message=\"" << (assertion.errorMessage.empty() ? "Failure" : escapeXML(assertion.errorMessage)) << "\" type=\"\">\n";
74+
output << "\t\t\t\tFile: " << escapeXML(Private::getFileName(assertion.file)) << '\n';
6775
output << "\t\t\t\tLine: " << assertion.lineNumber << '\n';
6876
if(!assertion.userMessage.empty())
69-
output << "\t\t\t\tMessage: " << assertion.userMessage << '\n';
70-
output << '\n';
77+
output << "\t\t\t\tMessage: " << escapeXML(assertion.userMessage) << '\n';
78+
output << "\t\t\t</failure>\n";
7179
}
72-
output << "\t\t\t</failure>\n";
7380
}
7481
output << "\t\t</testcase>\n";
7582
}

0 commit comments

Comments
 (0)