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