| package org.testng.internal; |
| |
| import org.testng.ITestContext; |
| import org.testng.ITestNGMethod; |
| import org.testng.ITestResult; |
| import org.testng.TestNGException; |
| import org.testng.annotations.IConfigurationAnnotation; |
| import org.testng.annotations.IDataProviderAnnotation; |
| import org.testng.annotations.IFactoryAnnotation; |
| import org.testng.annotations.IParameterizable; |
| import org.testng.annotations.IParametersAnnotation; |
| import org.testng.annotations.ITestAnnotation; |
| import org.testng.collections.Lists; |
| import org.testng.internal.annotations.AnnotationHelper; |
| import org.testng.internal.annotations.IAnnotationFinder; |
| import org.testng.internal.annotations.Sets; |
| import org.testng.xml.XmlSuite; |
| import org.testng.xml.XmlTest; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Methods that bind parameters declared in testng.xml to actual values |
| * used to invoke methods. |
| * |
| * @author <a href="mailto:cedric@beust.com">Cedric Beust</a> |
| * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a> |
| * |
| * @@ANNOTATIONS@@ |
| */ |
| public class Parameters { |
| public static final String NULL_VALUE= "null"; |
| |
| /** |
| * Creates the parameters needed for constructing a test class instance. |
| * @param finder TODO |
| */ |
| public static Object[] createInstantiationParameters(Constructor ctor, |
| String methodAnnotation, |
| IAnnotationFinder finder, |
| String[] parameterNames, |
| Map<String, String> params, XmlSuite xmlSuite) |
| { |
| return createParameters(ctor.toString(), ctor.getParameterTypes(), |
| finder.findOptionalValues(ctor), methodAnnotation, finder, parameterNames, new MethodParameters(params), xmlSuite); |
| } |
| |
| /** |
| * Creates the parameters needed for the specified <tt>@Configuration</tt> <code>Method</code>. |
| * |
| * @param m the configuraton method |
| * @param params |
| * @param currentTestMethod the current @Test method or <code>null</code> if no @Test is available (this is not |
| * only in case the configuration method is a @Before/@AfterMethod |
| * @param finder the annotation finder |
| * @param xmlSuite |
| * @return |
| */ |
| public static Object[] createConfigurationParameters(Method m, |
| Map<String, String> params, |
| Object[] parameterValues, |
| ITestNGMethod currentTestMethod, |
| IAnnotationFinder finder, |
| XmlSuite xmlSuite, |
| ITestContext ctx, |
| ITestResult testResult) |
| { |
| Method currentTestMeth= currentTestMethod != null ? |
| currentTestMethod.getMethod() : null; |
| return createParameters(m, |
| new MethodParameters(params, parameterValues, currentTestMeth, ctx, testResult), |
| finder, xmlSuite, IConfigurationAnnotation.class, "@Configuration"); |
| } |
| |
| //////////////////////////////////////////////////////// |
| |
| public static Object getInjectedParameter(Class<?> c, Method method, ITestContext context, |
| ITestResult testResult) { |
| Object result = null; |
| if (Method.class.equals(c)) { |
| result = method; |
| } |
| else if (ITestContext.class.equals(c)) { |
| result = context; |
| } |
| else if (XmlTest.class.equals(c)) { |
| result = context.getCurrentXmlTest(); |
| } |
| else if (ITestResult.class.equals(c)) { |
| result = testResult; |
| } |
| return result; |
| } |
| |
| /** |
| * @param optionalValues TODO |
| * @param finder TODO |
| * @param parameterAnnotations TODO |
| * @param m |
| * @param instance |
| * @return An array of parameters suitable to invoke this method, possibly |
| * picked from the property file |
| */ |
| private static Object[] createParameters(String methodName, |
| Class[] parameterTypes, |
| String[] optionalValues, |
| String methodAnnotation, |
| IAnnotationFinder finder, |
| String[] parameterNames, MethodParameters params, XmlSuite xmlSuite) |
| { |
| Object[] result = new Object[0]; |
| if(parameterTypes.length > 0) { |
| List<Object> vResult = Lists.newArrayList(); |
| |
| checkParameterTypes(methodName, parameterTypes, methodAnnotation, parameterNames); |
| |
| for(int i = 0, j = 0; i < parameterTypes.length; i++) { |
| Object inject = getInjectedParameter(parameterTypes[i], params.currentTestMethod, |
| params.context, params.testResult); |
| if (inject != null) { |
| vResult.add(inject); |
| } |
| else { |
| if (j < parameterNames.length) { |
| String p = parameterNames[j]; |
| String value = params.xmlParameters.get(p); |
| if(null == value) { |
| // try SysEnv entries |
| value= System.getProperty(p); |
| } |
| if (null == value) { |
| if (optionalValues != null) { |
| value = optionalValues[i]; |
| } |
| if (null == value) { |
| throw new TestNGException("Parameter '" + p + "' is required by " |
| + methodAnnotation |
| + " on method " |
| + methodName |
| + "\nbut has not been marked @Optional or defined " |
| + (xmlSuite.getFileName() != null ? "in " |
| + xmlSuite.getFileName() : "")); |
| } |
| } |
| |
| vResult.add(convertType(parameterTypes[i], value, p)); |
| j++; |
| } |
| } |
| } |
| |
| result = (Object[]) vResult.toArray(new Object[vResult.size()]); |
| } |
| |
| return result; |
| } |
| |
| // FIXME |
| private static void checkParameterTypes(String methodName, |
| Class[] parameterTypes, String methodAnnotation, String[] parameterNames) |
| { |
| if(parameterNames.length == parameterTypes.length) return; |
| |
| for(int i= parameterTypes.length - 1; i >= parameterNames.length; i--) { |
| Class type = parameterTypes[i]; |
| if(!ITestContext.class.equals(type) |
| && !ITestResult.class.equals(type) |
| && !XmlTest.class.equals(type) |
| && !Method.class.equals(type) |
| && !Object[].class.equals(type)) { |
| throw new TestNGException( "Method " + methodName + " requires " |
| + parameterTypes.length + " parameters but " |
| + parameterNames.length |
| + " were supplied in the " |
| + methodAnnotation |
| + " annotation."); |
| } |
| } |
| } |
| |
| //TODO: Cosmin - making this public is not the best solution |
| public static Object convertType(Class type, String value, String paramName) { |
| Object result = null; |
| |
| if(NULL_VALUE.equals(value.toLowerCase())) { |
| if(type.isPrimitive()) { |
| Utils.log("Parameters", 2, "Attempt to pass null value to primitive type parameter '" + paramName + "'"); |
| } |
| |
| return null; // null value must be used |
| } |
| |
| if(type == String.class) { |
| result = value; |
| } |
| else if(type == int.class || type == Integer.class) { |
| result = new Integer(Integer.parseInt(value)); |
| } |
| else if(type == boolean.class || type == Boolean.class) { |
| result = Boolean.valueOf(value); |
| } |
| else if(type == byte.class || type == Byte.class) { |
| result = new Byte(Byte.parseByte(value)); |
| } |
| else if(type == char.class || type == Character.class) { |
| result = new Character(value.charAt(0)); |
| } |
| else if(type == double.class || type == Double.class) { |
| result = new Double(Double.parseDouble(value)); |
| } |
| else if(type == float.class || type == Float.class) { |
| result = new Float(Float.parseFloat(value)); |
| } |
| else if(type == long.class || type == Long.class) { |
| result = new Long(Long.parseLong(value)); |
| } |
| else if(type == short.class || type == Short.class) { |
| result = new Short(Short.parseShort(value)); |
| } |
| else { |
| assert false : "Unsupported type parameter : " + type; |
| } |
| |
| return result; |
| } |
| |
| private static DataProviderHolder findDataProvider(Class clazz, Method m, |
| IAnnotationFinder finder) { |
| DataProviderHolder result = null; |
| String dataProviderName = null; |
| Class dataProviderClass = null; |
| |
| ITestAnnotation annotation = AnnotationHelper.findTest(finder, m); |
| if (annotation == null) { |
| annotation = AnnotationHelper.findTest(finder, clazz); |
| } |
| if (annotation != null) { |
| dataProviderName = annotation.getDataProvider(); |
| dataProviderClass = annotation.getDataProviderClass(); |
| } |
| |
| if (dataProviderName == null) { |
| IFactoryAnnotation factory = AnnotationHelper.findFactory(finder, m); |
| if (factory != null) { |
| dataProviderName = factory.getDataProvider(); |
| dataProviderClass = null; |
| } |
| } |
| |
| if (null != dataProviderName && ! "".equals(dataProviderName)) { |
| result = findDataProvider(clazz, finder, dataProviderName, dataProviderClass); |
| |
| if(null == result) { |
| throw new TestNGException("Method " + m + " requires a @DataProvider named : " |
| + dataProviderName + (dataProviderClass != null ? " in class " + dataProviderClass.getName() : "") |
| ); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Find a method that has a @DataProvider(name=name) |
| */ |
| private static DataProviderHolder findDataProvider(Class cls, IAnnotationFinder finder, |
| String name, Class dataProviderClass) |
| { |
| boolean shouldBeStatic = false; |
| if (dataProviderClass != null) { |
| cls = dataProviderClass; |
| shouldBeStatic = true; |
| } |
| |
| for (Method m : ClassHelper.getAvailableMethods(cls)) { |
| IDataProviderAnnotation dp = (IDataProviderAnnotation) |
| finder.findAnnotation(m, IDataProviderAnnotation.class); |
| if (null != dp && (name.equals(dp.getName()) || name.equals(m.getName()))) { |
| if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) { |
| throw new TestNGException("DataProvider should be static: " + m); |
| } |
| |
| return new DataProviderHolder(dp, m); |
| } |
| } |
| |
| return null; |
| } |
| |
| @SuppressWarnings({"deprecation"}) |
| private static Object[] createParameters(Method m, MethodParameters params, |
| IAnnotationFinder finder, XmlSuite xmlSuite, Class annotationClass, String atName) |
| { |
| List<Object> result = Lists.newArrayList(); |
| |
| Object[] extraParameters = new Object[0]; |
| // |
| // Try to find an @Parameters annotation |
| // |
| IParametersAnnotation annotation = (IParametersAnnotation) finder.findAnnotation(m, IParametersAnnotation.class); |
| Class<?>[] types = m.getParameterTypes(); |
| if(null != annotation) { |
| String[] parameterNames = annotation.getValue(); |
| extraParameters = createParameters(m.getName(), types, |
| finder.findOptionalValues(m), atName, finder, parameterNames, params, xmlSuite); |
| } |
| |
| // |
| // Else, use the deprecated syntax |
| // |
| else { |
| IParameterizable a = (IParameterizable) finder.findAnnotation(m, annotationClass); |
| if(null != a && a.getParameters().length > 0) { |
| String[] parameterNames = a.getParameters(); |
| extraParameters = createParameters(m.getName(), types, |
| finder.findOptionalValues(m), atName, finder, parameterNames, params, xmlSuite); |
| } |
| else { |
| extraParameters = createParameters(m.getName(), types, |
| finder.findOptionalValues(m), atName, finder, new String[0], params, xmlSuite); |
| } |
| } |
| |
| // If the method declared an Object[] parameter and we have parameter values, inject them |
| for (int i = 0; i < types.length; i++) { |
| Class<?> type = types[i]; |
| if (Object[].class.equals(type)) { |
| result.add(params.parameterValues); |
| } |
| } |
| |
| // |
| // Add the extra parameters we found |
| // |
| for (Object p : extraParameters) { |
| result.add(p); |
| } |
| |
| return result.toArray(new Object[result.size()]); |
| } |
| |
| /** |
| * If the method has parameters, fill them in. Either by using a @DataProvider |
| * if any was provided, or by looking up <parameters> in testng.xml |
| * @return An Iterator over the values for each parameter of this |
| * method. |
| */ |
| public static ParameterHolder handleParameters(ITestNGMethod testMethod, |
| Map<String, String> allParameterNames, |
| Object instance, |
| MethodParameters methodParams, |
| XmlSuite xmlSuite, |
| IAnnotationFinder annotationFinder, |
| Object fedInstance) |
| { |
| ParameterHolder result; |
| |
| Iterator<Object[]> parameters = null; |
| |
| // |
| // Do we have a @DataProvider? If yes, then we have several |
| // sets of parameters for this method |
| // |
| DataProviderHolder dataProviderHolder = |
| findDataProvider(testMethod.getTestClass().getRealClass(), |
| testMethod.getMethod(), annotationFinder); |
| |
| if (null != dataProviderHolder) { |
| int parameterCount = testMethod.getMethod().getParameterTypes().length; |
| |
| for (int i = 0; i < parameterCount; i++) { |
| String n = "param" + i; |
| allParameterNames.put(n, n); |
| } |
| |
| parameters = MethodHelper.invokeDataProvider( |
| instance, /* a test instance or null if the dataprovider is static*/ |
| dataProviderHolder.method, |
| testMethod, |
| methodParams.context, |
| fedInstance, |
| annotationFinder); |
| |
| Iterator<Object[]> filteredParameters = filterParameters(parameters, |
| testMethod.getInvocationNumbers()); |
| |
| result = new ParameterHolder(filteredParameters, ParameterHolder.ORIGIN_DATA_PROVIDER, |
| dataProviderHolder); |
| } |
| else { |
| // |
| // Normal case: we have only one set of parameters coming from testng.xml |
| // |
| allParameterNames.putAll(methodParams.xmlParameters); |
| // Create an Object[][] containing just one row of parameters |
| Object[][] allParameterValuesArray = new Object[1][]; |
| allParameterValuesArray[0] = createParameters(testMethod.getMethod(), |
| methodParams, annotationFinder, xmlSuite, ITestAnnotation.class, "@Test"); |
| |
| // Mark that this method needs to have at least a certain |
| // number of invocations (needed later to call AfterGroups |
| // at the right time). |
| testMethod.setParameterInvocationCount(allParameterValuesArray.length); |
| // Turn it into an Iterable |
| parameters = MethodHelper.createArrayIterator(allParameterValuesArray); |
| |
| result = new ParameterHolder(parameters, |
| allParameterValuesArray.length == 0 |
| ? ParameterHolder.ORIGIN_DATA_PROVIDER : ParameterHolder.ORIGIN_XML, |
| dataProviderHolder); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * If numbers is empty, return parameters, otherwise, return a subset of parameters |
| * whose ordinal number match these found in numbers. |
| */ |
| static private Iterator<Object[]> filterParameters(Iterator<Object[]> parameters, |
| List<Integer> list) { |
| if (list.isEmpty()) { |
| return parameters; |
| } else { |
| List<Object[]> result = Lists.newArrayList(); |
| int i = 0; |
| while (parameters.hasNext()) { |
| Object[] next = parameters.next(); |
| if (list.contains(i)) result.add(next); |
| i++; |
| } |
| return new ArrayIterator(result.toArray(new Object[list.size()][])); |
| } |
| } |
| |
| private static void ppp(String s) { |
| System.out.println("[Parameters] " + s); |
| } |
| |
| /** A parameter passing helper class. */ |
| public static class MethodParameters { |
| private final Map<String, String> xmlParameters; |
| private final Method currentTestMethod; |
| private final ITestContext context; |
| private Object[] parameterValues; |
| public ITestResult testResult; |
| |
| public MethodParameters(Map<String, String> params) { |
| this(params, null, null, null, null); |
| } |
| |
| public MethodParameters(Map<String, String> params, Method m) { |
| this(params, null, m, null, null); |
| } |
| |
| public MethodParameters(Map<String, String> params, Object[] pv, Method m, ITestContext ctx, |
| ITestResult tr) { |
| xmlParameters = params; |
| currentTestMethod = m; |
| context = ctx; |
| parameterValues = pv; |
| testResult = tr; |
| } |
| } |
| } |