ProcessTools.java revision 2736:5bb00344fa36
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 processBuilder ProcessBuilder 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 specifed 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     * Executes a process, waits for it to finish and returns the process output.
378     * The process will have exited before this method returns.
379     * @param pb The ProcessBuilder to execute.
380     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
381     */
382    public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
383        OutputAnalyzer output = null;
384        Process p = null;
385        boolean failed = false;
386        try {
387            p = pb.start();
388            output = new OutputAnalyzer(p);
389            p.waitFor();
390
391            return output;
392        } catch (Throwable t) {
393            if (p != null) {
394                p.destroyForcibly().waitFor();
395            }
396
397            failed = true;
398            System.out.println("executeProcess() failed: " + t);
399            throw t;
400        } finally {
401            if (failed) {
402                System.err.println(getProcessLog(pb, output));
403            }
404        }
405    }
406
407    /**
408     * Executes a process, waits for it to finish and returns the process output.
409     *
410     * The process will have exited before this method returns.
411     *
412     * @param cmds The command line to execute.
413     * @return The output from the process.
414     */
415    public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
416        return executeProcess(new ProcessBuilder(cmds));
417    }
418
419    /**
420     * Used to log command line, stdout, stderr and exit code from an executed process.
421     * @param pb The executed process.
422     * @param output The output from the process.
423     */
424    public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
425        String stderr = output == null ? "null" : output.getStderr();
426        String stdout = output == null ? "null" : output.getStdout();
427        String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
428        StringBuilder logMsg = new StringBuilder();
429        final String nl = System.getProperty("line.separator");
430        logMsg.append("--- ProcessLog ---" + nl);
431        logMsg.append("cmd: " + getCommandLine(pb) + nl);
432        logMsg.append("exitvalue: " + exitValue + nl);
433        logMsg.append("stderr: " + stderr + nl);
434        logMsg.append("stdout: " + stdout + nl);
435
436        return logMsg.toString();
437    }
438
439    /**
440     * @return The full command line for the ProcessBuilder.
441     */
442    public static String getCommandLine(ProcessBuilder pb) {
443        if (pb == null) {
444            return "null";
445        }
446        StringBuilder cmd = new StringBuilder();
447        for (String s : pb.command()) {
448            cmd.append(s).append(" ");
449        }
450        return cmd.toString().trim();
451    }
452
453    /**
454     * Executes a process, waits for it to finish, prints the process output
455     * to stdout, and returns the process output.
456     *
457     * The process will have exited before this method returns.
458     *
459     * @param cmds The command line to execute.
460     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
461     */
462    public static OutputAnalyzer executeCommand(String... cmds)
463            throws Throwable {
464        String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
465        System.out.println("Command line: [" + cmdLine + "]");
466        OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
467        System.out.println(analyzer.getOutput());
468        return analyzer;
469    }
470
471    /**
472     * Executes a process, waits for it to finish, prints the process output
473     * to stdout and returns the process output.
474     *
475     * The process will have exited before this method returns.
476     *
477     * @param pb The ProcessBuilder to execute.
478     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
479     */
480    public static OutputAnalyzer executeCommand(ProcessBuilder pb)
481            throws Throwable {
482        String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
483        System.out.println("Command line: [" + cmdLine + "]");
484        OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
485        System.out.println(analyzer.getOutput());
486        return analyzer;
487    }
488
489    private static class ProcessImpl extends Process {
490
491        private final Process p;
492        private final Future<Void> stdoutTask;
493        private final Future<Void> stderrTask;
494
495        public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
496            this.p = p;
497            this.stdoutTask = stdoutTask;
498            this.stderrTask = stderrTask;
499        }
500
501        @Override
502        public OutputStream getOutputStream() {
503            return p.getOutputStream();
504        }
505
506        @Override
507        public InputStream getInputStream() {
508            return p.getInputStream();
509        }
510
511        @Override
512        public InputStream getErrorStream() {
513            return p.getErrorStream();
514        }
515
516        @Override
517        public int waitFor() throws InterruptedException {
518            int rslt = p.waitFor();
519            waitForStreams();
520            return rslt;
521        }
522
523        @Override
524        public int exitValue() {
525            return p.exitValue();
526        }
527
528        @Override
529        public void destroy() {
530            p.destroy();
531        }
532
533        @Override
534        public long pid() {
535            return p.pid();
536        }
537
538        @Override
539        public boolean isAlive() {
540            return p.isAlive();
541        }
542
543        @Override
544        public Process destroyForcibly() {
545            return p.destroyForcibly();
546        }
547
548        @Override
549        public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
550            boolean rslt = p.waitFor(timeout, unit);
551            if (rslt) {
552                waitForStreams();
553            }
554            return rslt;
555        }
556
557        private void waitForStreams() throws InterruptedException {
558            try {
559                stdoutTask.get();
560            } catch (ExecutionException e) {
561            }
562            try {
563                stderrTask.get();
564            } catch (ExecutionException e) {
565            }
566        }
567    }
568}
569