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