1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2001, 2011, 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.exec; 28 29 import java.util.Hashtable; 30 import java.util.Vector; 31 32 import com.sun.javatest.Status; 33 import com.sun.javatest.TestDescription; 34 import com.sun.javatest.TestFilter; 35 import com.sun.javatest.TestResult; 36 import com.sun.javatest.TestResultTable; 37 import com.sun.javatest.util.Debug; 38 import com.sun.javatest.util.DynamicArray; 39 import java.util.logging.Level; 40 import java.util.logging.Logger; 41 42 /** 43 * Cached information about a particular tree node. 44 * We want to collect pass/fail stats and other variable information so it can 45 * be used to render a node quickly when requested by JTree. Code here should 46 * never run on the GUI thread. 47 * 48 * <p> 49 * Objects of this type run to completion and are then immutable. 50 */ 51 class TT_NodeCache implements Runnable { 52 53 /** 54 * Construct a cache object which will collect info about the given node 55 * with respect to the supplied filter. 56 * 57 * @param n Node to examine, should not be null. 58 * @param f Filter to use when producing information. Can be null. 59 */ 60 TT_NodeCache(TestResultTable.TreeNode n, TestFilter f, Logger l) { 61 filter = f; 62 node = n; 63 log = l; 64 65 // all the states plus filtered out 66 testLists = new Vector[Status.NUM_STATES + 1]; 67 for (int i = 0; i < Status.NUM_STATES + 1; i++) { 68 testLists[i] = new Vector<>(); 69 } 70 } 71 72 /** 73 * Start or resume processing. 74 */ 75 public void run() { 76 if (debug) { 77 Debug.println("TT_NodeCache starting"); 78 Debug.println(" -> " + this); 79 Debug.println(" -> node " + node + "(" + node.getName() + ")"); 80 Debug.println(" -> filter=" + filter); 81 Debug.println(" -> old state=" + state); 82 } 83 84 // needed for a new run, a paused object will already have one 85 if (it == null) { 86 it = init(); 87 } 88 89 if (debug) { 90 Debug.println(" -> iterator= " + it); 91 } 92 93 state = COMPUTING; 94 95 process(); 96 } 97 98 /** 99 * Pause processing, and return immediately. 100 * If the node has completed processing, calling this method has no 101 * effect. 102 * Be careful with MT activities when using <tt>pause() resume() isPaused()</tt>. 103 * @see #resume() 104 */ 105 void pause() { 106 if (state < COMPLETED) { 107 state = PAUSED; 108 if (debug) { 109 Debug.println("TT_NodeCache for " + node.getName() + " pausing..."); 110 } 111 } 112 } 113 114 /** 115 * Continue processing after a pause. 116 * @see #pause 117 * @throws IllegalStateException if <tt>pause()</tt> was not previously called. 118 */ 119 void resume() { 120 if (state != PAUSED) { 121 throw new IllegalStateException("Cache node not previously paused."); 122 } 123 if (debug) { 124 Debug.println("TT_NodeCache for " + node.getName() + " resuming..."); 125 } 126 state = COMPUTING; 127 process(); 128 } 129 130 /** 131 * @return True if the replacement has an effect on the statistics. False 132 * if it does not. 133 */ 134 synchronized boolean add(TestResultTable.TreeNode[] path, TestResult what, int index) { 135 boolean result = false; 136 boolean wouldAccept = false; // special case because of filtering 137 boolean needsProcessing = false; 138 139 // not even running yet 140 if (it == null) { 141 return false; 142 } 143 144 try { 145 TestDescription td = what.getDescription(); 146 TestFilter rejector = null; 147 148 synchronized (fObs) { 149 wouldAccept = filter.accepts(what, fObs); 150 if (!wouldAccept && fObs.lastTd == td) { 151 rejector = fObs.lastRejector; 152 fObs.clear(); 153 } 154 } // sync 155 156 needsProcessing = (!it.isPending(what)); 157 158 if (!needsProcessing) { 159 // It's still going to come out of the iterator. 160 // Make sure it's not the one which is committed to be next. 161 // Would be nice to work around this in a different way, either 162 // here or in the iterator. 163 Object peek = it.peek(); 164 if (peek instanceof TestResult && 165 ((TestResult) peek).getTestName().equals(what.getTestName())) { 166 it.next(); // consume 167 needsProcessing = true; 168 } else { 169 } 170 } else { 171 } 172 173 if (needsProcessing) { 174 if (!wouldAccept) { 175 // add to filtered out list 176 localRejectCount++; 177 testLists[testLists.length - 1].add(what); 178 rejectReasons.put(what, rejector); 179 } else { 180 int type = what.getStatus().getType(); 181 182 // unfortunately the add/remove messages don't seem to 183 // be 100% symmetric, or else there is some other race 184 // condition. I tried to find the problem, but 0-5 tests out 185 // of 10000 will end up added twice when loading a workdir at 186 // startup. 12/9/2002 187 if (!testLists[type].contains(what)) { 188 stats[type]++; 189 testLists[type].add(what); 190 } else { 191 } 192 } 193 194 result = true; 195 } else { // will be counted later... 196 result = false; 197 } 198 199 // send out notifications if needed 200 notify((wouldAccept ? what.getStatus().getType() + TT_NodeCacheObserver.OFFSET_FROM_STATUS 201 : TT_NodeCacheObserver.MSGS_FILTERED), 202 true, path, what, index); 203 204 if (result) { 205 notifyStats(); 206 } 207 } catch (TestResult.Fault f) { 208 209 //if (debug) { 210 // f.printStackTrace(Debug.getWriter()); 211 // Debug.println(msg); 212 // } 213 214 if (log != null && log.isLoggable(Level.SEVERE)) { 215 String msg = "TT_NodeCache - TR fault, purging old info. " + what.getTestName(); 216 log.log(Level.SEVERE, msg, f); 217 } 218 219 // TR is somehow corrupt, remove it 220 node.getEnclosingTable().resetTest(what.getTestName()); 221 result = false; 222 } catch (TestFilter.Fault f) { 223 224 f.printStackTrace(Debug.getWriter()); 225 226 if (log != null && log.isLoggable(Level.SEVERE)) { 227 log.log(Level.SEVERE, "TT_NodeCache - filter is broken", f); 228 } 229 230 // filter is broken, shove everything into filtered out. 231 // this behavior is similar to what would happen in the same 232 // case when the harness is iterating to select tests to run. 233 // trying to only do this if counting of this test would have 234 // been necessary. 235 if (needsProcessing) { 236 localRejectCount++; 237 testLists[testLists.length - 1].add(what); 238 // this reject will not have a reason entry in 239 // rejectReasons 240 } 241 242 // ignore error and don't do anything 243 result = false; 244 } 245 246 return result; 247 } 248 249 /** 250 * @return True if the replacement has an effect on the statistics. False 251 * if it does not. 252 */ 253 synchronized boolean remove(TestResultTable.TreeNode[] path, TestResult what, int index) { 254 boolean result = false; 255 // special case because of filtering 256 257 // not even running yet 258 if (it == null) { 259 return false; 260 } 261 262 int type = what.getStatus().getType(); 263 264 if (!it.isPending(what)) { // check iterator position 265 int[] rmList = locateTestInLists(what, type, -1); 266 267 if (rmList[0] != -1) { 268 testLists[rmList[0]].remove(rmList[1]); 269 270 // decrement counter 271 if (rmList[0] < stats.length) { 272 stats[rmList[0]]--; 273 } else { 274 localRejectCount--; 275 } 276 277 // send out notifications if needed 278 notify(rmList[0] + TT_NodeCacheObserver.OFFSET_FROM_STATUS, false, path, what, index); 279 result = true; 280 } else { 281 } 282 } else { // pending in iterator 283 Object peek = it.peek(); 284 if (peek instanceof TestResult && 285 ((TestResult) peek).getTestName().equals(what.getTestName())) { 286 it.next(); // consume 287 288 // do it again to make it happen 289 result = remove(path, what, index); 290 } 291 292 } 293 294 if (result) { 295 notifyStats(); 296 } 297 298 return result; 299 } 300 301 /** 302 * @return True if the replacement has an effect on the statistics. False 303 * if it does not. 304 */ 305 synchronized boolean replace(TestResultTable.TreeNode[] path, TestResult what, 306 int index, TestResult old) { 307 boolean result = false; 308 // not even running yet 309 if (it == null) { 310 return false; 311 } 312 313 if (!it.isPending(what)) { // check iterator position 314 int typeNew = what.getStatus().getType(); 315 int typeOld = old.getStatus().getType(); 316 317 // filtering of old does not work because of the status filter 318 boolean wouldAcceptNew = false; 319 TestFilter rejector = null; 320 321 try { 322 TestDescription td = what.getDescription(); 323 324 synchronized (fObs) { 325 wouldAcceptNew = filter.accepts(what, fObs); 326 if (!wouldAcceptNew && fObs.lastTd == td) { 327 rejector = fObs.lastRejector; 328 fObs.clear(); 329 } 330 } // sync 331 } // try 332 catch (TestResult.Fault f) { 333 334 f.printStackTrace(Debug.getWriter()); 335 if (log != null && log.isLoggable(Level.SEVERE)) { 336 log.log(Level.SEVERE, "TT_NodeCache - problem with test result", f); 337 } 338 339 // ignore error and don't do anything 340 return false; 341 } // catch 342 catch (TestFilter.Fault f) { 343 344 f.printStackTrace(Debug.getWriter()); 345 if (log != null && log.isLoggable(Level.SEVERE)) { 346 log.log(Level.SEVERE, "TT_NodeCache - filter is broken", f); 347 } 348 349 350 // ignore error and don't do anything 351 return false; 352 } // catch 353 354 // inserting into one of the status lists or the filtered out list 355 int targetList = (wouldAcceptNew ? typeNew : testLists.length - 1); 356 int[] rmList = null; 357 358 // optimization to search based on expected location of old test 359 // and optimize out null changes 360 if (what == old) { 361 if (typeOld != typeNew) { // special case optimization 362 // no change to lists 363 rmList = new int[]{-1, -1}; 364 } else { 365 rmList = locateTestInLists(old, Status.NOT_RUN, targetList); 366 367 if (rmList[0] == targetList) { 368 // same TR object, same status 369 // use -1 to specify no remove action 370 rmList[0] = -1; 371 rmList[1] = -1; 372 } 373 } 374 } else { 375 rmList = locateTestInLists(old, typeOld, targetList); 376 } 377 378 if (rmList[0] != -1) { 379 testLists[rmList[0]].remove(rmList[1]); 380 testLists[targetList].add(what); 381 382 // decrement counter 383 if (rmList[0] < stats.length) { 384 stats[rmList[0]]--; 385 } else { 386 localRejectCount--; 387 rejectReasons.remove(what); 388 } 389 390 // increment counter 391 if (targetList < stats.length) { 392 stats[targetList]++; 393 } else { 394 localRejectCount++; 395 rejectReasons.put(what, rejector); 396 } 397 398 // remove the old 399 // add the new 400 notify(rmList[0] + TT_NodeCacheObserver.OFFSET_FROM_STATUS, 401 false, path, old, index); 402 notify(targetList + TT_NodeCacheObserver.OFFSET_FROM_STATUS, 403 true, path, what, index); 404 405 result = true; 406 } else { 407 // must be a null change 408 } 409 } else // this TR has yet to be processed 410 { 411 result = false; 412 } 413 414 if (result) { 415 notifyStats(); 416 } 417 418 return result; 419 } 420 421 boolean isPaused() { 422 return (state == PAUSED); 423 } 424 425 /** 426 * Is this object eligible to run. 427 * This translates to being either unprocessed or paused. 428 */ 429 boolean canRun() { 430 return ((state == PAUSED || state == NOT_COMPUTED) && 431 valid == true); 432 } 433 434 void halt() { 435 if (debug) { 436 Debug.println("TT_NodeCache thread stopping"); 437 Debug.println(" -> " + this); 438 } 439 440 state = ABORTED; 441 valid = false; 442 } 443 444 /** 445 * Is info still being collected? 446 * The state is not considered active if the processing is paused. 447 * 448 * @return True if information is incomplete, false otherwise. 449 * @see #isPaused() 450 */ 451 boolean isActive() { 452 return (state == COMPUTING); 453 } 454 455 /** 456 * Has all the information been collected. 457 * @return True if all information is up to date, and will not change 458 * unless this node is invalidated. 459 */ 460 boolean isComplete() { 461 return (state == COMPLETED); 462 } 463 464 boolean isAborted() { 465 return (state == ABORTED); 466 } 467 468 /** 469 * Invalidate any information in this node cache. 470 * @see #isValid() 471 */ 472 void invalidate() { 473 valid = false; 474 } 475 476 /** 477 * Has the data in this node been invalidated. 478 * A node may be valid while it is active, but becomes invalid when 479 * notified that the constraints that it is executing with are no longer 480 * correct. It may also become invalid if the thread is interrupted. 481 * @see #invalidate() 482 */ 483 boolean isValid() { 484 return valid; 485 } 486 487 TestResultTable.TreeNode getNode() { 488 return node; 489 } 490 491 TestFilter getFilter() { 492 return filter; 493 } 494 495 /** 496 * Get the pass fail error notrun stats. 497 * The data may be in flux if the data is still being collected, use 498 * <tt>isActive()</tt> to anticipate this. 499 * @return An array of size Status.NUM_STATES. This is not a copy, do not 500 * alter. 501 * @see #isActive() 502 * @see com.sun.javatest.Status#NUM_STATES 503 */ 504 int[] getStats() { 505 return stats; 506 } 507 508 /** 509 * Find out how many tests were rejected by filters. 510 * @return Number of rejected tests found in and below this node. 511 */ 512 int getRejectCount() { 513 if (it != null) { 514 return it.getRejectCount() + localRejectCount; 515 } else { 516 return localRejectCount; 517 } 518 } 519 520 TestFilter getRejectReason(TestResult tr) { 521 return rejectReasons.get(tr); 522 } 523 524 /** 525 * Snapshot the current data and add an observer. 526 * This is an atomic operation so that you can get completely up to date 527 * and monitor all changes going forward. 528 * 529 * @param obs The observer to attach. Must not be null. 530 * @param needSnapshot Does the caller want a snapshot of the current test lists. 531 * True if yes, false if not. 532 * @return A copy of the Vectors that contain the current list of tests. Null if 533 * <tt>needSnapshot</tt> is false. 534 */ 535 synchronized Vector<TestResult>[] addObserver(TT_NodeCacheObserver obs, boolean needSnapshot) { 536 // snapshot the current data 537 // must be done before adding the observer to ensure correct data 538 // delivery to client 539 540 Vector<TestResult>[] cp = null; 541 if (needSnapshot) { 542 cp = new Vector[testLists.length]; 543 for (int i = 0; i < testLists.length; i++) { 544 cp[i] = (Vector<TestResult>) (testLists[i].clone()); 545 } 546 } 547 548 if (obs != null) { 549 observers = DynamicArray.append(observers, obs); 550 } 551 552 return cp; 553 } 554 555 // advisory - many thread access this. you should lock this object before 556 // calling. See BP_TestListSubpanel.reset(TT_NodeCache), which locks this, 557 // THEN itself (the GUI component) for proper locking sequence, since the 558 // highest contention is for this cache object. 559 synchronized void removeObserver(TT_NodeCacheObserver obs) { 560 observers = DynamicArray.remove(observers, obs); 561 } 562 563 // ------------- PRIVATE ----------------- 564 private void process() { 565 final TestResultTable trt = node.getEnclosingTable(); 566 if (trt == null) { 567 return; 568 } 569 try { 570 trt.getLock().lock(); 571 while (state != ABORTED && state != PAUSED && it.hasNext()) { 572 try { 573 synchronized (node) { // to maintain locking order 574 synchronized (this) { // sync to lockout during add/remove/replace 575 if (!it.hasNext()) // need to recheck after locking 576 { 577 continue; 578 } 579 580 TestResult tr = it.next(); 581 TestDescription td = tr.getDescription(); 582 TestFilter rejector = null; 583 boolean wouldAccept = false; 584 585 synchronized (fObs) { 586 try { 587 wouldAccept = filter.accepts(tr, fObs); 588 } catch (TestFilter.Fault f) { 589 f.printStackTrace(Debug.getWriter()); 590 if (log != null && log.isLoggable(Level.SEVERE)) { 591 log.log(Level.SEVERE, "TT_NodeCache - filter is broken", f); 592 } 593 594 // assume it is accepted, this is what would 595 // happen for test execution as well 596 wouldAccept = true; 597 } // catch 598 599 if (!wouldAccept && fObs.lastTd == td) { 600 rejector = fObs.lastRejector; 601 fObs.clear(); 602 } 603 } // sync 604 605 if (wouldAccept) { 606 int type = tr.getStatus().getType(); 607 608 if (!testLists[type].contains(tr)) { 609 stats[type]++; 610 testLists[type].add(tr); 611 612 // XXX We are not producing 613 // the all the parameters. it seems to be overkill 614 // for what we are currently using this event for. 615 // Perhaps the API need to be revisited. 616 notify(type + TT_NodeCacheObserver.OFFSET_FROM_STATUS, 617 true, null, tr, -1); 618 } else { 619 // duplicate, no action 620 } 621 622 } else { 623 // filtered out list 624 testLists[testLists.length - 1].add(tr); 625 rejectReasons.put(tr, rejector); 626 localRejectCount++; 627 628 // XXX We are not producing 629 // the all the parameters. it seems to be overkill 630 // for what we are currently using this event for. 631 // Perhaps the API need to be revisited. 632 notify(TT_NodeCacheObserver.MSGS_FILTERED, 633 true, null, tr, -1); 634 } 635 } // sync this 636 } // sync node 637 } // try 638 catch (TestResult.Fault f) { 639 f.printStackTrace(Debug.getWriter()); 640 if (log != null && log.isLoggable(Level.SEVERE)) { 641 log.log(Level.SEVERE, "TT_NodeCache - problem with test result", f); 642 } 643 644 // try to recover it? 645 // ignore error and don't do anything 646 } // catch 647 648 notifyStats(); 649 } // while 650 651 if (state != PAUSED) { 652 cleanup(); 653 } 654 655 } finally { 656 trt.getLock().unlock(); 657 } 658 } 659 660 /** 661 * Find a particular test in the test lists. You can specify which lists to 662 * search first or last to help mitigate the O(n) search performance. It is 663 * assumed that synchronization for access to the lists has been taken care of. 664 * 665 * @param tr The test to locate. 666 * @param firstListToCheck Hint of where to start looking. -1 to specify none. 667 * @param lastListToCheck Hint of where the last place to look should be. 668 * -1 to specify none. 669 * @return Array describing [0] which list the item was found in, [1] at 670 * what index. If [0] is greater than -1, then so will [1]. [0] 671 * of -1 indicates that then item was not found. 672 */ 673 private int[] locateTestInLists(TestResult tr, int firstListToCheck, 674 int lastListToCheck) { 675 int[] result = new int[2]; 676 result[0] = -1; 677 result[1] = -1; 678 679 if (firstListToCheck >= 0) { 680 int possible = testLists[firstListToCheck].indexOf(tr); 681 if (possible != -1) { 682 // done with search 683 result[0] = firstListToCheck; 684 result[1] = possible; 685 } 686 } else { 687 } 688 689 // do a more exhaustive search if not found yet 690 if (result[0] == -1) { 691 for (int i = 0; i < testLists.length; i++) { 692 // skip the lists which have been checked or that should be 693 // checked last 694 // this is just a performance optimization 695 if (i == firstListToCheck || i == lastListToCheck) { 696 continue; 697 } 698 699 int possible = testLists[i].indexOf(tr); 700 if (possible != -1) { 701 // found in list i, position possible 702 // done with search 703 result[0] = i; 704 result[1] = possible; 705 break; 706 } 707 } // for 708 } 709 710 // if still not found, check the list which was specified to be checked 711 // last, this is just a performance optimization 712 if (result[0] == -1 && lastListToCheck >= 0) { 713 int possible = testLists[lastListToCheck].indexOf(tr); 714 if (possible != -1) { 715 result[0] = lastListToCheck; 716 result[1] = possible; 717 } 718 } 719 720 return result; 721 } 722 723 /** 724 * Determine the index of a particular test in a vector. 725 */ 726 private int searchList(TestResult target, Vector<?> list) { 727 int possible = list.indexOf(target); 728 return possible; 729 } 730 731 private void cleanup() { 732 if (state == ABORTED) { 733 // no valid info 734 return; 735 } 736 737 state = COMPLETED; 738 } 739 740 /** 741 * Prepare to process this node. 742 * Avoid calling until it is time to process the node. 743 * @return Iterator for this node, or null if not possible. 744 */ 745 private TestResultTable.TreeIterator init() { 746 if (node == null) { 747 valid = false; 748 state = ABORTED; 749 return null; 750 } 751 752 return TestResultTable.getIterator(node); 753 } 754 755 private synchronized void notify(int type, boolean isAdd, 756 TestResultTable.TreeNode[] path, 757 TestResult what, int index) { 758 if (observers.length == 0) { 759 return; 760 } 761 762 for (int i = 0; i < observers.length; i++) { 763 boolean[] mask = observers[i].getEventMasks(); 764 if (mask[0] || mask[type]) { 765 if (isAdd) { 766 observers[i].testAdded(type, path, what, index); 767 } else { 768 observers[i].testRemoved(type, path, what, index); 769 } 770 } 771 } // for 772 } 773 774 private synchronized void notifyStats() { 775 if (observers.length == 0) { 776 return; 777 } 778 779 for (int i = 0; i < observers.length; i++) { 780 boolean[] mask = observers[i].getEventMasks(); 781 if (mask[0] || mask[TT_NodeCacheObserver.MSGS_STATS]) { 782 observers[i].statsUpdated(stats); 783 } 784 } // for 785 } 786 787 788 private final TestResultTable.TreeNode node; 789 private final Logger log; 790 private TestResultTable.TreeIterator it; 791 private TestFilter filter; 792 private int[] stats = new int[Status.NUM_STATES]; 793 private int localRejectCount; 794 private Hashtable<TestResult, TestFilter> rejectReasons = new Hashtable<>(); 795 private final FilterObserver fObs = new FilterObserver(); 796 private TT_NodeCacheObserver[] observers = new TT_NodeCacheObserver[0]; 797 private Vector<TestResult>[] testLists; // could use unsynchronized data structure 798 private volatile int state; 799 private volatile boolean valid = true; 800 private static final int NOT_COMPUTED = 0; 801 private static final int COMPUTING = 1; 802 private static final int COMPLETED = 2; 803 private static final int PAUSED = 3; 804 private static final int ABORTED = 4; 805 private static final boolean debug = Debug.getBoolean(TT_NodeCache.class); 806 807 static abstract class TT_NodeCacheObserver { 808 809 public TT_NodeCacheObserver() { 810 interestList = new boolean[EVENT_LIST_SIZE]; 811 } 812 813 /** 814 * Find out what messages this observer is interested in. 815 */ 816 public boolean[] getEventMasks() { 817 return interestList; 818 } 819 820 public abstract void testAdded(int messageType, 821 TestResultTable.TreeNode[] path, TestResult what, int index); 822 823 public abstract void testRemoved(int messageType, 824 TestResultTable.TreeNode[] path, TestResult what, int index); 825 826 public abstract void statsUpdated(int[] stats); 827 protected boolean[] interestList; 828 public static final int EVENT_LIST_SIZE = 7; 829 public static final int MSGS_ALL = 0; 830 public static final int MSGS_STATS = 1; 831 public static final int MSGS_PASSED = 2; 832 public static final int MSGS_FAILED = 3; 833 public static final int MSGS_ERRORS = 4; 834 public static final int MSGS_NOT_RUNS = 5; 835 public static final int MSGS_FILTERED = 6; 836 public static final int OFFSET_FROM_STATUS = 2; 837 } 838 839 static class FilterObserver implements TestFilter.Observer { 840 841 public void rejected(TestDescription d, TestFilter rejector) { 842 lastTd = d; 843 lastRejector = rejector; 844 } 845 846 public void clear() { 847 lastTd = null; 848 lastRejector = null; 849 } 850 TestDescription lastTd; 851 TestFilter lastRejector; 852 } 853 854 }