Skip to content

Commit 1e8e78f

Browse files
laeubimarcphilipp
andauthored
Add socket support to OpenTestReportGeneratingListener (#5096)
- Add `SOCKET_PROPERTY_NAME` constant for `junit.platform.reporting.open.xml.socket` - Implement `createDocumentWriter` method to support both file and socket outputs - When socket property is set, connect to `localhost:<port>` and write XML to socket - Add comprehensive test for socket-based reporting - Update documentation to describe socket configuration parameter - Update release notes with new feature --------- Co-authored-by: Marc Philipp <mail@marcphilipp.de>
1 parent 52fffcf commit 1e8e78f

File tree

4 files changed

+133
-4
lines changed

4 files changed

+133
-4
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.1.0-M1.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ repository on GitHub.
2828

2929
* Support for creating a `ModuleSelector` from a `java.lang.Module` and using
3030
its classloader for test discovery.
31+
* `OpenTestReportGeneratingListener` now supports redirecting XML events to a socket via
32+
the new `junit.platform.reporting.open.xml.socket` configuration parameter. When set to a
33+
port number, events are sent to `127.0.0.1:<port>` instead of being written to a file.
3134

3235

3336
[[release-notes-6.1.0-M1-junit-jupiter]]

documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,15 @@ The listener is auto-registered and can be configured via the following
4242
Enable/disable writing the report; defaults to `false`.
4343
`junit.platform.reporting.open.xml.git.enabled=true|false`::
4444
Enable/disable including information about the Git repository (see https://github.com/ota4j-team/open-test-reporting#git[Git extension schema] of open-test-reporting); defaults to `false`.
45+
`junit.platform.reporting.open.xml.socket=<port>`::
46+
Optional port number to redirect events to a socket instead of a file. When specified, the
47+
listener will connect to `127.0.0.1:<port>` and send the XML events to the socket. The socket
48+
connection is automatically closed when the test execution completes.
4549

4650
If enabled, the listener creates an XML report file named `open-test-report.xml` in the
47-
configured <<junit-platform-reporting-output-directory, output directory>>.
51+
configured <<junit-platform-reporting-output-directory, output directory>>, unless the
52+
`junit.platform.reporting.open.xml.socket` configuration parameter is set, in which case the
53+
events are sent to the specified socket instead.
4854

4955
If <<running-tests-capturing-output, output capturing>> is enabled, the captured output
5056
written to `System.out` and `System.err` will be included in the report as well.

junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@
5353
import static org.opentest4j.reporting.events.root.RootFactory.started;
5454

5555
import java.io.IOException;
56+
import java.io.OutputStreamWriter;
5657
import java.io.UncheckedIOException;
58+
import java.io.Writer;
5759
import java.net.InetAddress;
60+
import java.net.Socket;
5861
import java.net.UnknownHostException;
62+
import java.nio.charset.StandardCharsets;
5963
import java.nio.file.Path;
6064
import java.time.Instant;
6165
import java.time.LocalDateTime;
@@ -102,6 +106,7 @@ public class OpenTestReportGeneratingListener implements TestExecutionListener {
102106

103107
static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled";
104108
static final String GIT_ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.git.enabled";
109+
static final String SOCKET_PROPERTY_NAME = "junit.platform.reporting.open.xml.socket";
105110

106111
private final AtomicInteger idCounter = new AtomicInteger();
107112
private final Map<UniqueId, String> inProgressIds = new ConcurrentHashMap<>();
@@ -130,17 +135,40 @@ public void testPlanExecutionStarted(TestPlan testPlan) {
130135
.add("junit", JUnitFactory.NAMESPACE, "https://schemas.junit.org/open-test-reporting/junit-1.9.xsd") //
131136
.build();
132137
outputDir = testPlan.getOutputDirectoryCreator().getRootDirectory();
133-
Path eventsXml = outputDir.resolve("open-test-report.xml");
134138
try {
135-
eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml);
139+
eventsFileWriter = createDocumentWriter(config, namespaceRegistry);
136140
reportInfrastructure(config);
137141
}
138142
catch (Exception e) {
139-
throw new JUnitException("Failed to initialize XML events file: " + eventsXml, e);
143+
throw new JUnitException("Failed to initialize XML events writer", e);
140144
}
141145
}
142146
}
143147

148+
private DocumentWriter<Events> createDocumentWriter(ConfigurationParameters config,
149+
NamespaceRegistry namespaceRegistry) throws Exception {
150+
return config.get(SOCKET_PROPERTY_NAME, Integer::valueOf) //
151+
.map(port -> {
152+
try {
153+
Socket socket = new Socket(InetAddress.getLoopbackAddress(), port);
154+
Writer writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8);
155+
return Events.createDocumentWriter(namespaceRegistry, writer);
156+
}
157+
catch (Exception e) {
158+
throw new JUnitException("Failed to connect to socket on port " + port, e);
159+
}
160+
}) //
161+
.orElseGet(() -> {
162+
try {
163+
Path eventsXml = requireNonNull(outputDir).resolve("open-test-report.xml");
164+
return Events.createDocumentWriter(namespaceRegistry, eventsXml);
165+
}
166+
catch (Exception e) {
167+
throw new JUnitException("Failed to create XML events file", e);
168+
}
169+
});
170+
}
171+
144172
private boolean isEnabled(ConfigurationParameters config) {
145173
return config.getBoolean(ENABLED_PROPERTY_NAME).orElse(false);
146174
}

platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,20 @@
2424
import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher;
2525
import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME;
2626
import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.GIT_ENABLED_PROPERTY_NAME;
27+
import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.SOCKET_PROPERTY_NAME;
2728
import static org.junit.platform.reporting.testutil.FileUtils.findPath;
2829

30+
import java.io.BufferedReader;
31+
import java.io.InputStreamReader;
2932
import java.io.PrintStream;
33+
import java.net.InetAddress;
34+
import java.net.ServerSocket;
35+
import java.net.Socket;
3036
import java.net.URISyntaxException;
37+
import java.nio.charset.StandardCharsets;
3138
import java.nio.file.Files;
3239
import java.nio.file.Path;
40+
import java.time.Duration;
3341
import java.util.Map;
3442

3543
import org.junit.jupiter.api.AfterEach;
@@ -265,6 +273,90 @@ void stripsCredentialsFromOriginUrl(String configuredUrl, String reportedUrl, @T
265273
.isEqualTo(reportedUrl);
266274
}
267275

276+
@Test
277+
void writesXmlReportToSocket(@TempDir Path tempDirectory) throws Exception {
278+
var engine = new DemoHierarchicalTestEngine("dummy");
279+
engine.addTest("test1", "Test 1", (context, descriptor) -> {
280+
// Simple test
281+
});
282+
283+
// Start a server socket to receive the XML
284+
var builder = new StringBuilder();
285+
286+
try (var serverSocket = new ServerSocket(0, 50, InetAddress.getLoopbackAddress())) { // Use any available port
287+
int port = serverSocket.getLocalPort();
288+
289+
// Start a daemon thread to accept the connection and read the XML
290+
Thread serverThread = new Thread(() -> {
291+
try (Socket clientSocket = serverSocket.accept();
292+
var reader = new BufferedReader(
293+
new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8))) {
294+
String line;
295+
while ((line = reader.readLine()) != null) {
296+
builder.append(line).append("\n");
297+
}
298+
}
299+
catch (Exception e) {
300+
fail(e);
301+
}
302+
});
303+
serverThread.setDaemon(true);
304+
serverThread.start();
305+
306+
// Execute tests with socket configuration
307+
executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports"),
308+
Map.of(SOCKET_PROPERTY_NAME, String.valueOf(port)));
309+
310+
// Wait for the server to receive the data
311+
assertThat(serverThread.join(Duration.ofSeconds(10))).isTrue();
312+
313+
// Verify XML was received
314+
var expected = """
315+
<e:events xmlns="https://schemas.opentest4j.org/reporting/core/0.2.0"
316+
xmlns:e="https://schemas.opentest4j.org/reporting/events/0.2.0"
317+
xmlns:git="https://schemas.opentest4j.org/reporting/git/0.2.0"
318+
xmlns:java="https://schemas.opentest4j.org/reporting/java/0.2.0"
319+
xmlns:junit="https://schemas.junit.org/open-test-reporting"
320+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
321+
xsi:schemaLocation="https://schemas.junit.org/open-test-reporting https://schemas.junit.org/open-test-reporting/junit-1.9.xsd">
322+
<infrastructure>
323+
<hostName>${xmlunit.ignore}</hostName>
324+
<userName>${xmlunit.ignore}</userName>
325+
<operatingSystem>${xmlunit.ignore}</operatingSystem>
326+
<cpuCores>${xmlunit.ignore}</cpuCores>
327+
<java:javaVersion>${xmlunit.ignore}</java:javaVersion>
328+
<java:fileEncoding>${xmlunit.ignore}</java:fileEncoding>
329+
<java:heapSize max="${xmlunit.isNumber}"/>
330+
</infrastructure>
331+
<e:started id="1" name="dummy" time="${xmlunit.isDateTime}">
332+
<metadata>
333+
<junit:uniqueId>[engine:dummy]</junit:uniqueId>
334+
<junit:legacyReportingName>dummy</junit:legacyReportingName>
335+
<junit:type>CONTAINER</junit:type>
336+
</metadata>
337+
</e:started>
338+
<e:started id="2" name="Test 1" parentId="1" time="${xmlunit.isDateTime}">
339+
<metadata>
340+
<junit:uniqueId>[engine:dummy]/[test:test1]</junit:uniqueId>
341+
<junit:legacyReportingName>Test 1</junit:legacyReportingName>
342+
<junit:type>TEST</junit:type>
343+
</metadata>
344+
</e:started>
345+
<e:finished id="2" time="${xmlunit.isDateTime}">
346+
<result status="SUCCESSFUL"/>
347+
</e:finished>
348+
<e:finished id="1" time="${xmlunit.isDateTime}">
349+
<result status="SUCCESSFUL"/>
350+
</e:finished>
351+
</e:events>
352+
""";
353+
XmlAssert.assertThat(builder.toString()).and(expected) //
354+
.withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) //
355+
.ignoreWhitespace() //
356+
.areIdentical();
357+
}
358+
}
359+
268360
private static XmlAssert assertThatXml(Path xmlFile) {
269361
return XmlAssert.assertThat(xmlFile) //
270362
.withNamespaceContext(NAMESPACE_CONTEXT);

0 commit comments

Comments
 (0)