Skip to content

Commit e89948d

Browse files
committed
Write captured output to dedicated XML elements
If capturing output to `System.out`/`System.err` is enabled, it is now written into the `system-out`/`system-err` XML elements and excluded from the output for other report entries, if any. In addition, this commit slightly revises the existing output to `system-out` elements: - Now they are always written using CDATA sections. - The non standard unique-id/display-name `system-out` element always comes first. - If there are normal report entries, another `system-out` element is written next. - If there was output captured to `System.out`, another `system-out` element is written and always comes last. Resolves #780.
1 parent d397305 commit e89948d

File tree

3 files changed

+104
-25
lines changed

3 files changed

+104
-25
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.3.0-RC1.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ on GitHub.
3131
present as an interface `default` method.
3232
* A `ClasspathResourceSource` can now be created from a `URI` via the new `from(URI)`
3333
static factory method if the `URI` uses the `classpath` scheme.
34+
* If the new experimental feature is enabled, captured output to `System.out` and
35+
`System.err` is now written to the dedicated `system-out` and `system-err` elements,
36+
respectively, in the XML report generated by `ConsoleLauncher`.
3437

3538

3639
[[release-notes-5.3.0-RC1-junit-jupiter]]

junit-platform-console/src/main/java/org/junit/platform/console/tasks/XmlReportWriter.java

+54-23
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@
1717
import static org.junit.platform.commons.util.StringUtils.isNotBlank;
1818
import static org.junit.platform.console.tasks.XmlReportData.isFailure;
1919
import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
20+
import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY;
21+
import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY;
2022

2123
import java.io.Writer;
2224
import java.net.InetAddress;
2325
import java.net.UnknownHostException;
2426
import java.text.NumberFormat;
2527
import java.time.LocalDateTime;
28+
import java.util.ArrayList;
29+
import java.util.LinkedHashMap;
2630
import java.util.List;
2731
import java.util.Locale;
32+
import java.util.Map;
2833
import java.util.Optional;
2934
import java.util.Properties;
3035
import java.util.TreeSet;
@@ -97,7 +102,7 @@ private void writeTestsuite(TestIdentifier testIdentifier, List<TestIdentifier>
97102
writeTestcase(test, numberFormat, writer);
98103
}
99104

100-
writeNonStandardAttributesToSystemOutElement(testIdentifier, writer);
105+
writeOutputElement("system-out", formatNonStandardAttributesAsString(testIdentifier), writer);
101106

102107
writer.writeEndElement();
103108
newLine(writer);
@@ -146,8 +151,13 @@ private void writeTestcase(TestIdentifier testIdentifier, NumberFormat numberFor
146151
newLine(writer);
147152

148153
writeSkippedOrErrorOrFailureElement(testIdentifier, writer);
149-
writeReportEntriesToSystemOutElement(testIdentifier, writer);
150-
writeNonStandardAttributesToSystemOutElement(testIdentifier, writer);
154+
155+
List<String> systemOutElements = new ArrayList<>();
156+
List<String> systemErrElements = new ArrayList<>();
157+
systemOutElements.add(formatNonStandardAttributesAsString(testIdentifier));
158+
collectReportEntries(testIdentifier, systemOutElements, systemErrElements);
159+
writeOutputElements("system-out", systemOutElements, writer);
160+
writeOutputElements("system-err", systemErrElements, writer);
151161

152162
writer.writeEndElement();
153163
newLine(writer);
@@ -211,28 +221,41 @@ private void writeFailureAttributesAndContent(Throwable throwable, XMLStreamWrit
211221
writeCDataSafely(writer, readStackTrace(throwable));
212222
}
213223

214-
private void writeReportEntriesToSystemOutElement(TestIdentifier testIdentifier, XMLStreamWriter writer)
215-
throws XMLStreamException {
216-
224+
private void collectReportEntries(TestIdentifier testIdentifier, List<String> systemOutElements,
225+
List<String> systemErrElements) {
217226
List<ReportEntry> entries = this.reportData.getReportEntries(testIdentifier);
218227
if (!entries.isEmpty()) {
219-
writer.writeStartElement("system-out");
220-
newLine(writer);
228+
List<String> systemOutElementsForCapturedOutput = new ArrayList<>();
229+
StringBuilder formattedReportEntries = new StringBuilder();
221230
for (int i = 0; i < entries.size(); i++) {
222-
writer.writeCharacters(buildReportEntryDescription(entries.get(i), i + 1));
231+
ReportEntry reportEntry = entries.get(i);
232+
Map<String, String> keyValuePairs = new LinkedHashMap<>(reportEntry.getKeyValuePairs());
233+
removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDOUT_REPORT_ENTRY_KEY,
234+
systemOutElementsForCapturedOutput);
235+
removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDERR_REPORT_ENTRY_KEY, systemErrElements);
236+
if (!keyValuePairs.isEmpty()) {
237+
buildReportEntryDescription(reportEntry.getTimestamp(), keyValuePairs, i + 1,
238+
formattedReportEntries);
239+
}
223240
}
224-
writer.writeEndElement();
225-
newLine(writer);
241+
systemOutElements.add(formattedReportEntries.toString().trim());
242+
systemOutElements.addAll(systemOutElementsForCapturedOutput);
226243
}
227244
}
228245

