1 /*
   2  * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.testlibrary;
  25 
  26 import java.io.IOException;
  27 import java.io.InputStream;
  28 import java.io.OutputStream;
  29 import java.io.PrintStream;
  30 import java.lang.reflect.Field;
  31 import java.lang.reflect.Method;
  32 import java.util.ArrayList;
  33 import java.util.Arrays;
  34 import java.util.Collections;
  35 import java.util.concurrent.CountDownLatch;
  36 import java.util.Map;
  37 import java.util.concurrent.ExecutionException;
  38 import java.util.concurrent.Future;
  39 import java.util.concurrent.TimeUnit;
  40 import java.util.concurrent.TimeoutException;
  41 import java.util.function.Predicate;
  42 import java.util.function.Consumer;
  43 import java.util.stream.Collectors;
  44 
  45 public final class ProcessTools {
  46     private static final class LineForwarder extends StreamPumper.LinePump {
  47         private final PrintStream ps;
  48         private final String prefix;
  49         LineForwarder(String prefix, PrintStream os) {
  50             this.ps = os;
  51             this.prefix = prefix;
  52         }
  53         @Override
  54         protected void processLine(String line) {
  55             ps.println("[" + prefix + "] " + line);
  56         }
  57     }
  58 
  59     private ProcessTools() {
  60     }
  61 
  62     /**
  63      * <p>Starts a process from its builder.</p>
  64      * <span>The default redirects of STDOUT and STDERR are started</span>
  65      * @param name The process name
  66      * @param processBuilder The process builder
  67      * @return Returns the initialized process
  68      * @throws IOException
  69      */
  70     public static Process startProcess(String name,
  71                                        ProcessBuilder processBuilder)
  72     throws IOException {
  73         return startProcess(name, processBuilder, (Consumer<String>)null);
  74     }
  75 
  76     /**
  77      * <p>Starts a process from its builder.</p>
  78      * <span>The default redirects of STDOUT and STDERR are started</span>
  79      * <p>It is possible to monitor the in-streams via the provided {@code consumer}
  80      * @param name The process name
  81      * @param consumer {@linkplain Consumer} instance to process the in-streams
  82      * @param processBuilder The process builder
  83      * @return Returns the initialized process
  84      * @throws IOException
  85      */
  86     @SuppressWarnings("overloads")
  87     public static Process startProcess(String name,
  88                                        ProcessBuilder processBuilder,
  89                                        Consumer<String> consumer)
  90     throws IOException {
  91         try {
  92             return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS);
  93         } catch (InterruptedException | TimeoutException e) {
  94             // will never happen
  95             throw new RuntimeException(e);
  96         }
  97     }
  98 
  99     /**
 100      * <p>Starts a process from its builder.</p>
 101      * <span>The default redirects of STDOUT and STDERR are started</span>
 102      * <p>
 103      * It is possible to wait for the process to get to a warmed-up state
 104      * via {@linkplain Predicate} condition on the STDOUT
 105      * </p>
 106      * @param name The process name
 107      * @param processBuilder The process builder
 108      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 109      *                      Used to determine the moment the target app is
 110      *                      properly warmed-up.
 111      *                      It can be null - in that case the warmup is skipped.
 112      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 113      * @param unit The timeout {@linkplain TimeUnit}
 114      * @return Returns the initialized {@linkplain Process}
 115      * @throws IOException
 116      * @throws InterruptedException
 117      * @throws TimeoutException
 118      */
 119     public static Process startProcess(String name,
 120                                        ProcessBuilder processBuilder,
 121                                        final Predicate<String> linePredicate,
 122                                        long timeout,
 123                                        TimeUnit unit)
 124     throws IOException, InterruptedException, TimeoutException {
 125         return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
 126     }
 127 
 128     /**
 129      * <p>Starts a process from its builder.</p>
 130      * <span>The default redirects of STDOUT and STDERR are started</span>
 131      * <p>
 132      * It is possible to wait for the process to get to a warmed-up state
 133      * via {@linkplain Predicate} condition on the STDOUT and monitor the
 134      * in-streams via the provided {@linkplain Consumer}
 135      * </p>
 136      * @param name The process name
 137      * @param processBuilder The process builder
 138      * @param lineConsumer  The {@linkplain Consumer} the lines will be forwarded to
 139      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 140      *                      Used to determine the moment the target app is
 141      *                      properly warmed-up.
 142      *                      It can be null - in that case the warmup is skipped.
 143      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 144      * @param unit The timeout {@linkplain TimeUnit}
 145      * @return Returns the initialized {@linkplain Process}
 146      * @throws IOException
 147      * @throws InterruptedException
 148      * @throws TimeoutException
 149      */
 150     public static Process startProcess(String name,
 151                                        ProcessBuilder processBuilder,
 152                                        final Consumer<String> lineConsumer,
 153                                        final Predicate<String> linePredicate,
 154                                        long timeout,
 155                                        TimeUnit unit)
 156     throws IOException, InterruptedException, TimeoutException {
 157         System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
 158         Process p = processBuilder.start();
 159         StreamPumper stdout = new StreamPumper(p.getInputStream());
 160         StreamPumper stderr = new StreamPumper(p.getErrorStream());
 161 
 162         stdout.addPump(new LineForwarder(name, System.out));
 163         stderr.addPump(new LineForwarder(name, System.err));
 164         if (lineConsumer != null) {
 165             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 166                 @Override
 167                 protected void processLine(String line) {
 168                     lineConsumer.accept(line);
 169                 }
 170             };
 171             stdout.addPump(pump);
 172             stderr.addPump(pump);
 173         }
 174 
 175 
 176         CountDownLatch latch = new CountDownLatch(1);
 177         if (linePredicate != null) {
 178             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 179                 @Override
 180                 protected void processLine(String line) {
 181                     if (latch.getCount() > 0 && linePredicate.test(line)) {
 182                         latch.countDown();
 183                     }
 184                 }
 185             };
 186             stdout.addPump(pump);
 187             stderr.addPump(pump);
 188         } else {
 189             latch.countDown();
 190         }
 191         final Future<Void> stdoutTask = stdout.process();
 192         final Future<Void> stderrTask = stderr.process();
 193 
 194         try {
 195             if (timeout > -1) {
 196                 if (timeout == 0) {
 197                     latch.await();
 198                 } else {
 199                     if (!latch.await(Utils.adjustTimeout(timeout), unit)) {
 200                         throw new TimeoutException();
 201                     }
 202                 }
 203             }
 204         } catch (TimeoutException | InterruptedException e) {
 205             System.err.println("Failed to start a process (thread dump follows)");
 206             for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) {
 207                 printStack(s.getKey(), s.getValue());
 208             }
 209 
 210             if (p.isAlive()) {
 211                 p.destroyForcibly();
 212             }
 213 
 214             stdoutTask.cancel(true);
 215             stderrTask.cancel(true);
 216             throw e;
 217         }
 218 
 219         return new ProcessImpl(p, stdoutTask, stderrTask);
 220     }
 221 
 222     /**
 223      * <p>Starts a process from its builder.</p>
 224      * <span>The default redirects of STDOUT and STDERR are started</span>
 225      * <p>
 226      * It is possible to wait for the process to get to a warmed-up state
 227      * via {@linkplain Predicate} condition on the STDOUT. The warm-up will
 228      * wait indefinitely.
 229      * </p>
 230      * @param name The process name
 231      * @param processBuilder The process builder
 232      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 233      *                      Used to determine the moment the target app is
 234      *                      properly warmed-up.
 235      *                      It can be null - in that case the warmup is skipped.
 236      * @return Returns the initialized {@linkplain Process}
 237      * @throws IOException
 238      * @throws InterruptedException
 239      * @throws TimeoutException
 240      */
 241     @SuppressWarnings("overloads")
 242     public static Process startProcess(String name,
 243                                        ProcessBuilder processBuilder,
 244                                        final Predicate<String> linePredicate)
 245     throws IOException, InterruptedException, TimeoutException {
 246         return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS);
 247     }
 248 
 249     /**
 250      * Get the process id of the current running Java process
 251      *
 252      * @return Process id
 253      */
 254     public static int getProcessId() {
 255         return (int) ProcessHandle.current().getPid();
 256     }
 257 
 258     /**
 259      * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris)
 260      *
 261      * @return String[] with platform specific arguments, empty if there are
 262      *         none
 263      */
 264     public static String[] getPlatformSpecificVMArgs() {
 265         String osName = System.getProperty("os.name");
 266         String dataModel = System.getProperty("sun.arch.data.model");
 267 
 268         if (osName.equals("SunOS") && dataModel.equals("64")) {
 269             return new String[] { "-d64" };
 270         }
 271 
 272         return new String[] {};
 273     }
 274 
 275     /**
 276      * Create ProcessBuilder using the java launcher from the jdk to be tested,
 277      * and with any platform specific arguments prepended.
 278      *
 279      * @param command Arguments to pass to the java command.
 280      * @return The ProcessBuilder instance representing the java command.
 281      */
 282     public static ProcessBuilder createJavaProcessBuilder(String... command)
 283             throws Exception {
 284         return createJavaProcessBuilder(false, command);
 285     }
 286 
 287     /**
 288      * Create ProcessBuilder using the java launcher from the jdk to be tested,
 289      * and with any platform specific arguments prepended.
 290      *
 291      * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts
 292      *        to the java arguments.
 293      * @param command Arguments to pass to the java command.
 294      * @return The ProcessBuilder instance representing the java command.
 295      */
 296     public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) throws Exception {
 297         String javapath = JDKToolFinder.getJDKTool("java");
 298 
 299         ArrayList<String> args = new ArrayList<>();
 300         args.add(javapath);
 301         Collections.addAll(args, getPlatformSpecificVMArgs());
 302 
 303         if (addTestVmAndJavaOptions) {
 304             // -cp is needed to make sure the same classpath is used whether the test is
 305             // run in AgentVM mode or OtherVM mode. It was added to the hotspot version
 306             // of this API as part of 8077608. However, for the jdk version it is only
 307             // added when addTestVmAndJavaOptions is true in order to minimize
 308             // disruption to existing JDK tests, which have yet to be tested with -cp
 309             // being added. At some point -cp should always be added to be consistent
 310             // with what the hotspot version does.
 311             args.add("-cp");
 312             args.add(System.getProperty("java.class.path"));
 313             Collections.addAll(args, Utils.getTestJavaOpts());
 314         }
 315 
 316         Collections.addAll(args, command);
 317 
 318         // Reporting
 319         StringBuilder cmdLine = new StringBuilder();
 320         for (String cmd : args)
 321             cmdLine.append(cmd).append(' ');
 322         System.out.println("Command line: [" + cmdLine.toString() + "]");
 323 
 324         return new ProcessBuilder(args.toArray(new String[args.size()]));
 325     }
 326 
 327     private static void printStack(Thread t, StackTraceElement[] stack) {
 328         System.out.println("\t" +  t +
 329                            " stack: (length = " + stack.length + ")");
 330         if (t != null) {
 331             for (StackTraceElement stack1 : stack) {
 332                 System.out.println("\t" + stack1);
 333             }
 334             System.out.println();
 335         }
 336     }
 337 
 338     /**
 339      * Executes a test jvm process, waits for it to finish and returns the process output.
 340      * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added.
 341      * The java from the test.jdk is used to execute the command.
 342      *
 343      * The command line will be like:
 344      * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
 345      *
 346      * The jvm process will have exited before this method returns.
 347      *
 348      * @param cmds User specifed arguments.
 349      * @return The output from the process.
 350      */
 351     public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception {
 352         ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds));
 353         return executeProcess(pb);
 354     }
 355 
 356     /**
 357      * Executes a process, waits for it to finish and returns the process output.
 358      * The process will have exited before this method returns.
 359      * @param pb The ProcessBuilder to execute.
 360      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 361      */
 362     public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
 363         OutputAnalyzer output = null;
 364         Process p = null;
 365         boolean failed = false;
 366         try {
 367             p = pb.start();
 368             output = new OutputAnalyzer(p);
 369             p.waitFor();
 370 
 371             return output;
 372         } catch (Throwable t) {
 373             if (p != null) {
 374                 p.destroyForcibly().waitFor();
 375             }
 376 
 377             failed = true;
 378             System.out.println("executeProcess() failed: " + t);
 379             throw t;
 380         } finally {
 381             if (failed) {
 382                 System.err.println(getProcessLog(pb, output));
 383             }
 384         }
 385     }
 386 
 387     /**
 388      * Executes a process, waits for it to finish and returns the process output.
 389      *
 390      * The process will have exited before this method returns.
 391      *
 392      * @param cmds The command line to execute.
 393      * @return The output from the process.
 394      */
 395     public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
 396         return executeProcess(new ProcessBuilder(cmds));
 397     }
 398 
 399     /**
 400      * Used to log command line, stdout, stderr and exit code from an executed process.
 401      * @param pb The executed process.
 402      * @param output The output from the process.
 403      */
 404     public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
 405         String stderr = output == null ? "null" : output.getStderr();
 406         String stdout = output == null ? "null" : output.getStdout();
 407         String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
 408         StringBuilder logMsg = new StringBuilder();
 409         final String nl = System.getProperty("line.separator");
 410         logMsg.append("--- ProcessLog ---" + nl);
 411         logMsg.append("cmd: " + getCommandLine(pb) + nl);
 412         logMsg.append("exitvalue: " + exitValue + nl);
 413         logMsg.append("stderr: " + stderr + nl);
 414         logMsg.append("stdout: " + stdout + nl);
 415 
 416         return logMsg.toString();
 417     }
 418 
 419     /**
 420      * @return The full command line for the ProcessBuilder.
 421      */
 422     public static String getCommandLine(ProcessBuilder pb) {
 423         if (pb == null) {
 424             return "null";
 425         }
 426         StringBuilder cmd = new StringBuilder();
 427         for (String s : pb.command()) {
 428             cmd.append(s).append(" ");
 429         }
 430         return cmd.toString().trim();
 431     }
 432 
 433     /**
 434      * Executes a process, waits for it to finish, prints the process output
 435      * to stdout, and returns the process output.
 436      *
 437      * The process will have exited before this method returns.
 438      *
 439      * @param cmds The command line to execute.
 440      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 441      */
 442     public static OutputAnalyzer executeCommand(String... cmds)
 443             throws Throwable {
 444         String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
 445         System.out.println("Command line: [" + cmdLine + "]");
 446         OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
 447         System.out.println(analyzer.getOutput());
 448         return analyzer;
 449     }
 450 
 451     /**
 452      * Executes a process, waits for it to finish, prints the process output
 453      * to stdout and returns the process output.
 454      *
 455      * The process will have exited before this method returns.
 456      *
 457      * @param pb The ProcessBuilder to execute.
 458      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 459      */
 460     public static OutputAnalyzer executeCommand(ProcessBuilder pb)
 461             throws Throwable {
 462         String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
 463         System.out.println("Command line: [" + cmdLine + "]");
 464         OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
 465         System.out.println(analyzer.getOutput());
 466         return analyzer;
 467     }
 468 
 469     private static class ProcessImpl extends Process {
 470 
 471         private final Process p;
 472         private final Future<Void> stdoutTask;
 473         private final Future<Void> stderrTask;
 474 
 475         public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
 476             this.p = p;
 477             this.stdoutTask = stdoutTask;
 478             this.stderrTask = stderrTask;
 479         }
 480 
 481         @Override
 482         public OutputStream getOutputStream() {
 483             return p.getOutputStream();
 484         }
 485 
 486         @Override
 487         public InputStream getInputStream() {
 488             return p.getInputStream();
 489         }
 490 
 491         @Override
 492         public InputStream getErrorStream() {
 493             return p.getErrorStream();
 494         }
 495 
 496         @Override
 497         public int waitFor() throws InterruptedException {
 498             int rslt = p.waitFor();
 499             waitForStreams();
 500             return rslt;
 501         }
 502 
 503         @Override
 504         public int exitValue() {
 505             return p.exitValue();
 506         }
 507 
 508         @Override
 509         public void destroy() {
 510             p.destroy();
 511         }
 512 
 513         @Override
 514         public long getPid() {
 515             return p.getPid();
 516         }
 517 
 518         @Override
 519         public boolean isAlive() {
 520             return p.isAlive();
 521         }
 522 
 523         @Override
 524         public Process destroyForcibly() {
 525             return p.destroyForcibly();
 526         }
 527 
 528         @Override
 529         public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
 530             boolean rslt = p.waitFor(timeout, unit);
 531             if (rslt) {
 532                 waitForStreams();
 533             }
 534             return rslt;
 535         }
 536 
 537         private void waitForStreams() throws InterruptedException {
 538             try {
 539                 stdoutTask.get();
 540             } catch (ExecutionException e) {
 541             }
 542             try {
 543                 stderrTask.get();
 544             } catch (ExecutionException e) {
 545             }
 546         }
 547     }
 548 }