package org.testng.internal; | |
import org.testng.IConfigureCallBack; | |
import org.testng.IHookCallBack; | |
import org.testng.ITestClass; | |
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.IExpectedExceptionsAnnotation; | |
import org.testng.annotations.ITestAnnotation; | |
import org.testng.annotations.ITestOrConfiguration; | |
import org.testng.collections.Lists; | |
import org.testng.collections.Maps; | |
import org.testng.internal.annotations.AnnotationHelper; | |
import org.testng.internal.annotations.IAnnotationFinder; | |
import org.testng.internal.thread.IExecutor; | |
import org.testng.internal.thread.IFutureResult; | |
import org.testng.internal.thread.ThreadExecutionException; | |
import org.testng.internal.thread.ThreadTimeoutException; | |
import org.testng.internal.thread.ThreadUtil; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.Collection; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.regex.Pattern; | |
/** | |
* Collection of helper methods to help sort and arrange methods. | |
* | |
* @author <a href="mailto:cedric@beust.com">Cedric Beust</a> | |
* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a> | |
*/ | |
public class MethodHelper { | |
// static private boolean m_quiet = true; | |
// /// | |
// public methods | |
// | |
public static ITestNGMethod[] collectAndOrderMethods(List<ITestNGMethod> methods, | |
RunInfo runInfo, IAnnotationFinder finder, List<ITestNGMethod> outExcludedMethods) | |
{ | |
return internalCollectAndOrderMethods(methods.toArray(new ITestNGMethod[methods.size()]), | |
true /* forTest */, runInfo, finder, false /* unique */, outExcludedMethods); | |
} | |
/** | |
* @param methods | |
* @return All the methods that match the filtered groups. If a method belongs | |
* to an excluded group, it is automatically excluded. | |
*/ | |
public static ITestNGMethod[] collectAndOrderConfigurationMethods(List<ITestNGMethod> methods, | |
RunInfo runInfo, IAnnotationFinder finder, boolean unique, List<ITestNGMethod> outExcludedMethods) | |
{ | |
return internalCollectAndOrderMethods(methods.toArray(new ITestNGMethod[methods.size()]), | |
false /* forTests */, runInfo, finder, unique, outExcludedMethods); | |
} | |
private static ITestNGMethod[] internalCollectAndOrderMethods(ITestNGMethod[] methods, | |
boolean forTests, RunInfo runInfo, IAnnotationFinder finder, | |
boolean unique, List<ITestNGMethod> outExcludedMethods) | |
{ | |
List<ITestNGMethod> includedMethods = Lists.newArrayList(); | |
collectMethodsByGroup(methods, | |
forTests, | |
includedMethods, | |
outExcludedMethods, | |
runInfo, | |
finder, | |
unique); | |
return includedMethods.size() > 1 ? | |
sortMethods(forTests, includedMethods, finder).toArray(new ITestNGMethod[]{}) | |
: includedMethods.toArray(new ITestNGMethod[]{}); | |
} | |
/** | |
* @return all the methods that belong to the group specified by the regular | |
* expression groupRegExp. methods[] is the list of all the methods we | |
* are choosing from and method is the method that owns the dependsOnGroups | |
* statement (only used if a group is missing to flag an error on that method). | |
*/ | |
public static ITestNGMethod[] findMethodsThatBelongToGroup( | |
ITestNGMethod method, | |
ITestNGMethod[] methods, String groupRegexp) | |
{ | |
boolean foundGroup = false; | |
List<ITestNGMethod> vResult = Lists.newArrayList(); | |
for (ITestNGMethod tm : methods) { | |
String[] groups = tm.getGroups(); | |
for (String group : groups) { | |
if (Pattern.matches(groupRegexp, group)) { | |
vResult.add(tm); | |
foundGroup = true; | |
} | |
} | |
} | |
if (! foundGroup) { | |
method.setMissingGroup(groupRegexp); | |
} | |
ITestNGMethod[] result = vResult.toArray(new ITestNGMethod[vResult.size()]); | |
return result; | |
} | |
public static ITestNGMethod[] findMethodsNamed(ITestNGMethod m, | |
ITestNGMethod[] methods, String[] regexps) | |
{ | |
String mainMethod = calculateMethodCanonicalName(m); | |
List<ITestNGMethod> vResult = Lists.newArrayList(); | |
String currentRegexp = null; | |
for (String fullyQualifiedRegexp : regexps) { | |
boolean foundAtLeastAMethod = false; | |
if(null != fullyQualifiedRegexp) { | |
String regexp = escapeRegexp(fullyQualifiedRegexp); | |
currentRegexp = regexp; | |
boolean usePackage = regexp.indexOf(".") != -1; | |
for (ITestNGMethod method : methods) { | |
Method thisMethod = method.getMethod(); | |
String thisMethodName = thisMethod.getName(); | |
String methodName = usePackage ? | |
calculateMethodCanonicalName(thisMethod) | |
: thisMethodName; | |
// ppp("COMPARING\n" + regexp + "\n" + methodName); | |
if (Pattern.matches(regexp, methodName)) { | |
vResult.add(method); | |
foundAtLeastAMethod = true; | |
} | |
} | |
} | |
if (!foundAtLeastAMethod) { | |
if (m.ignoreMissingDependencies()) continue; | |
if (m.isAlwaysRun()) continue; | |
Method maybeReferringTo = findMethodByName(m, currentRegexp); | |
if (maybeReferringTo != null) { | |
throw new TestNGException(mainMethod + "() is not allowed to depend on " + maybeReferringTo); | |
} | |
throw new TestNGException(mainMethod | |
+ "() is depending on nonexistent method " + currentRegexp); | |
} | |
} | |
ITestNGMethod[] result = vResult.toArray(new ITestNGMethod[vResult.size()]); | |
return result; | |
} | |
private static Method findMethodByName(ITestNGMethod mainMethod, String regExp) { | |
if (regExp == null) return null; | |
int lastDot = regExp.lastIndexOf('.'); | |
String className, methodName; | |
if (lastDot == -1) { | |
className = mainMethod.getMethod().getDeclaringClass().getCanonicalName(); | |
methodName = regExp; | |
} else { | |
methodName = regExp.substring(lastDot+1); | |
className = regExp.substring(0, lastDot); | |
} | |
try { | |
Class<?> c = Class.forName(className); | |
for (Method m : c.getDeclaredMethods()) { | |
if (methodName.equals(m.getName())) return m; | |
} | |
} catch (Exception e) {} // ignore | |
return null; | |
} | |
/** | |
* Escapes $ in regexps as it is not meant for end-line matching, but inner class matches. | |
* Impl.is weird as the String methods are not available in 1.4 | |
*/ | |
private static String escapeRegexp(String regex) { | |
if(regex.indexOf('$') == -1) return regex; | |
String[] fragments= regex.split("\\$"); | |
StringBuffer result= new StringBuffer(); | |
for(int i= 0; i < fragments.length - 1; i++) { | |
result.append(fragments[i]).append("\\$"); | |
} | |
result.append(fragments[fragments.length - 1]); | |
if(regex.endsWith("$")) result.append("\\$"); | |
return result.toString(); | |
} | |
/** | |
* Read the expected exceptions, if any (need to handle both the old and new | |
* syntax) | |
*/ | |
public static ExpectedExceptionsHolder findExpectedExceptions(IAnnotationFinder finder, | |
Method method) { | |
ExpectedExceptionsHolder result = null; | |
IExpectedExceptionsAnnotation expectedExceptions= | |
(IExpectedExceptionsAnnotation) finder.findAnnotation(method, | |
IExpectedExceptionsAnnotation.class); | |
// Old syntax | |
if (expectedExceptions != null) { | |
result = new ExpectedExceptionsHolder(expectedExceptions.getValue(), ".*"); | |
} | |
else { | |
// New syntax | |
ITestAnnotation testAnnotation = | |
(ITestAnnotation) finder.findAnnotation(method, ITestAnnotation.class); | |
if (testAnnotation != null) { | |
Class<?>[] ee = testAnnotation.getExpectedExceptions(); | |
if (testAnnotation != null && ee.length > 0) { | |
result = new ExpectedExceptionsHolder(ee, | |
testAnnotation.getExpectedExceptionsMessageRegExp()); | |
} | |
} | |
} | |
return result; | |
} | |
// | |
// End of public methods | |
// /// | |
public static boolean isEnabled(Class<?> objectClass, IAnnotationFinder finder) { | |
ITestAnnotation testClassAnnotation= AnnotationHelper.findTest(finder, objectClass); | |
return isEnabled(testClassAnnotation); | |
} | |
public static boolean isEnabled(Method m, IAnnotationFinder finder) { | |
ITestAnnotation annotation = AnnotationHelper.findTest(finder, m); | |
// If no method annotation, look for one on the class | |
if (null == annotation) { | |
annotation = AnnotationHelper.findTest(finder, m.getDeclaringClass()); | |
} | |
return isEnabled(annotation); | |
} | |
public static boolean isEnabled(ITestOrConfiguration test) { | |
return null == test || (null != test && test.getEnabled()); | |
} | |
public static ITestNGMethod[] findMethodsThatBelongToGroup( | |
ITestNGMethod method, | |
List<ITestNGMethod> methods, String groupRegexp) | |
{ | |
ITestNGMethod[] allMethods = methods.toArray(new ITestNGMethod[methods | |
.size()]); | |
return findMethodsThatBelongToGroup(method, allMethods, groupRegexp); | |
} | |
/** | |
* @return The transitive closure of all the groups/methods included. | |
*/ | |
public static void findGroupTransitiveClosure(XmlMethodSelector xms, | |
List<ITestNGMethod> includedMethods, | |
List<ITestNGMethod> allMethods, | |
String[] includedGroups, | |
Set<String> outGroups, Set<ITestNGMethod> outMethods) | |
{ | |
Map<ITestNGMethod, ITestNGMethod> runningMethods = Maps.newHashMap(); | |
for (ITestNGMethod m : includedMethods) { | |
runningMethods.put(m, m); | |
} | |
Map<String, String> runningGroups = Maps.newHashMap(); | |
for (String thisGroup : includedGroups) { | |
runningGroups.put(thisGroup, thisGroup); | |
} | |
boolean keepGoing = true; | |
Map<ITestNGMethod, ITestNGMethod> newMethods = Maps.newHashMap(); | |
while (keepGoing) { | |
for (ITestNGMethod m : includedMethods) { | |
// | |
// Depends on groups? | |
// Adds all included methods to runningMethods | |
// | |
String[] ig = m.getGroupsDependedUpon(); | |
for (String g : ig) { | |
if (! runningGroups.containsKey(g)) { | |
// Found a new included group, add all the methods it contains to | |
// our outMethod closure | |
runningGroups.put(g, g); | |
ITestNGMethod[] im = | |
findMethodsThatBelongToGroup(m, allMethods, g); | |
for (ITestNGMethod thisMethod : im) { | |
if (! runningMethods.containsKey(thisMethod)) { | |
runningMethods.put(thisMethod, thisMethod); | |
newMethods.put(thisMethod, thisMethod); | |
} | |
} | |
} | |
} // groups | |
// | |
// Depends on methods? | |
// Adds all depended methods to runningMethods | |
// | |
String[] mdu = m.getMethodsDependedUpon(); | |
for (String tm : mdu) { | |
ITestNGMethod thisMethod = findMethodNamed(tm, allMethods); | |
if (thisMethod != null && ! runningMethods.containsKey(thisMethod)) { | |
runningMethods.put(thisMethod, thisMethod); | |
newMethods.put(thisMethod, thisMethod); | |
} | |
} | |
} // methods | |
// | |
// Only keep going if new methods have been added | |
// | |
keepGoing = newMethods.size() > 0; | |
includedMethods = Lists.newArrayList(); | |
includedMethods.addAll(newMethods.keySet()); | |
newMethods = Maps.newHashMap(); | |
} // while keepGoing | |
outMethods.addAll(runningMethods.keySet()); | |
outGroups.addAll(runningGroups.keySet()); | |
} | |
/** | |
* Extracts the map of groups and their corresponding methods from the <code>classes</code>. | |
*/ | |
public static Map<String, List<ITestNGMethod>> findGroupsMethods(Collection<ITestClass> classes, boolean before) { | |
Map<String, List<ITestNGMethod>> result = Maps.newHashMap(); | |
for (ITestClass cls : classes) { | |
ITestNGMethod[] methods = before ? cls.getBeforeGroupsMethods() : cls.getAfterGroupsMethods(); | |
for (ITestNGMethod method : methods) { | |
for (String group : before ? method.getBeforeGroups() : method.getAfterGroups()) { | |
List<ITestNGMethod> methodList = result.get(group); | |
if (methodList == null) { | |
methodList = Lists.newArrayList(); | |
result.put(group, methodList); | |
} | |
// NOTE(cbeust, 2007/01/23) | |
// BeforeGroups/AfterGroups methods should only be invoked once. | |
// I should probably use a map instead of a list for a contains(), but | |
// this list should usually be fairly short | |
if (! methodList.contains(method)) { | |
methodList.add(method); | |
} | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* Extracts the unique list of <code>ITestNGMethod</code>s. | |
*/ | |
public static List<ITestNGMethod> uniqueMethodList(Collection<List<ITestNGMethod>> methods) { | |
Map<ITestNGMethod, ITestNGMethod> uniq = Maps.newHashMap(); | |
for (List<ITestNGMethod> l : methods) { | |
for (ITestNGMethod m : l) { | |
uniq.put(m, m); | |
} | |
} | |
List<ITestNGMethod> result = Lists.newArrayList(); | |
result.addAll(uniq.values()); | |
return result; | |
} | |
private static ITestNGMethod findMethodNamed(String tm, List<ITestNGMethod> allMethods) { | |
for (ITestNGMethod m : allMethods) { | |
// TODO(cbeust): account for package | |
String methodName = | |
m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName(); | |
if (methodName.equals(tm)) return m; | |
} | |
return null; | |
} | |
private static boolean includeMethod(ITestOrConfiguration annotation, | |
RunInfo runInfo, ITestNGMethod tm, boolean forTests, boolean unique, List<ITestNGMethod> outIncludedMethods) | |
{ | |
boolean result = false; | |
if (isEnabled(annotation)) { | |
if (runInfo.includeMethod(tm, forTests)) { | |
if (unique) { | |
if (!isMethodAlreadyPresent(outIncludedMethods, tm)) { | |
result = true; | |
} | |
} | |
else { | |
result = true; | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* Collect all the methods that belong to the included groups and exclude all | |
* the methods that belong to an excluded group. | |
*/ | |
private static void collectMethodsByGroup(ITestNGMethod[] methods, | |
boolean forTests, | |
List<ITestNGMethod> outIncludedMethods, | |
List<ITestNGMethod> outExcludedMethods, | |
RunInfo runInfo, | |
IAnnotationFinder finder, boolean unique) | |
{ | |
for (ITestNGMethod tm : methods) { | |
boolean in = false; | |
Method m = tm.getMethod(); | |
// | |
// @Test method | |
// | |
if (forTests) { | |
in = includeMethod(AnnotationHelper.findTest(finder, m), | |
runInfo, tm, forTests, unique, outIncludedMethods); | |
} | |
// | |
// @Configuration method | |
// | |
else { | |
IConfigurationAnnotation annotation = AnnotationHelper.findConfiguration(finder, m); | |
if (annotation.getAlwaysRun()) { | |
in = true; | |
} | |
else { | |
in = includeMethod(AnnotationHelper.findTest(finder, m), | |
runInfo, tm, forTests, unique, outIncludedMethods); | |
} | |
} | |
if (in) { | |
outIncludedMethods.add(tm); | |
} | |
else { | |
outExcludedMethods.add(tm); | |
} | |
} | |
} | |
/** | |
* @param result | |
* @param tm | |
* @return true if a method by a similar name (and same hierarchy) already | |
* exists | |
*/ | |
private static boolean isMethodAlreadyPresent(List<ITestNGMethod> result, | |
ITestNGMethod tm) { | |
for (ITestNGMethod m : result) { | |
Method jm1 = m.getMethod(); | |
Method jm2 = tm.getMethod(); | |
if (jm1.getName().equals(jm2.getName())) { | |
// Same names, see if they are in the same hierarchy | |
Class<?> c1 = jm1.getDeclaringClass(); | |
Class<?> c2 = jm2.getDeclaringClass(); | |
if (c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
private static Graph<ITestNGMethod> topologicalSort(ITestNGMethod[] methods, | |
List<ITestNGMethod> sequentialList, List<ITestNGMethod> parallelList) { | |
Graph<ITestNGMethod> result = new Graph<ITestNGMethod>(); | |
if (methods.length == 0) return result; | |
// | |
// Create the graph | |
// | |
for (ITestNGMethod m : methods) { | |
result.addNode(m); | |
List<ITestNGMethod> predecessors = Lists.newArrayList(); | |
String[] methodsDependedUpon = m.getMethodsDependedUpon(); | |
String[] groupsDependedUpon = m.getGroupsDependedUpon(); | |
if (methodsDependedUpon.length > 0) { | |
ITestNGMethod[] methodsNamed = | |
MethodHelper.findMethodsNamed(m, methods, methodsDependedUpon); | |
for (ITestNGMethod pred : methodsNamed) { | |
predecessors.add(pred); | |
} | |
} | |
if (groupsDependedUpon.length > 0) { | |
for (String group : groupsDependedUpon) { | |
ITestNGMethod[] methodsThatBelongToGroup = | |
MethodHelper.findMethodsThatBelongToGroup(m, methods, group); | |
for (ITestNGMethod pred : methodsThatBelongToGroup) { | |
predecessors.add(pred); | |
} | |
} | |
} | |
for (ITestNGMethod predecessor : predecessors) { | |
result.addPredecessor(m, predecessor); | |
} | |
} | |
result.topologicalSort(); | |
sequentialList.addAll(result.getStrictlySortedNodes()); | |
parallelList.addAll(result.getIndependentNodes()); | |
return result; | |
} | |
public static String calculateMethodCanonicalName(ITestNGMethod m) { | |
return calculateMethodCanonicalName(m.getMethod()); | |
} | |
private static String calculateMethodCanonicalName(Method m) { | |
String packageName = m.getDeclaringClass().getName() + "." + m.getName(); | |
// Try to find the method on this class or parents | |
Class<?> cls = m.getDeclaringClass(); | |
while (cls != Object.class) { | |
try { | |
if (cls.getDeclaredMethod(m.getName(), m.getParameterTypes()) != null) { | |
packageName = cls.getName(); | |
break; | |
} | |
} | |
catch (Exception e) { | |
// ignore | |
} | |
cls = cls.getSuperclass(); | |
} | |
String result = packageName + "." + m.getName(); | |
return result; | |
} | |
private static List<ITestNGMethod> sortMethods(boolean forTests, | |
List<ITestNGMethod> allMethods, IAnnotationFinder finder) { | |
List<ITestNGMethod> sl = Lists.newArrayList(); | |
List<ITestNGMethod> pl = Lists.newArrayList(); | |
ITestNGMethod[] allMethodsArray = allMethods.toArray(new ITestNGMethod[allMethods.size()]); | |
// Fix the method inheritance if these are @Configuration methods to make | |
// sure base classes are invoked before child classes if 'before' and the | |
// other way around if they are 'after' | |
if (!forTests && allMethodsArray.length > 0) { | |
ITestNGMethod m = allMethodsArray[0]; | |
boolean before = m.isBeforeClassConfiguration() | |
|| m.isBeforeMethodConfiguration() || m.isBeforeSuiteConfiguration() | |
|| m.isBeforeTestConfiguration(); | |
MethodInheritance.fixMethodInheritance(allMethodsArray, before); | |
} | |
topologicalSort(allMethodsArray, sl, pl); | |
List<ITestNGMethod> result = Lists.newArrayList(); | |
result.addAll(sl); | |
result.addAll(pl); | |
return result; | |
} | |
public static void ppp(String s) { | |
System.out.println("[MethodHelper] " + s); | |
} | |
/** | |
* @param method | |
* @param allTestMethods | |
* @return A sorted array containing all the methods 'method' depends on | |
*/ | |
public static List<ITestNGMethod> getMethodsDependedUpon(ITestNGMethod method, ITestNGMethod[] methods) { | |
List<ITestNGMethod> parallelList = Lists.newArrayList(); | |
List<ITestNGMethod> sequentialList = Lists.newArrayList(); | |
Graph<ITestNGMethod> g = topologicalSort(methods, sequentialList, parallelList); | |
List<ITestNGMethod> result = g.findPredecessors(method); | |
return result; | |
} | |
public static Object invokeMethod(Method thisMethod, Object instance, Object[] parameters) | |
throws InvocationTargetException, IllegalAccessException | |
{ | |
Object result = null; | |
// TESTNG-326, allow IObjectFactory to load from non-standard classloader | |
// If the instance has a different classloader, its class won't match the method's class | |
if (!thisMethod.getDeclaringClass().isAssignableFrom(instance.getClass())) { | |
// for some reason, we can't call this method on this class | |
// is it static? | |
boolean isStatic = Modifier.isStatic(thisMethod.getModifiers()); | |
if (!isStatic) { | |
// not static, so grab a method with the same name and signature in this case | |
Class<?> clazz = instance.getClass(); | |
try { | |
thisMethod = clazz.getMethod(thisMethod.getName(), thisMethod.getParameterTypes()); | |
} catch (Exception e) { | |
// ignore, the method may be private | |
boolean found = false; | |
for (; clazz != null; clazz = clazz.getSuperclass()) { | |
try { | |
thisMethod = clazz.getDeclaredMethod(thisMethod.getName(), thisMethod.getParameterTypes()); | |
found = true; | |
break; | |
} catch (Exception e2) {} | |
} | |
if (!found) { | |
//should we assert here? Or just allow it to fail on invocation? | |
if (thisMethod.getDeclaringClass().getName().equals(instance.getClass().getName())) { | |
throw new RuntimeException("Can't invoke method " + thisMethod + ", probably due to classloader mismatch"); | |
} | |
throw new RuntimeException("Can't invoke method " + thisMethod + " on this instance of " + instance.getClass()+ " due to class mismatch"); | |
} | |
} | |
} | |
} | |
boolean isPublic = Modifier.isPublic(thisMethod.getModifiers()); | |
try { | |
if (!isPublic) { | |
thisMethod.setAccessible(true); | |
} | |
result = thisMethod.invoke(instance, parameters); | |
} | |
finally { | |
if (!isPublic) { | |
thisMethod.setAccessible(false); | |
} | |
} | |
return result; | |
} | |
public static Iterator<Object[]> createArrayIterator(final Object[][] objects) { | |
ArrayIterator result = new ArrayIterator(objects); | |
return result; | |
} | |
public static Iterator<Object[]> invokeDataProvider(Object instance, | |
Method dataProvider, ITestNGMethod method, ITestContext testContext, | |
Object fedInstance, IAnnotationFinder annotationFinder) | |
{ | |
Iterator<Object[]> result = null; | |
Method testMethod = method.getMethod(); | |
// If it returns an Object[][], convert it to an Iterable<Object[]> | |
try { | |
List<Object> lParameters = Lists.newArrayList(); | |
// Go through all the parameters declared on this Data Provider and | |
// make sure we have at most one Method and one ITestContext. | |
// Anything else is an error | |
Class<?>[] parameterTypes = dataProvider.getParameterTypes(); | |
if (parameterTypes.length > 2) { | |
throw new TestNGException("DataProvider " + dataProvider + " cannot have more than two parameters"); | |
} | |
int i = 0; | |
for (Class<?> cls : parameterTypes) { | |
boolean isTestInstance = annotationFinder.hasTestInstance(dataProvider, i++); | |
if (cls.equals(Method.class)) { | |
lParameters.add(testMethod); | |
} | |
else if (cls.equals(ITestContext.class)) { | |
lParameters.add(testContext); | |
} | |
else if (isTestInstance) { | |
lParameters.add(fedInstance); | |
} | |
} | |
Object[] parameters = lParameters.toArray(new Object[lParameters.size()]); | |
Class< ? > returnType = dataProvider.getReturnType(); | |
if (Object[][].class.isAssignableFrom(returnType)) { | |
Object[][] oResult = (Object[][]) MethodHelper.invokeMethod( | |
dataProvider, instance, parameters); | |
method.setParameterInvocationCount(oResult.length); | |
result = MethodHelper.createArrayIterator(oResult); | |
} | |
else if (Iterator.class.isAssignableFrom(returnType)) { | |
// Already an Iterable<Object[]>, assign it directly | |
result = (Iterator<Object[]>) MethodHelper.invokeMethod(dataProvider, | |
instance, parameters); | |
} | |
else { | |
throw new TestNGException("Data Provider " + dataProvider | |
+ " must return" + " either Object[][] or Iterator<Object>[], not " | |
+ returnType); | |
} | |
} | |
catch (InvocationTargetException e) { | |
throw new TestNGException(e); | |
} | |
catch (IllegalAccessException e) { | |
throw new TestNGException(e); | |
} | |
return result; | |
} | |
public static String calculateMethodCanonicalName(Class<?> methodClass, String methodName) { | |
Set<Method> methods = ClassHelper.getAvailableMethods(methodClass); // TESTNG-139 | |
Method result = null; | |
for (Method m : methods) { | |
if (methodName.equals(m.getName())) { | |
result = m; | |
break; | |
} | |
} | |
return result != null ? calculateMethodCanonicalName(result) : null; | |
} | |
public static void invokeConfigurable(final Object instance, | |
final Object[] parameters, ITestClass testClass, final Method thisMethod, | |
ITestResult testResult) throws NoSuchMethodException, | |
IllegalAccessException, InvocationTargetException, Throwable | |
{ | |
Method runMethod = testClass.getRealClass().getMethod("run", | |
new Class[] { IConfigureCallBack.class, ITestResult.class }); | |
final Throwable[] error = new Throwable[1]; | |
IConfigureCallBack callback = new IConfigureCallBack() { | |
@Override | |
public void runConfigurationMethod(ITestResult tr) { | |
try { | |
invokeMethod(thisMethod, instance, parameters); | |
} catch (Throwable t) { | |
error[0] = t; | |
tr.setThrowable(t); // make Throwable available to IConfigurable | |
} | |
} | |
}; | |
runMethod.invoke(instance, new Object[] { callback, testResult }); | |
if (error[0] != null) { | |
throw error[0]; | |
} | |
} | |
/** | |
* Invokes the <code>run</code> method of the <code>IHookable</code>. | |
* | |
* @param instance the instance to invoke the method in | |
* @param parameters the parameters to be passed to <code>IHookCallBack</code> | |
* @param testClass the test class | |
* @param thisMethod the method to be invoked through the <code>IHookCallBack</code> | |
* @param testResult the current <code>ITestResult</code> passed to <code>IHookable.run</code> | |
* @throws NoSuchMethodException | |
* @throws IllegalAccessException | |
* @throws InvocationTargetException | |
* @throws Throwable thrown if the reflective call to <tt>thisMethod</code> results in an exception | |
*/ | |
public static void invokeHookable(final Object instance, | |
final Object[] parameters, | |
ITestClass testClass, | |
final Method thisMethod, | |
TestResult testResult) | |
throws Throwable | |
{ | |
Method runMethod = testClass.getRealClass().getMethod("run", | |
new Class[] { IHookCallBack.class, ITestResult.class }); | |
final Throwable[] error = new Throwable[1]; | |
IHookCallBack callback = new IHookCallBack() { | |
@Override | |
public void runTestMethod(ITestResult tr) { | |
try { | |
invokeMethod(thisMethod, instance, parameters); | |
} | |
catch(Throwable t) { | |
error[0] = t; | |
tr.setThrowable( t ); // make Throwable available to IHookable | |
} | |
} | |
}; | |
runMethod.invoke(instance, new Object[]{callback, testResult}); | |
if (error[0] != null) { | |
throw error[0]; | |
} | |
} | |
public static long calculateTimeOut(ITestNGMethod tm) { | |
long result = tm.getTimeOut() > 0 ? tm.getTimeOut() : tm.getInvocationTimeOut(); | |
return result; | |
} | |
/** | |
* Invokes a method on a separate thread in order to allow us to timeout the invocation. | |
* It uses as implementation an <code>Executor</code> and a <code>CountDownLatch</code>. | |
* @param tm the | |
* @param instance | |
* @param parameterValues | |
* @param testResult | |
* @throws InterruptedException | |
* @throws ThreadExecutionException | |
*/ | |
public static void invokeWithTimeout(ITestNGMethod tm, Object instance, Object[] parameterValues, | |
ITestResult testResult) | |
throws InterruptedException, ThreadExecutionException { | |
IExecutor exec= ThreadUtil.createExecutor(1, tm.getMethod().getName()); | |
InvokeMethodRunnable imr = new InvokeMethodRunnable(tm, instance, parameterValues); | |
IFutureResult future= exec.submitRunnable(imr); | |
exec.shutdown(); | |
long realTimeOut = calculateTimeOut(tm); | |
boolean finished = exec.awaitTermination(realTimeOut); | |
if (! finished) { | |
exec.stopNow(); | |
ThreadTimeoutException exception = new ThreadTimeoutException("Method " | |
+ tm.getMethod() | |
+ " didn't finish within the time-out " | |
+ realTimeOut); | |
exception.setStackTrace(exec.getStackTraces()[0]); | |
testResult.setThrowable(exception); | |
testResult.setStatus(ITestResult.FAILURE); | |
} | |
else { | |
Utils.log("Invoker " + Thread.currentThread().hashCode(), 3, | |
"Method " + tm.getMethod() + " completed within the time-out " + tm.getTimeOut()); | |
// We don't need the result from the future but invoking get() on it | |
// will trigger the exception that was thrown, if any | |
future.get(); | |
// done.await(); | |
testResult.setStatus(ITestResult.SUCCESS); // if no exception till here than SUCCESS | |
} | |
} | |
} | |
// /// | |
class ArrayIterator implements Iterator { | |
private Object[][] m_objects; | |
private int m_count; | |
public ArrayIterator(Object[][] objects) { | |
m_objects = objects; | |
m_count = 0; | |
} | |
@Override | |
public boolean hasNext() { | |
return m_count < m_objects.length; | |
} | |
@Override | |
public Object next() { | |
return m_objects[m_count++]; | |
} | |
@Override | |
public void remove() { | |
// TODO Auto-generated method stub | |
} | |
} |