/*
* $Id$
*
* Copyright (c) 1996, 2016, 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;
import java.io.File;
import java.io.IOException;
import java.text.Collator;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import com.sun.javatest.finder.TestFinderDecorator;
import com.sun.javatest.tool.Preferences;
import com.sun.javatest.util.Debug;
import com.sun.javatest.util.DynamicArray;
import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.StringArray;
import com.sun.javatest.httpd.RootRegistry;
/**
* Collected results from a test suite.
* The data is represented as TestResult objects, although the test itself
* may not have been run, but just "found" so far. TestResultTable is
* populated by using a TestFinder, and is subsequently updated as tests are
* run by the harness.
*/
// debug values:
// 1 - info messages, stack traces on
// 2 - search details
// 10 - more scan/insert/remove detail
// 11 - heavy scan/insert/remove detail
// 99 - everything
// NOTE: stack traces are on for all levels above 0
public class TestResultTable {
public void dispose() {
if (trCache != null) {
trCache.shutdown();
}
}
/**
* Observer to monitor changes to a TestResultTable.
*/
public interface Observer {
/**
* The oldValue has been replaced by the newValue.
*
* @param oldValue Previous value being overwritten.
* @param newValue The new value stored in the TRT.
*/
void update(TestResult oldValue, TestResult newValue);
/**
* The given test was changed, but previously existed in this TRT.
* This is not a guarantee of change, but is the best possible hint.
*
* @param whichTR The test which was altered.
*/
void updated(TestResult whichTR);
/*
void stalled(String reason);
void ready();
*/
}
/**
* Tree-aware observer of the TRT.
*
* @since 3.0
*/
public interface TreeObserver {
/**
* A node has been inserted into the tree.
* @param path The path to the node that was inserted. Does not include
* the actual object which is new.
* @param what The actual object that changed. So path
plus
* this is the entire path. The type will either be TestResult or
* TreeNode.
* @param index The index in path[path.length-1]
where the
* new node was placed.
*/
void nodeInserted(TreeNode[] path, Object what, int index);
/**
* A node has changed.
* In the case of a test changing, the old object is the test result
* being replaced. In the case of a branch changing, the old object is
* the same as the what object.
*
* @param path The path to the node that changed. Does not include
* the actual object which changed.
* @param what The actual object that changed. So path
plus
* this is the entire path. The type will either be TestResult or
* TreeNode.
* @param index The index in path[path.length-1]
that changed.
* @param old The old value at the changed location.
*/
void nodeChanged(TreeNode[] path, Object what, int index, Object old);
/**
* An item has been removed from the tree.
*
* @param path The path to the node that changed. Does not include
* the actual object which changed.
* @param what The actual object that was removed. So path
plus
* this is the entire path. The type will either be TestResult or
* TreeNode.
* @param index The index in path[path.length-1]
that was
* removed.
*/
void nodeRemoved(TreeNode[] path, Object what, int index);
}
/**
* Extension to TreeObserver to receive notifications related
* to events happened on tree nodes.
*/
public interface TreeEventObserver extends TreeObserver {
/**
* A refresh has been stared on the node. All children
* will be recursively refreshed but only one notification about the
* original node will be delivered.
*
* @param origin Node the refresh has been originated
*
*/
void startRefresh(TreeNode origin);
/**
* A refresh has been finished on the node. In spite of the all children
* will be recursively refreshed the only one notification about the
* original node will be delivered.
*
* @param origin Node the refresh has been originated
*
*/
void finishRefresh(TreeNode origin);
}
/**
* Observer interface to watch a single tree node.
*
* @since 3.0
*/
public interface TreeNodeObserver {
/**
* A TreeNode has been inserted into the given parent node.
*
* @param parent The node which acquired the new node. This is the same as
* the object that the observer attached to.
* @param newNode The node which was added.
* @param index The index at which the node was added.
*/
public void insertedBranch(TreeNode parent, TreeNode newNode, int index);
/**
* A TestResult has been inserted into the given parent node.
*
* @param parent The node which acquired the new test. This is the same as
* the object that the observer attached to.
* @param test The test which was added.
* @param index The index at which the test was added.
*/
public void insertedResult(TreeNode parent, TestResult test, int index);
/**
* A TestResult has been replaced in the given parent node.
*
* @param parent The node which acquired the new test. This is the same as
* the object that the observer attached to.
* @param oldTest The test which was replaced.
* @param newTest The test which took the old test's place.
* @param index The index at which activity occurred.
*/
public void replacedResult(TreeNode parent, TestResult oldTest,
TestResult newTest, int index);
/**
* A TreeNode has been removed from the given parent node.
*
* @param parent The node which acquired the new test. This is the same as
* the object that the observer attached to.
* @param index The index at which the removed node resided in the parent.
*/
public void removedBranch(TreeNode parent, int index);
/**
* A TestResult has been removed from the given parent node.
*
* @param parent The node which acquired the new test. This is the same as
* the object that the observer attached to.
* @param test The test which was removed.
* @param index The index at which the removed test resided in the parent.
*/
public void removedResult(TreeNode parent, TestResult test, int index);
/**
* The statistics counters of the node have changed.
*
* @param node The node whose counters were invalidated.
* This is the same as the node which this observer attached to.
*/
public void countersInvalidated(TreeNode node);
}
/**
* Exception class to communicate any special conditions which may occur
* while using this class.
*/
public static class Fault extends Exception
{
Fault(I18NResourceBundle i18n, String s) {
super(i18n.getString(s));
}
Fault(I18NResourceBundle i18n, String s, Object o) {
super(i18n.getString(s, o));
}
Fault(I18NResourceBundle i18n, String s, Object[] o) {
super(i18n.getString(s, o));
}
}
/**
* Create a table ready to be occupied.
*/
public TestResultTable() {
statusTables = new Map[Status.NUM_STATES];
for (int i = 0; i < statusTables.length; i++)
statusTables[i] = new Hashtable<>();
root = new TRT_TreeNode(this, null);
instanceId++;
if (com.sun.javatest.httpd.HttpdServer.isActive()) {
String url = "/trt/" + instanceId;
httpHandle = new TRT_HttpHandler(this, url, instanceId);
RootRegistry.getInstance().addHandler(url, "Test Result Table",
httpHandle);
RootRegistry.associateObject(this, httpHandle);
}
rtc = new RequestsToCache();
}
/**
* Create a table for the tests in a work directory and its
* associated test suite and test finder.
*
* @param wd The work directory to associate with this table.
* @since 3.0
*/
public TestResultTable(WorkDirectory wd) {
this();
setWorkDirectory(wd);
}
/**
* Create a table for the tests in a work directory and its
* associated test suite, overriding the test suite's default test finder.
*
* @param wd The work directory to associate with this table.
* @param tf The finder to use. Do not use this constructor unless
* necessary.
* @see #TestResultTable(WorkDirectory)
* @since 3.0
*/
public TestResultTable(WorkDirectory wd, TestFinder tf) {
this();
setWorkDirectory(wd, tf);
}
/**
* Set the test finder for this object.
* It is illegal to call this method once the test finder for a instance
* has been set. Rather than use this method, it is probably better to
* supply the finder at construction time.
*
* @param tf The finder to use.
* @throws IllegalStateException Thrown if the finder for this object is already set.
* @see #getTestFinder
* @since 3.0
*/
public void setTestFinder(TestFinder tf) {
if (finder != null) {
throw new IllegalStateException(i18n.getString("trt.alreadyFinder"));
}
finder = tf;
if (trCache == null)
initFinder();
}
public void updateTestExecutionOrderOnTheFly(){
root = new TRT_TreeNode(TestResultTable.this, null);
}
/**
* Get the workdir associated with this object.
*
* @return The workdir. Null if not available.
* @deprecated Use getWorkDirectory
*/
public WorkDirectory getWorkDir() {
return getWorkDirectory();
}
/**
* Get the work directory associated with this object.
*
* @return The work directory, or null if none set.
* @see #setWorkDirectory
*/
public WorkDirectory getWorkDirectory() {
return workDir;
}
/**
* Set the work directory associated with this object.
* Once set, it cannot be changed.
*
* @param wd The work directory, or null if none set.
* @see #getWorkDirectory
*/
public void setWorkDirectory(WorkDirectory wd) {
setWorkDirectory(wd, wd.getTestSuite().getTestFinder());
}
// this method exists to support the obsolete constructor
// TestResultTable(WorkDirectory wd, TestFinder tf).
// When that constructor can be deleted, the main body of this
// method should be merged with setWorkDirectory(WorkDirectory wd)
private void setWorkDirectory(WorkDirectory wd, TestFinder tf) {
if (wd == null)
throw new NullPointerException();
if (workDir == wd) {
// already set
return;
}
if (workDir != null && workDir != wd) {
// already set to something else
throw new IllegalStateException();
}
if (finder != null && finder != tf)
throw new IllegalStateException();
workDir = wd;
finder = tf;
//root = new TRT_TreeNode(this, null);
initFinder();
/*OLD
// do this in the background because of possible high cost
Thread thr = new Thread("TRT background cache init.") {
public void run() {
try {
trCache = new TestResultCache(workDir, true);
trCache.setObserver(updater);
setCacheInitialized(true);
}
catch (TestResultCache.Fault f) {
if (debug > 0)
f.printStackTrace(Debug.getWriter());
// XXX ack, what can we do?!
//throw new Fault(i18n, "trt.trcCreate", f.getMessage());
} // try
} // run()
};
thr.setPriority(Thread.MIN_PRIORITY + 2);
thr.start();
*/
try {
trCache = new TestResultCache(workDir, updater);
}
catch (IOException e) {
// should consider throwing Fault, but that will destabilize too many APIs for now
updater.error(e);
}
}
/**
* How many tests have been found so far.
*
* @return A number greater than or equal to zero.
* @since 3.0
*/
public int getCurrentTestCount() {
return root.getCurrentSize();
}
void starting() {
/*OLD
isRunning++;
*/
}
void finished() {
// do on background thread? (OLD suggestion -- now asynchronous)
if (trCache != null) {
if (needsCacheCompress()) {
if (debug > 0) {
Debug.print("TRT.finished(), attempting cache compress...");
}
trCache.compress();
}
if (debug > 0) {
Debug.print("TRT.finished(), requesting cache flush...");
}
}
}
/**
* Update the information in the table with a new test result.
* The supplied TestResult may exist in the table already, it can replace
* an existing test or it can be completely new. Doing this operation will
* trigger appropriate observer messages.
*
* @param tr The test to insert or update.
* @throws JavaTestError Throws if the result cache throws an error.
*/
public void update(TestResult tr) {
update(tr, false);
}
/**
* Update the information in the table with a new test result.
* The supplied TestResult may exist in the table already, it can replace
* an existing test or it can be completely new. Doing this operation will
* trigger appropriate observer messages.
*
* @param tr The test to insert or update.
* @param suppressScan Request that test finder activity be suppressed if possible
* @throws JavaTestError Throws if the result cache throws an error.
*/
public void update(TestResult tr, boolean suppressScan) {
TestResult prev = insert(tr, suppressScan);
updateNotify(tr, prev);
}
/**
* Complete the notification process for insertion of a new result.
* This method adds it to the cache, sends notifications and does other
* bookkeeping only.
*
* This method was introduced to allow updates to occur from non-root
* branches (bottom up updates) or updates from insertions into the table
* which are usual during test execution (top down).
* @param tr Result inserted.
* @param prev Result replaced (if any).
*/
void updateNotify(TestResult tr, TestResult prev) {
if (tr != prev) {
tr.shareStatus(statusTables);
for (int i = 0; i < observers.length; i++)
observers[i].update(prev, tr);
}
else {
// tests are the same, we are probably changing the status
for (int i = 0; i < observers.length; i++)
observers[i].updated(tr);
}
testsInUpdate.add(tr);
// things which haven't been run are not put in cache
// doing so will cause problems in the cache and possibly other
// places because there is never a JTR file to reload the result from
if (trCache!= null && !updateInProgress && tr.getStatus().getType() != Status.NOT_RUN) {
trCache.insert(tr);
}
testsInUpdate.remove(tr);
}
/**
* This method blocks until the work directory data has been completely
* synchronized. It is recommended that you use this before creating and
* using an iterator to ensure that you get consistent and up to date
* data. It would also be advisable to do this before forcing the VM to
* exit.
* @return Always true. Reserved for later use.
*
* @since 3.0.1
*/
public synchronized boolean waitUntilReady() {
while ( ((workDir != null) && !cacheInitialized) ||
updateInProgress) {
try {
wait();
}
catch (InterruptedException e) {
if (debug > 0)
e.printStackTrace(Debug.getWriter());
}
}
if (trCache != null){
trCache.requestFullUpdate();
}
return true;
}
/**
* Determine the update status of the table.
*
* @return True if the table is in a consistent state.
* @see #waitUntilReady
*/
public synchronized boolean isReady() {
return (cacheInitialized && !updateInProgress);
}
/**
* Find a specific instance of a test result.
*
* @param td The test description which corresponds to the target result.
* @return The requested test result object, null if not found.
*/
public TestResult lookup(TestDescription td) {
// transforms to JTR path style to do lookup
return lookup(TestResult.getWorkRelativePath(td.getRootRelativeURL()));
}
/**
* Find a specific instance of a test result.
* If you only have the test URL, use TestResult.getWorkRelativePath() to get
* the jtrPath parameter for this method.
*
* @param jtrPath The work relative path of the test result file.
* Output from TestResult.getWorkRelativePath() is
* the best source of this info.
* @return The requested test result object, null if not found.
*/
public TestResult lookup(String jtrPath) {
// no tree yet
if (root == null) return null;
return findTest(root, jtrPath, jtrPath);
}
/**
* Take a URL and find the node in the current test tree to which it refers
* to. The resulting node may be:
*
TreeNode
* TestResult
* NOT_RUN
* test of the same name. This operation has no effect if the given test
* is not in the tree.
*
* Matching objects for removal is done only by reference. The operation
* may fail (return null
) if the test exists, but is not the
* same object. If you really want to remove a test by name, you can use
* resetTest(String)
.
*
* NOTE: This method will cause waitUntilReady() to block.
*
* @param tr The test to find, purge and replace.
* @return The new NOT_RUN
test. Null if the operation fails
* because the test could not be found.
* @see #resetTest(String)
* @since 3.0
*/
public synchronized TestResult resetTest(TestResult tr) {
TestResult newTest = null;
workDir.log(i18n, "trt.rstTest", tr.getTestName());
TreeNode[] location = getObjectPath(tr);
if (location == null) { // operation failed
// do nothing
// result = null
newTest = lookup(tr.getWorkRelativePath());
if (debug > 0)
Debug.println("Recovered test by replacement (1). " + newTest);
}
else {
TRT_TreeNode targetNode = (TRT_TreeNode)(location[location.length-1]);
int index = targetNode.getIndex(tr, false);
if (index >= 0) {
newTest = targetNode.resetTest(index, tr);
if (newTest == null && debug > 0)
Debug.println("reset of test " + tr.getTestName() + " failed.");
else {
/*OLD
try {
*/
// Insert into cache?
// this will cause a cache error on flush because it can't reload the
// result, but that should get it out of the cache
if (trCache != null) {
testsInUpdate.add(newTest);
trCache.insert(newTest);
testsInUpdate.remove(newTest);
}
notifyRemoveLeaf(location, tr, index);
notifyNewLeaf(location, newTest, index);
/*OLD
} // try
catch (TestResultCache.Fault f) {
if (debug > 0)
f.printStackTrace(Debug.getWriter());
else { }
throw new JavaTestError(i18n, "trt.trcFault", f);
} // catch
*/
} // inner else
} // middle if
else {
newTest = lookup(tr.getWorkRelativePath());
if (debug > 0)
Debug.println("Recovered test by replacement (2). " + newTest);
} // middle else
} // else
return newTest;
}
/**
* This method purges the given test, including attempting to delete the
* associated JTR file, then replaces it with a basic NOT_RUN
* test of the same name. This operation has no effect if the given test
* is not in the tree. The resetTest(TestResult)
method is
* more efficient than this one, use it if you already have the object.
*
* NOTE: This method may cause waitUntilReady() to block.
*
* @param testName The test to find, purge and replace. This is of the form
* given by TestResult.getTestName().
* @return The new
* The hashtable has keys of TestResults, and values which are TestFilters.
* Because of CompositeFilters, the set of filters found in the ``values''
* is not necessarily equivalent to those given by getFilters().
*
* @return Array as described or null if no tests have been rejected yet.
* @since 3.0.3
*/
public abstract HashMapNOT_RUN
test. Null if the given test name
* could not be found.
* @see com.sun.javatest.TestResult#getTestName
* @since 3.0
*/
public synchronized TestResult resetTest(String testName) {
TestResult tr = findTest(root, TestResult.getWorkRelativePath(testName), testName);
if (tr == null)
return null;
else
return resetTest(tr);
}
/**
* Refresh a test if the files on disk have been modified since the test was read.
* @param test The path for the test to be refreshed
* @return true if a refresh was needed, false otherwise.
* @throws TestResultTable.Fault if the test indicated cannot be located for
* refreshing.
*/
public synchronized boolean refreshIfNeeded(String test) throws Fault {
TestResult tr = lookup(TestResult.getWorkRelativePath(test));
if (tr == null)
throw new Fault(i18n, "trt.refreshNoTest", test);
TreeNode[] path = getObjectPath(tr);
if (path == null)
return false;
TRT_TreeNode tn = (TRT_TreeNode)path[path.length-1];
TestResult newTr = tn.refreshIfNeeded(tr);
if (newTr != tr)
notifyChangeLeaf(TestResultTable.getObjectPath(tn),
newTr, tn.getTestIndex(newTr, false), tr);
return false;
}
/**
* Refresh a folder if the files on disk have been modified since the
* folder was read. Notifies observers of the refresh happened.
* @param node the node representing the folder to be refreshed
* @return true if any refreshing was needed, false otherwise.
* @throws TestResultTable.Fault if the node indicated cannot be located for
* refreshing.
*/
public synchronized boolean refreshIfNeeded(TreeNode node) throws Fault {
if (node.getEnclosingTable() != this)
throw new IllegalStateException("refresh requested for node not owned by this table");
notifyStartRefresh(node);
try {
return recursiveRefresh((TRT_TreeNode)node);
} finally {
notifyFinishRefresh(node);
}
}
public synchronized boolean prune() throws Fault {
if (root == null)
return false;
boolean changes = false;
root.scanIfNeeded();
TreeNode[] nodes = root.getTreeNodes();
if (nodes == null)
return changes;
for (int i = 0; i < nodes.length; i++) {
changes = (changes || prune(nodes[i]));
} // for
return changes;
}
/**
* Removes empty nodes (things with no tests below them).
* @param node The node to perform the prune operation upon.
* @return True if some nodes were pruned, false otherwise.
*/
synchronized public boolean prune(TreeNode node) throws Fault {
TRT_TreeNode parent = ((TRT_TreeNode)(node.getParent()));
if (node.getChildCount() == 0) {
int index = parent.rmChild((TRT_TreeNode)node);
if (index != -1)
notifyRemoveLeaf(getObjectPath(parent), node, index);
return (index != -1 ? true : false);
}
TreeNode[] nodes = node.getTreeNodes();
if (nodes == null)
return false; // must mean there are tests in this node
for (int i = 0; i < nodes.length; i++) {
prune(nodes[i]);
} // for
if (node.getChildCount() == 0) {
// prune
int index = parent.rmChild((TRT_TreeNode)node);
if (index != -1)
notifyRemoveLeaf(getObjectPath(parent), node, index);
return (index != -1 ? true : false);
}
return false;
}
// ------- Private methods begin --------
/**
* Temporary method to suppress finder activity.
* Called from Harness when in batch mode. You should
* not change this after it has been set. It
* should be set before iteration and insertion of
* work directory information takes place.
*/
void suppressFinderScan(boolean state) {
if (state == false)
suppressFinderScan = false;
else if (workDir != null) {
TestSuite ts = workDir.getTestSuite();
if (ts != null && (
ts.getTestRefreshBehavior(TestSuite.DELETE_NONTEST_RESULTS) ||
ts.getTestRefreshBehavior(TestSuite.REFRESH_ON_RUN) ||
ts.getTestRefreshBehavior(TestSuite.CLEAR_CHANGED_TEST)) ) {
suppressFinderScan = false;
}
else
suppressFinderScan = state; // i.e. true
}
else
suppressFinderScan = state; // i.e. true
}
boolean isFinderScanSuppressed() {
return suppressFinderScan;
}
/**
* Determine if the path represented by the file is a branch or a leaf.
* This is the semantic equivalent of File.isDirectory(), but shielded
* behind this method for finders which do not use the filesystem.
* @param f The file to check. May not be null.
*/
boolean isBranchFile(File f) {
return finder.isFolder(f);
}
/**
* Determine the last logical time that a file was modified.
* This is the semantic equivalent of File.lastModified(), but shielded
* behind this method for finders which do not use the filesystem.
* @param f The file to check. May not be null.
*/
long getLastModifiedTime(File f) {
// this must be upgraded for binary test finder scanning to work without
// the actual files in the tests directory
return finder.lastModified(f);
}
private class DisassembledUrl {
private String[] data;
private String initStr;
public DisassembledUrl(String str) {
data = StringArray.splitList(str, "/");
initStr = str;
}
}
private static class SortingComparator implements ComparatorTestFinder
.
*/
private String[] sortByName(String[] in) {
if (in == null || in.length <= 1)
return in;
Comparator
*
*
* This method is designed to deprecate lookup(TreeNode, String[]).
*
* @param where Node to start recursive search at.
* @param url The directory name, test URL (TestDescription.getRootRelativeURL), or
* file prefix of a set of test ids (index.html might match {index.html#t1,
* index.html#t2, index.html#t3})
* @return Null if no matches. A TreeNode, or a non-empty set of TestResults.
*/
private static Object[] lookupInitURL(TreeNode where, String url) {
if (where == null || url == null)
throw new IllegalArgumentException("Starting node or URL may not be null!");
if (debug == 2 || debug == 99) {
Debug.println("Starting iurl lookup on " + url + " in " + where.getName());
}
Object simple = lookupNode(where, url);
if (simple != null) {
if (debug == 2 || debug == 99 && simple instanceof TreeNode) {
Debug.println(" -> simple match found " + getRootRelativePath((TreeNode)simple));
}
if (simple instanceof TestResult)
return new TestResult[] {(TestResult)simple};
else
return new TreeNode[] {(TreeNode)simple};
}
// first find the node directly above where we want to search
// if the url is foo/fivetests.html
// that should match foo/fivetests.html#1
// foo/fivetests.html#2
// foo/fivetests.html#3
// foo/fivetests.html#4
// foo/fivetests.html#5
if (debug == 2 || debug == 99) {
Debug.println("TRT looking for tests beginning with " + url + " IN " + where.getName());
Debug.println(" -> retrieving possible TRs from " + betail(url));
}
TreeNode tn = findNode(where, betail(url));
if (tn == null) { // the parent dir of the requested test does not exist
if (debug == 2 || debug == 99)
Debug.println(" -> No parent node found!");
return null;
}
TestResult[] trs = tn.getTestResults();
// found anything?
if (trs == null || trs.length == 0)
return null;
// try to partial match a test
Vectorremove()
is not
* supported.
*/
public interface TreeIterator extends Enumerationnext()
,
* or null if hasNext()
is false.
*/
public abstract Object peek();
/**
* Will the iterator be returning the given node later.
* There is no checking to ensure that the parameter is within the
* iterator's "address space". The comparison is not reference based,
* but location based, so, will the test at the location indicated by
* the given test be evaluated for iterator later? This query is done
* without respect to the filters.
* @param node The test result indicating the location in question.
* @return True if the test in question has not been passed by the
* iterator and may still be returned. False if the given
* test will not subsequently be returned by this iterator.
*/
public abstract boolean isPending(TestResult node);
} // TreeIterator
class Updater implements TestResultCache.Observer
{
//-----methods from TestResultCache.Observer-----
public void update(MapgetNodes()
.
*
* @return The indexes of the corresponding TreeNode at each level. Null if
* no index information is available;
*/
public int[] getIndicies() {
if (inds == null) {
inds = generateInds(tr);
}
return inds;
}
/**
* Get the nodes that represent the path.
*
* @return The path, closest to the root at the beginning of the array.
*/
public TreeNode[] getNodes() {
if (nodes == null) {
nodes = generateNodes(tr);
}
return nodes;
}
/**
* Generate the path to a given test.
*
* @param tr The test to generate the path to.
* @return The path that leads to the given test.
*/
public /* static */ TreeNode[] generateNodes(TestResult tr) {
if (tr == null) return null;
TreeNode[] nodes = null;
TreeNode node = tr.getParent();
while (node != null) {
nodes = DynamicArray.insert(nodes, node, 0);
node = node.getParent();
}
return nodes;
}
private /* static */ int[] generateInds(TestResult tr) {
// XXX implement me!
return null;
}
private TreeNode[] nodes;
private int[] inds;
private TestResult tr;
} // PathRecord
/**
* Remove one directory from the beginning of the path.
*
* @param path The path to manipulate.
* @return Beheaded path, or the same object if there is
* no leading directory to strip.
*/
static String behead(String path) {
int index = path.indexOf("/");
if (index == -1)
return path;
else {
// assume file separator is 1 char
return path.substring(index+1);
//return path.substring(index+File.separator.length());
}
}
/**
* Gives the first directory name in the path.
*
* @param path The path to manipulate.
*/
static String getDirName(String path) {
int index = path.indexOf('/');
if (index == -1)
return path;
else
return path.substring(0, index);
}
/**
* Opposite of behead - removes the last filename.
*
* @param path The path to manipulate.
*/
static String betail(String path) {
int index = path.lastIndexOf('/');
if (index == -1)
return path;
else
return path.substring(0, index);
}
/**
* Does the given array contain the given object.
* @return True if o is in arr, false if arr is null or zero length,or
* does not contain o.
*/
static boolean arrayContains(Object[] arr, Object o) {
if (arr == null || arr.length == 0)
return false;
else {
for (int i = 0; i < arr.length; i++)
if (arr[i] == o)
return true;
}
return false;
}
private ReentrantLock processLock = new ReentrantLock();
public ReentrantLock getLock() {
return processLock;
}
static final Status notYetRun = Status.notRun("test is awaiting execution");
private static int debug = Debug.getInt(TestResultTable.class);
}