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)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 public static Process startProcess(String name, 89 ProcessBuilder processBuilder, 90 Consumer<String> consumer) 91 throws IOException { 92 try { 93 return startProcess(name, processBuilder, consumer, null, -1, TimeUnit.NANOSECONDS); 94 } catch (InterruptedException | TimeoutException e) { 95 // will never happen 96 throw new RuntimeException(e); 97 } 98 } 99 100 /** 101 * <p>Starts a process from its builder.</p> 102 * <span>The default redirects of STDOUT and STDERR are started</span> 103 * <p> 104 * It is possible to wait for the process to get to a warmed-up state 105 * via {@linkplain Predicate} condition on the STDOUT 106 * </p> 107 * @param name The process name 108 * @param processBuilder The process builder 109 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 110 * Used to determine the moment the target app is 111 * properly warmed-up. 112 * It can be null - in that case the warmup is skipped. 113 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 114 * @param unit The timeout {@linkplain TimeUnit} 115 * @return Returns the initialized {@linkplain Process} 116 * @throws IOException 117 * @throws InterruptedException 118 * @throws TimeoutException 119 */ 120 public static Process startProcess(String name, 121 ProcessBuilder processBuilder, 122 final Predicate<String> linePredicate, 123 long timeout, 124 TimeUnit unit) 125 throws IOException, InterruptedException, TimeoutException { 126 return startProcess(name, processBuilder, null, linePredicate, timeout, unit); 127 } 128 129 /** 130 * <p>Starts a process from its builder.</p> 131 * <span>The default redirects of STDOUT and STDERR are started</span> 132 * <p> 133 * It is possible to wait for the process to get to a warmed-up state 134 * via {@linkplain Predicate} condition on the STDOUT and monitor the 135 * in-streams via the provided {@linkplain Consumer} 136 * </p> 137 * @param name The process name 138 * @param processBuilder The process builder 139 * @param lineConsumer The {@linkplain Consumer} the lines will be forwarded to 140 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 141 * Used to determine the moment the target app is 142 * properly warmed-up. 143 * It can be null - in that case the warmup is skipped. 144 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 145 * @param unit The timeout {@linkplain TimeUnit} 146 * @return Returns the initialized {@linkplain Process} 147 * @throws IOException 148 * @throws InterruptedException 149 * @throws TimeoutException 150 */ 151 public static Process startProcess(String name, 152 ProcessBuilder processBuilder, 153 final Consumer<String> lineConsumer, 154 final Predicate<String> linePredicate, 155 long timeout, 156 TimeUnit unit) 157 throws IOException, InterruptedException, TimeoutException { 158 System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" "))); 159 Process p = processBuilder.start(); 160 StreamPumper stdout = new StreamPumper(p.getInputStream()); 161 StreamPumper stderr = new StreamPumper(p.getErrorStream()); 162 163 stdout.addPump(new LineForwarder(name, System.out)); 164 stderr.addPump(new LineForwarder(name, System.err)); 165 if (lineConsumer != null) { 166 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 167 @Override 168 protected void processLine(String line) { 169 lineConsumer.accept(line); 170 } 171 }; 172 stdout.addPump(pump); 173 stderr.addPump(pump); 174 } 175 176 177 CountDownLatch latch = new CountDownLatch(1); 178 if (linePredicate != null) { 179 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 180 @Override 181 protected void processLine(String line) { 182 if (latch.getCount() > 0 && linePredicate.test(line)) { 183 latch.countDown(); 184 } 185 } 186 }; 187 stdout.addPump(pump); 188 stderr.addPump(pump); 189 } else { 190 latch.countDown(); 191 } 192 final Future<Void> stdoutTask = stdout.process(); 193 final Future<Void> stderrTask = stderr.process(); 194 195 try { 196 if (timeout > -1) { 197 if (timeout == 0) { 198 latch.await(); 199 } else { 200 if (!latch.await(Utils.adjustTimeout(timeout), unit)) { 201 throw new TimeoutException(); 202 } 203 } 204 } 205 } catch (TimeoutException | InterruptedException e) { 206 System.err.println("Failed to start a process (thread dump follows)"); 207 for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) { 208 printStack(s.getKey(), s.getValue()); 209 } 210 211 if (p.isAlive()) { 212 p.destroyForcibly(); 213 } 214 215 stdoutTask.cancel(true); 216 stderrTask.cancel(true); 217 throw e; 218 } 219 220 return new ProcessImpl(p, stdoutTask, stderrTask); 221 } 222 223 /** 224 * <p>Starts a process from its builder.</p> 225 * <span>The default redirects of STDOUT and STDERR are started</span> 226 * <p> 227 * It is possible to wait for the process to get to a warmed-up state 228 * via {@linkplain Predicate} condition on the STDOUT. The warm-up will 229 * wait indefinitely. 230 * </p> 231 * @param name The process name 232 * @param processBuilder The process builder 233 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 234 * Used to determine the moment the target app is 235 * properly warmed-up. 236 * It can be null - in that case the warmup is skipped. 237 * @return Returns the initialized {@linkplain Process} 238 * @throws IOException 239 * @throws InterruptedException 240 * @throws TimeoutException 241 */ 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() throws Exception { 255 RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); 256 int pid = Integer.parseInt(runtime.getName().split("@")[0]); 257 258 return pid; 259 } 260 261 /** 262 * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris) 263 * 264 * @return String[] with platform specific arguments, empty if there are 265 * none 266 */ 267 public static String[] getPlatformSpecificVMArgs() { 268 String osName = System.getProperty("os.name"); 269 String dataModel = System.getProperty("sun.arch.data.model"); 270 271 if (osName.equals("SunOS") && dataModel.equals("64")) { 272 return new String[] { "-d64" }; 273 } 274 275 return new String[] {}; 276 } 277 278 /** 279 * Create ProcessBuilder using the java launcher from the jdk to be tested 280 * and with any platform specific arguments prepended 281 */ 282 public static ProcessBuilder createJavaProcessBuilder(String... command) 283 throws Exception { 284 String javapath = JDKToolFinder.getJDKTool("java"); 285 286 ArrayList<String> args = new ArrayList<>(); 287 args.add(javapath); 288 Collections.addAll(args, getPlatformSpecificVMArgs()); 289 Collections.addAll(args, command); 290 291 // Reporting 292 StringBuilder cmdLine = new StringBuilder(); 293 for (String cmd : args) 294 cmdLine.append(cmd).append(' '); 295 System.out.println("Command line: [" + cmdLine.toString() + "]"); 296 297 return new ProcessBuilder(args.toArray(new String[args.size()])); 298 } 299 300 private static void printStack(Thread t, StackTraceElement[] stack) { 301 System.out.println("\t" + t + 302 " stack: (length = " + stack.length + ")"); 303 if (t != null) { 304 for (StackTraceElement stack1 : stack) { 305 System.out.println("\t" + stack1); 306 } 307 System.out.println(); 308 } 309 } 310 311 /** 312 * Executes a test jvm process, waits for it to finish and returns the process output. 313 * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added. 314 * The java from the test.jdk is used to execute the command. 315 * 316 * The command line will be like: 317 * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds 318 * 319 * The jvm process will have exited before this method returns. 320 * 321 * @param cmds User specifed arguments. 322 * @return The output from the process. 323 */ 324 public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception { 325 ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds)); 326 return executeProcess(pb); 327 } 328 329 /** 330 * Executes a process, waits for it to finish and returns the process output. 331 * The process will have exited before this method returns. 332 * @param pb The ProcessBuilder to execute. 333 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 334 */ 335 public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception { 336 OutputAnalyzer output = null; 337 Process p = null; 338 boolean failed = false; 339 try { 340 p = pb.start(); 341 output = new OutputAnalyzer(p); 342 p.waitFor(); 343 344 return output; 345 } catch (Throwable t) { 346 if (p != null) { 347 p.destroyForcibly().waitFor(); 348 } 349 350 failed = true; 351 System.out.println("executeProcess() failed: " + t); 352 throw t; 353 } finally { 354 if (failed) { 355 System.err.println(getProcessLog(pb, output)); 356 } 357 } 358 } 359 360 /** 361 * Executes a process, waits for it to finish and returns the process output. 362 * 363 * The process will have exited before this method returns. 364 * 365 * @param cmds The command line to execute. 366 * @return The output from the process. 367 */ 368 public static OutputAnalyzer executeProcess(String... cmds) throws Throwable { 369 return executeProcess(new ProcessBuilder(cmds)); 370 } 371 372 /** 373 * Used to log command line, stdout, stderr and exit code from an executed process. 374 * @param pb The executed process. 375 * @param output The output from the process. 376 */ 377 public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) { 378 String stderr = output == null ? "null" : output.getStderr(); 379 String stdout = output == null ? "null" : output.getStdout(); 380 String exitValue = output == null ? "null": Integer.toString(output.getExitValue()); 381 StringBuilder logMsg = new StringBuilder(); 382 final String nl = System.getProperty("line.separator"); 383 logMsg.append("--- ProcessLog ---" + nl); 384 logMsg.append("cmd: " + getCommandLine(pb) + nl); 385 logMsg.append("exitvalue: " + exitValue + nl); 386 logMsg.append("stderr: " + stderr + nl); 387 logMsg.append("stdout: " + stdout + nl); 388 389 return logMsg.toString(); 390 } 391 392 /** 393 * @return The full command line for the ProcessBuilder. 394 */ 395 public static String getCommandLine(ProcessBuilder pb) { 396 if (pb == null) { 397 return "null"; 398 } 399 StringBuilder cmd = new StringBuilder(); 400 for (String s : pb.command()) { 401 cmd.append(s).append(" "); 402 } 403 return cmd.toString().trim(); 404 } 405 406 /** 407 * Executes a process, waits for it to finish, prints the process output 408 * to stdout, 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 {@linkplain OutputAnalyzer} instance wrapping the process. 414 */ 415 public static OutputAnalyzer executeCommand(String... cmds) 416 throws Throwable { 417 String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" ")); 418 System.out.println("Command line: [" + cmdLine + "]"); 419 OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds); 420 System.out.println(analyzer.getOutput()); 421 return analyzer; 422 } 423 424 /** 425 * Executes a process, waits for it to finish, prints the process output 426 * to stdout and returns the process output. 427 * 428 * The process will have exited before this method returns. 429 * 430 * @param pb The ProcessBuilder to execute. 431 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 432 */ 433 public static OutputAnalyzer executeCommand(ProcessBuilder pb) 434 throws Throwable { 435 String cmdLine = pb.command().stream().collect(Collectors.joining(" ")); 436 System.out.println("Command line: [" + cmdLine + "]"); 437 OutputAnalyzer analyzer = ProcessTools.executeProcess(pb); 438 System.out.println(analyzer.getOutput()); 439 return analyzer; 440 } 441 442 private static class ProcessImpl extends Process { 443 444 private final Process p; 445 private final Future<Void> stdoutTask; 446 private final Future<Void> stderrTask; 447 448 public ProcessImpl(Process p, Future<Void> stdoutTask, Future<Void> stderrTask) { 449 this.p = p; 450 this.stdoutTask = stdoutTask; 451 this.stderrTask = stderrTask; 452 } 453 454 @Override 455 public OutputStream getOutputStream() { 456 return p.getOutputStream(); 457 } 458 459 @Override 460 public InputStream getInputStream() { 461 return p.getInputStream(); 462 } 463 464 @Override 465 public InputStream getErrorStream() { 466 return p.getErrorStream(); 467 } 468 469 @Override 470 public int waitFor() throws InterruptedException { 471 int rslt = p.waitFor(); 472 waitForStreams(); 473 return rslt; 474 } 475 476 @Override 477 public int exitValue() { 478 return p.exitValue(); 479 } 480 481 @Override 482 public void destroy() { 483 p.destroy(); 484 } 485 486 @Override 487 public long getPid() { 488 return p.getPid(); 489 } 490 491 @Override 492 public boolean isAlive() { 493 return p.isAlive(); 494 } 495 496 @Override 497 public Process destroyForcibly() { 498 return p.destroyForcibly(); 499 } 500 501 @Override 502 public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { 503 boolean rslt = p.waitFor(timeout, unit); 504 if (rslt) { 505 waitForStreams(); 506 } 507 return rslt; 508 } 509 510 private void waitForStreams() throws InterruptedException { 511 try { 512 stdoutTask.get(); 513 } catch (ExecutionException e) { 514 } 515 try { 516 stderrTask.get(); 517 } catch (ExecutionException e) { 518 } 519 } 520 } 521 }