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@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    */
createInstantiationParameters(Constructor ctor, String methodAnnotation, IAnnotationFinder finder, String[] parameterNames, Map<String, String> params, XmlSuite xmlSuite)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    */
createConfigurationParameters(Method m, Map<String, String> params, Object[] parameterValues, @Nullable ITestNGMethod currentTestMethod, IAnnotationFinder finder, XmlSuite xmlSuite, ITestContext ctx, ITestResult testResult)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 
getInjectedParameter(Class<?> c, Method method, ITestContext context, ITestResult testResult)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    */
createParameters(String methodName, Class[] parameterTypes, String[] optionalValues, String methodAnnotation, IAnnotationFinder finder, String[] parameterNames, MethodParameters params, XmlSuite xmlSuite)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 
checkParameterTypes(String methodName, Class[] parameterTypes, String methodAnnotation, String[] parameterNames)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 
convertType(Class type, String value, String paramName)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 
findDataProvider(Object instance, ITestClass clazz, ConstructorOrMethod m, IAnnotationFinder finder, ITestContext context)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    */
findDataProviderInfo(ITestClass clazz, ConstructorOrMethod m, IAnnotationFinder finder)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    */
findDataProvider(Object instance, ITestClass clazz, IAnnotationFinder finder, String name, Class dataProviderClass, ITestContext context)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 
getDataProviderName(IDataProviderAnnotation dp, Method m)343   private static String getDataProviderName(IDataProviderAnnotation dp, Method m) {
344 	  return Strings.isNullOrEmpty(dp.getName()) ? m.getName() : dp.getName();
345   }
346 
347   @SuppressWarnings({"deprecation"})
createParameters(Method m, MethodParameters params, IAnnotationFinder finder, XmlSuite xmlSuite, Class annotationClass, String atName)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    */
handleParameters(ITestNGMethod testMethod, Map<String, String> allParameterNames, Object instance, MethodParameters methodParams, XmlSuite xmlSuite, IAnnotationFinder annotationFinder, Object fedInstance)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    */
filterParameters(Iterator<Object[]> parameters, List<Integer> list)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 
ppp(String s)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 
MethodParameters(Map<String, String> params, Map<String, String> methodParams)501     public MethodParameters(Map<String, String> params, Map<String, String> methodParams) {
502       this(params, methodParams, null, null, null, null);
503     }
504 
MethodParameters(Map<String, String> params, Map<String, String> methodParams, Method m)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      */
MethodParameters(Map<String, String> params, Map<String, String> methodParams, Object[] pv, Method m, ITestContext ctx, ITestResult tr)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