1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest; 28 29 import java.io.BufferedInputStream; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileNotFoundException; 33 import java.io.InputStream; 34 import java.io.IOException; 35 import java.lang.ref.WeakReference; 36 import java.lang.reflect.InvocationTargetException; 37 import java.net.MalformedURLException; 38 import java.net.URL; 39 import java.net.URLClassLoader; 40 import java.util.HashMap; 41 import java.util.Iterator; 42 import java.util.Map; 43 import java.util.Properties; 44 import java.util.Vector; 45 import java.util.logging.Level; 46 import java.util.logging.Logger; 47 import java.util.logging.Handler; 48 import java.util.logging.LogRecord; 49 import com.sun.javatest.finder.BinaryTestFinder; 50 import com.sun.javatest.finder.HTMLTestFinder; 51 import com.sun.javatest.finder.TestFinderDecorator; 52 import com.sun.javatest.interview.LegacyParameters; 53 import com.sun.javatest.lib.KeywordScript; 54 import com.sun.javatest.logging.WorkDirLogHandler; 55 import com.sun.javatest.logging.ObservedFile; 56 import com.sun.javatest.services.ServiceManager; 57 import com.sun.javatest.services.ServiceReader; 58 import com.sun.javatest.services.PropertyServiceReader; 59 import com.sun.javatest.util.BackupPolicy; 60 import com.sun.javatest.util.I18NResourceBundle; 61 import com.sun.javatest.util.StringArray; 62 63 import java.lang.reflect.Method; 64 import java.lang.reflect.Modifier; 65 66 /** 67 * A class providing information about and access to the tests in a test suite. 68 * The primary methods to access and run the tests are 69 * <ul> 70 * <li>{@link TestSuite#createTestFinder createTestFinder } 71 * <li>{@link TestSuite#createTestFilter createTestFilter } 72 * <li>{@link TestSuite#createScript createScript } 73 * </ul> 74 */ 75 public class TestSuite 76 { 77 /** 78 * An exception used to report errors while using a TestSUite object. 79 */ 80 public static class Fault extends Exception 81 { 82 /** 83 * Create a Fault. 84 * @param i18n A resource bundle in which to find the detail message. 85 * @param s The key for the detail message. 86 */ 87 public Fault(I18NResourceBundle i18n, String s) { 88 super(i18n.getString(s)); 89 } 90 91 /** 92 * Create a Fault. 93 * @param i18n A resource bundle in which to find the detail message. 94 * @param s The key for the detail message. 95 * @param o An argument to be formatted with the detail message by 96 * {@link java.text.MessageFormat#format} 97 */ 98 public Fault(I18NResourceBundle i18n, String s, Object o) { 99 super(i18n.getString(s, o)); 100 } 101 102 /** 103 * Create a Fault. 104 * @param i18n A resource bundle in which to find the detail message. 105 * @param s The key for the detail message. 106 * @param o An array of arguments to be formatted with the detail message by 107 * {@link java.text.MessageFormat#format} 108 */ 109 public Fault(I18NResourceBundle i18n, String s, Object[] o) { 110 super(i18n.getString(s, o)); 111 } 112 } 113 114 /** 115 * An exception that is used to report that a given file is not a test suite. 116 */ 117 public static class NotTestSuiteFault extends Fault 118 { 119 /** 120 * Create a Fault. 121 * @param i18n A resource bundle in which to find the detail message. 122 * @param s The key for the detail message. 123 * @param f The file in question, to be formatted with the detail message by 124 * {@link java.text.MessageFormat#format} 125 */ 126 public NotTestSuiteFault(I18NResourceBundle i18n, String s, File f) { 127 super(i18n, s, f.getPath()); 128 } 129 } 130 131 public static class DuplicateLogNameFault extends Fault 132 { 133 /** 134 * Create a Fault. 135 * @param i18n A resource bundle in which to find the detail message. 136 * @param key The internal name of the log. 137 * {@link java.text.MessageFormat#format} 138 */ 139 public DuplicateLogNameFault(I18NResourceBundle i18n, String s, String key) { 140 super(i18n, s, key); 141 } 142 } 143 144 public static class NoSuchLogFault extends Fault 145 { 146 /** 147 * Create a Fault. 148 * @param i18n A resource bundle in which to find the detail message. 149 * @param key The internal name of the log. 150 * {@link java.text.MessageFormat#format} 151 */ 152 public NoSuchLogFault(I18NResourceBundle i18n, String s, String key) { 153 super(i18n, s, key); 154 } 155 } 156 157 /** 158 * Check if a file is the root of a valid test suite. A valid test suite is identified 159 * either by the root directory of the test suite, or by the file testsuite.html within 160 * that directory. The directory must contain either a test suite properties file 161 * (testsuite.jtt) or, for backwards compatibility, a file named testsuite.html. 162 * @param root The file to be checked. 163 * @return true if and only if <em>root</em> is the root of a valid test suite. 164 */ 165 public static boolean isTestSuite(File root) { 166 //System.err.println("TestSuite.isTestSuite: " + root); 167 File dir; 168 if (root.isDirectory()) 169 dir = root; 170 else { 171 if (root.getName().equalsIgnoreCase(TESTSUITE_HTML)) 172 dir = root.getParentFile(); 173 else 174 return false; 175 } 176 File jtt = new File(dir, TESTSUITE_JTT); 177 File parentDir = dir.getParentFile(); 178 File parent_jtt = (parentDir == null ? null : new File(parentDir, TESTSUITE_JTT)); 179 File html = new File(dir, TESTSUITE_HTML); 180 return (isReadableFile(jtt) 181 || isReadableFile(html) && (parent_jtt == null || !parent_jtt.exists())); 182 } 183 184 /** 185 * Open a test suite. 186 * @param root A file identifying the root of the test suite. 187 * @return A TestSuite object for the test suite in question. The actual type of the result 188 * will depend on the test suite properties found in the root directory of the test suite. 189 * @throws FileNotFoundException if <em>root</em> does not exist. 190 * @throws TestSuite.NotTestSuiteFault if <em>root</em> does not identify a valid test suite. 191 * @throws TestSuite.Fault if any other problems occur while trying to open the test suite. 192 * @see #isTestSuite 193 */ 194 public static TestSuite open(File root) throws FileNotFoundException, Fault, NotTestSuiteFault { 195 if (!root.exists()) 196 throw new FileNotFoundException(root.getPath()); 197 198 File canonRoot; 199 try { 200 canonRoot = root.getCanonicalFile(); 201 } 202 catch (IOException e) { 203 throw new Fault(i18n, "ts.cantCanonicalize", 204 new Object[] {root.getPath(), e.toString()}); 205 } 206 207 File canonRootDir; 208 if (canonRoot.isDirectory()) 209 canonRootDir = canonRoot; 210 else { 211 if (canonRoot.getName().equalsIgnoreCase(TESTSUITE_HTML)) 212 canonRootDir = canonRoot.getParentFile(); 213 else 214 throw new NotTestSuiteFault(i18n, "ts.notTestSuiteFile", canonRoot); 215 } 216 217 File f = new File(canonRootDir, TESTSUITE_JTT); 218 if (isReadableFile(f)) { 219 try { 220 Properties p = new Properties(); 221 InputStream in = new BufferedInputStream(new FileInputStream(f)); 222 p.load(in); 223 in.close(); 224 return open(canonRoot, p); 225 } 226 catch (IOException e) { 227 throw new Fault(i18n, "ts.cantReadTestSuiteFile", e.toString()); 228 } 229 } 230 else { 231 // check for old style test suite 232 File ts_html = new File(canonRootDir, TESTSUITE_HTML); 233 File parentDir = canonRootDir.getParentFile(); 234 File parent_jtt = (parentDir == null ? null : new File(parentDir, TESTSUITE_JTT)); 235 if (isReadableFile(ts_html) && (parent_jtt == null || !parent_jtt.exists())) 236 return open(canonRoot, new HashMap()); 237 else 238 throw new NotTestSuiteFault(i18n, "ts.notTestSuiteFile", canonRoot); 239 } 240 } 241 242 /** 243 * Open a test suite. 244 * @param root A file identifying the root of the test suite. 245 * @param tsInfo Test Suite properties read from the test suite properties file. 246 * @return A TestSuite object for the test suite in question. 247 * @throws TestSuite.Fault if any problems occur while opening the test suite 248 */ 249 private static TestSuite open(File root, Map tsInfo) throws Fault { 250 synchronized (dirMap) { 251 TestSuite ts; 252 253 // if this test suite has already been opened, return that 254 WeakReference<TestSuite> ref = dirMap.get(root); 255 if (ref != null) { 256 ts = ref.get(); 257 if (ts != null) { 258 return ts; 259 } 260 } 261 262 // otherwise, open it for real 263 ts = open0(root, tsInfo); 264 265 // save reference in case opened again 266 dirMap.put(root, new WeakReference<>(ts)); 267 return ts; 268 } 269 } 270 271 private static TestSuite open0(File root, Map tsInfo) throws Fault { 272 String[] classPath = StringArray.split((String) (tsInfo.get("classpath"))); 273 274 ClassLoader cl; 275 if (classPath.length == 0) 276 cl = null; 277 else { 278 try { 279 File rootDir = (root.isDirectory() ? root : root.getParentFile()); 280 URL[] p = new URL[classPath.length]; 281 for (int i = 0; i < classPath.length; i++) { 282 String cpi = classPath[i]; 283 if (cpi.toLowerCase().startsWith("http:")) 284 p[i] = new URL(cpi); 285 else { 286 File f = new File(cpi); 287 if (!f.isAbsolute()) 288 f = new File(rootDir, cpi); 289 p[i] = f.toURI().toURL(); 290 } 291 } 292 cl = new URLClassLoader(p, TestSuite.class.getClassLoader()); 293 } 294 catch (MalformedURLException e) { 295 throw new Fault(i18n, "ts.badClassPath", 296 new Object[] {root, e.getMessage()}); 297 } 298 } 299 300 String[] tsClassAndArgs = StringArray.split((String) (tsInfo.get("testsuite"))); 301 302 TestSuite testSuite; 303 if (tsClassAndArgs.length == 0) 304 testSuite = new TestSuite(root, tsInfo, cl); 305 else { 306 String className = tsClassAndArgs[0]; 307 308 try { 309 Class c = loadClass(className, cl); 310 Class[] tsArgTypes = {File.class, Map.class, ClassLoader.class}; 311 Object[] tsArgs = {root, tsInfo, cl}; 312 testSuite = (TestSuite)(newInstance(c, tsArgTypes, tsArgs)); 313 } 314 catch (ClassCastException e) { 315 throw new Fault(i18n, "ts.notASubtype", 316 new Object[] {className, "testsuite", TestSuite.class.getName()}); 317 } 318 catch (UnsupportedClassVersionError uce){ 319 throw new Fault(i18n, "ts.compiledRecentVersion", 320 new Object[] {System.getProperty("java.version"), root.getPath()}); 321 } 322 323 String[] args = new String[tsClassAndArgs.length - 1]; 324 System.arraycopy(tsClassAndArgs, 1, args, 0, args.length); 325 testSuite.init(args); 326 } 327 328 // initialize test finder 329 testSuite.setTestFinder(new TestFinderDecorator(testSuite.createTestFinder())); 330 331 return testSuite; 332 } 333 334 /** 335 * Disposed of the shared TestSuite object for this test suite. Use 336 * the value from <code>TestSuite.getRoot()</code> as the value for 337 * canonRoot. Using this is only desired when disposal of the shared 338 * TestSuite object is not desired - traditionally, it is not disposed 339 * and is reused if the test suite is reopened. 340 * @param canonRoot Canonical root of the test suite. 341 * @see TestSuite#getRoot 342 * @return The object which is about to be discarded. Null if it was not 343 * not cached here. 344 */ 345 /* 346 public static TestSuite close(File canonRoot) { 347 WeakReference ref = (WeakReference)(dirMap.remove(canonRoot)); 348 if (ref != null) { 349 TestSuite ts = (TestSuite)(ref.get()); 350 if (ts != null) { 351 return ts; 352 } 353 } 354 355 return null; 356 } 357 */ 358 359 /** 360 * Create a TestSuite object. 361 * @param root The root file for this test suite. 362 * @param tsInfo Test suite properties, typically read from the test suite properties file 363 * in the root directory of the test suite. 364 * @param cl A class loader to be used to load additional classes as required, 365 * typically using a class path defined in the test suite properties file. 366 * @throws TestSuite.Fault if a problem occurs while creating this test suite. 367 */ 368 public TestSuite(File root, Map tsInfo, ClassLoader cl) throws Fault { 369 this.root = root; 370 this.tsInfo = tsInfo; 371 this.loader = cl; 372 373 String kw = (tsInfo == null ? null : (String) (tsInfo.get("keywords"))); 374 keywords = (kw == null ? null : StringArray.split(kw)); 375 } 376 377 378 /** 379 * Create a TestSuite object, with no additional test suite properties and no 380 * class loader. 381 * @param root The root file for this test suite. 382 */ 383 public TestSuite(File root) { 384 this.root = root; 385 } 386 387 /** 388 * Initialize this test suite, with args typically read from a .jtt file. 389 * The default implementation does not recognize any arguments and always 390 * throws an exception. 391 * @param args an array of strings to initialize this test suite object 392 * @throws TestSuite.Fault if there are any problems initializing the 393 * test suite from the specified arguments. 394 */ 395 protected void init(String[] args) throws Fault { 396 if (args.length > 0) 397 throw new Fault(i18n, "ts.badArgs", args[0]); 398 // should be a decodeArgs loop 399 } 400 401 /** 402 * Get the path for the root file of this test suite. 403 * @return the path for the root file of this test suite. 404 */ 405 public String getPath() { 406 return root.getPath(); 407 } 408 409 /** 410 * Get the root file of this test suite. 411 * @return the root file of this test suite. 412 */ 413 public File getRoot() { 414 return root; 415 } 416 417 /** 418 * Get the root directory of this test suite. If the root file is itself a directory, 419 * the result will be that directory; otherwise, the result will be the parent directory 420 * of the root file. 421 * @return the root directory of this test suite. 422 */ 423 public File getRootDir() { 424 return (root.isDirectory() ? root : new File(root.getParent())); 425 } 426 427 /** 428 * Get the directory in the test suite that contains the tests. 429 * By default, the following are checked: 430 * <ol> 431 * <li>The <code>tests</code> property in the test suite properties file. 432 * If this entry is found, it must either identify an absolute filename, or 433 * a directory relative to the test suite root directory, using '/' to 434 * separate the components of the path. 435 * <li>If the file <em>root</em><code>/tests/testsuite.html</code> exists, 436 * the result is the directory <em>root</em><code>/tests</code>. This is 437 * for compatibility with standard TCK layout. 438 * <li>Otherwise, the result is the root directory of the test suite. 439 * </ol> 440 * @return the directory that contains the tests 441 */ 442 public File getTestsDir() { 443 String t = (String) (tsInfo == null ? null : tsInfo.get("tests")); 444 if (t == null || t.length() == 0) { 445 File rootDir = getRootDir(); 446 File testsDir = new File(rootDir, "tests"); 447 if (testsDir.isDirectory()) { 448 // if the tests directory exists, and there is no overriding 449 // testsuite.jtt entry, assume the tests dir is "tests/". 450 return testsDir; 451 } 452 // default 453 return rootDir; 454 } 455 else { 456 File f = new File(t); 457 if (f.isAbsolute()) 458 return f; 459 else 460 return new File(getRootDir(), t.replace('/', File.separatorChar)); 461 } 462 } 463 464 /** 465 * A notification method that is called when a test suite run is starting. 466 * The method may be used to do any test suite specific initialization. 467 * If overriding this method, be sure to call the superclass' method. It is 468 * fairly typical to register as a harness observer inside this method. Note 469 * that if an exception occurs during this method, it will be caught by the 470 * harness and reported as a Harness.Observer.error(). It is recommended 471 * that any implementations of this method register as an observer immediately 472 * so that they can catch this error and do any cleanup to abort the 473 * test suite startup sequence (check if services were started and close them 474 * down, etc). 475 * @param harness The harness that will be used to run the tests. 476 * @throws TestSuite.Fault if an error occurred while doing test suite-specific 477 * initialization that should cause the test run to be aborted. 478 */ 479 public void starting(Harness harness) throws Fault { 480 if (getServiceManager() != null) { 481 serviceManager.setHarness(harness); 482 } 483 } 484 485 /** 486 * Create a test suite specific filter to be used to filter the tests 487 * to be selected for a test run. 488 * The method should return null if no test suite specific filtering is required. 489 * The default is to return null. 490 * @param filterEnv Configuration data that may be used by the filter. 491 * @return a test suite filter, or null if no test suite specific filter is 492 * required for this test suite. 493 */ 494 public TestFilter createTestFilter(TestEnvironment filterEnv) { 495 return null; 496 } 497 498 /** 499 * Get a shared test finder to read the tests in this test suite. 500 * @return a test finder to read the tests in this test suite 501 * @see #createTestFinder 502 * @see #setTestFinder 503 */ 504 public TestFinder getTestFinder() { 505 return finder; 506 } 507 508 /** 509 * Set the shared test finder used to read the tests in this test suite. 510 * Only one test finder may be set; attempts to change the test finder will 511 * cause IllegalStateException to be thrown. 512 * This method is normally called by TestSuite.open to initialize the 513 * finder to the result of calling createTestFinder. 514 * @param tf the test finder to be used 515 * @throws IllegalStateException if the test finder has previously 516 * been set to a different value 517 * @see #getTestFinder 518 */ 519 protected void setTestFinder(TestFinder tf) { 520 if (tf == null) 521 throw new NullPointerException(); 522 523 if (finder != null && finder != tf) 524 throw new IllegalStateException(); 525 526 finder = tf; 527 } 528 529 /** 530 * Create a test finder to be used to access the tests in this test suite. 531 * The default implementation looks for a <code>finder</code> entry in the 532 * test suite properties file, which should identify the class to be used 533 * and any arguments it may require. The class will be loaded via the class 534 * loader specified when the test suite was opened, if one was given; 535 * otherwise, the system class loader will be used. 536 * 537 * The default implementation attempts to use a file <tt>testsuite.jtd</tt> 538 * in the tests directory. If found, a BinaryTestFinder will be created 539 * using this file. If it is not found, then it searches for a property 540 * named <tt>finder</tt> in the test suite properties and will attempt to 541 * instantiate that. If no entry is found or it is blank, an 542 * HTMLTestFinder is used, using whatever a basic settings HTMLTestFinder 543 * initializes to. 544 * @return a test finder to be used to read the tests in the test suite 545 * @throws TestSuite.Fault if there is a problem creating the test finder 546 * @see #getTestFinder 547 * @see #setTestFinder 548 * @see #getTestsDir 549 */ 550 protected TestFinder createTestFinder() throws Fault { 551 File testsDir = getTestsDir(); 552 553 // no BTF file; look for a finder=class args... entry 554 String[] finderCmd = StringArray.split((String) (tsInfo.get("finder"))); 555 String finderClassName; 556 String[] finderArgs = new String[0]; 557 558 if (finderCmd == null || finderCmd.length == 0) { 559 //finderCmd = new String[] {HTMLTestFinder.class.getName()}; 560 finderCmd = null; // ensure null for later use 561 finderClassName = HTMLTestFinder.class.getName(); 562 } 563 else { 564 finderClassName = finderCmd[0]; 565 566 if (finderCmd.length > 1) { 567 finderArgs = new String[finderCmd.length - 1]; 568 System.arraycopy(finderCmd, 1, finderArgs, 0, finderArgs.length); 569 } 570 else { 571 // finderArgs should remain empty array 572 } 573 } 574 575 // first, try looking for testsuite.jtd 576 String jtd = (String) (tsInfo.get("testsuite.jtd")); 577 File jtdFile = (jtd == null ? new File(testsDir, "testsuite.jtd") : new File(root, jtd)); 578 if (jtdFile.exists()) { 579 try { 580 // found a file for BinaryTestFinder 581 // only pass the finder class if it was not defaulted to HTMLTestFinder 582 return createBinaryTestFinder((finderCmd == null ? null : finderClassName), 583 finderArgs, testsDir, jtdFile); 584 } 585 catch (TestFinder.Fault e) { 586 // ignore, try to continue with normal finder 587 } 588 catch (Fault f) { 589 // ignore, try to continue with normal finder 590 } 591 } 592 593 try { 594 Class c = loadClass(finderClassName); 595 TestFinder tf = (TestFinder) (newInstance(c)); 596 // called old deprecated entry till we know no-one cares 597 //tf.init(finderArgs, testsRoot, null, null, tsInfo/*pass in env?*/); 598 // this likely kills ExpandTestFinder, finally 599 tf.init(finderArgs, testsDir, null, null, null/*pass in env?*/); 600 return tf; 601 } 602 catch (ClassCastException e) { 603 throw new Fault(i18n, "ts.notASubtype", 604 new Object[] {finderClassName, "finder", TestFinder.class.getName()}); 605 } 606 catch (TestFinder.Fault e) { 607 throw new Fault(i18n, "ts.errorInitFinder", 608 new Object[] {finderClassName, e.getMessage()}); 609 } 610 } 611 612 /** 613 * In the case where a JTD file is found, attempt to load a binary test finder. 614 * The default implementation attempts to use the finder property in the 615 * test suite properties if it is a BinaryTestFinder subclass. 616 * 617 * @param finderClassName Finder class name to attempt to use as a BTF. Null if 618 * the default BTF class should be used. 619 * @param finderArgs Arguments to finder given from the test suite property. 620 * @param testsDir Reference location to pass to finder. 621 * @param jtdFile Location of the JTD file to give to the BTF. 622 * @return The binary test finder which was created. 623 * @throws com.sun.javatest.TestSuite.Fault 624 * @throws com.sun.javatest.TestFinder.Fault 625 * @see com.sun.javatest.TestFinder 626 * @see com.sun.javatest.finder.BinaryTestFinder 627 */ 628 protected TestFinder createBinaryTestFinder(String finderClassName, 629 String finderArgs[], File testsDir, File jtdFile) throws Fault, TestFinder.Fault { 630 try { 631 TestFinder tf = null; 632 633 if (finderClassName != null) { 634 Class c = loadClass(finderClassName); 635 tf = (TestFinder) (newInstance(c)); 636 } 637 638 if (tf instanceof BinaryTestFinder) { 639 tf.init(finderArgs, testsDir, null, null, null); 640 return tf; 641 } 642 else { 643 return new BinaryTestFinder(testsDir, jtdFile); 644 } 645 } 646 catch (ClassCastException e) { 647 throw new Fault(i18n, "ts.notASubtype", 648 new Object[] {finderClassName, "finder", TestFinder.class.getName()}); 649 } 650 catch (TestFinder.Fault e) { 651 throw new Fault(i18n, "ts.errorInitFinder", 652 new Object[] {finderClassName, e.getMessage()}); 653 } 654 655 } 656 657 /** 658 * Create and initialize a TestRunner that can be used to run 659 * a series of tests. 660 * The default implementation returns a TestRunner that 661 * creates a number of test execution threads which each 662 * create and run a script for each test obtained from 663 * the test runners iterator. 664 * @return a TestRunner that can be used to run a series of tests 665 */ 666 public TestRunner createTestRunner() { 667 return new DefaultTestRunner(); 668 } 669 670 /** 671 * Create and initialize a Script that can be used to run a test. 672 * The default implementation looks for a <code>script</code> entry in the configuration 673 * data provided, and if not found, looks for a <code>script</code> entry in the 674 * test suite properties. The script entry should define the script class 675 * to use and any arguments it may require. The class will be loaded via the class 676 * loader specified when the test suite was opened, if one was given; 677 * otherwise, the system class loader will be used. Individual test suites will 678 * typically use a more direct means to create an appropriate script object. 679 * The parameters for this method are normally passed through to the script 680 * that is created. 681 * 682 * Note that the name of this method is "create", it is not recommended 683 * that the value returned ever be re-used or cached for subsequent requests 684 * to this method. 685 * @param td The test description for the test to be executed. 686 * @param exclTestCases Any test cases within the test that should not be executed. 687 * @param scriptEnv Configuration data to be given to the test as necessary. 688 * @param workDir A work directory in which to store the results of the test. 689 * @param backupPolicy A policy object used to control how to backup any files that 690 * might be overwritten. 691 * @return a script to be used to execute the given test 692 * @throws TestSuite.Fault if any errors occur while creating the script 693 */ 694 public Script createScript(TestDescription td, String[] exclTestCases, TestEnvironment scriptEnv, 695 WorkDirectory workDir, 696 BackupPolicy backupPolicy) throws Fault { 697 if (scriptClass == null) { 698 String[] script = envLookup(scriptEnv, "script"); 699 if (script.length == 0) 700 script = StringArray.split((String) tsInfo.get("script")); 701 if (script.length > 0) { 702 scriptClass = loadClass(script[0]); 703 if (!Script.class.isAssignableFrom(scriptClass)) { 704 throw new Fault(i18n, "ts.notASubtype", 705 new Object[] {script[0], "script", Script.class.getName()}); 706 } 707 scriptArgs = new String[script.length - 1]; 708 System.arraycopy(script, 1, scriptArgs, 0, scriptArgs.length); 709 } 710 else { 711 // for backwards compatibility, 712 // see if KeywordScript is a reasonable default 713 boolean keywordScriptOK = false; 714 for (Iterator i = scriptEnv.keys().iterator(); i.hasNext() && !keywordScriptOK; ) { 715 String key = (String)(i.next()); 716 keywordScriptOK = key.startsWith("script."); 717 } 718 if (keywordScriptOK) { 719 scriptClass = KeywordScript.class; 720 scriptArgs = new String[] { }; 721 } 722 else { 723 throw new Fault(i18n, "ts.noScript"); 724 } 725 } 726 } 727 728 Script s = (Script)(newInstance(scriptClass)); 729 s.initArgs(scriptArgs); 730 s.initTestDescription(td); 731 s.initExcludedTestCases(exclTestCases); 732 s.initTestEnvironment(scriptEnv); 733 s.initWorkDir(workDir); 734 s.initBackupPolicy(backupPolicy); 735 s.initClassLoader(loader); 736 return s; 737 } 738 739 /** 740 * Create a configuration interview that can be used to collection the configuration 741 * data for a test run. 742 * <p>The default implementation returns a {@link LegacyParameters default} 743 * interview suitable for use with test suites built with earlier versions 744 * of the JT Harness: it provides questions equivalent to the fields in 745 * the GUI Parameter Editor or command-line -params option. As such, much of the 746 * necessary configuration data is provided indirectly via environment (.jte) files 747 * which must be created and updated separately. 748 * <p>Individual test suites should provide their own interview, with questions 749 * customized to the configuration data they require. 750 * 751 * Note that the name of this method is "create", the harness may instantiate 752 * multiple copies for temporary use, resetting data or transferring data. 753 * Do not override this method with an implementation which caches the 754 * return value. 755 * @return A configuration interview to collect the configuration data for a test run. 756 * @throws TestSuite.Fault if a problem occurs while creating the interview 757 */ 758 public InterviewParameters createInterview() 759 throws Fault 760 { 761 String[] classNameAndArgs = StringArray.split((String) (tsInfo.get("interview"))); 762 if (classNameAndArgs == null || classNameAndArgs.length == 0) { 763 try { 764 return new LegacyParameters(this); 765 } 766 catch (InterviewParameters.Fault e) { 767 throw new Fault(i18n, "ts.errorInitDefaultInterview", 768 e.getMessage()); 769 } 770 } 771 772 773 String className = classNameAndArgs[0]; 774 String[] args = new String[classNameAndArgs.length - 1]; 775 System.arraycopy(classNameAndArgs, 1, args, 0, args.length); 776 777 try { 778 Class c = loadClass(className); 779 InterviewParameters p = (InterviewParameters) (newInstance(c)); 780 p.init(args); 781 p.setTestSuite(this); 782 return p; 783 } 784 catch (ClassCastException e) { 785 throw new Fault(i18n, "ts.notASubtype", 786 new Object[] {className, "interview", InterviewParameters.class.getName()}); 787 } 788 catch (InterviewParameters.Fault e) { 789 //e.printStackTrace(); 790 throw new Fault(i18n, "ts.errorInitInterview", 791 new Object[] {className, e.getMessage()}); 792 } 793 794 } 795 796 /** 797 * Create a configuration interview based on specified map of template values 798 * @return A configuration interview to collect the configuration data for a test run. 799 * @throws TestSuite.Fault if a problem occurs while creating the interview 800 */ 801 public InterviewParameters loadInterviewFromTemplate(Map<String, String> templateInfo, InterviewParameters newInterview) 802 throws Fault 803 { 804 newInterview.storeTemplateProperties(templateInfo); 805 newInterview.propagateTemplateForAll(); 806 return newInterview; 807 } 808 809 /** 810 * Create a configuration interview based on specified template file 811 * @return A configuration interview to collect the configuration data for a test run. 812 * null if specified file is not template 813 * @throws TestSuite.Fault if a problem occurs while creating the interview 814 * IOException if a problem occurs while reading a template file 815 */ 816 public InterviewParameters loadInterviewFromTemplate(File template, 817 InterviewParameters ip) 818 throws Fault, IOException 819 { 820 try (InputStream in = new BufferedInputStream(new FileInputStream(template))) { 821 Map<String, String> stringProps = com.sun.javatest.util.Properties.load(in); 822 String tm = stringProps.get(InterviewParameters.IS_TEMPLATE); 823 if (InterviewParameters.TRUE.equals(tm)) { 824 stringProps.put(InterviewParameters.TEMPLATE_PATH, 825 template.getAbsolutePath()); 826 ip.setTemplatePath(template.getAbsolutePath()); 827 return loadInterviewFromTemplate(stringProps, ip); 828 } else { 829 // XXX should probably return ip 830 // or throw Fault 831 return null; 832 } 833 } 834 } 835 836 837 /** 838 * Get a string containing a unique ID identifying this test suite, 839 * or null if not available. The default is taken from the "id" entry 840 * in the .jtt file. 841 * @return a unique ID identifying the test suite, or null if not specified. 842 * @see #getName 843 */ 844 public String getID() { 845 return (tsInfo == null ? null : (String) (tsInfo.get("id"))); 846 } 847 848 /** 849 * Get a string identifying this test suite, or null if not available. 850 * The default is taken from the "name" entry in the .jtt file. 851 * This string is for presentation to the user, and may be localized 852 * if appropriate. 853 * @return a string identifying the test suite, or null if not specified. 854 * @see #getID 855 */ 856 public String getName() { 857 return (tsInfo == null ? null : (String) (tsInfo.get("name"))); 858 } 859 860 /** 861 * Get the estimated number of tests in the test suite. 862 * The default is to use the value of the "testCount" property from the 863 * testsuite.jtt file. 864 * 865 * @return The estimated number of tests, or -1 if this number is not available. 866 */ 867 public int getEstimatedTestCount() { 868 try { 869 if (tsInfo != null) { 870 String s = (String) (tsInfo.get("testCount")); 871 if (s != null) 872 return Integer.parseInt(s); 873 } 874 } 875 catch (NumberFormatException e) { 876 // ignore 877 } 878 return -1; // unknown 879 } 880 881 /** 882 * Get the file name of the initial exclude list associated with the test suite. 883 * The default is to use the value of the "initial.jtx" property from the 884 * testsuite.jtt file. If the value is a relative filename, it will be made absolute 885 * by evaluating it relative to the test suite root directory. 886 * @return the name of the default exclude list, or null if none specified. 887 */ 888 public File getInitialExcludeList() { 889 String s = (tsInfo == null ? null : (String) (tsInfo.get("initial.jtx"))); 890 if (s == null) 891 return null; 892 893 File f = new File(s.replace('/', File.separatorChar)); 894 if (!f.isAbsolute()) 895 f = new File(getRootDir(), f.getPath()); 896 return f; 897 } 898 899 /** 900 * Check if the test suite has an initial exclude list. 901 * The default is to use getInitialExcludeList, and if that returns 902 * a non-null result, check whether that file exists or not. 903 * @return true if the test suite has an initial exclude list, 904 * and false otherwise 905 */ 906 public boolean hasInitialExcludeList() { 907 File f = getInitialExcludeList(); 908 return (f == null ? false : f.exists()); 909 } 910 911 /** 912 * Get the URL for the latest exclude list associated with the test suite. 913 * The default is to use the value of the "latest.jtx" property from the 914 * testsuite.jtt file., which (if present) must be a fully qualified URL 915 * identifying the latest exclude list for this test suite. 916 * @return the name of the latest exclude list, or null if none specified. 917 */ 918 public URL getLatestExcludeList() { 919 try { 920 String s = (tsInfo == null ? null : (String) (tsInfo.get("latest.jtx"))); 921 return (s == null ? null : new URL(s)); 922 } 923 catch (MalformedURLException e) { 924 // ignore 925 return null; 926 } 927 } 928 929 /** 930 * Check if the test suite has a latest exclude list. 931 * The default is to use getLatestExcludeList, and to 932 * check whether that return a non-null result. The URL is not 933 * itself checked for validity. 934 * @return true if the test suite has a latest exclude list, 935 * and false otherwise 936 */ 937 public boolean hasLatestExcludeList() { 938 URL u = getLatestExcludeList(); 939 return (u != null); 940 } 941 942 /** 943 * Get the names of any helpsets containing related documents for this 944 * test suite. The names should identify JavaHelp helpset files, as 945 * used by javax.help.HelpSet.findHelpSet(ClassLoader, String). 946 * Thus the names should identify resources of helpsets on the classpath. 947 * This means you will typically need to put the directory or jar file 948 * containing the help set on the classpath as well. 949 * By default, the names will be looked up under the name "additionalDocs" 950 * in the testsuite.jtt file. 951 * @return an array of names identifying helpsets that contain related 952 * documents for this testsuite. The result may be null if there are no 953 * such documents. 954 */ 955 public String[] getAdditionalDocNames() { 956 return (tsInfo == null 957 ? null 958 : StringArray.split((String) (tsInfo.get("additionalDocs")))); 959 } 960 961 /** 962 * Get the set of valid keywords for this test suite. 963 * By default, the keywords will be looked up under the name "keywords" 964 * in the testsuite.jtt file. 965 * @return the set of valid keywords for this test suite, or null 966 * if not known. 967 */ 968 public String[] getKeywords() { 969 return keywords; 970 } 971 972 /** 973 * Get a list of associated files for a specified test description. 974 * Normally, this will include the file containing the test description, 975 * and any source files used by the test. By default, the source files 976 * are determined from the test description's "source" entry. 977 * @see TestDescription#getSourceURLs() 978 * @param td The test description for which the associated files are required 979 * @return a list of associated files for this test description 980 */ 981 public URL[] getFilesForTest(TestDescription td) { 982 return td.getSourceURLs(); 983 } 984 985 /** 986 * This method should be overridden in subclasses 987 * @param path String, which determines path to currently selected test's folder. 988 * This is root relative path. This shouldn't be null, for the 989 * root folder use "". 990 * @return array of files with documentation for test's folder, determined by path. 991 * null means there no documentation for this folder 992 */ 993 public URL[] getDocsForFolder(String path) { 994 return null; 995 } 996 997 /** 998 * This method should be overridden in subclasses 999 * @param td TestDescription for currently selected test case. This shouldn't be null. 1000 * @return array of files with documentation for test case, determined td. 1001 * null means there no documentation for this test case 1002 */ 1003 public URL[] getDocsForTest(TestDescription td) { 1004 return null; 1005 } 1006 1007 /** 1008 * Get A URL identifying a logo for this test suite, or null if none available. 1009 * @return a URL for a logo for the testsuite, or null if not available 1010 */ 1011 public URL getLogo() { 1012 try { 1013 String s = (tsInfo == null ? null : (String) (tsInfo.get("logo"))); 1014 return (s == null ? null : new URL(getRootDir().toURL(), s)); 1015 } 1016 catch (MalformedURLException e) { 1017 // ignore 1018 return null; 1019 } 1020 } 1021 1022 private static String[] envLookup(TestEnvironment env, String name) throws Fault { 1023 try { 1024 return env.lookup(name); 1025 } 1026 catch (TestEnvironment.Fault e) { 1027 throw new Fault(i18n, "ts.cantFindNameInEnv", 1028 new Object[] {name, e.getMessage()}); 1029 } 1030 } 1031 1032 /** 1033 * Create a new instance of a class, translating any exceptions that may arise 1034 * into Fault. 1035 * @param c the class to be instantiated 1036 * @return an instance of the specified class 1037 * @throws TestSuite.Fault if any errors arise while trying to instantiate 1038 * the class. 1039 */ 1040 protected static Object newInstance(Class c) throws Fault { 1041 try { 1042 return c.newInstance(); 1043 } 1044 catch (InstantiationException e) { 1045 throw new Fault(i18n, "ts.cantInstantiate", 1046 new Object[] { c.getName(), e }); 1047 } 1048 catch (IllegalAccessException e) { 1049 throw new Fault(i18n, "ts.illegalAccess", 1050 new Object[] { c.getName(), e }); 1051 } 1052 } 1053 1054 1055 /** 1056 * Create a new instance of a class using a non-default constructor, 1057 * translating any exceptions that may arise into Fault. 1058 * @param c the class to be instantiated 1059 * @param argTypes the types of the argument to be passed to the constructor, 1060 * (thus implying the constructor to be used.) 1061 * @param args the arguments to be passed to the constructor 1062 * @return an instance of the specified class 1063 * @throws TestSuite.Fault if any errors arise while trying to instantiate 1064 * the class. 1065 */ 1066 protected static Object newInstance(Class<?> c, Class[] argTypes, Object[] args) 1067 throws Fault 1068 { 1069 try { 1070 return c.getConstructor(argTypes).newInstance(args); 1071 } 1072 catch (IllegalAccessException e) { 1073 throw new Fault(i18n, "ts.illegalAccess", 1074 new Object[] { c.getName(), e }); 1075 } 1076 catch (InstantiationException e) { 1077 throw new Fault(i18n, "ts.cantInstantiate", 1078 new Object[] { c.getName(), e }); 1079 } 1080 catch (InvocationTargetException e) { 1081 Throwable te = e.getTargetException(); 1082 if (te instanceof Fault) 1083 throw (Fault) te; 1084 else 1085 throw new Fault(i18n, "ts.cantInit", new Object[] { c.getName(), te }); 1086 } 1087 catch (NoSuchMethodException e) { 1088 // don't recurse past the use of a single arg constructor 1089 if (argTypes.length > 1 && Boolean.getBoolean(FIND_LEGACY_CONSTRUCTOR)) { 1090 return newInstance(c, new Class[] {File.class}, new Object[] {args[0]}); 1091 } 1092 1093 throw new Fault(i18n, "ts.cantFindConstructor", 1094 new Object[] { c.getName(), e }); 1095 } 1096 } 1097 1098 /** 1099 * Load a class using the class loader provided when this test suite was created. 1100 * @param className the name of the class to be loaded 1101 * @return the class that was loaded 1102 * @throws TestSuite.Fault if there was a problem loading the specified class 1103 */ 1104 public Class loadClass(String className) throws Fault { 1105 return loadClass(className, loader); 1106 } 1107 1108 /** 1109 * Load a class using a specified loader, translating any errors that may arise 1110 * into Fault. 1111 * @param className the name of the class to be loaded 1112 * @param cl the class loader to use to load the specified class 1113 * @return the class that was loaded 1114 * @throws TestSuite.Fault if there was a problem loading the specified class 1115 */ 1116 protected static Class loadClass(String className, ClassLoader cl) throws Fault { 1117 try { 1118 if (cl == null) 1119 return Class.forName(className); 1120 else 1121 return cl.loadClass(className); 1122 } 1123 catch (ClassNotFoundException e) { 1124 throw new Fault(i18n, "ts.classNotFound", 1125 new Object[] { className, e }); 1126 } 1127 catch (IllegalArgumentException e) { 1128 throw new Fault(i18n, "ts.badClassName", 1129 new Object[] { className }); 1130 } 1131 } 1132 1133 /** 1134 * Get the class loader specified when this test suite object was created. 1135 * @return the class loader specified when this test suite object was created 1136 */ 1137 public ClassLoader getClassLoader() { 1138 return loader; 1139 } 1140 1141 public ServiceManager getServiceManager() { 1142 if (!needServices()) { 1143 return null; 1144 } 1145 1146 if (serviceManager == null) { 1147 serviceManager = new ServiceManager(this); 1148 } 1149 1150 return serviceManager; 1151 } 1152 1153 /** 1154 * Checks if serviceReader is active and file with service description does 1155 * exist. 1156 * @return true, if it's needed to start services, false otherwise. 1157 */ 1158 public boolean needServices() { 1159 ServiceReader sr = getServiceReader(); 1160 if (sr == null) { 1161 return false; 1162 } 1163 1164 /* 1165 * Since jt4.5 the ServiceReader has been extended with a new method. 1166 * To preserve ability to use new javatest with old test suites 1167 * the extra check is performed: check if the newly introduced method 1168 * is abstract or not. 1169 */ 1170 boolean isLegacy = false; 1171 try { 1172 Method m = sr.getClass().getMethod("getServiceDescriptorFileName", new Class[0]); 1173 if (Modifier.isAbstract(m.getModifiers())) { 1174 isLegacy = true; 1175 } 1176 } catch (NoSuchMethodException e) { 1177 isLegacy = true; 1178 } 1179 File descrFile = isLegacy ? 1180 new File(getRootDir(), File.separator + "lib" + File.separator + "services.xml") : 1181 new File(getRootDir(), sr.getServiceDescriptorFileName()); 1182 1183 return descrFile.exists(); 1184 } 1185 /** 1186 * Returns a test suite specific ServiceReader, used to read Service 1187 * definitions. 1188 * 1189 * @return ServiceReader instance. Default is PropertyServiceReader 1190 */ 1191 public ServiceReader getServiceReader() { 1192 if (serviceReader != null) { 1193 return serviceReader; 1194 } 1195 1196 String servInfo = (String)tsInfo.get("serviceReader"); 1197 if (servInfo != null) { 1198 String[] args = servInfo.split(" "); 1199 try { 1200 Class c = loadClass(args[0]); 1201 serviceReader = (ServiceReader) (newInstance(c)); 1202 if (args.length > 1) { 1203 // problem with java1.5, which has no Arrays.copyOfRange(); 1204 String[] copy = new String[args.length - 1]; 1205 for (int i = 1; i < args.length; i++) { 1206 copy[i-1] = args[i]; 1207 } 1208 1209 serviceReader.init(this, copy); 1210 } 1211 else { 1212 serviceReader.init(this, null); 1213 } 1214 } 1215 catch (TestSuite.Fault e) { 1216 } 1217 } 1218 else { 1219 serviceReader = new PropertyServiceReader(); 1220 serviceReader.init(this, null); 1221 } 1222 1223 return serviceReader; 1224 } 1225 1226 /** 1227 * Get a map containing the test suite data in the .jtt file. 1228 * @return a map containing the test suite data in the .jtt file 1229 */ 1230 protected Map getTestSuiteInfo() { 1231 return tsInfo; 1232 } 1233 1234 /** 1235 * Get an entry from the data in the .jtt file. 1236 * @param name The name of the entry to get from the info in the .jtt file 1237 * @return the value of the specified entry, or null if not found. 1238 */ 1239 public String getTestSuiteInfo(String name) { 1240 if (tsInfo == null) 1241 return null; 1242 else 1243 return (String) (tsInfo.get(name)); 1244 } 1245 1246 /** 1247 * Get the requested behavior for dealing with conflicts between 1248 * which tests are in the test suite vs those in the work directory. 1249 * @see #DELETE_NONTEST_RESULTS 1250 * @see #REFRESH_ON_RUN 1251 * @see #CLEAR_CHANGED_TEST 1252 */ 1253 public boolean getTestRefreshBehavior(int event) { 1254 switch (event) { 1255 case DELETE_NONTEST_RESULTS: 1256 return Boolean.valueOf(getTestSuiteInfo("deleteNonExistTests")).booleanValue(); 1257 case REFRESH_ON_RUN: 1258 return Boolean.valueOf( getTestSuiteInfo("refreshTestsOnRun") ).booleanValue(); 1259 case CLEAR_CHANGED_TEST: 1260 return Boolean.valueOf( getTestSuiteInfo("clearChangedTests")).booleanValue(); 1261 default: 1262 return false; 1263 } 1264 } 1265 1266 1267 /** 1268 * Returns notification logger associated with 1269 * given working directory or common logger if null was specified 1270 * @param wd - working directory or null 1271 */ 1272 public Logger getNotificationLog(WorkDirectory wd) { 1273 return notifLogger; 1274 } 1275 1276 public ObservedFile getObservedFile(WorkDirectory wd) { 1277 return getObservedFile(wd.getLogFileName()); 1278 } 1279 1280 1281 public ObservedFile getObservedFile(String path) { 1282 String cPath = new File(path).getAbsolutePath(); 1283 if (observedFiles.containsKey(cPath)) { 1284 return (ObservedFile) observedFiles.get(cPath); 1285 } 1286 return null; 1287 } 1288 1289 void setLogFilePath(WorkDirectory wd) { 1290 ObservedFile f = new ObservedFile(wd.getLogFileName()); 1291 if (f.length() != 0) { 1292 f.backup(); 1293 } 1294 // return to current 1295 f = new ObservedFile(wd.getLogFileName()); 1296 1297 if(observedFiles == null) { 1298 observedFiles = new HashMap<>(); 1299 } 1300 if (!observedFiles.containsKey(f.getAbsolutePath())) { 1301 observedFiles.put(f.getAbsolutePath(), f); 1302 } 1303 1304 } 1305 1306 1307 /** 1308 * Creates general purpose logger with given key and ResourceBundleName registered for given WorkDirectory. 1309 * @param wd WorkDirectory logger should be registered for; may be <code>null</code> if no WorkDirectory 1310 * currently available (the log will be registered for the first WD created for this TestSuite 1311 * @param b name of ResorceBundle used for this logger; may be <code>null</code> if not required 1312 * @param key key for this log 1313 * @return general purpose logger with given key registered for given WorkDirectory or TestSuite (if WD is null) 1314 * @throws TestSuite.DuplicateLogNameFault if log with this key has been registered in the system already 1315 * @see #getLog 1316 */ 1317 1318 public Logger createLog(WorkDirectory wd, String b, String key) throws DuplicateLogNameFault { 1319 1320 if (key == null || "".equals(key)) { 1321 throw new IllegalArgumentException("Log name can not be empty"); 1322 } 1323 1324 String logName = wd.getLogFileName(); 1325 1326 if (gpls == null) 1327 gpls = new Vector<GeneralPurposeLogger>(); 1328 1329 for (int i = 0; i < gpls.size(); i++) { 1330 GeneralPurposeLogger gpl = gpls.get(i); 1331 if (gpl.getName().equals(key) && gpl.getLogFileName().equals(logName)) 1332 throw new DuplicateLogNameFault(i18n, "ts.logger.duplicatelognamefault", key); 1333 } 1334 1335 GeneralPurposeLogger gpl = new GeneralPurposeLogger( key, wd, b, this); 1336 gpls.add(gpl); 1337 return gpl; 1338 } 1339 1340 /** 1341 * Returns general purpose logger with given key registered for given WorkDirectory. 1342 * The log should be created first. 1343 * @param wd WorkDirectory desired logger is registered for 1344 * @param key key for this log 1345 * @return general purpose logger with given key registered for given WorkDirectory 1346 * @throws TestSuite.NoSuchLogFault if desired log not registered in the system 1347 * @throws NullPointerException if <code>wd</code> is null 1348 * @see #createLog 1349 */ 1350 public Logger getLog(WorkDirectory wd, String key) throws NoSuchLogFault { 1351 if (gpls == null) 1352 throw new NoSuchLogFault(i18n, "ts.logger.nologscreated", key); 1353 1354 if (wd == null) 1355 throw new NullPointerException(i18n.getString("ts.logger.nullwd")); 1356 1357 String logFile = wd.getLogFileName(); 1358 1359 for (int i = 0; i < gpls.size(); i++) { 1360 GeneralPurposeLogger logger = gpls.get(i); 1361 if (logger.getLogFileName().equals(logFile) && logger.getName().equals(key)) 1362 return logger; 1363 } 1364 throw new NoSuchLogFault(i18n, "ts.logger.nosuchlogfault", key); 1365 } 1366 1367 /** 1368 * Cleans the log file in given WorkDirectory 1369 * @param wd WorkDirectory desired logger is registered for 1370 * @throws IOException if log file's content can't be erased 1371 */ 1372 public void eraseLog(WorkDirectory wd) throws IOException { 1373 if (wd == null) 1374 throw new NullPointerException(i18n.getString("ts.logger.nullwd")); 1375 1376 if (gpls != null) 1377 for (int i = 0; i < gpls.size(); i++) { 1378 GeneralPurposeLogger gpl = gpls.get(i); 1379 if (gpl.getLogFileName().equals(wd.getLogFileName())) { 1380 Handler[] h = gpl.getHandlers(); 1381 if (h[0] instanceof WorkDirLogHandler) { 1382 ((WorkDirLogHandler)h[0]).eraseLogFile(); 1383 return; 1384 } 1385 } 1386 } 1387 } 1388 1389 private static boolean isReadableFile(File f) { 1390 return (f.exists() && f.isFile() && f.canRead()); 1391 } 1392 1393 /** 1394 * Should tests which no longer exist in the test suite be 1395 * deleted from a work directory when it is opened? 1396 */ 1397 public static final int DELETE_NONTEST_RESULTS = 0; 1398 1399 /* 1400 * Should the content of the test suite be refreshed as the 1401 * tests run? So the test description should be updated from the 1402 * finder just before the test runs. 1403 */ 1404 public static final int REFRESH_ON_RUN = 1; 1405 1406 /** 1407 * Should a test be reset to not run if it is found that the 1408 * test has changed in the test suite (test description does 1409 * not match the one in the existing result). 1410 */ 1411 public static final int CLEAR_CHANGED_TEST = 2; 1412 1413 private static class NotificationLogger extends Logger { 1414 private NotificationLogger(String resourceBundleName) { 1415 super(notificationLogName, resourceBundleName); 1416 setLevel(Level.CONFIG); 1417 // needs to be reimplemented - this initializes Swing, which is not 1418 // allowed inside the core harness 1419 // should be implemented so that the GUI attaches to the logging system 1420 // on startup 1421 //addHandler(new ErrorDialogHandler()); 1422 } 1423 1424 public synchronized void log(LogRecord record) { 1425 record.setLoggerName(this.getName()); 1426 if (record.getThrown() != null) { 1427 record.setLevel(Level.INFO); 1428 } 1429 super.log(record); 1430 } 1431 1432 1433 // overwrite to make sure exception is handled 1434 public void throwing(String sourceClass, String sourceMethod, Throwable thrown) { 1435 LogRecord lr = new LogRecord(Level.INFO, "THROW"); 1436 lr.setSourceClassName(sourceClass); 1437 lr.setSourceMethodName(sourceMethod); 1438 lr.setThrown(thrown); 1439 log(lr); 1440 } 1441 1442 } 1443 1444 private static class GeneralPurposeLogger extends Logger { 1445 private GeneralPurposeLogger(String name, WorkDirectory wd, String resourceBundleName, TestSuite ts) { 1446 super(name, resourceBundleName); 1447 this.logFileName = wd.getLogFileName(); 1448 1449 if (wd != null) { 1450 if (!handlersMap.containsKey(wd.getLogFileName())) { 1451 WorkDirLogHandler wdlh = new WorkDirLogHandler(ts.getObservedFile(wd)); 1452 handlersMap.put(wd.getLogFileName(), wdlh); 1453 } 1454 1455 addHandler(handlersMap.get(wd.getLogFileName())); 1456 } 1457 setLevel(Level.ALL); 1458 } 1459 1460 public void log(LogRecord record) { 1461 Handler targets[] = getHandlers(); 1462 if (targets != null) { 1463 for (int i = 0; i < targets.length; i++) { 1464 if (targets[i] instanceof WorkDirLogHandler) { 1465 ((WorkDirLogHandler)targets[i]).publish(record, getName()); 1466 } else { 1467 targets[i].publish(record); 1468 } 1469 } 1470 } 1471 } 1472 1473 private String getLogFileName() { 1474 return logFileName; 1475 } 1476 1477 private String logFileName; 1478 } 1479 1480 1481 private static final String TESTSUITE_HTML = "testsuite.html"; 1482 private static final String TESTSUITE_JTT = "testsuite.jtt"; 1483 private static final String FIND_LEGACY_CONSTRUCTOR = "com.sun.javatest.ts.findLegacyCtor"; 1484 1485 private File root; 1486 private Map tsInfo; 1487 private ClassLoader loader; 1488 private TestFinder finder; 1489 1490 // the following are used by the default impl of createScript 1491 private Class scriptClass; 1492 private String[] scriptArgs; 1493 1494 private String[] keywords; 1495 1496 private ServiceReader serviceReader; 1497 private ServiceManager serviceManager; 1498 1499 private static Map<File, WeakReference<TestSuite>> dirMap = new HashMap<>(2); 1500 1501 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestSuite.class); 1502 private static String notificationLogName = i18n.getString("notification.logname"); 1503 1504 static Map<String, WorkDirLogHandler> handlersMap = new HashMap<>(); 1505 private static Vector<GeneralPurposeLogger> gpls; 1506 private static Map<String, File> observedFiles; 1507 1508 private final NotificationLogger notifLogger = new NotificationLogger(null); 1509 1510 public static final String TM_CONTEXT_NAME = "tmcontext"; 1511 1512 }