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