1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2016, 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.File; 30 import java.io.IOException; 31 import java.text.Collator; 32 import java.util.*; 33 import java.util.concurrent.locks.ReentrantLock; 34 35 import com.sun.javatest.finder.TestFinderDecorator; 36 import com.sun.javatest.tool.Preferences; 37 import com.sun.javatest.util.Debug; 38 import com.sun.javatest.util.DynamicArray; 39 import com.sun.javatest.util.I18NResourceBundle; 40 import com.sun.javatest.util.StringArray; 41 import com.sun.javatest.httpd.RootRegistry; 42 43 /** 44 * Collected results from a test suite. 45 * The data is represented as TestResult objects, although the test itself 46 * may not have been run, but just "found" so far. TestResultTable is 47 * populated by using a TestFinder, and is subsequently updated as tests are 48 * run by the harness. 49 */ 50 51 // debug values: 52 // 1 - info messages, stack traces on 53 // 2 - search details 54 // 10 - more scan/insert/remove detail 55 // 11 - heavy scan/insert/remove detail 56 // 99 - everything 57 // NOTE: stack traces are on for all levels above 0 58 59 public class TestResultTable { 60 public void dispose() { 61 if (trCache != null) { 62 trCache.shutdown(); 63 } 64 } 65 66 /** 67 * Observer to monitor changes to a TestResultTable. 68 */ 69 public interface Observer { 70 /** 71 * The oldValue has been replaced by the newValue. 72 * 73 * @param oldValue Previous value being overwritten. 74 * @param newValue The new value stored in the TRT. 75 */ 76 void update(TestResult oldValue, TestResult newValue); 77 78 /** 79 * The given test was changed, but previously existed in this TRT. 80 * This is not a guarantee of change, but is the best possible hint. 81 * 82 * @param whichTR The test which was altered. 83 */ 84 void updated(TestResult whichTR); 85 86 /* 87 void stalled(String reason); 88 void ready(); 89 */ 90 } 91 92 /** 93 * Tree-aware observer of the TRT. 94 * 95 * @since 3.0 96 */ 97 public interface TreeObserver { 98 /** 99 * A node has been inserted into the tree. 100 * @param path The path to the node that was inserted. Does not include 101 * the actual object which is new. 102 * @param what The actual object that changed. So <code>path</code> plus 103 * this is the entire path. The type will either be TestResult or 104 * TreeNode. 105 * @param index The index in <code>path[path.length-1]</code> where the 106 * new node was placed. 107 */ 108 void nodeInserted(TreeNode[] path, Object what, int index); 109 110 /** 111 * A node has changed. 112 * In the case of a test changing, the old object is the test result 113 * being replaced. In the case of a branch changing, the old object is 114 * the same as the what object. 115 * 116 * @param path The path to the node that changed. Does not include 117 * the actual object which changed. 118 * @param what The actual object that changed. So <code>path</code> plus 119 * this is the entire path. The type will either be TestResult or 120 * TreeNode. 121 * @param index The index in <code>path[path.length-1]</code> that changed. 122 * @param old The old value at the changed location. 123 */ 124 void nodeChanged(TreeNode[] path, Object what, int index, Object old); 125 126 /** 127 * An item has been removed from the tree. 128 * 129 * @param path The path to the node that changed. Does not include 130 * the actual object which changed. 131 * @param what The actual object that was removed. So <code>path</code> plus 132 * this is the entire path. The type will either be TestResult or 133 * TreeNode. 134 * @param index The index in <code>path[path.length-1]</code> that was 135 * removed. 136 */ 137 void nodeRemoved(TreeNode[] path, Object what, int index); 138 } 139 140 /** 141 * Extension to TreeObserver to receive notifications related 142 * to events happened on tree nodes. 143 */ 144 public interface TreeEventObserver extends TreeObserver { 145 146 /** 147 * A refresh has been stared on the node. All children 148 * will be recursively refreshed but only one notification about the 149 * original node will be delivered. 150 * 151 * @param origin Node the refresh has been originated 152 * 153 */ 154 void startRefresh(TreeNode origin); 155 156 /** 157 * A refresh has been finished on the node. In spite of the all children 158 * will be recursively refreshed the only one notification about the 159 * original node will be delivered. 160 * 161 * @param origin Node the refresh has been originated 162 * 163 */ 164 void finishRefresh(TreeNode origin); 165 } 166 167 /** 168 * Observer interface to watch a single tree node. 169 * 170 * @since 3.0 171 */ 172 public interface TreeNodeObserver { 173 /** 174 * A TreeNode has been inserted into the given parent node. 175 * 176 * @param parent The node which acquired the new node. This is the same as 177 * the object that the observer attached to. 178 * @param newNode The node which was added. 179 * @param index The index at which the node was added. 180 */ 181 public void insertedBranch(TreeNode parent, TreeNode newNode, int index); 182 183 /** 184 * A TestResult has been inserted into the given parent node. 185 * 186 * @param parent The node which acquired the new test. This is the same as 187 * the object that the observer attached to. 188 * @param test The test which was added. 189 * @param index The index at which the test was added. 190 */ 191 public void insertedResult(TreeNode parent, TestResult test, int index); 192 193 /** 194 * A TestResult has been replaced in the given parent node. 195 * 196 * @param parent The node which acquired the new test. This is the same as 197 * the object that the observer attached to. 198 * @param oldTest The test which was replaced. 199 * @param newTest The test which took the old test's place. 200 * @param index The index at which activity occurred. 201 */ 202 public void replacedResult(TreeNode parent, TestResult oldTest, 203 TestResult newTest, int index); 204 205 /** 206 * A TreeNode has been removed from the given parent node. 207 * 208 * @param parent The node which acquired the new test. This is the same as 209 * the object that the observer attached to. 210 * @param index The index at which the removed node resided in the parent. 211 */ 212 public void removedBranch(TreeNode parent, int index); 213 214 /** 215 * A TestResult has been removed from the given parent node. 216 * 217 * @param parent The node which acquired the new test. This is the same as 218 * the object that the observer attached to. 219 * @param test The test which was removed. 220 * @param index The index at which the removed test resided in the parent. 221 */ 222 public void removedResult(TreeNode parent, TestResult test, int index); 223 224 /** 225 * The statistics counters of the node have changed. 226 * 227 * @param node The node whose counters were invalidated. 228 * This is the same as the node which this observer attached to. 229 */ 230 public void countersInvalidated(TreeNode node); 231 } 232 233 234 /** 235 * Exception class to communicate any special conditions which may occur 236 * while using this class. 237 */ 238 public static class Fault extends Exception 239 { 240 Fault(I18NResourceBundle i18n, String s) { 241 super(i18n.getString(s)); 242 } 243 244 Fault(I18NResourceBundle i18n, String s, Object o) { 245 super(i18n.getString(s, o)); 246 } 247 248 Fault(I18NResourceBundle i18n, String s, Object[] o) { 249 super(i18n.getString(s, o)); 250 } 251 } 252 253 /** 254 * Create a table ready to be occupied. 255 */ 256 public TestResultTable() { 257 statusTables = new Map[Status.NUM_STATES]; 258 for (int i = 0; i < statusTables.length; i++) 259 statusTables[i] = new Hashtable<>(); 260 261 root = new TRT_TreeNode(this, null); 262 263 instanceId++; 264 if (com.sun.javatest.httpd.HttpdServer.isActive()) { 265 String url = "/trt/" + instanceId; 266 httpHandle = new TRT_HttpHandler(this, url, instanceId); 267 RootRegistry.getInstance().addHandler(url, "Test Result Table", 268 httpHandle); 269 RootRegistry.associateObject(this, httpHandle); 270 } 271 272 rtc = new RequestsToCache(); 273 } 274 275 276 /** 277 * Create a table for the tests in a work directory and its 278 * associated test suite and test finder. 279 * 280 * @param wd The work directory to associate with this table. 281 * @since 3.0 282 */ 283 public TestResultTable(WorkDirectory wd) { 284 this(); 285 setWorkDirectory(wd); 286 } 287 288 /** 289 * Create a table for the tests in a work directory and its 290 * associated test suite, overriding the test suite's default test finder. 291 * 292 * @param wd The work directory to associate with this table. 293 * @param tf The finder to use. Do not use this constructor unless 294 * necessary. 295 * @see #TestResultTable(WorkDirectory) 296 * @since 3.0 297 */ 298 public TestResultTable(WorkDirectory wd, TestFinder tf) { 299 this(); 300 setWorkDirectory(wd, tf); 301 } 302 303 /** 304 * Set the test finder for this object. 305 * It is illegal to call this method once the test finder for a instance 306 * has been set. Rather than use this method, it is probably better to 307 * supply the finder at construction time. 308 * 309 * @param tf The finder to use. 310 * @throws IllegalStateException Thrown if the finder for this object is already set. 311 * @see #getTestFinder 312 * @since 3.0 313 */ 314 public void setTestFinder(TestFinder tf) { 315 if (finder != null) { 316 throw new IllegalStateException(i18n.getString("trt.alreadyFinder")); 317 } 318 319 finder = tf; 320 321 if (trCache == null) 322 initFinder(); 323 } 324 325 public void updateTestExecutionOrderOnTheFly(){ 326 root = new TRT_TreeNode(TestResultTable.this, null); 327 } 328 329 /** 330 * Get the workdir associated with this object. 331 * 332 * @return The workdir. Null if not available. 333 * @deprecated Use getWorkDirectory 334 */ 335 public WorkDirectory getWorkDir() { 336 return getWorkDirectory(); 337 } 338 339 /** 340 * Get the work directory associated with this object. 341 * 342 * @return The work directory, or null if none set. 343 * @see #setWorkDirectory 344 */ 345 public WorkDirectory getWorkDirectory() { 346 return workDir; 347 } 348 349 /** 350 * Set the work directory associated with this object. 351 * Once set, it cannot be changed. 352 * 353 * @param wd The work directory, or null if none set. 354 * @see #getWorkDirectory 355 */ 356 public void setWorkDirectory(WorkDirectory wd) { 357 setWorkDirectory(wd, wd.getTestSuite().getTestFinder()); 358 } 359 360 // this method exists to support the obsolete constructor 361 // TestResultTable(WorkDirectory wd, TestFinder tf). 362 // When that constructor can be deleted, the main body of this 363 // method should be merged with setWorkDirectory(WorkDirectory wd) 364 private void setWorkDirectory(WorkDirectory wd, TestFinder tf) { 365 if (wd == null) 366 throw new NullPointerException(); 367 368 if (workDir == wd) { 369 // already set 370 return; 371 } 372 373 if (workDir != null && workDir != wd) { 374 // already set to something else 375 throw new IllegalStateException(); 376 } 377 378 if (finder != null && finder != tf) 379 throw new IllegalStateException(); 380 381 workDir = wd; 382 finder = tf; 383 384 //root = new TRT_TreeNode(this, null); 385 initFinder(); 386 387 /*OLD 388 // do this in the background because of possible high cost 389 Thread thr = new Thread("TRT background cache init.") { 390 public void run() { 391 try { 392 trCache = new TestResultCache(workDir, true); 393 trCache.setObserver(updater); 394 setCacheInitialized(true); 395 } 396 catch (TestResultCache.Fault f) { 397 if (debug > 0) 398 f.printStackTrace(Debug.getWriter()); 399 400 // XXX ack, what can we do?! 401 //throw new Fault(i18n, "trt.trcCreate", f.getMessage()); 402 } // try 403 } // run() 404 }; 405 thr.setPriority(Thread.MIN_PRIORITY + 2); 406 thr.start(); 407 */ 408 try { 409 trCache = new TestResultCache(workDir, updater); 410 } 411 catch (IOException e) { 412 // should consider throwing Fault, but that will destabilize too many APIs for now 413 updater.error(e); 414 } 415 } 416 417 /** 418 * How many tests have been found so far. 419 * 420 * @return A number greater than or equal to zero. 421 * @since 3.0 422 */ 423 public int getCurrentTestCount() { 424 return root.getCurrentSize(); 425 } 426 427 void starting() { 428 /*OLD 429 isRunning++; 430 */ 431 } 432 433 void finished() { 434 // do on background thread? (OLD suggestion -- now asynchronous) 435 if (trCache != null) { 436 if (needsCacheCompress()) { 437 if (debug > 0) { 438 Debug.print("TRT.finished(), attempting cache compress..."); 439 } 440 trCache.compress(); 441 } 442 if (debug > 0) { 443 Debug.print("TRT.finished(), requesting cache flush..."); 444 } 445 } 446 } 447 448 /** 449 * Update the information in the table with a new test result. 450 * The supplied TestResult may exist in the table already, it can replace 451 * an existing test or it can be completely new. Doing this operation will 452 * trigger appropriate observer messages. 453 * 454 * @param tr The test to insert or update. 455 * @throws JavaTestError Throws if the result cache throws an error. 456 */ 457 public void update(TestResult tr) { 458 update(tr, false); 459 } 460 461 /** 462 * Update the information in the table with a new test result. 463 * The supplied TestResult may exist in the table already, it can replace 464 * an existing test or it can be completely new. Doing this operation will 465 * trigger appropriate observer messages. 466 * 467 * @param tr The test to insert or update. 468 * @param suppressScan Request that test finder activity be suppressed if possible 469 * @throws JavaTestError Throws if the result cache throws an error. 470 */ 471 public void update(TestResult tr, boolean suppressScan) { 472 TestResult prev = insert(tr, suppressScan); 473 updateNotify(tr, prev); 474 } 475 476 /** 477 * Complete the notification process for insertion of a new result. 478 * This method adds it to the cache, sends notifications and does other 479 * bookkeeping only. 480 * 481 * This method was introduced to allow updates to occur from non-root 482 * branches (bottom up updates) or updates from insertions into the table 483 * which are usual during test execution (top down). 484 * @param tr Result inserted. 485 * @param prev Result replaced (if any). 486 */ 487 void updateNotify(TestResult tr, TestResult prev) { 488 489 if (tr != prev) { 490 tr.shareStatus(statusTables); 491 492 for (int i = 0; i < observers.length; i++) 493 observers[i].update(prev, tr); 494 } 495 else { 496 // tests are the same, we are probably changing the status 497 for (int i = 0; i < observers.length; i++) 498 observers[i].updated(tr); 499 } 500 501 testsInUpdate.add(tr); 502 503 // things which haven't been run are not put in cache 504 // doing so will cause problems in the cache and possibly other 505 // places because there is never a JTR file to reload the result from 506 if (trCache!= null && !updateInProgress && tr.getStatus().getType() != Status.NOT_RUN) { 507 trCache.insert(tr); 508 } 509 510 testsInUpdate.remove(tr); 511 } 512 513 /** 514 * This method blocks until the work directory data has been completely 515 * synchronized. It is recommended that you use this before creating and 516 * using an iterator to ensure that you get consistent and up to date 517 * data. It would also be advisable to do this before forcing the VM to 518 * exit. 519 * @return Always true. Reserved for later use. 520 * 521 * @since 3.0.1 522 */ 523 public synchronized boolean waitUntilReady() { 524 while ( ((workDir != null) && !cacheInitialized) || 525 updateInProgress) { 526 try { 527 wait(); 528 } 529 catch (InterruptedException e) { 530 if (debug > 0) 531 e.printStackTrace(Debug.getWriter()); 532 } 533 } 534 535 if (trCache != null){ 536 trCache.requestFullUpdate(); 537 } 538 539 return true; 540 } 541 542 /** 543 * Determine the update status of the table. 544 * 545 * @return True if the table is in a consistent state. 546 * @see #waitUntilReady 547 */ 548 public synchronized boolean isReady() { 549 return (cacheInitialized && !updateInProgress); 550 } 551 552 /** 553 * Find a specific instance of a test result. 554 * 555 * @param td The test description which corresponds to the target result. 556 * @return The requested test result object, null if not found. 557 */ 558 public TestResult lookup(TestDescription td) { 559 // transforms to JTR path style to do lookup 560 return lookup(TestResult.getWorkRelativePath(td.getRootRelativeURL())); 561 } 562 563 /** 564 * Find a specific instance of a test result. 565 * If you only have the test URL, use TestResult.getWorkRelativePath() to get 566 * the jtrPath parameter for this method. 567 * 568 * @param jtrPath The work relative path of the test result file. 569 * Output from TestResult.getWorkRelativePath() is 570 * the best source of this info. 571 * @return The requested test result object, null if not found. 572 */ 573 public TestResult lookup(String jtrPath) { 574 // no tree yet 575 if (root == null) return null; 576 577 return findTest(root, jtrPath, jtrPath); 578 } 579 580 /** 581 * Take a URL and find the node in the current test tree to which it refers 582 * to. The resulting node may be: 583 * <ul> 584 * <li>A folder (non-leaf) node. Type <code>TreeNode</code> 585 * <li>A test (leaf) node. Type <code>TestResult</code> 586 * </ul> 587 * @param url A forward-slash separated path referring to node names along a 588 * path originating at the root node. 589 * @return The nodes that the given URL refers to. Null if no match. 590 * @since 3.2 591 */ 592 public Object resolveUrl(String url) { 593 return lookupNode(root, url); 594 } 595 596 /** 597 * Validate that a path is valid in this TestResultTable according to the 598 * rules for initial URLs. 599 * 600 * @param path The path to validate. Should be internal URL format (forward 601 * slashes, relative to test root, ...) Null is acceptable but will 602 * immediately result in null being returned. 603 * @return True if the given path is valid in this instance, false otherwise. 604 * @since 3.0.2 605 */ 606 public boolean validatePath(String path) { 607 if (path == null) 608 return false; 609 610 Object[] result = lookupInitURL(root, path); 611 612 if (result != null && result.length > 0) 613 return true; // ok 614 else 615 return false; // no matches for path 616 } 617 618 /** 619 * Find the specified test, recording the path from the root. 620 * 621 * @since 3.0 622 * @param target The test to generate the path for. 623 * @return The path to the root of the enclosing TRT. Null if the operation 624 * could not be completed. The target node is not included in the 625 * returned array. Index 0 will be the TRT root. 626 */ 627 public static TreeNode[] getObjectPath(TestResult target) { 628 if (target == null) 629 return null; 630 631 ArrayList<TreeNode> path = new ArrayList<>(); 632 TreeNode loc = target.getParent(); 633 while (loc != null) { 634 path.add(0, loc); 635 loc = loc.getParent(); 636 // getParent() will be null for the root node 637 } // while 638 639 TreeNode[] result = new TreeNode[path.size()]; 640 path.toArray(result); 641 642 if (debug == 2 || debug == 99) { 643 Debug.println("TRT - getObjectPath() results:"); 644 Debug.println(" -> target: " + target.getTestName()); 645 Debug.println(" -> resulting path length: " + result.length); 646 } 647 648 if (result == null || result.length == 0) 649 return null; 650 else 651 return result; 652 } 653 654 /** 655 * Find the specified test, recording the path from the root. 656 * 657 * @since 3.0 658 * @param target The node to generate the path for. 659 * @return The path to the root of the enclosing TRT. Null if the operation 660 * could not be completed. The target node is included in the returned array 661 * as the last element, index 0 will be the TRT root. 662 */ 663 public static TreeNode[] getObjectPath(TreeNode target) { 664 if (target == null) 665 return null; 666 667 ArrayList<TreeNode> path = new ArrayList<>(); 668 TreeNode loc = target; 669 while (loc != null) { 670 path.add(0, loc); 671 loc = loc.getParent(); // getParent() will be null for the root node 672 } // while 673 674 TreeNode[] result = new TreeNode[path.size()]; 675 path.toArray(result); 676 677 if (debug == 2 || debug == 99) { 678 Debug.println("TRT - getObjectPath() results:"); 679 Debug.println(" -> target RRP: " + TestResultTable.getRootRelativePath(target)); 680 Debug.println(" -> resulting path length: " + result.length); 681 } 682 683 return result; 684 } 685 686 /** 687 * List all the tests in the tree. 688 * 689 * @return An iterator which returns all tests in the tree. 690 * @since 3.0 691 */ 692 public TreeIterator getIterator() { 693 if (root == null) 694 return NullEnum.getInstance(); 695 else 696 return getIterator(root); 697 } 698 699 /** 700 * List all the tests in the tree. 701 * 702 * @return An enumerator which returns all tests in the tree. 703 * @see #getIterator() 704 * @since 3.0 705 */ 706 public Enumeration<TestResult> elements() { 707 return getIterator(); 708 } 709 710 /** 711 * List all the tests in the tree subject to the given filters. 712 * 713 * @param filters The Filters to run tests through before "selecting" 714 * them for iteration. May be null. 715 * @return An iterator which returns all tests in the tree after removing 716 * those filtered out by the filters. 717 * @since 3.0 718 */ 719 public TreeIterator getIterator(TestFilter[] filters) { 720 if (root == null) 721 return NullEnum.getInstance(); 722 else 723 return getIterator(root, filters); 724 } 725 726 /** 727 * Same description as getIterator() method with same args. 728 * 729 * @param filters The Filters to run tests through before "selecting" 730 * them for iteration. May be null. 731 * @return An enumerator which returns all tests in the tree after removing 732 * those filtered out by the filters. 733 * @since 3.0 734 * @see #getIterator() 735 */ 736 public Enumeration<TestResult> elements(TestFilter[] filters) { 737 return getIterator(filters); 738 } 739 740 /** 741 * List all the tests under this node. 742 * 743 * @param node The tree node to being the iteration at. 744 * @return An iterator which return all the tests below the given node. 745 * @since 3.0 746 */ 747 public static TreeIterator getIterator(TreeNode node) { 748 if (node == null) 749 return NullEnum.getInstance(); 750 else 751 return new TRT_Iterator(node); 752 } 753 754 /** 755 * List all the tests under this node. 756 * 757 * @param node The tree node to being the iteration at. 758 * @return An enumerator which return all the tests below the given node. 759 * @see #getIterator() 760 * @since 3.0 761 */ 762 public static Enumeration<TestResult> elements(TreeNode node) { 763 return getIterator(node); 764 } 765 766 /** 767 * Get an iterator capable of producing a filtered view of the test suite. 768 * If the node parameter is null, an iterator with no items will be returned. 769 * An empty or null set of filters is acceptable and will result in unfiltered 770 * iteration. 771 * 772 * @param node The tree node to being the iteration at. May be null. 773 * @param filter The filter to run tests through before "selecting" 774 * them for iteration. 775 * @return An iterator which returns test below the given node after 776 * removing any tests which the filter rejects. 777 * @since 3.0 778 */ 779 public static TreeIterator getIterator(TreeNode node, TestFilter filter) { 780 if (node == null) 781 return NullEnum.getInstance(); 782 else { 783 TestFilter[] filters = new TestFilter[] {filter}; 784 return new TRT_Iterator(node, filters); 785 } 786 } 787 788 /** 789 * Same description as getIterator() method with same args. 790 * 791 * @param node The tree node to being the enumeration at. May be null. 792 * @param filter The filter to run tests through before "selecting" 793 * them for enumeration. May be null. 794 * @return An enumerator which returns test below the given node after 795 * removing any tests which the filter rejects. 796 * @see #getIterator() 797 * @since 3.0 798 */ 799 public static Enumeration<TestResult> elements(TreeNode node, TestFilter filter) { 800 return getIterator(node, filter); 801 } 802 803 /** 804 * Get an iterator capable of producing a filtered view of the test suite. 805 * If the node parameter is null, an iterator with no items will be returned. 806 * An empty or null set of filters is acceptable and will result in unfiltered 807 * iteration. 808 * 809 * @param node The tree node to begin enumerating at. May be null. 810 * @param filters The test filters to apply to any tests found. 811 * @return An iterator which returns test below the given node after 812 * removing any tests which the filters reject. 813 * @since 3.0 814 */ 815 public static TreeIterator getIterator(TreeNode node, TestFilter[] filters) { 816 if (node == null) 817 return NullEnum.getInstance(); 818 else 819 return new TRT_Iterator(node, filters); 820 } 821 822 /** 823 * Same description as getIterator() method with same args. 824 * 825 * @param node The tree node to begin enumerating at. May be null. 826 * @param filters The test filters to apply to any tests found. 827 * @return An enumerator which returns test below the given node after 828 * removing any tests which the filters reject. 829 * @see #getIterator() 830 * @since 3.0 831 */ 832 public static Enumeration<TestResult> elements(TreeNode node, TestFilter[] filters) { 833 return getIterator(node, filters); 834 } 835 836 /** 837 * Get an enumerator capable of producing a filtered view of the test 838 * suite. This can be used to obtain a view of the test suite based on an 839 * initial URL selection. The URL can specify a folder/directory, a 840 * specific test, or a file which contains one or more tests. If the given 841 * URL parameter is null, an iterator with no elements will be returned. 842 * An empty or null set of filters is acceptable and will result in 843 * unfiltered iteration. 844 * 845 * @param url The test URL to scan. This value should have already be 846 * normalized to a '/' file separator. May be null. 847 * @param filters The test filters to apply to any tests found. May be null. 848 * @return An enumerator which returns test below the given location after 849 * removing any tests which the filters reject. 850 * @see #getIterator() 851 * @since 3.0 852 */ 853 public Enumeration<TestResult> elements(String url, TestFilter[] filters) { 854 if (url == null) 855 return NullEnum.getInstance(); 856 else { 857 String[] urls = {url}; 858 return elements(urls, filters); 859 } 860 } 861 862 /** 863 * Get an iterator capable of producing a filtered view of the test suite. 864 * This can be used to obtain a view of the test suite based on an initial 865 * URL selection. The URL can specify a folder/directory, a specific test, 866 * or a file which contains one or more tests. If the initial urls are 867 * null or zero length, a filtered iterator of the root will be returned. 868 * An empty or null set of filters is acceptable and will result in 869 * unfiltered iteration. 870 * 871 * @param tests The set of files base the iterator on. May be null. 872 * If this set is not empty, the contents should have already been 873 * validated using the validatePath() method. 874 * @param filters The test filters to apply to any tests found. May be null. 875 * @return An iterator which return the union of tests specified by the 876 * initial files but not removed by the filters. 877 * @throws TestResultTable.Fault Thrown if the given initialUrls are invalid. 878 * @since 3.0 879 * @see #validatePath 880 */ 881 public TreeIterator getIterator(File[] tests, TestFilter[] filters) throws Fault { 882 String[] urls = preProcessInitFiles(tests); 883 884 if (urls != null && urls.length > 0) 885 return getIterator(urls, filters); 886 else 887 return getIterator(filters); 888 } 889 890 /** 891 * Same as getIterator() with the same args. 892 * 893 * @param tests The set of files base the enumerator on. May be null. 894 * @param filters The test filters to apply to any tests found. May be null. 895 * @return An enumerator which return the union of tests specified by the 896 * initial files but not removed by the filters. 897 * @throws TestResultTable.Fault Thrown if the given initialUrls are invalid. 898 * @see #getIterator() 899 * @since 3.0 900 */ 901 public Enumeration<TestResult> elements(File[] tests, TestFilter[] filters) throws Fault { 902 return getIterator(tests, filters); 903 } 904 905 /** 906 * Get an iterator capable of producing a filtered view of the test suite. 907 * This can be used to obtain a view of the test suite based on an initial 908 * URL selection. An empty or null set of filters is acceptable and will 909 * result in unfiltered iteration. 910 * 911 * @param paths The test URLs to scan. Values should have already be normalized to a 912 * '/' file separator. May not be null. 913 * @param filters The test filters to apply to any tests found. May be null. 914 * @return An iterator which return the union of tests specified by the 915 * URLs but not removed by the filters. 916 * @since 3.0 917 */ 918 public TreeIterator getIterator(String[] paths, TestFilter[] filters) { 919 LinkedList<TreeNode> initNodes = new LinkedList<TreeNode>(); 920 LinkedList<TestResult> initTests = new LinkedList<TestResult>(); 921 922 String[] urls = sortByName(paths); // sorting in any case to improve performance of distilling 923 urls = distillUrls(urls); 924 925 if (!Boolean.parseBoolean( 926 Preferences.access().getPreference("javatest.sortExecution", "true"))) { 927 // need to exclude extra elements from initial array 928 if (urls.length != paths.length) { 929 urls = removeMissing(paths, urls); 930 } else { 931 // nothing was distilled - just taking paths 932 urls = paths; 933 } 934 } 935 936 for (int i = 0; i < urls.length; i++) { 937 Object[] objs = lookupInitURL(root, urls[i]); 938 if (debug == 1 || debug == 99) 939 Debug.println("TRT.lookupInitURL gave back " + Arrays.toString(objs)); 940 941 if (objs == null) { // no match 942 } 943 else if (objs instanceof TreeNode[]) { 944 // don't add duplicates 945 if (!initNodes.contains(objs[0])) 946 initNodes.add((TreeNode) objs[0]); 947 } 948 else if (objs instanceof TestResult[]) { 949 initTests.addAll(Arrays.asList((TestResult[]) objs)); 950 // XXX should uniquify 951 } 952 else { 953 // XXX should this be more friendly? 954 // or maybe it should be ignored 955 throw new IllegalArgumentException(i18n.getString("trt.invalidIURL", urls[i])); 956 } 957 } // for 958 959 if ((initNodes == null || initNodes.size() == 0) && 960 (initTests == null || initTests.size() == 0)) { 961 if (debug == 1 || debug == 99) 962 Debug.println("None of the initial URLs could be looked up."); 963 964 return NullEnum.getInstance(); 965 } 966 967 if (initTests.size() > 0) { 968 if (debug == 1 || debug == 99) 969 Debug.println("Using combo TreeIterator, " + initTests.size() + 970 " tests, " + initNodes.size() + " nodes."); 971 return new TRT_Iterator(initNodes.toArray(new TreeNode[0]), initTests.toArray(new TestResult[0]), filters); 972 } 973 else 974 return new TRT_Iterator(initNodes.toArray(new TreeNode[0]), filters); 975 } 976 977 /** 978 * This method is the same as getIterator() with the same params. 979 * 980 * @param urls The test URLs to scan. Values should have already be normalized to a 981 * '/' file separator. 982 * @param filters The test filters to apply to any tests found. 983 * @return An enumerator which return the union of tests specified by the 984 * URLs but not removed by the filters. 985 * @see #getIterator() 986 * @since 3.0 987 */ 988 public Enumeration<TestResult> elements(String[] urls, TestFilter[] filters) { 989 return getIterator(urls, filters); 990 } 991 992 /** 993 * Find out the size of the entire tree. This is a high overhead call, use 994 * with caution. 995 * 996 * @return The number of tests in the tree. 997 */ 998 public int size() { 999 if (root == null) 1000 return 0; 1001 else 1002 return root.getSize(); 1003 } 1004 1005 /** 1006 * Insert the given test into the tree. 1007 * 1008 * @param tr The test to insert. A null value is ignored. 1009 * @return The old value. Null if none or the parameter was null. 1010 */ 1011 TestResult insert(TestResult tr) { 1012 return insert(tr, false); 1013 } 1014 1015 /** 1016 * Insert the given test into the tree. 1017 * 1018 * @param tr The test to insert. A null value is ignored. 1019 * @param suppressScan Request that test finder activity be suppressed. 1020 * @return The old value. Null if none or the parameter was null. 1021 */ 1022 TestResult insert(TestResult tr, boolean suppressScan) { 1023 if (tr == null) 1024 return null; 1025 1026 String key = tr.getWorkRelativePath(); 1027 //maxDepth = 0; 1028 1029 TRT_TreeNode[] path = new TRT_TreeNode[0]; 1030 1031 return insert(root, key, tr, path, suppressScan); 1032 } 1033 1034 /** 1035 * Insert the given test and indicate that test's previous status. 1036 * 1037 * @param tr The test to insert, may or may not already exist in the table. 1038 * A null value is ignored. 1039 * @param oldStatus The previous status of the given test. 1040 * @return The old result which was replaced. Null if the parameter was null or 1041 * there was no previous value. 1042 */ 1043 TestResult insert(TestResult tr, Status oldStatus) { 1044 if (tr == null) 1045 return null; 1046 1047 String key = tr.getWorkRelativePath(); 1048 //maxDepth = 0; 1049 1050 TRT_TreeNode[] path = new TRT_TreeNode[0]; 1051 1052 return insert(root, key, tr, path, false); 1053 } 1054 1055 /** 1056 * Get the root TreeNode of this result table. 1057 * 1058 * @return The root of the tree. 1059 */ 1060 public TestResultTable.TreeNode getRoot() { 1061 return root; 1062 } 1063 1064 /** 1065 * Get the root URL of the test suite. 1066 * This may not match that given by the environment if the environment's 1067 * URL is partially invalid for some reason. 1068 * 1069 * @return A file representing the path to the root of the testsuite. 1070 */ 1071 public File getTestSuiteRoot() { 1072 return suiteRoot; 1073 } 1074 1075 /** 1076 * Get the finder that TRT is using to read the test suite. 1077 * Unless the TRT was constructed using a TestFinder, this value will 1078 * most likely be null. 1079 * 1080 * @return The active test finder. Null if no finder is being used. 1081 * @see #setTestFinder 1082 * @since 3.0 1083 */ 1084 public TestFinder getTestFinder() { 1085 return finder; 1086 } 1087 1088 /** 1089 * Get the path to this node, relative to the root of the testsuite. 1090 * The returned URL does not have a trailing slash, nor does it begin 1091 * with one. 1092 * @param node The node to find the path to. 1093 * @return The URL to the given node, with '/' as the path separator. 1094 * Zero length string if the node is a root. 1095 * @since 3.0 1096 */ 1097 public static String getRootRelativePath(TreeNode node) { 1098 if (node.isRoot()) return ""; 1099 1100 StringBuilder name = new StringBuilder(node.getName()); 1101 node = node.getParent(); 1102 1103 while (node != null && !node.isRoot()) { 1104 name.insert(0, '/'); 1105 name.insert(0, node.getName()); 1106 node = node.getParent(); 1107 } 1108 1109 return name.toString(); 1110 } 1111 1112 /** 1113 * Used to find a branch node somewhere in the tree based on a path. 1114 * If the <tt>path</tt> string is of zero length (the empty string), the 1115 * <tt>node</tt> parameter is returned. This is desirable for proper operation 1116 * because the path to the root is the empty path. 1117 * 1118 * @param node Where to start the search 1119 * @param path The work relative position of the JTR (TestResult.getWorkRelativePath()) 1120 * @return The node with the given path relative to the given node. Null if not found. 1121 * @throws IllegalArgumentException If the starting node or path is null. 1122 * @since 3.0 1123 */ 1124 public static TreeNode findNode(TreeNode node, String path) { 1125 if (node == null) 1126 throw new IllegalArgumentException(i18n.getString("trt.nodeNull")); 1127 if (path == null) 1128 throw new IllegalArgumentException(i18n.getString("trt.pathNull")); 1129 1130 // special case, should only happen on first call of this method 1131 if (path.length() == 0) 1132 return node; 1133 1134 String dir = getDirName(path); 1135 TreeNode tn = null; 1136 1137 if (debug > 9) 1138 Debug.println("TRT.findNode() looking for " + path + " in " + node.getName()); 1139 1140 if (dir.equals(path)) { // last element of the path 1141 tn = ((TRT_TreeNode)(node)).getTreeNode(path, false); 1142 } else { // recurse 1143 TreeNode next = ((TRT_TreeNode)node).getTreeNode(dir, false); 1144 if (next != null) 1145 tn = findNode(next, behead(path)); 1146 else { // not found 1147 } 1148 } 1149 1150 return tn; 1151 } 1152 1153 /** 1154 * Add a general purpose observer. 1155 * 1156 * @param o The observer to attach. Must never be null. 1157 */ 1158 public synchronized void addObserver(Observer o) { 1159 if (o == null) 1160 throw new NullPointerException(); 1161 observers = DynamicArray.append(observers, o); 1162 } 1163 1164 /** 1165 * Remove a general purpose observer. 1166 * Removing an observer which is not attached has no effect. 1167 * 1168 * @param o The observer to remove. 1169 */ 1170 public synchronized void removeObserver(Observer o) { 1171 observers = DynamicArray.remove(observers, o); 1172 } 1173 1174 /** 1175 * Add a tree-aware observer. 1176 * 1177 * @param obs The observer to attach. Must never be null. 1178 */ 1179 public void addObserver(TreeObserver obs) { 1180 treeObservers = DynamicArray.append(treeObservers, obs); 1181 } 1182 1183 /** 1184 * Remove a tree-aware observer. 1185 * Removing an observer which is not attached has no effect. 1186 * 1187 * @param obs The observer to remove. 1188 */ 1189 public void removeObserver(TreeObserver obs) { 1190 if (treeObservers != null) 1191 treeObservers = DynamicArray.remove(treeObservers, obs); 1192 } 1193 1194 /** 1195 * This method purges the given test, including attempting to delete the 1196 * associated JTR file, then replaces it with a basic <code>NOT_RUN</code> 1197 * test of the same name. This operation has no effect if the given test 1198 * is not in the tree. 1199 * <p> 1200 * Matching objects for removal is done only by reference. The operation 1201 * may fail (return <code>null</code>) if the test exists, but is not the 1202 * same object. If you really want to remove a test by name, you can use 1203 * <code>resetTest(String)</code>. 1204 * <p> 1205 * NOTE: This method will cause waitUntilReady() to block. 1206 * 1207 * @param tr The test to find, purge and replace. 1208 * @return The new <code>NOT_RUN</code> test. Null if the operation fails 1209 * because the test could not be found. 1210 * @see #resetTest(String) 1211 * @since 3.0 1212 */ 1213 public synchronized TestResult resetTest(TestResult tr) { 1214 TestResult newTest = null; 1215 1216 workDir.log(i18n, "trt.rstTest", tr.getTestName()); 1217 TreeNode[] location = getObjectPath(tr); 1218 1219 if (location == null) { // operation failed 1220 // do nothing 1221 // result = null 1222 newTest = lookup(tr.getWorkRelativePath()); 1223 if (debug > 0) 1224 Debug.println("Recovered test by replacement (1). " + newTest); 1225 } 1226 else { 1227 TRT_TreeNode targetNode = (TRT_TreeNode)(location[location.length-1]); 1228 int index = targetNode.getIndex(tr, false); 1229 if (index >= 0) { 1230 newTest = targetNode.resetTest(index, tr); 1231 if (newTest == null && debug > 0) 1232 Debug.println("reset of test " + tr.getTestName() + " failed."); 1233 else { 1234 /*OLD 1235 try { 1236 */ 1237 // Insert into cache? 1238 // this will cause a cache error on flush because it can't reload the 1239 // result, but that should get it out of the cache 1240 if (trCache != null) { 1241 testsInUpdate.add(newTest); 1242 trCache.insert(newTest); 1243 testsInUpdate.remove(newTest); 1244 } 1245 notifyRemoveLeaf(location, tr, index); 1246 notifyNewLeaf(location, newTest, index); 1247 /*OLD 1248 } // try 1249 catch (TestResultCache.Fault f) { 1250 if (debug > 0) 1251 f.printStackTrace(Debug.getWriter()); 1252 else { } 1253 1254 throw new JavaTestError(i18n, "trt.trcFault", f); 1255 } // catch 1256 */ 1257 } // inner else 1258 } // middle if 1259 else { 1260 newTest = lookup(tr.getWorkRelativePath()); 1261 if (debug > 0) 1262 Debug.println("Recovered test by replacement (2). " + newTest); 1263 } // middle else 1264 } // else 1265 1266 return newTest; 1267 } 1268 1269 /** 1270 * This method purges the given test, including attempting to delete the 1271 * associated JTR file, then replaces it with a basic <code>NOT_RUN</code> 1272 * test of the same name. This operation has no effect if the given test 1273 * is not in the tree. The <code>resetTest(TestResult)</code> method is 1274 * more efficient than this one, use it if you already have the object. 1275 * <p> 1276 * NOTE: This method may cause waitUntilReady() to block. 1277 * 1278 * @param testName The test to find, purge and replace. This is of the form 1279 * given by TestResult.getTestName(). 1280 * @return The new <code>NOT_RUN</code> test. Null if the given test name 1281 * could not be found. 1282 * @see com.sun.javatest.TestResult#getTestName 1283 * @since 3.0 1284 */ 1285 public synchronized TestResult resetTest(String testName) { 1286 TestResult tr = findTest(root, TestResult.getWorkRelativePath(testName), testName); 1287 if (tr == null) 1288 return null; 1289 else 1290 return resetTest(tr); 1291 } 1292 1293 /** 1294 * Refresh a test if the files on disk have been modified since the test was read. 1295 * @param test The path for the test to be refreshed 1296 * @return true if a refresh was needed, false otherwise. 1297 * @throws TestResultTable.Fault if the test indicated cannot be located for 1298 * refreshing. 1299 */ 1300 public synchronized boolean refreshIfNeeded(String test) throws Fault { 1301 TestResult tr = lookup(TestResult.getWorkRelativePath(test)); 1302 1303 if (tr == null) 1304 throw new Fault(i18n, "trt.refreshNoTest", test); 1305 1306 TreeNode[] path = getObjectPath(tr); 1307 1308 if (path == null) 1309 return false; 1310 1311 TRT_TreeNode tn = (TRT_TreeNode)path[path.length-1]; 1312 TestResult newTr = tn.refreshIfNeeded(tr); 1313 1314 if (newTr != tr) 1315 notifyChangeLeaf(TestResultTable.getObjectPath(tn), 1316 newTr, tn.getTestIndex(newTr, false), tr); 1317 1318 return false; 1319 } 1320 1321 /** 1322 * Refresh a folder if the files on disk have been modified since the 1323 * folder was read. Notifies observers of the refresh happened. 1324 * @param node the node representing the folder to be refreshed 1325 * @return true if any refreshing was needed, false otherwise. 1326 * @throws TestResultTable.Fault if the node indicated cannot be located for 1327 * refreshing. 1328 */ 1329 public synchronized boolean refreshIfNeeded(TreeNode node) throws Fault { 1330 if (node.getEnclosingTable() != this) 1331 throw new IllegalStateException("refresh requested for node not owned by this table"); 1332 1333 notifyStartRefresh(node); 1334 try { 1335 return recursiveRefresh((TRT_TreeNode)node); 1336 } finally { 1337 notifyFinishRefresh(node); 1338 } 1339 } 1340 1341 public synchronized boolean prune() throws Fault { 1342 if (root == null) 1343 return false; 1344 1345 boolean changes = false; 1346 1347 root.scanIfNeeded(); 1348 TreeNode[] nodes = root.getTreeNodes(); 1349 1350 if (nodes == null) 1351 return changes; 1352 1353 for (int i = 0; i < nodes.length; i++) { 1354 changes = (changes || prune(nodes[i])); 1355 } // for 1356 1357 return changes; 1358 } 1359 1360 /** 1361 * Removes empty nodes (things with no tests below them). 1362 * @param node The node to perform the prune operation upon. 1363 * @return True if some nodes were pruned, false otherwise. 1364 */ 1365 synchronized public boolean prune(TreeNode node) throws Fault { 1366 TRT_TreeNode parent = ((TRT_TreeNode)(node.getParent())); 1367 1368 if (node.getChildCount() == 0) { 1369 int index = parent.rmChild((TRT_TreeNode)node); 1370 1371 if (index != -1) 1372 notifyRemoveLeaf(getObjectPath(parent), node, index); 1373 1374 return (index != -1 ? true : false); 1375 } 1376 1377 TreeNode[] nodes = node.getTreeNodes(); 1378 1379 if (nodes == null) 1380 return false; // must mean there are tests in this node 1381 1382 for (int i = 0; i < nodes.length; i++) { 1383 prune(nodes[i]); 1384 } // for 1385 1386 if (node.getChildCount() == 0) { 1387 // prune 1388 int index = parent.rmChild((TRT_TreeNode)node); 1389 if (index != -1) 1390 notifyRemoveLeaf(getObjectPath(parent), node, index); 1391 1392 return (index != -1 ? true : false); 1393 } 1394 1395 return false; 1396 } 1397 1398 // ------- Private methods begin -------- 1399 1400 /** 1401 * Temporary method to suppress finder activity. 1402 * Called from Harness when in batch mode. You should 1403 * <b>not</b> change this after it has been set. It 1404 * should be set before iteration and insertion of 1405 * work directory information takes place. 1406 */ 1407 void suppressFinderScan(boolean state) { 1408 if (state == false) 1409 suppressFinderScan = false; 1410 else if (workDir != null) { 1411 TestSuite ts = workDir.getTestSuite(); 1412 if (ts != null && ( 1413 ts.getTestRefreshBehavior(TestSuite.DELETE_NONTEST_RESULTS) || 1414 ts.getTestRefreshBehavior(TestSuite.REFRESH_ON_RUN) || 1415 ts.getTestRefreshBehavior(TestSuite.CLEAR_CHANGED_TEST)) ) { 1416 suppressFinderScan = false; 1417 } 1418 else 1419 suppressFinderScan = state; // i.e. true 1420 } 1421 else 1422 suppressFinderScan = state; // i.e. true 1423 1424 } 1425 1426 boolean isFinderScanSuppressed() { 1427 return suppressFinderScan; 1428 } 1429 1430 /** 1431 * Determine if the path represented by the file is a branch or a leaf. 1432 * This is the semantic equivalent of File.isDirectory(), but shielded 1433 * behind this method for finders which do not use the filesystem. 1434 * @param f The file to check. May not be null. 1435 */ 1436 boolean isBranchFile(File f) { 1437 return finder.isFolder(f); 1438 } 1439 1440 /** 1441 * Determine the last logical time that a file was modified. 1442 * This is the semantic equivalent of File.lastModified(), but shielded 1443 * behind this method for finders which do not use the filesystem. 1444 * @param f The file to check. May not be null. 1445 */ 1446 long getLastModifiedTime(File f) { 1447 // this must be upgraded for binary test finder scanning to work without 1448 // the actual files in the tests directory 1449 return finder.lastModified(f); 1450 } 1451 1452 private class DisassembledUrl { 1453 private String[] data; 1454 private String initStr; 1455 1456 public DisassembledUrl(String str) { 1457 data = StringArray.splitList(str, "/"); 1458 initStr = str; 1459 } 1460 } 1461 1462 private static class SortingComparator implements Comparator<DisassembledUrl> { 1463 Comparator<String> c; 1464 1465 public SortingComparator(Comparator<String> c) { 1466 this.c = c; 1467 } 1468 1469 public int compare(DisassembledUrl o1, DisassembledUrl o2) { 1470 String[] s1 = o1.data, s2 = o2.data; 1471 for (int i = 0; i < s1.length; i++) { 1472 if (i >= s2.length) 1473 return 1; 1474 1475 int comp = c.compare(s1[i], s2[i]); 1476 if (comp == 0) 1477 continue; 1478 else 1479 return comp; 1480 } 1481 1482 // this case means that all s1 body exists in s2 1483 if (s2.length > s1.length) 1484 return -1; 1485 1486 // s1 is exact copy of s2. Should not happen really. 1487 return 0; 1488 } 1489 } 1490 1491 /** 1492 * Sort test and folder URLs by their ordering the tree, which is ultimately 1493 * determined by the <code>TestFinder</code>. 1494 */ 1495 private String[] sortByName(String[] in) { 1496 if (in == null || in.length <= 1) 1497 return in; 1498 1499 Comparator<String> c = finder.getComparator(); 1500 1501 if (c == null) { 1502 // show warning message? 1503 c = TestFinder.getDefaultComparator(); 1504 } 1505 1506 DisassembledUrl[] elements = new DisassembledUrl[in.length]; 1507 for (int i = 0; i < in.length; i++) { 1508 elements[i] = new DisassembledUrl(in[i]); 1509 } 1510 1511 Arrays.sort(elements, new SortingComparator(c)); // using standard way to sort the array 1512 1513 String result[] = new String[elements.length]; 1514 int i = 0; 1515 for(DisassembledUrl s: elements) 1516 result[i++] = s.initStr; 1517 1518 return result; 1519 } 1520 1521 private int compareStringArrays(Comparator<String> c, String[] s1, String[] s2) { 1522 // loop until names become unequal 1523 for (int i = 0; i < s1.length; i++) { 1524 if (i >= s2.length) 1525 return 1; 1526 1527 int comp = c.compare(s1[i], s2[i]); 1528 if (comp == 0) 1529 continue; 1530 else 1531 return comp; 1532 } 1533 1534 return 0; 1535 } 1536 1537 /** 1538 * Removes from sorted array all overlapping entries. 1539 * E.g. {"root/a/b.html", "root/a/b.html#a", "root/a/b.htmls", "root/c", "root/c/d"} 1540 * would be {"root/a/b.html", "root/a/b.htmls", "root/c"} 1541 * complexity: n 1542 * complexity including quicksort: n*n*ln(n) 1543 * 1544 * @param urls A sorted list of test urls 1545 */ 1546 public static String[] distillUrls(String[] urls) { 1547 // this method should guarantee that no one test would be runned twice. 1548 // testcases (in brackets are paths that should be thrown out): 1549 // 1) root, [root/a/b/c]; root/a, root/b, [root/b/c]; root/a.html, root/a.htmls; root/a.html, [root/a.html#boo]; root, [root/a], [root/b] 1550 1551 // no need to process in these cases 1552 if (urls == null || urls.length <= 1) 1553 return urls; 1554 1555 LinkedList<String> result = new LinkedList<String>(); 1556 result.add(urls[0]); 1557 1558 // as the array is expected to be sorted, it's known that foo/boo.html is just before foo/boo.html#anything 1559 1560 String prev = urls[0]; // should not be testcase (should not contain '#' chars) 1561 if (prev.contains("#")) 1562 prev = "//##//"; // can't be start of testname 1563 for (int i = 1; i < urls.length; i++) { 1564 if (isPartOf(urls[i], prev)) { 1565 continue; 1566 } 1567 1568 if (!urls[i].contains("#")) 1569 prev = urls[i]; 1570 result.add(urls[i]); 1571 } 1572 1573 1574 if (result.size() == urls.length) 1575 // Nothing was thrown out. No need to reconstruct array. 1576 return urls; 1577 else { 1578 String[] s = new String[result.size()]; 1579 return result.toArray(s); 1580 } 1581 } 1582 1583 private static boolean isPartOf(String part, String of) { 1584 // if S2 is a part of S1 (e.g. S1="root/a", S2="root/a/t.html#2"), then S2 = S1 + diff, where diff can only start with '#' or '/' 1585 if (part.length() <= of.length()) 1586 return false; 1587 1588 if (part.startsWith(of) && (part.charAt(of.length()) == '#' || part.charAt(of.length()) == '/')) 1589 return true; 1590 1591 return false; 1592 } 1593 1594 /** 1595 * Creates a new array which contains all elements from missingIn ordered as in removeFrom 1596 * 1597 * @param removeFrom unsorted not distilled array 1598 * @param missingIn sorted and distilled array 1599 * @return a new array which contains all elements from missingIn ordered as in removeFrom 1600 */ 1601 private String[] removeMissing(String[] removeFrom, String missingIn[]) { 1602 String[] result = new String[missingIn.length]; 1603 int i = 0; 1604 outer: 1605 for (String path: removeFrom) { 1606 for (String url: missingIn) { 1607 // if a path is in distilled array - save it and continue with next 1608 if (path == url) { 1609 result[i++] = path; 1610 continue outer; 1611 } 1612 } 1613 } 1614 return result; 1615 } 1616 1617 private synchronized void updateFromCache(Map<String, TestResult> m) { 1618 updateInProgress = true; 1619 1620 // this method could use further optimization to take advantage 1621 // of its sorted nature 1622 cachedResults = m; 1623 1624 if (rtc.getRequests() != null) { 1625 for (TestDescription td : rtc.getRequests()) { 1626 String testRes = TestResult.getWorkRelativePath(td.getRootRelativeURL()); 1627 TestResult tr = m.get(testRes); 1628 if (tr != null) { 1629 tr.setTestDescription(td); 1630 update(tr, suppressFinderScan); 1631 } 1632 } 1633 } 1634 rtc.clear(); 1635 1636 if (!cacheInitialized) 1637 cacheInitialized = true; 1638 1639 updateInProgress = false; 1640 1641 notifyAll(); 1642 } 1643 1644 /** 1645 * Recursively Insert the given test into the tree, recording the insertion 1646 * path along the way. This is <em>not</em> a general purpose method. 1647 * 1648 * @param path Remaining part of the path. Must not be null. 1649 * The expected format is: foo/bar/baz.html#bear 1650 * @param tr The test result object we are storing 1651 * @return The test result which was replaced by this operation, null if no 1652 * previous entry existed. 1653 */ 1654 synchronized TestResult insert(TRT_TreeNode node, String path, TestResult tr, 1655 TRT_TreeNode[] rec) { 1656 return insert(node, path, tr, rec, false); 1657 } 1658 1659 /** 1660 * Recursively Insert the given test into the tree, recording the insertion 1661 * path along the way. This is <em>not</em> a general purpose method. 1662 * 1663 * @param path Remaining part of the path. Must not be null. 1664 * The expected format is: foo/bar/baz.html#bear 1665 * @param tr The test result object we are storing 1666 * @param suppressScan Request that test finder activity be suppressed. 1667 * @return The test result which was replaced by this operation, null if no 1668 * previous entry existed. 1669 */ 1670 synchronized TestResult insert(TRT_TreeNode node, String path, TestResult tr, 1671 TRT_TreeNode[] rec, boolean suppressScan) { 1672 if (debug > 9) 1673 Debug.println("TRT Beginning insert " + path); 1674 1675 String newPath = behead(path); 1676 1677 if (path == newPath) { 1678 // this should be the test name, make it a leaf 1679 1680 // last parameter allows the TR to be dropped if it does not exist 1681 // in the test suite. 1682 TestResult oldTR = node.addChild(tr, suppressScan, !cacheInitialized); 1683 //tr.setParent(node); // now done in TRT_TreeNode.addChild() 1684 rec = DynamicArray.append(rec, node); 1685 1686 // index will be -1 if the node insertion was rejected 1687 // perhaps upgrade the code so that addChild() throws and 1688 // exception 1689 int index = node.getIndex(tr, suppressScan); 1690 1691 if (oldTR == null) { 1692 if (debug > 10) { 1693 Debug.println(" => Inserted TR: " + tr.getTestName()); 1694 Debug.println(" => Test Ref: " + tr); 1695 Debug.println(" => Status is: " + Status.typeToString(tr.getStatus().getType())); 1696 Debug.println(" => TRT: " + this); 1697 Debug.println(" => Node Ref: " + node); 1698 Debug.println(" => Node path: " + getRootRelativePath(node)); 1699 Debug.println(" => Index in node: " + node.getIndex(tr, suppressScan)); 1700 } // debug 1701 1702 if (index != -1) 1703 notifyNewLeaf(rec, tr, node.getIndex(tr, suppressScan)); 1704 } 1705 else if (oldTR == tr) { 1706 if (debug > 10) { 1707 Debug.println(" => Ignored new TR: " + tr.getTestName()); 1708 Debug.println(" => Test Ref: " + tr); 1709 Debug.println(" => Status is: " + Status.typeToString(tr.getStatus().getType())); 1710 Debug.println(" => RESETTING IT! " + updateInProgress); 1711 } 1712 1713 if (updateInProgress) 1714 resetTest(tr.getTestName()); 1715 } 1716 else { 1717 if (debug > 10) { 1718 Debug.println(" => Updated TR: " + tr.getTestName()); 1719 Debug.println(" => Test Ref: " + tr); 1720 Debug.println(" => Status is: " + Status.typeToString(tr.getStatus().getType())); 1721 Debug.println(" => TRT: " + this); 1722 Debug.println(" => Node Ref: " + node); 1723 Debug.println(" => Node path: " + getRootRelativePath(node)); 1724 Debug.println(" => Index in node: " + index); 1725 } // debug 1726 1727 if (index == -1) { 1728 // insert was ignored for some reason 1729 } 1730 else if (oldTR != null && oldTR != tr) { 1731 // handover known info if new tr is minimal 1732 if (tr.isShrunk()) { 1733 try { 1734 TestDescription desc = oldTR.getDescription(); 1735 if (desc != null) 1736 tr.setTestDescription(desc); 1737 } 1738 catch (TestResult.Fault f) { 1739 // give up 1740 } 1741 } 1742 1743 //notifyChangeLeaf(rec, tr, index, oldTR); 1744 notifyRemoveLeaf(rec, oldTR, index); 1745 notifyNewLeaf(rec, tr, index); 1746 } 1747 else 1748 notifyChangeLeaf(rec, tr, index, oldTR); 1749 } 1750 1751 return oldTR; 1752 } 1753 else { 1754 // has at least 1 dir name left 1755 // find or create a TRT_TreeNode and follow it 1756 1757 String nextDir = getDirName(path); 1758 TRT_TreeNode next = node.getTreeNode(nextDir, suppressScan); 1759 1760 if (next == null) { // create branch 1761 TRT_TreeNode tn = new TRT_TreeNode(this, node); 1762 tn.setName(getDirName(nextDir)); 1763 node.addChild(tn, suppressScan); 1764 1765 rec = DynamicArray.append(rec, tn); 1766 notifyNewBranch(rec, tn, node.getIndex(tn, suppressScan)); 1767 1768 return insert(tn, newPath, tr, rec, suppressScan); 1769 } 1770 else { // no work, just recurse 1771 rec = DynamicArray.append(rec, node); 1772 return insert(next, newPath, tr, rec, suppressScan); 1773 } 1774 1775 } 1776 } 1777 1778 /** 1779 * 1780 * @return true if any refreshing was needed, false otherwise. 1781 */ 1782 private boolean recursiveRefresh(TRT_TreeNode node) { 1783 boolean result = node.refreshIfNeeded(); 1784 1785 TreeNode[] children = node.getTreeNodes(); 1786 if (children != null) 1787 for (int i = 0; i < children.length; i++) 1788 result |= recursiveRefresh((TRT_TreeNode)children[i]); 1789 1790 return result; 1791 } 1792 1793 // package private for TRT_TreeNode for now 1794 // need a better solution 1795 void notifyNewBranch(TreeNode[] where, TreeNode what, int index) { 1796 if (treeObservers == null) return; 1797 1798 for (int i = 0; i < treeObservers.length; i++) { 1799 treeObservers[i].nodeInserted(where, what, index); 1800 } 1801 } 1802 1803 // package private for TRT_TreeNode for now 1804 // need a better solution 1805 void notifyNewLeaf(TreeNode[] where, TestResult what, int index) { 1806 if (treeObservers == null) return; 1807 1808 for (int i = 0; i < treeObservers.length; i++) { 1809 treeObservers[i].nodeInserted(where, what, index); 1810 } 1811 } 1812 1813 private void notifyChangeLeaf(TreeNode[] where, TestResult what, int index, 1814 TestResult old) { 1815 if (treeObservers == null) return; 1816 1817 for (int i = 0; i < treeObservers.length; i++) { 1818 treeObservers[i].nodeChanged(where, what, index, old); 1819 } 1820 } 1821 1822 // package private for TRT_TreeNode for now 1823 // need a better solution 1824 void notifyRemoveLeaf(TreeNode[] where, TestResult what, int index) { 1825 if (treeObservers == null) return; 1826 1827 for (int i = 0; i < treeObservers.length; i++) { 1828 treeObservers[i].nodeRemoved(where, what, index); 1829 } 1830 } 1831 1832 void notifyRemoveLeaf(TreeNode[] where, TreeNode what, int index) { 1833 if (treeObservers == null) return; 1834 1835 for (int i = 0; i < treeObservers.length; i++) { 1836 treeObservers[i].nodeRemoved(where, what, index); 1837 } 1838 } 1839 1840 void notifyStartRefresh(TreeNode origin) { 1841 if (treeObservers == null) return; 1842 1843 for (int i = 0; i < treeObservers.length; i++) { 1844 if (treeObservers[i] instanceof TreeEventObserver) { 1845 ((TreeEventObserver)treeObservers[i]).startRefresh(origin); 1846 } 1847 } 1848 } 1849 void notifyFinishRefresh(TreeNode origin) { 1850 if (treeObservers == null) return; 1851 1852 for (int i = 0; i < treeObservers.length; i++) { 1853 if (treeObservers[i] instanceof TreeEventObserver) { 1854 ((TreeEventObserver)treeObservers[i]).finishRefresh(origin); 1855 } 1856 } 1857 } 1858 1859 /** 1860 * Make all the initial files usable. This method turns the file paths 1861 * into paths relative to the testsuite root. It also removes any files 1862 * which match the testsuite root path. The <tt>getPath()</tt> value is 1863 * used to get the path to resolve. 1864 * 1865 * @param tests Files to resolve. May be zero length or null. 1866 * @return Root relative paths to the given file in internal URL format. 1867 * Null <b>or</b> zero length if the given parameter was an empty 1868 * set, or the given files could not be resolved in this TRT. 1869 * @throws Fault Will occur if any of the initial files are completely invalid. 1870 * validatePath() can be used to validate things in advance. 1871 * @see #validatePath 1872 * @see java.io.File#getPath() 1873 */ 1874 private String[] preProcessInitFiles(File[] tests) throws Fault { 1875 if (tests == null || tests.length == 0) { 1876 if (debug > 1) 1877 Debug.println("Initial files set empty."); 1878 return null; 1879 } 1880 1881 if (debug > 1) { 1882 Debug.println("Initial files: "); 1883 for (int i = 0; i < tests.length; i++) 1884 Debug.println(" + " + tests[i].getPath()); 1885 } 1886 1887 String[] files = new String[tests.length]; 1888 int filesLen = files.length; 1889 int distToDel = 1890 (getWorkDir() == null ? 0 : finder.getRootDir().getAbsolutePath().length() + 1); 1891 1892 for (int i = 0; i < tests.length; i++) { 1893 if (debug > 1) 1894 Debug.println(" *** init url resolve begin ***"); 1895 String relativeURL = null; 1896 1897 if (finder.getRootDir().equals(tests[i])) { 1898 // jtreg produces initial URLs which are equal to the testsuite root, 1899 // this isn't necessary, so we ignore those 1900 // maybe this should trigger a special case which causes all initial 1901 // URLs to be ignored and runs the whole testsuite 1902 filesLen--; 1903 if (debug > 1) 1904 Debug.println("An initial URL equals testsuite root, ignoring it."); 1905 1906 continue; 1907 } 1908 else if (tests[i].isAbsolute()) { 1909 String rrp = getRootRelativePath(getRoot()); 1910 1911 if (debug > 1) { 1912 Debug.println(" -> Initial URL is absolute, stripping from " + 1913 tests[i].getPath()); 1914 Debug.println(" -> Stripping: " + finder.getRootDir()); 1915 Debug.println(" -> removing rrp: " + rrp); 1916 } 1917 1918 String thisInitPath = tests[i].getPath(); 1919 1920 if (!thisInitPath.startsWith(finder.getRootDir().getPath())) { 1921 throw new Fault(i18n, "trt.badInitFile", thisInitPath); 1922 } 1923 1924 distToDel += ((rrp == null || rrp.length() == 0) ? 0 : rrp.length() + 1); 1925 1926 // strip length of testsuite root 1927 String platformPath = thisInitPath.substring(distToDel); 1928 1929 relativeURL = platformPath.replace(File.separatorChar, '/'); 1930 } 1931 else 1932 relativeURL = tests[i].getPath().replace(File.separatorChar, '/'); 1933 1934 files[i] = relativeURL; 1935 } 1936 1937 if (filesLen != tests.length) { 1938 String[] newFiles = new String[filesLen]; 1939 System.arraycopy(files, 0, newFiles, 0, filesLen); 1940 files = newFiles; 1941 } 1942 1943 if (debug > 1) 1944 Debug.println("*** finished preprocessing of init urls ***"); 1945 1946 return files; 1947 } 1948 1949 /** 1950 * @param where Where to start searching. 1951 * @param fullPath Work relative path to JTR to look for. Must not be null. 1952 * @param path Current part of the path, used to support recursion. 1953 * @return null will be returned if the node cannot be located. 1954 */ 1955 private static TestResult findTest(TreeNode where, String fullPath, String path) { 1956 ((TRT_TreeNode)where).scanIfNeeded(); 1957 1958 // this is an internal routine, so params are expected to be non-null 1959 1960 if (debug == 2 || debug == 99) 1961 Debug.println("TRT looking for " + path + " IN " + where.getName()); 1962 1963 String dir = TestResultTable.getDirName(path); 1964 TestResult tr = null; 1965 1966 if (dir == path) { 1967 if (debug == 2 || debug == 99) 1968 Debug.println(" -> Looking for TR in this node."); 1969 1970 int location = ((TRT_TreeNode)where).getResultIndex(fullPath, false); 1971 if (location != -1) { 1972 tr = (TestResult)(where.getChild(location)); 1973 if (debug == 2 || debug == 99) { 1974 Debug.println(" -> TRT.findTest() located " + tr); 1975 Debug.println(""); 1976 } // debug 1977 } 1978 else { 1979 // not found, branches exist, leaf does not 1980 if (debug == 2 || debug == 99) { 1981 Debug.println(" -> TRT.findTest(): unable to find node " + fullPath); 1982 Debug.println(""); 1983 } // debug 1984 } 1985 } 1986 else { 1987 if (debug == 2 || debug == 99) 1988 Debug.println(" -> Looking for branch name: " + dir); 1989 1990 TRT_TreeNode tn = ((TRT_TreeNode)where).getTreeNode(dir, false); 1991 1992 if (tn != null) { 1993 // go down a level 1994 tr = findTest(tn, fullPath, TestResultTable.behead(path)); 1995 } 1996 else { 1997 // not found, a branch name does not exits 1998 if (debug == 2 || debug == 99) 1999 Debug.println("TRT.findTest(): unable to find node " + fullPath); 2000 } 2001 } // outer else 2002 2003 return tr; 2004 } 2005 2006 /** 2007 * Get a node by path. 2008 * @since 3.2 2009 */ 2010 private static Object lookupNode(TreeNode where, String url) { 2011 TreeNode tn = findNode(where, url); 2012 // matched a branch! 2013 if (tn != null) { 2014 return tn; 2015 } 2016 2017 // if url is exec/index.html#ExecSucc 2018 // that should match a test with exactly that name 2019 String jtrPath = TestResult.getWorkRelativePath(url); 2020 TestResult tr = findTest(where, jtrPath, jtrPath); 2021 return tr; // may be null 2022 } 2023 2024 /** 2025 * Find a branch or leaf which matches the URL. 2026 * This method searches in the following order for the given URL: 2027 * <ul> 2028 * <li>Branch match 2029 * <li>Test URL match (up to and including the test id) 2030 * <li>Set of tests match (e.g. multiple test ids, same file) 2031 * </ul> 2032 * 2033 * This method is designed to deprecate lookup(TreeNode, String[]). 2034 * 2035 * @param where Node to start recursive search at. 2036 * @param url The directory name, test URL (TestDescription.getRootRelativeURL), or 2037 * file prefix of a set of test ids (index.html might match {index.html#t1, 2038 * index.html#t2, index.html#t3}) 2039 * @return Null if no matches. A TreeNode, or a non-empty set of TestResults. 2040 */ 2041 private static Object[] lookupInitURL(TreeNode where, String url) { 2042 if (where == null || url == null) 2043 throw new IllegalArgumentException("Starting node or URL may not be null!"); 2044 2045 if (debug == 2 || debug == 99) { 2046 Debug.println("Starting iurl lookup on " + url + " in " + where.getName()); 2047 } 2048 2049 Object simple = lookupNode(where, url); 2050 if (simple != null) { 2051 if (debug == 2 || debug == 99 && simple instanceof TreeNode) { 2052 Debug.println(" -> simple match found " + getRootRelativePath((TreeNode)simple)); 2053 } 2054 2055 if (simple instanceof TestResult) 2056 return new TestResult[] {(TestResult)simple}; 2057 else 2058 return new TreeNode[] {(TreeNode)simple}; 2059 } 2060 2061 // first find the node directly above where we want to search 2062 // if the url is foo/fivetests.html 2063 // that should match foo/fivetests.html#1 2064 // foo/fivetests.html#2 2065 // foo/fivetests.html#3 2066 // foo/fivetests.html#4 2067 // foo/fivetests.html#5 2068 if (debug == 2 || debug == 99) { 2069 Debug.println("TRT looking for tests beginning with " + url + " IN " + where.getName()); 2070 Debug.println(" -> retrieving possible TRs from " + betail(url)); 2071 } 2072 TreeNode tn = findNode(where, betail(url)); 2073 if (tn == null) { // the parent dir of the requested test does not exist 2074 if (debug == 2 || debug == 99) 2075 Debug.println(" -> No parent node found!"); 2076 return null; 2077 } 2078 2079 TestResult[] trs = tn.getTestResults(); 2080 // found anything? 2081 if (trs == null || trs.length == 0) 2082 return null; 2083 2084 // try to partial match a test 2085 Vector<TestResult> v = new Vector<>(); 2086 try { 2087 for (int i = 0; i < trs.length; i++) { 2088 if (trs[i].getDescription().getRootRelativeURL().startsWith(url)) 2089 v.addElement(trs[i]); // match 2090 } // for 2091 } 2092 catch (TestResult.Fault f) { 2093 // this is a very bad thing I think 2094 throw new JavaTestError(i18n, "trt.trNoTd", f); 2095 } 2096 2097 if (v.size() > 0) { 2098 trs = new TestResult[v.size()]; 2099 v.copyInto(trs); 2100 } 2101 else { 2102 // no matches 2103 trs = null; 2104 } 2105 2106 return trs; 2107 } 2108 2109 /** 2110 * Once the finder variable has been set, this should be called to initialize 2111 * the rest of the object state. 2112 */ 2113 private void initFinder() { 2114 suiteRoot = finder.getRoot(); 2115 } 2116 2117 void awakeCache() { 2118 if (trCache == null || !trCache.workerAlive()) { 2119 try { 2120 trCache = new TestResultCache(workDir, updater); 2121 } 2122 catch (IOException e) { 2123 // should consider throwing Fault, but that will destabilize too many APIs for now 2124 updater.error(e); 2125 } 2126 } 2127 } 2128 2129 2130 private boolean needsCacheCompress() { 2131 /*OLD 2132 int etc = workDir.getTestSuiteTestCount(); 2133 2134 if (etc > 0) { 2135 int percent = uncompressedTestCount / etc; 2136 2137 if (percent > COMPRESSION_THRESHOLD) { 2138 return true; 2139 } 2140 } 2141 2142 if (trCache.needsCompress()) 2143 return true; 2144 2145 return false; 2146 */ 2147 return trCache.needsCompress(); 2148 } 2149 2150 protected void finalize() throws Throwable { 2151 super.finalize(); 2152 // cleanup all the http stuff 2153 RootRegistry.getInstance().removeHandler(httpHandle); 2154 RootRegistry.unassociateObject(this, httpHandle); 2155 httpHandle = null; 2156 } 2157 2158 /** 2159 * Update the flag indicating that an update is in progress and notify 2160 * any and all threads that might be interested. 2161 */ 2162 private synchronized void setUpdateInProgress(boolean b) { 2163 updateInProgress = b; 2164 notifyAll(); 2165 } 2166 2167 /** 2168 * This method returns TestResult from map of test results, collected by 2169 * TestResultCache worker. If worker didn't finished his work yet, method 2170 * returns null and adds TestDescription of requested result to a special 2171 * set. 2172 * TestResults from this set will be updated after cache worker finishes 2173 * its work. 2174 */ 2175 public TestResult getCachedResult(TestDescription td) { 2176 if (cachedResults != null) { 2177 String url = TestResult.getWorkRelativePath(td.getRootRelativeURL()); 2178 TestResult res = cachedResults.get(url); 2179 if (res != null) { 2180 res.setTestDescription(td); 2181 } 2182 return res; 2183 } 2184 else { 2185 rtc.addToUpdateFromCache(td); 2186 return null; 2187 } 2188 } 2189 2190 /** 2191 * Inner class, which specifies methods to work with set of TestDescriptions, 2192 * which need to be updated after cache will finish his work. 2193 */ 2194 private class RequestsToCache { 2195 private HashSet<TestDescription> needUpdateFromCache; 2196 2197 public synchronized void addToUpdateFromCache(TestDescription td) { 2198 if (needUpdateFromCache == null) { 2199 needUpdateFromCache = new HashSet<>(); 2200 } 2201 needUpdateFromCache.add(td); 2202 } 2203 2204 public HashSet<TestDescription> getRequests () { 2205 return needUpdateFromCache; 2206 } 2207 2208 public synchronized void clear() { 2209 needUpdateFromCache = null; 2210 } 2211 } 2212 2213 private Map<String, TestResult> cachedResults; 2214 private RequestsToCache rtc; 2215 2216 /** 2217 * Update the flag indicating that the cache has been initialized and notify 2218 * any and all threads that might be interested. 2219 */ 2220 /*OLD 2221 private synchronized void setCacheInitialized(boolean b) { 2222 cacheInitialized = b; 2223 notifyAll(); 2224 } 2225 */ 2226 2227 /*OLD 2228 private static final String formatVersion = "JavaTest/Results/2.0"; 2229 */ 2230 private Map<String, Status>[] statusTables; 2231 // tables indexed by status.type mapping status.reason 2232 // to a unique status object 2233 private WorkDirectory workDir; 2234 private TestFinder finder; 2235 private String[] finderErrors =new String[0]; 2236 private Observer[] observers = new Observer[0]; 2237 private TRT_HttpHandler httpHandle; // the http handler for this instance 2238 private TreeObserver[] treeObservers = new TreeObserver[0]; 2239 private TestResultCache trCache; 2240 2241 private boolean suppressFinderScan = false; // false is traditional 2242 2243 private Updater updater = new Updater(); 2244 2245 /** 2246 * You must hold the lock on this object first, then change this variable, then 2247 * release in the opposite order. 2248 */ 2249 private volatile boolean updateInProgress; 2250 private volatile boolean cacheInitialized = false; 2251 2252 private List<TestResult> testsInUpdate = new Vector<>(); 2253 2254 /* 2255 * Effectively a count of the number of instances of TRTs that have been created. 2256 * It does not represent the number of existing instances though; the 2257 * purpose of the counter is to provide a unique identifier. To use, increment 2258 * the number then use it. 2259 */ 2260 private static int instanceId; 2261 2262 /*OLD 2263 * How much of the testsuite must be run before we force a cache 2264 * compression. 2265 * / 2266 private static float COMPRESSION_THRESHOLD = 0.40f; 2267 */ 2268 2269 private TRT_TreeNode root; 2270 private File suiteRoot; 2271 2272 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestResultTable.class); 2273 2274 // BEGIN INNER CLASSES 2275 // the public interface to a node. impl. is in TRT_TreeNode 2276 /** 2277 * Interface to a node which contitutes the skeleton of the test result tree. 2278 * These are in most cases equivalent to directories or folders. 2279 */ 2280 public interface TreeNode { 2281 /** 2282 * Add a observer for this particular node. 2283 * 2284 * @param obs The observer to attach to this node. Should never be 2285 * null. 2286 */ 2287 public void addObserver(TestResultTable.TreeNodeObserver obs); 2288 2289 /** 2290 * Add a observer for this particular node. 2291 * @param obs The observer to remove. No effect if it was never 2292 * attached. 2293 */ 2294 public void removeObserver(TestResultTable.TreeNodeObserver obs); 2295 2296 /** 2297 * Find out how many nodes are contained in this one and below. 2298 * Use with care, this may be a high overhead call. 2299 * 2300 * @return The number of tests under this node. 2301 */ 2302 public int getSize(); 2303 2304 /** 2305 * Get the parent of this node. 2306 * 2307 * @return Null if this is the root. Another TreeNode otherwise. 2308 */ 2309 public TreeNode getParent(); 2310 2311 /** 2312 * Is this the root of a tree. 2313 * A parent of null indicates this state. 2314 * 2315 * @return True if this is the root node, false otherwise. 2316 */ 2317 public boolean isRoot(); 2318 2319 /** 2320 * Find out what TestResultTable this node is part of. 2321 * 2322 * @return The TRT instance which this node is contained. 2323 */ 2324 public TestResultTable getEnclosingTable(); 2325 2326 /** 2327 * Has the finder been used to scan this node from secondary storage?. 2328 * This is an important performance consideration, since reading nodes 2329 * may cause a noticable pause. This method may allow you to defer 2330 * performance penalties. This method has no effect if a finder is not 2331 * being used by the TestResultTable. 2332 * 2333 * @return True if this location in tree has already been processed, 2334 * false otherwise. 2335 */ 2336 public boolean isUpToDate(); 2337 2338 /** 2339 * Find out how many children this node contains. 2340 * If you invoke this on a node which is being lazily read from a 2341 * TestFinder, this may cause a synchronous retrieval of data from the 2342 * TestFinder. 2343 * 2344 * @return The number of immediate children this node has. 2345 */ 2346 public int getChildCount(); 2347 2348 /** 2349 * Get the child at the specified location. 2350 * May be either a TestResult or TreeNode. 2351 * If you invoke this on a node which is being lazily read from a 2352 * TestFinder, this may cause a synchronous retrieval of data from the 2353 * TestFinder. 2354 * 2355 * @param index The location to retrieve. 2356 * @return Null if there are no children here or the specified index if out of 2357 * range. 2358 */ 2359 public Object getChild(int index); 2360 2361 /** 2362 * Get any immediate children of this node which are test result objects. 2363 * 2364 * @return List of TestResult objects in this node. null if none 2365 */ 2366 public TestResult[] getTestResults(); 2367 2368 /** 2369 * Get any immediate children of this node which are tree nodes. 2370 * 2371 * @return List of children nodes objects in this node. null if none 2372 */ 2373 public TreeNode[] getTreeNodes(); 2374 2375 /** 2376 * The name of this node, not including all the ancestors names. 2377 * This does not return a URL to this node from the root. 2378 * 2379 * @return Immediate name of this node, not the full name. 2380 * @see #getRootRelativePath 2381 */ 2382 public String getName(); 2383 2384 /** 2385 * Is the given element of this node a leaf. 2386 * In general this means that the element is a TestResult. It may also 2387 * mean that the TreeNode is empty, which indicates a testsuite which is not 2388 * well designed and needs trimming. 2389 * 2390 * @param index The element index of this node. An out of range index 2391 * will return false. 2392 * @return True if the element at the given index is a leaf. 2393 */ 2394 public boolean isLeaf(int index); 2395 2396 /** 2397 * Get the statistics for the state of tests under this node. 2398 * 2399 * @return An array of length Status.NUM_STATES, positionally representing 2400 * the number of tests under this node with that state. 2401 */ 2402 public int[] getChildStatus(); 2403 2404 /** 2405 * Search for a specific item in this node. 2406 * 2407 * @param target The target object should either be of type TreeNode or TestResult 2408 * @return The index at which the target object is located in this node. 2409 * -1 if not found in in this node. -2 if the parameter is null 2410 */ 2411 public int getIndex(Object target); 2412 2413 /** 2414 * Finds a TestResult in this node with the given name. 2415 * This is a match against the test URL, not the filename. This is not 2416 * recursive of course; it just does a straight match against all tests 2417 * in this node. 2418 * 2419 * @param url The full name of the test to find. 2420 * @return The matching test result object, or null if not found. Also 2421 * null if this node has no children. 2422 * @see com.sun.javatest.TestDescription#getRootRelativeURL() 2423 */ 2424 public TestResult matchTest(String url); 2425 } // TreeNode 2426 2427 private static class NullEnum extends TRT_Iterator { 2428 private NullEnum() { 2429 super(); 2430 } 2431 2432 // --- Iterator interface --- 2433 public boolean hasNext() { 2434 return hasMoreElements(); 2435 } 2436 2437 public TestResult next() { 2438 return nextElement(); 2439 } 2440 2441 /** 2442 * Do not call this method. 2443 * 2444 * @throws UnsupportedOperationException Not available for this iterator. 2445 */ 2446 public void remove() { 2447 throw new UnsupportedOperationException("Cannot remove from TestResultTable thhrough iterator. Do not call this method."); 2448 } 2449 2450 // --- Enumerator interface --- 2451 public boolean hasMoreElements() { 2452 return false; 2453 } 2454 2455 public TestResult nextElement() { 2456 throw new NoSuchElementException(i18n.getString("trt.noElements")); 2457 } 2458 2459 public static NullEnum getInstance() { 2460 if (instance == null) instance = new NullEnum(); 2461 2462 return instance; 2463 } 2464 2465 private static NullEnum instance; 2466 } // NullEnum 2467 2468 /** 2469 * Defines an iterator/enumerator interface for retrieving tests out of the 2470 * tree. This is a read-only interface, so <code>remove()</code> is not 2471 * supported. 2472 */ 2473 public interface TreeIterator extends Enumeration<TestResult>, Iterator<TestResult> { 2474 // --- Enumerator interface --- 2475 public abstract boolean hasMoreElements(); 2476 public abstract TestResult nextElement(); 2477 2478 // --- Iterator interface --- 2479 public abstract boolean hasNext(); 2480 public abstract TestResult next(); 2481 2482 /** 2483 * Do not call this method. 2484 * 2485 * @throws UnsupportedOperationException Not available for this iterator. 2486 */ 2487 public abstract void remove(); 2488 2489 // --- Statistics info --- 2490 /** 2491 * Find out how many tests were rejected by filters while doing iteration. 2492 * This number will be available despite the setting on setRecordRejects(). 2493 * 2494 * @return The number of tests found by rejected by the filters. The 2495 * value will be between zero and max. int. 2496 */ 2497 public abstract int getRejectCount(); 2498 2499 /** 2500 * Should the rejected tests be tracked. 2501 * The default is to not record this info, activating this feature will 2502 * make <tt>getFilterStats()</tt> return useful info. The setting can 2503 * be changed at any time, but never resets the statistics. The 2504 * recorded statistics represent iterator output during this object's 2505 * lifetime, while this feature was enabled. The setting here does not 2506 * affect information from <tt>getRejectCount()</tt> 2507 * @param state True to activate this feature, false to disable. 2508 * @see com.sun.javatest.TestResultTable.TreeIterator#getFilterStats() 2509 */ 2510 public abstract void setRecordRejects(boolean state); 2511 2512 /** 2513 * Find out which states the test which have been enumerated already were in. 2514 * The result is valid at any point in time, and represent the stats for the 2515 * entire selected set of tests when hasMoreElements() is false. 2516 * 2517 * @return Indexes refer to those values found in Status 2518 * @see com.sun.javatest.Status 2519 */ 2520 public abstract int[] getResultStats(); 2521 2522 /** 2523 * Find out which filters rejected which tests. 2524 * The data is valid at any point in time; hasNext() does not have to 2525 * be false. Note that filters are evaluated in the order shown in getFilters() 2526 * and that the statistics only registered the <em>first</em> filter that rejected 2527 * the test; there may be additional filters which would also reject any given 2528 * test. 2529 * <p> 2530 * The hashtable has keys of TestResults, and values which are TestFilters. 2531 * Because of CompositeFilters, the set of filters found in the ``values'' 2532 * is not necessarily equivalent to those given by getFilters(). 2533 * 2534 * @return Array as described or null if no tests have been rejected yet. 2535 * @since 3.0.3 2536 */ 2537 public abstract HashMap<TestFilter, ArrayList<TestDescription>> getFilterStats(); 2538 2539 // --- misc info --- 2540 /** 2541 * Find out what the effective filters are. 2542 * 2543 * @return Null if there are no active filters. 2544 */ 2545 public abstract TestFilter[] getFilters(); 2546 2547 /** 2548 * Find out what the effective initial URLs for this enumerator are. 2549 * The returned array can be any combination of URLs to individual tests 2550 * or URLs to directories. Remember these are URLs, so the paths are not 2551 * platform specific. 2552 * 2553 * @return Null if no nodes or tests were found. Any array of the initial 2554 * URLs otherwise. 2555 */ 2556 public abstract String[] getInitialURLs(); 2557 2558 /** 2559 * Peek into the future to see which object will be returned next. 2560 * @return The next object scheduled to come out of <code>next()</code>, 2561 * or null if <code>hasNext()</code> is false. 2562 */ 2563 public abstract Object peek(); 2564 2565 /** 2566 * Will the iterator be returning the given node later. 2567 * There is no checking to ensure that the parameter is within the 2568 * iterator's "address space". The comparison is not reference based, 2569 * but location based, so, will the test at the location indicated by 2570 * the given test be evaluated for iterator later? This query is done 2571 * without respect to the filters. 2572 * @param node The test result indicating the location in question. 2573 * @return True if the test in question has not been passed by the 2574 * iterator and may still be returned. False if the given 2575 * test will not subsequently be returned by this iterator. 2576 */ 2577 public abstract boolean isPending(TestResult node); 2578 } // TreeIterator 2579 2580 class Updater implements TestResultCache.Observer 2581 { 2582 //-----methods from TestResultCache.Observer----- 2583 public void update(Map<String, TestResult> tests) { 2584 updateFromCache(tests); 2585 } 2586 2587 public void waitingForLock(long timeSoFar) { 2588 // in time, could propogate this message to TRT.Observer so that 2589 // GUI code could present the info better, but, for now, stay basic 2590 int seconds = (int) (timeSoFar/1000); 2591 int minutes = seconds/60; 2592 seconds = seconds - 60*minutes; 2593 writeI18N("trt.waitingForLock", 2594 new Object[] { workDir.getRoot(), new Integer(minutes), new Integer(seconds) }); 2595 } 2596 2597 public void timeoutWaitingForLock() { 2598 // in time, could propogate this message to TRT.Observer so that 2599 // GUI code could present the info better, but, for now, stay basic 2600 writeI18N("trt.timeoutForLock", workDir.getRoot()); 2601 } 2602 2603 public void acquiredLock() { 2604 // in time, could propogate this message to TRT.Observer so that 2605 // GUI code could present the info better, but, for now, stay basic 2606 } 2607 2608 public void releasedLock() { 2609 // in time, could propogate this message to TRT.Observer so that 2610 // GUI code could present the info better, but, for now, stay basic 2611 } 2612 2613 public void buildingCache(boolean reset) { 2614 // in time, could propogate this message to TRT.Observer so that 2615 // GUI code could present the info better, but, for now, stay basic 2616 rebuildCount = 0; 2617 } 2618 2619 public void buildingCache(TestResult tr) { 2620 // in time, could propogate this message to TRT.Observer so that 2621 // GUI code could present the info better, but, for now, stay basic 2622 rebuildCount++; 2623 if (rebuildCount == 100) 2624 writeI18N("trt.rebuild"); 2625 else if ((rebuildCount % 100) == 0) 2626 System.err.println("."); 2627 } 2628 2629 public void builtCache() { 2630 // in time, could propogate this message to TRT.Observer so that 2631 // GUI code could present the info better, but, for now, stay basic 2632 if (rebuildCount > 0) 2633 System.err.println(); 2634 } 2635 2636 public void error(Throwable t) { 2637 // in time, could propogate this message to TRT.Observer so that 2638 // GUI code could present the info better, but, for now, stay basic 2639 writeI18N("trt.cacheError", t); 2640 t.printStackTrace(); 2641 } 2642 2643 private void writeI18N(String key) { 2644 System.err.println(i18n.getString(key)); 2645 } 2646 2647 private void writeI18N(String key, Object arg) { 2648 System.err.println(i18n.getString(key, arg)); 2649 } 2650 2651 private void writeI18N(String key, Object[] args) { 2652 System.err.println(i18n.getString(key, args)); 2653 } 2654 2655 private int rebuildCount; 2656 2657 /*OLD 2658 public void newEntry(TestResult result) { 2659 if (debug > 9) 2660 Debug.println("TRT - New TR from cache: " + result.getWorkRelativePath()); 2661 2662 if (testsInUpdate.contains(result)) { 2663 // this TRT instance just inserted this test 2664 if (debug > 9) 2665 Debug.println(" -> ignoring"); 2666 2667 return; 2668 } 2669 2670 if (result != null) 2671 insert(result); 2672 } 2673 */ 2674 2675 /* 2676 public void cacheResetting() { 2677 if (debug) 2678 Debug.println("TRT - cache indicates restart beginning."); 2679 } 2680 */ 2681 2682 /*OLD 2683 public void resetCache() { 2684 synchronized (TestResultTable.this) { 2685 while (updateInProgress) { 2686 try { 2687 TestResultTable.this.wait(3000); // will be notified when updated; time allows debug monitor 2688 2689 // abort 2690 if (cacheShutdown) 2691 return; 2692 } 2693 catch (InterruptedException e) { 2694 if (debug > 0) 2695 e.printStackTrace(Debug.getWriter()); 2696 } // catch 2697 } // while 2698 2699 setUpdateInProgress(true); 2700 } 2701 2702 // hope this doesn't happen too often, because it is really 2703 // expensive 2704 if (debug > 0) 2705 Debug.println("TRT(" +TestResultTable.this+") - Received cache reset message."); 2706 2707 // NOTE: this will result in flushing the pre-existing test 2708 // results which may contain full JTR info. getEntries() 2709 // always produces minimal size TR objects 2710 2711 Thread updateThread = new Thread() { 2712 { setName("TRT Background Cache Reset"); } 2713 2714 public void run() { 2715 // too expensive to do in the foreground 2716 try { 2717 final Set newSet = trCache.getEntries(); 2718 2719 // abort if not needed 2720 if (cacheShutdown) { 2721 setUpdateInProgress(false); 2722 return; 2723 } 2724 2725 update(newSet); 2726 } 2727 catch (TestResultCache.CacheShutdownFault f) { 2728 // ignore, handled below... 2729 } 2730 catch (TestResultCache.Fault f) { 2731 if (debug > 0) 2732 f.printStackTrace(); 2733 throw new JavaTestError(i18n, "trt.noEntries", f); 2734 } // catch 2735 2736 setUpdateInProgress(false); 2737 2738 } 2739 2740 protected void finalize() throws Throwable { 2741 setUpdateInProgress(false); 2742 } 2743 }; // anonymous thread 2744 2745 // abort if not needed 2746 if (cacheShutdown) { 2747 return; 2748 } 2749 2750 updateThread.setPriority(Thread.MIN_PRIORITY + 2); 2751 updateThread.start(); 2752 } 2753 */ 2754 2755 /*OLD 2756 public void cacheShutdown() { 2757 cacheShutdown = true; 2758 } 2759 */ 2760 } 2761 2762 /** 2763 * Keeps track of the path to a specific node in the TestResultTable tree. 2764 * This class is made available so that a path which consists of zero or more 2765 * TreeNodes plus zero or one TestResult can be represented without using a 2766 * Object[]. 2767 */ 2768 public class PathRecord { 2769 PathRecord() { 2770 } 2771 2772 /** 2773 * Create a path with only one element, the target TestResult. 2774 */ 2775 PathRecord(TestResult tr) { 2776 this.tr = tr; 2777 } 2778 2779 /** 2780 * Create a path which represents the path to a leaf node, the TestResult. 2781 */ 2782 PathRecord(TreeNode[] path, TestResult tr) { 2783 this.tr = tr; 2784 nodes = path; 2785 } 2786 2787 /** 2788 * @param path The TreeNode objects that correspond to the path. 2789 * @param inds The indexes of each TreeNode in the tree. 2790 * This is really duplicated information, but will remove the 2791 * need to search for a node at each level. 2792 */ 2793 PathRecord(TreeNode[] path, int[] inds) { 2794 nodes = path; 2795 this.inds = inds; 2796 } 2797 2798 /** 2799 * The end of the path. 2800 */ 2801 void setTestResult(TestResult tr) { 2802 this.tr = tr; 2803 } 2804 2805 /** 2806 * @exception JavaTestError Will be thrown if you attempt to add a node to a 2807 * path that already has a leaf (TestResult) assigned. 2808 */ 2809 void addNode(TreeNode tn) { 2810 if (tr != null) throw new JavaTestError(i18n, "trt.invalidPath"); 2811 2812 nodes = DynamicArray.append(nodes, tn); 2813 } 2814 2815 /* 2816 public synchronized void append(Object node, int index) { 2817 int[] newArr = new int[inds.length+1]; 2818 System.arraycopy(inds, 0, newArr, 0, inds.length); 2819 inds = newArr; 2820 2821 nodes = DynamicArray.append(nodes, node); 2822 2823 System.out.println("Path Recorded"); 2824 for (int i = 0; i < nodes.length; i++) 2825 System.out.println(nodes[i] + " " + inds[i]); 2826 } 2827 */ 2828 2829 /** 2830 * Provides the indexes into each node provided by <code>getNodes()</code>. 2831 * 2832 * @return The indexes of the corresponding TreeNode at each level. Null if 2833 * no index information is available; 2834 */ 2835 public int[] getIndicies() { 2836 if (inds == null) { 2837 inds = generateInds(tr); 2838 } 2839 2840 return inds; 2841 } 2842 2843 /** 2844 * Get the nodes that represent the path. 2845 * 2846 * @return The path, closest to the root at the beginning of the array. 2847 */ 2848 public TreeNode[] getNodes() { 2849 if (nodes == null) { 2850 nodes = generateNodes(tr); 2851 } 2852 2853 return nodes; 2854 } 2855 2856 /** 2857 * Generate the path to a given test. 2858 * 2859 * @param tr The test to generate the path to. 2860 * @return The path that leads to the given test. 2861 */ 2862 public /* static */ TreeNode[] generateNodes(TestResult tr) { 2863 if (tr == null) return null; 2864 2865 TreeNode[] nodes = null; 2866 TreeNode node = tr.getParent(); 2867 2868 while (node != null) { 2869 nodes = DynamicArray.insert(nodes, node, 0); 2870 node = node.getParent(); 2871 } 2872 2873 return nodes; 2874 } 2875 2876 private /* static */ int[] generateInds(TestResult tr) { 2877 // XXX implement me! 2878 return null; 2879 } 2880 2881 private TreeNode[] nodes; 2882 private int[] inds; 2883 private TestResult tr; 2884 } // PathRecord 2885 2886 /** 2887 * Remove one directory from the beginning of the path. 2888 * 2889 * @param path The path to manipulate. 2890 * @return Beheaded path, or the <b>same</b> object if there is 2891 * no leading directory to strip. 2892 */ 2893 static String behead(String path) { 2894 int index = path.indexOf("/"); 2895 2896 if (index == -1) 2897 return path; 2898 else { 2899 // assume file separator is 1 char 2900 return path.substring(index+1); 2901 //return path.substring(index+File.separator.length()); 2902 } 2903 } 2904 2905 /** 2906 * Gives the first directory name in the path. 2907 * 2908 * @param path The path to manipulate. 2909 */ 2910 static String getDirName(String path) { 2911 int index = path.indexOf('/'); 2912 2913 if (index == -1) 2914 return path; 2915 else 2916 return path.substring(0, index); 2917 } 2918 2919 /** 2920 * Opposite of behead - removes the last filename. 2921 * 2922 * @param path The path to manipulate. 2923 */ 2924 static String betail(String path) { 2925 int index = path.lastIndexOf('/'); 2926 2927 if (index == -1) 2928 return path; 2929 else 2930 return path.substring(0, index); 2931 } 2932 2933 /** 2934 * Does the given array contain the given object. 2935 * @return True if o is in arr, false if arr is null or zero length,or 2936 * does not contain o. 2937 */ 2938 static boolean arrayContains(Object[] arr, Object o) { 2939 if (arr == null || arr.length == 0) 2940 return false; 2941 else { 2942 for (int i = 0; i < arr.length; i++) 2943 if (arr[i] == o) 2944 return true; 2945 } 2946 2947 return false; 2948 } 2949 2950 private ReentrantLock processLock = new ReentrantLock(); 2951 2952 public ReentrantLock getLock() { 2953 return processLock; 2954 } 2955 2956 static final Status notYetRun = Status.notRun("test is awaiting execution"); 2957 private static int debug = Debug.getInt(TestResultTable.class); 2958 }