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