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 processHandler ProcessHandler 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().getPid();
 302     }
 303 
 304 
 305 
 306     /**
 307      * Create ProcessBuilder using the java launcher from the jdk to be tested and
 308      * with any platform specific arguments prepended
 309      */
 310     public static ProcessBuilder createJavaProcessBuilder(String... command) {
 311         return createJavaProcessBuilder(false, command);
 312     }
 313 
 314     /**
 315      * Create ProcessBuilder using the java launcher from the jdk to be tested,
 316      * and with any platform specific arguments prepended.
 317      *
 318      * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts
 319      *        to the java arguments.
 320      * @param command Arguments to pass to the java command.
 321      * @return The ProcessBuilder instance representing the java command.
 322      */
 323     public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) {
 324         String javapath = JDKToolFinder.getJDKTool("java");
 325 
 326         ArrayList<String> args = new ArrayList<>();
 327         args.add(javapath);
 328 
 329         args.add("-cp");
 330         args.add(System.getProperty("java.class.path"));
 331 
 332         if (addTestVmAndJavaOptions) {
 333             Collections.addAll(args, Utils.getTestJavaOpts());
 334         }
 335 
 336         Collections.addAll(args, command);
 337 
 338         // Reporting
 339         StringBuilder cmdLine = new StringBuilder();
 340         for (String cmd : args)
 341             cmdLine.append(cmd).append(' ');
 342         System.out.println("Command line: [" + cmdLine.toString() + "]");
 343 
 344         return new ProcessBuilder(args.toArray(new String[args.size()]));
 345     }
 346 
 347     private static void printStack(Thread t, StackTraceElement[] stack) {
 348         System.out.println("\t" +  t +
 349                            " stack: (length = " + stack.length + ")");
 350         if (t != null) {
 351             for (StackTraceElement stack1 : stack) {
 352                 System.out.println("\t" + stack1);
 353             }
 354             System.out.println();
 355         }
 356     }
 357 
 358     /**
 359      * Executes a test jvm process, waits for it to finish and returns the process output.
 360      * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added.
 361      * The java from the test.jdk is used to execute the command.
 362      *
 363      * The command line will be like:
 364      * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
 365      *
 366      * The jvm process will have exited before this method returns.
 367      *
 368      * @param cmds User specifed arguments.
 369      * @return The output from the process.
 370      */
 371     public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception {
 372         ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds));
 373         return executeProcess(pb);
 374     }
 375 
 376     /**
 377      * Executes a process, waits for it to finish and returns the process output.
 378      * The process will have exited before this method returns.
 379      * @param pb The ProcessBuilder to execute.
 380      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 381      */
 382     public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
 383         OutputAnalyzer output = null;
 384         Process p = null;
 385         boolean failed = false;
 386         try {
 387             p = pb.start();
 388             output = new OutputAnalyzer(p);
 389             p.waitFor();
 390 
 391             return output;
 392         } catch (Throwable t) {
 393             if (p != null) {
 394                 p.destroyForcibly().waitFor();
 395             }
 396 
 397             failed = true;
 398             System.out.println("executeProcess() failed: " + t);
 399             throw t;
 400         } finally {
 401             if (failed) {
 402                 System.err.println(getProcessLog(pb, output));
 403             }
 404         }
 405     }
 406 
 407     /**
 408      * Executes a process, waits for it to finish and returns the process output.
 409      *
 410      * The process will have exited before this method returns.
 411      *
 412      * @param cmds The command line to execute.
 413      * @return The output from the process.
 414      */
 415     public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
 416         return executeProcess(new ProcessBuilder(cmds));
 417     }
 418 
 419     /**
 420      * Used to log command line, stdout, stderr and exit code from an executed process.
 421      * @param pb The executed process.
 422      * @param output The output from the process.
 423      */
 424     public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
 425         String stderr = output == null ? "null" : output.getStderr();
 426         String stdout = output == null ? "null" : output.getStdout();
 427         String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
 428         StringBuilder logMsg = new StringBuilder();
 429         final String nl = System.getProperty("line.separator");
 430         logMsg.append("--- ProcessLog ---" + nl);
 431         logMsg.append("cmd: " + getCommandLine(pb) + nl);
 432         logMsg.append("exitvalue: " + exitValue + nl);
 433         logMsg.append("stderr: " + stderr + nl);
 434         logMsg.append("stdout: " + stdout + nl);
 435 
 436         return logMsg.toString();
 437     }
 438 
 439     /**
 440      * @return The full command line for the ProcessBuilder.
 441      */
 442     public static String getCommandLine(ProcessBuilder pb) {
 443         if (pb == null) {
 444             return "null";
 445         }
 446         StringBuilder cmd = new StringBuilder();
 447         for (String s : pb.command()) {
 448             cmd.append(s).append(" ");
 449         }
 450         return cmd.toString().trim();
 451     }
 452 
 453     /**
 454      * Executes a process, waits for it to finish, prints the process output
 455      * to stdout, and returns the process output.
 456      *
 457      * The process will have exited before this method returns.
 458      *
 459      * @param cmds The command line to execute.
 460      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 461      */
 462     public static OutputAnalyzer executeCommand(String... cmds)
 463             throws Throwable {
 464         String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
 465         System.out.println("Command line: [" + cmdLine + "]");
 466         OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
 467         System.out.println(analyzer.getOutput());
 468         return analyzer;
 469     }
 470 
 471     /**
 472      * Executes a process, waits for it to finish, prints the process output
 473      * to stdout and returns the process output.
 474      *
 475      * The process will have exited before this method returns.
 476      *
 477      * @param pb The ProcessBuilder to execute.
 478      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 479      */
 480     public static OutputAnalyzer executeCommand(ProcessBuilder pb)
 481             throws Throwable {
 482         String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
 483         System.out.println("Command line: [" + cmdLine + "]");
 484         OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
 485         System.out.println(analyzer.getOutput());
 486         return analyzer;
 487     }
 488 
 489     private static class ProcessImpl extends Process {
 490 
 491         private final Process p;
 492         private final Future<Void> stdoutTask;
 493         private final Future<Void> stderrTask;
 494 
 495         public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
 496             this.p = p;
 497             this.stdoutTask = stdoutTask;
 498             this.stderrTask = stderrTask;
 499         }
 500 
 501         @Override
 502         public OutputStream getOutputStream() {
 503             return p.getOutputStream();
 504         }
 505 
 506         @Override
 507         public InputStream getInputStream() {
 508             return p.getInputStream();
 509         }
 510 
 511         @Override
 512         public InputStream getErrorStream() {
 513             return p.getErrorStream();
 514         }
 515 
 516         @Override
 517         public int waitFor() throws InterruptedException {
 518             int rslt = p.waitFor();
 519             waitForStreams();
 520             return rslt;
 521         }
 522 
 523         @Override
 524         public int exitValue() {
 525             return p.exitValue();
 526         }
 527 
 528         @Override
 529         public void destroy() {
 530             p.destroy();
 531         }
 532 
 533         @Override
 534         public long getPid() {
 535             return p.getPid();
 536         }
 537 
 538         @Override
 539         public boolean isAlive() {
 540             return p.isAlive();
 541         }
 542 
 543         @Override
 544         public Process destroyForcibly() {
 545             return p.destroyForcibly();
 546         }
 547 
 548         @Override
 549         public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
 550             boolean rslt = p.waitFor(timeout, unit);
 551             if (rslt) {
 552                 waitForStreams();
 553             }
 554             return rslt;
 555         }
 556 
 557         private void waitForStreams() throws InterruptedException {
 558             try {
 559                 stdoutTask.get();
 560             } catch (ExecutionException e) {
 561             }
 562             try {
 563                 stderrTask.get();
 564             } catch (ExecutionException e) {
 565             }
 566         }
 567     }
 568 }