Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.testng.internal.Invoker Maven / Gradle / Ivy
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);
}
}