From 77d248d388a1c70732853dd4bdde5b914b6ac443 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 da4d81625f6d..0d2314bb7687 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 32a135896554..a9e7e647f218 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 b34fd72c1a15..150b15880dab 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 01aacc458db0..b6ffc1c049f8 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,