Skip to content

Commit 2e340e8

Browse files
committedApr 26, 2023
8233725: ProcessTools.startProcess() has output issues when using an OutputAnalyzer at the same time
Reviewed-by: cjplummer, sspitsyn
1 parent 35e7bc2 commit 2e340e8

File tree

3 files changed

+209
-12
lines changed

3 files changed

+209
-12
lines changed
 

‎test/jdk/sun/tools/jstatd/JstatdTest.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -350,7 +350,10 @@ private void runTest(boolean useShortSyntax) throws Throwable {
350350

351351
// Verify output from jstatd
352352
OutputAnalyzer output = jstatdThread.getOutput();
353-
output.shouldBeEmptyIgnoreVMWarnings();
353+
List<String> stdout = output.asLinesWithoutVMWarnings();
354+
output.reportDiagnosticSummary();
355+
assertEquals(stdout.size(), 1, "Output should contain one line");
356+
assertTrue(stdout.get(0).startsWith("jstatd started"), "List should start with 'jstatd started'");
354357
assertNotEquals(output.getExitValue(), 0,
355358
"jstatd process exited with unexpected exit code");
356359
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @summary Unit test for ProcessTools.startProcess()
27+
* @library /test/lib
28+
* @compile ProcessToolsStartProcessTest.java
29+
* @run main ProcessToolsStartProcessTest
30+
*/
31+
32+
import java.util.function.Consumer;
33+
import java.io.File;
34+
35+
import jdk.test.lib.JDKToolLauncher;
36+
import jdk.test.lib.Utils;
37+
import jdk.test.lib.process.OutputAnalyzer;
38+
import jdk.test.lib.process.ProcessTools;
39+
40+
public class ProcessToolsStartProcessTest {
41+
static final int NUM_LINES = 50;
42+
static String output = "";
43+
44+
private static Consumer<String> outputConsumer = s -> {
45+
output += s + "\n";
46+
};
47+
48+
static boolean testStartProcess(boolean withConsumer) throws Exception {
49+
boolean success = true;
50+
Process p;
51+
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("java");
52+
launcher.addToolArg("-cp");
53+
launcher.addToolArg(Utils.TEST_CLASSES);
54+
launcher.addToolArg("ProcessToolsStartProcessTest");
55+
launcher.addToolArg("test"); // This one argument triggers producing the output
56+
ProcessBuilder pb = new ProcessBuilder();
57+
pb.command(launcher.getCommand());
58+
59+
System.out.println("DEBUG: Test with withConsumer=" + withConsumer);
60+
System.out.println("DEBUG: about to start process.");
61+
if (withConsumer) {
62+
p = ProcessTools.startProcess("java", pb, outputConsumer);
63+
} else {
64+
p = ProcessTools.startProcess("java", pb);
65+
}
66+
OutputAnalyzer out = new OutputAnalyzer(p);
67+
68+
System.out.println("DEBUG: process started.");
69+
p.waitFor();
70+
if (p.exitValue() != 0) {
71+
throw new RuntimeException("Bad exit value: " + p.exitValue());
72+
}
73+
74+
if (withConsumer) {
75+
int numLines = output.split("\n").length;
76+
if (numLines != NUM_LINES ) {
77+
System.out.print("FAILED: wrong number of lines in Consumer output\n");
78+
success = false;
79+
}
80+
System.out.println("DEBUG: Consumer output: got " + numLines + " lines , expected "
81+
+ NUM_LINES + ". Output follow:");
82+
System.out.print(output);
83+
System.out.println("DEBUG: done with Consumer output.");
84+
}
85+
86+
int numLinesOut = out.getStdout().split("\n").length;
87+
if (numLinesOut != NUM_LINES) {
88+
System.out.print("FAILED: wrong number of lines in OutputAnalyzer output\n");
89+
success = false;
90+
}
91+
System.out.println("DEBUG: OutputAnalyzer output: got " + numLinesOut + " lines, expected "
92+
+ NUM_LINES + ". Output follows:");
93+
System.out.print(out.getStdout());
94+
System.out.println("DEBUG: done with OutputAnalyzer stdout.");
95+
96+
return success;
97+
}
98+
99+
public static void main(String[] args) {
100+
if (args.length > 0) {
101+
for (int i = 0; i < NUM_LINES; i++) {
102+
System.out.println("A line on stdout " + i);
103+
}
104+
} else {
105+
try {
106+
boolean test1Result = testStartProcess(false);
107+
boolean test2Result = testStartProcess(true);
108+
if (!test1Result || !test2Result) {
109+
throw new RuntimeException("One or more tests failed. See output for details.");
110+
}
111+
} catch (RuntimeException re) {
112+
re.printStackTrace();
113+
System.out.println("Test ERROR");
114+
throw re;
115+
} catch (Exception ex) {
116+
ex.printStackTrace();
117+
System.out.println("Test ERROR");
118+
throw new RuntimeException(ex);
119+
}
120+
}
121+
}
122+
}

‎test/lib/jdk/test/lib/process/ProcessTools.java

+82-10
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import jdk.test.lib.Platform;
2828
import jdk.test.lib.Utils;
2929

30+
import java.io.ByteArrayOutputStream;
3031
import java.io.File;
3132
import java.io.IOException;
3233
import java.io.InputStream;
@@ -71,14 +72,17 @@ protected void processLine(String line) {
7172
ps.println("[" + prefix + "] " + line);
7273
}
7374
}
74-
7575
private ProcessTools() {
7676
}
7777

