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