All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testng.internal.Invoker Maven / Gradle / Ivy

There is a newer version: 7.10.1
Show newest version
package org.testng.internal;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import java.util.concurrent.ConcurrentHashMap;

import org.testng.IClass;
import org.testng.IClassListener;
import org.testng.IConfigurable;
import org.testng.IDataProviderListener;
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.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.collections.Lists;
import org.testng.collections.Maps;
import org.testng.collections.Sets;
import org.testng.internal.InvokeMethodRunnable.TestNGRuntimeException;
import static org.testng.internal.ParameterHandler.ParameterBag;
import org.testng.internal.annotations.AnnotationHelper;
import org.testng.internal.annotations.IAnnotationFinder;
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 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 Cedric Beust
 * @author Alexandru Popescu
 */
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 Collection m_invokedMethodListeners;
  private final boolean m_continueOnFailedConfiguration;
  private final List m_classListeners;
  private final Collection m_dataproviderListeners;

  private final Set m_executedConfigMethods = ConcurrentHashMap.newKeySet();

  /** Group failures must be synced as the Invoker is accessed concurrently */
  private final Map m_beforegroupsFailures = Maps.newConcurrentMap();

  /** Class failures must be synced as the Invoker is accessed concurrently */
  private final Map, Set> m_classInvocationResults = Maps.newConcurrentMap();

  /** Test methods whose configuration methods have failed. */
  private final Map> m_methodInvocationResults = Maps.newConcurrentMap();

  private IConfiguration m_configuration;

  /** Predicate to filter methods */
  private static final Predicate CAN_RUN_FROM_CLASS =
      new CanRunFromClassPredicate();
  /** Predicate to filter methods */
  private static final Predicate SAME_CLASS = new SameClassNamePredicate();

  private void setClassInvocationFailure(Class clazz, Object instance) {
    synchronized (m_classInvocationResults) {
      Set instances =
          m_classInvocationResults.computeIfAbsent(clazz, k -> Sets.newHashSet());
      instances.add(instance);
    }
  }

  private void setMethodInvocationFailure(ITestNGMethod method, Object instance) {
    Set instances =
        m_methodInvocationResults.computeIfAbsent(method, k -> Sets.newHashSet());
    instances.add(TestNgMethodUtils.getMethodInvocationToken(method, instance));
  }

  public Invoker(
      IConfiguration configuration,
      ITestContext testContext,
      ITestResultNotifier notifier,
      SuiteRunState state,
      boolean skipFailedInvocationCounts,
      Collection invokedMethodListeners,
      List classListeners,
      Collection dataProviderListeners) {
    m_configuration = configuration;
    m_testContext = testContext;
    m_suiteState = state;
    m_notifier = notifier;
    m_annotationFinder = configuration.getAnnotationFinder();
    m_skipFailedInvocationCounts = skipFailedInvocationCounts;
    m_invokedMethodListeners = invokedMethodListeners;
    m_continueOnFailedConfiguration =
        testContext.getSuite().getXmlSuite().getConfigFailurePolicy()
            == XmlSuite.FailurePolicy.CONTINUE;
    m_classListeners = classListeners;
    m_dataproviderListeners = dataProviderListeners;
  }

  /**
   * Invoke configuration methods if they belong to the same TestClass passed in parameter..
   *
   * 

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 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 params, Object[] parameterValues, Object instance, ITestResult testMethodResult) { if (null == allMethods || allMethods.length == 0) { log(5, "No configuration methods found"); return; } ITestNGMethod[] methods = TestNgMethodUtils.filterMethods(testClass, allMethods, SAME_CLASS); Object[] parameters = new Object[] {}; for (ITestNGMethod tm : methods) { if (null == testClass) { testClass = tm.getTestClass(); } ITestResult testResult = TestResult.newContextAwareTestResult(tm, m_testContext); testResult.setStatus(ITestResult.STARTED); IConfigurationAnnotation configurationAnnotation = null; try { Object inst = tm.getInstance(); if (inst == null) { inst = instance; } Class objectClass = inst.getClass(); ConstructorOrMethod method = tm.getConstructorOrMethod(); // Only run the configuration if // - the test is enabled and // - the Configuration method belongs to the same class or a parent configurationAnnotation = AnnotationHelper.findConfiguration(m_annotationFinder, method); boolean alwaysRun = MethodHelper.isAlwaysRun(configurationAnnotation); boolean canProcessMethod = MethodHelper.isEnabled(objectClass, m_annotationFinder) || alwaysRun; if (!canProcessMethod) { log( 3, "Skipping " + Utils.detailedMethodName(tm, true) + " because " + objectClass.getName() + " is not enabled"); continue; } if (MethodHelper.isDisabled(configurationAnnotation)) { log(3, "Skipping " + Utils.detailedMethodName(tm, true) + " because it is not enabled"); continue; } if (!confInvocationPassed(tm, currentTestMethod, testClass, instance) && !alwaysRun) { log(3, "Skipping " + Utils.detailedMethodName(tm, true)); InvokedMethod invokedMethod = new InvokedMethod(instance, tm, System.currentTimeMillis(), testResult); runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult); testResult.setStatus(ITestResult.SKIP); runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult); handleConfigurationSkip( tm, testResult, configurationAnnotation, currentTestMethod, instance, suite); continue; } log(3, "Invoking " + Utils.detailedMethodName(tm, true)); if (testMethodResult != null) { ((TestResult) testMethodResult).setMethod(currentTestMethod); } parameters = Parameters.createConfigurationParameters( tm.getConstructorOrMethod().getMethod(), params, parameterValues, currentTestMethod, m_annotationFinder, suite, m_testContext, testMethodResult); testResult.setParameters(parameters); runConfigurationListeners(testResult, true /* before */); Object newInstance = computeInstance(instance, inst, tm); if (isConfigMethodEligibleForScrutiny(tm)) { if (m_executedConfigMethods.add(currentTestMethod)) { invokeConfigurationMethod(newInstance, tm, parameters, testResult); } } else { invokeConfigurationMethod(newInstance, tm, parameters, testResult); } copyAttributesFromNativelyInjectedTestResult(parameters, testMethodResult); runConfigurationListeners(testResult, false /* after */); } catch (Throwable ex) { handleConfigurationFailure( ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite); copyAttributesFromNativelyInjectedTestResult(parameters, testMethodResult); } } // for methods } private static void copyAttributesFromNativelyInjectedTestResult( Object[] source, ITestResult target) { if (source == null || target == null) { return; } Arrays.stream(source) .filter(each -> each instanceof ITestResult) .findFirst() .ifPresent(eachSource -> TestResult.copyAttributes((ITestResult) eachSource, target)); } private static Object computeInstance(Object instance, Object inst, ITestNGMethod tm) { if (instance == null || !tm.getConstructorOrMethod().getDeclaringClass().isAssignableFrom(instance.getClass())) { return inst; } return instance; } /** Marks the current TestResult 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 */); } 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 (isSkipExceptionAndSkip(cause)) { testResult.setThrowable(cause); handleConfigurationSkip(tm, testResult, annotation, currentTestMethod, instance, suite); return; } Utils.log( "", 3, "Failed to invoke configuration method " + tm.getQualifiedName() + ":" + cause.getMessage()); handleException(cause, tm, testResult, 1); testResult.setStatus(ITestResult.FAILURE); 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); } } /** * 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 // stanza as failed for configuration else if (annotation.getBeforeTest() || annotation.getAfterTest()) { setClassInvocationFailure(tm.getRealClass(), instance); XmlClass[] classes = ClassHelper.findClassesInSameTest(tm.getRealClass(), suite); for (XmlClass xmlClass : classes) { setClassInvocationFailure(xmlClass.getSupportClass(), instance); } } String[] beforeGroups = annotation.getBeforeGroups(); for (String group : beforeGroups) { m_beforegroupsFailures.put(group, Boolean.FALSE); } } /** @return true if this class or a parent class failed to initialize. */ private boolean classConfigurationFailed(Class cls, Object instance) { return m_classInvocationResults .entrySet() .stream() .anyMatch( classSetEntry -> { Set obj = classSetEntry.getValue(); Class c = classSetEntry.getKey(); boolean containsBeforeTestOrBeforeSuiteFailure = obj.contains(null); return c == cls || c.isAssignableFrom(cls) && (obj.contains(instance) || containsBeforeTestOrBeforeSuiteFailure); }); } /** * @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; Class cls = testClass.getRealClass(); if (m_suiteState.isFailed()) { result = false; } else { boolean hasConfigurationFailures = classConfigurationFailed(cls, instance); if (hasConfigurationFailures) { if (!m_continueOnFailedConfiguration) { result = false; } else { Set set = getInvocationResults(testClass); result = !set.contains(instance); } return result; } // if method is BeforeClass, currentTestMethod will be null if (m_continueOnFailedConfiguration && hasConfigFailure(currentTestMethod)) { Object key = TestNgMethodUtils.getMethodInvocationToken(currentTestMethod, instance); result = !m_methodInvocationResults.get(currentTestMethod).contains(key); } else if (!m_continueOnFailedConfiguration) { for (Class clazz : m_classInvocationResults.keySet()) { if (clazz.isAssignableFrom(cls) && m_classInvocationResults.get(clazz).contains(instance)) { result = false; break; } } } } // check if there are failed @BeforeGroups String[] groups = method.getGroups(); for (String group : groups) { if (m_beforegroupsFailures.containsKey(group)) { result = false; break; } } return result; } private boolean hasConfigFailure(ITestNGMethod currentTestMethod) { return currentTestMethod != null && m_methodInvocationResults.containsKey(currentTestMethod); } private Set getInvocationResults(IClass testClass) { Class cls = testClass.getRealClass(); Set set = null; // We need to continuously search till either our Set is not null (or) till we reached // Object class because it is very much possible that the test method is residing in a child // class // and maybe the parent method has configuration methods which may have had a failure // So lets walk up the inheritance tree until either we find failures or till we // reached the Object class. while (!cls.equals(Object.class)) { set = m_classInvocationResults.get(cls); if (set != null) { break; } cls = cls.getSuperclass(); } if (set == null) { // This should never happen because we have walked up all the way till Object class // and yet found no failures, but our logic indicates that there was a failure somewhere up // the // inheritance order. We don't know what to do at this point. throw new IllegalStateException("No failure logs for " + testClass.getRealClass()); } return set; } private static boolean isConfigMethodEligibleForScrutiny(ITestNGMethod tm) { if (!tm.isBeforeMethodConfiguration()) { return false; } if (!(tm instanceof ConfigurationMethod)) { return false; } ConfigurationMethod cfg = (ConfigurationMethod) tm; return cfg.isFirstTimeOnly(); } /** Effectively invokes a configuration method on all passed in instances. */ // TODO: Change this method to be more like invokeMethod() so that we can handle calls to {@code // IInvokedMethodListener} better. private void invokeConfigurationMethod( Object targetInstance, ITestNGMethod tm, Object[] params, ITestResult testResult) throws InvocationTargetException, IllegalAccessException { // Mark this method with the current thread id tm.setId(ThreadUtil.currentThreadInfo()); InvokedMethod invokedMethod = new InvokedMethod(targetInstance, tm, System.currentTimeMillis(), testResult); runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult); m_notifier.addInvokedMethod(invokedMethod); try { Reporter.setCurrentTestResult(testResult); ConstructorOrMethod method = tm.getConstructorOrMethod(); IConfigurable configurableInstance = computeConfigurableInstance(method, targetInstance); if (RuntimeBehavior.isDryRun()) { testResult.setStatus(ITestResult.SUCCESS); return; } if (configurableInstance != null) { MethodInvocationHelper.invokeConfigurable( targetInstance, params, configurableInstance, method.getMethod(), testResult); } else { MethodInvocationHelper.invokeMethodConsideringTimeout( tm, method, targetInstance, params, testResult); } testResult.setStatus(ITestResult.SUCCESS); } catch (InvocationTargetException | IllegalAccessException ex) { throwConfigurationFailure(testResult, ex); testResult.setStatus(ITestResult.FAILURE); throw ex; } catch (Throwable ex) { throwConfigurationFailure(testResult, ex); testResult.setStatus(ITestResult.FAILURE); throw new TestNGException(ex); } finally { testResult.setEndMillis(System.currentTimeMillis()); Reporter.setCurrentTestResult(testResult); runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult); Reporter.setCurrentTestResult(null); } } private IConfigurable computeConfigurableInstance( ConstructorOrMethod method, Object targetInstance) { return IConfigurable.class.isAssignableFrom(method.getDeclaringClass()) ? (IConfigurable) targetInstance : m_configuration.getConfigurable(); } 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.isEmpty()); } // pass both paramValues and paramIndex to be thread safe in case parallel=true + dataprovider. private ITestResult invokeMethod( Object instance, ITestNGMethod tm, Object[] parameterValues, int parametersIndex, XmlSuite suite, Map params, ITestClass testClass, ITestNGMethod[] beforeMethods, ITestNGMethod[] afterMethods, ConfigurationGroupMethods groupMethods, FailureContext failureContext) { TestResult testResult = TestResult.newEmptyTestResult(); invokeBeforeGroupsConfigurations(tm, groupMethods, suite, params, instance); ITestNGMethod[] setupConfigMethods = TestNgMethodUtils.filterSetupConfigurationMethods(tm, beforeMethods); invokeConfigurations( testClass, tm, setupConfigMethods, suite, params, parameterValues, instance, testResult); InvokedMethod invokedMethod = new InvokedMethod(instance, tm, System.currentTimeMillis(), testResult); if (!confInvocationPassed(tm, tm, testClass, instance)) { Throwable exception = ExceptionUtils.getExceptionDetails(m_testContext, instance); ITestResult result = registerSkippedTestResult(tm, System.currentTimeMillis(), exception); TestResult.copyAttributes(testResult, result); m_notifier.addSkippedTest(tm, result); tm.incrementCurrentInvocationCount(); testResult.setMethod(tm); invokeListenersForSkippedTestResult(result, invokedMethod); ITestNGMethod[] teardownConfigMethods = TestNgMethodUtils.filterTeardownConfigurationMethods(tm, afterMethods); invokeConfigurations( testClass, tm, teardownConfigMethods, suite, params, parameterValues, instance, testResult); invokeAfterGroupsConfigurations(tm, groupMethods, suite, params, instance); return result; } // // Create the ExtraOutput for this method // try { testResult = TestResult.newTestResultFrom(testResult, tm, m_testContext,System.currentTimeMillis()); //Recreate the invoked method object again, because we now have a new test result object invokedMethod = new InvokedMethod(instance, tm, invokedMethod.getDate(), testResult); testResult.setParameters(parameterValues); testResult.setParameterIndex(parametersIndex); testResult.setHost(m_testContext.getHost()); testResult.setStatus(ITestResult.STARTED); Reporter.setCurrentTestResult(testResult); // 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 if (!m_suiteState.isFailed()) { runTestResultListener(testResult); } log(3, "Invoking " + tm.getQualifiedName()); runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult); m_notifier.addInvokedMethod(invokedMethod); Method thisMethod = tm.getConstructorOrMethod().getMethod(); if (RuntimeBehavior.isDryRun()) { setTestStatus(testResult, ITestResult.SUCCESS); return testResult; } // If this method is a IHookable, invoke its run() method IHookable hookableInstance = IHookable.class.isAssignableFrom(tm.getRealClass()) ? (IHookable) instance : m_configuration.getHookable(); if (MethodHelper.calculateTimeOut(tm) <= 0) { if (hookableInstance != null) { MethodInvocationHelper.invokeHookable( instance, parameterValues, hookableInstance, thisMethod, testResult); } else { // Not a IHookable, invoke directly MethodInvocationHelper.invokeMethod(thisMethod, instance, parameterValues); } setTestStatus(testResult, ITestResult.SUCCESS); } else { // Method with a timeout MethodInvocationHelper.invokeWithTimeout( tm, instance, parameterValues, testResult, hookableInstance); } } catch (InvocationTargetException ite) { testResult.setThrowable(ite.getCause()); setTestStatus(testResult, 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); } setTestStatus(testResult, ITestResult.FAILURE); } catch (Throwable thr) { // covers the non-wrapper exceptions testResult.setThrowable(thr); setTestStatus(testResult, ITestResult.FAILURE); } finally { // Set end time ASAP testResult.setEndMillis(System.currentTimeMillis()); ExpectedExceptionsHolder expectedExceptionClasses = new ExpectedExceptionsHolder( m_annotationFinder, tm, new RegexpExpectedExceptionsHolder(m_annotationFinder, tm)); StatusHolder holder = considerExceptions(tm, testResult, expectedExceptionClasses, failureContext); int statusBeforeListenerInvocation = testResult.getStatus(); runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult); boolean wasResultUnaltered = statusBeforeListenerInvocation == testResult.getStatus(); handleInvocationResults(tm, testResult, failureContext, holder, wasResultUnaltered); // 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(); runTestResultListener(testResult); collectResults(tm, testResult); ITestNGMethod[] tearDownConfigMethods = TestNgMethodUtils.filterTeardownConfigurationMethods(tm, afterMethods); invokeConfigurations( testClass, tm, tearDownConfigMethods, suite, params, parameterValues, instance, testResult); // // Invoke afterGroups configurations // invokeAfterGroupsConfigurations(tm, groupMethods, suite, params, instance); // Reset the test result last. If we do this too early, Reporter.log() // invocations from listeners will be discarded Reporter.setCurrentTestResult(null); } return testResult; } private static void setTestStatus(ITestResult result, int status) { // set the test to success as long as the testResult hasn't been changed by the user via // Reporter.getCurrentTestResult if (result.getStatus() == ITestResult.STARTED) { result.setStatus(status); } } private void collectResults(ITestNGMethod testMethod, ITestResult result) { // Collect the results int status = result.getStatus(); if (ITestResult.SUCCESS == status) { m_notifier.addPassedTest(testMethod, result); } else if (ITestResult.SKIP == status) { m_notifier.addSkippedTest(testMethod, result); } else if (ITestResult.FAILURE == status) { m_notifier.addFailedTest(testMethod, result); } else if (ITestResult.SUCCESS_PERCENTAGE_FAILURE == status) { m_notifier.addFailedButWithinSuccessPercentageTest(testMethod, result); } else { assert false : "UNKNOWN STATUS:" + status; } } /** * invokeTestMethods() eventually converge here to invoke a single @Test method. * *

