Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cartesian product for data providers #865

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/testng/annotations/Factory.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* The name of the data provider for this test method.
* @see org.testng.annotations.DataProvider
*/
public String dataProvider() default "";
public String[] dataProvider() default {};

/**
* The class where to look for the data provider. If not
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/testng/annotations/ITestAnnotation.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public interface ITestAnnotation extends ITestOrConfiguration, IDataProvidable {
public boolean getSingleThreaded();
public void setSingleThreaded(boolean f);

public String getDataProvider();
public void setDataProvider(String v);
public String[] getDataProvider();
public void setDataProvider(String[] v);

public Class<?> getDataProviderClass();
public void setDataProviderClass(Class<?> v);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/testng/annotations/Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
* The name of the data provider for this test method.
* @see org.testng.annotations.DataProvider
*/
public String dataProvider() default "";
public String[] dataProvider() default {};

/**
* The class where to look for the data provider. If not
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/org/testng/internal/Invoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod,
List<TestMethodWithDataProviderMethodWorker> workers = Lists.newArrayList();

if (bag.parameterHolder.origin == ParameterOrigin.ORIGIN_DATA_PROVIDER &&
bag.parameterHolder.dataProviderHolder.annotation.isParallel()) {
hasParallelDataProvider(bag.parameterHolder)) {
while (allParameterValues.hasNext()) {
Object[] parameterValues = injectParameters(allParameterValues.next(),
testMethod.getMethod(), testContext, null /* test result */);
Expand Down Expand Up @@ -1190,6 +1190,15 @@ public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod,

} // invokeTestMethod

private boolean hasParallelDataProvider(ParameterHolder parameterHolder) {
for (DataProviderHolder holder : parameterHolder.dataProviderHolders) {
if (holder.annotation.isParallel()) {
return true;
}
}
return false;
}

private ITestResult registerSkippedTestResult(ITestNGMethod testMethod, Object instance,
long start, Throwable throwable) {
ITestResult result =
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/testng/internal/ParameterHolder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.testng.internal;

import java.util.Iterator;
import java.util.List;

/**
* A simple holder for parameters that contains the parameters and where these came from
Expand All @@ -17,15 +18,15 @@ public enum ParameterOrigin {
ORIGIN_XML // TestNG XML suite
};

public DataProviderHolder dataProviderHolder;
public List<DataProviderHolder> dataProviderHolders;
public Iterator<Object[]> parameters;
public ParameterOrigin origin;

public ParameterHolder(Iterator<Object[]> parameters, ParameterOrigin origin, DataProviderHolder dph) {
public ParameterHolder(Iterator<Object[]> parameters, ParameterOrigin origin, List<DataProviderHolder> dphs) {
super();
this.parameters = parameters;
this.origin = origin;
this.dataProviderHolder = dph;
this.dataProviderHolders = dphs;
}

}
119 changes: 80 additions & 39 deletions src/main/java/org/testng/internal/Parameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -245,24 +246,18 @@ else if (type.isEnum()) {
return result;
}

private static DataProviderHolder findDataProvider(Object instance, ITestClass clazz,
private static List<DataProviderHolder> findDataProvider(Object instance, ITestClass clazz,
ConstructorOrMethod m,
IAnnotationFinder finder, ITestContext context) {
DataProviderHolder result = null;
List<DataProviderHolder> result = null;

IDataProvidable dp = findDataProviderInfo(clazz, m, finder);
if (dp != null) {
String dataProviderName = dp.getDataProvider();
String[] dataProviderNames = dp.getDataProvider();
Class dataProviderClass = dp.getDataProviderClass();

if (! Utils.isStringEmpty(dataProviderName)) {
result = findDataProvider(instance, clazz, finder, dataProviderName, dataProviderClass, context);

if(null == result) {
throw new TestNGException("Method " + m + " requires a @DataProvider named : "
+ dataProviderName + (dataProviderClass != null ? " in class " + dataProviderClass.getName() : "")
);
}
if (dataProviderNames.length > 0) {
result = findDataProvider(m, instance, clazz, finder, dataProviderNames, dataProviderClass, context);
}
}

Expand Down Expand Up @@ -306,38 +301,44 @@ private static IDataProvidable findDataProviderInfo(ITestClass clazz, Constructo
/**
* Find a method that has a @DataProvider(name=name)
*/
private static DataProviderHolder findDataProvider(Object instance, ITestClass clazz,
IAnnotationFinder finder,
String name, Class dataProviderClass,
ITestContext context)
private static List<DataProviderHolder> findDataProvider(ConstructorOrMethod annotated, Object instance,
ITestClass clazz, IAnnotationFinder finder, String[] names, Class dataProviderClass, ITestContext context)
{
DataProviderHolder result = null;

Class cls = clazz.getRealClass();
boolean shouldBeStatic = false;
if (dataProviderClass != null) {
cls = dataProviderClass;
shouldBeStatic = true;
}

for (Method m : ClassHelper.getAvailableMethods(cls)) {
IDataProviderAnnotation dp = finder.findAnnotation(m, IDataProviderAnnotation.class);
if (null != dp && name.equals(getDataProviderName(dp, m))) {
if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) {
Injector injector = context.getInjector(clazz);
if (injector != null) {
instance = injector.getInstance(dataProviderClass);
List<DataProviderHolder> providers = new ArrayList<>();
for (String name : names) {
DataProviderHolder result = null;
for (Method m : ClassHelper.getAvailableMethods(cls)) {
IDataProviderAnnotation dp = finder.findAnnotation(m, IDataProviderAnnotation.class);
if (null != dp && name.equals(getDataProviderName(dp, m))) {
if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) {
Injector injector = context.getInjector(clazz);
if (injector != null) {
instance = injector.getInstance(dataProviderClass);
}
}
}

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);
}
result = new DataProviderHolder(dp, m, instance);
}
result = new DataProviderHolder(dp, m, instance);
}
if(null == result) {
throw new TestNGException("Method " + annotated + " requires a @DataProvider named : "
+ name + (dataProviderClass != null ? " in class " + dataProviderClass.getName() : "")
);
}
providers.add(result);
}

return result;
return providers;
}

