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