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