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