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@beust.com>
35  * @author nullin <nalin.makar * gmail.com>
36  *
37  */
38 public class MethodInvocationHelper {
39 
invokeMethod(Method thisMethod, Object instance, Object[] parameters)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 
invokeDataProvider(Object instance, Method dataProvider, ITestNGMethod method, ITestContext testContext, Object fedInstance, IAnnotationFinder annotationFinder)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    */
invokeHookable(final Object testInstance, final Object[] parameters, final IHookable hookable, final Method thisMethod, final ITestResult testResult)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    */
invokeWithTimeout(ITestNGMethod tm, Object instance, Object[] parameterValues, ITestResult testResult)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 
invokeWithTimeout(ITestNGMethod tm, Object instance, Object[] parameterValues, ITestResult testResult, IHookable hookable)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 
invokeWithTimeoutWithNoExecutor(ITestNGMethod tm, Object instance, Object[] parameterValues, ITestResult testResult, IHookable hookable)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 
invokeWithTimeoutWithNewExecutor(ITestNGMethod tm, Object instance, Object[] parameterValues, ITestResult testResult, IHookable hookable)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 
invokeConfigurable(final Object instance, final Object[] parameters, final IConfigurable configurableInstance, final Method thisMethod, final ITestResult testResult)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