001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.junit;
017:
018: import com.google.gwt.junit.client.TestResults;
019:
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.List;
024: import java.util.Map;
025:
026: /**
027: * A message queue to pass data between {@link JUnitShell} and {@link
028: * com.google.gwt.junit.server.JUnitHostImpl} in a thread-safe manner.
029: *
030: * <p>
031: * The public methods are called by the servlet to find out what test to execute
032: * next, and to report the results of the last test to run.
033: * </p>
034: *
035: * <p>
036: * The protected methods are called by the shell to fetch test results and drive
037: * the next test the client should run.
038: * </p>
039: */
040: public class JUnitMessageQueue {
041:
042: /**
043: * Tracks which test each client is requesting.
044: *
045: * Key = client-id (e.g. agent+host) Value = the index of the current
046: * requested test
047: */
048: private Map<String, Integer> clientTestRequests = new HashMap<String, Integer>();
049:
050: /**
051: * The index of the current test being executed.
052: */
053: private int currentTestIndex = -1;
054:
055: /**
056: * The number of TestCase clients executing in parallel.
057: */
058: private int numClients = 1;
059:
060: /**
061: * The lock used to synchronize access around testMethod, clientTestRequests,
062: * and currentTestIndex.
063: */
064: private Object readTestLock = new Object();
065:
066: /**
067: * The lock used to synchronize access around testResults.
068: */
069: private Object resultsLock = new Object();
070:
071: /**
072: * The name of the test class to execute.
073: */
074: private String testClass;
075:
076: /**
077: * The name of the test method to execute.
078: */
079: private String testMethod;
080:
081: /**
082: * The results for the current test method.
083: */
084: private List<TestResults> testResults = new ArrayList<TestResults>();
085:
086: /**
087: * Creates a message queue with one client.
088: *
089: * @see JUnitMessageQueue#JUnitMessageQueue(int)
090: */
091: JUnitMessageQueue() {
092: }
093:
094: /**
095: * Only instantiatable within this package.
096: *
097: * @param numClients The number of parallel clients being served by this
098: * queue.
099: */
100: JUnitMessageQueue(int numClients) {
101: this .numClients = numClients;
102: }
103:
104: /**
105: * Called by the servlet to query for for the next method to test.
106: *
107: * @param testClassName The name of the test class.
108: * @param timeout How long to wait for an answer.
109: * @return The next test to run, or <code>null</code> if
110: * <code>timeout</code> is exceeded or the next test does not match
111: * <code>testClassName</code>.
112: */
113: public String getNextTestName(String clientId,
114: String testClassName, long timeout) {
115: synchronized (readTestLock) {
116: long stopTime = System.currentTimeMillis() + timeout;
117: while (!testIsAvailableFor(clientId, testClassName)) {
118: long timeToWait = stopTime - System.currentTimeMillis();
119: if (timeToWait < 1) {
120: return null;
121: }
122: try {
123: readTestLock.wait(timeToWait);
124: } catch (InterruptedException e) {
125: // just abort
126: return null;
127: }
128: }
129:
130: if (!testClassName.equals(testClass)) {
131: // it's an old client that is now done
132: return null;
133: }
134:
135: bumpClientTestRequest(clientId);
136: return testMethod;
137: }
138: }
139:
140: /**
141: * Called by the servlet to report the results of the last test to run.
142: *
143: * @param testClassName The name of the test class.
144: * @param results The result of running the test.
145: */
146: public void reportResults(String testClassName, TestResults results) {
147: synchronized (resultsLock) {
148: if (!testClassName.equals(testClass)) {
149: // an old client is trying to report results, do nothing
150: return;
151: }
152: testResults.add(results);
153: }
154: }
155:
156: /**
157: * Fetches the results of a completed test.
158: *
159: * @param testClassName The name of the test class.
160: * @return An getException thrown from a failed test, or <code>null</code>
161: * if the test completed without error.
162: */
163: List<TestResults> getResults(String testClassName) {
164: assert (testClassName.equals(testClass));
165: return testResults;
166: }
167:
168: /**
169: * Called by the shell to see if the currently-running test has completed.
170: *
171: * @param testClassName The name of the test class.
172: * @return If the test has completed, <code>true</code>, otherwise
173: * <code>false</code>.
174: */
175: boolean hasResult(String testClassName) {
176: synchronized (resultsLock) {
177: assert (testClassName.equals(testClass));
178: return testResults.size() == numClients;
179: }
180: }
181:
182: /**
183: * Returns <code>true</code> if all clients have requested the
184: * currently-running test.
185: */
186: boolean haveAllClientsRetrievedCurrentTest() {
187: synchronized (readTestLock) {
188: // If a client hasn't yet contacted, it will have no entry
189: Collection<Integer> clientIndices = clientTestRequests
190: .values();
191: if (clientIndices.size() < numClients) {
192: return false;
193: }
194: // Every client must have been bumped PAST the current test index
195: for (Integer value : clientIndices) {
196: if (value <= currentTestIndex) {
197: return false;
198: }
199: }
200: return true;
201: }
202: }
203:
204: /**
205: * Called by the shell to set the name of the next method to run for this test
206: * class.
207: *
208: * @param testClassName The name of the test class.
209: * @param testName The name of the method to run.
210: */
211: void setNextTestName(String testClassName, String testName) {
212: synchronized (readTestLock) {
213: testClass = testClassName;
214: testMethod = testName;
215: ++currentTestIndex;
216: testResults = new ArrayList<TestResults>(numClients);
217: readTestLock.notifyAll();
218: }
219: }
220:
221: /**
222: * Sets the number of clients that will be executing the JUnit tests in
223: * parallel.
224: *
225: * @param numClients must be > 0
226: */
227: void setNumClients(int numClients) {
228: this .numClients = numClients;
229: }
230:
231: // This method requires that readTestLock is being held for the duration.
232: private void bumpClientTestRequest(String clientId) {
233: Integer index = clientTestRequests.get(clientId);
234: clientTestRequests.put(clientId, index + 1);
235: }
236:
237: // This method requires that readTestLock is being held for the duration.
238: private boolean testIsAvailableFor(String clientId,
239: String testClassName) {
240: if (!testClassName.equals(testClass)) {
241: // the "null" test is always available for an old client
242: return true;
243: }
244: Integer index = clientTestRequests.get(clientId);
245: if (index == null) {
246: index = 0;
247: clientTestRequests.put(clientId, index);
248: }
249: return index == currentTestIndex;
250: }
251: }
|