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