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 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 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 - trueIf 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 retry); + + /** + * @return - An Class which implements {@link IRetryDataProvider} and which can be used to retry a + * data provider. + */ + Class 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 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 retryUsing; @Override public boolean isParallel() { @@ -50,4 +52,14 @@ public void propagateFailureAsTestFailure() { public boolean isPropagateFailureAsTestFailure() { return m_bubbleUpFailures; } + + @Override + public void setRetryUsing(Class retry) { + this.retryUsing = retry; + } + + @Override + public Class 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; + } +}