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 
  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.util.ArrayList;
  32 import java.util.Arrays;
  33 import java.util.Collections;
  34 import java.util.concurrent.CountDownLatch;
  35 import java.util.Map;
  36 import java.util.concurrent.ExecutionException;
  37 import java.util.concurrent.Future;
  38 import java.util.concurrent.TimeUnit;
  39 import java.util.concurrent.TimeoutException;
  40 import java.util.function.Predicate;
  41 import java.util.function.Consumer;
  42 import java.util.stream.Collectors;
  43 
  44 import jdk.test.lib.JDKToolFinder;
  45 import jdk.test.lib.Utils;
  46 
  47 public final class ProcessTools {
  48     private static final class LineForwarder extends StreamPumper.LinePump {
  49         private final PrintStream ps;
  50         private final String prefix;
  51         LineForwarder(String prefix, PrintStream os) {
  52             this.ps = os;
  53             this.prefix = prefix;
  54         }
  55         @Override
  56         protected void processLine(String line) {
  57             ps.println("[" + prefix + "] " + line);
  58         }
  59     }
  60 
  61     private ProcessTools() {
  62     }
  63 
  64     /**
  65      * Pumps stdout and stderr from running the process into a String.
  66      *
  67      * @param processBuilder ProcessBuilder to run.
  68      * @return Output from process.
  69      * @throws IOException If an I/O error occurs.
  70      */
  71     public static OutputBuffer getOutput(ProcessBuilder processBuilder) throws IOException {
  72         return getOutput(processBuilder.start());
  73     }
  74 
  75     /**
  76      * Pumps stdout and stderr the running process into a String.
  77      *
  78      * @param process Process to pump.
  79      * @return Output from process.
  80      * @throws IOException If an I/O error occurs.
  81      */
  82     public static OutputBuffer getOutput(Process process) throws IOException {
  83         ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream();
  84         ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream();
  85         StreamPumper outPumper = new StreamPumper(process.getInputStream(), stdoutBuffer);
  86         StreamPumper errPumper = new StreamPumper(process.getErrorStream(), stderrBuffer);
  87         Thread outPumperThread = new Thread(outPumper);
  88         Thread errPumperThread = new Thread(errPumper);
  89 
  90         outPumperThread.setDaemon(true);
  91         errPumperThread.setDaemon(true);
  92 
  93         outPumperThread.start();
  94         errPumperThread.start();
  95 
  96         try {
  97             process.waitFor();
  98             outPumperThread.join();
  99             errPumperThread.join();
 100         } catch (InterruptedException e) {
 101             Thread.currentThread().interrupt();
 102             return null;
 103         }
 104 
 105         return new OutputBuffer(stdoutBuffer.toString(), stderrBuffer.toString());
 106     }
 107 
 108     /**
 109      * <p>Starts a process from its builder.</p>
 110      * <span>The default redirects of STDOUT and STDERR are started</span>
 111      * @param name The process name
 112      * @param processBuilder The process builder
 113      * @return Returns the initialized process
 114      * @throws IOException
 115      */
 116     public static Process startProcess(String name,
 117                                        ProcessBuilder processBuilder)
 118     throws IOException {
 119         return startProcess(name, processBuilder, (Consumer<String>)null);
 120     }
 121 
 122     /**
 123      * <p>Starts a process from its builder.</p>
 124      * <span>The default redirects of STDOUT and STDERR are started</span>
 125      * <p>It is possible to monitor the in-streams via the provided {@code consumer}
 126      * @param name The process name
 127      * @param consumer {@linkplain Consumer} instance to process the in-streams
 128      * @param processBuilder The process builder
 129      * @return Returns the initialized process
 130      * @throws IOException
 131      */
 132     @SuppressWarnings("overloads")
 133     public static Process startProcess(String name,
 134                                        ProcessBuilder processBuilder,
 135                                        Consumer<String> consumer)
 136     throws IOException {
 137         try {
 138             return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS);
 139         } catch (InterruptedException | TimeoutException e) {
 140             // will never happen
 141             throw new RuntimeException(e);
 142         }
 143     }
 144 
 145     /**
 146      * <p>Starts a process from its builder.</p>
 147      * <span>The default redirects of STDOUT and STDERR are started</span>
 148      * <p>
 149      * It is possible to wait for the process to get to a warmed-up state
 150      * via {@linkplain Predicate} condition on the STDOUT
 151      * </p>
 152      * @param name The process name
 153      * @param processBuilder The process builder
 154      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 155      *                      Used to determine the moment the target app is
 156      *                      properly warmed-up.
 157      *                      It can be null - in that case the warmup is skipped.
 158      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 159      * @param unit The timeout {@linkplain TimeUnit}
 160      * @return Returns the initialized {@linkplain Process}
 161      * @throws IOException
 162      * @throws InterruptedException
 163      * @throws TimeoutException
 164      */
 165     public static Process startProcess(String name,
 166                                        ProcessBuilder processBuilder,
 167                                        final Predicate<String> linePredicate,
 168                                        long timeout,
 169                                        TimeUnit unit)
 170     throws IOException, InterruptedException, TimeoutException {
 171         return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
 172     }
 173 
 174     /**
 175      * <p>Starts a process from its builder.</p>
 176      * <span>The default redirects of STDOUT and STDERR are started</span>
 177      * <p>
 178      * It is possible to wait for the process to get to a warmed-up state
 179      * via {@linkplain Predicate} condition on the STDOUT and monitor the
 180      * in-streams via the provided {@linkplain Consumer}
 181      * </p>
 182      * @param name The process name
 183      * @param processBuilder The process builder
 184      * @param lineConsumer  The {@linkplain Consumer} the lines will be forwarded to
 185      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 186      *                      Used to determine the moment the target app is
 187      *                      properly warmed-up.
 188      *                      It can be null - in that case the warmup is skipped.
 189      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 190      * @param unit The timeout {@linkplain TimeUnit}
 191      * @return Returns the initialized {@linkplain Process}
 192      * @throws IOException
 193      * @throws InterruptedException
 194      * @throws TimeoutException
 195      */
 196     public static Process startProcess(String name,
 197                                        ProcessBuilder processBuilder,
 198                                        final Consumer<String> lineConsumer,
 199                                        final Predicate<String> linePredicate,
 200                                        long timeout,
 201                                        TimeUnit unit)
 202     throws IOException, InterruptedException, TimeoutException {
 203         System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
 204         Process p = processBuilder.start();
 205         StreamPumper stdout = new StreamPumper(p.getInputStream());
 206         StreamPumper stderr = new StreamPumper(p.getErrorStream());
 207 
 208         stdout.addPump(new LineForwarder(name, System.out));
 209         stderr.addPump(new LineForwarder(name, System.err));
 210         if (lineConsumer != null) {
 211             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 212                 @Override
 213                 protected void processLine(String line) {
 214                     lineConsumer.accept(line);
 215                 }
 216             };
 217             stdout.addPump(pump);
 218             stderr.addPump(pump);
 219         }
 220 
 221 
 222         CountDownLatch latch = new CountDownLatch(1);
 223         if (linePredicate != null) {
 224             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 225                 @Override
 226                 protected void processLine(String line) {
 227                     if (latch.getCount() > 0 && linePredicate.test(line)) {
 228                         latch.countDown();
 229                     }
 230                 }
 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 specified 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      * @see #executeTestJvm(String...)
 378      * @param cmds User specified arguments.
 379      * @return The output from the process.
 380      */
 381     public static OutputAnalyzer executeTestJava(String... cmds) throws Exception {
 382         return executeTestJvm(cmds);
 383     }
 384 
 385     /**
 386      * Executes a process, waits for it to finish and returns the process output.
 387      * The process will have exited before this method returns.
 388      * @param pb The ProcessBuilder to execute.
 389      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 390      */
 391     public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
 392         OutputAnalyzer output = null;
 393         Process p = null;
 394         boolean failed = false;
 395         try {
 396             p = pb.start();
 397             output = new OutputAnalyzer(p);
 398             p.waitFor();
 399 
 400             return output;
 401         } catch (Throwable t) {
 402             if (p != null) {
 403                 p.destroyForcibly().waitFor();
 404             }
 405 
 406             failed = true;
 407             System.out.println("executeProcess() failed: " + t);
 408             throw t;
 409         } finally {
 410             if (failed) {
 411                 System.err.println(getProcessLog(pb, output));
 412             }
 413         }
 414     }
 415 
 416     /**
 417      * Executes a process, waits for it to finish and returns the process output.
 418      *
 419      * The process will have exited before this method returns.
 420      *
 421      * @param cmds The command line to execute.
 422      * @return The output from the process.
 423      */
 424     public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
 425         return executeProcess(new ProcessBuilder(cmds));
 426     }
 427 
 428     /**
 429      * Used to log command line, stdout, stderr and exit code from an executed process.
 430      * @param pb The executed process.
 431      * @param output The output from the process.
 432      */
 433     public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
 434         String stderr = output == null ? "null" : output.getStderr();
 435         String stdout = output == null ? "null" : output.getStdout();
 436         String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
 437         StringBuilder logMsg = new StringBuilder();
 438         final String nl = System.getProperty("line.separator");
 439         logMsg.append("--- ProcessLog ---" + nl);
 440         logMsg.append("cmd: " + getCommandLine(pb) + nl);
 441         logMsg.append("exitvalue: " + exitValue + nl);
 442         logMsg.append("stderr: " + stderr + nl);
 443         logMsg.append("stdout: " + stdout + nl);
 444 
 445         return logMsg.toString();
 446     }
 447 
 448     /**
 449      * @return The full command line for the ProcessBuilder.
 450      */
 451     public static String getCommandLine(ProcessBuilder pb) {
 452         if (pb == null) {
 453             return "null";
 454         }
 455         StringBuilder cmd = new StringBuilder();
 456         for (String s : pb.command()) {
 457             cmd.append(s).append(" ");
 458         }
 459         return cmd.toString().trim();
 460     }
 461 
 462     /**
 463      * Executes a process, waits for it to finish, prints the process output
 464      * to stdout, and returns the process output.
 465      *
 466      * The process will have exited before this method returns.
 467      *
 468      * @param cmds The command line to execute.
 469      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 470      */
 471     public static OutputAnalyzer executeCommand(String... cmds)
 472             throws Throwable {
 473         String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
 474         System.out.println("Command line: [" + cmdLine + "]");
 475         OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
 476         System.out.println(analyzer.getOutput());
 477         return analyzer;
 478     }
 479 
 480     /**
 481      * Executes a process, waits for it to finish, prints the process output
 482      * to stdout and returns the process output.
 483      *
 484      * The process will have exited before this method returns.
 485      *
 486      * @param pb The ProcessBuilder to execute.
 487      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 488      */
 489     public static OutputAnalyzer executeCommand(ProcessBuilder pb)
 490             throws Throwable {
 491         String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
 492         System.out.println("Command line: [" + cmdLine + "]");
 493         OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
 494         System.out.println(analyzer.getOutput());
 495         return analyzer;
 496     }
 497 
 498     private static class ProcessImpl extends Process {
 499 
 500         private final Process p;
 501         private final Future<Void> stdoutTask;
 502         private final Future<Void> stderrTask;
 503 
 504         public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
 505             this.p = p;
 506             this.stdoutTask = stdoutTask;
 507             this.stderrTask = stderrTask;
 508         }
 509 
 510         @Override
 511         public OutputStream getOutputStream() {
 512             return p.getOutputStream();
 513         }
 514 
 515         @Override
 516         public InputStream getInputStream() {
 517             return p.getInputStream();
 518         }
 519 
 520         @Override
 521         public InputStream getErrorStream() {
 522             return p.getErrorStream();
 523         }
 524 
 525         @Override
 526         public int waitFor() throws InterruptedException {
 527             int rslt = p.waitFor();
 528             waitForStreams();
 529             return rslt;
 530         }
 531 
 532         @Override
 533         public int exitValue() {
 534             return p.exitValue();
 535         }
 536 
 537         @Override
 538         public void destroy() {
 539             p.destroy();
 540         }
 541 
 542         @Override
 543         public long pid() {
 544             return p.pid();
 545         }
 546 
 547         @Override
 548         public boolean isAlive() {
 549             return p.isAlive();
 550         }
 551 
 552         @Override
 553         public Process destroyForcibly() {
 554             return p.destroyForcibly();
 555         }
 556 
 557         @Override
 558         public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
 559             boolean rslt = p.waitFor(timeout, unit);
 560             if (rslt) {
 561                 waitForStreams();
 562             }
 563             return rslt;
 564         }
 565 
 566         private void waitForStreams() throws InterruptedException {
 567             try {
 568                 stdoutTask.get();
 569             } catch (ExecutionException e) {
 570             }
 571             try {
 572                 stderrTask.get();
 573             } catch (ExecutionException e) {
 574             }
 575         }
 576     }
 577 }