From f607bf59814bf5a7c7ccdb1cb3b4392b70287c9b Mon Sep 17 00:00:00 2001 From: mobounya Date: Sat, 13 Jan 2024 19:32:12 +0100 Subject: [PATCH] Add new merged standard streams interceptor. Add method registerMergedStandardStreams in StreamInterceptor to merge stdout and stderr so both can be intercepted in an stdout interceptor. This will keep the relative order for both outputs, which makes it easier to correlate error messages with the corresponding output. Add new configuration parameter junit.platform.output.capture.merge to merge stdout and stderr and publish it as STDOUT_REPORT_ENTRY_KEY to all registered TestExecutionListener instances. Issue: #3166 --- .../platform/launcher/LauncherConstants.java | 15 +++++++++++++ ...reamInterceptingTestExecutionListener.java | 22 ++++++++++++++----- .../launcher/core/StreamInterceptor.java | 6 +++++ ...TestExecutionListenerIntegrationTests.java | 4 +++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index da4d81625f6..0d2314bb768 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -60,6 +60,21 @@ public class LauncherConstants { */ public static final String CAPTURE_STDERR_PROPERTY_NAME = "junit.platform.output.capture.stderr"; + /** + * Property name used to enable merging and capturing output to {@link System#err} and {@link System#out}: + * {@value} + * + *

If enabled, the JUnit Platform merges stdout and stderr and publishes + * it as a {@link ReportEntry} using the + * {@value #STDOUT_REPORT_ENTRY_KEY} key immediately before reporting the + * test identifier as finished. + * + * @see #STDOUT_REPORT_ENTRY_KEY + * @see ReportEntry + * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) + */ + public static final String CAPTURE_MERGED_STANDARD_STREAMS_PROPERTY_NAME = "junit.platform.output.capture.merge"; + /** * Property name used to configure the maximum number of bytes for buffering * to use per thread and output type if output capturing is enabled: diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java index 32a13589655..a9e7e647f21 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java @@ -12,6 +12,7 @@ import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_DEFAULT; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MERGED_STANDARD_STREAMS_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; @@ -43,17 +44,28 @@ static Optional create(ConfigurationPar boolean captureStdout = configurationParameters.getBoolean(CAPTURE_STDOUT_PROPERTY_NAME).orElse(false); boolean captureStderr = configurationParameters.getBoolean(CAPTURE_STDERR_PROPERTY_NAME).orElse(false); - if (!captureStdout && !captureStderr) { + boolean captureMergeStandardStreams = configurationParameters.getBoolean( + CAPTURE_MERGED_STANDARD_STREAMS_PROPERTY_NAME).orElse(false); + + if (!captureStdout && !captureStderr && !captureMergeStandardStreams) { return Optional.empty(); } int maxSize = configurationParameters.get(CAPTURE_MAX_BUFFER_PROPERTY_NAME, Integer::valueOf) // .orElse(CAPTURE_MAX_BUFFER_DEFAULT); - Optional stdoutInterceptor = captureStdout ? StreamInterceptor.registerStdout(maxSize) - : Optional.empty(); - Optional stderrInterceptor = captureStderr ? StreamInterceptor.registerStderr(maxSize) - : Optional.empty(); + Optional stdoutInterceptor = Optional.empty(); + Optional stderrInterceptor = Optional.empty(); + + if (captureMergeStandardStreams) { + stdoutInterceptor = StreamInterceptor.registerMergedStandardStreams(maxSize); + captureStderr = false; + captureStdout = true; + } + else { + stdoutInterceptor = captureStdout ? StreamInterceptor.registerStdout(maxSize) : Optional.empty(); + stderrInterceptor = captureStderr ? StreamInterceptor.registerStderr(maxSize) : Optional.empty(); + } if ((!stdoutInterceptor.isPresent() && captureStdout) || (!stderrInterceptor.isPresent() && captureStderr)) { stdoutInterceptor.ifPresent(StreamInterceptor::unregister); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java index b34fd72c1a1..150b15880da 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java @@ -37,6 +37,12 @@ static Optional registerStderr(int maxNumberOfBytesPerThread) return register(System.err, System::setErr, maxNumberOfBytesPerThread); } + static Optional registerMergedStandardStreams(int maxNumberOfBytesPerThread) { + Optional interceptor = registerStdout(maxNumberOfBytesPerThread); + interceptor.ifPresent((System::setErr)); + return interceptor; + } + static Optional register(PrintStream originalStream, Consumer streamSetter, int maxNumberOfBytesPerThread) { if (originalStream instanceof StreamInterceptor) { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java index 01aacc458db..b6ffc1c049f 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MERGED_STANDARD_STREAMS_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; @@ -119,7 +120,8 @@ void doesNotInterceptStreamWhenAlreadyBeingIntercepted(String configParam, private static Stream systemStreams() { return Stream.of(// streamType(CAPTURE_STDOUT_PROPERTY_NAME, () -> System.out, STDOUT_REPORT_ENTRY_KEY), // - streamType(CAPTURE_STDERR_PROPERTY_NAME, () -> System.err, STDERR_REPORT_ENTRY_KEY)); + streamType(CAPTURE_STDERR_PROPERTY_NAME, () -> System.err, STDERR_REPORT_ENTRY_KEY), // + streamType(CAPTURE_MERGED_STANDARD_STREAMS_PROPERTY_NAME, () -> System.out, STDOUT_REPORT_ENTRY_KEY)); } private static Arguments streamType(String configParam, Supplier printStreamSupplier,