This method is responsible for actually invoking the method. It decides if the invocation * must be done: * *

    *
  • through an IHookable *
  • directly (through reflection) *
  • in a separate thread (in case it needs to timeout) *
* *

This method is also responsible for * invoking @BeforeGroup, @BeforeMethod, @AfterMethod, @AfterGroup if it is the case for the * passed in @Test method. */ ITestResult invokeTestMethod( Object instance, ITestNGMethod tm, Object[] parameterValues, int parametersIndex, XmlSuite suite, Map params, ITestClass testClass, ITestNGMethod[] beforeMethods, ITestNGMethod[] afterMethods, ConfigurationGroupMethods groupMethods, FailureContext failureContext) { // Mark this method with the current thread id tm.setId(ThreadUtil.currentThreadInfo()); return invokeMethod( instance, tm, parameterValues, parametersIndex, suite, params, testClass, beforeMethods, afterMethods, groupMethods, failureContext); } /** * Filter all the beforeGroups methods and invoke only those that apply to the current test method */ private void invokeBeforeGroupsConfigurations( ITestNGMethod currentTestMethod, ConfigurationGroupMethods groupMethods, XmlSuite suite, Map params, Object instance) { List filteredMethods = Lists.newArrayList(); String[] groups = currentTestMethod.getGroups(); for (String group : groups) { List methods = groupMethods.getBeforeGroupMethodsForGroup(group); if (methods != null) { filteredMethods.addAll(methods); } } ITestNGMethod[] beforeMethodsArray = filteredMethods.toArray(new ITestNGMethod[0]); // // 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, /* no parameter values */ null, instance); } // // Remove them so they don't get run again // groupMethods.removeBeforeGroups(groups); } private void invokeAfterGroupsConfigurations( ITestNGMethod currentTestMethod, ConfigurationGroupMethods groupMethods, XmlSuite suite, Map 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 filteredGroups = Maps.newHashMap(); String[] groups = currentTestMethod.getGroups(); for (String group : groups) { if (groupMethods.isLastMethodForGroup(group, currentTestMethod)) { filteredGroups.put(group, group); } } if (filteredGroups.isEmpty()) { return; } // The list of afterMethods to run Map 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. for (String g : filteredGroups.values()) { List methods = groupMethods.getAfterGroupMethodsForGroup(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[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, afterMethodsArray, suite, params, /* no parameter values */ null, instance); // Remove the groups so they don't get run again groupMethods.removeAfterGroups(filteredGroups.keySet()); } FailureContext retryFailed( Object instance, ITestNGMethod tm, Object[] paramValues, ITestClass testClass, ITestNGMethod[] beforeMethods, ITestNGMethod[] afterMethods, ConfigurationGroupMethods groupMethods, List result, int failureCount, ITestContext testContext, Map parameters, int parametersIndex) { FailureContext failure = new FailureContext(); failure.count = failureCount; do { failure.instances = Lists.newArrayList(); Map 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. ParameterHandler handler = new ParameterHandler(m_annotationFinder, m_dataproviderListeners); ParameterBag bag = handler.createParameters(tm, parameters, allParameters, testContext); Object[] parameterValues = Parameters.getParametersFromIndex(bag.parameterHolder.parameters, parametersIndex); if (bag.parameterHolder.origin == ParameterHolder.ParameterOrigin.NATIVE) { parameterValues = paramValues; } result.add( invokeMethod( instance, tm, parameterValues, parametersIndex, testContext.getSuite().getXmlSuite(), allParameters, testClass, beforeMethods, afterMethods, groupMethods, failure)); } while (!failure.instances.isEmpty()); return failure; } /** * 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. * *

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[], int, XmlSuite, Map, * ITestClass, ITestNGMethod[], ITestNGMethod[], ConfigurationGroupMethods, FailureContext)} and * this would simplify the implementation (see how DataTestMethodWorker is used) */ @Override public List invokeTestMethods( ITestNGMethod testMethod, Map testParameters, ConfigurationGroupMethods groupMethods, Object instance, ITestContext testContext) { // Potential bug here if the test method was declared on a parent class if (testMethod.getTestClass() == null) { throw new IllegalArgumentException( "COULDN'T FIND TESTCLASS FOR " + testMethod.getRealClass()); } XmlSuite suite = testContext.getSuite().getXmlSuite(); if (!MethodHelper.isEnabled( testMethod.getConstructorOrMethod().getMethod(), m_annotationFinder)) { // return if the method is not enabled. No need to do any more calculations return Collections.emptyList(); } // By the time this testMethod to be invoked, // all dependencies should be already run or we need to skip this method, // so invocation count should not affect dependencies check String okToProceed = checkDependencies(testMethod, testContext.getAllTestMethods()); if (okToProceed != null) { // // Not okToProceed. Test is being skipped // ITestResult result = registerSkippedTestResult( testMethod, System.currentTimeMillis(), new Throwable(okToProceed)); m_notifier.addSkippedTest(testMethod, result); InvokedMethod invokedMethod = new InvokedMethod(result.getInstance(), testMethod, System.currentTimeMillis(), result); invokeListenersForSkippedTestResult(result, invokedMethod); return Collections.singletonList(result); } Map parameters = testMethod.findMethodParameters(testContext.getCurrentXmlTest()); // For invocationCount > 1 and threadPoolSize > 1 run this method in its own pool thread. if (testMethod.getInvocationCount() > 1 && testMethod.getThreadPoolSize() > 1) { return invokePooledTestMethods(testMethod, suite, parameters, groupMethods, testContext); } long timeOutInvocationCount = testMethod.getInvocationTimeOut(); // FIXME: Is this correct? boolean onlyOne = testMethod.getThreadPoolSize() > 1 || timeOutInvocationCount > 0; int invocationCount = onlyOne ? 1 : testMethod.getInvocationCount(); ITestClass testClass = testMethod.getTestClass(); List result = Lists.newArrayList(); FailureContext failure = new FailureContext(); ITestNGMethod[] beforeMethods = TestNgMethodUtils.filterBeforeTestMethods(testClass, CAN_RUN_FROM_CLASS); ITestNGMethod[] afterMethods = TestNgMethodUtils.filterAfterTestMethods(testClass, CAN_RUN_FROM_CLASS); while (invocationCount-- > 0) { long start = System.currentTimeMillis(); Map allParameterNames = Maps.newHashMap(); ParameterHandler handler = new ParameterHandler(m_annotationFinder, m_dataproviderListeners); ParameterBag bag = handler.createParameters( testMethod, parameters, allParameterNames, testContext, instance); if (bag.hasErrors()) { ITestResult tr = bag.errorResult; Throwable throwable = tr.getThrowable(); if (throwable instanceof TestNGException) { tr.setStatus(ITestResult.FAILURE); m_notifier.addFailedTest(testMethod, tr); } else { tr.setStatus(ITestResult.SKIP); m_notifier.addSkippedTest(testMethod, tr); } runTestResultListener(tr); result.add(tr); continue; } Iterator allParameterValues = bag.parameterHolder.parameters; int parametersIndex = 0; try { if (bag.runInParallel()) { List workers = Lists.newArrayList(); while (allParameterValues.hasNext()) { Object[] next = allParameterValues.next(); if (next == null) { // skipped value parametersIndex++; continue; } Object[] parameterValues = Parameters.injectParameters( next, testMethod.getConstructorOrMethod().getMethod(), testContext); TestMethodWithDataProviderMethodWorker w = new TestMethodWithDataProviderMethodWorker( this, testMethod, parametersIndex, parameterValues, instance, parameters, testClass, beforeMethods, afterMethods, groupMethods, testContext, m_skipFailedInvocationCounts, invocationCount, failure.count, m_notifier); workers.add(w); // testng387: increment the param index in the bag. parametersIndex++; } PoolService> ps = new PoolService<>(suite.getDataProviderThreadCount()); List> r = ps.submitTasksAndWait(workers); for (List l2 : r) { result.addAll(l2); } } else { while (allParameterValues.hasNext()) { Object[] next = allParameterValues.next(); if (next == null) { // skipped value parametersIndex++; continue; } Object[] parameterValues = Parameters.injectParameters( next, testMethod.getConstructorOrMethod().getMethod(), testContext); List tmpResults = Lists.newArrayList(); int tmpResultsIndex = -1; try { tmpResults.add( invokeTestMethod( instance, testMethod, parameterValues, parametersIndex, suite, parameters, testClass, beforeMethods, afterMethods, groupMethods, failure)); tmpResultsIndex++; } finally { boolean lastSucces = false; if (tmpResultsIndex >= 0) { lastSucces = (tmpResults.get(tmpResultsIndex).getStatus() == ITestResult.SUCCESS); } if (failure.instances.isEmpty() || lastSucces) { result.addAll(tmpResults); } else { List retryResults = Lists.newArrayList(); failure = retryFailed( instance, testMethod, parameterValues, testClass, beforeMethods, afterMethods, groupMethods, retryResults, failure.count, testContext, parameters, parametersIndex); result.addAll(retryResults); } // If we have a failure, skip all the // other invocationCounts if (failure.count > 0 && (m_skipFailedInvocationCounts || testMethod.skipFailedInvocations())) { while (invocationCount-- > 0) { ITestResult r = registerSkippedTestResult(testMethod, System.currentTimeMillis(), null); result.add(r); InvokedMethod invokedMethod = new InvokedMethod(r.getInstance(), testMethod, System.currentTimeMillis(), r); invokeListenersForSkippedTestResult(r, invokedMethod); } } } // end finally parametersIndex++; } } } catch (Throwable cause) { ITestResult r = TestResult.newEndTimeAwareTestResult(testMethod, m_testContext, cause, start); r.setStatus(TestResult.FAILURE); result.add(r); runTestResultListener(r); m_notifier.addFailedTest(testMethod, r); } // catch } return result; } private void invokeListenersForSkippedTestResult(ITestResult r, InvokedMethod invokedMethod) { if (m_configuration.alwaysRunListeners()) { runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, r); runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, r); } runTestResultListener(r); } private ITestResult registerSkippedTestResult( ITestNGMethod testMethod, long start, Throwable throwable) { ITestResult result = TestResult.newEndTimeAwareTestResult(testMethod, m_testContext, throwable, start); result.setStatus(ITestResult.STARTED); runTestResultListener(result); result.setStatus(TestResult.SKIP); Reporter.setCurrentTestResult(result); return result; } /** Invokes a method that has a specified threadPoolSize. */ private List invokePooledTestMethods( ITestNGMethod testMethod, XmlSuite suite, Map parameters, ConfigurationGroupMethods groupMethods, ITestContext testContext) { // // Create the workers // List> workers = Lists.newArrayList(); // Create one worker per invocationCount 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); workers.add(new SingleTestMethodWorker(this, mi, parameters, testContext, m_classListeners)); } return runWorkers( testMethod, workers, testMethod.getThreadPoolSize(), groupMethods, suite, parameters); } static class FailureContext { int count = 0; List instances = Lists.newArrayList(); } private static class StatusHolder { boolean handled = false; int status; } private StatusHolder considerExceptions( ITestNGMethod tm, ITestResult testresult, ExpectedExceptionsHolder exceptionsHolder, FailureContext failure) { StatusHolder holder = new StatusHolder(); holder.status = testresult.getStatus(); holder.handled = false; Throwable ite = testresult.getThrowable(); if (holder.status == ITestResult.FAILURE && ite != null) { // Invocation caused an exception, see if the method was annotated with @ExpectedException if (exceptionsHolder != null) { if (exceptionsHolder.isExpectedException(ite)) { testresult.setStatus(ITestResult.SUCCESS); holder.status = ITestResult.SUCCESS; } else { if (isSkipExceptionAndSkip(ite)) { holder.status = ITestResult.SKIP; } else { testresult.setThrowable(exceptionsHolder.wrongException(ite)); holder.status = ITestResult.FAILURE; } } } else { handleException(ite, tm, testresult, failure.count++); holder.handled = true; holder.status = testresult.getStatus(); } } else if (holder.status != ITestResult.SKIP && exceptionsHolder != null) { TestException exception = exceptionsHolder.noException(tm); if (exception != null) { testresult.setThrowable(exception); holder.status = ITestResult.FAILURE; } } return holder; } private void handleInvocationResults( ITestNGMethod testMethod, ITestResult testResult, FailureContext failure, StatusHolder holder, boolean wasResultUnaltered) { // // Go through all the results and create a TestResult for each of them // List resultsToRetry = Lists.newArrayList(); Throwable ite = testResult.getThrowable(); int status = computeTestStatusComparingTestResultAndStatusHolder(testResult, holder, wasResultUnaltered); boolean handled = holder.handled; IRetryAnalyzer retryAnalyzer = testMethod.getRetryAnalyzer(); boolean willRetry = retryAnalyzer != null && status == ITestResult.FAILURE && failure.instances != null && retryAnalyzer.retry(testResult); if (willRetry) { resultsToRetry.add(testResult); Object instance = testResult.getInstance(); if (!failure.instances.contains(instance)) { failure.instances.add(instance); } testResult.setStatus(ITestResult.SKIP); testResult.setWasRetried(true); } else { testResult.setStatus(status); if (status == ITestResult.FAILURE && !handled) { int count = failure.count++; if (testMethod.isDataDriven()) { count = 0; } handleException(ite, testMethod, testResult, count); } } } private static int computeTestStatusComparingTestResultAndStatusHolder( ITestResult testResult, StatusHolder holder, boolean wasResultUnaltered) { if (wasResultUnaltered) { return holder.status; } return testResult.getStatus(); } private boolean isSkipExceptionAndSkip(Throwable ite) { return SkipException.class.isAssignableFrom(ite.getClass()) && ((SkipException) ite).isSkip(); } /** * 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 runWorkers( ITestNGMethod testMethod, List> workers, int threadPoolSize, ConfigurationGroupMethods groupMethods, XmlSuite suite, Map 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(testMethod, groupMethods, suite, parameters, instance); } long maxTimeOut = -1; // 10 seconds for (IWorker tmw : workers) { long mt = tmw.getTimeOut(); if (mt > maxTimeOut) { maxTimeOut = mt; } } ThreadUtil.execute("methods", workers, threadPoolSize, maxTimeOut, true); // // Collect all the TestResults // List result = Lists.newArrayList(); for (IWorker tmw : workers) { if (tmw instanceof TestMethodWorker) { result.addAll(((TestMethodWorker) tmw).getTestResults()); } } for (Object instance : instances) { invokeAfterGroupsConfigurations(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 * @return error message or null if dependencies have been run successfully */ private String checkDependencies(ITestNGMethod testMethod, ITestNGMethod[] allTestMethods) { // If this method is marked alwaysRun, no need to check for its dependencies if (testMethod.isAlwaysRun()) { return null; } // Any missing group? if (testMethod.getMissingGroup() != null && !testMethod.ignoreMissingDependencies()) { return "Method " + testMethod + " depends on nonexistent group \"" + testMethod.getMissingGroup() + "\""; } // If this method depends on groups, collect all the methods that // belong to these groups and make sure they have been run successfully String[] groups = testMethod.getGroupsDependedUpon(); if (null != groups && groups.length > 0) { // Get all the methods that belong to the group depended upon for (String element : groups) { ITestNGMethod[] methods = MethodGroupsHelper.findMethodsThatBelongToGroup( testMethod, m_testContext.getAllTestMethods(), element); if (methods.length == 0 && !testMethod.ignoreMissingDependencies()) { // Group is missing return "Method " + testMethod + " depends on nonexistent group \"" + element + "\""; } if (!haveBeenRunSuccessfully(testMethod, methods)) { return "Method " + testMethod + " depends on not successfully finished methods in group \"" + element + "\""; } } } // depends on groups // If this method depends on other methods, make sure all these other // methods have been run successfully if (TestNgMethodUtils.cannotRunMethodIndependently(testMethod)) { ITestNGMethod[] methods = MethodHelper.findDependedUponMethods(testMethod, allTestMethods); if (!haveBeenRunSuccessfully(testMethod, methods)) { return "Method " + testMethod + " depends on not successfully finished methods"; } } return null; } /** @return the test results that apply to one of the instances of the testMethod. */ private Set keepSameInstances(ITestNGMethod method, Set results) { Set result = Sets.newHashSet(); for (ITestResult r : results) { Object o = method.getInstance(); // 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().getInstance(); 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 results = keepSameInstances(testMethod, m_notifier.getPassedTests(method)); Set failedAndSkippedMethods = Sets.newHashSet(); Set skippedAttempts = m_notifier.getSkippedTests(method); failedAndSkippedMethods.addAll(m_notifier.getFailedTests(method)); failedAndSkippedMethods.addAll(skippedAttempts); Set failedresults = keepSameInstances(testMethod, failedAndSkippedMethods); boolean wasMethodRetried = !results.isEmpty() && !skippedAttempts.isEmpty(); if (!wasMethodRetried && !failedresults.isEmpty()) { // If failed results were returned on the same instance, then these tests didn't pass return false; } for (ITestResult result : results) { if (!result.isSuccess()) { return false; } } } return true; } /** * 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) { if (throwable != null && testResult.getThrowable() == null) { 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); } } interface Predicate { boolean isTrue(K k, T v); } static class CanRunFromClassPredicate implements Predicate { @Override public boolean isTrue(ITestNGMethod m, IClass v) { return m.canRunFromClass(v); } } static class SameClassNamePredicate implements Predicate { @Override public boolean isTrue(ITestNGMethod m, IClass c) { return c == null || m.getTestClass().getName().equals(c.getName()); } } private void runConfigurationListeners(ITestResult tr, boolean before) { if (before) { TestListenerHelper.runPreConfigurationListeners(tr, m_notifier.getConfigurationListeners()); } else { TestListenerHelper.runPostConfigurationListeners(tr, m_notifier.getConfigurationListeners()); } } void runTestResultListener(ITestResult tr) { TestListenerHelper.runTestListeners(tr, m_notifier.getTestListeners()); } private void log(int level, String s) { Utils.log("Invoker " + Thread.currentThread().hashCode(), level, s); } }