diff --git a/CHANGES.txt b/CHANGES.txt
index 79b0ad4c92..a8ff79ac8f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
Current
+Fixed: GITHUB-2819: Ability to retry a data provider in case of failures (Krishnan Mahadevan)
Fixed: GITHUB-2308: StringIndexOutOfBoundsException in findClassesInPackage - Surefire/Maven - JDK 11 fails (Krishnan Mahadevan)
Fixed: GITHUB:2788: TestResult.isSuccess() is TRUE when test fails due to expectedExceptions (Krishnan Mahadevan)
Fixed: GITHUB-2800: Running Test Classes with Inherited @Factory and @DataProvider Annotated Non-Static Methods Fail (Krishnan Mahadevan)
diff --git a/testng-core-api/src/main/java/org/testng/IDataProviderMethod.java b/testng-core-api/src/main/java/org/testng/IDataProviderMethod.java
index 324f955e3f..4011bdd5c6 100644
--- a/testng-core-api/src/main/java/org/testng/IDataProviderMethod.java
+++ b/testng-core-api/src/main/java/org/testng/IDataProviderMethod.java
@@ -30,4 +30,12 @@ public interface IDataProviderMethod {
default boolean propagateFailureAsTestFailure() {
return false;
}
+
+ /**
+ * @return - An Class which implements {@link IRetryDataProvider} and which can be used to retry a
+ * data provider.
+ */
+ default Class extends IRetryDataProvider> retryUsing() {
+ return IRetryDataProvider.DisableDataProviderRetries.class;
+ }
}
diff --git a/testng-core-api/src/main/java/org/testng/IRetryDataProvider.java b/testng-core-api/src/main/java/org/testng/IRetryDataProvider.java
new file mode 100644
index 0000000000..b594ce9fa1
--- /dev/null
+++ b/testng-core-api/src/main/java/org/testng/IRetryDataProvider.java
@@ -0,0 +1,21 @@
+package org.testng;
+
+/** Represents the ability to retry a data provider. */
+public interface IRetryDataProvider {
+
+ /**
+ * @param dataProvider - The {@link IDataProviderMethod} object which represents the data provider
+ * to be invoked.
+ * @return - true
if the data provider should be invoked again.
+ */
+ boolean retry(IDataProviderMethod dataProvider);
+
+ /** A dummy implementation which disables retrying of a failed data provider. */
+ class DisableDataProviderRetries implements IRetryDataProvider {
+
+ @Override
+ public boolean retry(IDataProviderMethod dataProvider) {
+ return false;
+ }
+ }
+}
diff --git a/testng-core-api/src/main/java/org/testng/annotations/DataProvider.java b/testng-core-api/src/main/java/org/testng/annotations/DataProvider.java
index b30dd366ef..9d8124dd8d 100644
--- a/testng-core-api/src/main/java/org/testng/annotations/DataProvider.java
+++ b/testng-core-api/src/main/java/org/testng/annotations/DataProvider.java
@@ -5,6 +5,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import org.testng.IRetryDataProvider;
/**
* Mark a method as supplying data for a test method.
@@ -55,4 +56,11 @@
* @return the value
*/
boolean propagateFailureAsTestFailure() default false;
+
+ /**
+ * @return - An Class which implements {@link IRetryDataProvider} and which can be used to retry a
+ * data provider.
+ */
+ Class extends IRetryDataProvider> retryUsing() default
+ IRetryDataProvider.DisableDataProviderRetries.class;
}
diff --git a/testng-core-api/src/main/java/org/testng/annotations/IDataProviderAnnotation.java b/testng-core-api/src/main/java/org/testng/annotations/IDataProviderAnnotation.java
index 372b6f24f5..a13a198bd2 100644
--- a/testng-core-api/src/main/java/org/testng/annotations/IDataProviderAnnotation.java
+++ b/testng-core-api/src/main/java/org/testng/annotations/IDataProviderAnnotation.java
@@ -1,6 +1,7 @@
package org.testng.annotations;
import java.util.List;
+import org.testng.IRetryDataProvider;
/** Encapsulate the @DataProvider / @testng.data-provider annotation */
public interface IDataProviderAnnotation extends IAnnotation {
@@ -27,4 +28,16 @@ public interface IDataProviderAnnotation extends IAnnotation {
/** @return - true
If data provider failures should be propagated as test failures */
boolean isPropagateFailureAsTestFailure();
+
+ /**
+ * @param retry - A Class that implements {@link IRetryDataProvider} and which can be used to
+ * retry a data provider.
+ */
+ void setRetryUsing(Class extends IRetryDataProvider> retry);
+
+ /**
+ * @return - An Class which implements {@link IRetryDataProvider} and which can be used to retry a
+ * data provider.
+ */
+ Class extends IRetryDataProvider> retryUsing();
}
diff --git a/testng-core/src/main/java/org/testng/internal/DataProviderMethod.java b/testng-core/src/main/java/org/testng/internal/DataProviderMethod.java
index dfbc3a5259..bce2a1e426 100644
--- a/testng-core/src/main/java/org/testng/internal/DataProviderMethod.java
+++ b/testng-core/src/main/java/org/testng/internal/DataProviderMethod.java
@@ -3,6 +3,7 @@
import java.lang.reflect.Method;
import java.util.List;
import org.testng.IDataProviderMethod;
+import org.testng.IRetryDataProvider;
import org.testng.annotations.IDataProviderAnnotation;
/** Represents an @{@link org.testng.annotations.DataProvider} annotated method. */
@@ -47,4 +48,9 @@ public List getIndices() {
public boolean propagateFailureAsTestFailure() {
return annotation.isPropagateFailureAsTestFailure();
}
+
+ @Override
+ public Class extends IRetryDataProvider> retryUsing() {
+ return annotation.retryUsing();
+ }
}
diff --git a/testng-core/src/main/java/org/testng/internal/FilteredParameters.java b/testng-core/src/main/java/org/testng/internal/FilteredParameters.java
new file mode 100644
index 0000000000..2f47230f89
--- /dev/null
+++ b/testng-core/src/main/java/org/testng/internal/FilteredParameters.java
@@ -0,0 +1,62 @@
+package org.testng.internal;
+
+import java.util.Iterator;
+import java.util.List;
+import org.testng.ITestNGMethod;
+import org.testng.TestNGException;
+
+class FilteredParameters implements Iterator {
+
+ private int index = 0;
+ private boolean hasWarn = false;
+ private final Iterator parameters;
+ private final ITestNGMethod testMethod;
+ private final String dataProviderName;
+ private final List indices;
+
+ public FilteredParameters(
+ Iterator parameters,
+ ITestNGMethod testMethod,
+ String dataProviderName,
+ List indices) {
+ this.parameters = parameters;
+ this.testMethod = testMethod;
+ this.dataProviderName = dataProviderName;
+ this.indices = indices;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (index == 0 && !parameters.hasNext() && !hasWarn) {
+ hasWarn = true;
+ String msg =
+ String.format(
+ "The test method '%s' will be skipped since its "
+ + "data provider '%s' "
+ + "returned an empty array or iterator. ",
+ testMethod.getQualifiedName(), dataProviderName);
+ Utils.warn(msg);
+ }
+ return parameters.hasNext();
+ }
+
+ @Override
+ public Object[] next() {
+ testMethod.setParameterInvocationCount(index);
+ Object[] next = parameters.next();
+ if (next == null) {
+ throw new TestNGException("Parameters must not be null");
+ }
+ if (!indices.isEmpty() && !indices.contains(index)) {
+ // Skip parameters
+ next = null;
+ }
+ index++;
+ return next;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+}
diff --git a/testng-core/src/main/java/org/testng/internal/Parameters.java b/testng-core/src/main/java/org/testng/internal/Parameters.java
index 90bf412d95..f65b8314a0 100644
--- a/testng-core/src/main/java/org/testng/internal/Parameters.java
+++ b/testng-core/src/main/java/org/testng/internal/Parameters.java
@@ -6,11 +6,11 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
-import javax.annotation.Nullable;
import org.testng.DataProviderHolder;
import org.testng.IDataProviderInterceptor;
import org.testng.IDataProviderListener;
import org.testng.IDataProviderMethod;
+import org.testng.IRetryDataProvider;
import org.testng.ITestClass;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
@@ -147,7 +147,7 @@ public static Object[] createConfigurationParameters(
Method m,
Map params,
Object[] parameterValues,
- @Nullable ITestNGMethod currentTestMethod,
+ ITestNGMethod currentTestMethod,
IAnnotationFinder finder,
XmlSuite xmlSuite,
ITestContext ctx,
@@ -618,34 +618,33 @@ private static IDataProviderMethod findDataProvider(
for (Method m : ClassHelper.getAvailableMethods(cls)) {
IDataProviderAnnotation dp = finder.findAnnotation(m, IDataProviderAnnotation.class);
- if (null != dp && name.equals(getDataProviderName(dp, m))) {
- Object instanceToUse;
- if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) {
- IObjectDispenser dispenser = Dispenser.newInstance(objectFactory);
- BasicAttributes basic = new BasicAttributes(clazz, dataProviderClass);
- CreationAttributes attributes = new CreationAttributes(context, basic, null);
- instanceToUse = dispenser.dispense(attributes);
- } else {
- instanceToUse = instance;
- }
- // Not a static method but no instance exists, then create new one if possible
- if ((m.getModifiers() & Modifier.STATIC) == 0 && instanceToUse == null) {
- try {
- instanceToUse = objectFactory.newInstance(cls);
- } catch (TestNGException e) {
- instanceToUse = null;
- }
+ boolean proceed = null != dp && name.equals(getDataProviderName(dp, m));
+ if (!proceed) {
+ continue;
+ }
+ Object instanceToUse = instance;
+ if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) {
+ IObjectDispenser dispenser = Dispenser.newInstance(objectFactory);
+ BasicAttributes basic = new BasicAttributes(clazz, dataProviderClass);
+ CreationAttributes attributes = new CreationAttributes(context, basic, null);
+ instanceToUse = dispenser.dispense(attributes);
+ }
+ // Not a static method but no instance exists, then create new one if possible
+ if ((m.getModifiers() & Modifier.STATIC) == 0 && instanceToUse == null) {
+ try {
+ instanceToUse = objectFactory.newInstance(cls);
+ } catch (TestNGException ignored) {
}
+ }
- if (result != null) {
- throw new TestNGException("Found two providers called '" + name + "' on " + cls);
- }
+ if (result != null) {
+ throw new TestNGException("Found two providers called '" + name + "' on " + cls);
+ }
- if (isDynamicDataProvider) {
- result = new DataProviderMethodRemovable(instanceToUse, m, dp);
- } else {
- result = new DataProviderMethod(instanceToUse, m, dp);
- }
+ if (isDynamicDataProvider) {
+ result = new DataProviderMethodRemovable(instanceToUse, m, dp);
+ } else {
+ result = new DataProviderMethod(instanceToUse, m, dp);
}
}
@@ -780,31 +779,46 @@ public static ParameterHolder handleParameters(
String n = "param" + i;
allParameterNames.put(n, n);
}
-
- for (IDataProviderListener dataProviderListener : holder.getListeners()) {
- dataProviderListener.beforeDataProviderExecution(
- dataProviderMethod, testMethod, methodParams.context);
+ Class> retryClass = dataProviderMethod.retryUsing();
+ boolean shouldRetry = !retryClass.equals(IRetryDataProvider.DisableDataProviderRetries.class);
+ IRetryDataProvider retry = null;
+ if (shouldRetry) {
+ IObjectDispenser dispenser = Dispenser.newInstance(objectFactory);
+ BasicAttributes basic = new BasicAttributes(testMethod.getTestClass(), retryClass);
+ CreationAttributes attributes = new CreationAttributes(methodParams.context, basic, null);
+ retry = (IRetryDataProvider) dispenser.dispense(attributes);
}
- Iterator initParams;
- try {
- initParams =
- MethodInvocationHelper.invokeDataProvider(
- dataProviderMethod
- .getInstance(), /* a test instance or null if the data provider is static*/
- dataProviderMethod.getMethod(),
- testMethod,
- methodParams.context,
- fedInstance,
- annotationFinder);
- } catch (RuntimeException e) {
- for (IDataProviderListener each : holder.getListeners()) {
- each.onDataProviderFailure(testMethod, methodParams.context, e);
+ Iterator initParams = null;
+ do {
+
+ for (IDataProviderListener dataProviderListener : holder.getListeners()) {
+ dataProviderListener.beforeDataProviderExecution(
+ dataProviderMethod, testMethod, methodParams.context);
}
- throw e;
- }
- final Iterator parameters = initParams;
+ try {
+ initParams =
+ MethodInvocationHelper.invokeDataProvider(
+ dataProviderMethod
+ .getInstance(), /* a test instance or null if the data provider is static*/
+ dataProviderMethod.getMethod(),
+ testMethod,
+ methodParams.context,
+ fedInstance,
+ annotationFinder);
+ shouldRetry = false;
+ } catch (RuntimeException e) {
+ for (IDataProviderListener each : holder.getListeners()) {
+ each.onDataProviderFailure(testMethod, methodParams.context, e);
+ }
+ if (shouldRetry) {
+ shouldRetry = retry.retry(dataProviderMethod);
+ } else {
+ throw e;
+ }
+ }
+ } while (shouldRetry);
for (IDataProviderListener dataProviderListener : holder.getListeners()) {
dataProviderListener.afterDataProviderExecution(
@@ -817,45 +831,7 @@ public static ParameterHolder handleParameters(
allIndices.addAll(dataProviderMethod.getIndices());
Iterator filteredParameters =
- new Iterator() {
- int index = 0;
- boolean hasWarn = false;
-
- @Override
- public boolean hasNext() {
- if (index == 0 && !parameters.hasNext() && !hasWarn) {
- hasWarn = true;
- String msg =
- String.format(
- "The test method '%s' will be skipped since its "
- + "data provider '%s' "
- + "returned an empty array or iterator. ",
- testMethod.getQualifiedName(), dataProviderMethod.getName());
- Utils.warn(msg);
- }
- return parameters.hasNext();
- }
-
- @Override
- public Object[] next() {
- testMethod.setParameterInvocationCount(index);
- Object[] next = parameters.next();
- if (next == null) {
- throw new TestNGException("Parameters must not be null");
- }
- if (!allIndices.isEmpty() && !allIndices.contains(index)) {
- // Skip parameters
- next = null;
- }
- index++;
- return next;
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("remove");
- }
- };
+ new FilteredParameters(initParams, testMethod, dataProviderMethod.getName(), allIndices);
testMethod.setMoreInvocationChecker(filteredParameters::hasNext);
for (IDataProviderInterceptor interceptor : holder.getInterceptors()) {
@@ -919,18 +895,6 @@ public static Object[] injectParameters(
return matcher.getConformingArguments();
}
- public static Object[] getParametersFromIndex(Iterator parametersValues, int index) {
- while (parametersValues.hasNext()) {
- Object[] parameters = parametersValues.next();
-
- if (index == 0) {
- return parameters;
- }
- index--;
- }
- return null;
- }
-
/** A parameter passing helper class. */
public static class MethodParameters {
private final Map xmlParameters;
diff --git a/testng-core/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java b/testng-core/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java
index 12ca6e796d..e793e72f41 100644
--- a/testng-core/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java
+++ b/testng-core/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java
@@ -1,6 +1,7 @@
package org.testng.internal.annotations;
import java.util.List;
+import org.testng.IRetryDataProvider;
import org.testng.annotations.IDataProviderAnnotation;
/** An implementation of IDataProvider. */
@@ -10,6 +11,7 @@ public class DataProviderAnnotation extends BaseAnnotation implements IDataProvi
private boolean m_parallel;
private List m_indices;
private boolean m_bubbleUpFailures = false;
+ private Class extends IRetryDataProvider> retryUsing;
@Override
public boolean isParallel() {
@@ -50,4 +52,14 @@ public void propagateFailureAsTestFailure() {
public boolean isPropagateFailureAsTestFailure() {
return m_bubbleUpFailures;
}
+
+ @Override
+ public void setRetryUsing(Class extends IRetryDataProvider> retry) {
+ this.retryUsing = retry;
+ }
+
+ @Override
+ public Class extends IRetryDataProvider> retryUsing() {
+ return retryUsing;
+ }
}
diff --git a/testng-core/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java b/testng-core/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java
index 7fc2bdf81d..98d64910ad 100644
--- a/testng-core/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java
+++ b/testng-core/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java
@@ -417,17 +417,17 @@ private IAnnotation createConfigurationTag(
private IAnnotation createDataProviderTag(Method method, Annotation a) {
DataProviderAnnotation result = new DataProviderAnnotation();
DataProvider c = (DataProvider) a;
+ String name = c.name();
if (c.name().isEmpty()) {
- result.setName(method.getName());
- } else {
- result.setName(c.name());
+ name = method.getName();
}
+ result.setName(name);
result.setParallel(c.parallel());
result.setIndices(Ints.asList(c.indices()));
if (c.propagateFailureAsTestFailure()) {
result.propagateFailureAsTestFailure();
}
-
+ result.setRetryUsing(c.retryUsing());
return result;
}
diff --git a/testng-core/src/test/java/test/dataprovider/DataProviderTest.java b/testng-core/src/test/java/test/dataprovider/DataProviderTest.java
index 6b2f5058e4..e08da4d356 100644
--- a/testng-core/src/test/java/test/dataprovider/DataProviderTest.java
+++ b/testng-core/src/test/java/test/dataprovider/DataProviderTest.java
@@ -40,9 +40,54 @@
import test.dataprovider.issue2565.SampleTestUsingFunction;
import test.dataprovider.issue2565.SampleTestUsingPredicate;
import test.dataprovider.issue2565.SampleTestUsingSupplier;
+import test.dataprovider.issue2819.DataProviderListenerForRetryAwareTests;
+import test.dataprovider.issue2819.SimpleRetry;
+import test.dataprovider.issue2819.TestClassSample;
+import test.dataprovider.issue2819.TestClassUsingDataProviderRetrySample;
+import test.dataprovider.issue2819.TestClassWithMultipleRetryImplSample;
public class DataProviderTest extends SimpleBaseTest {
+ @Test(description = "GITHUB-2819")
+ public void testDataProviderCanBeRetriedOnFailures() {
+ TestNG testng = create(TestClassUsingDataProviderRetrySample.class);
+ DataProviderListenerForRetryAwareTests listener = new DataProviderListenerForRetryAwareTests();
+ testng.addListener(listener);
+ testng.run();
+ // Without retrying itself we would have already invoked the listener once.
+ assertThat(listener.getBeforeInvocations()).isEqualTo(3);
+ assertThat(listener.getFailureInvocations()).isEqualTo(2);
+ assertThat(listener.getAfterInvocations()).isEqualTo(1);
+ }
+
+ @Test(description = "GITHUB-2819")
+ public void testDataProviderCanBeRetriedViaAnnotationTransformer() {
+ TestNG testng = create(TestClassSample.class);
+ TestClassSample.EnableRetryForDataProvider transformer =
+ new TestClassSample.EnableRetryForDataProvider();
+ DataProviderListenerForRetryAwareTests listener = new DataProviderListenerForRetryAwareTests();
+ testng.addListener(transformer);
+ testng.addListener(listener);
+ testng.run();
+ // Without retrying itself we would have already invoked the listener once.
+ assertThat(listener.getBeforeInvocations()).isEqualTo(3);
+ assertThat(listener.getFailureInvocations()).isEqualTo(2);
+ assertThat(listener.getAfterInvocations()).isEqualTo(1);
+ }
+
+ @Test(description = "GITHUB-2819")
+ public void testDataProviderRetryInstancesAreUniqueForEachDataDrivenTest() {
+ TestNG testng = create(TestClassWithMultipleRetryImplSample.class);
+ DataProviderListenerForRetryAwareTests listener = new DataProviderListenerForRetryAwareTests();
+ testng.addListener(listener);
+ testng.run();
+ assertThat(SimpleRetry.getHashCodes()).hasSize(2);
+ // Without retrying itself we would have already invoked the listener once.
+ assertThat(listener.getBeforeInvocations()).isEqualTo(6);
+ assertThat(listener.getFailureInvocations()).isEqualTo(4);
+ assertThat(listener.getAfterInvocations()).isEqualTo(2);
+ }
+
@Test(description = "GITHUB-2800")
public void testDataProviderFromAbstractClassWhenCoupledWithFactories() {
InvokedMethodNameListener listener = run(test.dataprovider.issue2800.TestClassGenerator.class);
diff --git a/testng-core/src/test/java/test/dataprovider/issue2819/DataProviderListenerForRetryAwareTests.java b/testng-core/src/test/java/test/dataprovider/issue2819/DataProviderListenerForRetryAwareTests.java
new file mode 100644
index 0000000000..943d5e4c44
--- /dev/null
+++ b/testng-core/src/test/java/test/dataprovider/issue2819/DataProviderListenerForRetryAwareTests.java
@@ -0,0 +1,42 @@
+package test.dataprovider.issue2819;
+
+import org.testng.IDataProviderListener;
+import org.testng.IDataProviderMethod;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+
+public class DataProviderListenerForRetryAwareTests implements IDataProviderListener {
+
+ private int beforeInvocations = 0;
+ private int afterInvocations = 0;
+ private int failureInvocations = 0;
+
+ public int getBeforeInvocations() {
+ return beforeInvocations;
+ }
+
+ public int getFailureInvocations() {
+ return failureInvocations;
+ }
+
+ public int getAfterInvocations() {
+ return afterInvocations;
+ }
+
+ @Override
+ public void beforeDataProviderExecution(
+ IDataProviderMethod dp, ITestNGMethod tm, ITestContext ctx) {
+ beforeInvocations++;
+ }
+
+ @Override
+ public void afterDataProviderExecution(
+ IDataProviderMethod dp, ITestNGMethod tm, ITestContext ctx) {
+ afterInvocations++;
+ }
+
+ @Override
+ public void onDataProviderFailure(ITestNGMethod method, ITestContext ctx, RuntimeException t) {
+ failureInvocations++;
+ }
+}
diff --git a/testng-core/src/test/java/test/dataprovider/issue2819/SimpleRetry.java b/testng-core/src/test/java/test/dataprovider/issue2819/SimpleRetry.java
new file mode 100644
index 0000000000..7c3ca99f38
--- /dev/null
+++ b/testng-core/src/test/java/test/dataprovider/issue2819/SimpleRetry.java
@@ -0,0 +1,27 @@
+package test.dataprovider.issue2819;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.testng.IDataProviderMethod;
+import org.testng.IRetryDataProvider;
+
+public class SimpleRetry implements IRetryDataProvider {
+
+ private static final Set hashCodes = new HashSet<>();
+
+ public static Set getHashCodes() {
+ return Collections.unmodifiableSet(hashCodes);
+ }
+
+ public SimpleRetry() {
+ hashCodes.add(hashCode());
+ }
+
+ private int counter = 0;
+
+ @Override
+ public boolean retry(IDataProviderMethod dataProvider) {
+ return counter++ < 2;
+ }
+}
diff --git a/testng-core/src/test/java/test/dataprovider/issue2819/TestClassSample.java b/testng-core/src/test/java/test/dataprovider/issue2819/TestClassSample.java
new file mode 100644
index 0000000000..ce1a1cc590
--- /dev/null
+++ b/testng-core/src/test/java/test/dataprovider/issue2819/TestClassSample.java
@@ -0,0 +1,34 @@
+package test.dataprovider.issue2819;
+
+import java.lang.reflect.Method;
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.Test;
+
+public class TestClassSample {
+
+ private int counter = 0;
+
+ @Test(dataProvider = "dp")
+ public void sampleTest(int ignored) {}
+
+ @DataProvider(name = "dp")
+ public Object[][] getTestData() {
+ if (shouldSimulateFailure()) {
+ throw new RuntimeException("Simulating a failure");
+ }
+ return new Object[][] {{1}};
+ }
+
+ private boolean shouldSimulateFailure() {
+ return counter++ < 2;
+ }
+
+ public static class EnableRetryForDataProvider implements IAnnotationTransformer {
+ @Override
+ public void transform(IDataProviderAnnotation annotation, Method method) {
+ annotation.setRetryUsing(SimpleRetry.class);
+ }
+ }
+}
diff --git a/testng-core/src/test/java/test/dataprovider/issue2819/TestClassUsingDataProviderRetrySample.java b/testng-core/src/test/java/test/dataprovider/issue2819/TestClassUsingDataProviderRetrySample.java
new file mode 100644
index 0000000000..f7a3185fca
--- /dev/null
+++ b/testng-core/src/test/java/test/dataprovider/issue2819/TestClassUsingDataProviderRetrySample.java
@@ -0,0 +1,24 @@
+package test.dataprovider.issue2819;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestClassUsingDataProviderRetrySample {
+
+ private int counter = 0;
+
+ @Test(dataProvider = "dp")
+ public void sampleTest(int ignored) {}
+
+ @DataProvider(name = "dp", retryUsing = SimpleRetry.class)
+ public Object[][] getTestData() {
+ if (shouldSimulateFailure()) {
+ throw new RuntimeException("Simulating a failure");
+ }
+ return new Object[][] {{1}};
+ }
+
+ private boolean shouldSimulateFailure() {
+ return counter++ < 2;
+ }
+}
diff --git a/testng-core/src/test/java/test/dataprovider/issue2819/TestClassWithMultipleRetryImplSample.java b/testng-core/src/test/java/test/dataprovider/issue2819/TestClassWithMultipleRetryImplSample.java
new file mode 100644
index 0000000000..0d7ae31b4c
--- /dev/null
+++ b/testng-core/src/test/java/test/dataprovider/issue2819/TestClassWithMultipleRetryImplSample.java
@@ -0,0 +1,41 @@
+package test.dataprovider.issue2819;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestClassWithMultipleRetryImplSample {
+
+ @Test(dataProvider = "dp")
+ public void sampleTest(int ignored) {}
+
+ @DataProvider(name = "dp", retryUsing = SimpleRetry.class)
+ public Object[][] getTestData() {
+ if (shouldSimulateFailureForGetTestData()) {
+ throw new RuntimeException("Simulating a failure");
+ }
+ return new Object[][] {{1}};
+ }
+
+ @Test(dataProvider = "anotherDP")
+ public void anotherSampleTest(int ignored) {}
+
+ @DataProvider(name = "anotherDP", retryUsing = SimpleRetry.class)
+ public Object[][] getMoreTestData() {
+ if (shouldSimulateFailureForMoreTestData()) {
+ throw new RuntimeException("Simulating another failure");
+ }
+ return new Object[][] {{200}};
+ }
+
+ private int moreTestDataCounter = 0;
+
+ private boolean shouldSimulateFailureForMoreTestData() {
+ return moreTestDataCounter++ < 2;
+ }
+
+ private int testDataCounter = 0;
+
+ private boolean shouldSimulateFailureForGetTestData() {
+ return testDataCounter++ < 2;
+ }
+}