| package org.testng.internal; |
| |
| import org.testng.IClass; |
| import org.testng.IConfigurable; |
| import org.testng.IConfigurationListener; |
| import org.testng.IHookable; |
| import org.testng.IInvokedMethod; |
| import org.testng.IInvokedMethodListener; |
| import org.testng.IRetryAnalyzer; |
| import org.testng.ITestClass; |
| import org.testng.ITestContext; |
| import org.testng.ITestListener; |
| import org.testng.ITestNGMethod; |
| import org.testng.ITestResult; |
| import org.testng.Reporter; |
| import org.testng.SkipException; |
| import org.testng.SuiteRunState; |
| import org.testng.TestException; |
| import org.testng.TestNGException; |
| import org.testng.annotations.IConfigurationAnnotation; |
| import org.testng.annotations.NoInjection; |
| import org.testng.collections.Lists; |
| import org.testng.collections.Maps; |
| import org.testng.internal.InvokeMethodRunnable.TestNGRuntimeException; |
| import org.testng.internal.ParameterHolder.ParameterOrigin; |
| import org.testng.internal.annotations.AnnotationHelper; |
| import org.testng.internal.annotations.IAnnotationFinder; |
| import org.testng.internal.annotations.Sets; |
| import org.testng.internal.invokers.InvokedMethodListenerInvoker; |
| import org.testng.internal.invokers.InvokedMethodListenerMethod; |
| import org.testng.internal.thread.ThreadExecutionException; |
| import org.testng.internal.thread.ThreadUtil; |
| import org.testng.internal.thread.graph.IWorker; |
| import org.testng.xml.XmlClass; |
| import org.testng.xml.XmlSuite; |
| import org.testng.xml.XmlTest; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| import static org.testng.internal.invokers.InvokedMethodListenerMethod.AFTER_INVOCATION; |
| import static org.testng.internal.invokers.InvokedMethodListenerMethod.BEFORE_INVOCATION; |
| |
| /** |
| * This class is responsible for invoking methods: |
| * - test methods |
| * - configuration methods |
| * - possibly in a separate thread |
| * and then for notifying the result listeners. |
| * |
| * @author <a href="mailto:cedric@beust.com">Cedric Beust</a> |
| * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a> |
| */ |
| public class Invoker implements IInvoker { |
| private final ITestContext m_testContext; |
| private final ITestResultNotifier m_notifier; |
| private final IAnnotationFinder m_annotationFinder; |
| private final SuiteRunState m_suiteState; |
| private final boolean m_skipFailedInvocationCounts; |
| private final List<IInvokedMethodListener> m_invokedMethodListeners; |
| private final boolean m_continueOnFailedConfiguration; |
| |
| /** Group failures must be synced as the Invoker is accessed concurrently */ |
| private Map<String, Boolean> m_beforegroupsFailures = Maps.newHashtable(); |
| |
| /** Class failures must be synced as the Invoker is accessed concurrently */ |
| private Map<Class<?>, Set<Object>> m_classInvocationResults = Maps.newHashtable(); |
| |
| /** Test methods whose configuration methods have failed. */ |
| private Map<ITestNGMethod, Set<Object>> m_methodInvocationResults = Maps.newHashtable(); |
| private IConfiguration m_configuration; |
| |
| private void setClassInvocationFailure(Class<?> clazz, Object instance) { |
| Set<Object> instances = m_classInvocationResults.get( clazz ); |
| if (instances == null) { |
| instances = Sets.newHashSet(); |
| m_classInvocationResults.put(clazz, instances); |
| } |
| instances.add(instance); |
| } |
| |
| private void setMethodInvocationFailure(ITestNGMethod method, Object instance) { |
| Set<Object> instances = m_methodInvocationResults.get(method); |
| if (instances == null) { |
| instances = Sets.newHashSet(); |
| m_methodInvocationResults.put(method, instances); |
| } |
| instances.add(instance); |
| } |
| |
| public Invoker(IConfiguration configuration, |
| ITestContext testContext, |
| ITestResultNotifier notifier, |
| SuiteRunState state, |
| boolean skipFailedInvocationCounts, |
| List<IInvokedMethodListener> invokedMethodListeners) { |
| m_configuration = configuration; |
| m_testContext= testContext; |
| m_suiteState= state; |
| m_notifier= notifier; |
| m_annotationFinder= configuration.getAnnotationFinder(); |
| m_skipFailedInvocationCounts = skipFailedInvocationCounts; |
| m_invokedMethodListeners = invokedMethodListeners; |
| m_continueOnFailedConfiguration = XmlSuite.CONTINUE.equals(testContext.getSuite().getXmlSuite().getConfigFailurePolicy()); |
| } |
| |
| /** |
| * Invoke configuration methods if they belong to the same TestClass passed |
| * in parameter.. <p/>TODO: Calculate ahead of time which methods should be |
| * invoked for each class. Might speed things up for users who invoke the |
| * same test class with different parameters in the same suite run. |
| * |
| * If instance is non-null, the configuration will be run on it. If it is null, |
| * the configuration methods will be run on all the instances retrieved |
| * from the ITestClass. |
| */ |
| @Override |
| public void invokeConfigurations(IClass testClass, |
| ITestNGMethod[] allMethods, |
| XmlSuite suite, |
| Map<String, String> params, |
| Object[] parameterValues, |
| Object instance) |
| { |
| invokeConfigurations(testClass, null, allMethods, suite, params, parameterValues, instance, |
| null); |
| } |
| |
| private void invokeConfigurations(IClass testClass, |
| ITestNGMethod currentTestMethod, |
| ITestNGMethod[] allMethods, |
| XmlSuite suite, |
| Map<String, String> params, |
| Object[] parameterValues, |
| Object instance, |
| ITestResult testMethodResult) |
| { |
| if(null == allMethods) { |
| log(5, "No configuration methods found"); |
| |
| return; |
| } |
| |
| ITestNGMethod[] methods= filterMethodsUnique(testClass, allMethods); |
| |
| for(ITestNGMethod tm : methods) { |
| if(null == testClass) { |
| testClass= tm.getTestClass(); |
| } |
| |
| ITestResult testResult= new TestResult(testClass, |
| instance, |
| tm, |
| null, |
| System.currentTimeMillis(), |
| System.currentTimeMillis()); |
| |
| IConfigurationAnnotation configurationAnnotation= null; |
| try { |
| Object[] instances= tm.getInstances(); |
| if (instances == null || instances.length == 0) { |
| instances = new Object[] { instance }; |
| } |
| Class<?> objectClass= instances[0].getClass(); |
| Method method= tm.getMethod(); |
| |
| // Only run the configuration if |
| // - the test is enabled and |
| // - the Configuration method belongs to the same class or a parent |
| if(MethodHelper.isEnabled(objectClass, m_annotationFinder)) { |
| configurationAnnotation = AnnotationHelper.findConfiguration(m_annotationFinder, method); |
| |
| if (MethodHelper.isEnabled(configurationAnnotation)) { |
| boolean isClassConfiguration = isClassConfiguration(configurationAnnotation); |
| boolean isSuiteConfiguration = isSuiteConfiguration(configurationAnnotation); |
| boolean alwaysRun= isAlwaysRun(configurationAnnotation); |
| |
| if (!confInvocationPassed(tm, currentTestMethod, testClass, instance) && !alwaysRun) { |
| handleConfigurationSkip(tm, testResult, configurationAnnotation, currentTestMethod, instance, suite); |
| continue; |
| } |
| |
| log(3, "Invoking " + Utils.detailedMethodName(tm, true)); |
| |
| Object[] parameters = Parameters.createConfigurationParameters(tm.getMethod(), |
| params, |
| parameterValues, |
| currentTestMethod, |
| m_annotationFinder, |
| suite, |
| m_testContext, |
| testMethodResult); |
| testResult.setParameters(parameters); |
| |
| Object[] newInstances= (null != instance) ? new Object[] { instance } : instances; |
| |
| runConfigurationListeners(testResult, true /* before */); |
| |
| invokeConfigurationMethod(newInstances, tm, |
| parameters, isClassConfiguration, isSuiteConfiguration, testResult); |
| |
| // TODO: probably we should trigger the event for each instance??? |
| testResult.setEndMillis(System.currentTimeMillis()); |
| runConfigurationListeners(testResult, false /* after */); |
| } |
| else { |
| log(3, |
| "Skipping " |
| + Utils.detailedMethodName(tm, true) |
| + " because it is not enabled"); |
| } |
| } // if is enabled |
| else { |
| log(3, |
| "Skipping " |
| + Utils.detailedMethodName(tm, true) |
| + " because " |
| + objectClass.getName() |
| + " is not enabled"); |
| } |
| } |
| catch(InvocationTargetException ex) { |
| handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite); |
| } |
| catch(TestNGException ex) { |
| // Don't wrap TestNGExceptions, it could be a missing parameter on a |
| // @Configuration method |
| handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite); |
| } |
| catch(Throwable ex) { // covers the non-wrapper exceptions |
| handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite); |
| } |
| } // for methods |
| } |
| |
| /** |
| * Marks the current <code>TestResult</code> as skipped and invokes the listeners. |
| */ |
| private void handleConfigurationSkip(ITestNGMethod tm, |
| ITestResult testResult, |
| IConfigurationAnnotation annotation, |
| ITestNGMethod currentTestMethod, |
| Object instance, |
| XmlSuite suite) { |
| recordConfigurationInvocationFailed(tm, testResult.getTestClass(), annotation, currentTestMethod, instance, suite); |
| testResult.setStatus(ITestResult.SKIP); |
| runConfigurationListeners(testResult, false /* after */); |
| } |
| |
| /** |
| * Is the current <code>IConfiguration</code> a class-level method. |
| */ |
| private boolean isClassConfiguration(IConfigurationAnnotation configurationAnnotation) { |
| if (null == configurationAnnotation) { |
| return false; |
| } |
| |
| boolean before = configurationAnnotation.getBeforeTestClass(); |
| boolean after = configurationAnnotation.getAfterTestClass(); |
| |
| return before || after; |
| } |
| |
| /** |
| * Is the current <code>IConfiguration</code> a suite level method. |
| */ |
| private boolean isSuiteConfiguration(IConfigurationAnnotation configurationAnnotation) { |
| if (null == configurationAnnotation) { |
| return false; |
| } |
| |
| boolean before = configurationAnnotation.getBeforeSuite(); |
| boolean after = configurationAnnotation.getAfterSuite(); |
| |
| return before || after; |
| } |
| |
| /** |
| * Is the <code>IConfiguration</code> marked as alwaysRun. |
| */ |
| private boolean isAlwaysRun(IConfigurationAnnotation configurationAnnotation) { |
| if(null == configurationAnnotation) { |
| return false; |
| } |
| |
| boolean alwaysRun= false; |
| if ((configurationAnnotation.getAfterSuite() |
| || configurationAnnotation.getAfterTest() |
| || configurationAnnotation.getAfterTestClass() |
| || configurationAnnotation.getAfterTestMethod()) |
| && configurationAnnotation.getAlwaysRun()) |
| { |
| alwaysRun= true; |
| } |
| |
| return alwaysRun; |
| } |
| |
| private void handleConfigurationFailure(Throwable ite, |
| ITestNGMethod tm, |
| ITestResult testResult, |
| IConfigurationAnnotation annotation, |
| ITestNGMethod currentTestMethod, |
| Object instance, |
| XmlSuite suite) |
| { |
| Throwable cause= ite.getCause() != null ? ite.getCause() : ite; |
| |
| if(SkipException.class.isAssignableFrom(cause.getClass())) { |
| SkipException skipEx= (SkipException) cause; |
| if(skipEx.isSkip()) { |
| testResult.setThrowable(skipEx); |
| handleConfigurationSkip(tm, testResult, annotation, currentTestMethod, instance, suite); |
| return; |
| } |
| } |
| Utils.log("", 3, "Failed to invoke configuration method " |
| + tm.getRealClass().getName() + "." + tm.getMethodName() + ":" + cause.getMessage()); |
| handleException(cause, tm, testResult, 1); |
| runConfigurationListeners(testResult, false /* after */); |
| |
| // |
| // If in TestNG mode, need to take a look at the annotation to figure out |
| // what kind of @Configuration method we're dealing with |
| // |
| if (null != annotation) { |
| recordConfigurationInvocationFailed(tm, testResult.getTestClass(), annotation, currentTestMethod, instance, suite); |
| } |
| } |
| |
| /** |
| * @return All the classes that belong to the same <test> tag as @param cls |
| */ |
| private XmlClass[] findClassesInSameTest(Class<?> cls, XmlSuite suite) { |
| Map<String, XmlClass> vResult= Maps.newHashMap(); |
| String className= cls.getName(); |
| for(XmlTest test : suite.getTests()) { |
| for(XmlClass testClass : test.getXmlClasses()) { |
| if(testClass.getName().equals(className)) { |
| |
| // Found it, add all the classes in this test in the result |
| for(XmlClass thisClass : test.getXmlClasses()) { |
| vResult.put(thisClass.getName(), thisClass); |
| } |
| // Note: we need to iterate through the entire suite since the same |
| // class might appear in several <test> tags |
| } |
| } |
| } |
| |
| XmlClass[] result= vResult.values().toArray(new XmlClass[vResult.size()]); |
| |
| return result; |
| } |
| |
| /** |
| * Record internally the failure of a Configuration, so that we can determine |
| * later if @Test should be skipped. |
| */ |
| private void recordConfigurationInvocationFailed(ITestNGMethod tm, |
| IClass testClass, |
| IConfigurationAnnotation annotation, |
| ITestNGMethod currentTestMethod, |
| Object instance, |
| XmlSuite suite) { |
| // If beforeTestClass or afterTestClass failed, mark either the config method's |
| // entire class as failed, or the class under tests as failed, depending on |
| // the configuration failure policy |
| if (annotation.getBeforeTestClass() || annotation.getAfterTestClass()) { |
| // tm is the configuration method, and currentTestMethod is null for BeforeClass |
| // methods, so we need testClass |
| if (m_continueOnFailedConfiguration) { |
| setClassInvocationFailure(testClass.getRealClass(), instance); |
| } else { |
| setClassInvocationFailure(tm.getRealClass(), instance); |
| } |
| } |
| |
| // If before/afterTestMethod failed, mark either the config method's entire |
| // class as failed, or just the current test method as failed, depending on |
| // the configuration failure policy |
| else if (annotation.getBeforeTestMethod() || annotation.getAfterTestMethod()) { |
| if (m_continueOnFailedConfiguration) { |
| setMethodInvocationFailure(currentTestMethod, instance); |
| } else { |
| setClassInvocationFailure(tm.getRealClass(), instance); |
| } |
| } |
| |
| // If beforeSuite or afterSuite failed, mark *all* the classes as failed |
| // for configurations. At this point, the entire Suite is screwed |
| else if (annotation.getBeforeSuite() || annotation.getAfterSuite()) { |
| m_suiteState.failed(); |
| } |
| |
| // beforeTest or afterTest: mark all the classes in the same |
| // <test> stanza as failed for configuration |
| else if (annotation.getBeforeTest() || annotation.getAfterTest()) { |
| setClassInvocationFailure(tm.getRealClass(), instance); |
| XmlClass[] classes= findClassesInSameTest(tm.getRealClass(), suite); |
| for(XmlClass xmlClass : classes) { |
| setClassInvocationFailure(xmlClass.getSupportClass(), instance); |
| } |
| } |
| String[] beforeGroups= annotation.getBeforeGroups(); |
| if(null != beforeGroups && beforeGroups.length > 0) { |
| for(String group: beforeGroups) { |
| m_beforegroupsFailures.put(group, Boolean.FALSE); |
| } |
| } |
| } |
| |
| /** |
| * @return true if this class has successfully run all its @Configuration |
| * method or false if at least one of these methods failed. |
| */ |
| private boolean confInvocationPassed(ITestNGMethod method, ITestNGMethod currentTestMethod, IClass testClass, Object instance) { |
| boolean result= true; |
| |
| // If continuing on config failure, check invocation results for the class |
| // under test, otherwise use the method's declaring class |
| Class<?> cls = m_continueOnFailedConfiguration ? |
| testClass.getRealClass() : method.getMethod().getDeclaringClass(); |
| |
| if(m_suiteState.isFailed()) { |
| result= false; |
| } |
| else { |
| if (m_classInvocationResults.containsKey(cls)) { |
| if (! m_continueOnFailedConfiguration) { |
| result = !m_classInvocationResults.containsKey(cls); |
| } else { |
| result = !m_classInvocationResults.get(cls).contains(instance); |
| } |
| } |
| // if method is BeforeClass, currentTestMethod will be null |
| else if (m_continueOnFailedConfiguration && |
| currentTestMethod != null && |
| m_methodInvocationResults.containsKey(currentTestMethod)) { |
| result = !m_methodInvocationResults.get(currentTestMethod).contains(instance); |
| } |
| else if (! m_continueOnFailedConfiguration) { |
| for(Class<?> clazz: m_classInvocationResults.keySet()) { |
| // if (clazz == cls) { |
| if(clazz.isAssignableFrom(cls)) { |
| result= false; |
| break; |
| } |
| } |
| } |
| } |
| |
| // check if there are failed @BeforeGroups |
| String[] groups= method.getGroups(); |
| if(null != groups && groups.length > 0) { |
| for(String group: groups) { |
| if(m_beforegroupsFailures.containsKey(group)) { |
| result= false; |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Effectively invokes a configuration method on all passed in instances. |
| * TODO: Should change this method to be more like invokeMethod() so that we can |
| * handle calls to {@code IInvokedMethodListener} better. |
| * |
| * @param instances the instances to invoke the configuration method on |
| * @param tm the configuration method |
| * @param params the parameters needed for method invocation |
| * @param isClass flag if the configuration method is a class level method // FIXME: this looks like a missusage |
| * @param testResult |
| * @throws InvocationTargetException |
| * @throws IllegalAccessException |
| */ |
| private void invokeConfigurationMethod(Object[] instances, |
| ITestNGMethod tm, |
| Object[] params, |
| boolean isClass, |
| boolean isSuite, |
| ITestResult testResult) |
| throws InvocationTargetException, IllegalAccessException |
| { |
| // Mark this method with the current thread id |
| tm.setId(ThreadUtil.currentThreadInfo()); |
| |
| for(Object targetInstance : instances) { |
| InvokedMethod invokedMethod= new InvokedMethod(targetInstance, |
| tm, |
| params, |
| false, /* isTest */ |
| isClass, /* ??? */ |
| System.currentTimeMillis()); |
| |
| runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult); |
| m_notifier.addInvokedMethod(invokedMethod); |
| try { |
| Reporter.setCurrentTestResult(testResult); |
| Method method = tm.getMethod(); |
| |
| // |
| // If this method is a IHookable, invoke its run() method |
| // |
| IConfigurable configurableInstance = |
| IConfigurable.class.isAssignableFrom(tm.getMethod().getDeclaringClass()) ? |
| (IConfigurable) targetInstance : m_configuration.getConfigurable(); |
| if (configurableInstance != null) { |
| // |
| // If this method is a IConfigurable, invoke its run() method |
| // |
| MethodInvocationHelper.invokeConfigurable(targetInstance, params, configurableInstance, method, |
| testResult); |
| } |
| else { |
| // |
| // Not a IConfigurable, invoke directly |
| // |
| if (MethodHelper.calculateTimeOut(tm) <= 0) { |
| MethodInvocationHelper.invokeMethod(method, targetInstance, params); |
| } |
| else { |
| MethodInvocationHelper.invokeWithTimeout(tm, targetInstance, params, testResult); |
| if (!testResult.isSuccess()) { |
| // A time out happened |
| throwConfigurationFailure(testResult, testResult.getThrowable()); |
| throw testResult.getThrowable(); |
| } |
| } |
| } |
| // Only run the method once if it's @BeforeSuite or @AfterSuite |
| if (isSuite) { |
| break; |
| } |
| } |
| catch (InvocationTargetException ex) { |
| throwConfigurationFailure(testResult, ex); |
| throw ex; |
| } |
| catch (IllegalAccessException ex) { |
| throwConfigurationFailure(testResult, ex); |
| throw ex; |
| } |
| catch (NoSuchMethodException ex) { |
| throwConfigurationFailure(testResult, ex); |
| throw new TestNGException(ex); |
| } |
| catch (Throwable ex) { |
| throwConfigurationFailure(testResult, ex); |
| throw new TestNGException(ex); |
| } |
| finally { |
| Reporter.setCurrentTestResult(testResult); |
| runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult); |
| } |
| } |
| } |
| |
| private void throwConfigurationFailure(ITestResult testResult, Throwable ex) |
| { |
| testResult.setStatus(ITestResult.FAILURE);; |
| testResult.setThrowable(ex.getCause() == null ? ex : ex.getCause()); |
| } |
| |
| private void runInvokedMethodListeners(InvokedMethodListenerMethod listenerMethod, IInvokedMethod invokedMethod, |
| ITestResult testResult) |
| { |
| if ( noListenersPresent() ) { |
| return; |
| } |
| |
| InvokedMethodListenerInvoker invoker = new InvokedMethodListenerInvoker(listenerMethod, testResult, m_testContext); |
| for (IInvokedMethodListener currentListener : m_invokedMethodListeners) { |
| invoker.invokeListener(currentListener, invokedMethod); |
| } |
| } |
| |
| private boolean noListenersPresent() { |
| return (m_invokedMethodListeners == null) || (m_invokedMethodListeners.size() == 0); |
| } |
| |
| // pass both paramValues and paramIndex to be thread safe in case parallel=true + dataprovider. |
| private ITestResult invokeMethod(Object[] instances, |
| int instanceIndex, |
| final ITestNGMethod tm, |
| Object[] parameterValues, |
| int parametersIndex, |
| XmlSuite suite, |
| Map<String, String> params, |
| ITestClass testClass, |
| ITestNGMethod[] beforeMethods, |
| ITestNGMethod[] afterMethods, |
| ConfigurationGroupMethods groupMethods) { |
| TestResult testResult = new TestResult(); |
| |
| // |
| // Invoke beforeGroups configurations |
| // |
| Object instance = instances[instanceIndex]; |
| invokeBeforeGroupsConfigurations(testClass, tm, groupMethods, suite, params, |
| instance); |
| |
| // |
| // Invoke beforeMethods only if |
| // - firstTimeOnly is not set |
| // - firstTimeOnly is set, and we are reaching at the first invocationCount |
| // |
| invokeConfigurations(testClass, tm, |
| filterConfigurationMethods(tm, beforeMethods, true /* beforeMethods */), |
| suite, params, parameterValues, |
| instance, testResult); |
| |
| // |
| // Create the ExtraOutput for this method |
| // |
| InvokedMethod invokedMethod = null; |
| try { |
| testResult.init(testClass, instance, |
| tm, |
| null, |
| System.currentTimeMillis(), |
| 0); |
| testResult.setParameters(parameterValues); |
| testResult.setHost(m_testContext.getHost()); |
| testResult.setStatus(ITestResult.STARTED); |
| |
| invokedMethod= new InvokedMethod(instance, |
| tm, |
| parameterValues, |
| true /* isTest */, |
| false /* isConfiguration */, |
| System.currentTimeMillis()); |
| |
| // Fix from ansgarkonermann |
| // invokedMethod is used in the finally, which can be invoked if |
| // any of the test listeners throws an exception, therefore, |
| // invokedMethod must have a value before we get here |
| runTestListeners(testResult); |
| |
| runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult); |
| |
| m_notifier.addInvokedMethod(invokedMethod); |
| |
| Method thisMethod= tm.getMethod(); |
| |
| if(confInvocationPassed(tm, tm, testClass, instance)) { |
| log(3, "Invoking " + thisMethod.getDeclaringClass().getName() + "." + |
| thisMethod.getName()); |
| |
| // If no timeOut, just invoke the method |
| if (MethodHelper.calculateTimeOut(tm) <= 0) { |
| try { |
| Reporter.setCurrentTestResult(testResult); |
| // |
| // If this method is a IHookable, invoke its run() method |
| // |
| IHookable hookableInstance = |
| IHookable.class.isAssignableFrom(thisMethod.getDeclaringClass()) ? |
| (IHookable) instance : m_configuration.getHookable(); |
| if (hookableInstance != null) { |
| MethodInvocationHelper.invokeHookable(instance, |
| parameterValues, hookableInstance, thisMethod, testResult); |
| } |
| // |
| // Not a IHookable, invoke directly |
| // |
| else { |
| MethodInvocationHelper.invokeMethod(thisMethod, instance, |
| parameterValues); |
| } |
| testResult.setStatus(ITestResult.SUCCESS); |
| } |
| finally { |
| Reporter.setCurrentTestResult(null); |
| } |
| } |
| else { |
| // |
| // Method with a timeout |
| // |
| try { |
| Reporter.setCurrentTestResult(testResult); |
| MethodInvocationHelper.invokeWithTimeout(tm, instance, parameterValues, testResult); |
| } |
| finally { |
| Reporter.setCurrentTestResult(null); |
| } |
| } |
| } |
| else { |
| testResult.setStatus(ITestResult.SKIP); |
| } |
| } |
| catch(InvocationTargetException ite) { |
| testResult.setThrowable(ite.getCause()); |
| testResult.setStatus(ITestResult.FAILURE); |
| } |
| catch(ThreadExecutionException tee) { // wrapper for TestNGRuntimeException |
| Throwable cause= tee.getCause(); |
| if(TestNGRuntimeException.class.equals(cause.getClass())) { |
| testResult.setThrowable(cause.getCause()); |
| } |
| else { |
| testResult.setThrowable(cause); |
| } |
| testResult.setStatus(ITestResult.FAILURE); |
| } |
| catch(Throwable thr) { // covers the non-wrapper exceptions |
| testResult.setThrowable(thr); |
| testResult.setStatus(ITestResult.FAILURE); |
| } |
| finally { |
| ExpectedExceptionsHolder expectedExceptionClasses |
| = MethodHelper.findExpectedExceptions(m_annotationFinder, tm.getMethod()); |
| List<ITestResult> results = Lists.newArrayList(); |
| results.add(testResult); |
| handleInvocationResults(tm, results, null, 0, expectedExceptionClasses, false, |
| true /* collect results */); |
| |
| // If this method has a data provider and just failed, memorize the number |
| // at which it failed. |
| // Note: we're not exactly testing that this method has a data provider, just |
| // that it has parameters, so might have to revisit this if bugs get reported |
| // for the case where this method has parameters that don't come from a data |
| // provider |
| if (testResult.getThrowable() != null && parameterValues.length > 0) { |
| tm.addFailedInvocationNumber(parametersIndex); |
| } |
| |
| // |
| // Increment the invocation count for this method |
| // |
| tm.incrementCurrentInvocationCount(); |
| |
| if (testResult != null) { |
| testResult.setEndMillis(System.currentTimeMillis()); |
| } |
| |
| // Run invokedMethodListeners after updating TestResult |
| runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult); |
| runTestListeners(testResult); |
| |
| // |
| // Invoke afterMethods only if |
| // - lastTimeOnly is not set |
| // - lastTimeOnly is set, and we are reaching the last invocationCount |
| // |
| invokeConfigurations(testClass, tm, |
| filterConfigurationMethods(tm, afterMethods, false /* beforeMethods */), |
| suite, params, parameterValues, |
| instance, |
| testResult); |
| |
| // |
| // Invoke afterGroups configurations |
| // |
| invokeAfterGroupsConfigurations(testClass, tm, groupMethods, suite, |
| params, instance); |
| } |
| |
| return testResult; |
| } |
| |
| /** |
| * The array of methods contains @BeforeMethods if isBefore if true, @AfterMethods |
| * otherwise. This function removes all the methods that should not be run at this |
| * point because they are either firstTimeOnly or lastTimeOnly and we haven't reached |
| * the current invocationCount yet |
| */ |
| private ITestNGMethod[] filterConfigurationMethods(ITestNGMethod tm, |
| ITestNGMethod[] methods, boolean isBefore) |
| { |
| List<ITestNGMethod> result = Lists.newArrayList(); |
| for (ITestNGMethod m : methods) { |
| ConfigurationMethod cm = (ConfigurationMethod) m; |
| if (isBefore) { |
| if (! cm.isFirstTimeOnly() || |
| (cm.isFirstTimeOnly() && tm.getCurrentInvocationCount() == 0)) |
| { |
| result.add(m); |
| } |
| } |
| else { |
| int current = tm.getCurrentInvocationCount(); |
| boolean isLast = false; |
| // If we have parameters, set the boolean if we are about to run |
| // the last invocation |
| if (tm.getParameterInvocationCount() > 0) { |
| isLast = current == tm.getParameterInvocationCount(); |
| } |
| // If we have invocationCount > 1, set the boolean if we are about to |
| // run the last invocation |
| else if (tm.getInvocationCount() > 1) { |
| isLast = current == tm.getInvocationCount(); |
| } |
| if (! cm.isLastTimeOnly() || (cm.isLastTimeOnly() && isLast)) { |
| result.add(m); |
| } |
| } |
| } |
| |
| return result.toArray(new ITestNGMethod[result.size()]); |
| } |
| |
| /** |
| * {@link #invokeTestMethods()} eventually converge here to invoke a single @Test method. |
| * <p/> |
| * This method is responsible for actually invoking the method. It decides if the invocation |
| * must be done: |
| * <ul> |
| * <li>through an <code>IHookable</code></li> |
| * <li>directly (through reflection)</li> |
| * <li>in a separate thread (in case it needs to timeout) |
| * </ul> |
| * |
| * <p/> |
| * This method is also reponsible for invoking @BeforeGroup, @BeforeMethod, @AfterMethod, @AfterGroup |
| * if it is the case for the passed in @Test method. |
| */ |
| protected List<ITestResult> invokeTestMethod(Object[] instances, |
| final ITestNGMethod tm, |
| Object[] parameterValues, |
| int parametersIndex, |
| XmlSuite suite, |
| Map<String, String> params, |
| ITestClass testClass, |
| ITestNGMethod[] beforeMethods, |
| ITestNGMethod[] afterMethods, |
| ConfigurationGroupMethods groupMethods) |
| { |
| List<ITestResult> results = Lists.newArrayList(); |
| |
| // Mark this method with the current thread id |
| tm.setId(ThreadUtil.currentThreadInfo()); |
| |
| for(int i= 0; i < instances.length; i++) { |
| results.add(invokeMethod(instances, i, tm, parameterValues, parametersIndex, suite, params, |
| testClass, beforeMethods, afterMethods, groupMethods)); |
| } |
| |
| return results; |
| } |
| |
| /** |
| * Filter all the beforeGroups methods and invoke only those that apply |
| * to the current test method |
| */ |
| private void invokeBeforeGroupsConfigurations(ITestClass testClass, |
| ITestNGMethod currentTestMethod, |
| ConfigurationGroupMethods groupMethods, |
| XmlSuite suite, |
| Map<String, String> params, |
| Object instance) |
| { |
| synchronized(groupMethods) { |
| List<ITestNGMethod> filteredMethods = Lists.newArrayList(); |
| String[] groups = currentTestMethod.getGroups(); |
| Map<String, List<ITestNGMethod>> beforeGroupMap = groupMethods.getBeforeGroupsMap(); |
| |
| for (String group : groups) { |
| List<ITestNGMethod> methods = beforeGroupMap.get(group); |
| if (methods != null) { |
| filteredMethods.addAll(methods); |
| } |
| } |
| |
| ITestNGMethod[] beforeMethodsArray = filteredMethods.toArray(new ITestNGMethod[filteredMethods.size()]); |
| // |
| // Invoke the right groups methods |
| // |
| if(beforeMethodsArray.length > 0) { |
| // don't pass the IClass or the instance as the method may be external |
| // the invocation must be similar to @BeforeTest/@BeforeSuite |
| invokeConfigurations(null, beforeMethodsArray, suite, params, |
| null, /* no parameter values */ |
| null); |
| } |
| |
| // |
| // Remove them so they don't get run again |
| // |
| groupMethods.removeBeforeGroups(groups); |
| } |
| } |
| |
| private void invokeAfterGroupsConfigurations(ITestClass testClass, |
| ITestNGMethod currentTestMethod, |
| ConfigurationGroupMethods groupMethods, |
| XmlSuite suite, |
| Map<String, String> params, |
| Object instance) |
| { |
| // Skip this if the current method doesn't belong to any group |
| // (only a method that belongs to a group can trigger the invocation |
| // of afterGroups methods) |
| if (currentTestMethod.getGroups().length == 0) { |
| return; |
| } |
| |
| // See if the currentMethod is the last method in any of the groups |
| // it belongs to |
| Map<String, String> filteredGroups = Maps.newHashMap(); |
| String[] groups = currentTestMethod.getGroups(); |
| synchronized(groupMethods) { |
| for (String group : groups) { |
| if (groupMethods.isLastMethodForGroup(group, currentTestMethod)) { |
| filteredGroups.put(group, group); |
| } |
| } |
| |
| if(filteredGroups.isEmpty()) { |
| return; |
| } |
| |
| // The list of afterMethods to run |
| Map<ITestNGMethod, ITestNGMethod> afterMethods = Maps.newHashMap(); |
| |
| // Now filteredGroups contains all the groups for which we need to run the afterGroups |
| // method. Find all the methods that correspond to these groups and invoke them. |
| Map<String, List<ITestNGMethod>> map = groupMethods.getAfterGroupsMap(); |
| for (String g : filteredGroups.values()) { |
| List<ITestNGMethod> methods = map.get(g); |
| // Note: should put them in a map if we want to make sure the same afterGroups |
| // doesn't get run twice |
| if (methods != null) { |
| for (ITestNGMethod m : methods) { |
| afterMethods.put(m, m); |
| } |
| } |
| } |
| |
| // Got our afterMethods, invoke them |
| ITestNGMethod[] afterMethodsArray = afterMethods.keySet().toArray(new ITestNGMethod[afterMethods.size()]); |
| // don't pass the IClass or the instance as the method may be external |
| // the invocation must be similar to @BeforeTest/@BeforeSuite |
| invokeConfigurations(null, afterMethodsArray, suite, params, |
| null, /* no parameter values */ |
| null); |
| |
| // Remove the groups so they don't get run again |
| groupMethods.removeAfterGroups(filteredGroups.keySet()); |
| } |
| } |
| |
| private Object[] getParametersFromIndex(Iterator<Object[]> parametersValues, int index) { |
| while (parametersValues.hasNext()) { |
| Object[] parameters = parametersValues.next(); |
| |
| if (index == 0) { |
| return parameters; |
| } |
| index--; |
| } |
| return null; |
| } |
| |
| int retryFailed(Object[] instances, |
| int instanceIndex, |
| final ITestNGMethod tm, |
| XmlSuite suite, |
| ITestClass testClass, |
| ITestNGMethod[] beforeMethods, |
| ITestNGMethod[] afterMethods, |
| ConfigurationGroupMethods groupMethods, |
| List<ITestResult> result, |
| int failureCount, |
| ExpectedExceptionsHolder expectedExceptionHolder, |
| ITestContext testContext, |
| Map<String, String> parameters, |
| int parametersIndex) { |
| List<Object> failedInstances; |
| |
| do { |
| failedInstances = Lists.newArrayList(); |
| Map<String, String> allParameters = Maps.newHashMap(); |
| /** |
| * TODO: This recreates all the parameters every time when we only need |
| * one specific set. Should optimize it by only recreating the set needed. |
| */ |
| ParameterBag bag = createParameters(testClass, tm, parameters, |
| allParameters, null, suite, testContext, null /* fedInstance */, null /* testResult */); |
| Object[] parameterValues = |
| getParametersFromIndex(bag.parameterHolder.parameters, parametersIndex); |
| |
| result.add(invokeMethod(instances, instanceIndex, tm, parameterValues,parametersIndex, suite, |
| allParameters, testClass, beforeMethods, afterMethods, groupMethods)); |
| failureCount = handleInvocationResults(tm, result, failedInstances, |
| failureCount, expectedExceptionHolder, true, true /* collect results */); |
| } |
| while (!failedInstances.isEmpty()); |
| return failureCount; |
| } |
| |
| private ParameterBag createParameters(ITestClass testClass, |
| ITestNGMethod testMethod, |
| Map<String, String> parameters, |
| Map<String, String> allParameterNames, |
| Object[] parameterValues, |
| XmlSuite suite, |
| ITestContext testContext, |
| Object fedInstance, |
| ITestResult testResult) |
| { |
| Object instance; |
| if (fedInstance != null) { |
| instance = fedInstance; |
| } |
| else { |
| Object[] instances = testClass.getInstances(true); |
| instance = instances[0]; |
| } |
| |
| ParameterBag bag= handleParameters(testMethod, |
| instance, allParameterNames, parameters, parameterValues, suite, testContext, fedInstance, |
| testResult); |
| |
| return bag; |
| } |
| |
| /** |
| * Invoke all the test methods. Note the plural: the method passed in |
| * parameter might be invoked several times if the test class it belongs |
| * to has more than one instance (i.e., if an @Factory method has been |
| * declared somewhere that returns several instances of this TestClass). |
| * If no @Factory method was specified, testMethod will only be invoked |
| * once. |
| * <p/> |
| * Note that this method also takes care of invoking the beforeTestMethod |
| * and afterTestMethod, if any. |
| * |
| * Note (alex): this method can be refactored to use a SingleTestMethodWorker that |
| * directly invokes |
| * {@link #invokeTestMethod(Object[], ITestNGMethod, Object[], XmlSuite, Map, ITestClass, ITestNGMethod[], ITestNGMethod[], ConfigurationGroupMethods)} |
| * and this would simplify the implementation (see how DataTestMethodWorker is used) |
| */ |
| @Override |
| public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod, |
| ITestNGMethod[] allTestMethods, |
| int testMethodIndex, |
| XmlSuite suite, |
| Map<String, String> parameters, |
| ConfigurationGroupMethods groupMethods, |
| Object[] instances, |
| ITestContext testContext) |
| { |
| // Potential bug here if the test method was declared on a parent class |
| assert null != testMethod.getTestClass() |
| : "COULDN'T FIND TESTCLASS FOR " + testMethod.getMethod().getDeclaringClass(); |
| |
| List<ITestResult> result = Lists.newArrayList(); |
| |
| if (!MethodHelper.isEnabled(testMethod.getMethod(), m_annotationFinder)) { |
| /* |
| * return if the method is not enabled. No need to do any more calculations |
| */ |
| return result; |
| } |
| |
| ITestClass testClass= testMethod.getTestClass(); |
| long start = System.currentTimeMillis(); |
| |
| // For invocationCount > 1 and threadPoolSize > 1 the method will be invoked on a thread pool |
| long timeOutInvocationCount = testMethod.getInvocationTimeOut(); |
| //FIXME: Is this correct? |
| boolean onlyOne = testMethod.getThreadPoolSize() > 1 || |
| timeOutInvocationCount > 0; |
| |
| int invocationCount = onlyOne ? 1 : testMethod.getInvocationCount(); |
| int failureCount = 0; |
| |
| ExpectedExceptionsHolder expectedExceptionHolder = |
| MethodHelper.findExpectedExceptions(m_annotationFinder, testMethod.getMethod()); |
| while(invocationCount-- > 0) { |
| boolean okToProceed = checkDependencies(testMethod, allTestMethods); |
| |
| if (!okToProceed) { |
| // |
| // Not okToProceed. Test is being skipped |
| // |
| ITestResult testResult = new TestResult(testClass, null /* instance */, |
| testMethod, |
| null /* cause */, |
| start, |
| System.currentTimeMillis()); |
| String missingGroup = testMethod.getMissingGroup(); |
| if (missingGroup != null) { |
| testResult.setThrowable(new Throwable("Method " + testMethod |
| + " depends on nonexistent group \"" + missingGroup + "\"")); |
| } |
| |
| testResult.setStatus(ITestResult.SKIP); |
| result.add(testResult); |
| m_notifier.addSkippedTest(testMethod, testResult); |
| runTestListeners(testResult); |
| return result; |
| } |
| |
| // |
| // If threadPoolSize specified, run this method in its own pool thread. |
| // |
| if (testMethod.getThreadPoolSize() > 1 && testMethod.getInvocationCount() > 1) { |
| return invokePooledTestMethods(testMethod, allTestMethods, suite, |
| parameters, groupMethods, testContext); |
| } |
| // |
| // No threads, regular invocation |
| // |
| else { |
| ITestNGMethod[] beforeMethods = filterMethods(testClass, testClass.getBeforeTestMethods()); |
| ITestNGMethod[] afterMethods = filterMethods(testClass, testClass.getAfterTestMethods()); |
| |
| Map<String, String> allParameterNames = Maps.newHashMap(); |
| ParameterBag bag = createParameters(testClass, testMethod, |
| parameters, allParameterNames, null, suite, testContext, instances[0], |
| null); |
| |
| if (bag.hasErrors()) { |
| failureCount = handleInvocationResults(testMethod, |
| bag.errorResults, null, failureCount, expectedExceptionHolder, true, |
| true /* collect results */); |
| ITestResult tr = registerSkippedTestResult(testMethod, instances[0], start, |
| bag.errorResults.get(0).getThrowable()); |
| result.add(tr); |
| continue; |
| } |
| |
| Iterator<Object[]> allParameterValues = bag.parameterHolder.parameters; |
| int parametersIndex = 0; |
| |
| try { |
| List<TestMethodWithDataProviderMethodWorker> workers = Lists.newArrayList(); |
| |
| if (bag.parameterHolder.origin == ParameterOrigin.ORIGIN_DATA_PROVIDER && |
| bag.parameterHolder.dataProviderHolder.annotation.isParallel()) { |
| while (allParameterValues.hasNext()) { |
| Object[] parameterValues = injectParameters(allParameterValues.next(), |
| testMethod.getMethod(), testContext, null /* test result */); |
| TestMethodWithDataProviderMethodWorker w = |
| new TestMethodWithDataProviderMethodWorker(this, |
| testMethod, parametersIndex, |
| parameterValues, instances, suite, parameters, testClass, |
| beforeMethods, afterMethods, groupMethods, |
| expectedExceptionHolder, testContext, m_skipFailedInvocationCounts, |
| invocationCount, failureCount, m_notifier); |
| workers.add(w); |
| // testng387: increment the param index in the bag. |
| parametersIndex++; |
| } |
| PoolService ps = PoolService.getInstance(); |
| List<ITestResult> r = ps.submitTasksAndWait(testMethod, workers); |
| result.addAll(r); |
| |
| } else { |
| while (allParameterValues.hasNext()) { |
| Object[] parameterValues = injectParameters(allParameterValues.next(), |
| testMethod.getMethod(), testContext, null /* test result */); |
| |
| List<ITestResult> tmpResults = Lists.newArrayList(); |
| |
| try { |
| tmpResults.addAll(invokeTestMethod(instances, |
| testMethod, |
| parameterValues, |
| parametersIndex, |
| suite, |
| parameters, |
| testClass, |
| beforeMethods, |
| afterMethods, |
| groupMethods)); |
| } |
| finally { |
| List<Object> failedInstances = Lists.newArrayList(); |
| |
| failureCount = handleInvocationResults(testMethod, tmpResults, |
| failedInstances, failureCount, expectedExceptionHolder, true, |
| false /* don't collect results */); |
| if (failedInstances.isEmpty()) { |
| result.addAll(tmpResults); |
| } else { |
| for (int i = 0; i < failedInstances.size(); i++) { |
| List<ITestResult> retryResults = Lists.newArrayList(); |
| |
| failureCount = |
| retryFailed(failedInstances.toArray(), |
| i, testMethod, suite, testClass, beforeMethods, |
| afterMethods, groupMethods, retryResults, |
| failureCount, expectedExceptionHolder, |
| testContext, parameters, parametersIndex); |
| result.addAll(retryResults); |
| } |
| } |
| |
| // |
| // If we have a failure, skip all the |
| // other invocationCounts |
| // |
| if (failureCount > 0 |
| && (m_skipFailedInvocationCounts |
| || testMethod.skipFailedInvocations())) { |
| while (invocationCount-- > 0) { |
| result.add(registerSkippedTestResult(testMethod, instances[0], start, null)); |
| } |
| break; |
| } |
| }// end finally |
| parametersIndex++; |
| } |
| } |
| } |
| catch (Throwable cause) { |
| ITestResult r = |
| new TestResult(testMethod.getTestClass(), |
| instances[0], |
| testMethod, |
| cause, |
| start, |
| System.currentTimeMillis()); |
| r.setStatus(TestResult.FAILURE); |
| result.add(r); |
| runTestListeners(r); |
| m_notifier.addFailedTest(testMethod, r); |
| } // catch |
| } |
| } |
| |
| return result; |
| |
| } // invokeTestMethod |
| |
| private ITestResult registerSkippedTestResult(ITestNGMethod testMethod, Object instance, |
| long start, Throwable throwable) { |
| ITestResult result = |
| new TestResult(testMethod.getTestClass(), |
| instance, |
| testMethod, |
| throwable, |
| start, |
| System.currentTimeMillis()); |
| result.setStatus(TestResult.SKIP); |
| runTestListeners(result); |
| |
| return result; |
| } |
| |
| /** |
| * Gets an array of parameter values returned by data provider or the ones that |
| * are injected based on parameter type. The method also checks for {@code NoInjection} |
| * annotation |
| * @param parameterValues parameter values from a data provider |
| * @param method method to be invoked |
| * @param context test context |
| * @param testResult test result |
| * @return |
| */ |
| private Object[] injectParameters(Object[] parameterValues, Method method, |
| ITestContext context, ITestResult testResult) |
| throws TestNGException { |
| List<Object> vResult = Lists.newArrayList(); |
| int i = 0; |
| int numValues = parameterValues.length; |
| int numParams = method.getParameterTypes().length; |
| |
| if (numValues > numParams && ! method.isVarArgs()) { |
| throw new TestNGException("The data provider is trying to pass " + numValues |
| + " parameters but the method " |
| + method.getDeclaringClass().getName() + "#" + method.getName() |
| + " takes " + numParams); |
| } |
| |
| // beyond this, numValues <= numParams |
| for (Class<?> cls : method.getParameterTypes()) { |
| Annotation[] annotations = method.getParameterAnnotations()[i]; |
| boolean noInjection = false; |
| for (Annotation a : annotations) { |
| if (a instanceof NoInjection) { |
| noInjection = true; |
| break; |
| } |
| } |
| Object injected = Parameters.getInjectedParameter(cls, method, context, testResult); |
| if (injected != null && ! noInjection) { |
| vResult.add(injected); |
| } else { |
| try { |
| if (method.isVarArgs()) vResult.add(parameterValues); |
| else vResult.add(parameterValues[i++]); |
| } catch (ArrayIndexOutOfBoundsException ex) { |
| throw new TestNGException("The data provider is trying to pass " + numValues |
| + " parameters but the method " |
| + method.getDeclaringClass().getName() + "#" + method.getName() |
| + " takes " + numParams |
| + " and TestNG is unable in inject a suitable object", ex); |
| } |
| } |
| } |
| return vResult.toArray(new Object[vResult.size()]); |
| } |
| |
| private ParameterBag handleParameters(ITestNGMethod testMethod, |
| Object instance, |
| Map<String, String> allParameterNames, |
| Map<String, String> parameters, |
| Object[] parameterValues, |
| XmlSuite suite, |
| ITestContext testContext, |
| Object fedInstance, |
| ITestResult testResult) |
| { |
| try { |
| return new ParameterBag( |
| Parameters.handleParameters(testMethod, |
| allParameterNames, |
| instance, |
| new Parameters.MethodParameters(parameters, parameterValues, |
| testMethod.getMethod(), testContext, testResult), |
| suite, |
| m_annotationFinder, |
| fedInstance), |
| null /* TestResult */); |
| } |
| // catch(TestNGException ex) { |
| // throw ex; |
| // } |
| catch(Throwable cause) { |
| return new ParameterBag(null /* ParameterHolder */, |
| new TestResult( |
| testMethod.getTestClass(), |
| instance, |
| testMethod, |
| cause, |
| System.currentTimeMillis(), |
| System.currentTimeMillis())); |
| } |
| } |
| |
| /** |
| * Invokes a method that has a specified threadPoolSize. |
| */ |
| private List<ITestResult> invokePooledTestMethods(ITestNGMethod testMethod, |
| ITestNGMethod[] allTestMethods, |
| XmlSuite suite, |
| Map<String, String> parameters, |
| ConfigurationGroupMethods groupMethods, |
| ITestContext testContext) |
| { |
| // |
| // Create the workers |
| // |
| List<IWorker<ITestNGMethod>> workers = Lists.newArrayList(); |
| |
| for (int i = 0; i < testMethod.getInvocationCount(); i++) { |
| // we use clones for reporting purposes |
| ITestNGMethod clonedMethod= testMethod.clone(); |
| clonedMethod.setInvocationCount(1); |
| clonedMethod.setThreadPoolSize(1); |
| |
| MethodInstance mi = new MethodInstance(clonedMethod, clonedMethod.getTestClass().getInstances(true)); |
| workers.add(new SingleTestMethodWorker(this, |
| mi, |
| suite, |
| parameters, |
| allTestMethods, |
| testContext)); |
| } |
| |
| return runWorkers(testMethod, workers, testMethod.getThreadPoolSize(), groupMethods, suite, parameters); |
| } |
| |
| /** |
| * @param testMethod |
| * @param result |
| * @param failureCount |
| * @param expectedExceptionsHolder |
| * @return |
| */ |
| int handleInvocationResults(ITestNGMethod testMethod, |
| List<ITestResult> result, |
| List<Object> failedInstances, |
| int failureCount, |
| ExpectedExceptionsHolder expectedExceptionsHolder, |
| boolean triggerListeners, |
| boolean collectResults) |
| { |
| // |
| // Go through all the results and create a TestResult for each of them |
| // |
| List<ITestResult> resultsToRetry = Lists.newArrayList(); |
| |
| for (int i = 0; i < result.size(); i++) { |
| ITestResult testResult = result.get(i); |
| Throwable ite= testResult.getThrowable(); |
| int status= testResult.getStatus(); |
| |
| // Exception thrown? |
| if (ite != null) { |
| |
| // Invocation caused an exception, see if the method was annotated with @ExpectedException |
| if (isExpectedException(ite, expectedExceptionsHolder)) { |
| if (messageRegExpMatches(expectedExceptionsHolder.messageRegExp, ite)) { |
| testResult.setStatus(ITestResult.SUCCESS); |
| status= ITestResult.SUCCESS; |
| } |
| else { |
| testResult.setThrowable( |
| new TestException("The exception was thrown with the wrong message:" + |
| " expected \"" + expectedExceptionsHolder.messageRegExp + "\"" + |
| " but got \"" + ite.getMessage() + "\"", ite)); |
| status= ITestResult.FAILURE; |
| } |
| } else if (ite != null && expectedExceptionsHolder != null) { |
| testResult.setThrowable( |
| new TestException("Expected exception " |
| + expectedExceptionsHolder.expectedClasses[0].getName() |
| + " but got " + ite, ite)); |
| status= ITestResult.FAILURE; |
| } |
| else if (SkipException.class.isAssignableFrom(ite.getClass())){ |
| SkipException skipEx= (SkipException) ite; |
| if(skipEx.isSkip()) { |
| status = ITestResult.SKIP; |
| } |
| else { |
| handleException(ite, testMethod, testResult, failureCount++); |
| status = ITestResult.FAILURE; |
| } |
| } |
| else { |
| handleException(ite, testMethod, testResult, failureCount++); |
| status= testResult.getStatus(); |
| } |
| } |
| |
| // No exception thrown, make sure we weren't expecting one |
| else if(status != ITestResult.SKIP && expectedExceptionsHolder != null) { |
| Class<?>[] classes = expectedExceptionsHolder.expectedClasses; |
| if (classes != null && classes.length > 0) { |
| testResult.setThrowable( |
| new TestException("Method " + testMethod + " should have thrown an exception of " |
| + expectedExceptionsHolder.expectedClasses[0])); |
| status= ITestResult.FAILURE; |
| } |
| } |
| |
| testResult.setStatus(status); |
| |
| boolean retry = false; |
| |
| if (testResult.getStatus() == ITestResult.FAILURE) { |
| IRetryAnalyzer retryAnalyzer = testMethod.getRetryAnalyzer(); |
| |
| if (retryAnalyzer != null && failedInstances != null) { |
| retry = retryAnalyzer.retry(testResult); |
| } |
| |
| if (retry) { |
| resultsToRetry.add(testResult); |
| if (failedInstances != null) { |
| failedInstances.add(testResult.getInstance()); |
| } |
| } |
| } |
| if (!retry || collectResults) { |
| // Collect the results |
| if(ITestResult.SUCCESS == status) { |
| m_notifier.addPassedTest(testMethod, testResult); |
| } |
| else if(ITestResult.SKIP == status) { |
| m_notifier.addSkippedTest(testMethod, testResult); |
| } |
| else if(ITestResult.FAILURE == status) { |
| m_notifier.addFailedTest(testMethod, testResult); |
| } |
| else if(ITestResult.SUCCESS_PERCENTAGE_FAILURE == status) { |
| m_notifier.addFailedButWithinSuccessPercentageTest(testMethod, testResult); |
| } |
| else { |
| assert false : "UNKNOWN STATUS:" + status; |
| } |
| // if (triggerListeners && status != ITestResult.SUCCESS) { |
| // runTestListeners(testResult); |
| // } |
| } |
| } // for results |
| |
| return removeResultsToRetryFromResult(resultsToRetry, result, failureCount); |
| } |
| |
| /** |
| * message / regEx .* other |
| * null true false |
| * non-null true match |
| */ |
| private boolean messageRegExpMatches(String messageRegExp, Throwable ite) { |
| if (".*".equals(messageRegExp)) { |
| return true; |
| } else { |
| if (ite.getMessage() == null) { |
| return false; |
| } else { |
| return Pattern.matches(messageRegExp, ite.getMessage()); |
| } |
| } |
| } |
| |
| private int removeResultsToRetryFromResult(List<ITestResult> resultsToRetry, |
| List<ITestResult> result, int failureCount) { |
| if (resultsToRetry != null) { |
| for (ITestResult res : resultsToRetry) { |
| result.remove(res); |
| failureCount--; |
| } |
| } |
| return failureCount; |
| } |
| |
| /** |
| * To reduce thread contention and also to correctly handle thread-confinement |
| * this method invokes the @BeforeGroups and @AfterGroups corresponding to the current @Test method. |
| */ |
| private List<ITestResult> runWorkers(ITestNGMethod testMethod, |
| List<IWorker<ITestNGMethod>> workers, |
| int threadPoolSize, |
| ConfigurationGroupMethods groupMethods, |
| XmlSuite suite, |
| Map<String, String> parameters) |
| { |
| // Invoke @BeforeGroups on the original method (reduce thread contention, |
| // and also solve thread confinement) |
| ITestClass testClass= testMethod.getTestClass(); |
| Object[] instances = testClass.getInstances(true); |
| for(Object instance: instances) { |
| invokeBeforeGroupsConfigurations(testClass, testMethod, groupMethods, suite, parameters, instance); |
| } |
| |
| |
| long maxTimeOut= -1; // 10 seconds |
| |
| for(IWorker<ITestNGMethod> tmw : workers) { |
| long mt= tmw.getTimeOut(); |
| if(mt > maxTimeOut) { |
| maxTimeOut= mt; |
| } |
| } |
| |
| ThreadUtil.execute(workers, threadPoolSize, maxTimeOut, true); |
| |
| // |
| // Collect all the TestResults |
| // |
| List<ITestResult> result = Lists.newArrayList(); |
| for (IWorker<ITestNGMethod> tmw : workers) { |
| if (tmw instanceof TestMethodWorker) { |
| result.addAll(((TestMethodWorker)tmw).getTestResults()); |
| } |
| } |
| |
| for(Object instance: instances) { |
| invokeAfterGroupsConfigurations(testClass, testMethod, groupMethods, suite, parameters, instance); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Checks to see of the test method has certain dependencies that prevents |
| * TestNG from executing it |
| * @param testMethod test method being checked for |
| * @param testClass |
| * @return dependencies have been run successfully |
| */ |
| private boolean checkDependencies(ITestNGMethod testMethod, |
| ITestNGMethod[] allTestMethods) |
| { |
| boolean result = true; |
| |
| // If this method is marked alwaysRun, no need to check for its dependencies |
| if (testMethod.isAlwaysRun()) { |
| return true; |
| } |
| |
| // Any missing group? |
| if (testMethod.getMissingGroup() != null |
| && !testMethod.ignoreMissingDependencies()) { |
| return false; |
| } |
| |
| // If this method depends on groups, collect all the methods that |
| // belong to these groups and make sure they have been run successfully |
| if (dependsOnGroups(testMethod)) { |
| String[] groupsDependedUpon = testMethod.getGroupsDependedUpon(); |
| |
| // Get all the methods that belong to the group depended upon |
| for (String element : groupsDependedUpon) { |
| ITestNGMethod[] methods = |
| MethodGroupsHelper.findMethodsThatBelongToGroup(testMethod, |
| m_testContext.getAllTestMethods(), |
| element); |
| |
| result = result && haveBeenRunSuccessfully(testMethod, methods); |
| } |
| } // depends on groups |
| |
| // If this method depends on other methods, make sure all these other |
| // methods have been run successfully |
| if (result && dependsOnMethods(testMethod)) { |
| ITestNGMethod[] methods = |
| MethodHelper.findDependedUponMethods(testMethod, allTestMethods); |
| |
| result = result && haveBeenRunSuccessfully(testMethod, methods); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @return the test results that apply to one of the instances of the testMethod. |
| */ |
| private Set<ITestResult> keepSameInstances(ITestNGMethod method, Set<ITestResult> results) { |
| Set<ITestResult> result = Sets.newHashSet(); |
| for (ITestResult r : results) { |
| for (Object o : method.getInstances()) { |
| // Keep this instance if 1) It's on a different class or 2) It's on the same class |
| // and on the same instance |
| Object instance = r.getInstance() != null |
| ? r.getInstance() : r.getMethod().getInstances()[0]; |
| if (r.getTestClass() != method.getTestClass() || instance == o) result.add(r); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @return true if all the methods have been run successfully |
| */ |
| private boolean haveBeenRunSuccessfully(ITestNGMethod testMethod, ITestNGMethod[] methods) { |
| // Make sure the method has been run successfully |
| for (ITestNGMethod method : methods) { |
| Set<ITestResult> results = keepSameInstances(testMethod, m_notifier.getPassedTests(method)); |
| Set<ITestResult> failedAndSkippedMethods = Sets.newHashSet(); |
| failedAndSkippedMethods.addAll(m_notifier.getFailedTests(method)); |
| failedAndSkippedMethods.addAll(m_notifier.getSkippedTests(method)); |
| Set<ITestResult> failedresults = keepSameInstances(testMethod, failedAndSkippedMethods); |
| |
| // If failed results were returned on the same instance, then these tests didn't pass |
| if (failedresults != null && failedresults.size() > 0) { |
| return false; |
| } |
| |
| for (ITestResult result : results) { |
| if(!result.isSuccess()) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private boolean containsInstance(Set<ITestResult> failedresults, Object[] instances) { |
| for (ITestResult tr : failedresults) { |
| for (Object o : instances) { |
| if (o == tr.getInstance()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * An exception was thrown by the test, determine if this method |
| * should be marked as a failure or as failure_but_within_successPercentage |
| */ |
| private void handleException(Throwable throwable, |
| ITestNGMethod testMethod, |
| ITestResult testResult, |
| int failureCount) { |
| testResult.setThrowable(throwable); |
| int successPercentage= testMethod.getSuccessPercentage(); |
| int invocationCount= testMethod.getInvocationCount(); |
| float numberOfTestsThatCanFail= ((100 - successPercentage) * invocationCount) / 100f; |
| |
| if(failureCount < numberOfTestsThatCanFail) { |
| testResult.setStatus(ITestResult.SUCCESS_PERCENTAGE_FAILURE); |
| } |
| else { |
| testResult.setStatus(ITestResult.FAILURE); |
| } |
| |
| } |
| |
| /** |
| * @param ite The exception that was just thrown |
| * @param expectedExceptions The list of expected exceptions for this |
| * test method |
| * @return true if the exception that was just thrown is part of the |
| * expected exceptions |
| */ |
| private boolean isExpectedException(Throwable ite, ExpectedExceptionsHolder exceptionHolder) { |
| if (exceptionHolder == null) { |
| return false; |
| } |
| |
| // TestException is the wrapper exception that TestNG will be throwing when an exception was |
| // expected but not thrown |
| if (ite.getClass() == TestException.class) { |
| return false; |
| } |
| |
| Class<?>[] exceptions = exceptionHolder.expectedClasses; |
| Class<?> realExceptionClass= ite.getClass(); |
| |
| for (Class<?> exception : exceptions) { |
| if (exception.isAssignableFrom(realExceptionClass)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @return Only the ITestNGMethods applicable for this testClass |
| */ |
| private ITestNGMethod[] filterMethods(IClass testClass, ITestNGMethod[] methods) { |
| List<ITestNGMethod> vResult= Lists.newArrayList(); |
| |
| for(ITestNGMethod tm : methods) { |
| if(tm.canRunFromClass(testClass)) { |
| log(9, "Keeping method " + tm + " for class " + testClass); |
| vResult.add(tm); |
| } |
| else { |
| log(9, "Filtering out method " + tm + " for class " + testClass); |
| } |
| } |
| |
| ITestNGMethod[] result= vResult.toArray(new ITestNGMethod[vResult.size()]); |
| |
| return result; |
| } |
| |
| private ITestNGMethod[] filterMethodsUnique(IClass testClass, ITestNGMethod[] methods) { |
| if(null == testClass) { |
| return methods; |
| } |
| |
| List<ITestNGMethod> vResult= Lists.newArrayList(); |
| |
| for(ITestNGMethod tm : methods) { |
| testClass= tm.getTestClass(); |
| |
| if (tm.getTestClass().getName().equals(testClass.getName())) { |
| log(9, " Keeping method " + tm + " for class " + testClass); |
| |
| vResult.add(tm); |
| } |
| else { |
| log(9, " Filtering out method " + tm + " for class " + testClass); |
| } |
| } |
| |
| return vResult.toArray(new ITestNGMethod[vResult.size()]); |
| } |
| |
| /** |
| * @return true if this method depends on certain groups. |
| */ |
| private boolean dependsOnGroups(ITestNGMethod tm) { |
| String[] groups = tm.getGroupsDependedUpon(); |
| boolean result = (null != groups) && (groups.length > 0); |
| return result; |
| } |
| |
| /** |
| * @return true if this method depends on certain groups. |
| */ |
| private boolean dependsOnMethods(ITestNGMethod tm) { |
| String[] methods = tm.getMethodsDependedUpon(); |
| boolean result = (null != methods) && (methods.length > 0); |
| return result; |
| } |
| |
| private void runConfigurationListeners(ITestResult tr, boolean before) { |
| if (before) { |
| for(IConfigurationListener icl: m_notifier.getConfigurationListeners()) { |
| icl.beforeConfiguration(tr); |
| } |
| } else { |
| for(IConfigurationListener icl: m_notifier.getConfigurationListeners()) { |
| switch(tr.getStatus()) { |
| case ITestResult.SKIP: |
| icl.onConfigurationSkip(tr); |
| break; |
| case ITestResult.FAILURE: |
| icl.onConfigurationFailure(tr); |
| break; |
| case ITestResult.SUCCESS: |
| icl.onConfigurationSuccess(tr); |
| break; |
| } |
| } |
| } |
| } |
| |
| void runTestListeners(ITestResult tr) { |
| runTestListeners(tr, m_notifier.getTestListeners()); |
| } |
| |
| // TODO: move this from here as it is directly called from TestNG |
| public static void runTestListeners(ITestResult tr, List<ITestListener> listeners) { |
| for (ITestListener itl : listeners) { |
| switch(tr.getStatus()) { |
| case ITestResult.SKIP: { |
| itl.onTestSkipped(tr); |
| break; |
| } |
| case ITestResult.SUCCESS_PERCENTAGE_FAILURE: { |
| itl.onTestFailedButWithinSuccessPercentage(tr); |
| break; |
| } |
| case ITestResult.FAILURE: { |
| itl.onTestFailure(tr); |
| break; |
| } |
| case ITestResult.SUCCESS: { |
| itl.onTestSuccess(tr); |
| break; |
| } |
| |
| case ITestResult.STARTED: { |
| itl.onTestStart(tr); |
| break; |
| } |
| |
| default: { |
| assert false : "UNKNOWN STATUS:" + tr; |
| } |
| } |
| } |
| } |
| |
| private void log(int level, String s) { |
| Utils.log("Invoker " + Thread.currentThread().hashCode(), level, s); |
| } |
| |
| /** |
| * This class holds a {@code ParameterHolder} and in case of an error, a non-null |
| * {@code TestResult} containing the cause |
| */ |
| private static class ParameterBag { |
| final ParameterHolder parameterHolder; |
| final List<ITestResult> errorResults = Lists.newArrayList(); |
| |
| public ParameterBag(ParameterHolder params, TestResult tr) { |
| parameterHolder = params; |
| if (tr != null) { |
| errorResults.add(tr); |
| } |
| } |
| |
| public boolean hasErrors() { |
| return !errorResults.isEmpty(); |
| } |
| } |
| |
| } |