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.management.ManagementFactory;
  31 import java.lang.management.RuntimeMXBean;
  32 import java.lang.reflect.Field;
  33 import java.lang.reflect.Method;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.Collections;
  37 import java.util.concurrent.CountDownLatch;
  38 import java.util.Map;
  39 import java.util.concurrent.ExecutionException;
  40 import java.util.concurrent.Future;
  41 import java.util.concurrent.TimeUnit;
  42 import java.util.concurrent.TimeoutException;
  43 import java.util.function.Predicate;
  44 import java.util.function.Consumer;
  45 import java.util.stream.Collectors;
  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      * <p>Starts a process from its builder.</p>
  66      * <span>The default redirects of STDOUT and STDERR are started</span>
  67      * @param name The process name
  68      * @param processBuilder The process builder
  69      * @return Returns the initialized process
  70      * @throws IOException
  71      */
  72     public static Process startProcess(String name,
  73                                        ProcessBuilder processBuilder)
  74     throws IOException {
  75         return startProcess(name, processBuilder, (Consumer)null);
  76     }
  77 
  78     /**
  79      * <p>Starts a process from its builder.</p>
  80      * <span>The default redirects of STDOUT and STDERR are started</span>
  81      * <p>It is possible to monitor the in-streams via the provided {@code consumer}
  82      * @param name The process name
  83      * @param consumer {@linkplain Consumer} instance to process the in-streams
  84      * @param processBuilder The process builder
  85      * @return Returns the initialized process
  86      * @throws IOException
  87      */
  88     public static Process startProcess(String name,
  89                                        ProcessBuilder processBuilder,
  90                                        Consumer<String> consumer)
  91     throws IOException {
  92         try {
  93             return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS);
  94         } catch (InterruptedException | TimeoutException e) {
  95             // will never happen
  96             throw new RuntimeException(e);
  97         }
  98     }
  99 
 100     /**
 101      * <p>Starts a process from its builder.</p>
 102      * <span>The default redirects of STDOUT and STDERR are started</span>
 103      * <p>
 104      * It is possible to wait for the process to get to a warmed-up state
 105      * via {@linkplain Predicate} condition on the STDOUT
 106      * </p>
 107      * @param name The process name
 108      * @param processBuilder The process builder
 109      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 110      *                      Used to determine the moment the target app is
 111      *                      properly warmed-up.
 112      *                      It can be null - in that case the warmup is skipped.
 113      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 114      * @param unit The timeout {@linkplain TimeUnit}
 115      * @return Returns the initialized {@linkplain Process}
 116      * @throws IOException
 117      * @throws InterruptedException
 118      * @throws TimeoutException
 119      */
 120     public static Process startProcess(String name,
 121                                        ProcessBuilder processBuilder,
 122                                        final Predicate<String> linePredicate,
 123                                        long timeout,
 124                                        TimeUnit unit)
 125     throws IOException, InterruptedException, TimeoutException {
 126         return startProcess(name, processBuilder, null, linePredicate, timeout, unit);
 127     }
 128 
 129     /**
 130      * <p>Starts a process from its builder.</p>
 131      * <span>The default redirects of STDOUT and STDERR are started</span>
 132      * <p>
 133      * It is possible to wait for the process to get to a warmed-up state
 134      * via {@linkplain Predicate} condition on the STDOUT and monitor the
 135      * in-streams via the provided {@linkplain Consumer}
 136      * </p>
 137      * @param name The process name
 138      * @param processBuilder The process builder
 139      * @param lineConsumer  The {@linkplain Consumer} the lines will be forwarded to
 140      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 141      *                      Used to determine the moment the target app is
 142      *                      properly warmed-up.
 143      *                      It can be null - in that case the warmup is skipped.
 144      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 145      * @param unit The timeout {@linkplain TimeUnit}
 146      * @return Returns the initialized {@linkplain Process}
 147      * @throws IOException
 148      * @throws InterruptedException
 149      * @throws TimeoutException
 150      */
 151     public static Process startProcess(String name,
 152                                        ProcessBuilder processBuilder,
 153                                        final Consumer<String> lineConsumer,
 154                                        final Predicate<String> linePredicate,
 155                                        long timeout,
 156                                        TimeUnit unit)
 157     throws IOException, InterruptedException, TimeoutException {
 158         System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
 159         Process p = processBuilder.start();
 160         StreamPumper stdout = new StreamPumper(p.getInputStream());
 161         StreamPumper stderr = new StreamPumper(p.getErrorStream());
 162 
 163         stdout.addPump(new LineForwarder(name, System.out));
 164         stderr.addPump(new LineForwarder(name, System.err));
 165         if (lineConsumer != null) {
 166             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 167                 @Override
 168                 protected void processLine(String line) {
 169                     lineConsumer.accept(line);
 170                 }
 171             };
 172             stdout.addPump(pump);
 173             stderr.addPump(pump);
 174         }
 175 
 176 
 177         CountDownLatch latch = new CountDownLatch(1);
 178         if (linePredicate != null) {
 179             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 180                 @Override
 181                 protected void processLine(String line) {
 182                     if (latch.getCount() > 0 && linePredicate.test(line)) {
 183                         latch.countDown();
 184                     }
 185                 }
 186             };
 187             stdout.addPump(pump);
 188             stderr.addPump(pump);
 189         } else {
 190             latch.countDown();
 191         }
 192         final Future<Void> stdoutTask = stdout.process();
 193         final Future<Void> stderrTask = stderr.process();
 194 
 195         try {
 196             if (timeout > -1) {
 197                 if (timeout == 0) {
 198                     latch.await();
 199                 } else {
 200                     if (!latch.await(Utils.adjustTimeout(timeout), unit)) {
 201                         throw new TimeoutException();
 202                     }
 203                 }
 204             }
 205         } catch (TimeoutException | InterruptedException e) {
 206             System.err.println("Failed to start a process (thread dump follows)");
 207             for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) {
 208                 printStack(s.getKey(), s.getValue());
 209             }
 210 
 211             if (p.isAlive()) {
 212                 p.destroyForcibly();
 213             }
 214 
 215             stdoutTask.cancel(true);
 216             stderrTask.cancel(true);
 217             throw e;
 218         }
 219 
 220         return new ProcessImpl(p, stdoutTask, stderrTask);
 221     }
 222 
 223     /**
 224      * <p>Starts a process from its builder.</p>
 225      * <span>The default redirects of STDOUT and STDERR are started</span>
 226      * <p>
 227      * It is possible to wait for the process to get to a warmed-up state
 228      * via {@linkplain Predicate} condition on the STDOUT. The warm-up will
 229      * wait indefinitely.
 230      * </p>
 231      * @param name The process name
 232      * @param processBuilder The process builder
 233      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 234      *                      Used to determine the moment the target app is
 235      *                      properly warmed-up.
 236      *                      It can be null - in that case the warmup is skipped.
 237      * @return Returns the initialized {@linkplain Process}
 238      * @throws IOException
 239      * @throws InterruptedException
 240      * @throws TimeoutException
 241      */
 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() throws Exception {
 255         RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
 256         int pid = Integer.parseInt(runtime.getName().split("@")[0]);
 257 
 258         return pid;
 259     }
 260 
 261     /**
 262      * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris)
 263      *
 264      * @return String[] with platform specific arguments, empty if there are
 265      *         none
 266      */
 267     public static String[] getPlatformSpecificVMArgs() {
 268         String osName = System.getProperty("os.name");
 269         String dataModel = System.getProperty("sun.arch.data.model");
 270 
 271         if (osName.equals("SunOS") && dataModel.equals("64")) {
 272             return new String[] { "-d64" };
 273         }
 274 
 275         return new String[] {};
 276     }
 277 
 278     /**
 279      * Create ProcessBuilder using the java launcher from the jdk to be tested
 280      * and with any platform specific arguments prepended
 281      */
 282     public static ProcessBuilder createJavaProcessBuilder(String... command)
 283             throws Exception {
 284         String javapath = JDKToolFinder.getJDKTool("java");
 285 
 286         ArrayList<String> args = new ArrayList<>();
 287         args.add(javapath);
 288         Collections.addAll(args, getPlatformSpecificVMArgs());
 289         Collections.addAll(args, command);
 290 
 291         // Reporting
 292         StringBuilder cmdLine = new StringBuilder();
 293         for (String cmd : args)
 294             cmdLine.append(cmd).append(' ');
 295         System.out.println("Command line: [" + cmdLine.toString() + "]");
 296 
 297         return new ProcessBuilder(args.toArray(new String[args.size()]));
 298     }
 299 
 300     private static void printStack(Thread t, StackTraceElement[] stack) {
 301         System.out.println("\t" +  t +
 302                            " stack: (length = " + stack.length + ")");
 303         if (t != null) {
 304             for (StackTraceElement stack1 : stack) {
 305                 System.out.println("\t" + stack1);
 306             }
 307             System.out.println();
 308         }
 309     }
 310 
 311     /**
 312      * Executes a test jvm process, waits for it to finish and returns the process output.
 313      * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added.
 314      * The java from the test.jdk is used to execute the command.
 315      *
 316      * The command line will be like:
 317      * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
 318      *
 319      * The jvm process will have exited before this method returns.
 320      *
 321      * @param cmds User specifed arguments.
 322      * @return The output from the process.
 323      */
 324     public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception {
 325         ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds));
 326         return executeProcess(pb);
 327     }
 328 
 329     /**
 330      * Executes a process, waits for it to finish and returns the process output.
 331      * The process will have exited before this method returns.
 332      * @param pb The ProcessBuilder to execute.
 333      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 334      */
 335     public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
 336         OutputAnalyzer output = null;
 337         Process p = null;
 338         boolean failed = false;
 339         try {
 340             p = pb.start();
 341             output = new OutputAnalyzer(p);
 342             p.waitFor();
 343 
 344             return output;
 345         } catch (Throwable t) {
 346             if (p != null) {
 347                 p.destroyForcibly().waitFor();
 348             }
 349 
 350             failed = true;
 351             System.out.println("executeProcess() failed: " + t);
 352             throw t;
 353         } finally {
 354             if (failed) {
 355                 System.err.println(getProcessLog(pb, output));
 356             }
 357         }
 358     }
 359 
 360     /**
 361      * Executes a process, waits for it to finish and returns the process output.
 362      *
 363      * The process will have exited before this method returns.
 364      *
 365      * @param cmds The command line to execute.
 366      * @return The output from the process.
 367      */
 368     public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
 369         return executeProcess(new ProcessBuilder(cmds));
 370     }
 371 
 372     /**
 373      * Used to log command line, stdout, stderr and exit code from an executed process.
 374      * @param pb The executed process.
 375      * @param output The output from the process.
 376      */
 377     public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
 378         String stderr = output == null ? "null" : output.getStderr();
 379         String stdout = output == null ? "null" : output.getStdout();
 380         String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
 381         StringBuilder logMsg = new StringBuilder();
 382         final String nl = System.getProperty("line.separator");
 383         logMsg.append("--- ProcessLog ---" + nl);
 384         logMsg.append("cmd: " + getCommandLine(pb) + nl);
 385         logMsg.append("exitvalue: " + exitValue + nl);
 386         logMsg.append("stderr: " + stderr + nl);
 387         logMsg.append("stdout: " + stdout + nl);
 388 
 389         return logMsg.toString();
 390     }
 391 
 392     /**
 393      * @return The full command line for the ProcessBuilder.
 394      */
 395     public static String getCommandLine(ProcessBuilder pb) {
 396         if (pb == null) {
 397             return "null";
 398         }
 399         StringBuilder cmd = new StringBuilder();
 400         for (String s : pb.command()) {
 401             cmd.append(s).append(" ");
 402         }
 403         return cmd.toString().trim();
 404     }
 405 
 406     /**
 407      * Executes a process, waits for it to finish, prints the process output
 408      * to stdout, 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 {@linkplain OutputAnalyzer} instance wrapping the process.
 414      */
 415     public static OutputAnalyzer executeCommand(String... cmds)
 416             throws Throwable {
 417         String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
 418         System.out.println("Command line: [" + cmdLine + "]");
 419         OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
 420         System.out.println(analyzer.getOutput());
 421         return analyzer;
 422     }
 423 
 424     /**
 425      * Executes a process, waits for it to finish, prints the process output
 426      * to stdout and returns the process output.
 427      *
 428      * The process will have exited before this method returns.
 429      *
 430      * @param pb The ProcessBuilder to execute.
 431      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 432      */
 433     public static OutputAnalyzer executeCommand(ProcessBuilder pb)
 434             throws Throwable {
 435         String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
 436         System.out.println("Command line: [" + cmdLine + "]");
 437         OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
 438         System.out.println(analyzer.getOutput());
 439         return analyzer;
 440     }
 441 
 442     private static class ProcessImpl extends Process {
 443 
 444         private final Process p;
 445         private final Future<Void> stdoutTask;
 446         private final Future<Void> stderrTask;
 447 
 448         public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) {
 449             this.p = p;
 450             this.stdoutTask = stdoutTask;
 451             this.stderrTask = stderrTask;
 452         }
 453 
 454         @Override
 455         public OutputStream getOutputStream() {
 456             return p.getOutputStream();
 457         }
 458 
 459         @Override
 460         public InputStream getInputStream() {
 461             return p.getInputStream();
 462         }
 463 
 464         @Override
 465         public InputStream getErrorStream() {
 466             return p.getErrorStream();
 467         }
 468 
 469         @Override
 470         public int waitFor() throws InterruptedException {
 471             int rslt = p.waitFor();
 472             waitForStreams();
 473             return rslt;
 474         }
 475 
 476         @Override
 477         public int exitValue() {
 478             return p.exitValue();
 479         }
 480 
 481         @Override
 482         public void destroy() {
 483             p.destroy();
 484         }
 485 
 486         @Override
 487         public long getPid() {
 488             return p.getPid();
 489         }
 490 
 491         @Override
 492         public boolean isAlive() {
 493             return p.isAlive();
 494         }
 495 
 496         @Override
 497         public Process destroyForcibly() {
 498             return p.destroyForcibly();
 499         }
 500 
 501         @Override
 502         public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
 503             boolean rslt = p.waitFor(timeout, unit);
 504             if (rslt) {
 505                 waitForStreams();
 506             }
 507             return rslt;
 508         }
 509 
 510         private void waitForStreams() throws InterruptedException {
 511             try {
 512                 stdoutTask.get();
 513             } catch (ExecutionException e) {
 514             }
 515             try {
 516                 stderrTask.get();
 517             } catch (ExecutionException e) {
 518             }
 519         }
 520     }
 521 }