ProcessTools.java revision 2609:df33ef1dc163
1/*
2 * Copyright (c) 2013, 2017, 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
24package jdk.test.lib.process;
25
26import java.io.ByteArrayOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
30import java.io.PrintStream;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collections;
34import java.util.concurrent.CountDownLatch;
35import java.util.Map;
36import java.util.concurrent.ExecutionException;
37import java.util.concurrent.Future;
38import java.util.concurrent.TimeUnit;
39import java.util.concurrent.TimeoutException;
40import java.util.function.Predicate;
41import java.util.function.Consumer;
42import java.util.stream.Collectors;
43
44import jdk.test.lib.JDKToolFinder;
45import jdk.test.lib.Utils;
46
47public final class ProcessTools {
48    private static final class LineForwarder extends StreamPumper.LinePump {
49        private final PrintStream ps;
50        private final String prefix;
51        LineForwarder(String prefix, PrintStream os) {
52            this.ps = os;
53            this.prefix = prefix;
54        }
55        @Override
56        protected void processLine(String line) {
57            ps.println("[" + prefix + "] " + line);
58        }
59    }
60
61    private ProcessTools() {
62    }
63
64    /**
65     * Pumps stdout and stderr from running the process into a String.
66     *
67     * @param processHandler ProcessHandler to run.
68     * @return Output from process.
69     * @throws IOException If an I/O error occurs.
70     */
71    public static OutputBuffer getOutput(ProcessBuilder processBuilder) throws IOException {
72        return getOutput(processBuilder.start());
73    }
74
75    /**
76     * Pumps stdout and stderr the running process into a String.
77     *
78     * @param process Process to pump.
79     * @return Output from process.
80     * @throws IOException If an I/O error occurs.
81     */
82    public static OutputBuffer getOutput(Process process) throws IOException {
83        ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream();
84        ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
85        StreamPumper outPumper = new StreamPumper(process.getInputStream(), stdoutBuffer);
86        StreamPumper errPumper = new StreamPumper(process.getErrorStream(), stderrBuffer);
87        Thread outPumperThread = new Thread(outPumper);
88        Thread errPumperThread = new Thread(errPumper);
89
90        outPumperThread.setDaemon(true);
91        errPumperThread.setDaemon(true);
92
93        outPumperThread.start();
94        errPumperThread.start();
95
96        try {
97            process.waitFor();
98            outPumperThread.join();
99            errPumperThread.join();
100        } catch (InterruptedException e) {
101            Thread.currentThread().interrupt();
102            return null;
103        }
104
105        return new OutputBuffer(stdoutBuffer.toString(), stderrBuffer.toString());
106    }
107
108    /**
109     * <p>Starts a process from its builder.</p>
110     * <span>The default redirects of STDOUT and STDERR are started</span>
111     * @param name The process name
112     * @param processBuilder The process builder
113     * @return Returns the initialized process
114     * @throws IOException
115     */
116    public static Process startProcess(String name,
117                                       ProcessBuilder processBuilder)
118    throws IOException {
119        return startProcess(name, processBuilder, (Consumer<String>)null);
120    }
121
122    /**
123     * <p>Starts a process from its builder.</p>
124     * <span>The default redirects of STDOUT and STDERR are started</span>
125     * <p>It is possible to monitor the in-streams via the provided {@code consumer}
126     * @param name The process name
127     * @param consumer {@linkplain Consumer} instance to process the in-streams
128     * @param processBuilder The process builder
129     * @return Returns the initialized process
130     * @throws IOException
131     */
132    @SuppressWarnings("overloads")
133    public static Process startProcess(String name,
134                                       ProcessBuilder processBuilder,
135                                       Consumer<String> consumer)
136    throws IOException {
137        try {
138            return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS);
139        } catch (InterruptedException | TimeoutException e) {
140            // will never happen
141            throw new RuntimeException(e);
142        }
143    }
144
145    /**
146     * <p>Starts a process from its builder.</p>
147     * <span>The default redirects of STDOUT and STDERR are started</span>
148     * <p>
149     * It is possible to wait for the process to get to a warmed-up state
150     * via {@linkplain Predicate} condition on the STDOUT
151     * </p>
152     * @param name The process name
153     * @param processBuilder The process builder
154     * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
155     *                      Used to determine the moment the target app is
156     *                      properly warmed-up.
157     *                      It can be null - in that case the warmup is skipped.
158     * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
159     * @param unit The timeout {@linkplain TimeUnit}
160     * @return Returns the initialized {@linkplain Process}
161     * @throws IOException
162     * @throws InterruptedException
163     * @throws TimeoutException
164     */
165    public static Process startProcess(String name,
166                                       ProcessBuilder processBuilder,
167                                       final Predicate<String> linePredicate,
168                                       long timeout,
169                                       TimeUnit unit)
170    throws IOException, InterruptedException, TimeoutException {
171        return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
172    }
173
174    /**
175     * <p>Starts a process from its builder.</p>
176     * <span>The default redirects of STDOUT and STDERR are started</span>
177     * <p>
178     * It is possible to wait for the process to get to a warmed-up state
179     * via {@linkplain Predicate} condition on the STDOUT and monitor the
180     * in-streams via the provided {@linkplain Consumer}
181     * </p>
182     * @param name The process name
183     * @param processBuilder The process builder
184     * @param lineConsumer  The {@linkplain Consumer} the lines will be forwarded to
185     * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
186     *                      Used to determine the moment the target app is
187     *                      properly warmed-up.
188     *                      It can be null - in that case the warmup is skipped.
189     * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
190     * @param unit The timeout {@linkplain TimeUnit}
191     * @return Returns the initialized {@linkplain Process}
192     * @throws IOException
193     * @throws InterruptedException
194     * @throws TimeoutException
195     */
196    public static Process startProcess(String name,
197                                       ProcessBuilder processBuilder,
198                                       final Consumer<String> lineConsumer,
199                                       final Predicate<String> linePredicate,
200                                       long timeout,
201                                       TimeUnit unit)
202    throws IOException, InterruptedException, TimeoutException {
203        System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
204        Process p = processBuilder.start();
205        StreamPumper stdout = new StreamPumper(p.getInputStream());
206        StreamPumper stderr = new StreamPumper(p.getErrorStream());
207
208        stdout.addPump(new LineForwarder(name, System.out));
209        stderr.addPump(new LineForwarder(name, System.err));
210        if (lineConsumer != null) {
211            StreamPumper.LinePump pump = new StreamPumper.LinePump() {
212                @Override
213                protected void processLine(String line) {
214                    lineConsumer.accept(line);
215                }
216            };
217            stdout.addPump(pump);
218            stderr.addPump(pump);
219        }
220
221
222        CountDownLatch latch = new CountDownLatch(1);
223        if (linePredicate != null) {
224            StreamPumper.LinePump pump = new StreamPumper.LinePump() {
225                @Override
226                protected void processLine(String line) {
227                    if (latch.getCount() > 0 && linePredicate.test(line)) {
228                        latch.countDown();
229                    }
230                }
231            };
232            stdout.addPump(pump);
233            stderr.addPump(pump);
234        } else {
235            latch.countDown();
236        }
237        final Future<Void> stdoutTask = stdout.process();
238        final Future<Void> stderrTask = stderr.process();
239
240        try {
241            if (timeout > -1) {
242                if (timeout == 0) {
243                    latch.await();
244                } else {
245                    if (!latch.await(Utils.adjustTimeout(timeout), unit)) {
246                        throw new TimeoutException();
247                    }
248                }
249            }
250        } catch (TimeoutException | InterruptedException e) {
251            System.err.println("Failed to start a process (thread dump follows)");
252            for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) {
253                printStack(s.getKey(), s.getValue());
254            }
255
256            if (p.isAlive()) {
257                p.destroyForcibly();
258            }
259
260            stdoutTask.cancel(true);
261            stderrTask.cancel(true);
262            throw e;
263        }
264
265        return new ProcessImpl(p, stdoutTask, stderrTask);
266    }
267
268    /**
269     * <p>Starts a process from its builder.</p>
270     * <span>The default redirects of STDOUT and STDERR are started</span>
271     * <p>
272     * It is possible to wait for the process to get to a warmed-up state
273     * via {@linkplain Predicate} condition on the STDOUT. The warm-up will
274     * wait indefinitely.
275     * </p>
276     * @param name The process name
277     * @param processBuilder The process builder
278     * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
279     *                      Used to determine the moment the target app is
280     *                      properly warmed-up.
281     *                      It can be null - in that case the warmup is skipped.
282     * @return Returns the initialized {@linkplain Process}
283     * @throws IOException
284     * @throws InterruptedException
285     * @throws TimeoutException
286     */
287    @SuppressWarnings("overloads")
288    public static Process startProcess(String name,
289                                       ProcessBuilder processBuilder,
290                                       final Predicate<String> linePredicate)
291    throws IOException, InterruptedException, TimeoutException {
292        return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS);
293    }
294
295    /**
296     * Get the process id of the current running Java process
297     *
298     * @return Process id
299     */
300    public static long getProcessId() throws Exception {
301        return ProcessHandle.current().pid();
302    }
303
304
305
306    /**
307     * Create ProcessBuilder using the java launcher from the jdk to be tested and
308     * with any platform specific arguments prepended
309     */
310    public static ProcessBuilder createJavaProcessBuilder(String... command) {
311        return createJavaProcessBuilder(false, command);
312    }
313
314    /**
315     * Create ProcessBuilder using the java launcher from the jdk to be tested,
316     * and with any platform specific arguments prepended.
317     *
318     * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts
319     *        to the java arguments.
320     * @param command Arguments to pass to the java command.
321     * @return The ProcessBuilder instance representing the java command.
322     */
323    public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) {
324        String javapath = JDKToolFinder.getJDKTool("java");
325
326        ArrayList<String> args = new ArrayList<>();
327        args.add(javapath);
328
329        args.add("-cp");
330        args.add(System.getProperty("java.class.path"));
331
332        if (addTestVmAndJavaOptions) {
333            Collections.addAll(args, Utils.getTestJavaOpts());
334        }
335
336        Collections.addAll(args, command);
337
338        // Reporting
339        StringBuilder cmdLine = new StringBuilder();
340        for (String cmd : args)
341            cmdLine.append(cmd).append(' ');
342        System.out.println("Command line: [" + cmdLine.toString() + "]");
343
344        return new ProcessBuilder(args.toArray(new String[args.size()]));
345    }
346
347    private static void printStack(Thread t, StackTraceElement[] stack) {
348        System.out.println("\t" +  t +
349                           " stack: (length = " + stack.length + ")");
350        if (t != null) {
351            for (StackTraceElement stack1 : stack) {
352                System.out.println("\t" + stack1);
353            }
354            System.out.println();
355        }
356    }
357
358    /**
359     * Executes a test jvm process, waits for it to finish and returns the process output.
360     * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added.
361     * The java from the test.jdk is used to execute the command.
362     *
363     * The command line will be like:
364     * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
365     *
366     * The jvm process will have exited before this method returns.
367     *
368     * @param cmds User specified arguments.
369     * @return The output from the process.
370     */
371    public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception {
372        ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds));
373        return executeProcess(pb);
374    }
375
376    /**
377     * @see #executeTestJvm(String...)
378     * @param cmds User specified arguments.
379     * @return The output from the process.
380     */
381    public static OutputAnalyzer executeTestJava(String... cmds) throws Exception {
382        return executeTestJvm(cmds);
383    }
384
385    /**
386     * Executes a process, waits for it to finish and returns the process output.
387     * The process will have exited before this method returns.
388     * @param pb The ProcessBuilder to execute.
389     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
390     */
391    public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
392        OutputAnalyzer output = null;
393        Process p = null;
394        boolean failed = false;
395        try {
396            p = pb.start();
397            output = new OutputAnalyzer(p);
398            p.waitFor();
399
400            return output;
401        } catch (Throwable t) {
402            if (p != null) {
403                p.destroyForcibly().waitFor();
404            }
405
406            failed = true;
407            System.out.println("executeProcess() failed: " + t);
408            throw t;
409        } finally {
410            if (failed) {
411                System.err.println(getProcessLog(pb, output));
412            }
413        }
414    }
415
416    /**
417     * Executes a process, waits for it to finish and returns the process output.
418     *
419     * The process will have exited before this method returns.
420     *
421     * @param cmds The command line to execute.
422     * @return The output from the process.
423     */
424    public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
425        return executeProcess(new ProcessBuilder(cmds));
426    }
427
428    /**
429     * Used to log command line, stdout, stderr and exit code from an executed process.
430     * @param pb The executed process.
431     * @param output The output from the process.
432     */
433    public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
434        String stderr = output == null ? "null" : output.getStderr();
435        String stdout = output == null ? "null" : output.getStdout();
436        String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
437        StringBuilder logMsg = new StringBuilder();
438        final String nl = System.getProperty("line.separator");
439        logMsg.append("--- ProcessLog ---" + nl);
440        logMsg.append("cmd: " + getCommandLine(pb) + nl);
441        logMsg.append("exitvalue: " + exitValue + nl);
442        logMsg.append("stderr: " + stderr + nl);
443        logMsg.append("stdout: " + stdout + nl);
444
445        return logMsg.toString();
446    }
447
448    /**
449     * @return The full command line for the ProcessBuilder.
450     */
451    public static String getCommandLine(ProcessBuilder pb) {
452        if (pb == null) {
453            return "null";
454        }
455        StringBuilder cmd = new StringBuilder();
456        for (String s : pb.command()) {
457            cmd.append(s).append(" ");
458        }
459        return cmd.toString().trim();
460    }
461
462    /**
463     * Executes a process, waits for it to finish, prints the process output
464     * to stdout, and returns the process output.
465     *
466     * The process will have exited before this method returns.
467     *
468     * @param cmds The command line to execute.
469     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
470     */
471    public static OutputAnalyzer executeCommand(String... cmds)
472            throws Throwable {
473        String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
474        System.out.println("Command line: [" + cmdLine + "]");
475        OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
476        System.out.println(analyzer.getOutput());
477        return analyzer;
478    }
479
480    /**
481     * Executes a process, waits for it to finish, prints the process output
482     * to stdout and returns the process output.
483     *
484     * The process will have exited before this method returns.
485     *
486     * @param pb The ProcessBuilder to execute.
487     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
488     */
489    public static OutputAnalyzer executeCommand(ProcessBuilder pb)
490            throws Throwable {
491        String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
492        System.out.println("Command line: [" + cmdLine + "]");
493        OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
494        System.out.println(analyzer.getOutput());
495        return analyzer;
496    }
497
498    private static class ProcessImpl extends Process {
499
500        private final Process p;
501        private final Future<Void> stdoutTask;
502        private final Future<Void> stderrTask;
503
504        public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
505            this.p = p;
506            this.stdoutTask = stdoutTask;
507            this.stderrTask = stderrTask;
508        }
509
510        @Override
511        public OutputStream getOutputStream() {
512            return p.getOutputStream();
513        }
514
515        @Override
516        public InputStream getInputStream() {
517            return p.getInputStream();
518        }
519
520        @Override
521        public InputStream getErrorStream() {
522            return p.getErrorStream();
523        }
524
525        @Override
526        public int waitFor() throws InterruptedException {
527            int rslt = p.waitFor();
528            waitForStreams();
529            return rslt;
530        }
531
532        @Override
533        public int exitValue() {
534            return p.exitValue();
535        }
536
537        @Override
538        public void destroy() {
539            p.destroy();
540        }
541
542        @Override
543        public long pid() {
544            return p.pid();
545        }
546
547        @Override
548        public boolean isAlive() {
549            return p.isAlive();
550        }
551
552        @Override
553        public Process destroyForcibly() {
554            return p.destroyForcibly();
555        }
556
557        @Override
558        public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
559            boolean rslt = p.waitFor(timeout, unit);
560            if (rslt) {
561                waitForStreams();
562            }
563            return rslt;
564        }
565
566        private void waitForStreams() throws InterruptedException {
567            try {
568                stdoutTask.get();
569            } catch (ExecutionException e) {
570            }
571            try {
572                stderrTask.get();
573            } catch (ExecutionException e) {
574            }
575        }
576    }
577}
578