LingeredApp.java revision 2224:2a8815d86b93
1/* 2 * Copyright (c) 2015, 2016, 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 24package jdk.test.lib.apps; 25 26import java.io.BufferedReader; 27import java.io.IOException; 28import java.io.InputStream; 29import java.io.InputStreamReader; 30import java.nio.file.Files; 31import java.nio.file.NoSuchFileException; 32import java.nio.file.Path; 33import java.nio.file.Paths; 34import java.nio.file.attribute.BasicFileAttributes; 35import java.nio.file.attribute.FileTime; 36import java.util.ArrayList; 37import java.util.Date; 38import java.util.List; 39import java.util.Map; 40import java.util.UUID; 41 42/** 43 * This is a framework to launch an app that could be synchronized with caller 44 * to make further attach actions reliable across supported platforms 45 46 * Caller example: 47 * SmartTestApp a = SmartTestApp.startApp(cmd); 48 * // do something 49 * a.stopApp(); 50 * 51 * or fine grained control 52 * 53 * a = new SmartTestApp("MyLock.lck"); 54 * a.createLock(); 55 * a.runApp(); 56 * a.waitAppReady(); 57 * // do something 58 * a.deleteLock(); 59 * a.waitAppTerminate(); 60 * 61 * Then you can work with app output and process object 62 * 63 * output = a.getAppOutput(); 64 * process = a.getProcess(); 65 * 66 */ 67public class LingeredApp { 68 69 private static final long spinDelay = 1000; 70 private static final int appWaitTime = 100; 71 72 private final String lockFileName; 73 private long lockCreationTime; 74 private Process appProcess; 75 private final ArrayList<String> storedAppOutput; 76 77 /* 78 * Drain child process output, store it into string array 79 */ 80 class InputGobbler extends Thread { 81 82 InputStream is; 83 List<String> astr; 84 85 InputGobbler(InputStream is, List<String> astr) { 86 this.is = is; 87 this.astr = astr; 88 } 89 90 public void run() { 91 try { 92 InputStreamReader isr = new InputStreamReader(is); 93 BufferedReader br = new BufferedReader(isr); 94 String line = null; 95 while ((line = br.readLine()) != null) { 96 astr.add(line); 97 } 98 } catch (IOException ex) { 99 // pass 100 } 101 } 102 } 103 104 /** 105 * Create LingeredApp object on caller side. Lock file have be a valid filename 106 * at writable location 107 * 108 * @param lockFileName - the name of lock file 109 */ 110 public LingeredApp(String lockFileName) { 111 this.lockFileName = lockFileName; 112 this.storedAppOutput = new ArrayList<String>(); 113 } 114 115 public LingeredApp() { 116 final String lockName = UUID.randomUUID().toString() + ".lck"; 117 this.lockFileName = lockName; 118 this.storedAppOutput = new ArrayList<String>(); 119 } 120 121 /** 122 * 123 * @return name of lock file 124 */ 125 public String getLockFileName() { 126 return this.lockFileName; 127 } 128 129 /** 130 * 131 * @return name of testapp 132 */ 133 public String getAppName() { 134 return this.getClass().getName(); 135 } 136 137 /** 138 * 139 * @return pid of java process running testapp 140 */ 141 public long getPid() { 142 if (appProcess == null) { 143 throw new RuntimeException("Process is not alive"); 144 } 145 return appProcess.getPid(); 146 } 147 148 /** 149 * 150 * @return process object 151 */ 152 public Process getProcess() { 153 return appProcess; 154 } 155 156 /** 157 * 158 * @return application output as string array. Empty array if application produced no output 159 */ 160 public List<String> getAppOutput() { 161 if (appProcess.isAlive()) { 162 throw new RuntimeException("Process is still alive. Can't get its output."); 163 } 164 return storedAppOutput; 165 } 166 167 /* Make sure all part of the app use the same method to get dates, 168 as different methods could produce different results 169 */ 170 private static long epoch() { 171 return new Date().getTime(); 172 } 173 174 private static long lastModified(String fileName) throws IOException { 175 Path path = Paths.get(fileName); 176 BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); 177 return attr.lastModifiedTime().toMillis(); 178 } 179 180 private static void setLastModified(String fileName, long newTime) throws IOException { 181 Path path = Paths.get(fileName); 182 FileTime fileTime = FileTime.fromMillis(newTime); 183 Files.setLastModifiedTime(path, fileTime); 184 } 185 186 /** 187 * create lock 188 * 189 * @throws IOException 190 */ 191 public void createLock() throws IOException { 192 Path path = Paths.get(lockFileName); 193 // Files.deleteIfExists(path); 194 Files.createFile(path); 195 lockCreationTime = lastModified(lockFileName); 196 } 197 198 /** 199 * Delete lock 200 * 201 * @throws IOException 202 */ 203 public void deleteLock() throws IOException { 204 try { 205 Path path = Paths.get(lockFileName); 206 Files.delete(path); 207 } catch (NoSuchFileException ex) { 208 // Lock already deleted. Ignore error 209 } 210 } 211 212 public void waitAppTerminate() { 213 while (true) { 214 try { 215 appProcess.waitFor(); 216 break; 217 } catch (InterruptedException ex) { 218 // pass 219 } 220 } 221 } 222 223 /** 224 * The app touches the lock file when it's started 225 * wait while it happens. Caller have to delete lock on wait error. 226 * 227 * @param timeout 228 * @throws java.io.IOException 229 */ 230 public void waitAppReady(long timeout) throws IOException { 231 long here = epoch(); 232 while (true) { 233 long epoch = epoch(); 234 if (epoch - here > (timeout * 1000)) { 235 throw new IOException("App waiting timeout"); 236 } 237 238 // Live process should touch lock file every second 239 long lm = lastModified(lockFileName); 240 if (lm > lockCreationTime) { 241 break; 242 } 243 244 // Make sure process didn't already exit 245 if (!appProcess.isAlive()) { 246 throw new IOException("App exited unexpectedly with " + appProcess.exitValue()); 247 } 248 249 try { 250 Thread.sleep(spinDelay); 251 } catch (InterruptedException ex) { 252 // pass 253 } 254 } 255 } 256 257 /** 258 * Run the app 259 * 260 * @param vmArguments 261 * @throws IOException 262 */ 263 public void runApp(List<String> vmArguments) 264 throws IOException { 265 266 // We should always use testjava or throw an exception, 267 // so we can't use JDKToolFinder.getJDKTool("java"); 268 // that falls back to compile java on error 269 String jdkPath = System.getProperty("test.jdk"); 270 if (jdkPath == null) { 271 // we are not under jtreg, try env 272 Map<String, String> env = System.getenv(); 273 jdkPath = env.get("TESTJAVA"); 274 } 275 276 if (jdkPath == null) { 277 throw new RuntimeException("Can't determine jdk path neither test.jdk property no TESTJAVA env are set"); 278 } 279 280 String osname = System.getProperty("os.name"); 281 String javapath = jdkPath + ((osname.startsWith("window")) ? "/bin/java.exe" : "/bin/java"); 282 283 List<String> cmd = new ArrayList<String>(); 284 cmd.add(javapath); 285 286 287 if (vmArguments == null) { 288 // Propagate test.vm.options to LingeredApp, filter out possible empty options 289 String testVmOpts[] = System.getProperty("test.vm.opts","").split("\\s+"); 290 for (String s : testVmOpts) { 291 if (!s.equals("")) { 292 cmd.add(s); 293 } 294 } 295 } 296 else{ 297 // Lets user manage LingeredApp options 298 cmd.addAll(vmArguments); 299 } 300 301 // Make sure we set correct classpath to run the app 302 cmd.add("-cp"); 303 String classpath = System.getProperty("test.class.path"); 304 cmd.add((classpath == null) ? "." : classpath); 305 306 cmd.add(this.getAppName()); 307 cmd.add(lockFileName); 308 309 // Reporting 310 StringBuilder cmdLine = new StringBuilder(); 311 for (String strCmd : cmd) { 312 cmdLine.append("'").append(strCmd).append("' "); 313 } 314 315 // A bit of verbosity 316 System.out.println("Command line: [" + cmdLine.toString() + "]"); 317 318 ProcessBuilder pb = new ProcessBuilder(cmd); 319 // we don't expect any error output but make sure we are not stuck on pipe 320 // pb.redirectErrorStream(false); 321 pb.redirectError(ProcessBuilder.Redirect.INHERIT); 322 323 appProcess = pb.start(); 324 325 // Create pipe reader for process, and read stdin and stderr to array of strings 326 InputGobbler gb = new InputGobbler(appProcess.getInputStream(), storedAppOutput); 327 gb.start(); 328 } 329 330 /** 331 * Delete lock file that signals app to terminate, then 332 * wait until app is actually terminated. 333 * @throws IOException 334 */ 335 public void stopApp() throws IOException { 336 deleteLock(); 337 waitAppTerminate(); 338 int exitcode = appProcess.exitValue(); 339 if (exitcode != 0) { 340 throw new IOException("LingeredApp terminated with non-zero exit code " + exitcode); 341 } 342 } 343 344 /** 345 * High level interface for test writers 346 */ 347 /** 348 * Factory method that creates LingeredApp object with ready to use application 349 * lock name is autogenerated 350 * @param cmd - vm options, could be null to auto add testvm.options 351 * @return LingeredApp object 352 * @throws IOException 353 */ 354 public static LingeredApp startApp(List<String> cmd) throws IOException { 355 LingeredApp a = new LingeredApp(); 356 a.createLock(); 357 try { 358 a.runApp(cmd); 359 a.waitAppReady(appWaitTime); 360 } catch (Exception ex) { 361 a.deleteLock(); 362 throw ex; 363 } 364 365 return a; 366 } 367 368 /** 369 * Factory method that starts pre-created LingeredApp 370 * lock name is autogenerated 371 * @param cmd - vm options, could be null to auto add testvm.options 372 * @param theApp - app to start 373 * @return LingeredApp object 374 * @throws IOException 375 */ 376 377 public static void startApp(List<String> cmd, LingeredApp theApp) throws IOException { 378 theApp.createLock(); 379 try { 380 theApp.runApp(cmd); 381 theApp.waitAppReady(appWaitTime); 382 } catch (Exception ex) { 383 theApp.deleteLock(); 384 throw ex; 385 } 386 } 387 388 public static LingeredApp startApp() throws IOException { 389 return startApp(null); 390 } 391 392 public static void stopApp(LingeredApp app) throws IOException { 393 if (app != null) { 394 // LingeredApp can throw an exception during the intialization, 395 // make sure we don't have cascade NPE 396 app.stopApp(); 397 } 398 } 399 400 /** 401 * LastModified time might not work correctly in some cases it might 402 * cause later failures 403 */ 404 405 public static boolean isLastModifiedWorking() { 406 boolean sane = true; 407 try { 408 long lm = lastModified("."); 409 if (lm == 0) { 410 System.err.println("SANITY Warning! The lastModifiedTime() doesn't work on this system, it returns 0"); 411 sane = false; 412 } 413 414 long now = epoch(); 415 if (lm > now) { 416 System.err.println("SANITY Warning! The Clock is wrong on this system lastModifiedTime() > getTime()"); 417 sane = false; 418 } 419 420 setLastModified(".", epoch()); 421 long lm1 = lastModified("."); 422 if (lm1 <= lm) { 423 System.err.println("SANITY Warning! The setLastModified doesn't work on this system"); 424 sane = false; 425 } 426 } 427 catch(IOException e) { 428 System.err.println("SANITY Warning! IOException during sanity check " + e); 429 sane = false; 430 } 431 432 return sane; 433 } 434 435 /** 436 * This part is the application it self 437 */ 438 public static void main(String args[]) { 439 440 if (args.length != 1) { 441 System.err.println("Lock file name is not specified"); 442 System.exit(7); 443 } 444 445 String theLockFileName = args[0]; 446 447 try { 448 Path path = Paths.get(theLockFileName); 449 450 while (Files.exists(path)) { 451 // Touch the lock to indicate our readiness 452 setLastModified(theLockFileName, epoch()); 453 Thread.sleep(spinDelay); 454 } 455 } catch (NoSuchFileException ex) { 456 // Lock deleted while we are setting last modified time. 457 // Ignore error and lets the app exits 458 } catch (Exception ex) { 459 System.err.println("LingeredApp ERROR: " + ex); 460 // Leave exit_code = 1 to Java launcher 461 System.exit(3); 462 } 463 464 System.exit(0); 465 } 466} 467