ProcessTools.java revision 2567:03fe61bb7670
1182918Smarius/*
2182918Smarius * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
3182918Smarius * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4182918Smarius *
5182918Smarius * This code is free software; you can redistribute it and/or modify it
6182918Smarius * under the terms of the GNU General Public License version 2 only, as
7182918Smarius * published by the Free Software Foundation.
8182918Smarius *
9182918Smarius * This code is distributed in the hope that it will be useful, but WITHOUT
10182918Smarius * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11182918Smarius * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12182918Smarius * version 2 for more details (a copy is included in the LICENSE file that
13182918Smarius * accompanied this code).
14182918Smarius *
15182918Smarius * You should have received a copy of the GNU General Public License version
16182918Smarius * 2 along with this work; if not, write to the Free Software Foundation,
17182918Smarius * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18182918Smarius *
19182918Smarius * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20182918Smarius * or visit www.oracle.com if you need additional information or have any
21182918Smarius * questions.
22182918Smarius */
23182918Smarius
24182918Smariuspackage jdk.test.lib.process;
25182918Smarius
26182918Smariusimport java.io.ByteArrayOutputStream;
27182918Smariusimport java.io.IOException;
28182918Smariusimport java.io.InputStream;
29182918Smariusimport java.io.OutputStream;
30182918Smariusimport java.io.PrintStream;
31182918Smariusimport java.util.ArrayList;
32182918Smariusimport java.util.Arrays;
33182918Smariusimport java.util.Collections;
34182918Smariusimport java.util.concurrent.CountDownLatch;
35182918Smariusimport java.util.Map;
36182918Smariusimport java.util.concurrent.ExecutionException;
37182918Smariusimport java.util.concurrent.Future;
38182918Smariusimport java.util.concurrent.TimeUnit;
39182918Smariusimport java.util.concurrent.TimeoutException;
40182918Smariusimport java.util.function.Predicate;
41182918Smariusimport java.util.function.Consumer;
42182918Smariusimport java.util.stream.Collectors;
43197164Smarius
44197164Smariusimport jdk.test.lib.JDKToolFinder;
45197164Smariusimport jdk.test.lib.Utils;
46197164Smarius
47182918Smariuspublic final class ProcessTools {
48182918Smarius    private static final class LineForwarder extends StreamPumper.LinePump {
49182918Smarius        private final PrintStream ps;
50182918Smarius        private final String prefix;
51182918Smarius        LineForwarder(String prefix, PrintStream os) {
52182918Smarius            this.ps = os;
53182918Smarius            this.prefix = prefix;
54182918Smarius        }
55182918Smarius        @Override
56182918Smarius        protected void processLine(String line) {
57182918Smarius            ps.println("[" + prefix + "] " + line);
58182918Smarius        }
59182918Smarius    }
60182918Smarius
61182918Smarius    private ProcessTools() {
62182918Smarius    }
63182918Smarius
64182918Smarius    /**
65182918Smarius     * Pumps stdout and stderr from running the process into a String.
66182918Smarius     *
67182918Smarius     * @param processHandler ProcessHandler to run.
68182918Smarius     * @return Output from process.
69182918Smarius     * @throws IOException If an I/O error occurs.
70182918Smarius     */
71182918Smarius    public static OutputBuffer getOutput(ProcessBuilder processBuilder) throws IOException {
72182918Smarius        return getOutput(processBuilder.start());
73182918Smarius    }
74182918Smarius
75182918Smarius    /**
76182918Smarius     * Pumps stdout and stderr the running process into a String.
77182918Smarius     *
78182918Smarius     * @param process Process to pump.
79182918Smarius     * @return Output from process.
80182918Smarius     * @throws IOException If an I/O error occurs.
81182918Smarius     */
82182918Smarius    public static OutputBuffer getOutput(Process process) throws IOException {
83182918Smarius        ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream();
84182918Smarius        ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
85227848Smarius        StreamPumper outPumper = new StreamPumper(process.getInputStream(), stdoutBuffer);
86182918Smarius        StreamPumper errPumper = new StreamPumper(process.getErrorStream(), stderrBuffer);
87182918Smarius        Thread outPumperThread = new Thread(outPumper);
88182918Smarius        Thread errPumperThread = new Thread(errPumper);
89182918Smarius
90182918Smarius        outPumperThread.setDaemon(true);
91182918Smarius        errPumperThread.setDaemon(true);
92182918Smarius
93182918Smarius        outPumperThread.start();
94182918Smarius        errPumperThread.start();
95182918Smarius
96182918Smarius        try {
97182918Smarius            process.waitFor();
98182918Smarius            outPumperThread.join();
99182918Smarius            errPumperThread.join();
100182918Smarius        } catch (InterruptedException e) {
101182918Smarius            Thread.currentThread().interrupt();
102182918Smarius            return null;
103182918Smarius        }
104182918Smarius
105182918Smarius        return new OutputBuffer(stdoutBuffer.toString(), stderrBuffer.toString());
106182918Smarius    }
107182918Smarius
108182918Smarius    /**
109182918Smarius     * <p>Starts a process from its builder.</p>
110182918Smarius     * <span>The default redirects of STDOUT and STDERR are started</span>
111182918Smarius     * @param name The process name
112182918Smarius     * @param processBuilder The process builder
113182918Smarius     * @return Returns the initialized process
114182918Smarius     * @throws IOException
115182918Smarius     */
116182918Smarius    public static Process startProcess(String name,
117182918Smarius                                       ProcessBuilder processBuilder)
118182918Smarius    throws IOException {
119182918Smarius        return startProcess(name, processBuilder, (Consumer<String>)null);
120182918Smarius    }
121182918Smarius
122182918Smarius    /**
123182918Smarius     * <p>Starts a process from its builder.</p>
124182918Smarius     * <span>The default redirects of STDOUT and STDERR are started</span>
125182918Smarius     * <p>It is possible to monitor the in-streams via the provided {@code consumer}
126182918Smarius     * @param name The process name
127182918Smarius     * @param consumer {@linkplain Consumer} instance to process the in-streams
128182918Smarius     * @param processBuilder The process builder
129182918Smarius     * @return Returns the initialized process
130182918Smarius     * @throws IOException
131182918Smarius     */
132182918Smarius    @SuppressWarnings("overloads")
133182918Smarius    public static Process startProcess(String name,
134182918Smarius                                       ProcessBuilder processBuilder,
135182918Smarius                                       Consumer<String> consumer)
136182918Smarius    throws IOException {
137182918Smarius        try {
138182918Smarius            return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS);
139182918Smarius        } catch (InterruptedException | TimeoutException e) {
140182918Smarius            // will never happen
141182918Smarius            throw new RuntimeException(e);
142182918Smarius        }
143182918Smarius    }
144182918Smarius
145182918Smarius    /**
146182918Smarius     * <p>Starts a process from its builder.</p>
147182918Smarius     * <span>The default redirects of STDOUT and STDERR are started</span>
148182918Smarius     * <p>
149182918Smarius     * It is possible to wait for the process to get to a warmed-up state
150182918Smarius     * via {@linkplain Predicate} condition on the STDOUT
151182918Smarius     * </p>
152182918Smarius     * @param name The process name
153182918Smarius     * @param processBuilder The process builder
154182918Smarius     * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
155182918Smarius     *                      Used to determine the moment the target app is
156182918Smarius     *                      properly warmed-up.
157197164Smarius     *                      It can be null - in that case the warmup is skipped.
158182918Smarius     * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
159182918Smarius     * @param unit The timeout {@linkplain TimeUnit}
160182918Smarius     * @return Returns the initialized {@linkplain Process}
161182918Smarius     * @throws IOException
162182918Smarius     * @throws InterruptedException
163182918Smarius     * @throws TimeoutException
164182918Smarius     */
165182918Smarius    public static Process startProcess(String name,
166182918Smarius                                       ProcessBuilder processBuilder,
167182918Smarius                                       final Predicate<String> linePredicate,
168182918Smarius                                       long timeout,
169182918Smarius                                       TimeUnit unit)
170182918Smarius    throws IOException, InterruptedException, TimeoutException {
171182918Smarius        return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
172182918Smarius    }
173182918Smarius
174182918Smarius    /**
175182918Smarius     * <p>Starts a process from its builder.</p>
176182918Smarius     * <span>The default redirects of STDOUT and STDERR are started</span>
177182918Smarius     * <p>
178182918Smarius     * It is possible to wait for the process to get to a warmed-up state
179182918Smarius     * via {@linkplain Predicate} condition on the STDOUT and monitor the
180182918Smarius     * in-streams via the provided {@linkplain Consumer}
181182918Smarius     * </p>
182182918Smarius     * @param name The process name
183182918Smarius     * @param processBuilder The process builder
184182918Smarius     * @param lineConsumer  The {@linkplain Consumer} the lines will be forwarded to
185182918Smarius     * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
186182918Smarius     *                      Used to determine the moment the target app is
187182918Smarius     *                      properly warmed-up.
188182918Smarius     *                      It can be null - in that case the warmup is skipped.
189182918Smarius     * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
190182918Smarius     * @param unit The timeout {@linkplain TimeUnit}
191182918Smarius     * @return Returns the initialized {@linkplain Process}
192182918Smarius     * @throws IOException
193182918Smarius     * @throws InterruptedException
194182918Smarius     * @throws TimeoutException
195182918Smarius     */
196182918Smarius    public static Process startProcess(String name,
197182918Smarius                                       ProcessBuilder processBuilder,
198182918Smarius                                       final Consumer<String> lineConsumer,
199182918Smarius                                       final Predicate<String> linePredicate,
200182918Smarius                                       long timeout,
201182918Smarius                                       TimeUnit unit)
202182918Smarius    throws IOException, InterruptedException, TimeoutException {
203182918Smarius        System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
204182918Smarius        Process p = processBuilder.start();
205182918Smarius        StreamPumper stdout = new StreamPumper(p.getInputStream());
206182918Smarius        StreamPumper stderr = new StreamPumper(p.getErrorStream());
207182918Smarius
208182918Smarius        stdout.addPump(new LineForwarder(name, System.out));
209182918Smarius        stderr.addPump(new LineForwarder(name, System.err));
210182918Smarius        if (lineConsumer != null) {
211182918Smarius            StreamPumper.LinePump pump = new StreamPumper.LinePump() {
212182918Smarius                @Override
213182918Smarius                protected void processLine(String line) {
214182918Smarius                    lineConsumer.accept(line);
215182918Smarius                }
216182918Smarius            };
217182918Smarius            stdout.addPump(pump);
218182918Smarius            stderr.addPump(pump);
219182918Smarius        }
220182918Smarius
221182918Smarius
222182918Smarius        CountDownLatch latch = new CountDownLatch(1);
223182918Smarius        if (linePredicate != null) {
224182918Smarius            StreamPumper.LinePump pump = new StreamPumper.LinePump() {
225182918Smarius                @Override
226182918Smarius                protected void processLine(String line) {
227182918Smarius                    if (latch.getCount() > 0 && linePredicate.test(line)) {
228182918Smarius                        latch.countDown();
229182918Smarius                    }
230182918Smarius                }
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