7878
/**
7979
* <p>Starts a process from its builder.</p>
8080
* <span>The default redirects of STDOUT and STDERR are started</span>
81-
*
81+
* <p>
82+
* Same as
83+
* {@linkplain #startProcess(String, ProcessBuilder, Consumer, Predicate, long, TimeUnit) startProcess}
84+
* {@code (name, processBuilder, null, null, -1, TimeUnit.NANOSECONDS)}
85+
* </p>
8286
* @param name The process name
8387
* @param processBuilder The process builder
8488
* @return Returns the initialized process
@@ -93,11 +97,15 @@ public static Process startProcess(String name,
9397
/**
9498
* <p>Starts a process from its builder.</p>
9599
* <span>The default redirects of STDOUT and STDERR are started</span>
96-
* <p>It is possible to monitor the in-streams via the provided {@code consumer}
100+
* <p>
101+
* Same as
102+
* {@linkplain #startProcess(String, ProcessBuilder, Consumer, Predicate, long, TimeUnit) startProcess}
103+
* {@code (name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS)}
104+
* </p>
97105
*
98106
* @param name The process name
99-
* @param consumer {@linkplain Consumer} instance to process the in-streams
100107
* @param processBuilder The process builder
108+
* @param consumer {@linkplain Consumer} instance to process the in-streams
101109
* @return Returns the initialized process
102110
* @throws IOException
103111
*/
@@ -118,8 +126,9 @@ public static Process startProcess(String name,
118126
* <p>Starts a process from its builder.</p>
119127
* <span>The default redirects of STDOUT and STDERR are started</span>
120128
* <p>
121-
* It is possible to wait for the process to get to a warmed-up state
122-
* via {@linkplain Predicate} condition on the STDOUT/STDERR
129+
* Same as
130+
* {@linkplain #startProcess(String, ProcessBuilder, Consumer, Predicate, long, TimeUnit) startProcess}
131+
* {@code (name, processBuilder, null, linePredicate, timeout, unit)}
123132
* </p>
124133
*
125134
* @param name The process name
@@ -144,6 +153,58 @@ public static Process startProcess(String name,
144153
return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
145154
}
146155

156+
157+
/*
158+
BufferOutputStream and BufferInputStream allow to re-use p.getInputStream() amd p.getOutputStream() of
159+
processes started with ProcessTools.startProcess(...).
160+
Implementation cashes ALL process output and allow to read it through InputStream.
161+
*/
162+
private static class BufferOutputStream extends ByteArrayOutputStream {
163+
private int current = 0;
164+
final private Process p;
165+
166+
public BufferOutputStream(Process p) {
167+
this.p = p;
168+
}
169+
170+
synchronized int readNext() {
171+
if (current > count) {
172+
throw new RuntimeException("Shouldn't ever happen. start: "
173+
+ current + " count: " + count + " buffer: " + this);
174+
}
175+
while (current == count) {
176+
if (!p.isAlive()) {
177+
return -1;
178+
}
179+
try {
180+
wait(1);
181+
} catch (InterruptedException ie) {
182+
return -1;
183+
}
184+
}
185+
return this.buf[current++];
186+
}
187+
}
188+
189+
private static class BufferInputStream extends InputStream {
190+
191+
private final BufferOutputStream buffer;
192+
193+
public BufferInputStream(Process p) {
194+
buffer = new BufferOutputStream(p);
195+
}
196+
197+
OutputStream getOutputStream() {
198+
return buffer;
199+
}
200+
201+
@Override
202+
public int read() throws IOException {
203+
return buffer.readNext();
204+
}
205+
}
206+
207+
147208
/**
148209
* <p>Starts a process from its builder.</p>
149210
* <span>The default redirects of STDOUT and STDERR are started</span>
@@ -181,6 +242,12 @@ public static Process startProcess(String name,
181242

182243
stdout.addPump(new LineForwarder(name, System.out));
183244
stderr.addPump(new LineForwarder(name, System.err));
245+
BufferInputStream stdOut = new BufferInputStream(p);
246+
BufferInputStream stdErr = new BufferInputStream(p);
247+
248+
stdout.addOutputStream(stdOut.getOutputStream());
249+
stderr.addOutputStream(stdErr.getOutputStream());
250+
184251
if (lineConsumer != null) {
185252
StreamPumper.LinePump pump = new StreamPumper.LinePump() {
186253
@Override
@@ -250,7 +317,7 @@ protected void processLine(String line) {
250317
throw e;
251318
}
252319

253-
return new ProcessImpl(p, stdoutTask, stderrTask);
320+
return new ProcessImpl(p, stdoutTask, stderrTask, stdOut, stdErr);
254321
}
255322

256323
/**
@@ -701,14 +768,19 @@ private static Process privilegedStart(ProcessBuilder pb) throws IOException {
701768

702769
private static class ProcessImpl extends Process {
703770

771+
private final InputStream stdOut;
772+
private final InputStream stdErr;
704773
private final Process p;
705774
private final Future<Void> stdoutTask;
706775
private final Future<Void> stderrTask;
707776

708-
public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
777+
public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask,
778+
InputStream stdOut, InputStream etdErr) {
709779
this.p = p;
710780
this.stdoutTask = stdoutTask;
711781
this.stderrTask = stderrTask;
782+
this.stdOut = stdOut;
783+
this.stdErr = etdErr;
712784
}
713785

714786
@Override
@@ -718,12 +790,12 @@ public OutputStream getOutputStream() {
718790

719791
@Override
720792
public InputStream getInputStream() {
721-
return p.getInputStream();
793+
return stdOut;
722794
}
723795

724796
@Override
725797
public InputStream getErrorStream() {
726-
return p.getErrorStream();
798+
return stdErr;
727799
}
728800

729801
@Override

0 commit comments

Comments
 (0)
Please sign in to comment.