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