1 /* 2 * Copyright (c) 2013, 2015, 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.testlibrary; 25 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.io.PrintStream; 30 import java.lang.management.ManagementFactory; 31 import java.lang.management.RuntimeMXBean; 32 import java.lang.reflect.Field; 33 import java.lang.reflect.Method; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.concurrent.CountDownLatch; 38 import java.util.Map; 39 import java.util.concurrent.ExecutionException; 40 import java.util.concurrent.Future; 41 import java.util.concurrent.TimeUnit; 42 import java.util.concurrent.TimeoutException; 43 import java.util.function.Predicate; 44 import java.util.function.Consumer; 45 import java.util.stream.Collectors; 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 * <p>Starts a process from its builder.</p> 66 * <span>The default redirects of STDOUT and STDERR are started</span> 67 * @param name The process name 68 * @param processBuilder The process builder 69 * @return Returns the initialized process 70 * @throws IOException 71 */ 72 public static Process startProcess(String name, 73 ProcessBuilder processBuilder) 74 throws IOException { 75 return startProcess(name, processBuilder, (Consumer<String>)null); 76 } 77 78 /** 79 * <p>Starts a process from its builder.</p> 80 * <span>The default redirects of STDOUT and STDERR are started</span> 81 * <p>It is possible to monitor the in-streams via the provided {@code consumer} 82 * @param name The process name 83 * @param consumer {@linkplain Consumer} instance to process the in-streams 84 * @param processBuilder The process builder 85 * @return Returns the initialized process 86 * @throws IOException 87 */ 88 @SuppressWarnings("overloads") 89 public static Process startProcess(String name, 90 ProcessBuilder processBuilder, 91 Consumer<String> consumer) 92 throws IOException { 93 try { 94 return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS); 95 } catch (InterruptedException | TimeoutException e) { 96 // will never happen 97 throw new RuntimeException(e); 98 } 99 } 100 101 /** 102 * <p>Starts a process from its builder.</p> 103 * <span>The default redirects of STDOUT and STDERR are started</span> 104 * <p> 105 * It is possible to wait for the process to get to a warmed-up state 106 * via {@linkplain Predicate} condition on the STDOUT 107 * </p> 108 * @param name The process name 109 * @param processBuilder The process builder 110 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 111 * Used to determine the moment the target app is 112 * properly warmed-up. 113 * It can be null - in that case the warmup is skipped. 114 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 115 * @param unit The timeout {@linkplain TimeUnit} 116 * @return Returns the initialized {@linkplain Process} 117 * @throws IOException 118 * @throws InterruptedException 119 * @throws TimeoutException 120 */ 121 public static Process startProcess(String name, 122 ProcessBuilder processBuilder, 123 final Predicate<String> linePredicate, 124 long timeout, 125 TimeUnit unit) 126 throws IOException, InterruptedException, TimeoutException { 127 return startProcess(name, processBuilder, null, linePredicate, timeout, unit); 128 } 129 130 /** 131 * <p>Starts a process from its builder.</p> 132 * <span>The default redirects of STDOUT and STDERR are started</span> 133 * <p> 134 * It is possible to wait for the process to get to a warmed-up state 135 * via {@linkplain Predicate} condition on the STDOUT and monitor the 136 * in-streams via the provided {@linkplain Consumer} 137 * </p> 138 * @param name The process name 139 * @param processBuilder The process builder 140 * @param lineConsumer The {@linkplain Consumer} the lines will be forwarded to 141 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 142 * Used to determine the moment the target app is 143 * properly warmed-up. 144 * It can be null - in that case the warmup is skipped. 145 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 146 * @param unit The timeout {@linkplain TimeUnit} 147 * @return Returns the initialized {@linkplain Process} 148 * @throws IOException 149 * @throws InterruptedException 150 * @throws TimeoutException 151 */ 152 public static Process startProcess(String name, 153 ProcessBuilder processBuilder, 154 final Consumer<String> lineConsumer, 155 final Predicate<String> linePredicate, 156 long timeout, 157 TimeUnit unit) 158 throws IOException, InterruptedException, TimeoutException { 159 System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" "))); 160 Process p = processBuilder.start(); 161 StreamPumper stdout = new StreamPumper(p.getInputStream()); 162 StreamPumper stderr = new StreamPumper(p.getErrorStream()); 163 164 stdout.addPump(new LineForwarder(name, System.out)); 165 stderr.addPump(new LineForwarder(name, System.err)); 166 if (lineConsumer != null) { 167 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 168 @Override 169 protected void processLine(String line) { 170 lineConsumer.accept(line); 171 } 172 }; 173 stdout.addPump(pump); 174 stderr.addPump(pump); 175 } 176 177 178 CountDownLatch latch = new CountDownLatch(1); 179 if (linePredicate != null) { 180 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 181 @Override 182 protected void processLine(String line) { 183 if (latch.getCount() > 0 && linePredicate.test(line)) { 184 latch.countDown(); 185 } 186 } 187 }; 188 stdout.addPump(pump); 189 stderr.addPump(pump); 190 } else { 191 latch.countDown(); 192 } 193 final Future<Void> stdoutTask = stdout.process(); 194 final Future<Void> stderrTask = stderr.process(); 195 196 try { 197 if (timeout > -1) { 198 if (timeout == 0) { 199 latch.await(); 200 } else { 201 if (!latch.await(Utils.adjustTimeout(timeout), unit)) { 202 throw new TimeoutException(); 203 } 204 } 205 } 206 } catch (TimeoutException | InterruptedException e) { 207 System.err.println("Failed to start a process (thread dump follows)"); 208 for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) { 209 printStack(s.getKey(), s.getValue()); 210 } 211 212 if (p.isAlive()) { 213 p.destroyForcibly(); 214 } 215 216 stdoutTask.cancel(true); 217 stderrTask.cancel(true); 218 throw e; 219 } 220 221 return new ProcessImpl(p, stdoutTask, stderrTask); 222 } 223 224 /** 225 * <p>Starts a process from its builder.</p> 226 * <span>The default redirects of STDOUT and STDERR are started</span> 227 * <p> 228 * It is possible to wait for the process to get to a warmed-up state 229 * via {@linkplain Predicate} condition on the STDOUT. The warm-up will 230 * wait indefinitely. 231 * </p> 232 * @param name The process name 233 * @param processBuilder The process builder 234 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 235 * Used to determine the moment the target app is 236 * properly warmed-up. 237 * It can be null - in that case the warmup is skipped. 238 * @return Returns the initialized {@linkplain Process} 239 * @throws IOException 240 * @throws InterruptedException 241 * @throws TimeoutException 242 */ 243 @SuppressWarnings("overloads") 244 public static Process startProcess(String name, 245 ProcessBuilder processBuilder, 246 final Predicate<String> linePredicate) 247 throws IOException, InterruptedException, TimeoutException { 248 return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS); 249 } 250 251 /** 252 * Get the process id of the current running Java process 253 * 254 * @return Process id 255 */ 256 public static int getProcessId() throws Exception { 257 RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); 258 int pid = Integer.parseInt(runtime.getName().split("@")[0]); 259 260 return pid; 261 } 262 263 /** 264 * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris) 265 * 266 * @return String[] with platform specific arguments, empty if there are 267 * none 268 */ 269 public static String[] getPlatformSpecificVMArgs() { 270 String osName = System.getProperty("os.name"); 271 String dataModel = System.getProperty("sun.arch.data.model"); 272 273 if (osName.equals("SunOS") && dataModel.equals("64")) { 274 return new String[] { "-d64" }; 275 } 276 277 return new String[] {}; 278 } 279 280 /** 281 * Create ProcessBuilder using the java launcher from the jdk to be tested, 282 * and with any platform specific arguments prepended. 283 * 284 * @param command Arguments to pass to the java command. 285 * @return The ProcessBuilder instance representing the java command. 286 */ 287 public static ProcessBuilder createJavaProcessBuilder(String... command) 288 throws Exception { 289 return createJavaProcessBuilder(false, command); 290 } 291 292 /** 293 * Create ProcessBuilder using the java launcher from the jdk to be tested, 294 * and with any platform specific arguments prepended. 295 * 296 * @param addTestVmAndJavaOptions If true, adds test.vm.opts and test.java.opts 297 * to the java arguments. 298 * @param command Arguments to pass to the java command. 299 * @return The ProcessBuilder instance representing the java command. 300 */ 301 public static ProcessBuilder createJavaProcessBuilder(boolean addTestVmAndJavaOptions, String... command) throws Exception { 302 String javapath = JDKToolFinder.getJDKTool("java"); 303 304 ArrayList<String> args = new ArrayList<>(); 305 args.add(javapath); 306 Collections.addAll(args, getPlatformSpecificVMArgs()); 307 308 if (addTestVmAndJavaOptions) { 309 // -cp is needed to make sure the same classpath is used whether the test is 310 // run in AgentVM mode or OtherVM mode. It was added to the hotspot version 311 // of this API as part of 8077608. However, for the jdk version it is only 312 // added when addTestVmAndJavaOptions is true in order to minimize 313 // disruption to existing JDK tests, which have yet to be tested with -cp 314 // being added. At some point -cp should always be added to be consistent 315 // with what the hotspot version does. 316 args.add("-cp"); 317 args.add(System.getProperty("java.class.path")); 318 Collections.addAll(args, Utils.getTestJavaOpts()); 319 } 320 321 Collections.addAll(args, command); 322 323 // Reporting 324 StringBuilder cmdLine = new StringBuilder(); 325 for (String cmd : args) 326 cmdLine.append(cmd).append(' '); 327 System.out.println("Command line: [" + cmdLine.toString() + "]"); 328 329 return new ProcessBuilder(args.toArray(new String[args.size()])); 330 } 331 332 private static void printStack(Thread t, StackTraceElement[] stack) { 333 System.out.println("\t" + t + 334 " stack: (length = " + stack.length + ")"); 335 if (t != null) { 336 for (StackTraceElement stack1 : stack) { 337 System.out.println("\t" + stack1); 338 } 339 System.out.println(); 340 } 341 } 342 343 /** 344 * Executes a test jvm process, waits for it to finish and returns the process output. 345 * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added. 346 * The java from the test.jdk is used to execute the command. 347 * 348 * The command line will be like: 349 * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds 350 * 351 * The jvm process will have exited before this method returns. 352 * 353 * @param cmds User specifed arguments. 354 * @return The output from the process. 355 */ 356 public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception { 357 ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds)); 358 return executeProcess(pb); 359 } 360 361 /** 362 * Executes a process, waits for it to finish and returns the process output. 363 * The process will have exited before this method returns. 364 * @param pb The ProcessBuilder to execute. 365 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 366 */ 367 public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception { 368 return executeProcess(pb, null); 369 } 370 371 /** 372 * Executes a process, pipe some text into its STDIN, waits for it 373 * to finish and returns the process output. The process will have exited 374 * before this method returns. 375 * @param pb The ProcessBuilder to execute. 376 * @param input The text to pipe into STDIN. Can be null. 377 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 378 */ 379 public static OutputAnalyzer executeProcess(ProcessBuilder pb, String input) 380 throws Exception { 381 OutputAnalyzer output = null; 382 Process p = null; 383 boolean failed = false; 384 try { 385 p = pb.start(); 386 if (input != null) { 387 try (OutputStream os = p.getOutputStream()) { 388 os.write(input.getBytes()); 389 os.flush(); 390 } 391 } 392 output = new OutputAnalyzer(p); 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().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 class ProcessImpl extends Process { 494 495 private final Process p; 496 private final Future<Void> stdoutTask; 497 private final Future<Void> stderrTask; 498 499 public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) { 500 this.p = p; 501 this.stdoutTask = stdoutTask; 502 this.stderrTask = stderrTask; 503 } 504 505 @Override 506 public OutputStream getOutputStream() { 507 return p.getOutputStream(); 508 } 509 510 @Override 511 public InputStream getInputStream() { 512 return p.getInputStream(); 513 } 514 515 @Override 516 public InputStream getErrorStream() { 517 return p.getErrorStream(); 518 } 519 520 @Override 521 public int waitFor() throws InterruptedException { 522 int rslt = p.waitFor(); 523 waitForStreams(); 524 return rslt; 525 } 526 527 @Override 528 public int exitValue() { 529 return p.exitValue(); 530 } 531 532 @Override 533 public void destroy() { 534 p.destroy(); 535 } 536 537 @Override 538 public long getPid() { 539 return p.getPid(); 540 } 541 542 @Override 543 public boolean isAlive() { 544 return p.isAlive(); 545 } 546 547 @Override 548 public Process destroyForcibly() { 549 return p.destroyForcibly(); 550 } 551 552 @Override 553 public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { 554 boolean rslt = p.waitFor(timeout, unit); 555 if (rslt) { 556 waitForStreams(); 557 } 558 return rslt; 559 } 560 561 private void waitForStreams() throws InterruptedException { 562 try { 563 stdoutTask.get(); 564 } catch (ExecutionException e) { 565 } 566 try { 567 stderrTask.get(); 568 } catch (ExecutionException e) { 569 } 570 } 571 } 572 }