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