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