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/share/classes/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     * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris)
264     *
265     * @return String[] with platform specific arguments, empty if there are
266     *         none
267     */
268    public static String[] getPlatformSpecificVMArgs() {
269        String osName = System.getProperty("os.name");
270        String dataModel = System.getProperty("sun.arch.data.model");
271
272        if (osName.equals("SunOS") && dataModel.equals("64")) {
273            return new String[] { "-d64" };
274        }
275
276        return new String[] {};
277    }
278
279    /**
280     * Create ProcessBuilder using the java launcher from the jdk to be tested,
281     * and with any platform specific arguments prepended.
282     *
283     * @param command Arguments to pass to the java command.
284     * @return The ProcessBuilder instance representing the java command.
285     */
286    public static ProcessBuilder createJavaProcessBuilder(String... command)
287            throws Exception {
288        return createJavaProcessBuilder(false, command);
289    }
290
291    /**
292     * Create ProcessBuilder using the java launcher from the jdk to be tested,
293     * and with any platform specific arguments prepended.
294     *
295     * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts
296     *        to the java arguments.
297     * @param command Arguments to pass to the java command.
298     * @return The ProcessBuilder instance representing the java command.
299     */
300    public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) throws Exception {
301        String javapath = JDKToolFinder.getJDKTool("java");
302
303        ArrayList<String> args = new ArrayList<>();
304        args.add(javapath);
305        Collections.addAll(args, getPlatformSpecificVMArgs());
306
307        if (addTestVmAndJavaOptions) {
308            // -cp is needed to make sure the same classpath is used whether the test is
309            // run in AgentVM mode or OtherVM mode. It was added to the hotspot version
310            // of this API as part of 8077608. However, for the jdk version it is only
311            // added when addTestVmAndJavaOptions is true in order to minimize
312            // disruption to existing JDK tests, which have yet to be tested with -cp
313            // being added. At some point -cp should always be added to be consistent
314            // with what the hotspot version does.
315            args.add("-cp");
316            args.add(System.getProperty("java.class.path"));
317            Collections.addAll(args, Utils.getTestJavaOpts());
318        }
319
320        Collections.addAll(args, command);
321
322        // Reporting
323        StringBuilder cmdLine = new StringBuilder();
324        for (String cmd : args)
325            cmdLine.append(cmd).append(' ');
326        System.out.println("Command line: [" + cmdLine.toString() + "]");
327
328        return new ProcessBuilder(args.toArray(new String[args.size()]));
329    }
330
331    private static void printStack(Thread t, StackTraceElement[] stack) {
332        System.out.println("\t" +  t +
333                           " stack: (length = " + stack.length + ")");
334        if (t != null) {
335            for (StackTraceElement stack1 : stack) {
336                System.out.println("\t" + stack1);
337            }
338            System.out.println();
339        }
340    }
341
342    /**
343     * Executes a test java process, waits for it to finish and returns the process output.
344     * The default options from jtreg, test.vm.opts and test.java.opts, are added.
345     * The java from the test.jdk is used to execute the command.
346     *
347     * The command line will be like:
348     * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
349     *
350     * The java process will have exited before this method returns.
351     *
352     * @param cmds User specifed arguments.
353     * @return The output from the process.
354     */
355    public static OutputAnalyzer executeTestJava(String... options) throws Exception {
356        ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(options));
357        return executeProcess(pb);
358    }
359
360    /**
361     * @deprecated Use executeTestJava instead
362     */
363    public static OutputAnalyzer executeTestJvm(String... options) throws Exception {
364        return executeTestJava(options);
365    }
366
367    /**
368     * Executes a process, waits for it to finish and returns the process output.
369     * The process will have exited before this method returns.
370     * @param pb The ProcessBuilder to execute.
371     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
372     */
373    public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
374        return executeProcess(pb, null);
375    }
376
377    /**
378     * Executes a process, pipe some text into its STDIN, waits for it
379     * to finish and returns the process output. The process will have exited
380     * before this method returns.
381     * @param pb The ProcessBuilder to execute.
382     * @param input The text to pipe into STDIN. Can be null.
383     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
384     */
385    public static OutputAnalyzer executeProcess(ProcessBuilder pb, String input)
386            throws Exception {
387        OutputAnalyzer output = null;
388        Process p = null;
389        boolean failed = false;
390        try {
391            p = pb.start();
392            if (input != null) {
393                try (OutputStream os = p.getOutputStream();
394                        PrintStream ps = new PrintStream(os)) {
395                    ps.print(input);
396                    ps.flush();
397                }
398            }
399            output = new OutputAnalyzer(p);
400            p.waitFor();
401
402            return output;
403        } catch (Throwable t) {
404            if (p != null) {
405                p.destroyForcibly().waitFor();
406            }
407
408            failed = true;
409            System.out.println("executeProcess() failed: " + t);
410            throw t;
411        } finally {
412            if (failed) {
413                System.err.println(getProcessLog(pb, output));
414            }
415        }
416    }
417
418    /**
419     * Executes a process, waits for it to finish and returns the process output.
420     *
421     * The process will have exited before this method returns.
422     *
423     * @param cmds The command line to execute.
424     * @return The output from the process.
425     */
426    public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
427        return executeProcess(new ProcessBuilder(cmds));
428    }
429
430    /**
431     * Used to log command line, stdout, stderr and exit code from an executed process.
432     * @param pb The executed process.
433     * @param output The output from the process.
434     */
435    public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
436        String stderr = output == null ? "null" : output.getStderr();
437        String stdout = output == null ? "null" : output.getStdout();
438        String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
439        StringBuilder logMsg = new StringBuilder();
440        final String nl = System.getProperty("line.separator");
441        logMsg.append("--- ProcessLog ---" + nl);
442        logMsg.append("cmd: " + getCommandLine(pb) + nl);
443        logMsg.append("exitvalue: " + exitValue + nl);
444        logMsg.append("stderr: " + stderr + nl);
445        logMsg.append("stdout: " + stdout + nl);
446
447        return logMsg.toString();
448    }
449
450    /**
451     * @return The full command line for the ProcessBuilder.
452     */
453    public static String getCommandLine(ProcessBuilder pb) {
454        if (pb == null) {
455            return "null";
456        }
457        StringBuilder cmd = new StringBuilder();
458        for (String s : pb.command()) {
459            cmd.append(s).append(" ");
460        }
461        return cmd.toString().trim();
462    }
463
464    /**
465     * Executes a process, waits for it to finish, prints the process output
466     * to stdout, and returns the process output.
467     *
468     * The process will have exited before this method returns.
469     *
470     * @param cmds The command line to execute.
471     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
472     */
473    public static OutputAnalyzer executeCommand(String... cmds)
474            throws Throwable {
475        String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
476        System.out.println("Command line: [" + cmdLine + "]");
477        OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
478        System.out.println(analyzer.getOutput());
479        return analyzer;
480    }
481
482    /**
483     * Executes a process, waits for it to finish, prints the process output
484     * to stdout and returns the process output.
485     *
486     * The process will have exited before this method returns.
487     *
488     * @param pb The ProcessBuilder to execute.
489     * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
490     */
491    public static OutputAnalyzer executeCommand(ProcessBuilder pb)
492            throws Throwable {
493        String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
494        System.out.println("Command line: [" + cmdLine + "]");
495        OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
496        System.out.println(analyzer.getOutput());
497        return analyzer;
498    }
499
500    private static class ProcessImpl extends Process {
501
502        private final Process p;
503        private final Future<Void> stdoutTask;
504        private final Future<Void> stderrTask;
505
506        public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
507            this.p = p;
508            this.stdoutTask = stdoutTask;
509            this.stderrTask = stderrTask;
510        }
511
512        @Override
513        public OutputStream getOutputStream() {
514            return p.getOutputStream();
515        }
516
517        @Override
518        public InputStream getInputStream() {
519            return p.getInputStream();
520        }
521
522        @Override
523        public InputStream getErrorStream() {
524            return p.getErrorStream();
525        }
526
527        @Override
528        public int waitFor() throws InterruptedException {
529            int rslt = p.waitFor();
530            waitForStreams();
531            return rslt;
532        }
533
534        @Override
535        public int exitValue() {
536            return p.exitValue();
537        }
538
539        @Override
540        public void destroy() {
541            p.destroy();
542        }
543
544        @Override
545        public long pid() {
546            return p.pid();
547        }
548
549        @Override
550        public boolean isAlive() {
551            return p.isAlive();
552        }
553
554        @Override
555        public Process destroyForcibly() {
556            return p.destroyForcibly();
557        }
558
559        @Override
560        public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
561            boolean rslt = p.waitFor(timeout, unit);
562            if (rslt) {
563                waitForStreams();
564            }
565            return rslt;
566        }
567
568        private void waitForStreams() throws InterruptedException {
569            try {
570                stdoutTask.get();
571            } catch (ExecutionException e) {
572            }
573            try {
574                stderrTask.get();
575            } catch (ExecutionException e) {
576            }
577        }
578    }
579}
580