private static String getDataProviderName(IDataProviderAnnotation dp, Method m) {
Expand Down Expand Up @@ -415,31 +416,35 @@ public static ParameterHolder handleParameters(ITestNGMethod testMethod,
* Do we have a @DataProvider? If yes, then we have several
* sets of parameters for this method
*/
DataProviderHolder dataProviderHolder =
List<DataProviderHolder> dataProviderHolders =
findDataProvider(instance, testMethod.getTestClass(),
testMethod.getConstructorOrMethod(), annotationFinder, methodParams.context);

if (null != dataProviderHolder) {
if (null != dataProviderHolders && !dataProviderHolders.isEmpty()) {
int parameterCount = testMethod.getConstructorOrMethod().getParameterTypes().length;

for (int i = 0; i < parameterCount; i++) {
String n = "param" + i;
allParameterNames.put(n, n);
}

parameters = MethodInvocationHelper.invokeDataProvider(
dataProviderHolder.instance, /* a test instance or null if the dataprovider is static*/
dataProviderHolder.method,
testMethod,
methodParams.context,
fedInstance,
annotationFinder);
List<Iterator<Object[]>> cartesianParameters = new ArrayList<>();
for (DataProviderHolder dataProviderHolder : dataProviderHolders) {
cartesianParameters.add(MethodInvocationHelper.invokeDataProvider(
dataProviderHolder.instance, /* a test instance or null if the dataprovider is static*/
dataProviderHolder.method,
testMethod,
methodParams.context,
fedInstance,
annotationFinder));
}
parameters = cartesianProduct(cartesianParameters);

Iterator<Object[]> filteredParameters = filterParameters(parameters,
testMethod.getInvocationNumbers());

result = new ParameterHolder(filteredParameters, ParameterOrigin.ORIGIN_DATA_PROVIDER,
dataProviderHolder);
dataProviderHolders);
}
else {
//
Expand All @@ -464,6 +469,42 @@ public static ParameterHolder handleParameters(ITestNGMethod testMethod,
return result;
}

private static Iterator<Object[]> cartesianProduct(List<Iterator<Object[]>> cartesianParameters) {
if (cartesianParameters.size() == 0) {
throw new IllegalArgumentException("Must provide more than one collection of data");
} else if (cartesianParameters.size() == 1) {
return cartesianParameters.get(0);
}

List<Object[]> result = new ArrayList<>();

Iterator<Object[]> left = cartesianParameters.get(0);
List<Object[]> right = new ArrayList<>();
while (cartesianParameters.get(1).hasNext()) {
right.add(cartesianParameters.get(1).next());
}
while (left.hasNext()) {
Object[] leftRow = left.next();
for (Object[] rightRow : right) {
Object[] row = new Object[leftRow.length + rightRow.length];
System.arraycopy(leftRow, 0, row, 0, leftRow.length);
System.arraycopy(rightRow, 0, row, leftRow.length, rightRow.length);
result.add(row);
}
}

if (cartesianParameters.size()> 2) {
List<Iterator<Object[]>> nextProduct = new ArrayList<>();
nextProduct.add(result.iterator());
for (int i = 2; i < cartesianParameters.size(); i++) {
nextProduct.add(cartesianParameters.get(i));
}
return cartesianProduct(nextProduct);
}

return result.iterator();
}

/**
* If numbers is empty, return parameters, otherwise, return a subset of parameters
* whose ordinal number match these found in numbers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ public class FactoryAnnotation
implements IFactoryAnnotation
{
private String[] m_parameters = {};
private String m_dataProvider = null;
private String[] m_dataProvider = null;
private Class<?> m_dataProviderClass;
private boolean m_enabled = true;

@Override
public String getDataProvider() {
public String[] getDataProvider() {
return m_dataProvider;
}

@Override
public void setDataProvider(String dataProvider) {
public void setDataProvider(String[] dataProvider) {
m_dataProvider = dataProvider;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
* @author Cedric Beust <[email protected]>
*/
public interface IDataProvidable {
public String getDataProvider();
public void setDataProvider(String v);
public String[] getDataProvider();
public void setDataProvider(String[] v);

public Class<?> getDataProviderClass();
public void setDataProviderClass(Class<?> v);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class TestAnnotation extends TestOrConfiguration implements ITestAnnotati
private int m_invocationCount = 1;
private int m_threadPoolSize = 0;
private int m_successPercentage = 100;
private String m_dataProvider = "";
private String[] m_dataProvider = {};
private boolean m_alwaysRun = false;
private Class<?>[] m_expectedExceptions = {};
private String m_expectedExceptionsMessageRegExp = ".*";
Expand Down Expand Up @@ -61,7 +61,7 @@ public void setAlwaysRun(boolean alwaysRun) {
}

@Override
public void setDataProvider(String dataProvider) {
public void setDataProvider(String[] dataProvider) {
m_dataProvider = dataProvider;
}

Expand Down Expand Up @@ -107,7 +107,7 @@ public int getSuccessPercentage() {
}

@Override
public String getDataProvider() {
public String[] getDataProvider() {
return m_dataProvider;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
Expand Down Expand Up @@ -226,9 +227,9 @@ private Properties getTestResultAttributes(ITestResult testResult) {
if (cm.getMethod() != null) {
testAnnotation = cm.getMethod().getAnnotation(Test.class);
if (testAnnotation != null) {
String dataProvider = testAnnotation.dataProvider();
if (!Strings.isNullOrEmpty(dataProvider)) {
attributes.setProperty(XMLReporterConfig.ATTR_DATA_PROVIDER, dataProvider);
String[] dataProvider = testAnnotation.dataProvider();
if (dataProvider.length > 0) {
attributes.setProperty(XMLReporterConfig.ATTR_DATA_PROVIDER, Arrays.toString(dataProvider));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ public void transform(ITestAnnotation annotation, Class testClass,

@Override
public void transform(IFactoryAnnotation annotation, Method testMethod) {
annotation.setDataProvider("dataProvider");
annotation.setDataProvider(new String[] {"dataProvider"});
}
}
Loading