1 /*
   2  * Copyright (c) 2013, 2015, 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 
  24 package jdk.test.lib.process;
  25 
  26 import java.io.ByteArrayOutputStream;
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.io.OutputStream;
  30 import java.io.PrintStream;
  31 import java.lang.management.ManagementFactory;
  32 import java.lang.management.RuntimeMXBean;
  33 import java.util.ArrayList;
  34 import java.util.Arrays;
  35 import java.util.Collections;
  36 import java.util.concurrent.CountDownLatch;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.concurrent.ExecutionException;
  40 import java.util.concurrent.Future;
  41 import java.util.concurrent.TimeUnit;
  42 import java.util.concurrent.TimeoutException;
  43 import java.util.function.Predicate;
  44 import java.util.function.Consumer;
  45 import java.util.stream.Collectors;
  46 
  47 import jdk.test.lib.JDKToolFinder;
  48 import jdk.test.lib.Platform;
  49 import jdk.test.lib.Utils;
  50 
  51 public 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) throws Exception {
 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) throws Exception {
 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 }