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