/* * $Id$ * * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javatest.exec; import java.util.Hashtable; import java.util.Vector; import com.sun.javatest.Status; import com.sun.javatest.TestDescription; import com.sun.javatest.TestFilter; import com.sun.javatest.TestResult; import com.sun.javatest.TestResultTable; import com.sun.javatest.util.Debug; import com.sun.javatest.util.DynamicArray; import java.util.logging.Level; import java.util.logging.Logger; /** * Cached information about a particular tree node. * We want to collect pass/fail stats and other variable information so it can * be used to render a node quickly when requested by JTree. Code here should * never run on the GUI thread. * *
* Objects of this type run to completion and are then immutable.
*/
class TT_NodeCache implements Runnable {
/**
* Construct a cache object which will collect info about the given node
* with respect to the supplied filter.
*
* @param n Node to examine, should not be null.
* @param f Filter to use when producing information. Can be null.
*/
TT_NodeCache(TestResultTable.TreeNode n, TestFilter f, Logger l) {
filter = f;
node = n;
log = l;
// all the states plus filtered out
testLists = new Vector[Status.NUM_STATES + 1];
for (int i = 0; i < Status.NUM_STATES + 1; i++) {
testLists[i] = new Vector<>();
}
}
/**
* Start or resume processing.
*/
public void run() {
if (debug) {
Debug.println("TT_NodeCache starting");
Debug.println(" -> " + this);
Debug.println(" -> node " + node + "(" + node.getName() + ")");
Debug.println(" -> filter=" + filter);
Debug.println(" -> old state=" + state);
}
// needed for a new run, a paused object will already have one
if (it == null) {
it = init();
}
if (debug) {
Debug.println(" -> iterator= " + it);
}
state = COMPUTING;
process();
}
/**
* Pause processing, and return immediately.
* If the node has completed processing, calling this method has no
* effect.
* Be careful with MT activities when using pause() resume() isPaused().
* @see #resume()
*/
void pause() {
if (state < COMPLETED) {
state = PAUSED;
if (debug) {
Debug.println("TT_NodeCache for " + node.getName() + " pausing...");
}
}
}
/**
* Continue processing after a pause.
* @see #pause
* @throws IllegalStateException if pause() was not previously called.
*/
void resume() {
if (state != PAUSED) {
throw new IllegalStateException("Cache node not previously paused.");
}
if (debug) {
Debug.println("TT_NodeCache for " + node.getName() + " resuming...");
}
state = COMPUTING;
process();
}
/**
* @return True if the replacement has an effect on the statistics. False
* if it does not.
*/
synchronized boolean add(TestResultTable.TreeNode[] path, TestResult what, int index) {
boolean result = false;
boolean wouldAccept = false; // special case because of filtering
boolean needsProcessing = false;
// not even running yet
if (it == null) {
return false;
}
try {
TestDescription td = what.getDescription();
TestFilter rejector = null;
synchronized (fObs) {
wouldAccept = filter.accepts(what, fObs);
if (!wouldAccept && fObs.lastTd == td) {
rejector = fObs.lastRejector;
fObs.clear();
}
} // sync
needsProcessing = (!it.isPending(what));
if (!needsProcessing) {
// It's still going to come out of the iterator.
// Make sure it's not the one which is committed to be next.
// Would be nice to work around this in a different way, either
// here or in the iterator.
Object peek = it.peek();
if (peek instanceof TestResult &&
((TestResult) peek).getTestName().equals(what.getTestName())) {
it.next(); // consume
needsProcessing = true;
} else {
}
} else {
}
if (needsProcessing) {
if (!wouldAccept) {
// add to filtered out list
localRejectCount++;
testLists[testLists.length - 1].add(what);
rejectReasons.put(what, rejector);
} else {
int type = what.getStatus().getType();
// unfortunately the add/remove messages don't seem to
// be 100% symmetric, or else there is some other race
// condition. I tried to find the problem, but 0-5 tests out
// of 10000 will end up added twice when loading a workdir at
// startup. 12/9/2002
if (!testLists[type].contains(what)) {
stats[type]++;
testLists[type].add(what);
} else {
}
}
result = true;
} else { // will be counted later...
result = false;
}
// send out notifications if needed
notify((wouldAccept ? what.getStatus().getType() + TT_NodeCacheObserver.OFFSET_FROM_STATUS
: TT_NodeCacheObserver.MSGS_FILTERED),
true, path, what, index);
if (result) {
notifyStats();
}
} catch (TestResult.Fault f) {
//if (debug) {
// f.printStackTrace(Debug.getWriter());
// Debug.println(msg);
// }
if (log != null && log.isLoggable(Level.SEVERE)) {
String msg = "TT_NodeCache - TR fault, purging old info. " + what.getTestName();
log.log(Level.SEVERE, msg, f);
}
// TR is somehow corrupt, remove it
node.getEnclosingTable().resetTest(what.getTestName());
result = false;
} catch (TestFilter.Fault f) {
f.printStackTrace(Debug.getWriter());
if (log != null && log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, "TT_NodeCache - filter is broken", f);
}
// filter is broken, shove everything into filtered out.
// this behavior is similar to what would happen in the same
// case when the harness is iterating to select tests to run.
// trying to only do this if counting of this test would have
// been necessary.
if (needsProcessing) {
localRejectCount++;
testLists[testLists.length - 1].add(what);
// this reject will not have a reason entry in
// rejectReasons
}
// ignore error and don't do anything
result = false;
}
return result;
}
/**
* @return True if the replacement has an effect on the statistics. False
* if it does not.
*/
synchronized boolean remove(TestResultTable.TreeNode[] path, TestResult what, int index) {
boolean result = false;
// special case because of filtering
// not even running yet
if (it == null) {
return false;
}
int type = what.getStatus().getType();
if (!it.isPending(what)) { // check iterator position
int[] rmList = locateTestInLists(what, type, -1);
if (rmList[0] != -1) {
testLists[rmList[0]].remove(rmList[1]);
// decrement counter
if (rmList[0] < stats.length) {
stats[rmList[0]]--;
} else {
localRejectCount--;
}
// send out notifications if needed
notify(rmList[0] + TT_NodeCacheObserver.OFFSET_FROM_STATUS, false, path, what, index);
result = true;
} else {
}
} else { // pending in iterator
Object peek = it.peek();
if (peek instanceof TestResult &&
((TestResult) peek).getTestName().equals(what.getTestName())) {
it.next(); // consume
// do it again to make it happen
result = remove(path, what, index);
}
}
if (result) {
notifyStats();
}
return result;
}
/**
* @return True if the replacement has an effect on the statistics. False
* if it does not.
*/
synchronized boolean replace(TestResultTable.TreeNode[] path, TestResult what,
int index, TestResult old) {
boolean result = false;
// not even running yet
if (it == null) {
return false;
}
if (!it.isPending(what)) { // check iterator position
int typeNew = what.getStatus().getType();
int typeOld = old.getStatus().getType();
// filtering of old does not work because of the status filter
boolean wouldAcceptNew = false;
TestFilter rejector = null;
try {
TestDescription td = what.getDescription();
synchronized (fObs) {
wouldAcceptNew = filter.accepts(what, fObs);
if (!wouldAcceptNew && fObs.lastTd == td) {
rejector = fObs.lastRejector;
fObs.clear();
}
} // sync
} // try
catch (TestResult.Fault f) {
f.printStackTrace(Debug.getWriter());
if (log != null && log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, "TT_NodeCache - problem with test result", f);
}
// ignore error and don't do anything
return false;
} // catch
catch (TestFilter.Fault f) {
f.printStackTrace(Debug.getWriter());
if (log != null && log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, "TT_NodeCache - filter is broken", f);
}
// ignore error and don't do anything
return false;
} // catch
// inserting into one of the status lists or the filtered out list
int targetList = (wouldAcceptNew ? typeNew : testLists.length - 1);
int[] rmList = null;
// optimization to search based on expected location of old test
// and optimize out null changes
if (what == old) {
if (typeOld != typeNew) { // special case optimization
// no change to lists
rmList = new int[]{-1, -1};
} else {
rmList = locateTestInLists(old, Status.NOT_RUN, targetList);
if (rmList[0] == targetList) {
// same TR object, same status
// use -1 to specify no remove action
rmList[0] = -1;
rmList[1] = -1;
}
}
} else {
rmList = locateTestInLists(old, typeOld, targetList);
}
if (rmList[0] != -1) {
testLists[rmList[0]].remove(rmList[1]);
testLists[targetList].add(what);
// decrement counter
if (rmList[0] < stats.length) {
stats[rmList[0]]--;
} else {
localRejectCount--;
rejectReasons.remove(what);
}
// increment counter
if (targetList < stats.length) {
stats[targetList]++;
} else {
localRejectCount++;
rejectReasons.put(what, rejector);
}
// remove the old
// add the new
notify(rmList[0] + TT_NodeCacheObserver.OFFSET_FROM_STATUS,
false, path, old, index);
notify(targetList + TT_NodeCacheObserver.OFFSET_FROM_STATUS,
true, path, what, index);
result = true;
} else {
// must be a null change
}
} else // this TR has yet to be processed
{
result = false;
}
if (result) {
notifyStats();
}
return result;
}
boolean isPaused() {
return (state == PAUSED);
}
/**
* Is this object eligible to run.
* This translates to being either unprocessed or paused.
*/
boolean canRun() {
return ((state == PAUSED || state == NOT_COMPUTED) &&
valid == true);
}
void halt() {
if (debug) {
Debug.println("TT_NodeCache thread stopping");
Debug.println(" -> " + this);
}
state = ABORTED;
valid = false;
}
/**
* Is info still being collected?
* The state is not considered active if the processing is paused.
*
* @return True if information is incomplete, false otherwise.
* @see #isPaused()
*/
boolean isActive() {
return (state == COMPUTING);
}
/**
* Has all the information been collected.
* @return True if all information is up to date, and will not change
* unless this node is invalidated.
*/
boolean isComplete() {
return (state == COMPLETED);
}
boolean isAborted() {
return (state == ABORTED);
}
/**
* Invalidate any information in this node cache.
* @see #isValid()
*/
void invalidate() {
valid = false;
}
/**
* Has the data in this node been invalidated.
* A node may be valid while it is active, but becomes invalid when
* notified that the constraints that it is executing with are no longer
* correct. It may also become invalid if the thread is interrupted.
* @see #invalidate()
*/
boolean isValid() {
return valid;
}
TestResultTable.TreeNode getNode() {
return node;
}
TestFilter getFilter() {
return filter;
}
/**
* Get the pass fail error notrun stats.
* The data may be in flux if the data is still being collected, use
* isActive() to anticipate this.
* @return An array of size Status.NUM_STATES. This is not a copy, do not
* alter.
* @see #isActive()
* @see com.sun.javatest.Status#NUM_STATES
*/
int[] getStats() {
return stats;
}
/**
* Find out how many tests were rejected by filters.
* @return Number of rejected tests found in and below this node.
*/
int getRejectCount() {
if (it != null) {
return it.getRejectCount() + localRejectCount;
} else {
return localRejectCount;
}
}
TestFilter getRejectReason(TestResult tr) {
return rejectReasons.get(tr);
}
/**
* Snapshot the current data and add an observer.
* This is an atomic operation so that you can get completely up to date
* and monitor all changes going forward.
*
* @param obs The observer to attach. Must not be null.
* @param needSnapshot Does the caller want a snapshot of the current test lists.
* True if yes, false if not.
* @return A copy of the Vectors that contain the current list of tests. Null if
* needSnapshot is false.
*/
synchronized Vector