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.PrintStream;
  28 import java.lang.management.ManagementFactory;
  29 import java.lang.management.RuntimeMXBean;
  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.Future;
  38 import java.util.concurrent.TimeUnit;
  39 import java.util.concurrent.TimeoutException;
  40 import java.util.function.Predicate;
  41 import java.util.function.Consumer;
  42 import java.util.stream.Collectors;
  43 
  44 import sun.management.VMManagement;
  45 
  46 public final class ProcessTools {
  47     private static final class LineForwarder extends StreamPumper.LinePump {
  48         private final PrintStream ps;
  49         private final String prefix;
  50         LineForwarder(String prefix, PrintStream os) {
  51             this.ps = os;
  52             this.prefix = prefix;
  53         }
  54         @Override
  55         protected void processLine(String line) {
  56             ps.println("[" + prefix + "] " + line);
  57         }
  58     }
  59 
  60     private ProcessTools() {
  61     }
  62 
  63     /**
  64      * <p>Starts a process from its builder.</p>
  65      * <span>The default redirects of STDOUT and STDERR are started</span>
  66      * @param name The process name
  67      * @param processBuilder The process builder
  68      * @return Returns the initialized process
  69      * @throws IOException
  70      */
  71     public static Process startProcess(String name,
  72                                        ProcessBuilder processBuilder)
  73     throws IOException {
  74         return startProcess(name, processBuilder, (Consumer)null);
  75     }
  76 
  77     /**
  78      * <p>Starts a process from its builder.</p>
  79      * <span>The default redirects of STDOUT and STDERR are started</span>
  80      * <p>It is possible to monitor the in-streams via the provided {@code consumer}
  81      * @param name The process name
  82      * @param consumer {@linkplain Consumer} instance to process the in-streams
  83      * @param processBuilder The process builder
  84      * @return Returns the initialized process
  85      * @throws IOException
  86      */
  87     public static Process startProcess(String name,
  88                                        ProcessBuilder processBuilder,
  89                                        Consumer<String> consumer)
  90     throws IOException {
  91         Process p = null;
  92         try {
  93             p = startProcess(
  94                 name,
  95                 processBuilder,
  96                 line -> {
  97                     if (consumer != null) {
  98                         consumer.accept(line);
  99                     }
 100                     return false;
 101                 },
 102                 -1,
 103                 TimeUnit.NANOSECONDS
 104             );
 105         } catch (InterruptedException | TimeoutException e) {
 106             // can't ever happen
 107         }
 108         return p;
 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      * <p>
 115      * It is possible to wait for the process to get to a warmed-up state
 116      * via {@linkplain Predicate} condition on the STDOUT
 117      * </p>
 118      * @param name The process name
 119      * @param processBuilder The process builder
 120      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 121      *                      Used to determine the moment the target app is
 122      *                      properly warmed-up.
 123      *                      It can be null - in that case the warmup is skipped.
 124      * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever
 125      * @param unit The timeout {@linkplain TimeUnit}
 126      * @return Returns the initialized {@linkplain Process}
 127      * @throws IOException
 128      * @throws InterruptedException
 129      * @throws TimeoutException
 130      */
 131     public static Process startProcess(String name,
 132                                        ProcessBuilder processBuilder,
 133                                        final Predicate<String> linePredicate,
 134                                        long timeout,
 135                                        TimeUnit unit)
 136     throws IOException, InterruptedException, TimeoutException {
 137         System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" ")));
 138         Process p = processBuilder.start();
 139         StreamPumper stdout = new StreamPumper(p.getInputStream());
 140         StreamPumper stderr = new StreamPumper(p.getErrorStream());
 141 
 142         stdout.addPump(new LineForwarder(name, System.out));
 143         stderr.addPump(new LineForwarder(name, System.err));
 144         CountDownLatch latch = new CountDownLatch(1);
 145         if (linePredicate != null) {
 146             StreamPumper.LinePump pump = new StreamPumper.LinePump() {
 147                 @Override
 148                 protected void processLine(String line) {
 149                     if (latch.getCount() > 0 && linePredicate.test(line)) {
 150                         latch.countDown();
 151                     }
 152                 }
 153             };
 154             stdout.addPump(pump);
 155             stderr.addPump(pump);
 156         } else {
 157             latch.countDown();
 158         }
 159         Future<Void> stdoutTask = stdout.process();
 160         Future<Void> stderrTask = stderr.process();
 161 
 162         try {
 163             if (timeout > -1) {
 164                 if (timeout == 0) {
 165                     latch.await();
 166                 } else {
 167                     if (!latch.await(Utils.adjustTimeout(timeout), unit)) {
 168                         throw new TimeoutException();
 169                     }
 170                 }
 171             }
 172         } catch (TimeoutException | InterruptedException e) {
 173             System.err.println("Failed to start a process (thread dump follows)");
 174             for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) {
 175                 printStack(s.getKey(), s.getValue());
 176             }
 177 
 178             if (p.isAlive()) {
 179                 p.destroyForcibly();
 180             }
 181 
 182             stdoutTask.cancel(true);
 183             stderrTask.cancel(true);
 184             throw e;
 185         }
 186 
 187         return p;
 188     }
 189 
 190     /**
 191      * <p>Starts a process from its builder.</p>
 192      * <span>The default redirects of STDOUT and STDERR are started</span>
 193      * <p>
 194      * It is possible to wait for the process to get to a warmed-up state
 195      * via {@linkplain Predicate} condition on the STDOUT
 196      * </p>
 197      * @param name The process name
 198      * @param processBuilder The process builder
 199      * @param linePredicate The {@linkplain Predicate} to use on the STDOUT
 200      *                      Used to determine the moment the target app is
 201      *                      properly warmed-up.
 202      *                      It can be null - in that case the warmup is skipped.
 203      * @return Returns the initialized {@linkplain Process}
 204      * @throws IOException
 205      * @throws InterruptedException
 206      * @throws TimeoutException
 207      */
 208     public static Process startProcess(String name,
 209                                        ProcessBuilder processBuilder,
 210                                        final Predicate<String> linePredicate)
 211     throws IOException, InterruptedException, TimeoutException {
 212         return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS);
 213     }
 214 
 215     /**
 216      * Get the process id of the current running Java process
 217      *
 218      * @return Process id
 219      */
 220     public static int getProcessId() throws Exception {
 221 
 222         // Get the current process id using a reflection hack
 223         RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
 224         Field jvm = runtime.getClass().getDeclaredField("jvm");
 225 
 226         jvm.setAccessible(true);
 227         VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
 228 
 229         Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
 230 
 231         pid_method.setAccessible(true);
 232 
 233         int pid = (Integer) pid_method.invoke(mgmt);
 234 
 235         return pid;
 236     }
 237 
 238     /**
 239      * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris)
 240      *
 241      * @return String[] with platform specific arguments, empty if there are
 242      *         none
 243      */
 244     public static String[] getPlatformSpecificVMArgs() {
 245         String osName = System.getProperty("os.name");
 246         String dataModel = System.getProperty("sun.arch.data.model");
 247 
 248         if (osName.equals("SunOS") && dataModel.equals("64")) {
 249             return new String[] { "-d64" };
 250         }
 251 
 252         return new String[] {};
 253     }
 254 
 255     /**
 256      * Create ProcessBuilder using the java launcher from the jdk to be tested
 257      * and with any platform specific arguments prepended
 258      */
 259     public static ProcessBuilder createJavaProcessBuilder(String... command)
 260             throws Exception {
 261         String javapath = JDKToolFinder.getJDKTool("java");
 262 
 263         ArrayList<String> args = new ArrayList<>();
 264         args.add(javapath);
 265         Collections.addAll(args, getPlatformSpecificVMArgs());
 266         Collections.addAll(args, command);
 267 
 268         // Reporting
 269         StringBuilder cmdLine = new StringBuilder();
 270         for (String cmd : args)
 271             cmdLine.append(cmd).append(' ');
 272         System.out.println("Command line: [" + cmdLine.toString() + "]");
 273 
 274         return new ProcessBuilder(args.toArray(new String[args.size()]));
 275     }
 276 
 277     private static void printStack(Thread t, StackTraceElement[] stack) {
 278         System.out.println("\t" +  t +
 279                            " stack: (length = " + stack.length + ")");
 280         if (t != null) {
 281             for (StackTraceElement stack1 : stack) {
 282                 System.out.println("\t" + stack1);
 283             }
 284             System.out.println();
 285         }
 286     }
 287 
 288     /**
 289      * Executes a test jvm process, waits for it to finish and returns the process output.
 290      * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added.
 291      * The java from the test.jdk is used to execute the command.
 292      *
 293      * The command line will be like:
 294      * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds
 295      *
 296      * The jvm process will have exited before this method returns.
 297      *
 298      * @param cmds User specifed arguments.
 299      * @return The output from the process.
 300      */
 301     public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception {
 302         ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds));
 303         return executeProcess(pb);
 304     }
 305 
 306     /**
 307      * Executes a process, waits for it to finish and returns the process output.
 308      * The process will have exited before this method returns.
 309      * @param pb The ProcessBuilder to execute.
 310      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 311      */
 312     public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception {
 313         OutputAnalyzer output = null;
 314         Process p = null;
 315         boolean failed = false;
 316         try {
 317             p = pb.start();
 318             output = new OutputAnalyzer(p);
 319             p.waitFor();
 320 
 321             return output;
 322         } catch (Throwable t) {
 323             if (p != null) {
 324                 p.destroyForcibly().waitFor();
 325             }
 326 
 327             failed = true;
 328             System.out.println("executeProcess() failed: " + t);
 329             throw t;
 330         } finally {
 331             if (failed) {
 332                 System.err.println(getProcessLog(pb, output));
 333             }
 334         }
 335     }
 336 
 337     /**
 338      * Executes a process, waits for it to finish and returns the process output.
 339      *
 340      * The process will have exited before this method returns.
 341      *
 342      * @param cmds The command line to execute.
 343      * @return The output from the process.
 344      */
 345     public static OutputAnalyzer executeProcess(String... cmds) throws Throwable {
 346         return executeProcess(new ProcessBuilder(cmds));
 347     }
 348 
 349     /**
 350      * Used to log command line, stdout, stderr and exit code from an executed process.
 351      * @param pb The executed process.
 352      * @param output The output from the process.
 353      */
 354     public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) {
 355         String stderr = output == null ? "null" : output.getStderr();
 356         String stdout = output == null ? "null" : output.getStdout();
 357         String exitValue = output == null ? "null": Integer.toString(output.getExitValue());
 358         StringBuilder logMsg = new StringBuilder();
 359         final String nl = System.getProperty("line.separator");
 360         logMsg.append("--- ProcessLog ---" + nl);
 361         logMsg.append("cmd: " + getCommandLine(pb) + nl);
 362         logMsg.append("exitvalue: " + exitValue + nl);
 363         logMsg.append("stderr: " + stderr + nl);
 364         logMsg.append("stdout: " + stdout + nl);
 365 
 366         return logMsg.toString();
 367     }
 368 
 369     /**
 370      * @return The full command line for the ProcessBuilder.
 371      */
 372     public static String getCommandLine(ProcessBuilder pb) {
 373         if (pb == null) {
 374             return "null";
 375         }
 376         StringBuilder cmd = new StringBuilder();
 377         for (String s : pb.command()) {
 378             cmd.append(s).append(" ");
 379         }
 380         return cmd.toString().trim();
 381     }
 382 
 383     /**
 384      * Executes a process, waits for it to finish, prints the process output
 385      * to stdout, and returns the process output.
 386      *
 387      * The process will have exited before this method returns.
 388      *
 389      * @param cmds The command line to execute.
 390      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 391      */
 392     public static OutputAnalyzer executeCommand(String... cmds)
 393             throws Throwable {
 394         String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
 395         System.out.println("Command line: [" + cmdLine + "]");
 396         OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds);
 397         System.out.println(analyzer.getOutput());
 398         return analyzer;
 399     }
 400 
 401     /**
 402      * Executes a process, waits for it to finish, prints the process output
 403      * to stdout and returns the process output.
 404      *
 405      * The process will have exited before this method returns.
 406      *
 407      * @param pb The ProcessBuilder to execute.
 408      * @return The {@linkplain OutputAnalyzer} instance wrapping the process.
 409      */
 410     public static OutputAnalyzer executeCommand(ProcessBuilder pb)
 411             throws Throwable {
 412         String cmdLine = pb.command().stream().collect(Collectors.joining(" "));
 413         System.out.println("Command line: [" + cmdLine + "]");
 414         OutputAnalyzer analyzer = ProcessTools.executeProcess(pb);
 415         System.out.println(analyzer.getOutput());
 416         return analyzer;
 417     }
 418 }