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