Patch adds the infrastructure to re-run a test. Useful for htmlUnit which can
be flaky sometimes.
Patch by: amitmanjhi
Review by: jlabanca
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6687 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/JUnitMessageQueue.java b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
index 4662c5a..0137956 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.Map.Entry;
/**
* A message queue to pass data between {@link JUnitShell} and
@@ -395,6 +396,38 @@
}
}
+ /*
+ * Returns true iff any there are no results, missing results, or any of the
+ * test results is an exception other than JUnitFatalLaunchException.
+ */
+ boolean needsRerunning(TestInfo testInfo) {
+ Map<String, JUnitResult> results = getResults(testInfo);
+ if (results == null) {
+ return true;
+ }
+ if (results.size() != numClients) {
+ return true;
+ }
+ for (Entry<String, JUnitResult> entry : results.entrySet()) {
+ JUnitResult result = entry.getValue();
+ if (result == null) {
+ return true;
+ }
+ Throwable exception = result.getException();
+ if (exception != null
+ && !(exception instanceof JUnitFatalLaunchException)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void removeResults(TestInfo testInfo) {
+ synchronized (clientStatusesLock) {
+ testResults.remove(testInfo);
+ }
+ }
+
void waitForResults(int millis) {
synchronized (clientStatusesLock) {
try {
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 93aa231..36b420d 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -241,7 +241,6 @@
}
});
- // TODO: currently, only two values but soon may have multiple values.
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
@@ -578,7 +577,7 @@
// TODO: install a shutdown hook? Not necessary with GWTShell.
unitTestShell.lastLaunchFailed = false;
}
-
+ unitTestShell.checkArgs();
return unitTestShell;
}
@@ -861,8 +860,8 @@
/**
* Accessor method to HostedModeBase.setHeadless -- without this, we get
- * IllegalAccessError from the -notHeadless arg handler. Compiler bug?
- *
+ * IllegalAccessError from the -notHeadless arg handler. Compiler bug?
+ *
* @param headlessMode
*/
void setHeadlessAccessor(boolean headlessMode) {
@@ -879,11 +878,17 @@
void setNumClients(int numClients) {
this.numClients = numClients;
}
-
+
void setStandardsMode(boolean standardsMode) {
this.standardsMode = standardsMode;
}
+ private void checkArgs() {
+ if (runStyle.getTries() > 1 && !(batchingStrategy instanceof NoBatchingStrategy)) {
+ throw new JUnitFatalLaunchException("Batching does not work with tries > 1");
+ }
+ }
+
/**
* Create the specified (or default) runStyle.
*
@@ -934,6 +939,20 @@
&& bannedPlatforms.contains(Platform.HtmlUnit);
}
+ private boolean mustRetry(int numTries) {
+ if (numTries >= runStyle.getTries()) {
+ return false;
+ }
+ assert (batchingStrategy instanceof NoBatchingStrategy);
+ // checked in {@code checkArgs()}
+ /*
+ * If a batching strategy is being used, the client will already have moved
+ * passed the failed test case. The whole block could be re-executed, but it
+ * would be more complicated.
+ */
+ return true;
+ }
+
private void processTestResult(TestCase testCase, TestResult testResult,
Strategy strategy) {
@@ -978,10 +997,15 @@
}
}
+ private void runTestImpl(GWTTestCase testCase, TestResult testResult)
+ throws UnableToCompleteException {
+ runTestImpl(testCase, testResult, 0);
+ }
+
/**
* Runs a particular test case.
*/
- private void runTestImpl(GWTTestCase testCase, TestResult testResult)
+ private void runTestImpl(GWTTestCase testCase, TestResult testResult, int numTries)
throws UnableToCompleteException {
testBatchingMethodTimeoutMillis = batchingStrategy.getTimeoutMultiplier()
@@ -1021,6 +1045,7 @@
currentTestInfo = new TestInfo(currentModule.getName(),
testCase.getClass().getName(), testCase.getName());
+ numTries++;
if (messageQueue.hasResults(currentTestInfo)) {
// Already have a result.
processTestResult(testCase, testResult, strategy);
@@ -1038,6 +1063,7 @@
return;
}
+ boolean mustRetry = mustRetry(numTries);
// Wait for test to complete
try {
// Set a timeout period to automatically fail if the servlet hasn't been
@@ -1048,17 +1074,29 @@
while (notDone()) {
messageQueue.waitForResults(1000);
}
- if (pendingException != null) {
+ if (!mustRetry && pendingException != null) {
UnableToCompleteException e = pendingException;
pendingException = null;
throw e;
}
} catch (TimeoutException e) {
- lastLaunchFailed = true;
- testResult.addError(testCase, e);
- return;
+ if (!mustRetry) {
+ lastLaunchFailed = true;
+ testResult.addError(testCase, e);
+ return;
+ }
}
+ if (mustRetry) {
+ if (messageQueue.needsRerunning(currentTestInfo)) {
+ // remove the result if it is present and rerun
+ messageQueue.removeResults(currentTestInfo);
+ getTopLogger().log(TreeLogger.WARN,
+ currentTestInfo + " is being retried, retry attempt = " + numTries);
+ runTestImpl(testCase, testResult, numTries);
+ return;
+ }
+ }
assert (messageQueue.hasResults(currentTestInfo));
processTestResult(testCase, testResult, testCase.getStrategy());
}
diff --git a/user/src/com/google/gwt/junit/RunStyle.java b/user/src/com/google/gwt/junit/RunStyle.java
index d054429..e6d7b94 100644
--- a/user/src/com/google/gwt/junit/RunStyle.java
+++ b/user/src/com/google/gwt/junit/RunStyle.java
@@ -40,6 +40,14 @@
}
/**
+ * Returns the number of times this test should be tried to run. A test
+ * succeeds if it succeeds even once.
+ */
+ public int getTries() {
+ return 1;
+ }
+
+ /**
* Initialize the runstyle with any supplied arguments.
*
* @param args arguments passed in -runStyle option, null if none supplied
diff --git a/user/src/com/google/gwt/junit/RunStyleHtmlUnit.java b/user/src/com/google/gwt/junit/RunStyleHtmlUnit.java
index 1923bf5..45e085f 100644
--- a/user/src/com/google/gwt/junit/RunStyleHtmlUnit.java
+++ b/user/src/com/google/gwt/junit/RunStyleHtmlUnit.java
@@ -186,6 +186,16 @@
}
@Override
+ public int getTries() {
+ // TODO: set this via the command-line
+ /*
+ * as long as this number is greater than 1, GWTTestCaseTest::testRetry will
+ * pass
+ */
+ return 3;
+ }
+
+ @Override
public boolean initialize(String args) {
if (args == null || args.length() == 0) {
// If no browsers specified, default to Firefox 3.
diff --git a/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java b/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
index 0d34966..f8cd726 100644
--- a/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
+++ b/user/test/com/google/gwt/junit/JUnitMessageQueueTest.java
@@ -31,6 +31,10 @@
*/
public class JUnitMessageQueueTest extends TestCase {
+ private final static int TWO_CLIENTS = 2;
+ private final static int ONE_BLOCK = 1;
+ private final static int ONE_TEST_PER_BLOCK = 1;
+
public void testAddTestBlocks() {
JUnitMessageQueue queue = new JUnitMessageQueue(10);
assertEquals(0, queue.getTestBlocks().size());
@@ -171,7 +175,6 @@
}
public void testGetResults() {
- final long timeout = System.currentTimeMillis() + 15;
JUnitMessageQueue queue = createQueue(3, 2, 3);
TestInfo[] testBlock0 = queue.getTestBlocks().get(0);
TestInfo test0_0 = testBlock0[0];
@@ -273,7 +276,6 @@
}
public void testHasResults() {
- final long timeout = System.currentTimeMillis() + 15;
JUnitMessageQueue queue = createQueue(3, 2, 3);
TestInfo[] testBlock0 = queue.getTestBlocks().get(0);
TestInfo test0_0 = testBlock0[0];
@@ -343,6 +345,49 @@
}
}
+ public void testNeedRerunningExceptions() {
+ JUnitMessageQueue queue = createQueue(TWO_CLIENTS, ONE_BLOCK,
+ ONE_TEST_PER_BLOCK);
+ // an exception
+ TestInfo testInfo = queue.getTestBlocks().get(0)[0];
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ JUnitResult junitResult = new JUnitResult();
+ junitResult.setException(new AssertionError());
+ results.put(testInfo, junitResult);
+ queue.reportResults("client0", "ie6", results);
+ results = new HashMap<TestInfo, JUnitResult>();
+ junitResult = new JUnitResult();
+ junitResult.setException(new JUnitFatalLaunchException());
+ results.put(testInfo, junitResult);
+ queue.reportResults("client1", "ff3", createTestResults(ONE_TEST_PER_BLOCK));
+ assertTrue(queue.needsRerunning(testInfo));
+
+ // an exception but exception in launch module
+ queue.removeResults(testInfo);
+ results = new HashMap<TestInfo, JUnitResult>();
+ junitResult = new JUnitResult();
+ junitResult.setException(new JUnitFatalLaunchException());
+ results.put(testInfo, junitResult);
+ queue.reportResults("client0", "ie6", results);
+ queue.reportResults("client1", "ff3", createTestResults(ONE_TEST_PER_BLOCK));
+ assertFalse(queue.needsRerunning(testInfo));
+ }
+
+ public void testNeedRerunningIncompleteResults() {
+ JUnitMessageQueue queue = createQueue(TWO_CLIENTS, ONE_BLOCK,
+ ONE_TEST_PER_BLOCK);
+ TestInfo testInfo = queue.getTestBlocks().get(0)[0];
+
+ // incomplete results
+ assertTrue(queue.needsRerunning(testInfo));
+ queue.reportResults("client0", "ff3", createTestResults(1));
+ assertTrue(queue.needsRerunning(testInfo));
+
+ // complete results
+ queue.reportResults("client1", "ie7", createTestResults(1));
+ assertFalse(queue.needsRerunning(testInfo));
+ }
+
public void testNewClients() {
final long timeout = System.currentTimeMillis() + 15;
JUnitMessageQueue queue = createQueue(15, 1, 1);
@@ -377,6 +422,42 @@
}
}
+ public void testRemove() {
+ JUnitMessageQueue queue = createQueue(TWO_CLIENTS, ONE_BLOCK,
+ ONE_TEST_PER_BLOCK);
+ TestInfo testInfo = queue.getTestBlocks().get(0)[0];
+ assertFalse(queue.hasResults(testInfo));
+
+ queue.reportResults("client0", "ie6", createTestResults(ONE_TEST_PER_BLOCK));
+ assertFalse(queue.hasResults(testInfo));
+ queue.reportResults("client1", "ff3", createTestResults(ONE_TEST_PER_BLOCK));
+ assertTrue(queue.hasResults(testInfo));
+
+ queue.removeResults(testInfo);
+ assertFalse(queue.hasResults(testInfo));
+ }
+
+ public void testRetries() {
+ JUnitMessageQueue queue = createQueue(TWO_CLIENTS, ONE_BLOCK,
+ ONE_TEST_PER_BLOCK);
+ TestInfo testInfo = queue.getTestBlocks().get(0)[0];
+ Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
+ JUnitResult junitResult = new JUnitResult();
+ junitResult.setException(new AssertionError());
+ results.put(testInfo, junitResult);
+ queue.reportResults("client0", "ff3", results);
+ assertTrue(queue.needsRerunning(testInfo));
+ assertTrue(queue.getResults(testInfo).get("client0").getException() != null);
+
+ queue.removeResults(testInfo);
+
+ queue.reportResults("client0", "ff3", createTestResults(ONE_TEST_PER_BLOCK));
+ queue.reportResults("client1", "ie6", createTestResults(ONE_TEST_PER_BLOCK));
+ assertFalse(queue.needsRerunning(testInfo));
+ // check that the updated result appears now.
+ assertTrue(queue.getResults(testInfo).get("client0").getException() == null);
+ }
+
/**
* Assert that two arrays are the same size and contain the same elements.
* Ordering does not matter.
@@ -442,7 +523,7 @@
private Map<TestInfo, JUnitResult> createTestResults(int numTests) {
Map<TestInfo, JUnitResult> results = new HashMap<TestInfo, JUnitResult>();
for (int i = 0; i < numTests; i++) {
- TestInfo testInfo = new TestInfo("testModule", "testClass", "testMethod"
+ TestInfo testInfo = new TestInfo("testModule0", "testClass", "testMethod"
+ i);
results.put(testInfo, new JUnitResult());
}
diff --git a/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java b/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
index f3e32a1..3a7e091 100644
--- a/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
+++ b/user/test/com/google/gwt/junit/client/GWTTestCaseTest.java
@@ -24,6 +24,8 @@
import static com.google.gwt.junit.client.GWTTestCaseTest.SetUpTearDownState.IS_SETUP;
import static com.google.gwt.junit.client.GWTTestCaseTest.SetUpTearDownState.IS_TORNDOWN;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
import com.google.gwt.user.client.Timer;
import junit.framework.AssertionFailedError;
@@ -47,6 +49,13 @@
*/
private static String outOfBandError = null;
+ /**
+ * These two variables test the retry functionality, currently used with
+ * HtmlUnit.
+ */
+ private static boolean attemptedOnce = false;
+ private static boolean htmlunitMode = true;
+
private static void assertNotEquals(double a, double b, double delta) {
boolean failed = false;
try {
@@ -357,6 +366,27 @@
fail("Unexpected exception during assertTrue(String, boolean) testing");
}
+ /*
+ * Just setting the htmlunit mode.
+ */
+ @DoNotRunWith(Platform.HtmlUnit)
+ public void testSetRetry() {
+ htmlunitMode = false;
+ }
+
+ /*
+ * This test MUST appear after testSetRetry().
+ */
+ public void testRetry() {
+ if (htmlunitMode && !attemptedOnce) {
+ // fail
+ attemptedOnce = true;
+ assertTrue(false);
+ } else {
+ assertTrue(true);
+ }
+ }
+
public void testSetUpTearDown() throws Exception {
assertSame(IS_SETUP, setupTeardownFlag);
tearDown();