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