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.PrintStream; 28 import java.lang.management.ManagementFactory; 29 import java.lang.management.RuntimeMXBean; 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.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 44 import sun.management.VMManagement; 45 46 public final class ProcessTools { 47 private static final class LineForwarder extends StreamPumper.LinePump { 48 private final PrintStream ps; 49 private final String prefix; 50 LineForwarder(String prefix, PrintStream os) { 51 this.ps = os; 52 this.prefix = prefix; 53 } 54 @Override 55 protected void processLine(String line) { 56 ps.println("[" + prefix + "] " + line); 57 } 58 } 59 60 private ProcessTools() { 61 } 62 63 /** 64 * <p>Starts a process from its builder.</p> 65 * <span>The default redirects of STDOUT and STDERR are started</span> 66 * @param name The process name 67 * @param processBuilder The process builder 68 * @return Returns the initialized process 69 * @throws IOException 70 */ 71 public static Process startProcess(String name, 72 ProcessBuilder processBuilder) 73 throws IOException { 74 return startProcess(name, processBuilder, (Consumer)null); 75 } 76 77 /** 78 * <p>Starts a process from its builder.</p> 79 * <span>The default redirects of STDOUT and STDERR are started</span> 80 * <p>It is possible to monitor the in-streams via the provided {@code consumer} 81 * @param name The process name 82 * @param consumer {@linkplain Consumer} instance to process the in-streams 83 * @param processBuilder The process builder 84 * @return Returns the initialized process 85 * @throws IOException 86 */ 87 public static Process startProcess(String name, 88 ProcessBuilder processBuilder, 89 Consumer<String> consumer) 90 throws IOException { 91 Process p = null; 92 try { 93 p = startProcess( 94 name, 95 processBuilder, 96 line -> { 97 if (consumer != null) { 98 consumer.accept(line); 99 } 100 return false; 101 }, 102 -1, 103 TimeUnit.NANOSECONDS 104 ); 105 } catch (InterruptedException | TimeoutException e) { 106 // can't ever happen 107 } 108 return p; 109 } 110 111 /** 112 * <p>Starts a process from its builder.</p> 113 * <span>The default redirects of STDOUT and STDERR are started</span> 114 * <p> 115 * It is possible to wait for the process to get to a warmed-up state 116 * via {@linkplain Predicate} condition on the STDOUT 117 * </p> 118 * @param name The process name 119 * @param processBuilder The process builder 120 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 121 * Used to determine the moment the target app is 122 * properly warmed-up. 123 * It can be null - in that case the warmup is skipped. 124 * @param timeout The timeout for the warmup waiting; -1 = no wait; 0 = wait forever 125 * @param unit The timeout {@linkplain TimeUnit} 126 * @return Returns the initialized {@linkplain Process} 127 * @throws IOException 128 * @throws InterruptedException 129 * @throws TimeoutException 130 */ 131 public static Process startProcess(String name, 132 ProcessBuilder processBuilder, 133 final Predicate<String> linePredicate, 134 long timeout, 135 TimeUnit unit) 136 throws IOException, InterruptedException, TimeoutException { 137 System.out.println("["+name+"]:" + processBuilder.command().stream().collect(Collectors.joining(" "))); 138 Process p = processBuilder.start(); 139 StreamPumper stdout = new StreamPumper(p.getInputStream()); 140 StreamPumper stderr = new StreamPumper(p.getErrorStream()); 141 142 stdout.addPump(new LineForwarder(name, System.out)); 143 stderr.addPump(new LineForwarder(name, System.err)); 144 CountDownLatch latch = new CountDownLatch(1); 145 if (linePredicate != null) { 146 StreamPumper.LinePump pump = new StreamPumper.LinePump() { 147 @Override 148 protected void processLine(String line) { 149 if (latch.getCount() > 0 && linePredicate.test(line)) { 150 latch.countDown(); 151 } 152 } 153 }; 154 stdout.addPump(pump); 155 stderr.addPump(pump); 156 } else { 157 latch.countDown(); 158 } 159 Future<Void> stdoutTask = stdout.process(); 160 Future<Void> stderrTask = stderr.process(); 161 162 try { 163 if (timeout > -1) { 164 if (timeout == 0) { 165 latch.await(); 166 } else { 167 if (!latch.await(Utils.adjustTimeout(timeout), unit)) { 168 throw new TimeoutException(); 169 } 170 } 171 } 172 } catch (TimeoutException | InterruptedException e) { 173 System.err.println("Failed to start a process (thread dump follows)"); 174 for(Map.Entry<Thread, StackTraceElement[]> s : Thread.getAllStackTraces().entrySet()) { 175 printStack(s.getKey(), s.getValue()); 176 } 177 178 if (p.isAlive()) { 179 p.destroyForcibly(); 180 } 181 182 stdoutTask.cancel(true); 183 stderrTask.cancel(true); 184 throw e; 185 } 186 187 return p; 188 } 189 190 /** 191 * <p>Starts a process from its builder.</p> 192 * <span>The default redirects of STDOUT and STDERR are started</span> 193 * <p> 194 * It is possible to wait for the process to get to a warmed-up state 195 * via {@linkplain Predicate} condition on the STDOUT 196 * </p> 197 * @param name The process name 198 * @param processBuilder The process builder 199 * @param linePredicate The {@linkplain Predicate} to use on the STDOUT 200 * Used to determine the moment the target app is 201 * properly warmed-up. 202 * It can be null - in that case the warmup is skipped. 203 * @return Returns the initialized {@linkplain Process} 204 * @throws IOException 205 * @throws InterruptedException 206 * @throws TimeoutException 207 */ 208 public static Process startProcess(String name, 209 ProcessBuilder processBuilder, 210 final Predicate<String> linePredicate) 211 throws IOException, InterruptedException, TimeoutException { 212 return startProcess(name, processBuilder, linePredicate, 0, TimeUnit.SECONDS); 213 } 214 215 /** 216 * Get the process id of the current running Java process 217 * 218 * @return Process id 219 */ 220 public static int getProcessId() throws Exception { 221 222 // Get the current process id using a reflection hack 223 RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); 224 Field jvm = runtime.getClass().getDeclaredField("jvm"); 225 226 jvm.setAccessible(true); 227 VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime); 228 229 Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId"); 230 231 pid_method.setAccessible(true); 232 233 int pid = (Integer) pid_method.invoke(mgmt); 234 235 return pid; 236 } 237 238 /** 239 * Get platform specific VM arguments (e.g. -d64 on 64bit Solaris) 240 * 241 * @return String[] with platform specific arguments, empty if there are 242 * none 243 */ 244 public static String[] getPlatformSpecificVMArgs() { 245 String osName = System.getProperty("os.name"); 246 String dataModel = System.getProperty("sun.arch.data.model"); 247 248 if (osName.equals("SunOS") && dataModel.equals("64")) { 249 return new String[] { "-d64" }; 250 } 251 252 return new String[] {}; 253 } 254 255 /** 256 * Create ProcessBuilder using the java launcher from the jdk to be tested 257 * and with any platform specific arguments prepended 258 */ 259 public static ProcessBuilder createJavaProcessBuilder(String... command) 260 throws Exception { 261 String javapath = JDKToolFinder.getJDKTool("java"); 262 263 ArrayList<String> args = new ArrayList<>(); 264 args.add(javapath); 265 Collections.addAll(args, getPlatformSpecificVMArgs()); 266 Collections.addAll(args, command); 267 268 // Reporting 269 StringBuilder cmdLine = new StringBuilder(); 270 for (String cmd : args) 271 cmdLine.append(cmd).append(' '); 272 System.out.println("Command line: [" + cmdLine.toString() + "]"); 273 274 return new ProcessBuilder(args.toArray(new String[args.size()])); 275 } 276 277 private static void printStack(Thread t, StackTraceElement[] stack) { 278 System.out.println("\t" + t + 279 " stack: (length = " + stack.length + ")"); 280 if (t != null) { 281 for (StackTraceElement stack1 : stack) { 282 System.out.println("\t" + stack1); 283 } 284 System.out.println(); 285 } 286 } 287 288 /** 289 * Executes a test jvm process, waits for it to finish and returns the process output. 290 * The default jvm options from jtreg, test.vm.opts and test.java.opts, are added. 291 * The java from the test.jdk is used to execute the command. 292 * 293 * The command line will be like: 294 * {test.jdk}/bin/java {test.vm.opts} {test.java.opts} cmds 295 * 296 * The jvm process will have exited before this method returns. 297 * 298 * @param cmds User specifed arguments. 299 * @return The output from the process. 300 */ 301 public static OutputAnalyzer executeTestJvm(String... cmds) throws Exception { 302 ProcessBuilder pb = createJavaProcessBuilder(Utils.addTestJavaOpts(cmds)); 303 return executeProcess(pb); 304 } 305 306 /** 307 * Executes a process, waits for it to finish and returns the process output. 308 * The process will have exited before this method returns. 309 * @param pb The ProcessBuilder to execute. 310 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 311 */ 312 public static OutputAnalyzer executeProcess(ProcessBuilder pb) throws Exception { 313 OutputAnalyzer output = null; 314 Process p = null; 315 boolean failed = false; 316 try { 317 p = pb.start(); 318 output = new OutputAnalyzer(p); 319 p.waitFor(); 320 321 return output; 322 } catch (Throwable t) { 323 if (p != null) { 324 p.destroyForcibly().waitFor(); 325 } 326 327 failed = true; 328 System.out.println("executeProcess() failed: " + t); 329 throw t; 330 } finally { 331 if (failed) { 332 System.err.println(getProcessLog(pb, output)); 333 } 334 } 335 } 336 337 /** 338 * Executes a process, waits for it to finish and returns the process output. 339 * 340 * The process will have exited before this method returns. 341 * 342 * @param cmds The command line to execute. 343 * @return The output from the process. 344 */ 345 public static OutputAnalyzer executeProcess(String... cmds) throws Throwable { 346 return executeProcess(new ProcessBuilder(cmds)); 347 } 348 349 /** 350 * Used to log command line, stdout, stderr and exit code from an executed process. 351 * @param pb The executed process. 352 * @param output The output from the process. 353 */ 354 public static String getProcessLog(ProcessBuilder pb, OutputAnalyzer output) { 355 String stderr = output == null ? "null" : output.getStderr(); 356 String stdout = output == null ? "null" : output.getStdout(); 357 String exitValue = output == null ? "null": Integer.toString(output.getExitValue()); 358 StringBuilder logMsg = new StringBuilder(); 359 final String nl = System.getProperty("line.separator"); 360 logMsg.append("--- ProcessLog ---" + nl); 361 logMsg.append("cmd: " + getCommandLine(pb) + nl); 362 logMsg.append("exitvalue: " + exitValue + nl); 363 logMsg.append("stderr: " + stderr + nl); 364 logMsg.append("stdout: " + stdout + nl); 365 366 return logMsg.toString(); 367 } 368 369 /** 370 * @return The full command line for the ProcessBuilder. 371 */ 372 public static String getCommandLine(ProcessBuilder pb) { 373 if (pb == null) { 374 return "null"; 375 } 376 StringBuilder cmd = new StringBuilder(); 377 for (String s : pb.command()) { 378 cmd.append(s).append(" "); 379 } 380 return cmd.toString().trim(); 381 } 382 383 /** 384 * Executes a process, waits for it to finish, prints the process output 385 * to stdout, and returns the process output. 386 * 387 * The process will have exited before this method returns. 388 * 389 * @param cmds The command line to execute. 390 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 391 */ 392 public static OutputAnalyzer executeCommand(String... cmds) 393 throws Throwable { 394 String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" ")); 395 System.out.println("Command line: [" + cmdLine + "]"); 396 OutputAnalyzer analyzer = ProcessTools.executeProcess(cmds); 397 System.out.println(analyzer.getOutput()); 398 return analyzer; 399 } 400 401 /** 402 * Executes a process, waits for it to finish, prints the process output 403 * to stdout and returns the process output. 404 * 405 * The process will have exited before this method returns. 406 * 407 * @param pb The ProcessBuilder to execute. 408 * @return The {@linkplain OutputAnalyzer} instance wrapping the process. 409 */ 410 public static OutputAnalyzer executeCommand(ProcessBuilder pb) 411 throws Throwable { 412 String cmdLine = pb.command().stream().collect(Collectors.joining(" ")); 413 System.out.println("Command line: [" + cmdLine + "]"); 414 OutputAnalyzer analyzer = ProcessTools.executeProcess(pb); 415 System.out.println(analyzer.getOutput()); 416 return analyzer; 417 } 418 }