| /* |
| * Copyright 2008 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.impl.JUnitResult; |
| import com.google.gwt.junit.client.impl.JUnitHost.TestInfo; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| 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 testResult. |
| */ |
| private Object resultsLock = new Object(); |
| |
| /** |
| * The name of the module to execute. |
| */ |
| private String testModule; |
| |
| /** |
| * 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<JUnitResult> testResult = new ArrayList<JUnitResult>(); |
| |
| /** |
| * Creates a message queue with one client. |
| * |
| * @see JUnitMessageQueue#JUnitMessageQueue(int) |
| */ |
| JUnitMessageQueue() { |
| } |
| |
| /** |
| * Only instantiable 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 moduleName the name of the executing module |
| * @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 TestInfo getNextTestInfo(String clientId, String moduleName, |
| long timeout) { |
| synchronized (readTestLock) { |
| long stopTime = System.currentTimeMillis() + timeout; |
| while (!testIsAvailableFor(clientId, moduleName)) { |
| long timeToWait = stopTime - System.currentTimeMillis(); |
| if (timeToWait < 1) { |
| return null; |
| } |
| try { |
| readTestLock.wait(timeToWait); |
| } catch (InterruptedException e) { |
| // just abort |
| return null; |
| } |
| } |
| |
| if (!moduleName.equals(testModule)) { |
| // it's an old client that is now done |
| return null; |
| } |
| |
| bumpClientTestRequest(clientId); |
| return new TestInfo(testClass, testMethod); |
| } |
| } |
| |
| /** |
| * Called by the servlet to report the results of the last test to run. |
| * |
| * @param moduleName the name of the test module |
| * @param results the result of running the test |
| */ |
| public void reportResults(String moduleName, JUnitResult results) { |
| synchronized (resultsLock) { |
| if (!moduleName.equals(testModule)) { |
| // an old client is trying to report results, do nothing |
| return; |
| } |
| testResult.add(results); |
| } |
| } |
| |
| /** |
| * Fetches the results of a completed test. |
| * |
| * @param moduleName the name of the test module |
| * @return An getException thrown from a failed test, or <code>null</code> |
| * if the test completed without error. |
| */ |
| List<JUnitResult> getResults(String moduleName) { |
| assert (moduleName.equals(testModule)); |
| return testResult; |
| } |
| |
| /** |
| * Called by the shell to see if the currently-running test has completed. |
| * |
| * @param moduleName the name of the test module |
| * @return If the test has completed, <code>true</code>, otherwise |
| * <code>false</code>. |
| */ |
| boolean hasResult(String moduleName) { |
| synchronized (resultsLock) { |
| assert (moduleName.equals(testModule)); |
| return testResult.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<Integer> clientIndices = clientTestRequests.values(); |
| if (clientIndices.size() < numClients) { |
| return false; |
| } |
| // Every client must have been bumped PAST the current test index |
| for (Integer value : clientIndices) { |
| if (value <= 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 testModule, String testClass, String testMethod) { |
| synchronized (readTestLock) { |
| this.testModule = testModule; |
| this.testClass = testClass; |
| this.testMethod = testMethod; |
| ++currentTestIndex; |
| testResult = new ArrayList<JUnitResult>(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 = clientTestRequests.get(clientId); |
| clientTestRequests.put(clientId, index + 1); |
| } |
| |
| // This method requires that readTestLock is being held for the duration. |
| private boolean testIsAvailableFor(String clientId, String moduleName) { |
| if (!moduleName.equals(testModule)) { |
| // the "null" test is always available for an old client |
| return true; |
| } |
| Integer index = clientTestRequests.get(clientId); |
| if (index == null) { |
| index = 0; |
| clientTestRequests.put(clientId, index); |
| } |
| return index == currentTestIndex; |
| } |
| } |