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