1 /* 2 * Copyright (c) 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.test.lib.apps; 25 26 import java.io.BufferedReader; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.InputStreamReader; 30 import java.nio.file.Files; 31 import java.nio.file.NoSuchFileException; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.nio.file.attribute.BasicFileAttributes; 35 import java.nio.file.attribute.FileTime; 36 import java.util.ArrayList; 37 import java.util.Date; 38 import java.util.List; 39 import java.util.Map; 40 import 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 */ 67 public class LingeredApp { 68 69 private static final long spinDelay = 1000; 70 71 private final String lockFileName; 72 private long lockCreationTime; 73 private Process appProcess; 74 private final ArrayList<String> storedAppOutput; 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 /** 115 * 116 * @return name of lock file 117 */ 118 public String getLockFileName() { 119 return this.lockFileName; 120 } 121 122 /** 123 * 124 * @return name of testapp 125 */ 126 public String getAppName() { 127 return this.getClass().getName(); 128 } 129 130 /** 131 * 132 * @return pid of java process running testapp 133 */ 134 public long getPid() { 135 if (appProcess == null) { 136 throw new RuntimeException("Process is not alive"); 137 } 138 return appProcess.getPid(); 139 } 140 141 /** 142 * 143 * @return process object 144 */ 145 public Process getProcess() { 146 return appProcess; 147 } 148 149 /** 150 * 151 * @return application output as string array. Empty array if application produced no output 152 */ 153 public List<String> getAppOutput() { 154 if (appProcess.isAlive()) { 155 throw new RuntimeException("Process is still alive. Can't get its output."); 156 } 157 return storedAppOutput; 158 } 159 160 /* Make sure all part of the app use the same method to get dates, 161 as different methods could produce different results 162 */ 163 private static long epoch() { 164 return new Date().getTime(); 165 } 166 167 private static long lastModified(String fileName) throws IOException { 168 Path path = Paths.get(fileName); 169 BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); 170 return attr.lastModifiedTime().toMillis(); 171 } 172 173 private static void setLastModified(String fileName, long newTime) throws IOException { 174 Path path = Paths.get(fileName); 175 FileTime fileTime = FileTime.fromMillis(newTime); 176 Files.setLastModifiedTime(path, fileTime); 177 } 178 179 /** 180 * create lock 181 * 182 * @throws IOException 183 */ 184 public void createLock() throws IOException { 185 Path path = Paths.get(lockFileName); 186 // Files.deleteIfExists(path); 187 Files.createFile(path); 188 lockCreationTime = lastModified(lockFileName); 189 } 190 191 /** 192 * Delete lock 193 * 194 * @throws IOException 195 */ 196 public void deleteLock() throws IOException { 197 try { 198 Path path = Paths.get(lockFileName); 199 Files.delete(path); 200 } catch (NoSuchFileException ex) { 201 // Lock already deleted. Ignore error 202 } 203 } 204 205 public void waitAppTerminate() { 206 while (true) { 207 try { 208 appProcess.waitFor(); 209 break; 210 } catch (InterruptedException ex) { 211 // pass 212 } 213 } 214 } 215 216 /** 217 * The app touches the lock file when it's started 218 * wait while it happens. Caller have to delete lock on wait error. 219 * 220 * @param timeout 221 * @throws java.io.IOException 222 */ 223 public void waitAppReady(long timeout) throws IOException { 224 long here = epoch(); 225 while (true) { 226 long epoch = epoch(); 227 if (epoch - here > (timeout * 1000)) { 228 throw new IOException("App waiting timeout"); 229 } 230 231 // Live process should touch lock file every second 232 long lm = lastModified(lockFileName); 233 if (lm > lockCreationTime) { 234 break; 235 } 236 237 // Make sure process didn't already exit 238 if (!appProcess.isAlive()) { 239 throw new IOException("App exited unexpectedly with " + appProcess.exitValue()); 240 } 241 242 try { 243 Thread.sleep(spinDelay); 244 } catch (InterruptedException ex) { 245 // pass 246 } 247 } 248 } 249 250 /** 251 * Run the app 252 * 253 * @param vmArguments 254 * @throws IOException 255 */ 256 public void runApp(List<String> vmArguments) 257 throws IOException { 258 259 // We should always use testjava or throw an exception, 260 // so we can't use JDKToolFinder.getJDKTool("java"); 261 // that falls back to compile java on error 262 String jdkPath = System.getProperty("test.jdk"); 263 if (jdkPath == null) { 264 // we are not under jtreg, try env 265 Map<String, String> env = System.getenv(); 266 jdkPath = env.get("TESTJAVA"); 267 } 268 269 if (jdkPath == null) { 270 throw new RuntimeException("Can't determine jdk path neither test.jdk property no TESTJAVA env are set"); 271 } 272 273 String osname = System.getProperty("os.name"); 274 String javapath = jdkPath + ((osname.startsWith("window")) ? "/bin/java.exe" : "/bin/java"); 275 276 List<String> cmd = new ArrayList<String>(); 277 cmd.add(javapath); 278 279 280 if (vmArguments == null) { 281 // Propagate test.vm.options to LingeredApp, filter out possible empty options 282 String testVmOpts[] = System.getProperty("test.vm.opts","").split("\\s+"); 283 for (String s : testVmOpts) { 284 if (!s.equals("")) { 285 cmd.add(s); 286 } 287 } 288 } 289 else{ 290 // Lets user manage LingerApp options 291 cmd.addAll(vmArguments); 292 } 293 294 // Make sure we set correct classpath to run the app 295 cmd.add("-cp"); 296 String classpath = System.getProperty("test.class.path"); 297 cmd.add((classpath == null) ? "." : classpath); 298 299 cmd.add(this.getAppName()); 300 cmd.add(lockFileName); 301 302 // Reporting 303 StringBuilder cmdLine = new StringBuilder(); 304 for (String strCmd : cmd) { 305 cmdLine.append("'").append(strCmd).append("' "); 306 } 307 308 // A bit of verbosity 309 System.out.println("Command line: [" + cmdLine.toString() + "]"); 310 311 ProcessBuilder pb = new ProcessBuilder(cmd); 312 // we don't expect any error output but make sure we are not stuck on pipe 313 // pb.redirectErrorStream(false); 314 pb.redirectError(ProcessBuilder.Redirect.INHERIT); 315 316 appProcess = pb.start(); 317 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 * High level interface for test writers 325 */ 326 /** 327 * Factory method that creates SmartAppTest object with ready to use application 328 * lock name is autogenerated, wait timeout is hardcoded 329 * @param cmd - vm options, could be null to auto add testvm.options 330 * @return LingeredApp object 331 * @throws IOException 332 */ 333 public static LingeredApp startApp(List<String> cmd) throws IOException { 334 final String lockName = UUID.randomUUID().toString() + ".lck"; 335 final int waitTime = 10; 336 337 LingeredApp a = new LingeredApp(lockName); 338 a.createLock(); 339 try { 340 a.runApp(cmd); 341 a.waitAppReady(waitTime); 342 } catch (Exception ex) { 343 a.deleteLock(); 344 throw ex; 345 } 346 347 return a; 348 } 349 350 public static LingeredApp startApp() throws IOException { 351 return startApp(null); 352 } 353 354 /** 355 * Delete lock file that signal app to terminate, then 356 * waits until app is actually terminated. 357 * @throws IOException 358 */ 359 public void stopApp() throws IOException { 360 deleteLock(); 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 * LastModified time might not work correctly in some cases it might 370 * cause later failures 371 */ 372 373 public static boolean isLastModifiedWorking() { 374 boolean sane = true; 375 try { 376 long lm = lastModified("."); 377 if (lm == 0) { 378 System.err.println("SANITY Warning! The lastModifiedTime() doesn't work on this system, it returns 0"); 379 sane = false; 380 } 381 382 long now = epoch(); 383 if (lm > now) { 384 System.err.println("SANITY Warning! The Clock is wrong on this system lastModifiedTime() > getTime()"); 385 sane = false; 386 } 387 388 setLastModified(".", epoch()); 389 long lm1 = lastModified("."); 390 if (lm1 <= lm) { 391 System.err.println("SANITY Warning! The setLastModified doesn't work on this system"); 392 sane = false; 393 } 394 } 395 catch(IOException e) { 396 System.err.println("SANITY Warning! IOException during sanity check " + e); 397 sane = false; 398 } 399 400 return sane; 401 } 402 403 /** 404 * This part is the application it self 405 */ 406 public static void main(String args[]) { 407 408 if (args.length != 1) { 409 System.err.println("Lock file name is not specified"); 410 System.exit(7); 411 } 412 413 String theLockFileName = args[0]; 414 415 try { 416 Path path = Paths.get(theLockFileName); 417 418 while (Files.exists(path)) { 419 // Touch the lock to indicate our readiness 420 setLastModified(theLockFileName, epoch()); 421 Thread.sleep(spinDelay); 422 } 423 } catch (NoSuchFileException ex) { 424 // Lock deleted while we are setting last modified time. 425 // Ignore error and lets the app exits 426 } catch (Exception ex) { 427 System.err.println("LingeredApp ERROR: " + ex); 428 // Leave exit_code = 1 to Java launcher 429 System.exit(3); 430 } 431 432 System.exit(0); 433 } 434 }