Home | History | Annotate | Download | only in internal
      1 package org.testng.internal;
      2 
      3 import com.google.inject.Injector;
      4 
      5 import java.lang.reflect.Constructor;
      6 import java.lang.reflect.Method;
      7 import java.lang.reflect.Modifier;
      8 import java.util.Collections;
      9 import java.util.HashSet;
     10 import java.util.Iterator;
     11 import java.util.List;
     12 import java.util.Map;
     13 import java.util.Set;
     14 
     15 import org.testng.ITestClass;
     16 import org.testng.ITestContext;
     17 import org.testng.ITestNGMethod;
     18 import org.testng.ITestResult;
     19 import org.testng.TestNGException;
     20 import org.testng.annotations.IConfigurationAnnotation;
     21 import org.testng.annotations.IDataProviderAnnotation;
     22 import org.testng.annotations.IParameterizable;
     23 import org.testng.annotations.IParametersAnnotation;
     24 import org.testng.annotations.ITestAnnotation;
     25 import org.testng.collections.Lists;
     26 import org.testng.collections.Maps;
     27 import org.testng.internal.ParameterHolder.ParameterOrigin;
     28 import org.testng.internal.annotations.AnnotationHelper;
     29 import org.testng.internal.annotations.IAnnotationFinder;
     30 import org.testng.internal.annotations.IDataProvidable;
     31 import org.testng.util.Strings;
     32 import org.testng.xml.XmlSuite;
     33 import org.testng.xml.XmlTest;
     34 
     35 /**
     36  * Methods that bind parameters declared in testng.xml to actual values
     37  * used to invoke methods.
     38  *
     39  * @author <a href="mailto:cedric (at) beust.com">Cedric Beust</a>
     40  */
     41 public class Parameters {
     42   public static final String NULL_VALUE= "null";
     43 
     44   /**
     45    * Creates the parameters needed for constructing a test class instance.
     46    * @param finder TODO
     47    */
     48   public static Object[] createInstantiationParameters(Constructor ctor,
     49       String methodAnnotation,
     50       IAnnotationFinder finder,
     51       String[] parameterNames,
     52       Map<String, String> params, XmlSuite xmlSuite)
     53   {
     54     return createParameters(ctor.toString(), ctor.getParameterTypes(),
     55         finder.findOptionalValues(ctor), methodAnnotation, finder, parameterNames,
     56             new MethodParameters(params, Collections.<String, String>emptyMap()),
     57             xmlSuite);
     58   }
     59 
     60   /**
     61    * Creates the parameters needed for the specified <tt>@Configuration</tt> <code>Method</code>.
     62    *
     63    * @param m the configuraton method
     64    * @param currentTestMethod the current @Test method or <code>null</code> if no @Test is available (this is not
     65    *    only in case the configuration method is a @Before/@AfterMethod
     66    * @param finder the annotation finder
     67    */
     68   public static Object[] createConfigurationParameters(Method m,
     69       Map<String, String> params,
     70       Object[] parameterValues,
     71       @Nullable ITestNGMethod currentTestMethod,
     72       IAnnotationFinder finder,
     73       XmlSuite xmlSuite,
     74       ITestContext ctx,
     75       ITestResult testResult)
     76   {
     77     Method currentTestMeth= currentTestMethod != null ?
     78         currentTestMethod.getMethod() : null;
     79 
     80     Map<String, String> methodParams = currentTestMethod != null
     81         ? currentTestMethod.findMethodParameters(ctx.getCurrentXmlTest())
     82         : Collections.<String, String>emptyMap();
     83 
     84     return createParameters(m,
     85         new MethodParameters(params,
     86             methodParams,
     87             parameterValues,
     88             currentTestMeth, ctx, testResult),
     89         finder, xmlSuite, IConfigurationAnnotation.class, "@Configuration");
     90   }
     91 
     92   ////////////////////////////////////////////////////////
     93 
     94   public static Object getInjectedParameter(Class<?> c, Method method, ITestContext context,
     95       ITestResult testResult) {
     96     Object result = null;
     97     if (Method.class.equals(c)) {
     98       result = method;
     99     }
    100     else if (ITestContext.class.equals(c)) {
    101       result = context;
    102     }
    103     else if (XmlTest.class.equals(c)) {
    104       result = context.getCurrentXmlTest();
    105     }
    106     else if (ITestResult.class.equals(c)) {
    107       result = testResult;
    108     }
    109     return result;
    110   }
    111 
    112   /**
    113    * @return An array of parameters suitable to invoke this method, possibly
    114    * picked from the property file
    115    */
    116   private static Object[] createParameters(String methodName,
    117       Class[] parameterTypes,
    118       String[] optionalValues,
    119       String methodAnnotation,
    120       IAnnotationFinder finder,
    121       String[] parameterNames, MethodParameters params, XmlSuite xmlSuite)
    122   {
    123     Object[] result = new Object[0];
    124     if(parameterTypes.length > 0) {
    125       List<Object> vResult = Lists.newArrayList();
    126 
    127       checkParameterTypes(methodName, parameterTypes, methodAnnotation, parameterNames);
    128 
    129       for(int i = 0, j = 0; i < parameterTypes.length; i++) {
    130         Object inject = getInjectedParameter(parameterTypes[i], params.currentTestMethod,
    131             params.context, params.testResult);
    132         if (inject != null) {
    133           vResult.add(inject);
    134         }
    135         else {
    136           if (j < parameterNames.length) {
    137             String p = parameterNames[j];
    138             String value = params.xmlParameters.get(p);
    139             if(null == value) {
    140               // try SysEnv entries
    141               value= System.getProperty(p);
    142             }
    143             if (null == value) {
    144               if (optionalValues != null) {
    145                 value = optionalValues[i];
    146               }
    147               if (null == value) {
    148               throw new TestNGException("Parameter '" + p + "' is required by "
    149                   + methodAnnotation
    150                   + " on method "
    151                   + methodName
    152                   + " but has not been marked @Optional or defined\n"
    153                   + (xmlSuite.getFileName() != null ? "in "
    154                   + xmlSuite.getFileName() : ""));
    155               }
    156             }
    157 
    158             vResult.add(convertType(parameterTypes[i], value, p));
    159             j++;
    160           }
    161         }
    162       }
    163 
    164       result = vResult.toArray(new Object[vResult.size()]);
    165     }
    166 
    167     return result;
    168   }
    169 
    170   private static void checkParameterTypes(String methodName,
    171       Class[] parameterTypes, String methodAnnotation, String[] parameterNames)
    172   {
    173     int totalLength = parameterTypes.length;
    174     Set<Class> injectedTypes = new HashSet<Class>() {
    175       private static final long serialVersionUID = -5324894581793435812L;
    176 
    177     {
    178       add(ITestContext.class);
    179       add(ITestResult.class);
    180       add(XmlTest.class);
    181       add(Method.class);
    182       add(Object[].class);
    183     }};
    184     for (Class parameterType : parameterTypes) {
    185       if (injectedTypes.contains(parameterType)) {
    186         totalLength--;
    187       }
    188     }
    189 
    190     if (parameterNames.length != totalLength) {
    191       throw new TestNGException( "Method " + methodName + " requires "
    192           + parameterTypes.length + " parameters but "
    193           + parameterNames.length
    194           + " were supplied in the "
    195           + methodAnnotation
    196           + " annotation.");
    197     }
    198   }
    199 
    200   public static Object convertType(Class type, String value, String paramName) {
    201     Object result = null;
    202 
    203     if(NULL_VALUE.equals(value.toLowerCase())) {
    204       if(type.isPrimitive()) {
    205         Utils.log("Parameters", 2, "Attempt to pass null value to primitive type parameter '" + paramName + "'");
    206       }
    207 
    208       return null; // null value must be used
    209     }
    210 
    211     if(type == String.class) {
    212       result = value;
    213     }
    214     else if(type == int.class || type == Integer.class) {
    215       result = Integer.parseInt(value);
    216     }
    217     else if(type == boolean.class || type == Boolean.class) {
    218       result = Boolean.valueOf(value);
    219     }
    220     else if(type == byte.class || type == Byte.class) {
    221       result = Byte.parseByte(value);
    222     }
    223     else if(type == char.class || type == Character.class) {
    224       result = value.charAt(0);
    225     }
    226     else if(type == double.class || type == Double.class) {
    227       result = Double.parseDouble(value);
    228     }
    229     else if(type == float.class || type == Float.class) {
    230       result = Float.parseFloat(value);
    231     }
    232     else if(type == long.class || type == Long.class) {
    233       result = Long.parseLong(value);
    234     }
    235     else if(type == short.class || type == Short.class) {
    236       result = Short.parseShort(value);
    237     }
    238     else if (type.isEnum()) {
    239     	result = Enum.valueOf(type, value);
    240     }
    241     else {
    242       assert false : "Unsupported type parameter : " + type;
    243     }
    244 
    245     return result;
    246   }
    247 
    248   private static DataProviderHolder findDataProvider(Object instance, ITestClass clazz,
    249                                                      ConstructorOrMethod m,
    250                                                      IAnnotationFinder finder, ITestContext context) {
    251     DataProviderHolder result = null;
    252 
    253     IDataProvidable dp = findDataProviderInfo(clazz, m, finder);
    254     if (dp != null) {
    255       String dataProviderName = dp.getDataProvider();
    256       Class dataProviderClass = dp.getDataProviderClass();
    257 
    258       if (! Utils.isStringEmpty(dataProviderName)) {
    259         result = findDataProvider(instance, clazz, finder, dataProviderName, dataProviderClass, context);
    260 
    261         if(null == result) {
    262           throw new TestNGException("Method " + m + " requires a @DataProvider named : "
    263               + dataProviderName + (dataProviderClass != null ? " in class " + dataProviderClass.getName() : "")
    264               );
    265         }
    266       }
    267     }
    268 
    269     return result;
    270   }
    271 
    272   /**
    273    * Find the data provider info (data provider name and class) on either @Test(dataProvider),
    274    * @Factory(dataProvider) on a method or @Factory(dataProvider) on a constructor.
    275    */
    276   private static IDataProvidable findDataProviderInfo(ITestClass clazz, ConstructorOrMethod m,
    277       IAnnotationFinder finder) {
    278     IDataProvidable result;
    279 
    280     if (m.getMethod() != null) {
    281       //
    282       // @Test(dataProvider) on a method
    283       //
    284       result = AnnotationHelper.findTest(finder, m.getMethod());
    285       if (result == null) {
    286         //
    287         // @Factory(dataProvider) on a method
    288         //
    289         result = AnnotationHelper.findFactory(finder, m.getMethod());
    290       }
    291       if (result == null) {
    292         //
    293         // @Test(dataProvider) on a class
    294         result = AnnotationHelper.findTest(finder, clazz.getRealClass());
    295       }
    296     } else {
    297       //
    298       // @Factory(dataProvider) on a constructor
    299       //
    300       result = AnnotationHelper.findFactory(finder, m.getConstructor());
    301     }
    302 
    303     return result;
    304   }
    305 
    306   /**
    307    * Find a method that has a @DataProvider(name=name)
    308    */
    309   private static DataProviderHolder findDataProvider(Object instance, ITestClass clazz,
    310                                                      IAnnotationFinder finder,
    311                                                      String name, Class dataProviderClass,
    312                                                      ITestContext context)
    313   {
    314     DataProviderHolder result = null;
    315 
    316     Class cls = clazz.getRealClass();
    317     boolean shouldBeStatic = false;
    318     if (dataProviderClass != null) {
    319       cls = dataProviderClass;
    320       shouldBeStatic = true;
    321     }
    322 
    323     for (Method m : ClassHelper.getAvailableMethods(cls)) {
    324       IDataProviderAnnotation dp = finder.findAnnotation(m, IDataProviderAnnotation.class);
    325       if (null != dp && name.equals(getDataProviderName(dp, m))) {
    326         if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) {
    327           Injector injector = context.getInjector(clazz);
    328           if (injector != null) {
    329             instance = injector.getInstance(dataProviderClass);
    330           }
    331         }
    332 
    333         if (result != null) {
    334           throw new TestNGException("Found two providers called '" + name + "' on " + cls);
    335         }
    336         result = new DataProviderHolder(dp, m, instance);
    337       }
    338     }
    339 
    340     return result;
    341   }
    342 
    343   private static String getDataProviderName(IDataProviderAnnotation dp, Method m) {
    344 	  return Strings.isNullOrEmpty(dp.getName()) ? m.getName() : dp.getName();
    345   }
    346 
    347   @SuppressWarnings({"deprecation"})
    348   private static Object[] createParameters(Method m, MethodParameters params,
    349       IAnnotationFinder finder, XmlSuite xmlSuite, Class annotationClass, String atName)
    350   {
    351     List<Object> result = Lists.newArrayList();
    352 
    353     Object[] extraParameters;
    354     //
    355     // Try to find an @Parameters annotation
    356     //
    357     IParametersAnnotation annotation = finder.findAnnotation(m, IParametersAnnotation.class);
    358     Class<?>[] types = m.getParameterTypes();
    359     if(null != annotation) {
    360       String[] parameterNames = annotation.getValue();
    361       extraParameters = createParameters(m.getName(), types,
    362           finder.findOptionalValues(m), atName, finder, parameterNames, params, xmlSuite);
    363     }
    364 
    365     //
    366     // Else, use the deprecated syntax
    367     //
    368     else {
    369       IParameterizable a = (IParameterizable) finder.findAnnotation(m, annotationClass);
    370       if(null != a && a.getParameters().length > 0) {
    371         String[] parameterNames = a.getParameters();
    372         extraParameters = createParameters(m.getName(), types,
    373             finder.findOptionalValues(m), atName, finder, parameterNames, params, xmlSuite);
    374       }
    375       else {
    376         extraParameters = createParameters(m.getName(), types,
    377             finder.findOptionalValues(m), atName, finder, new String[0], params, xmlSuite);
    378       }
    379     }
    380 
    381     //
    382     // Add the extra parameters we found
    383     //
    384     Collections.addAll(result, extraParameters);
    385 
    386     // If the method declared an Object[] parameter and we have parameter values, inject them
    387     for (int i = 0; i < types.length; i++) {
    388         if (Object[].class.equals(types[i])) {
    389             result.add(i, params.parameterValues);
    390         }
    391     }
    392 
    393 
    394     return result.toArray(new Object[result.size()]);
    395   }
    396 
    397   /**
    398    * If the method has parameters, fill them in. Either by using a @DataProvider
    399    * if any was provided, or by looking up <parameters> in testng.xml
    400    * @return An Iterator over the values for each parameter of this
    401    * method.
    402    */
    403   public static ParameterHolder handleParameters(ITestNGMethod testMethod,
    404       Map<String, String> allParameterNames,
    405       Object instance,
    406       MethodParameters methodParams,
    407       XmlSuite xmlSuite,
    408       IAnnotationFinder annotationFinder,
    409       Object fedInstance)
    410   {
    411     ParameterHolder result;
    412     Iterator<Object[]> parameters;
    413 
    414     /*
    415      * Do we have a @DataProvider? If yes, then we have several
    416      * sets of parameters for this method
    417      */
    418     DataProviderHolder dataProviderHolder =
    419         findDataProvider(instance, testMethod.getTestClass(),
    420             testMethod.getConstructorOrMethod(), annotationFinder, methodParams.context);
    421 
    422     if (null != dataProviderHolder) {
    423       int parameterCount = testMethod.getConstructorOrMethod().getParameterTypes().length;
    424 
    425       for (int i = 0; i < parameterCount; i++) {
    426         String n = "param" + i;
    427         allParameterNames.put(n, n);
    428       }
    429 
    430       parameters = MethodInvocationHelper.invokeDataProvider(
    431           dataProviderHolder.instance, /* a test instance or null if the dataprovider is static*/
    432           dataProviderHolder.method,
    433           testMethod,
    434           methodParams.context,
    435           fedInstance,
    436           annotationFinder);
    437 
    438       Iterator<Object[]> filteredParameters = filterParameters(parameters,
    439           testMethod.getInvocationNumbers());
    440 
    441       result = new ParameterHolder(filteredParameters, ParameterOrigin.ORIGIN_DATA_PROVIDER,
    442           dataProviderHolder);
    443     }
    444     else {
    445       //
    446       // Normal case: we have only one set of parameters coming from testng.xml
    447       //
    448       allParameterNames.putAll(methodParams.xmlParameters);
    449       // Create an Object[][] containing just one row of parameters
    450       Object[][] allParameterValuesArray = new Object[1][];
    451       allParameterValuesArray[0] = createParameters(testMethod.getMethod(),
    452           methodParams, annotationFinder, xmlSuite, ITestAnnotation.class, "@Test");
    453 
    454       // Mark that this method needs to have at least a certain
    455       // number of invocations (needed later to call AfterGroups
    456       // at the right time).
    457       testMethod.setParameterInvocationCount(allParameterValuesArray.length);
    458       // Turn it into an Iterable
    459       parameters = MethodHelper.createArrayIterator(allParameterValuesArray);
    460 
    461       result = new ParameterHolder(parameters, ParameterOrigin.ORIGIN_XML, null);
    462     }
    463 
    464     return result;
    465   }
    466 
    467   /**
    468    * If numbers is empty, return parameters, otherwise, return a subset of parameters
    469    * whose ordinal number match these found in numbers.
    470    */
    471   static private Iterator<Object[]> filterParameters(Iterator<Object[]> parameters,
    472       List<Integer> list) {
    473     if (list.isEmpty()) {
    474       return parameters;
    475     } else {
    476       List<Object[]> result = Lists.newArrayList();
    477       int i = 0;
    478       while (parameters.hasNext()) {
    479         Object[] next = parameters.next();
    480         if (list.contains(i)) {
    481           result.add(next);
    482         }
    483         i++;
    484       }
    485       return new ArrayIterator(result.toArray(new Object[list.size()][]));
    486     }
    487   }
    488 
    489   private static void ppp(String s) {
    490     System.out.println("[Parameters] " + s);
    491   }
    492 
    493   /** A parameter passing helper class. */
    494   public static class MethodParameters {
    495     private final Map<String, String> xmlParameters;
    496     private final Method currentTestMethod;
    497     private final ITestContext context;
    498     private Object[] parameterValues;
    499     public ITestResult testResult;
    500 
    501     public MethodParameters(Map<String, String> params, Map<String, String> methodParams) {
    502       this(params, methodParams, null, null, null, null);
    503     }
    504 
    505     public MethodParameters(Map<String, String> params, Map<String, String> methodParams,
    506         Method m) {
    507       this(params, methodParams, null, m, null, null);
    508     }
    509 
    510     /**
    511      * @param params parameters found in the suite and test tags
    512      * @param methodParams parameters found in the include tag
    513      * @param pv
    514      * @param m
    515      * @param ctx
    516      * @param tr
    517      */
    518     public MethodParameters(Map<String, String> params,
    519         Map<String, String> methodParams,
    520         Object[] pv, Method m, ITestContext ctx,
    521         ITestResult tr) {
    522       Map<String, String> allParams = Maps.newHashMap();
    523       allParams.putAll(params);
    524       allParams.putAll(methodParams);
    525       xmlParameters = allParams;
    526       currentTestMethod = m;
    527       context = ctx;
    528       parameterValues = pv;
    529       testResult = tr;
    530     }
    531   }
    532 }
    533