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