You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2015/08/21 22:29:34 UTC
[7/9] incubator-geode git commit: Test framework refactoring
Test framework refactoring
Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/33d2c1c8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/33d2c1c8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/33d2c1c8
Branch: refs/heads/feature/GEODE-217
Commit: 33d2c1c86e0532f1b913f6728be85fdd2cf68782
Parents: fcd2142
Author: Kirk Lund <kl...@pivotal.io>
Authored: Fri Aug 21 13:17:45 2015 -0700
Committer: Kirk Lund <kl...@pivotal.io>
Committed: Fri Aug 21 13:17:45 2015 -0700
----------------------------------------------------------------------
.../internal/lang/reflect/ReflectionUtils.java | 41 +++++
.../lang/reflect/ReflectionUtilsJUnitTest.java | 78 +++++++++
.../test/dunit/DistributedSystemSupport.java | 102 ++++++++++++
.../gemfire/test/dunit/NetworkSupport.java | 23 +++
.../test/dunit/tests/DUnitTestSuite.java | 16 ++
.../tests/DistributedTestNameDUnitTest.java | 75 +++++++++
.../gemfire/test/dunit/tests/MyTestSuite.java | 23 +++
.../gemfire/test/golden/GoldenTestSuite.java | 27 ++++
.../com/gemstone/gemfire/test/junit/Retry.java | 17 ++
.../gemfire/test/junit/rules/RetryRule.java | 161 +++++++++++++++++++
10 files changed, 563 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/main/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtils.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtils.java b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtils.java
new file mode 100755
index 0000000..0b5fee2
--- /dev/null
+++ b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtils.java
@@ -0,0 +1,41 @@
+package com.gemstone.gemfire.internal.lang.reflect;
+
+/**
+ * Utility class for helping in various reflection operations. See the
+ * java.lang.reflect package for the classes that this class utilizes.
+ *
+ * TODO: centralize methods from these classes to here:
+ * <li>com.gemstone.gemfire.management.internal.cli.util.spring.ReflectionUtils
+ * <li>com.gemstone.gemfire.internal.logging.LogService
+ * <li>com.gemstone.gemfire.internal.tools.gfsh.app.misc.util.ReflectionUtil
+ *
+ * @author Kirk Lund
+ * @see com.gemstone.gemfire.internal.tools.gfsh.app.misc.util.ReflectionUtil
+ * @see com.gemstone.gemfire.internal.logging.LogService
+ * @see com.gemstone.gemfire.management.internal.cli.util.spring.ReflectionUtils
+ */
+public abstract class ReflectionUtils {
+
+ /**
+ * Gets the class name of the caller in the current stack at the given {@code depth}.
+ *
+ * @param depth a 0-based index in the current stack.
+ * @return a class name
+ */
+ public static String getClassName(final int depth) {
+ return Thread.currentThread().getStackTrace()[depth].getClassName();
+ }
+
+ public static String getClassName() {
+ return Thread.currentThread().getStackTrace()[2].getClassName();
+ }
+
+ public static String getMethodName(final int depth) {
+ return Thread.currentThread().getStackTrace()[depth].getMethodName();
+ }
+
+ public static String getMethodName() {
+ return Thread.currentThread().getStackTrace()[2].getMethodName();
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtilsJUnitTest.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtilsJUnitTest.java b/gemfire-core/src/test/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtilsJUnitTest.java
new file mode 100755
index 0000000..346dc75
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/internal/lang/reflect/ReflectionUtilsJUnitTest.java
@@ -0,0 +1,78 @@
+package com.gemstone.gemfire.internal.lang.reflect;
+
+import static com.gemstone.gemfire.internal.lang.reflect.ReflectionUtils.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import com.gemstone.gemfire.test.junit.categories.UnitTest;
+
+/**
+ * Unit tests for the ReflectionUtils class.
+ *
+ * @author Kirk Lund
+ */
+@Category(UnitTest.class)
+public class ReflectionUtilsJUnitTest {
+
+ @Rule
+ public TestWatcher watchman = new TestWatcher() {
+ @Override
+ protected void starting(final Description description) {
+ testClassName = description.getClassName();
+ testMethodName = description.getMethodName();
+ }
+ };
+
+ private String testClassName;
+ private String testMethodName;
+
+ @Test
+ public void getClassNameZeroShouldReturnReflectionUtilsClass() {
+ assertThat(getClassName(0), is(Thread.class.getName()));
+ }
+
+ @Test
+ public void getClassNameOneShouldReturnReflectionUtilsClass() {
+ assertThat(getClassName(1), is(ReflectionUtils.class.getName()));
+ }
+
+ @Test
+ public void getClassNameTwoShouldReturnReflectionUtilsClass() {
+ assertThat(getClassName(2), is(getClass().getName()));
+ assertThat(getClassName(2), is(this.testClassName));
+ }
+
+ @Test
+ public void getClassNameShouldReturnReflectionUtilsClass() {
+ assertThat(getClassName(), is(getClass().getName()));
+ assertThat(getClassName(), is(this.testClassName));
+ }
+
+ @Test
+ public void getMethodNameZeroShouldReturnGetStackTrace() {
+ assertThat(getMethodName(0), is("getStackTrace"));
+ }
+
+ @Test
+ public void getMethodNameOneShouldReturnGetMethodName() {
+ assertThat(getMethodName(1), is("getMethodName"));
+ }
+
+ @Test
+ public void getMethodNameTwoShouldReturnThisMethod() {
+ assertThat(getMethodName(2), is("getMethodNameTwoShouldReturnThisMethod"));
+ assertThat(getMethodName(2), is(this.testMethodName));
+ }
+
+ @Test
+ public void getMethodNameShouldReturnThisMethod() {
+ assertThat(getMethodName(), is("getMethodNameShouldReturnThisMethod"));
+ assertThat(getMethodName(), is(this.testMethodName));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/DistributedSystemSupport.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/DistributedSystemSupport.java b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/DistributedSystemSupport.java
new file mode 100755
index 0000000..9c52e46
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/DistributedSystemSupport.java
@@ -0,0 +1,102 @@
+package com.gemstone.gemfire.test.dunit;
+
+import static com.gemstone.gemfire.test.dunit.Wait.waitForCriterion;
+
+import java.io.File;
+
+import com.gemstone.gemfire.distributed.DistributedSystem;
+import com.gemstone.gemfire.distributed.internal.InternalDistributedSystem;
+import com.gemstone.gemfire.distributed.internal.membership.jgroup.MembershipManagerHelper;
+import com.gemstone.org.jgroups.Event;
+import com.gemstone.org.jgroups.JChannel;
+import com.gemstone.org.jgroups.stack.Protocol;
+
+public class DistributedSystemSupport {
+
+ protected DistributedSystemSupport() {
+ }
+
+ /**
+ * Crash the cache in the given VM in such a way that it immediately stops communicating with
+ * peers. This forces the VM's membership manager to throw a ForcedDisconnectException by
+ * forcibly terminating the JGroups protocol stack with a fake EXIT event.<p>
+ *
+ * NOTE: if you use this method be sure that you clean up the VM before the end of your
+ * test with disconnectFromDS() or disconnectAllFromDS().
+ */
+ public static boolean crashDistributedSystem(VM vm) { // TODO: move
+ return (Boolean)vm.invoke(new SerializableCallable("crash distributed system") {
+ public Object call() throws Exception {
+ DistributedSystem msys = InternalDistributedSystem.getAnyInstance();
+ crashDistributedSystem(msys);
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Crash the cache in the given VM in such a way that it immediately stops communicating with
+ * peers. This forces the VM's membership manager to throw a ForcedDisconnectException by
+ * forcibly terminating the JGroups protocol stack with a fake EXIT event.<p>
+ *
+ * NOTE: if you use this method be sure that you clean up the VM before the end of your
+ * test with disconnectFromDS() or disconnectAllFromDS().
+ */
+ public static void crashDistributedSystem(final DistributedSystem msys) { // TODO: move
+ MembershipManagerHelper.inhibitForcedDisconnectLogging(true);
+ MembershipManagerHelper.playDead(msys);
+ JChannel c = MembershipManagerHelper.getJChannel(msys);
+ Protocol udp = c.getProtocolStack().findProtocol("UDP");
+ udp.stop();
+ udp.passUp(new Event(Event.EXIT, new RuntimeException("killing member's ds")));
+ try {
+ MembershipManagerHelper.getJChannel(msys).waitForClose();
+ }
+ catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ // attempt rest of work with interrupt bit set
+ }
+ MembershipManagerHelper.inhibitForcedDisconnectLogging(false);
+ WaitCriterion wc = new WaitCriterion() {
+ public boolean done() {
+ return !msys.isConnected();
+ }
+ public String description() {
+ return "waiting for distributed system to finish disconnecting: " + msys;
+ }
+ };
+// try {
+ waitForCriterion(wc, 10000, 1000, true);
+// } finally {
+// dumpMyThreads(getLogWriter());
+// }
+ }
+
+ /** get the host name to use for a server cache in client/server dunit
+ * testing
+ * @param host
+ * @return the host name
+ */
+ public static String getServerHostName(Host host) {
+ return System.getProperty("gemfire.server-bind-address") != null?
+ System.getProperty("gemfire.server-bind-address")
+ : host.getHostName();
+ }
+
+ /**
+ * Delete locator state files. Use this after getting a random port
+ * to ensure that an old locator state file isn't picked up by the
+ * new locator you're starting.
+ *
+ * @param ports
+ */
+ public static void deleteLocatorStateFile(final int... ports) {
+ for (int i=0; i<ports.length; i++) {
+ final File stateFile = new File("locator"+ports[i]+"state.dat");
+ if (stateFile.exists()) {
+ stateFile.delete();
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/NetworkSupport.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/NetworkSupport.java b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/NetworkSupport.java
new file mode 100755
index 0000000..f702b4e
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/NetworkSupport.java
@@ -0,0 +1,23 @@
+package com.gemstone.gemfire.test.dunit;
+
+import java.net.UnknownHostException;
+
+import com.gemstone.gemfire.internal.SocketCreator;
+
+public class NetworkSupport {
+
+ protected NetworkSupport() {
+ }
+
+ /** get the IP literal name for the current host, use this instead of
+ * "localhost" to avoid IPv6 name resolution bugs in the JDK/machine config.
+ * @return an ip literal, this method honors java.net.preferIPvAddresses
+ */
+ public static String getIPLiteral() { // TODO: move
+ try {
+ return SocketCreator.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ throw new Error("problem determining host IP address", e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DUnitTestSuite.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DUnitTestSuite.java b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DUnitTestSuite.java
new file mode 100755
index 0000000..3c9fe83
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DUnitTestSuite.java
@@ -0,0 +1,16 @@
+package com.gemstone.gemfire.test.dunit.tests;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ BasicDUnitTest.class,
+ DistributedTestNameDUnitTest.class,
+ VMDUnitTest.class,
+})
+/**
+ * Suite of tests for the test.dunit DUnit Test framework.
+ */
+public class DUnitTestSuite {
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DistributedTestNameDUnitTest.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DistributedTestNameDUnitTest.java b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DistributedTestNameDUnitTest.java
new file mode 100755
index 0000000..d6afc02
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/DistributedTestNameDUnitTest.java
@@ -0,0 +1,75 @@
+package com.gemstone.gemfire.test.dunit.tests;
+
+import static com.gemstone.gemfire.test.dunit.Invoke.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import com.gemstone.gemfire.internal.lang.reflect.ReflectionUtils;
+import com.gemstone.gemfire.test.dunit.DistributedTestCase;
+import com.gemstone.gemfire.test.dunit.SerializableRunnable;
+import com.gemstone.gemfire.test.junit.categories.DistributedTest;
+
+/**
+ * Verifies that test name is available and consistent in the controller JVM
+ * and all 4 dunit JVMs.
+ *
+ * @author Kirk Lund
+ */
+@Category(DistributedTest.class)
+public class DistributedTestNameDUnitTest extends DistributedTestCase {
+ private static final long serialVersionUID = 1L;
+
+ // TODO: remove transient and fix bug so test FAILs fast
+
+ @Rule
+ public transient TestWatcher watchman = new TestWatcher() {
+ protected void starting(final Description description) {
+ testClassName = description.getClassName();
+ testMethodName = description.getMethodName();
+ }
+ };
+
+ private String testClassName;
+ private String testMethodName;
+
+ @Test
+ public void testNameShouldBeConsistentInAllJVMs() throws Exception {
+ final String methodName = this.testMethodName;
+
+ // JUnit Rule provides getMethodName in Controller JVM
+ assertThat(getMethodName(), is(methodName));
+
+ // Controller JVM sets testName = getMethodName in itself and all 4 other JVMs
+ assertThat(getTestName(), is(methodName));
+
+ invokeInEveryVM(new SerializableRunnable(getMethodName()) {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void run() {
+ assertThat(getTestName(), is(methodName));
+ }
+ });
+ }
+
+ @Test
+ public void uniqueNameShouldBeConsistentInAllJVMs() throws Exception {
+ //final String uniqueName = testClassName + "_" + testMethodName;
+ final String uniqueName = getClass().getSimpleName() + "_" + testMethodName;
+
+ assertThat(getUniqueName(), is(uniqueName));
+
+ invokeInEveryVM(new SerializableRunnable(getMethodName()) {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void run() {
+ assertThat(getUniqueName(), is(uniqueName));
+ }
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/MyTestSuite.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/MyTestSuite.java b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/MyTestSuite.java
new file mode 100755
index 0000000..ec90a36
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/test/dunit/tests/MyTestSuite.java
@@ -0,0 +1,23 @@
+package com.gemstone.gemfire.test.dunit.tests;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import com.gemstone.gemfire.distributed.DistributedMemberDUnitTest;
+import com.gemstone.gemfire.distributed.HostedLocatorsDUnitTest;
+import com.gemstone.gemfire.internal.offheap.OutOfOffHeapMemoryDUnitTest;
+import com.gemstone.gemfire.test.catchexception.CatchExceptionExampleDUnitTest;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ BasicDUnitTest.class,
+ DistributedTestNameDUnitTest.class,
+ VMDUnitTest.class,
+
+ CatchExceptionExampleDUnitTest.class,
+ DistributedMemberDUnitTest.class,
+ HostedLocatorsDUnitTest.class,
+ OutOfOffHeapMemoryDUnitTest.class,
+})
+public class MyTestSuite {
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-core/src/test/java/com/gemstone/gemfire/test/golden/GoldenTestSuite.java
----------------------------------------------------------------------
diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/test/golden/GoldenTestSuite.java b/gemfire-core/src/test/java/com/gemstone/gemfire/test/golden/GoldenTestSuite.java
new file mode 100755
index 0000000..ef2686e
--- /dev/null
+++ b/gemfire-core/src/test/java/com/gemstone/gemfire/test/golden/GoldenTestSuite.java
@@ -0,0 +1,27 @@
+package com.gemstone.gemfire.test.golden;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ FailWithErrorInOutputJUnitTest.class,
+ FailWithExtraLineInOutputJUnitTest.class,
+ FailWithLineMissingFromEndOfOutputJUnitTest.class,
+ FailWithLineMissingFromMiddleOfOutputJUnitTest.class,
+ FailWithLoggerErrorInOutputJUnitTest.class,
+ FailWithLoggerFatalInOutputJUnitTest.class,
+ FailWithLoggerWarnInOutputJUnitTest.class,
+ FailWithSevereInOutputJUnitTest.class,
+ FailWithTimeoutOfWaitForOutputToMatchJUnitTest.class,
+ FailWithWarningInOutputJUnitTest.class,
+ PassJUnitTest.class,
+ PassWithExpectedErrorJUnitTest.class,
+ PassWithExpectedSevereJUnitTest.class,
+ PassWithExpectedWarningJUnitTest.class,
+})
+/**
+ * Suite of tests for the test.golden Golden Test framework classes.
+ */
+public class GoldenTestSuite {
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/Retry.java
----------------------------------------------------------------------
diff --git a/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/Retry.java b/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/Retry.java
new file mode 100755
index 0000000..af1dc2f
--- /dev/null
+++ b/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/Retry.java
@@ -0,0 +1,17 @@
+package com.gemstone.gemfire.test.junit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Java Annotation used to annotate a test suite class test case method in order to
+ * retry it in case of failure up to the specified maximum attempts.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Retry {
+
+ public static int DEFAULT = 1;
+
+ int value() default DEFAULT;
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/33d2c1c8/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/rules/RetryRule.java
----------------------------------------------------------------------
diff --git a/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/rules/RetryRule.java b/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/rules/RetryRule.java
new file mode 100755
index 0000000..0de55ac
--- /dev/null
+++ b/gemfire-junit/src/test/java/com/gemstone/gemfire/test/junit/rules/RetryRule.java
@@ -0,0 +1,161 @@
+package com.gemstone.gemfire.test.junit.rules;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import com.gemstone.gemfire.test.junit.Retry;
+
+/**
+ * JUnit Rule that enables retrying a failed test up to a maximum number of retries.
+ * </p>
+ * RetryRule can be used globally for all tests in a test case by specifying a
+ * retryCount when instantiating it:
+ * <pre>
+ * @Rule
+ * public final RetryRule retryRule = new RetryRule(3);
+ *
+ * @Test
+ * public void shouldBeRetriedUntilPasses() {
+ * ...
+ * }
+ * </pre>
+ * </p>
+ * The above will result in 3 retries for every test in the test case.
+ * </p>
+ * RetryRule can be used locally for specific tests by annotating the test
+ * method with @Rule and specifying a retryCount for that test:
+ * <pre>
+ * @Rule
+ * public final RetryRule retryRule = new RetryRule();
+ *
+ * @Test
+ * @Retry(3)
+ * public void shouldBeRetriedUntilPasses() {
+ * ...
+ * }
+ * </pre>
+ * </p>
+ * This version of RetryRule will retry a test that fails because of any kind
+ * of Throwable.
+ */
+public class RetryRule implements TestRule {
+ /**
+ * Enables printing of failures to System.err even if test passes on a retry
+ */
+ private static final boolean LOG = false;
+
+ private final AbstractRetryRule implementation;
+
+ public RetryRule() {
+ this.implementation = new LocalRetryRule();
+ }
+
+ public RetryRule(final int retryCount) {
+ this.implementation = new GlobalRetryRule(retryCount);
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return this.implementation.apply(base, description);
+ }
+
+ protected abstract class AbstractRetryRule implements TestRule {
+ protected AbstractRetryRule() {
+ }
+ protected void evaluate(final Statement base, final Description description, final int retryCount) throws Throwable {
+ if (retryCount == 0) {
+
+ }
+ Throwable caughtThrowable = null;
+
+ for (int count = 0; count < retryCount; count++) {
+ try {
+ base.evaluate();
+ return;
+ } catch (Throwable t) {
+ caughtThrowable = t;
+ debug(description.getDisplayName() + ": run " + (count + 1) + " failed");
+ }
+ }
+
+ debug(description.getDisplayName() + ": giving up after " + retryCount + " failures");
+ throw caughtThrowable;
+ }
+ private void debug(final String message) {
+ if (LOG) {
+ System.err.println(message);
+ }
+ }
+ }
+
+ /**
+ * Implementation of RetryRule for all test methods in a test case
+ */
+ protected class GlobalRetryRule extends AbstractRetryRule {
+
+ private final int retryCount;
+
+ protected GlobalRetryRule(final int retryCount) {
+ if (retryCount < 1) {
+ throw new IllegalArgumentException("Retry count must be greater than zero");
+ }
+ this.retryCount = retryCount;
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ GlobalRetryRule.this.evaluatePerCase(base, description);
+ }
+ };
+ }
+
+ protected void evaluatePerCase(final Statement base, final Description description) throws Throwable {
+ evaluate(base, description, this.retryCount);
+ }
+ }
+
+ /**
+ * Implementation of RetryRule for test methods annotated with Retry
+ */
+ protected class LocalRetryRule extends AbstractRetryRule {
+
+ protected LocalRetryRule() {
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ LocalRetryRule.this.evaluatePerTest(base, description);
+ }
+ };
+ }
+
+ protected void evaluatePerTest(final Statement base, final Description description) throws Throwable {
+ if (isTest(description)) {
+ Retry retry = description.getAnnotation(Retry.class);
+ int retryCount = getRetryCount(retry);
+ evaluate(base, description, retryCount);
+ }
+ }
+
+ private int getRetryCount(final Retry retry) {
+ int retryCount = Retry.DEFAULT;
+
+ if (retry != null) {
+ retryCount = retry.value();
+ }
+
+ return retryCount;
+ }
+
+ private boolean isTest(final Description description) {
+ return (description.isSuite() || description.isTest());
+ }
+ }
+}