From 16853cc3de7b037b71af69e1362d0c6f3c87aa3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Thu, 24 Sep 2026 09:06:32 +0300 Subject: [PATCH] Preserve caller output streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- .../maven/executor/ExecutorRequest.java | 4 +- .../ProcessBuilderExecutorSupport.java | 12 +-- .../ProcessBuilderExecutorSupportTest.java | 81 +++++++++++++++++++ 3 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 maven-executor/src/test/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupportTest.java diff --git a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java index e5a54dd..f4ea29d 100644 --- a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java +++ b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java @@ -114,7 +114,7 @@ public interface ExecutorRequest { * Optional consumer for STD out of the Maven. If given, this consumer will get all output from the std out of * Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in * {@link #arguments()}, as if log file is set, not much will go to stdout. - * The stream is closed once tool execution is finished. + * The stream is not closed by the executor. * * @return an Optional containing the stdout consumer, or empty if not specified. */ @@ -124,7 +124,7 @@ public interface ExecutorRequest { * Optional consumer for STD err of the Maven. If given, this consumer will get all output from the std err of * Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in * {@link #arguments()}, as if log file is set, not much will go to stderr. - * The stream is closed once tool execution is finished. + * The stream is not closed by the executor. * * @return an Optional containing the stderr consumer, or empty if not specified. */ diff --git a/maven-executor/src/main/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupport.java b/maven-executor/src/main/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupport.java index 4f8b29d..daaf75c 100644 --- a/maven-executor/src/main/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupport.java +++ b/maven-executor/src/main/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupport.java @@ -121,9 +121,9 @@ protected CountDownLatch pump(Process p, InputStream stdIn, OutputStream stdOut, CountDownLatch latch = new CountDownLatch(3); String suffix = "-pump-" + ThreadLocalRandom.current().nextInt(); Thread stdoutPump = new Thread(() -> { - try (OutputStream stdout = stdOut) { - IOTools.transferTo(p.getInputStream(), stdout); - stdout.flush(); + try { + IOTools.transferTo(p.getInputStream(), stdOut); + stdOut.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } finally { @@ -134,9 +134,9 @@ protected CountDownLatch pump(Process p, InputStream stdIn, OutputStream stdOut, stdoutPump.setDaemon(true); stdoutPump.start(); Thread stderrPump = new Thread(() -> { - try (OutputStream stderr = stdErr) { - IOTools.transferTo(p.getErrorStream(), stderr); - stderr.flush(); + try { + IOTools.transferTo(p.getErrorStream(), stdErr); + stdErr.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } finally { diff --git a/maven-executor/src/test/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupportTest.java b/maven-executor/src/test/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupportTest.java new file mode 100644 index 0000000..ddfa9fc --- /dev/null +++ b/maven-executor/src/test/java/org/apache/maven/executor/support/ProcessBuilderExecutorSupportTest.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.executor.support; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.maven.executor.ExecutorRequest; +import org.apache.maven.executor.ExecutorResult; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProcessBuilderExecutorSupportTest { + + @Test + void doesNotCloseCallerOutputStreams() throws Exception { + Process process = new ProcessBuilder(System.getProperty("java.home") + "/bin/java", "-version").start(); + TrackingOutputStream stdout = new TrackingOutputStream(); + TrackingOutputStream stderr = new TrackingOutputStream(); + + new TestSupport() + .pump( + process, + new InputStream() { + @Override + public int read() { + return -1; + } + }, + stdout, + stderr) + .await(); + process.waitFor(); + + assertFalse(stdout.closed); + assertFalse(stderr.closed); + assertTrue(stderr.toString(StandardCharsets.UTF_8.name()).contains("version")); + } + + private static class TestSupport extends ProcessBuilderExecutorSupport { + @Override + public ExecutorResult execute(ExecutorRequest executorRequest) { + return null; + } + + @Override + public String mavenVersion() { + return UNKNOWN_VERSION; + } + } + + private static class TrackingOutputStream extends ByteArrayOutputStream { + private boolean closed; + + @Override + public void close() throws IOException { + closed = true; + super.close(); + } + } +}