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 44 import jdk.test.lib.JDKToolFinder; 45 import jdk.test.lib.Utils; 46 47 public final class ProcessTools { 48 private static final class LineForwarder extends StreamPumper.LinePump { 49 private final PrintStream ps; 50 private final String prefix; 51 LineForwarder(String prefix, PrintStream os) { 52 this.ps = os; 53 this.prefix = prefix; 54 } 55 @Override 56 protected void processLine(String line) { 57 ps.println("[" + prefix + "] " + line); 58 } 59 } 60 61 private ProcessTools() { 62 } 63 64 /** 65 * Pumps stdout and stderr from running the process into a String. 66 * 67 * @param processHandler ProcessHandler to run. 68 * @return Output from process. 69 * @throws IOException If an I/O error occurs. 70 */ 71 public static OutputBuffer getOutput(ProcessBuilder processBuilder) throws IOException { 72 return getOutput(processBuilder.start()); 73 } 74 75 /** 76 * Pumps stdout and stderr the running process into a String. 77 * 78 * @param process Process to pump. 79 * @return Output from process. 80 * @throws IOException If an I/O error occurs. 81 */ 82 public static OutputBuffer getOutput(Process process) throws IOException { 83 ByteArrayOutputStream stderrBuffer = new ByteArrayOutputStream(); 84 ByteArrayOutputStream stdoutBuffer = new ByteArrayOutputStream(); 85 StreamPumper outPumper = new StreamPumper(process.getInputStream(), stdoutBuffer); 86 StreamPumper errPumper = new StreamPumper(process.getErrorStream(), stderrBuffer); 87 Thread outPumperThread = new Thread(outPumper); 88 Thread errPumperThread = new Thread(errPumper); 89 90 outPumperThread.setDaemon(true); 91 errPumperThread.setDaemon(true); 92 93 outPumperThread.start(); 94 errPumperThread.start(); 95 96 try { 97 process.waitFor(); 98 outPumperThread.join(); 99 errPumperThread.join(); 100 } catch (InterruptedException e) { 101 Thread.currentThread().interrupt(); 102 return null; 103 } 104 105 return new OutputBuffer(stdoutBuffer.toString(), stderrBuffer.toString()); 106 } 107 108 /** 109 * <p>Starts a process from its builder.</p> 110 * <span>The default redirects of STDOUT and STDERR are started</span> 111 * @param name The process name 112 * @param processBuilder The process builder 113 * @return Returns the initialized process 114 * @throws IOException 115 */ 116 public static Process startProcess(String name, 117 ProcessBuilder processBuilder) 118 throws IOException { 119 return startProcess(name, processBuilder, (Consumer<String>)null); 120 } 121 122 /** 123 * <p>Starts a process from its builder.</p> 124 * <span>The default redirects of STDOUT and STDERR are started</span> 125 * <p>It is possible to monitor the in-streams via the provided {@code consumer} 126 * @param name The process name 127 * @param consumer {@linkplain Consumer} instance to process the in-streams 128 * @param processBuilder The process builder 129 * @return Returns the initialized process 130 * @throws IOException 131 */ 132 @SuppressWarnings("overloads") 133 public static Process startProcess(String name, 134 ProcessBuilder processBuilder, 135 Consumer<String> consumer) 136 throws IOException { 137 try { 138 return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS); 139 } catch (InterruptedException | TimeoutException e) { 140 // will never happen 141 throw new RuntimeException(e); 142 } 143 } 144 145 /** 146 * <p>Starts a process from its builder.</p> 147 * <span>The default redirects of STDOUT and STDERR are started</span> 148 * <p> 149 * It is possible to wait for the process to get to a warmed-up state 150 * via {@linkplain Predicate} condition on the STDOUT 151 * </p> 152 * @param name The process name 153 * @param processBuilder The process builder 154 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 155 * Used to determine the moment the target app is 156 * properly warmed-up. 157 * It can be null - in that case the warmup is skipped. 158 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 159 * @param unit The timeout {@linkplain TimeUnit} 160 * @return Returns the initialized {@linkplain Process} 161 * @throws IOException 162 * @throws InterruptedException 163 * @throws TimeoutException 164 */ 165 public static Process startProcess(String name, 166 ProcessBuilder processBuilder, 167 final Predicate<String> linePredicate, 168 long timeout, 169 TimeUnit unit) 170 throws IOException, InterruptedException, TimeoutException { 171 return startProcess(name, processBuilder, null, linePredicate, timeout, unit); 172 } 173 174 /** 175 * <p>Starts a process from its builder.</p> 176 * <span>The default redirects of STDOUT and STDERR are started</span> 177 * <p> 178 * It is possible to wait for the process to get to a warmed-up state 179 * via {@linkplain Predicate} condition on the STDOUT and monitor the 180 * in-streams via the provided {@linkplain Consumer} 181 * </p> 182 * @param name The process name 183 * @param processBuilder The process builder 184 * @param lineConsumer The {@linkplain Consumer} the lines will be forwarded to 185 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 186 * Used to determine the moment the target app is 187 * properly warmed-up. 188 * It can be null - in that case the warmup is skipped. 189 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 190 * @param unit The timeout {@linkplain TimeUnit} 191 * @return Returns the initialized {@linkplain Process} 192 * @throws IOException 193 * @throws InterruptedException 194 * @throws TimeoutException 195 */ 196 public static Process startProcess(String name, 197 ProcessBuilder processBuilder, 198 final Consumer<String> lineConsumer, 199 final Predicate<String> linePredicate, 200 long timeout, 201 TimeUnit unit) 202 throws IOException, InterruptedException, TimeoutException { 203 System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" "))); 204 Process p = processBuilder.start(); 205 StreamPumper stdout = new StreamPumper(p.getInputStream()); 206 StreamPumper stderr = new StreamPumper(p.getErrorStream()); 207 208 stdout.addPump(new LineForwarder(name, System.out)); 209 stderr.addPump(new LineForwarder(name, System.err)); 210 if (lineConsumer != null) { 211 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 212 @Override 213 protected void processLine(String line) { 214 lineConsumer.accept(line); 215 } 216 }; 217 stdout.addPump(pump); 218 stderr.addPump(pump); 219 } 220 221 222 CountDownLatch latch = new CountDownLatch(1); 223 if (linePredicate != null) { 224 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 225 @Override 226 protected void processLine(String line) { 227 if (latch.getCount() > 0 && linePredicate.test(line)) { 228 latch.countDown(); 229 } 230 } 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().getPid(); 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 getPid() { 535 return p.getPid(); 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 }