ProcessTools.java revision 2567:03fe61bb7670
1182918Smarius/* 2182918Smarius * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 3182918Smarius * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4182918Smarius * 5182918Smarius * This code is free software; you can redistribute it and/or modify it 6182918Smarius * under the terms of the GNU General Public License version 2 only, as 7182918Smarius * published by the Free Software Foundation. 8182918Smarius * 9182918Smarius * This code is distributed in the hope that it will be useful, but WITHOUT 10182918Smarius * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11182918Smarius * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12182918Smarius * version 2 for more details (a copy is included in the LICENSE file that 13182918Smarius * accompanied this code). 14182918Smarius * 15182918Smarius * You should have received a copy of the GNU General Public License version 16182918Smarius * 2 along with this work; if not, write to the Free Software Foundation, 17182918Smarius * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18182918Smarius * 19182918Smarius * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20182918Smarius * or visit www.oracle.com if you need additional information or have any 21182918Smarius * questions. 22182918Smarius */ 23182918Smarius 24182918Smariuspackage jdk.test.lib.process; 25182918Smarius 26182918Smariusimport java.io.ByteArrayOutputStream; 27182918Smariusimport java.io.IOException; 28182918Smariusimport java.io.InputStream; 29182918Smariusimport java.io.OutputStream; 30182918Smariusimport java.io.PrintStream; 31182918Smariusimport java.util.ArrayList; 32182918Smariusimport java.util.Arrays; 33182918Smariusimport java.util.Collections; 34182918Smariusimport java.util.concurrent.CountDownLatch; 35182918Smariusimport java.util.Map; 36182918Smariusimport java.util.concurrent.ExecutionException; 37182918Smariusimport java.util.concurrent.Future; 38182918Smariusimport java.util.concurrent.TimeUnit; 39182918Smariusimport java.util.concurrent.TimeoutException; 40182918Smariusimport java.util.function.Predicate; 41182918Smariusimport java.util.function.Consumer; 42182918Smariusimport java.util.stream.Collectors; 43197164Smarius 44197164Smariusimport jdk.test.lib.JDKToolFinder; 45197164Smariusimport jdk.test.lib.Utils; 46197164Smarius 47182918Smariuspublic final class ProcessTools { 48182918Smarius private static final class LineForwarder extends StreamPumper.LinePump { 49182918Smarius private final PrintStream ps; 50182918Smarius private final String prefix; 51182918Smarius LineForwarder(String prefix, PrintStream os) { 52182918Smarius this.ps = os; 53182918Smarius this.prefix = prefix; 54182918Smarius } 55182918Smarius @Override 56182918Smarius protected void processLine(String line) { 57182918Smarius ps.println("[" + prefix + "] " + line); 58182918Smarius } 59182918Smarius } 60182918Smarius 61182918Smarius private ProcessTools() { 62182918Smarius } 63182918Smarius 64182918Smarius /** 65182918Smarius * Pumps stdout and stderr from running the process into a String. 66182918Smarius * 67182918Smarius * @param processHandler ProcessHandler to run. 68182918Smarius * @return Output from process. 69182918Smarius * @throws IOException If an I/O error occurs. 70182918Smarius */ 71182918Smarius public static OutputBuffer getOutput(ProcessBuilder processBuilder) throws IOException { 72182918Smarius return getOutput(processBuilder.start()); 73182918Smarius } 74182918Smarius 75182918Smarius /** 76182918Smarius * Pumps stdout and stderr the running process into a String. 77182918Smarius * 78182918Smarius * @param process Process to pump. 79182918Smarius * @return Output from process. 80182918Smarius * @throws IOException If an I/O error occurs. 81182918Smarius */ 82182918Smarius public static OutputBuffer getOutput(Process process) throws IOException { 83182918Smarius ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream(); 84182918Smarius ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream(); 85227848Smarius StreamPumper outPumper = new StreamPumper(process.getInputStream(), stdoutBuffer); 86182918Smarius StreamPumper errPumper = new StreamPumper(process.getErrorStream(), stderrBuffer); 87182918Smarius Thread outPumperThread = new Thread(outPumper); 88182918Smarius Thread errPumperThread = new Thread(errPumper); 89182918Smarius 90182918Smarius outPumperThread.setDaemon(true); 91182918Smarius errPumperThread.setDaemon(true); 92182918Smarius 93182918Smarius outPumperThread.start(); 94182918Smarius errPumperThread.start(); 95182918Smarius 96182918Smarius try { 97182918Smarius process.waitFor(); 98182918Smarius outPumperThread.join(); 99182918Smarius errPumperThread.join(); 100182918Smarius } catch (InterruptedException e) { 101182918Smarius Thread.currentThread().interrupt(); 102182918Smarius return null; 103182918Smarius } 104182918Smarius 105182918Smarius return new OutputBuffer(stdoutBuffer.toString(), stderrBuffer.toString()); 106182918Smarius } 107182918Smarius 108182918Smarius /** 109182918Smarius * <p>Starts a process from its builder.</p> 110182918Smarius * <span>The default redirects of STDOUT and STDERR are started</span> 111182918Smarius * @param name The process name 112182918Smarius * @param processBuilder The process builder 113182918Smarius * @return Returns the initialized process 114182918Smarius * @throws IOException 115182918Smarius */ 116182918Smarius public static Process startProcess(String name, 117182918Smarius ProcessBuilder processBuilder) 118182918Smarius throws IOException { 119182918Smarius return startProcess(name, processBuilder, (Consumer<String>)null); 120182918Smarius } 121182918Smarius 122182918Smarius /** 123182918Smarius * <p>Starts a process from its builder.</p> 124182918Smarius * <span>The default redirects of STDOUT and STDERR are started</span> 125182918Smarius * <p>It is possible to monitor the in-streams via the provided {@code consumer} 126182918Smarius * @param name The process name 127182918Smarius * @param consumer {@linkplain Consumer} instance to process the in-streams 128182918Smarius * @param processBuilder The process builder 129182918Smarius * @return Returns the initialized process 130182918Smarius * @throws IOException 131182918Smarius */ 132182918Smarius @SuppressWarnings("overloads") 133182918Smarius public static Process startProcess(String name, 134182918Smarius ProcessBuilder processBuilder, 135182918Smarius Consumer<String> consumer) 136182918Smarius throws IOException { 137182918Smarius try { 138182918Smarius return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS); 139182918Smarius } catch (InterruptedException | TimeoutException e) { 140182918Smarius // will never happen 141182918Smarius throw new RuntimeException(e); 142182918Smarius } 143182918Smarius } 144182918Smarius 145182918Smarius /** 146182918Smarius * <p>Starts a process from its builder.</p> 147182918Smarius * <span>The default redirects of STDOUT and STDERR are started</span> 148182918Smarius * <p> 149182918Smarius * It is possible to wait for the process to get to a warmed-up state 150182918Smarius * via {@linkplain Predicate} condition on the STDOUT 151182918Smarius * </p> 152182918Smarius * @param name The process name 153182918Smarius * @param processBuilder The process builder 154182918Smarius * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 155182918Smarius * Used to determine the moment the target app is 156182918Smarius * properly warmed-up. 157197164Smarius * It can be null - in that case the warmup is skipped. 158182918Smarius * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 159182918Smarius * @param unit The timeout {@linkplain TimeUnit} 160182918Smarius * @return Returns the initialized {@linkplain Process} 161182918Smarius * @throws IOException 162182918Smarius * @throws InterruptedException 163182918Smarius * @throws TimeoutException 164182918Smarius */ 165182918Smarius public static Process startProcess(String name, 166182918Smarius ProcessBuilder processBuilder, 167182918Smarius final Predicate<String> linePredicate, 168182918Smarius long timeout, 169182918Smarius TimeUnit unit) 170182918Smarius throws IOException, InterruptedException, TimeoutException { 171182918Smarius return startProcess(name, processBuilder, null, linePredicate, timeout, unit); 172182918Smarius } 173182918Smarius 174182918Smarius /** 175182918Smarius * <p>Starts a process from its builder.</p> 176182918Smarius * <span>The default redirects of STDOUT and STDERR are started</span> 177182918Smarius * <p> 178182918Smarius * It is possible to wait for the process to get to a warmed-up state 179182918Smarius * via {@linkplain Predicate} condition on the STDOUT and monitor the 180182918Smarius * in-streams via the provided {@linkplain Consumer} 181182918Smarius * </p> 182182918Smarius * @param name The process name 183182918Smarius * @param processBuilder The process builder 184182918Smarius * @param lineConsumer The {@linkplain Consumer} the lines will be forwarded to 185182918Smarius * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 186182918Smarius * Used to determine the moment the target app is 187182918Smarius * properly warmed-up. 188182918Smarius * It can be null - in that case the warmup is skipped. 189182918Smarius * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 190182918Smarius * @param unit The timeout {@linkplain TimeUnit} 191182918Smarius * @return Returns the initialized {@linkplain Process} 192182918Smarius * @throws IOException 193182918Smarius * @throws InterruptedException 194182918Smarius * @throws TimeoutException 195182918Smarius */ 196182918Smarius public static Process startProcess(String name, 197182918Smarius ProcessBuilder processBuilder, 198182918Smarius final Consumer<String> lineConsumer, 199182918Smarius final Predicate<String> linePredicate, 200182918Smarius long timeout, 201182918Smarius TimeUnit unit) 202182918Smarius throws IOException, InterruptedException, TimeoutException { 203182918Smarius System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" "))); 204182918Smarius Process p = processBuilder.start(); 205182918Smarius StreamPumper stdout = new StreamPumper(p.getInputStream()); 206182918Smarius StreamPumper stderr = new StreamPumper(p.getErrorStream()); 207182918Smarius 208182918Smarius stdout.addPump(new LineForwarder(name, System.out)); 209182918Smarius stderr.addPump(new LineForwarder(name, System.err)); 210182918Smarius if (lineConsumer != null) { 211182918Smarius StreamPumper.LinePump pump = new StreamPumper.LinePump() { 212182918Smarius @Override 213182918Smarius protected void processLine(String line) { 214182918Smarius lineConsumer.accept(line); 215182918Smarius } 216182918Smarius }; 217182918Smarius stdout.addPump(pump); 218182918Smarius stderr.addPump(pump); 219182918Smarius } 220182918Smarius 221182918Smarius 222182918Smarius CountDownLatch latch = new CountDownLatch(1); 223182918Smarius if (linePredicate != null) { 224182918Smarius StreamPumper.LinePump pump = new StreamPumper.LinePump() { 225182918Smarius @Override 226182918Smarius protected void processLine(String line) { 227182918Smarius if (latch.getCount() > 0 && linePredicate.test(line)) { 228182918Smarius latch.countDown(); 229182918Smarius } 230182918Smarius } 231 }; 232 stdout.addPump(pump); 233 stderr.addPump(pump); 234 } else { 235 latch.countDown(); 236 } 237 final Future<Void> stdoutTask = stdout.process(); 238 final Future<Void> stderrTask = stderr.process(); 239 240 try { 241 if (timeout > -1) { 242 if (timeout == 0) { 243 latch.await(); 244 } else { 245 if (!latch.await(Utils.adjustTimeout(timeout), unit)) { 246 throw new TimeoutException(); 247 } 248 } 249 } 250 } catch (TimeoutException | InterruptedException e) { 251 System.err.println("Failed to start a process (thread dump follows)"); 252 for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) { 253 printStack(s.getKey(), s.getValue()); 254 } 255 256 if (p.isAlive()) { 257 p.destroyForcibly(); 258 } 259 260 stdoutTask.cancel(true); 261 stderrTask.cancel(true); 262 throw e; 263 } 264 265 return new ProcessImpl(p, stdoutTask, stderrTask); 266 } 267 268 /** 269 * <p>Starts a process from its builder.</p> 270 * <span>The default redirects of STDOUT and STDERR are started</span> 271 * <p> 272 * It is possible to wait for the process to get to a warmed-up state 273 * via {@linkplain Predicate} condition on the STDOUT. The warm-up will 274 * wait indefinitely. 275 * </p> 276 * @param name The process name 277 * @param processBuilder The process builder 278 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 279 * Used to determine the moment the target app is 280 * properly warmed-up. 281 * It can be null - in that case the warmup is skipped. 282 * @return Returns the initialized {@linkplain Process} 283 * @throws IOException 284 * @throws InterruptedException 285 * @throws TimeoutException 286 */ 287 @SuppressWarnings("overloads") 288 public static Process startProcess(String name, 289 ProcessBuilder processBuilder, 290 final Predicate<String> linePredicate) 291 throws IOException, InterruptedException, TimeoutException { 292 return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS); 293 } 294 295 /** 296 * Get the process id of the current running Java process 297 * 298 * @return Process id 299 */ 300 public static long getProcessId() throws Exception { 301 return ProcessHandle.current().pid(); 302 } 303 304 305 306 /** 307 * Create ProcessBuilder using the java launcher from the jdk to be tested and 308 * with any platform specific arguments prepended 309 */ 310 public static ProcessBuilder createJavaProcessBuilder(String... command) { 311 return createJavaProcessBuilder(false, command); 312 } 313 314 /** 315 * Create ProcessBuilder using the java launcher from the jdk to be tested, 316 * and with any platform specific arguments prepended. 317 * 318 * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts 319 * to the java arguments. 320 * @param command Arguments to pass to the java command. 321 * @return The ProcessBuilder instance representing the java command. 322 */ 323 public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) { 324 String javapath = JDKToolFinder.getJDKTool("java"); 325 326 ArrayList<String> args = new ArrayList<>(); 327 args.add(javapath); 328 329 args.add("-cp"); 330 args.add(System.getProperty("java.class.path")); 331 332 if (addTestVmAndJavaOptions) { 333 Collections.addAll(args, Utils.getTestJavaOpts()); 334 } 335 336 Collections.addAll(args, command); 337 338 // Reporting 339 StringBuilder cmdLine = new StringBuilder(); 340 for (String cmd : args) 341 cmdLine.append(cmd).append(' '); 342 System.out.println("Command line: [" + cmdLine.toString() + "]"); 343 344 return new ProcessBuilder(args.toArray(new String[args.size()])); 345 } 346 347 private static void printStack(Thread t, StackTraceElement[] stack) { 348 System.out.println("\t" + t + 349 " stack: (length = " + stack.length + ")"); 350 if (t != null) { 351 for (StackTraceElement stack1 : stack) { 352 System.out.println("\t" + stack1); 353 } 354 System.out.println(); 355 } 356 } 357 358 /** 359 * Executes a test jvm process, waits for it to finish and returns the process output. 360 * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added. 361 * The java from the test.jdk is used to execute the command. 362 * 363 * The command line will be like: 364 * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds 365 * 366 * The jvm process will have exited before this method returns. 367 * 368 * @param cmds User specifed arguments. 369 * @return The output from the process. 370 */ 371 public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception { 372 ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds)); 373 return executeProcess(pb); 374 } 375 376 /** 377 * Executes a process, waits for it to finish and returns the process output. 378 * The process will have exited before this method returns. 379 * @param pb The ProcessBuilder to execute. 380 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 381 */ 382 public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception { 383 OutputAnalyzer output = null; 384 Process p = null; 385 boolean failed = false; 386 try { 387 p = pb.start(); 388 output = new OutputAnalyzer(p); 389 p.waitFor(); 390 391 return output; 392 } catch (Throwable t) { 393 if (p != null) { 394 p.destroyForcibly().waitFor(); 395 } 396 397 failed = true; 398 System.out.println("executeProcess() failed: " + t); 399 throw t; 400 } finally { 401 if (failed) { 402 System.err.println(getProcessLog(pb, output)); 403 } 404 } 405 } 406 407 /** 408 * Executes a process, waits for it to finish and returns the process output. 409 * 410 * The process will have exited before this method returns. 411 * 412 * @param cmds The command line to execute. 413 * @return The output from the process. 414 */ 415 public static OutputAnalyzer executeProcess(String... cmds) throws Throwable { 416 return executeProcess(new ProcessBuilder(cmds)); 417 } 418 419 /** 420 * Used to log command line, stdout, stderr and exit code from an executed process. 421 * @param pb The executed process. 422 * @param output The output from the process. 423 */ 424 public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) { 425 String stderr = output == null ? "null" : output.getStderr(); 426 String stdout = output == null ? "null" : output.getStdout(); 427 String exitValue = output == null ? "null": Integer.toString(output.getExitValue()); 428 StringBuilder logMsg = new StringBuilder(); 429 final String nl = System.getProperty("line.separator"); 430 logMsg.append("--- ProcessLog ---" + nl); 431 logMsg.append("cmd: " + getCommandLine(pb) + nl); 432 logMsg.append("exitvalue: " + exitValue + nl); 433 logMsg.append("stderr: " + stderr + nl); 434 logMsg.append("stdout: " + stdout + nl); 435 436 return logMsg.toString(); 437 } 438 439 /** 440 * @return The full command line for the ProcessBuilder. 441 */ 442 public static String getCommandLine(ProcessBuilder pb) { 443 if (pb == null) { 444 return "null"; 445 } 446 StringBuilder cmd = new StringBuilder(); 447 for (String s : pb.command()) { 448 cmd.append(s).append(" "); 449 } 450 return cmd.toString().trim(); 451 } 452 453 /** 454 * Executes a process, waits for it to finish, prints the process output 455 * to stdout, and returns the process output. 456 * 457 * The process will have exited before this method returns. 458 * 459 * @param cmds The command line to execute. 460 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 461 */ 462 public static OutputAnalyzer executeCommand(String... cmds) 463 throws Throwable { 464 String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" ")); 465 System.out.println("Command line: [" + cmdLine + "]"); 466 OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds); 467 System.out.println(analyzer.getOutput()); 468 return analyzer; 469 } 470 471 /** 472 * Executes a process, waits for it to finish, prints the process output 473 * to stdout and returns the process output. 474 * 475 * The process will have exited before this method returns. 476 * 477 * @param pb The ProcessBuilder to execute. 478 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 479 */ 480 public static OutputAnalyzer executeCommand(ProcessBuilder pb) 481 throws Throwable { 482 String cmdLine = pb.command().stream().collect(Collectors.joining(" ")); 483 System.out.println("Command line: [" + cmdLine + "]"); 484 OutputAnalyzer analyzer = ProcessTools.executeProcess(pb); 485 System.out.println(analyzer.getOutput()); 486 return analyzer; 487 } 488 489 private static class ProcessImpl extends Process { 490 491 private final Process p; 492 private final Future<Void> stdoutTask; 493 private final Future<Void> stderrTask; 494 495 public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) { 496 this.p = p; 497 this.stdoutTask = stdoutTask; 498 this.stderrTask = stderrTask; 499 } 500 501 @Override 502 public OutputStream getOutputStream() { 503 return p.getOutputStream(); 504 } 505 506 @Override 507 public InputStream getInputStream() { 508 return p.getInputStream(); 509 } 510 511 @Override 512 public InputStream getErrorStream() { 513 return p.getErrorStream(); 514 } 515 516 @Override 517 public int waitFor() throws InterruptedException { 518 int rslt = p.waitFor(); 519 waitForStreams(); 520 return rslt; 521 } 522 523 @Override 524 public int exitValue() { 525 return p.exitValue(); 526 } 527 528 @Override 529 public void destroy() { 530 p.destroy(); 531 } 532 533 @Override 534 public long pid() { 535 return p.pid(); 536 } 537 538 @Override 539 public boolean isAlive() { 540 return p.isAlive(); 541 } 542 543 @Override 544 public Process destroyForcibly() { 545 return p.destroyForcibly(); 546 } 547 548 @Override 549 public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { 550 boolean rslt = p.waitFor(timeout, unit); 551 if (rslt) { 552 waitForStreams(); 553 } 554 return rslt; 555 } 556 557 private void waitForStreams() throws InterruptedException { 558 try { 559 stdoutTask.get(); 560 } catch (ExecutionException e) { 561 } 562 try { 563 stderrTask.get(); 564 } catch (ExecutionException e) { 565 } 566 } 567 } 568} 569