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