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