MethodHelper.java revision e934f66fb4fb247ebf5fec45770bc6820fbba29a
1package org.testng.internal;
2
3import org.testng.IConfigureCallBack;
4import org.testng.IHookCallBack;
5import org.testng.ITestClass;
6import org.testng.ITestContext;
7import org.testng.ITestNGMethod;
8import org.testng.ITestResult;
9import org.testng.TestNGException;
10import org.testng.annotations.IConfigurationAnnotation;
11import org.testng.annotations.IExpectedExceptionsAnnotation;
12import org.testng.annotations.ITestAnnotation;
13import org.testng.annotations.ITestOrConfiguration;
14import org.testng.collections.Lists;
15import org.testng.collections.Maps;
16import org.testng.internal.annotations.AnnotationHelper;
17import org.testng.internal.annotations.IAnnotationFinder;
18import org.testng.internal.thread.IExecutor;
19import org.testng.internal.thread.IFutureResult;
20import org.testng.internal.thread.ThreadExecutionException;
21import org.testng.internal.thread.ThreadTimeoutException;
22import org.testng.internal.thread.ThreadUtil;
23
24import java.lang.reflect.InvocationTargetException;
25import java.lang.reflect.Method;
26import java.lang.reflect.Modifier;
27import java.util.Collection;
28import java.util.Iterator;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32import java.util.regex.Pattern;
33
34/**
35 * Collection of helper methods to help sort and arrange methods.
36 *
37 * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
38 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
39 */
40public class MethodHelper {
41
42  public static ITestNGMethod[] collectAndOrderMethods(List<ITestNGMethod> methods,
43      RunInfo runInfo, IAnnotationFinder finder, List<ITestNGMethod> outExcludedMethods)
44  {
45    return internalCollectAndOrderMethods(methods.toArray(new ITestNGMethod[methods.size()]),
46        true /* forTest */, runInfo, finder, false /* unique */, outExcludedMethods);
47  }
48
49  /**
50   * @param methods
51   * @return All the methods that match the filtered groups. If a method belongs
52   *         to an excluded group, it is automatically excluded.
53   */
54  public static ITestNGMethod[] collectAndOrderConfigurationMethods(List<ITestNGMethod> methods,
55      RunInfo runInfo, IAnnotationFinder finder, boolean unique, List<ITestNGMethod> outExcludedMethods)
56  {
57    return internalCollectAndOrderMethods(methods.toArray(new ITestNGMethod[methods.size()]),
58          false /* forTests */, runInfo, finder, unique, outExcludedMethods);
59  }
60
61  private static ITestNGMethod[] internalCollectAndOrderMethods(ITestNGMethod[] methods,
62      boolean forTests, RunInfo runInfo, IAnnotationFinder finder,
63      boolean unique, List<ITestNGMethod> outExcludedMethods)
64  {
65    List<ITestNGMethod> includedMethods = Lists.newArrayList();
66    collectMethodsByGroup(methods,
67        forTests,
68        includedMethods,
69        outExcludedMethods,
70        runInfo,
71        finder,
72        unique);
73
74    return includedMethods.size() > 1 ?
75      sortMethods(forTests, includedMethods, finder).toArray(new ITestNGMethod[]{})
76      : includedMethods.toArray(new ITestNGMethod[]{});
77  }
78
79  /**
80   * @return all the methods that belong to the group specified by the regular
81   * expression groupRegExp.  methods[] is the list of all the methods we
82   * are choosing from and method is the method that owns the dependsOnGroups
83   * statement (only used if a group is missing to flag an error on that method).
84   */
85  public static ITestNGMethod[] findMethodsThatBelongToGroup(
86      ITestNGMethod method,
87      ITestNGMethod[] methods, String groupRegexp)
88  {
89    boolean foundGroup = false;
90    List<ITestNGMethod> vResult = Lists.newArrayList();
91    for (ITestNGMethod tm : methods) {
92      String[] groups = tm.getGroups();
93      for (String group : groups) {
94        if (Pattern.matches(groupRegexp, group)) {
95          vResult.add(tm);
96          foundGroup = true;
97        }
98      }
99    }
100
101    if (! foundGroup) {
102      method.setMissingGroup(groupRegexp);
103    }
104
105    ITestNGMethod[] result = vResult.toArray(new ITestNGMethod[vResult.size()]);
106    return result;
107  }
108
109  public static ITestNGMethod[] findMethodsNamed(ITestNGMethod m,
110      ITestNGMethod[] methods, String[] regexps)
111  {
112    String mainMethod = calculateMethodCanonicalName(m);
113    List<ITestNGMethod> vResult = Lists.newArrayList();
114    String currentRegexp = null;
115    for (String fullyQualifiedRegexp : regexps) {
116      boolean foundAtLeastAMethod = false;
117
118      if(null != fullyQualifiedRegexp) {
119        String regexp = escapeRegexp(fullyQualifiedRegexp);
120        currentRegexp = regexp;
121        boolean usePackage = regexp.indexOf('.') != -1;
122
123        for (ITestNGMethod method : methods) {
124          Method thisMethod = method.getMethod();
125          String thisMethodName = thisMethod.getName();
126          String methodName = usePackage ?
127              calculateMethodCanonicalName(thisMethod)
128              : thisMethodName;
129//            ppp("COMPARING\n" + regexp + "\n" + methodName);
130          if (Pattern.matches(regexp, methodName)) {
131            vResult.add(method);
132            foundAtLeastAMethod = true;
133          }
134        }
135      }
136
137      if (!foundAtLeastAMethod) {
138        if (m.ignoreMissingDependencies()) {
139          continue;
140        }
141        if (m.isAlwaysRun()) {
142          continue;
143        }
144        Method maybeReferringTo = findMethodByName(m, currentRegexp);
145        if (maybeReferringTo != null) {
146          throw new TestNGException(mainMethod + "() is not allowed to depend on " + maybeReferringTo);
147        }
148        throw new TestNGException(mainMethod
149            + "() is depending on nonexistent method " + currentRegexp);
150      }
151    }
152
153    ITestNGMethod[] result = vResult.toArray(new ITestNGMethod[vResult.size()]);
154
155    return result;
156  }
157
158  private static Method findMethodByName(ITestNGMethod mainMethod, String regExp) {
159    if (regExp == null) {
160      return null;
161    }
162    int lastDot = regExp.lastIndexOf('.');
163    String className, methodName;
164    if (lastDot == -1) {
165      className = mainMethod.getMethod().getDeclaringClass().getCanonicalName();
166      methodName = regExp;
167    } else {
168      methodName = regExp.substring(lastDot+1);
169      className = regExp.substring(0, lastDot);
170    }
171
172    try {
173      Class<?> c = Class.forName(className);
174      for (Method m : c.getDeclaredMethods()) {
175        if (methodName.equals(m.getName())) {
176          return m;
177        }
178      }
179    } catch (Exception e) {} // ignore
180    return null;
181  }
182
183  /**
184   * Escapes $ in regexps as it is not meant for end-line matching, but inner class matches.
185   * Impl.is weird as the String methods are not available in 1.4
186   */
187  private static String escapeRegexp(String regex) {
188    if(regex.indexOf('$') == -1) return regex;
189    String[] fragments= regex.split("\\$");
190    StringBuffer result= new StringBuffer();
191    for(int i= 0; i < fragments.length - 1; i++) {
192      result.append(fragments[i]).append("\\$");
193    }
194    result.append(fragments[fragments.length - 1]);
195    if(regex.endsWith("$")) result.append("\\$");
196
197    return result.toString();
198  }
199
200  /**
201   * Read the expected exceptions, if any (need to handle both the old and new
202   * syntax)
203   */
204  public static ExpectedExceptionsHolder findExpectedExceptions(IAnnotationFinder finder,
205      Method method) {
206    ExpectedExceptionsHolder result = null;
207    IExpectedExceptionsAnnotation expectedExceptions=
208      (IExpectedExceptionsAnnotation) finder.findAnnotation(method,
209        IExpectedExceptionsAnnotation.class);
210    // Old syntax
211    if (expectedExceptions != null) {
212      result = new ExpectedExceptionsHolder(expectedExceptions.getValue(), ".*");
213    }
214    else {
215      // New syntax
216      ITestAnnotation testAnnotation =
217        (ITestAnnotation) finder.findAnnotation(method, ITestAnnotation.class);
218      if (testAnnotation != null) {
219        Class<?>[] ee = testAnnotation.getExpectedExceptions();
220        if (testAnnotation != null && ee.length > 0) {
221          result = new ExpectedExceptionsHolder(ee,
222              testAnnotation.getExpectedExceptionsMessageRegExp());
223        }
224      }
225    }
226
227    return result;
228  }
229
230  //
231  // End of public methods
232  // ///
233
234  public static boolean isEnabled(Class<?> objectClass, IAnnotationFinder finder) {
235    ITestAnnotation testClassAnnotation= AnnotationHelper.findTest(finder, objectClass);
236
237    return isEnabled(testClassAnnotation);
238  }
239
240  public static boolean isEnabled(Method m, IAnnotationFinder finder) {
241    ITestAnnotation annotation = AnnotationHelper.findTest(finder, m);
242
243    // If no method annotation, look for one on the class
244    if (null == annotation) {
245      annotation = AnnotationHelper.findTest(finder, m.getDeclaringClass());
246    }
247
248    return isEnabled(annotation);
249  }
250
251  public static boolean isEnabled(ITestOrConfiguration test) {
252    return null == test || (null != test && test.getEnabled());
253  }
254
255  public static ITestNGMethod[] findMethodsThatBelongToGroup(
256      ITestNGMethod method,
257      List<ITestNGMethod> methods, String groupRegexp)
258  {
259    ITestNGMethod[] allMethods = methods.toArray(new ITestNGMethod[methods
260        .size()]);
261    return findMethodsThatBelongToGroup(method, allMethods, groupRegexp);
262  }
263
264  /**
265   * @return The transitive closure of all the groups/methods included.
266   */
267  public static void findGroupTransitiveClosure(XmlMethodSelector xms,
268      List<ITestNGMethod> includedMethods,
269      List<ITestNGMethod> allMethods,
270      String[] includedGroups,
271      Set<String> outGroups, Set<ITestNGMethod> outMethods)
272  {
273    Map<ITestNGMethod, ITestNGMethod> runningMethods = Maps.newHashMap();
274    for (ITestNGMethod m : includedMethods) {
275      runningMethods.put(m, m);
276    }
277
278    Map<String, String> runningGroups = Maps.newHashMap();
279    for (String thisGroup : includedGroups) {
280      runningGroups.put(thisGroup, thisGroup);
281    }
282
283    boolean keepGoing = true;
284
285    Map<ITestNGMethod, ITestNGMethod> newMethods = Maps.newHashMap();
286    while (keepGoing) {
287      for (ITestNGMethod m : includedMethods) {
288
289        //
290        // Depends on groups?
291        // Adds all included methods to runningMethods
292        //
293        String[] ig = m.getGroupsDependedUpon();
294        for (String g : ig) {
295          if (! runningGroups.containsKey(g)) {
296            // Found a new included group, add all the methods it contains to
297            // our outMethod closure
298            runningGroups.put(g, g);
299            ITestNGMethod[] im =
300              findMethodsThatBelongToGroup(m, allMethods, g);
301            for (ITestNGMethod thisMethod : im) {
302              if (! runningMethods.containsKey(thisMethod)) {
303                runningMethods.put(thisMethod, thisMethod);
304                newMethods.put(thisMethod, thisMethod);
305              }
306            }
307          }
308        } // groups
309
310        //
311        // Depends on methods?
312        // Adds all depended methods to runningMethods
313        //
314        String[] mdu = m.getMethodsDependedUpon();
315        for (String tm : mdu) {
316          ITestNGMethod thisMethod = findMethodNamed(tm, allMethods);
317          if (thisMethod != null && ! runningMethods.containsKey(thisMethod)) {
318            runningMethods.put(thisMethod, thisMethod);
319            newMethods.put(thisMethod, thisMethod);
320          }
321        }
322
323      } // methods
324
325      //
326      // Only keep going if new methods have been added
327      //
328      keepGoing = newMethods.size() > 0;
329      includedMethods = Lists.newArrayList();
330      includedMethods.addAll(newMethods.keySet());
331      newMethods = Maps.newHashMap();
332    } // while keepGoing
333
334    outMethods.addAll(runningMethods.keySet());
335    outGroups.addAll(runningGroups.keySet());
336  }
337
338  /**
339   * Extracts the map of groups and their corresponding methods from the <code>classes</code>.
340   */
341  public static Map<String, List<ITestNGMethod>> findGroupsMethods(Collection<ITestClass> classes, boolean before) {
342    Map<String, List<ITestNGMethod>> result = Maps.newHashMap();
343    for (ITestClass cls : classes) {
344      ITestNGMethod[] methods = before ? cls.getBeforeGroupsMethods() : cls.getAfterGroupsMethods();
345      for (ITestNGMethod method : methods) {
346        for (String group : before ? method.getBeforeGroups() : method.getAfterGroups()) {
347          List<ITestNGMethod> methodList = result.get(group);
348          if (methodList == null) {
349            methodList = Lists.newArrayList();
350            result.put(group, methodList);
351          }
352          // NOTE(cbeust, 2007/01/23)
353          // BeforeGroups/AfterGroups methods should only be invoked once.
354          // I should probably use a map instead of a list for a contains(), but
355          // this list should usually be fairly short
356          if (! methodList.contains(method)) {
357            methodList.add(method);
358          }
359        }
360      }
361    }
362
363    return result;
364  }
365
366  /**
367   * Extracts the unique list of <code>ITestNGMethod</code>s.
368   */
369  public static List<ITestNGMethod> uniqueMethodList(Collection<List<ITestNGMethod>> methods) {
370    Map<ITestNGMethod, ITestNGMethod> uniq = Maps.newHashMap();
371
372    for (List<ITestNGMethod> l : methods) {
373      for (ITestNGMethod m : l) {
374        uniq.put(m, m);
375      }
376    }
377
378    List<ITestNGMethod> result = Lists.newArrayList();
379    result.addAll(uniq.values());
380
381    return result;
382  }
383
384  private static ITestNGMethod findMethodNamed(String tm, List<ITestNGMethod> allMethods) {
385    for (ITestNGMethod m : allMethods) {
386      // TODO(cbeust):  account for package
387      String methodName =
388        m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName();
389      if (methodName.equals(tm)) return m;
390    }
391
392    return null;
393  }
394
395  private static boolean includeMethod(ITestOrConfiguration annotation,
396      RunInfo runInfo, ITestNGMethod tm, boolean forTests, boolean unique, List<ITestNGMethod> outIncludedMethods)
397  {
398    boolean result = false;
399
400    if (isEnabled(annotation)) {
401      if (runInfo.includeMethod(tm, forTests)) {
402        if (unique) {
403          if (!isMethodAlreadyPresent(outIncludedMethods, tm)) {
404            result = true;
405          }
406        }
407        else {
408          result = true;
409        }
410      }
411    }
412
413    return result;
414  }
415
416  /**
417   * Collect all the methods that belong to the included groups and exclude all
418   * the methods that belong to an excluded group.
419   */
420  private static void collectMethodsByGroup(ITestNGMethod[] methods,
421      boolean forTests,
422      List<ITestNGMethod> outIncludedMethods,
423      List<ITestNGMethod> outExcludedMethods,
424      RunInfo runInfo,
425      IAnnotationFinder finder, boolean unique)
426  {
427    for (ITestNGMethod tm : methods) {
428      boolean in = false;
429      Method m = tm.getMethod();
430      //
431      // @Test method
432      //
433      if (forTests) {
434        in = includeMethod(AnnotationHelper.findTest(finder, m),
435            runInfo, tm, forTests, unique, outIncludedMethods);
436      }
437
438      //
439      // @Configuration method
440      //
441      else {
442        IConfigurationAnnotation annotation = AnnotationHelper.findConfiguration(finder, m);
443        if (annotation.getAlwaysRun()) {
444          in = true;
445        }
446        else {
447          in = includeMethod(AnnotationHelper.findTest(finder, m),
448              runInfo, tm, forTests, unique, outIncludedMethods);
449        }
450      }
451      if (in) {
452        outIncludedMethods.add(tm);
453      }
454      else {
455        outExcludedMethods.add(tm);
456      }
457    }
458  }
459
460  /**
461   * @param result
462   * @param tm
463   * @return true if a method by a similar name (and same hierarchy) already
464   *         exists
465   */
466  private static boolean isMethodAlreadyPresent(List<ITestNGMethod> result,
467      ITestNGMethod tm) {
468    for (ITestNGMethod m : result) {
469      Method jm1 = m.getMethod();
470      Method jm2 = tm.getMethod();
471      if (jm1.getName().equals(jm2.getName())) {
472        // Same names, see if they are in the same hierarchy
473        Class<?> c1 = jm1.getDeclaringClass();
474        Class<?> c2 = jm2.getDeclaringClass();
475        if (c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1)) {
476          return true;
477        }
478      }
479    }
480
481    return false;
482  }
483
484  private static Graph<ITestNGMethod> topologicalSort(ITestNGMethod[] methods,
485      List<ITestNGMethod> sequentialList, List<ITestNGMethod> parallelList) {
486    Graph<ITestNGMethod> result = new Graph<ITestNGMethod>();
487
488    if (methods.length == 0) return result;
489
490    //
491    // Create the graph
492    //
493    for (ITestNGMethod m : methods) {
494      result.addNode(m);
495
496      List<ITestNGMethod> predecessors = Lists.newArrayList();
497
498      String[] methodsDependedUpon = m.getMethodsDependedUpon();
499      String[] groupsDependedUpon = m.getGroupsDependedUpon();
500      if (methodsDependedUpon.length > 0) {
501        ITestNGMethod[] methodsNamed =
502          MethodHelper.findMethodsNamed(m, methods, methodsDependedUpon);
503        for (ITestNGMethod pred : methodsNamed) {
504          predecessors.add(pred);
505        }
506      }
507      if (groupsDependedUpon.length > 0) {
508        for (String group : groupsDependedUpon) {
509          ITestNGMethod[] methodsThatBelongToGroup =
510            MethodHelper.findMethodsThatBelongToGroup(m, methods, group);
511          for (ITestNGMethod pred : methodsThatBelongToGroup) {
512            predecessors.add(pred);
513          }
514        }
515      }
516
517      for (ITestNGMethod predecessor : predecessors) {
518        result.addPredecessor(m, predecessor);
519      }
520    }
521
522    result.topologicalSort();
523    sequentialList.addAll(result.getStrictlySortedNodes());
524    parallelList.addAll(result.getIndependentNodes());
525
526    return result;
527  }
528
529  public static String calculateMethodCanonicalName(ITestNGMethod m) {
530    return calculateMethodCanonicalName(m.getMethod());
531  }
532
533  private static String calculateMethodCanonicalName(Method m) {
534    String packageName = m.getDeclaringClass().getName() + "." + m.getName();
535
536    // Try to find the method on this class or parents
537    Class<?> cls = m.getDeclaringClass();
538    while (cls != Object.class) {
539      try {
540        if (cls.getDeclaredMethod(m.getName(), m.getParameterTypes()) != null) {
541          packageName = cls.getName();
542          break;
543        }
544      }
545      catch (Exception e) {
546        // ignore
547      }
548      cls = cls.getSuperclass();
549    }
550
551    String result = packageName + "." + m.getName();
552    return result;
553  }
554
555  private static List<ITestNGMethod> sortMethods(boolean forTests,
556      List<ITestNGMethod> allMethods, IAnnotationFinder finder) {
557    List<ITestNGMethod> sl = Lists.newArrayList();
558    List<ITestNGMethod> pl = Lists.newArrayList();
559    ITestNGMethod[] allMethodsArray = allMethods.toArray(new ITestNGMethod[allMethods.size()]);
560
561    // Fix the method inheritance if these are @Configuration methods to make
562    // sure base classes are invoked before child classes if 'before' and the
563    // other way around if they are 'after'
564    if (!forTests && allMethodsArray.length > 0) {
565      ITestNGMethod m = allMethodsArray[0];
566      boolean before = m.isBeforeClassConfiguration()
567          || m.isBeforeMethodConfiguration() || m.isBeforeSuiteConfiguration()
568          || m.isBeforeTestConfiguration();
569      MethodInheritance.fixMethodInheritance(allMethodsArray, before);
570    }
571
572    topologicalSort(allMethodsArray, sl, pl);
573
574    List<ITestNGMethod> result = Lists.newArrayList();
575    result.addAll(sl);
576    result.addAll(pl);
577    return result;
578  }
579
580  /**
581   * @param method
582   * @param allTestMethods
583   * @return A sorted array containing all the methods 'method' depends on
584   */
585  public static List<ITestNGMethod> getMethodsDependedUpon(ITestNGMethod method, ITestNGMethod[] methods) {
586    List<ITestNGMethod> parallelList = Lists.newArrayList();
587    List<ITestNGMethod> sequentialList = Lists.newArrayList();
588    Graph<ITestNGMethod> g = topologicalSort(methods, sequentialList, parallelList);
589
590    List<ITestNGMethod> result = g.findPredecessors(method);
591    return result;
592  }
593
594  public static Object invokeMethod(Method thisMethod, Object instance, Object[] parameters)
595  throws InvocationTargetException, IllegalAccessException
596  {
597    Object result = null;
598    // TESTNG-326, allow IObjectFactory to load from non-standard classloader
599    // If the instance has a different classloader, its class won't match the method's class
600    if (!thisMethod.getDeclaringClass().isAssignableFrom(instance.getClass())) {
601      // for some reason, we can't call this method on this class
602      // is it static?
603      boolean isStatic = Modifier.isStatic(thisMethod.getModifiers());
604      if (!isStatic) {
605        // not static, so grab a method with the same name and signature in this case
606        Class<?> clazz = instance.getClass();
607        try {
608          thisMethod = clazz.getMethod(thisMethod.getName(), thisMethod.getParameterTypes());
609        } catch (Exception e) {
610          // ignore, the method may be private
611          boolean found = false;
612          for (; clazz != null; clazz = clazz.getSuperclass()) {
613            try {
614              thisMethod = clazz.getDeclaredMethod(thisMethod.getName(), thisMethod.getParameterTypes());
615              found = true;
616              break;
617            } catch (Exception e2) {}
618          }
619          if (!found) {
620            //should we assert here?  Or just allow it to fail on invocation?
621            if (thisMethod.getDeclaringClass().getName().equals(instance.getClass().getName())) {
622              throw new RuntimeException("Can't invoke method " + thisMethod + ", probably due to classloader mismatch");
623            }
624            throw new RuntimeException("Can't invoke method " + thisMethod + " on this instance of " + instance.getClass()+ " due to class mismatch");
625          }
626        }
627      }
628
629
630
631    }
632
633    boolean isPublic = Modifier.isPublic(thisMethod.getModifiers());
634
635    try {
636      if (!isPublic) {
637        thisMethod.setAccessible(true);
638      }
639      result = thisMethod.invoke(instance, parameters);
640    }
641    finally {
642      if (!isPublic) {
643        thisMethod.setAccessible(false);
644      }
645    }
646
647    return result;
648  }
649
650  public static Iterator<Object[]> createArrayIterator(final Object[][] objects) {
651    ArrayIterator result = new ArrayIterator(objects);
652    return result;
653  }
654
655  public static Iterator<Object[]> invokeDataProvider(Object instance,
656      Method dataProvider, ITestNGMethod method, ITestContext testContext,
657      Object fedInstance, IAnnotationFinder annotationFinder)
658  {
659    Iterator<Object[]> result = null;
660    Method testMethod = method.getMethod();
661
662    // If it returns an Object[][], convert it to an Iterable<Object[]>
663    try {
664      List<Object> lParameters = Lists.newArrayList();
665
666      // Go through all the parameters declared on this Data Provider and
667      // make sure we have at most one Method and one ITestContext.
668      // Anything else is an error
669      Class<?>[] parameterTypes = dataProvider.getParameterTypes();
670      if (parameterTypes.length > 2) {
671        throw new TestNGException("DataProvider " + dataProvider + " cannot have more than two parameters");
672      }
673
674      int i = 0;
675      for (Class<?> cls : parameterTypes) {
676        boolean isTestInstance = annotationFinder.hasTestInstance(dataProvider, i++);
677        if (cls.equals(Method.class)) {
678          lParameters.add(testMethod);
679        }
680        else if (cls.equals(ITestContext.class)) {
681          lParameters.add(testContext);
682        }
683        else if (isTestInstance) {
684          lParameters.add(fedInstance);
685        }
686      }
687      Object[] parameters = lParameters.toArray(new Object[lParameters.size()]);
688
689      Class< ? > returnType = dataProvider.getReturnType();
690      if (Object[][].class.isAssignableFrom(returnType)) {
691        Object[][] oResult = (Object[][]) MethodHelper.invokeMethod(
692            dataProvider, instance, parameters);
693        method.setParameterInvocationCount(oResult.length);
694        result = MethodHelper.createArrayIterator(oResult);
695      }
696      else if (Iterator.class.isAssignableFrom(returnType)) {
697        // Already an Iterable<Object[]>, assign it directly
698        result = (Iterator<Object[]>) MethodHelper.invokeMethod(dataProvider,
699            instance, parameters);
700      }
701      else {
702        throw new TestNGException("Data Provider " + dataProvider
703            + " must return" + " either Object[][] or Iterator<Object>[], not "
704            + returnType);
705      }
706    }
707    catch (InvocationTargetException e) {
708      throw new TestNGException(e);
709    }
710    catch (IllegalAccessException e) {
711      throw new TestNGException(e);
712    }
713
714    return result;
715  }
716
717  public static String calculateMethodCanonicalName(Class<?> methodClass, String methodName) {
718    Set<Method> methods = ClassHelper.getAvailableMethods(methodClass); // TESTNG-139
719    Method result = null;
720    for (Method m : methods) {
721      if (methodName.equals(m.getName())) {
722        result = m;
723        break;
724      }
725    }
726
727    return result != null ? calculateMethodCanonicalName(result) : null;
728  }
729
730  public static void invokeConfigurable(final Object instance,
731      final Object[] parameters, Object configurableInstance,
732      final Method thisMethod, ITestResult testResult)
733      throws NoSuchMethodException, IllegalAccessException,
734      InvocationTargetException, Throwable
735  {
736    Method runMethod = configurableInstance.getClass().getMethod("run",
737        new Class[] { IConfigureCallBack.class, ITestResult.class });
738    final Throwable[] error = new Throwable[1];
739
740    IConfigureCallBack callback = new IConfigureCallBack() {
741      @Override
742      public void runConfigurationMethod(ITestResult tr) {
743        try {
744          invokeMethod(thisMethod, instance, parameters);
745        } catch (Throwable t) {
746          error[0] = t;
747          tr.setThrowable(t); // make Throwable available to IConfigurable
748        }
749      }
750
751      @Override
752      public Object[] getParameters() {
753        return parameters;
754      }
755    };
756    runMethod.invoke(configurableInstance, new Object[] { callback, testResult });
757    if (error[0] != null) {
758      throw error[0];
759    }
760  }
761
762  /**
763   * Invokes the <code>run</code> method of the <code>IHookable</code>.
764   *
765   * @param testInstance the instance to invoke the method in
766   * @param parameters the parameters to be passed to <code>IHookCallBack</code>
767   * @param thisMethod the method to be invoked through the <code>IHookCallBack</code>
768   * @param testResult the current <code>ITestResult</code> passed to <code>IHookable.run</code>
769   * @throws NoSuchMethodException
770   * @throws IllegalAccessException
771   * @throws InvocationTargetException
772   * @throws Throwable thrown if the reflective call to <tt>thisMethod</code> results in an exception
773   */
774  public static void invokeHookable(final Object testInstance,
775                                    final Object[] parameters,
776                                    Object hookableInstance,
777                                    final Method thisMethod,
778                                    TestResult testResult)
779  throws Throwable
780  {
781    Method runMethod = hookableInstance.getClass().getMethod("run",
782        new Class[] { IHookCallBack.class, ITestResult.class });
783    final Throwable[] error = new Throwable[1];
784
785    IHookCallBack callback = new IHookCallBack() {
786      @Override
787      public void runTestMethod(ITestResult tr) {
788        try {
789          invokeMethod(thisMethod, testInstance, parameters);
790         }
791         catch(Throwable t) {
792           error[0] = t;
793           tr.setThrowable( t ); // make Throwable available to IHookable
794         }
795       }
796
797      @Override
798      public Object[] getParameters() {
799        return parameters;
800      }
801    };
802    runMethod.invoke(hookableInstance, new Object[]{callback, testResult});
803    if (error[0] != null) {
804      throw error[0];
805    }
806  }
807
808  public static long calculateTimeOut(ITestNGMethod tm) {
809    long result = tm.getTimeOut() > 0 ? tm.getTimeOut() : tm.getInvocationTimeOut();
810    return result;
811  }
812
813  /**
814   * Invokes a method on a separate thread in order to allow us to timeout the invocation.
815   * It uses as implementation an <code>Executor</code> and a <code>CountDownLatch</code>.
816   * @param tm the
817   * @param instance
818   * @param parameterValues
819   * @param testResult
820   * @throws InterruptedException
821   * @throws ThreadExecutionException
822   */
823  public static void invokeWithTimeout(ITestNGMethod tm, Object instance, Object[] parameterValues,
824      ITestResult testResult)
825  throws InterruptedException, ThreadExecutionException {
826    IExecutor exec= ThreadUtil.createExecutor(1, tm.getMethod().getName());
827
828    InvokeMethodRunnable imr = new InvokeMethodRunnable(tm, instance, parameterValues);
829    IFutureResult future= exec.submitRunnable(imr);
830    exec.shutdown();
831    long realTimeOut = calculateTimeOut(tm);
832    boolean finished = exec.awaitTermination(realTimeOut);
833
834    if (! finished) {
835      exec.stopNow();
836      ThreadTimeoutException exception = new ThreadTimeoutException("Method "
837        + tm.getClass().getName() + "." + tm.getMethodName() + "()"
838        + " didn't finish within the time-out "
839        + realTimeOut);
840      exception.setStackTrace(exec.getStackTraces()[0]);
841      testResult.setThrowable(exception);
842      testResult.setStatus(ITestResult.FAILURE);
843    }
844    else {
845      Utils.log("Invoker " + Thread.currentThread().hashCode(), 3,
846          "Method " + tm.getMethod() + " completed within the time-out " + tm.getTimeOut());
847
848      // We don't need the result from the future but invoking get() on it
849      // will trigger the exception that was thrown, if any
850      future.get();
851//      done.await();
852
853      testResult.setStatus(ITestResult.SUCCESS); // if no exception till here than SUCCESS
854    }
855  }
856}
857
858// ///
859
860class ArrayIterator implements Iterator {
861  private Object[][] m_objects;
862
863  private int m_count;
864
865  public ArrayIterator(Object[][] objects) {
866    m_objects = objects;
867    m_count = 0;
868  }
869
870  @Override
871  public boolean hasNext() {
872    return m_count < m_objects.length;
873  }
874
875  @Override
876  public Object next() {
877    return m_objects[m_count++];
878  }
879
880  @Override
881  public void remove() {
882    // TODO Auto-generated method stub
883
884  }
885
886}
887