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