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