Home | History | Annotate | Download | only in internal
      1 package org.testng.internal;
      2 
      3 import org.testng.IConfigurable;
      4 import org.testng.IConfigureCallBack;
      5 import org.testng.IHookCallBack;
      6 import org.testng.IHookable;
      7 import org.testng.ITestContext;
      8 import org.testng.ITestNGMethod;
      9 import org.testng.ITestResult;
     10 import org.testng.TestNGException;
     11 import org.testng.annotations.DataProvider;
     12 import org.testng.collections.Lists;
     13 import org.testng.internal.annotations.IAnnotationFinder;
     14 import org.testng.internal.collections.Pair;
     15 import org.testng.internal.thread.IExecutor;
     16 import org.testng.internal.thread.IFutureResult;
     17 import org.testng.internal.thread.ThreadExecutionException;
     18 import org.testng.internal.thread.ThreadTimeoutException;
     19 import org.testng.internal.thread.ThreadUtil;
     20 import org.testng.xml.XmlSuite;
     21 
     22 import java.lang.reflect.Constructor;
     23 import java.lang.reflect.InvocationTargetException;
     24 import java.lang.reflect.Method;
     25 import java.lang.reflect.Modifier;
     26 import java.util.ArrayList;
     27 import java.util.Collection;
     28 import java.util.Iterator;
     29 import java.util.List;
     30 
     31 /**
     32  * Collections of helper methods to help deal with invocation of TestNG methods
     33  *
     34  * @author Cedric Beust <cedric (at) beust.com>
     35  * @author nullin <nalin.makar * gmail.com>
     36  *
     37  */
     38 public class MethodInvocationHelper {
     39 
     40   protected static Object invokeMethod(Method thisMethod, Object instance, Object[] parameters)
     41       throws InvocationTargetException, IllegalAccessException {
     42     Utils.checkInstanceOrStatic(instance, thisMethod);
     43 
     44     // TESTNG-326, allow IObjectFactory to load from non-standard classloader
     45     // If the instance has a different classloader, its class won't match the
     46     // method's class
     47     if (instance == null || !thisMethod.getDeclaringClass().isAssignableFrom(instance.getClass())) {
     48       // for some reason, we can't call this method on this class
     49       // is it static?
     50       boolean isStatic = Modifier.isStatic(thisMethod.getModifiers());
     51       if (!isStatic) {
     52         // not static, so grab a method with the same name and signature in this case
     53         Class<?> clazz = instance.getClass();
     54         try {
     55           thisMethod = clazz.getMethod(thisMethod.getName(), thisMethod.getParameterTypes());
     56         } catch (Exception e) {
     57           // ignore, the method may be private
     58           boolean found = false;
     59           for (; clazz != null; clazz = clazz.getSuperclass()) {
     60             try {
     61               thisMethod = clazz.getDeclaredMethod(thisMethod.getName(),
     62                   thisMethod.getParameterTypes());
     63               found = true;
     64               break;
     65             } catch (Exception e2) {
     66             }
     67           }
     68           if (!found) {
     69             // should we assert here? Or just allow it to fail on invocation?
     70             if (thisMethod.getDeclaringClass().getName().equals(instance.getClass().getName())) {
     71               throw new RuntimeException("Can't invoke method " + thisMethod
     72                   + ", probably due to classloader mismatch");
     73             }
     74             throw new RuntimeException("Can't invoke method " + thisMethod
     75                 + " on this instance of " + instance.getClass() + " due to class mismatch");
     76           }
     77         }
     78       }
     79     }
     80 
     81     synchronized(thisMethod) {
     82       if (! Modifier.isPublic(thisMethod.getModifiers())) {
     83         thisMethod.setAccessible(true);
     84       }
     85     }
     86     return thisMethod.invoke(instance, parameters);
     87   }
     88 
     89   protected static Iterator<Object[]> invokeDataProvider(Object instance, Method dataProvider,
     90       ITestNGMethod method, ITestContext testContext, Object fedInstance,
     91       IAnnotationFinder annotationFinder) {
     92     Iterator<Object[]> result;
     93     final ConstructorOrMethod com = method.getConstructorOrMethod();
     94 
     95     // If it returns an Object[][], convert it to an Iterable<Object[]>
     96     try {
     97       List<Object> lParameters = Lists.newArrayList();
     98 
     99       // Go through all the parameters declared on this Data Provider and
    100       // make sure we have at most one Method and one ITestContext.
    101       // Anything else is an error
    102       Class<?>[] parameterTypes = dataProvider.getParameterTypes();
    103 
    104       final Collection<Pair<Integer, Class<?>>> unresolved = new ArrayList<>(parameterTypes.length);
    105       int i = 0;
    106       for (Class<?> cls : parameterTypes) {
    107         boolean isTestInstance = annotationFinder.hasTestInstance(dataProvider, i++);
    108         if (cls.equals(Method.class)) {
    109           lParameters.add(com.getMethod());
    110         } else if (cls.equals(Constructor.class)) {
    111           lParameters.add(com.getConstructor());
    112         } else if (cls.equals(ConstructorOrMethod.class)) {
    113           lParameters.add(com);
    114         } else if (cls.equals(ITestNGMethod.class)) {
    115           lParameters.add(method);
    116         } else if (cls.equals(ITestContext.class)) {
    117           lParameters.add(testContext);
    118         } else if (isTestInstance) {
    119           lParameters.add(fedInstance);
    120         } else {
    121           unresolved.add(new Pair<Integer, Class<?>>(i, cls));
    122         }
    123       }
    124       if (!unresolved.isEmpty()) {
    125         final StringBuilder sb = new StringBuilder();
    126         sb.append("Some DataProvider ").append(dataProvider).append(" parameters unresolved: ");
    127         for (Pair<Integer, Class<?>> pair : unresolved) {
    128           sb.append(" at ").append(pair.first()).append(" type ").append(pair.second()).append("\n");
    129         }
    130         throw new TestNGException(sb.toString());
    131       }
    132 
    133       Object[] parameters = lParameters.toArray(new Object[lParameters.size()]);
    134 
    135       Class<?> returnType = dataProvider.getReturnType();
    136       if (Object[][].class.isAssignableFrom(returnType)) {
    137         Object[][] originalResult = (Object[][]) invokeMethod(dataProvider, instance, parameters);
    138 
    139         // If the data provider is restricting the indices to return, filter them out
    140         int[] indices = dataProvider.getAnnotation(DataProvider.class).indices();
    141         Object[][] oResult;
    142         if (indices.length > 0) {
    143           oResult = new Object[indices.length][];
    144           for (int j = 0; j < indices.length; j++) {
    145             oResult[j] = originalResult[indices[j]];
    146           }
    147         } else {
    148           oResult = originalResult;
    149         }
    150 
    151         method.setParameterInvocationCount(oResult.length);
    152         result = MethodHelper.createArrayIterator(oResult);
    153       } else if (Iterator.class.isAssignableFrom(returnType)) {
    154         // Already an Iterator<Object[]>, assign it directly
    155         result = (Iterator<Object[]>) invokeMethod(dataProvider, instance, parameters);
    156       } else {
    157         throw new TestNGException("Data Provider " + dataProvider + " must return"
    158             + " either Object[][] or Iterator<Object>[], not " + returnType);
    159       }
    160     } catch (InvocationTargetException | IllegalAccessException e) {
    161       // Don't throw TestNGException here or this test won't be reported as a
    162       // skip or failure
    163       throw new RuntimeException(e.getCause());
    164     }
    165 
    166     return result;
    167   }
    168 
    169   /**
    170    * Invokes the <code>run</code> method of the <code>IHookable</code>.
    171    *
    172    * @param testInstance
    173    *          the instance to invoke the method in
    174    * @param parameters
    175    *          the parameters to be passed to <code>IHookCallBack</code>
    176    * @param thisMethod
    177    *          the method to be invoked through the <code>IHookCallBack</code>
    178    * @param testResult
    179    *          the current <code>ITestResult</code> passed to
    180    *          <code>IHookable.run</code>
    181    * @throws NoSuchMethodException
    182    * @throws IllegalAccessException
    183    * @throws InvocationTargetException
    184    * @throws Throwable
    185    *           thrown if the reflective call to
    186    *           <tt>thisMethod</code> results in an exception
    187    */
    188   protected static void invokeHookable(final Object testInstance, final Object[] parameters,
    189                                        final IHookable hookable, final Method thisMethod,
    190                                        final ITestResult testResult) throws Throwable {
    191     final Throwable[] error = new Throwable[1];
    192 
    193     IHookCallBack callback = new IHookCallBack() {
    194       @Override
    195       public void runTestMethod(ITestResult tr) {
    196         try {
    197           invokeMethod(thisMethod, testInstance, parameters);
    198         } catch (Throwable t) {
    199           error[0] = t;
    200           tr.setThrowable(t); // make Throwable available to IHookable
    201         }
    202       }
    203 
    204       @Override
    205       public Object[] getParameters() {
    206         return parameters;
    207       }
    208     };
    209     hookable.run(callback, testResult);
    210     if (error[0] != null) {
    211       throw error[0];
    212     }
    213   }
    214 
    215   /**
    216    * Invokes a method on a separate thread in order to allow us to timeout the
    217    * invocation. It uses as implementation an <code>Executor</code> and a
    218    * <code>CountDownLatch</code>.
    219    */
    220   protected static void invokeWithTimeout(ITestNGMethod tm, Object instance,
    221       Object[] parameterValues, ITestResult testResult)
    222       throws InterruptedException, ThreadExecutionException {
    223     invokeWithTimeout(tm, instance, parameterValues, testResult, null);
    224   }
    225 
    226   protected static void invokeWithTimeout(ITestNGMethod tm, Object instance,
    227       Object[] parameterValues, ITestResult testResult, IHookable hookable)
    228       throws InterruptedException, ThreadExecutionException {
    229     if (ThreadUtil.isTestNGThread() && testResult.getTestContext().getCurrentXmlTest().getParallel() != XmlSuite.ParallelMode.TESTS) {
    230       // We are already running in our own executor, don't create another one (or we will
    231       // lose the time out of the enclosing executor).
    232       invokeWithTimeoutWithNoExecutor(tm, instance, parameterValues, testResult, hookable);
    233     } else {
    234       invokeWithTimeoutWithNewExecutor(tm, instance, parameterValues, testResult, hookable);
    235     }
    236   }
    237 
    238   private static void invokeWithTimeoutWithNoExecutor(ITestNGMethod tm, Object instance,
    239       Object[] parameterValues, ITestResult testResult, IHookable hookable) {
    240 
    241     InvokeMethodRunnable imr = new InvokeMethodRunnable(tm, instance, parameterValues, hookable, testResult);
    242     try {
    243       imr.run();
    244       testResult.setStatus(ITestResult.SUCCESS);
    245     } catch (Exception ex) {
    246       testResult.setThrowable(ex.getCause());
    247       testResult.setStatus(ITestResult.FAILURE);
    248     }
    249   }
    250 
    251   private static void invokeWithTimeoutWithNewExecutor(ITestNGMethod tm, Object instance,
    252       Object[] parameterValues, ITestResult testResult, IHookable hookable)
    253       throws InterruptedException, ThreadExecutionException {
    254     IExecutor exec = ThreadUtil.createExecutor(1, tm.getMethodName());
    255 
    256     InvokeMethodRunnable imr = new InvokeMethodRunnable(tm, instance, parameterValues, hookable, testResult);
    257     IFutureResult future = exec.submitRunnable(imr);
    258     exec.shutdown();
    259     long realTimeOut = MethodHelper.calculateTimeOut(tm);
    260     boolean finished = exec.awaitTermination(realTimeOut);
    261 
    262     if (!finished) {
    263       exec.stopNow();
    264       ThreadTimeoutException exception = new ThreadTimeoutException("Method "
    265           + tm.getClass().getName() + "." + tm.getMethodName() + "()"
    266           + " didn't finish within the time-out " + realTimeOut);
    267       exception.setStackTrace(exec.getStackTraces()[0]);
    268       testResult.setThrowable(exception);
    269       testResult.setStatus(ITestResult.FAILURE);
    270     } else {
    271       Utils.log("Invoker " + Thread.currentThread().hashCode(), 3, "Method " + tm.getMethodName()
    272           + " completed within the time-out " + tm.getTimeOut());
    273 
    274       // We don't need the result from the future but invoking get() on it
    275       // will trigger the exception that was thrown, if any
    276       future.get();
    277       // done.await();
    278 
    279       testResult.setStatus(ITestResult.SUCCESS); // if no exception till here
    280                                                  // than SUCCESS
    281     }
    282   }
    283 
    284   protected static void invokeConfigurable(final Object instance, final Object[] parameters,
    285                                            final IConfigurable configurableInstance, final Method thisMethod,
    286                                            final ITestResult testResult) throws Throwable {
    287     final Throwable[] error = new Throwable[1];
    288 
    289     IConfigureCallBack callback = new IConfigureCallBack() {
    290       @Override
    291       public void runConfigurationMethod(ITestResult tr) {
    292         try {
    293           invokeMethod(thisMethod, instance, parameters);
    294         } catch (Throwable t) {
    295           error[0] = t;
    296           tr.setThrowable(t); // make Throwable available to IConfigurable
    297         }
    298       }
    299 
    300       @Override
    301       public Object[] getParameters() {
    302         return parameters;
    303       }
    304     };
    305     configurableInstance.run(callback, testResult);
    306     if (error[0] != null) {
    307       throw error[0];
    308     }
    309   }
    310 
    311 }
    312