blob: f95dd57ceccfd7478d3995562b6feb5694d4d7c2 [file] [log] [blame]
/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.junit;
import com.google.gwt.junit.client.TestResults;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* A message queue to pass data between {@link JUnitShell} and {@link
* com.google.gwt.junit.server.JUnitHostImpl} in a thread-safe manner.
*
* <p>
* The public methods are called by the servlet to find out what test to execute
* next, and to report the results of the last test to run.
* </p>
*
* <p>
* The protected methods are called by the shell to fetch test results and drive
* the next test the client should run.
* </p>
*/
public class JUnitMessageQueue {
/**
* Tracks which test each client is requesting.
*
* Key = client-id (e.g. agent+host) Value = the index of the current
* requested test
*/
private Map/* <String,Integer> */clientTestRequests = new HashMap/* <String,Integer> */();
/**
* The index of the current test being executed.
*/
private int currentTestIndex = -1;
/**
* The number of TestCase clients executing in parallel.
*/
private int numClients = 1;
/**
* The lock used to synchronize access around testMethod, clientTestRequests,
* and currentTestIndex.
*/
private Object readTestLock = new Object();
/**
* The lock used to synchronize access around testResults.
*/
private Object resultsLock = new Object();
/**
* The name of the test class to execute.
*/
private String testClass;
/**
* The name of the test method to execute.
*/
private String testMethod;
/**
* The results for the current test method.
*/
private List/* <TestResults> */testResults = new ArrayList/* <TestResults> */();
/**
* Creates a message queue with one client.
*
* @see JUnitMessageQueue#JUnitMessageQueue(int)
*/
JUnitMessageQueue() {
}
/**
* Only instantiatable within this package.
*
* @param numClients The number of parallel clients being served by this
* queue.
*/
JUnitMessageQueue(int numClients) {
this.numClients = numClients;
}
/**
* Called by the servlet to query for for the next method to test.
*
* @param testClassName The name of the test class.
* @param timeout How long to wait for an answer.
* @return The next test to run, or <code>null</code> if
* <code>timeout</code> is exceeded or the next test does not match
* <code>testClassName</code>.
*/
public String getNextTestName(String clientId, String testClassName,
long timeout) {
synchronized (readTestLock) {
long stopTime = System.currentTimeMillis() + timeout;
while (!testIsAvailableFor(clientId, testClassName)) {
long timeToWait = stopTime - System.currentTimeMillis();
if (timeToWait < 1) {
return null;
}
try {
readTestLock.wait(timeToWait);
} catch (InterruptedException e) {
// just abort
return null;
}
}
if (!testClassName.equals(testClass)) {
// it's an old client that is now done
return null;
}
bumpClientTestRequest(clientId);
return testMethod;
}
}
/**
* Called by the servlet to report the results of the last test to run.
*
* @param testClassName The name of the test class.
* @param results The result of running the test.
*/
public void reportResults(String testClassName, TestResults results) {
synchronized (resultsLock) {
if (!testClassName.equals(testClass)) {
// an old client is trying to report results, do nothing
return;
}
testResults.add(results);
}
}
/**
* Fetches the results of a completed test.
*
* @param testClassName The name of the test class.
* @return An getException thrown from a failed test, or <code>null</code>
* if the test completed without error.
*/
List/* <TestResults> */getResults(String testClassName) {
assert (testClassName.equals(testClass));
return testResults;
}
/**
* Called by the shell to see if the currently-running test has completed.
*
* @param testClassName The name of the test class.
* @return If the test has completed, <code>true</code>, otherwise
* <code>false</code>.
*/
boolean hasResult(String testClassName) {
synchronized (resultsLock) {
assert (testClassName.equals(testClass));
return testResults.size() == numClients;
}
}
/**
* Returns <code>true</code> if all clients have requested the
* currently-running test.
*/
boolean haveAllClientsRetrievedCurrentTest() {
synchronized (readTestLock) {
// If a client hasn't yet contacted, it will have no entry
Collection clientIndices = clientTestRequests.values();
if (clientIndices.size() < numClients) {
return false;
}
// Every client must have been bumped PAST the current test index
for (Iterator it = clientIndices.iterator(); it.hasNext();) {
Integer value = (Integer) it.next();
if (value.intValue() <= currentTestIndex) {
return false;
}
}
return true;
}
}
/**
* Called by the shell to set the name of the next method to run for this test
* class.
*
* @param testClassName The name of the test class.
* @param testName The name of the method to run.
*/
void setNextTestName(String testClassName, String testName) {
synchronized (readTestLock) {
testClass = testClassName;
testMethod = testName;
++currentTestIndex;
testResults = new ArrayList/* <TestResults> */(numClients);
readTestLock.notifyAll();
}
}
/**
* Sets the number of clients that will be executing the JUnit tests in
* parallel.
*
* @param numClients must be > 0
*/
void setNumClients(int numClients) {
this.numClients = numClients;
}
// This method requires that readTestLock is being held for the duration.
private void bumpClientTestRequest(String clientId) {
Integer index = (Integer) clientTestRequests.get(clientId);
clientTestRequests.put(clientId, new Integer(index.intValue() + 1));
}
// This method requires that readTestLock is being held for the duration.
private boolean testIsAvailableFor(String clientId, String testClassName) {
if (!testClassName.equals(testClass)) {
// the "null" test is always available for an old client
return true;
}
Integer index = (Integer) clientTestRequests.get(clientId);
if (index == null) {
index = new Integer(0);
clientTestRequests.put(clientId, index);
}
return index.intValue() == currentTestIndex;
}
}