229-
private String buildReportEntryDescription(ReportEntry reportEntry, int entryNumber) {
230-
StringBuilder builder = new StringBuilder(format("Report Entry #{0} (timestamp: {1})\n", entryNumber,
231-
ISO_LOCAL_DATE_TIME.format(reportEntry.getTimestamp())));
232-
233-
reportEntry.getKeyValuePairs().forEach((key, value) -> builder.append(format("\t- {0}: {1}\n", key, value)));
246+
private void removeIfPresentAndAddAsSeparateElement(Map<String, String> keyValuePairs, String key,
247+
List<String> elements) {
248+
String value = keyValuePairs.remove(key);
249+
if (value != null) {
250+
elements.add(value);
251+
}
252+
}
234253

235-
return builder.toString();
254+
private void buildReportEntryDescription(LocalDateTime timestamp, Map<String, String> keyValuePairs,
255+
int entryNumber, StringBuilder result) {
256+
result.append(
257+
format("Report Entry #{0} (timestamp: {1})\n", entryNumber, ISO_LOCAL_DATE_TIME.format(timestamp)));
258+
keyValuePairs.forEach((key, value) -> result.append(format("\t- {0}: {1}\n", key, value)));
236259
}
237260

238261
private String getTime(TestIdentifier testIdentifier, NumberFormat numberFormat) {
@@ -252,14 +275,22 @@ private LocalDateTime getCurrentDateTime() {
252275
return LocalDateTime.now(this.reportData.getClock()).withNano(0);
253276
}
254277

255-
private void writeNonStandardAttributesToSystemOutElement(TestIdentifier testIdentifier, XMLStreamWriter writer)
256-
throws XMLStreamException {
278+
private String formatNonStandardAttributesAsString(TestIdentifier testIdentifier) {
279+
return "unique-id: " + testIdentifier.getUniqueId() //
280+
+ "\ndisplay-name: " + testIdentifier.getDisplayName();
281+
}
257282

258-
String cData = "\nunique-id: " + testIdentifier.getUniqueId() //
259-
+ "\ndisplay-name: " + testIdentifier.getDisplayName() + "\n";
283+
private void writeOutputElements(String elementName, List<String> elements, XMLStreamWriter writer)
284+
throws XMLStreamException {
285+
for (String content : elements) {
286+
writeOutputElement(elementName, content, writer);
287+
}
288+
}
260289

261-
writer.writeStartElement("system-out");
262-
writeCDataSafely(writer, cData);
290+
private void writeOutputElement(String elementName, String content, XMLStreamWriter writer)
291+
throws XMLStreamException {
292+
writer.writeStartElement(elementName);
293+
writeCDataSafely(writer, "\n" + content + "\n");
263294
writer.writeEndElement();
264295
newLine(writer);
265296
}

platform-tests/src/test/java/org/junit/platform/console/tasks/XmlReportWriterTests.java

+47-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
import static org.junit.platform.console.tasks.XmlReportAssertions.assertValidAccordingToJenkinsSchema;
1717
import static org.junit.platform.engine.TestExecutionResult.failed;
1818
import static org.junit.platform.engine.TestExecutionResult.successful;
19+
import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY;
20+
import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY;
1921

2022
import java.io.StringWriter;
2123
import java.time.Clock;
24+
import java.util.Map;
2225

2326
import org.junit.jupiter.api.Test;
2427
import org.junit.platform.engine.UniqueId;
@@ -71,12 +74,54 @@ void writesReportEntry() throws Exception {
7174
assertThat(content)
7275
.containsSubsequence(
7376
"<system-out>",
74-
"Report Entry #1 (timestamp: ",
75-
"- myKey: myValue",
77+
"Report Entry #1 (timestamp: ",
78+
"- myKey: myValue",
7679
"</system-out>");
7780
//@formatter:on
7881
}
7982

83+
@Test
84+
void writesCapturedOutput() throws Exception {
85+
UniqueId uniqueId = engineDescriptor.getUniqueId().append("test", "test");
86+
TestDescriptorStub testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest");
87+
engineDescriptor.addChild(testDescriptor);
88+
TestPlan testPlan = TestPlan.from(singleton(engineDescriptor));
89+
90+
XmlReportData reportData = new XmlReportData(testPlan, Clock.systemDefaultZone());
91+
ReportEntry reportEntry = ReportEntry.from(Map.of( //
92+
STDOUT_REPORT_ENTRY_KEY, "normal output", //
93+
STDERR_REPORT_ENTRY_KEY, "error output", //
94+
"foo", "bar"));
95+
reportData.addReportEntry(TestIdentifier.from(testDescriptor), reportEntry);
96+
reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from(Map.of("baz", "qux")));
97+
reportData.markFinished(testPlan.getTestIdentifier(uniqueId.toString()), successful());
98+
99+
String content = writeXmlReport(testPlan, reportData);
100+
101+
assertValidAccordingToJenkinsSchema(content);
102+
//@formatter:off
103+
assertThat(content)
104+
.containsSubsequence(
105+
"<system-out>",
106+
"unique-id: ", "test:test",
107+
"display-name: successfulTest",
108+
"</system-out>",
109+
"<system-out>",
110+
"Report Entry #1 (timestamp: ",
111+
"- foo: bar",
112+
"Report Entry #2 (timestamp: ",
113+
"- baz: qux",
114+
"</system-out>",
115+
"<system-out>",
116+
"normal output",
117+
"</system-out>",
118+
"<system-err>",
119+
"error output",
120+
"</system-err>")
121+
.doesNotContain(STDOUT_REPORT_ENTRY_KEY, STDERR_REPORT_ENTRY_KEY);
122+
//@formatter:on
123+
}
124+
80125
@Test
81126
void writesEmptySkippedElementForSkippedTestWithoutReason() throws Exception {
82127
UniqueId uniqueId = engineDescriptor.getUniqueId().append("test", "test");

0 commit comments

Comments
 (0)