blob: 48f29f54ddd469d9458cd281092b34bf333f7bee [file] [log] [blame]
package org.testng.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.testng.ClassMethodMap;
import org.testng.IClass;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestClass;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.TestException;
import org.testng.TestNGException;
import org.testng.internal.InvokeMethodRunnable.TestNGRuntimeException;
import org.testng.internal.annotations.AnnotationHelper;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.annotations.IConfiguration;
import org.testng.internal.annotations.IExpectedExceptions;
import org.testng.internal.annotations.ITest;
import org.testng.internal.thread.ICountDown;
import org.testng.internal.thread.IExecutor;
import org.testng.internal.thread.IFutureResult;
import org.testng.internal.thread.IPooledExecutor;
import org.testng.internal.thread.IThreadFactory;
import org.testng.internal.thread.ThreadExecutionException;
import org.testng.internal.thread.ThreadTimeoutException;
import org.testng.internal.thread.ThreadUtil;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
/**
* This class is responsible for invoking methods:
* - test methods
* - configuration methods
* - possibly in a separate thread
* and then for notifying the result listeners.
*
* @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
* @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
*/
public class Invoker implements IInvoker {
private ITestContext m_testContext;
private ITestResultNotifier m_notifier;
private IAnnotationFinder m_annotationFinder;
public Invoker(ITestContext testContext,
ITestResultNotifier notifier,
IAnnotationFinder annotationFinder) {
m_testContext= testContext;
m_notifier= notifier;
m_annotationFinder= annotationFinder;
}
/**
* Invoke configuration methods if they belong to the same TestClass passed
* in parameter.. <p/>TODO: Calculate ahead of time which methods should be
* invoked for each class. Might speed things up for users who invoke the
* same test class with different parameters in the same suite run.
*
* If instance is non-null, the configuration will be run on it. If it is null,
* the configuration methods will be run on all the instances retrieved
* from the ITestClass.
*/
public void invokeConfigurations(IClass testClass,
ITestNGMethod[] allMethods,
XmlSuite suite,
Map<String, String> params,
Object instance)
{
if(null == allMethods) {
log(5, "No @Configuration methods found");
return;
}
ITestNGMethod[] methods= filterMethodsUnique(testClass, allMethods);
for(ITestNGMethod tm : methods) {
if(null == testClass) {
testClass= tm.getTestClass();
}
ITestResult testResult= new TestResult(testClass, instance,
tm,
null,
System.currentTimeMillis(),
System.currentTimeMillis());
IConfiguration configurationAnnotation= null;
try {
Object[] instances= tm.getInstances();
if (instances == null || instances.length == 0) instances = new Object[] { instance };
Class objectClass= instances[0].getClass();
Method method= tm.getMethod();
// Only run the configuration if
// - the test is enabled and
// - the Configuration method belongs to the same class or a parent
if(MethodHelper.isEnabled(objectClass, m_annotationFinder)) {
configurationAnnotation= (IConfiguration)
AnnotationHelper.findConfiguration(m_annotationFinder, method);
boolean before= (null != configurationAnnotation)
? configurationAnnotation.getBeforeTestClass()
: false;
boolean after= (null != configurationAnnotation)
? configurationAnnotation.getAfterTestClass()
: false;
boolean alwaysRun= false;
if(null != configurationAnnotation) {
if ((configurationAnnotation.getAfterSuite()
|| configurationAnnotation.getAfterTest()
|| configurationAnnotation.getAfterTestClass()
|| configurationAnnotation.getAfterTestMethod())
&& configurationAnnotation.getAlwaysRun())
{
alwaysRun= true;
}
}
if(!confInvocationPassed(tm.getRealClass()) && !alwaysRun) {
testResult.setStatus(ITestResult.SKIP);
m_notifier.addSkippedTest(tm, testResult);
runTestListeners(testResult);
continue;
}
boolean isClassConfiguration= (null != configurationAnnotation) && (before || after);
log(3, "Invoking " + tm);
Object[] parameters= Parameters.createConfigurationParameters(tm.getMethod(),
params,
m_annotationFinder,
suite);
testResult.setParameters(parameters);
// Reporter.setCurrentOutput(tm.getExtraOutput().getOutput());
Object[] newInstances= (null != instance) ? new Object[] { instance } : instances;
invokeConfigurationMethod(newInstances, tm, parameters,
isClassConfiguration, testResult);
// Reporter.setCurrentOutput(null);
} // if is enabled
else {
log(3,
"Skipping "
+ method.getDeclaringClass().getName() + "." + method.getName() + "()"
+ " because "
+ objectClass.getName()
+ " is not enabled");
}
}
catch(InvocationTargetException ex) {
handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, suite);
}
// Don't wrap TestNGExceptions, it could be a missing parameter on a
// @Configuration method
catch(TestNGException ex) {
handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, suite);
Utils.log("", 1, ex.getMessage());
}
catch(Throwable ex) { // covers the non-wrapper exceptions
handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, suite);
}
} // for methods
}
private void handleConfigurationFailure(Throwable ite,
ITestNGMethod tm,
ITestResult testResult,
IConfiguration annotation,
XmlSuite suite)
{
handleException(ite.getCause(), tm, testResult, 1);
runTestListeners(testResult);
//
// 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) {
// If beforeTestClass/beforeTestMethod or afterTestClass/afterTestMethod
// failed, mark this entire class as failed, but only this class (the other
// classes should keep running normally)
if(annotation.getBeforeTestClass()
|| annotation.getAfterTestClass()
|| annotation.getBeforeTestMethod()
|| annotation.getAfterTestMethod())
{
setClassInvocationFailure(tm.getRealClass(), false);
}
// 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_suiteConfigurationFailed= true;
}
// beforeTest or afterTest: mark all the classes in the same
// <test> stanza as failed for configuration
else if (annotation.getBeforeTest() || annotation.getAfterTest()) {
XmlClass[] classes= findClassesInSameTest(tm.getRealClass(), suite);
for(XmlClass xmlClass : classes) {
setClassInvocationFailure(xmlClass.getSupportClass(), false);
}
}
}
else {
//
// If we're in JUnit mode, mark the entire class failed
//
String methodName = tm.getMethod().getName();
boolean isJUnit = "setUp".equals(methodName) ||
"tearDown".equals(methodName);
if (isJUnit) {
setClassInvocationFailure(tm.getRealClass(), false);
}
}
}
/**
* @return All the classes that belong to the same <test> tag as @param cls
*/
private XmlClass[] findClassesInSameTest(Class cls, XmlSuite suite) {
Map<String, XmlClass> vResult= new HashMap<String, XmlClass>();
String className= cls.getName();
for(XmlTest test : suite.getTests()) {
for(XmlClass testClass : test.getXmlClasses()) {
if(testClass.getName().equals(className)) {
// Found it, add all the classes in this test in the result
for(XmlClass thisClass : test.getXmlClasses()) {
vResult.put(thisClass.getName(), thisClass);
}
// Note: we need to iterate through the entire suite since the same
// class might appear in several <test> tags
}
}
}
XmlClass[] result= vResult.values().toArray(new XmlClass[vResult.size()]);
return result;
}
/**
* @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(Class cls) {
boolean result= true;
if(m_suiteConfigurationFailed) {
result= false;
}
else {
if(m_classInvocationResults.containsKey(cls)) {
result= m_classInvocationResults.get(cls);
}
else {
for(Class clazz: m_classInvocationResults.keySet()) {
if(clazz.isAssignableFrom(cls)) {
result= false;
break;
}
}
}
}
return result;
}
private Map<Class, Boolean> m_classInvocationResults= new HashMap<Class, Boolean>();
// True if at least a beforeSuite or afterSuit method failed
private boolean m_suiteConfigurationFailed= false;
private void setClassInvocationFailure(Class clazz, boolean flag) {
m_classInvocationResults.put(clazz, flag);
}
private void invokeConfigurationMethod(Object[] instances,
ITestNGMethod tm,
Object[] params,
boolean isClass,
ITestResult testResult)
throws InvocationTargetException, IllegalAccessException
{
// Mark this method with the current thread id
tm.setId(Thread.currentThread().hashCode());
long timeOut= tm.getTimeOut();
for(Object targetInstance : instances) {
InvokedMethod im= new InvokedMethod(targetInstance,
tm,
params,
false, /* isTest */
isClass, /* ??? */
System.currentTimeMillis());
m_notifier.addInvokedMethod(im);
try {
Reporter.setCurrentTestResult(testResult);
MethodHelper.invokeMethod(tm.getMethod(), targetInstance, params);
}
finally {
Reporter.setCurrentTestResult(testResult);
}
}
}
/**
* Both invokeTestMethod() and invokeConfigurationMethod() eventually
* converge here. This method is responsible for actually invoking the
* method. If a timeOut was specified for this test method, it will be invoked
* in a separate thread so that we can interrupt it when the timeOut expires.
*
*/
private List<ITestResult> invokeMethod(Object[] instances,
final ITestNGMethod tm,
Object[] parameterValues,
XmlSuite suite,
Map<String, String> params,
ITestClass testClass,
ITestNGMethod[] beforeMethods,
ITestNGMethod[] afterMethods,
ConfigurationGroupMethods groupMethods)
{
List<ITestResult> results = new ArrayList<ITestResult>();
Method thisMethod= null;
// Mark this method with the current thread id
tm.setId(Thread.currentThread().hashCode());
long timeOut= tm.getTimeOut();
for(int i= 0; i < instances.length; i++) {
//
// Invoke beforeGroups configurations
//
invokeBeforeGroupsConfigurations(testClass, tm, groupMethods, suite, params, instances[i]);
//
// Invoke beforeMethod configurations
//
invokeConfigurations(testClass, beforeMethods, suite, params, instances[i]);
//
// Create the ExtraOutput for this method
//
TestResult testResult = null;
try {
testResult= new TestResult(testClass, instances[i],
tm,
null,
System.currentTimeMillis(),
0);
testResult.setParameters(parameterValues);
testResult.setHost(m_testContext.getHost());
testResult.setStatus(ITestResult.STARTED);
runTestListeners(testResult);
results.add(testResult);
InvokedMethod invokedMethod= new InvokedMethod(instances[i],
tm,
parameterValues,
true,
false,
System.currentTimeMillis());
m_notifier.addInvokedMethod(invokedMethod);
thisMethod= tm.getMethod();
if(confInvocationPassed(thisMethod.getDeclaringClass())) {
log(3,
"Invoking " + thisMethod.getDeclaringClass().getName() + "." + thisMethod.getName());
// If no timeOut, just invoke the method
if(timeOut <= 0) {
//
// If this method is a IHookable, invoke its run() method
//
if (IHookable.class.isAssignableFrom(thisMethod.getDeclaringClass())) {
invokeHookable(instances, parameterValues, testClass, thisMethod, i, testResult);
}
//
// Not a IHookable, invoke directly
//
else {
try {
Reporter.setCurrentTestResult(testResult);
MethodHelper.invokeMethod(thisMethod, instances[i], parameterValues);
testResult.setStatus(ITestResult.SUCCESS);
}
finally {
Reporter.setCurrentTestResult(null);
}
}
}
else {
//
// Timeout was specified, use an Executor/CountDownLatch
//
ICountDown done= ThreadUtil.createCountDown(1);
IThreadFactory factory= ThreadUtil.createFactory(tm.getMethod().getName());
IExecutor exec= ThreadUtil.createExecutor(1, factory);
try {
InvokeMethodRunnable imr= new InvokeMethodRunnable(thisMethod,
instances[i],
parameterValues,
done);
IFutureResult future= exec.submitRunnable(imr);
exec.shutdown();
boolean finished= exec.awaitTermination(timeOut);
if(!finished) {
exec.stopNow();
testResult.setThrowable(new ThreadTimeoutException("Method "
+ thisMethod
+ " didn't finish within the time-out "
+ timeOut));
testResult.setStatus(ITestResult.FAILURE);
}
else {
log(3, "Method " + thisMethod + " completed within the time-out " + timeOut);
// We don't need the result from the future but invoking get() on it
// will trigger the exception that was thrown, if any
future.get();
done.await();
testResult.setStatus(ITestResult.SUCCESS); // if no exception till here than SUCCESS
}
}
catch(InterruptedException e) {
System.err.println("WARN: invocation of method " + thisMethod
+ " has been interrupted.");
}
}
}
else {
testResult.setStatus(ITestResult.SKIP);
}
}
catch(InvocationTargetException ite) {
testResult.setThrowable(ite.getCause());
}
catch(ThreadExecutionException tee) { // wrapper for TestNGRuntimeException
Throwable cause= tee.getCause();
if(TestNGRuntimeException.class.equals(cause.getClass())) {
testResult.setThrowable(cause.getCause());
}
else {
testResult.setThrowable(cause);
}
}
catch(Throwable thr) { // covers the non-wrapper exceptions
testResult.setThrowable(thr);
}
finally {
if (testResult != null) testResult.setEndMillis(System.currentTimeMillis());
//
// Invoke afterMethods
//
invokeConfigurations(testClass, afterMethods, suite, params, instances[i]);
//
// Invoke beforeGroups configurations
//
invokeAfterGroupsConfigurations(testClass, tm,
groupMethods, suite, params, instances[i]);
}
} // for instances
return results;
}
/**
* Filter all the beforeGroups methods and invoke only those that apply
* to the current test method
*/
private void invokeBeforeGroupsConfigurations(ITestClass testClass,
ITestNGMethod tm,
ConfigurationGroupMethods groupMethods,
XmlSuite suite, Map<String, String> params,
Object instance)
{
List<ITestNGMethod> filteredMethods = new ArrayList<ITestNGMethod>();
String[] groups = tm.getGroups();
Map<String, List<ITestNGMethod>> beforeGroupMap = groupMethods.getBeforeGroupsMap();
for (String group : groups) {
List<ITestNGMethod> methods = beforeGroupMap.get(group);
if (methods != null) {
filteredMethods.addAll(methods);
}
}
//
// Invoke the right groups methods
//
ITestNGMethod[] beforeMethodsArray =
filteredMethods.toArray(new ITestNGMethod[filteredMethods.size()]);
invokeConfigurations(testClass, beforeMethodsArray, suite, params, instance);
//
// Remove them so they don't get run again
//
groupMethods.removeBeforeGroups(groups);
}
private void invokeAfterGroupsConfigurations(ITestClass testClass,
ITestNGMethod currentTestMethod,
ConfigurationGroupMethods groupMethods,
XmlSuite suite, Map<String, String> params, Object instance)
{
// Skip this if no afterGroups have been defined
if (testClass.getAfterGroupsMethods().length == 0) return;
// Skip this if the current method doesn't belong to any group
// (only a method that belongs to a group can trigger the invocation
// of afterGroups methods)
if (currentTestMethod.getGroups().length == 0) return;
// See if the currentMethod is the last method in any of the groups
// it belongs to
Map<String, String> filteredGroups = new HashMap<String, String>();
String[] groups = currentTestMethod.getGroups();
for (String group : groups) {
if (groupMethods.isLastMethodForGroup(group, currentTestMethod)) {
filteredGroups.put(group, group);
}
}
// The list of afterMethods to run
Map<ITestNGMethod, ITestNGMethod> afterMethods =
new HashMap<ITestNGMethod, ITestNGMethod>();
// Now filteredGroups contains all the groups for which we need to run the afterGroups
// method. Find all the methods that correspond to these groups and invoke them.
Map<String, List<ITestNGMethod>> map = groupMethods.getAfterGroupsMap();
for (String g : filteredGroups.values()) {
List<ITestNGMethod> methods = map.get(g);
// Note: should put them in a map if we want to make sure the same afterGroups
// doesn't get run twice
if (methods != null) {
for (ITestNGMethod m : methods) {
afterMethods.put(m, m);
}
}
}
// Got our afterMethods, invoke them
ITestNGMethod[] afterMethodsArray =
afterMethods.keySet().toArray(new ITestNGMethod[afterMethods.size()]);
invokeConfigurations(testClass, afterMethodsArray, suite, params, instance);
// Remove the groups so they don't get run again
groupMethods.removeAfterGroups(filteredGroups.keySet());
}
private void invokeHookable(Object[] instances, Object[] parameters,
ITestClass testClass, Method thisMethod, int i, TestResult testResult)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, Throwable
{
Method runMethod = null;
runMethod = testClass.getRealClass().getMethod("run",
new Class[] { IHookCallBack.class, ITestResult.class });
final Method a = thisMethod;
final Object b = instances[i];
final Object[] c = parameters;
final Throwable[] error = new Throwable[1];
IHookCallBack callback = new IHookCallBack() {
public void runTestMethod(ITestResult tr) {
try {
MethodHelper.invokeMethod(a, b, c);
}
catch(Throwable t) {
error[0] = t;
}
}
};
runMethod.invoke(instances[i],new Object[]{callback, testResult});
if (error[0] != null) {
throw error[0];
}
testResult.setStatus(ITestResult.SUCCESS);
}
/**
* If the method has parameters, fill them in. Either by using a @DataProvider
* if any was provided, or by looking up <parameters> in testng.xml
*/
private Iterator<Object[]> handleParameters(ITestNGMethod testMethod,
Map<String, String> allParameterNames,
ITestClass testClass, Map<String, String> parameters, XmlSuite xmlSuite)
{
Iterator<Object[]> result = null;
//
// Do we have a @DataProvider? If yes, then we have several
// sets of parameters for this method
//
Method dataProvider = Parameters.findDataProvider(testMethod.getTestClass().getRealClass(),
testMethod.getMethod(),
m_annotationFinder);
if (null != dataProvider) {
int parameterCount = testMethod.getMethod().getParameterTypes().length;
for (int i = 0; i < parameterCount; i++) {
String n = "param" + i;
allParameterNames.put(n, n);
}
result = MethodHelper.invokeDataProvider(
testClass.getInstances(true)[0], /* a test instance */
dataProvider, testMethod.getMethod());
}
else {
//
// Normal case: we have only one set of parameters coming from testng.xml
//
allParameterNames.putAll(parameters);
// Create an Object[][] containing just one row of parameters
Object[][] allParameterValuesArray = new Object[1][];
allParameterValuesArray[0] =
Parameters.createTestParameters(testMethod.getMethod(),
parameters,
m_annotationFinder,
xmlSuite);
// Turn it into an Iterable
result = MethodHelper.createArrayIterator(allParameterValuesArray);
}
return result;
}
/**
* Invoke all the test methods. Note the plural: the method passed in
* parameter might be invoked several times if the test class it belongs
* to has more than one instance (i.e., if an @Factory method has been
* declared somewhere that returns several instances of this TestClass).
* If no @Factory method was specified, testMethod will only be invoked
* once.
* <p/>
* Note that this method also takes care of invoking the beforeTestMethod
* and afterTestMethod, if any.
*/
public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod,
XmlSuite suite,
Map<String, String> parameters,
ITestNGMethod[] allTestMethods,
int testMethodIndex,
ConfigurationGroupMethods groupMethods)
{
List<ITestResult> result = null;
ITestClass testClass= testMethod.getTestClass();
Method method= testMethod.getMethod();
// Potential bug here if the test method was declared on a parent class
assert null != testClass : "COULDN'T FIND TESTCLASS FOR " + method.getDeclaringClass();
long start= System.currentTimeMillis();
//
// Invoke before method methods
//
ITestNGMethod[] beforeMethods = filterMethods(testClass, testClass.getBeforeTestMethods());
ITestNGMethod[] afterMethods = filterMethods(testClass, testClass.getAfterTestMethods());
int invocationCount = testMethod.getInvocationCount();
if (isWithinThreadedMethod()) invocationCount = 1;
int failureCount = 0;
// This boolean is used to exit this loop early if we are using a pool
// thread. The reason is that in this case, all the invocationCount methods
// will be invoked at once in different threads
boolean more = true;
int threadPoolSize = testMethod.getThreadPoolSize();
Class[] expectedExceptionClasses =
findExpectedExceptions(m_annotationFinder, testMethod.getMethod());
while(invocationCount-- > 0 && more) {
boolean okToProceed = checkDependencies(testMethod, testClass, allTestMethods);
Object[] parameterValues = null;
if (okToProceed) {
//
// Invoke test method
//
Map<String, String> allParameterNames = new HashMap<String, String>();
Iterator<Object[]> allParameterValues =
handleParameters(testMethod, allParameterNames, testClass, parameters,
suite);
//
// Invoke the test method if it's enabled
//
if (MethodHelper.isEnabled(testMethod.getMethod(), m_annotationFinder)) {
Iterator<Object[]> it = allParameterValues;
while (it.hasNext()) {
parameterValues = it.next();
// Reporter.setCurrentOutput(testMethod.getExtraOutput().getOutput());
Object[] instances = testClass.getInstances(true);
//
// Special behavior for JUnit: call setName on the instance with
// the name of the method to be invoked
//
if (JUnitUtils.isAssignableFromTestCase(testClass.getRealClass())) {
String name = testMethod.getMethodName();
try {
Method m =
testClass.getRealClass().getMethod("setName", new Class[] { String.class });
for (Object instance : instances) {
m.invoke(instance, new Object[] { name });
}
}
catch (Exception e) {
e.printStackTrace();
}
} // JUnit
//
// If threadPoolSize specified, run this method in its own
// pool thread. The extra boolean is here to make sure
// we don't invoke the invoker recursively forever.
//
if (threadPoolSize > 1 && ! isWithinThreadedMethod()) {
//
// Create the workers
//
Map<ITestClass, ITestClass> beforeMethodsMap =
new HashMap<ITestClass, ITestClass>();
Map<ITestClass, ITestClass> afterMethodsMap =
new HashMap<ITestClass, ITestClass>();
List<TestMethodWorker> workers= new ArrayList<TestMethodWorker>();
ClassMethodMap classMethodMap = new ClassMethodMap(allTestMethods);
for (int i = 0; i < testMethod.getInvocationCount(); i++) {
workers.add(new TestMethodWorker(this,
new ITestNGMethod[] {testMethod},
suite, parameters,
beforeMethodsMap, afterMethodsMap,
allTestMethods,
groupMethods,
classMethodMap));
}
setWithinThreadedMethod(true);
result = runWorkers(testMethod, workers, threadPoolSize);
setWithinThreadedMethod(false);
more = false;
}
//
// No threads, regular invocation
//
else {
result = invokeMethod(instances,
testMethod,
parameterValues,
suite,
allParameterNames,
testClass,
beforeMethods,
afterMethods,
groupMethods);
}
//
// Go through all the results and create a TestResult for each of them
//
for(ITestResult testResult : result) {
Throwable ite= testResult.getThrowable();
int status= testResult.getStatus();
// Exception thrown?
if(ite != null) {
// Invocation caused an exception, see if the method was annotated with @ExpectedException
if(isExpectedException(ite, expectedExceptionClasses)) {
testResult.setStatus(ITestResult.SUCCESS);
status= ITestResult.SUCCESS;
}
else {
handleException(ite, testMethod, testResult, failureCount++);
status= testResult.getStatus();
}
}
// No exception thrown, make sure we weren't expecting one
else {
if (expectedExceptionClasses.length > 0) {
testResult.setThrowable(new TestException("Expected an exception in test method "
+ testMethod));
status= ITestResult.FAILURE;
}
}
testResult.setStatus(status);
// Collect the results
if(ITestResult.SUCCESS == status) {
m_notifier.addPassedTest(testMethod, testResult);
}
else if(ITestResult.SKIP == status) {
m_notifier.addSkippedTest(testMethod, testResult);
}
else if(ITestResult.FAILURE == status) {
m_notifier.addFailedTest(testMethod, testResult);
}
else if(ITestResult.SUCCESS_PERCENTAGE_FAILURE == status) {
m_notifier.addFailedButWithinSuccessPercentageTest(testMethod, testResult);
}
else {
assert false : "UNKNOWN STATUS:" + status;
}
if (! isWithinThreadedMethod()) {
runTestListeners(testResult);
}
} // isTestMethodEnabled
} // for
} // for parameters
} // okToProceed
else {
//
// Test is being skipped
//
ITestResult testResult= new TestResult(testClass, null,
testMethod,
null,
start,
System.currentTimeMillis());
testResult.setEndMillis(System.currentTimeMillis());
testResult.setParameters(parameterValues);
String missingGroup = testMethod.getMissingGroup();
if (missingGroup != null) {
testResult.setThrowable(
new Throwable("Method " + testMethod +
" depends on nonexistent group \"" + missingGroup + "\""));
}
testResult.setStatus(ITestResult.SKIP);
m_notifier.addSkippedTest(testMethod, testResult);
runTestListeners(testResult);
}
}
// Reporter.setCurrentOutput(null);
return result;
} // invokeTestMethod
/**
* Read the expected exceptions, if any (need to handle both the old and new
* syntax
*/
public static Class[] findExpectedExceptions(IAnnotationFinder finder, Method method) {
Class[] result = {};
IExpectedExceptions expectedExceptions=
(IExpectedExceptions) finder.findAnnotation(method,
IExpectedExceptions.class);
// Old syntax
if (expectedExceptions != null) {
result = expectedExceptions.getValue();
}
else {
// New syntax
ITest testAnnotation =
(ITest) finder.findAnnotation(method, ITest.class);
if (testAnnotation != null) {
Class[] ee = testAnnotation.getExpectedExceptions();
if (testAnnotation != null && ee.length > 0) {
result = ee;
}
}
}
return result;
}
private boolean m_withinThreadedMethod = false;
private boolean isWithinThreadedMethod() {
return m_withinThreadedMethod;
}
private void setWithinThreadedMethod(boolean f) {
m_withinThreadedMethod = f;
}
private List<ITestResult> runWorkers(ITestNGMethod testMethod,
List<TestMethodWorker> workers, int threadPoolSize)
{
long maxTimeOut= 10 * 1000; // 1 second
IPooledExecutor executor= ThreadUtil.createPooledExecutor(threadPoolSize);
for(TestMethodWorker tmw : workers) {
long mt= tmw.getMaxTimeOut();
if(mt > maxTimeOut) {
maxTimeOut= mt;
}
executor.execute(tmw);
}
try {
executor.shutdown();
log(3, "Waiting for termination, timeout:" + maxTimeOut);
executor.awaitTermination(maxTimeOut);
log(3, "Successful termination");
}
catch(InterruptedException e) {
e.printStackTrace();
}
//
// Collect all the TestResults
//
List<ITestResult> result = new ArrayList<ITestResult>();
for (TestMethodWorker tmw : workers) {
result.addAll(tmw.getTestResults());
}
return result;
}
/**
* @param testMethod
* @param testClass
* @return dependencies have been run successfully
*/
private boolean checkDependencies(ITestNGMethod testMethod,
ITestClass testClass, ITestNGMethod[] allTestMethods)
{
boolean result= true;
// If this method is marked alwaysRun, no need to check for its
// dependencies
if (testMethod.isAlwaysRun()) {
return true;
}
// Any missing group?
if (testMethod.getMissingGroup() != null) {
return false;
}
// If this method depends on groups, collect all the methods that
// belong to these groups and make sure they have been run successfully
if(dependsOnGroups(testMethod)) {
String[] groupsDependedUpon= testMethod.getGroupsDependedUpon();
// Get all the methods that belong to the group depended upon
for(int i= 0; i < groupsDependedUpon.length; i++) {
ITestNGMethod[] methods =
MethodHelper.findMethodsThatBelongToGroup(testMethod,
m_testContext.getAllTestMethods(),
groupsDependedUpon[i]);
result = result && haveBeenRunSuccessfully(methods);
// As soon as we detect a failure, return right away and let the caller
// know that the dependencies are not satisfied
// if (! result) {
// return false;
// }
}
} // depends on groups
// If this method depends on other methods, make sure all these other
// methods have been run successfully
if(dependsOnMethods(testMethod)) {
ITestNGMethod[] methods =
MethodHelper.findMethodsNamed(testMethod.getMethod().getName(),
allTestMethods,
testMethod.getMethodsDependedUpon());
result= result && haveBeenRunSuccessfully(methods);
}
return result;
}
/**
* @return true if all the methods have been run successfully
*/
private boolean haveBeenRunSuccessfully(ITestNGMethod[] methods) {
// Make sure the method has been run successfully
for(int j= 0; j < methods.length; j++) {
Set<ITestResult> results= m_notifier.getPassedTests(methods[j]);
// If no results were returned, then these tests didn't pass
if (results == null || results.size() == 0) 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) {
testResult.setThrowable(throwable);
int successPercentage= testMethod.getSuccessPercentage();
int invocationCount= testMethod.getInvocationCount();
float numberOfTestsThatCanFail= ((100 - successPercentage) * invocationCount) / 100;
if(failureCount < numberOfTestsThatCanFail) {
testResult.setStatus(ITestResult.SUCCESS_PERCENTAGE_FAILURE);
m_notifier.addFailedButWithinSuccessPercentageTest(testMethod, testResult);
}
else {
testResult.setStatus(ITestResult.FAILURE);
m_notifier.addFailedTest(testMethod, testResult);
}
}
/**
* @param ite The exception that was just thrown
* @param expectedExceptions The list of expected exceptions for this
* test method
* @return true if the exception that was just thrown is part of the
* expected exceptions
*/
private boolean isExpectedException(Throwable ite, Class[] exceptions) {
if(null == exceptions) {
return false;
}
Class realExceptionClass= ite.getClass();
for(int i= 0; i < exceptions.length; i++) {
if(exceptions[i].isAssignableFrom(realExceptionClass)) {
return true;
}
}
return false;
}
/**
* @return Only the ITestNGMethods applicable for this testClass
*/
private ITestNGMethod[] filterMethods(IClass testClass, ITestNGMethod[] methods) {
List<ITestNGMethod> vResult= new ArrayList<ITestNGMethod>();
for(ITestNGMethod tm : methods) {
if(tm.canRunFromClass(testClass)) {
log(9, "Keeping method " + tm + " for class " + testClass);
vResult.add(tm);
}
else {
log(9, "Filtering out method " + tm + " for class " + testClass);
}
}
ITestNGMethod[] result= vResult.toArray(new ITestNGMethod[vResult.size()]);
return result;
}
private ITestNGMethod[] filterMethodsUnique(IClass testClass, ITestNGMethod[] methods) {
if(null == testClass) {
return methods;
}
List<ITestNGMethod> vResult= new ArrayList<ITestNGMethod>();
for(ITestNGMethod tm : methods) {
if(null == testClass) {
testClass= tm.getTestClass();
}
if(tm.getTestClass().getName().equals(testClass.getName())) {
log(9, " Keeping method " + tm + " for class " + testClass);
vResult.add(tm);
}
else {
log(9, " Filtering out method " + tm + " for class " + testClass);
}
}
ITestNGMethod[] result= vResult.toArray(new ITestNGMethod[vResult.size()]);
return result;
}
/**
* @return true if this method depends on certain groups.
*/
private boolean dependsOnGroups(ITestNGMethod tm) {
String[] groups= tm.getGroupsDependedUpon();
boolean result= (null != groups) && (groups.length > 0);
return result;
}
/**
* @return true if this method depends on certain groups.
*/
private boolean dependsOnMethods(ITestNGMethod tm) {
String[] methods= tm.getMethodsDependedUpon();
boolean result= (null != methods) && (methods.length > 0);
return result;
}
public void runTestListeners(ITestResult tr) {
runTestListeners(tr, m_notifier.getTestListeners());
}
public static void runTestListeners(ITestResult tr, List<ITestListener> listeners) {
for (ITestListener itl : listeners) {
switch(tr.getStatus()) {
case ITestResult.SKIP: {
itl.onTestSkipped(tr);
break;
}
case ITestResult.SUCCESS_PERCENTAGE_FAILURE: {
itl.onTestFailedButWithinSuccessPercentage(tr);
break;
}
case ITestResult.FAILURE: {
itl.onTestFailure(tr);
break;
}
case ITestResult.SUCCESS: {
itl.onTestSuccess(tr);
break;
}
case ITestResult.STARTED: {
itl.onTestStart(tr);
break;
}
default: {
assert false : "UNKNOWN STATUS:" + tr;
}
}
}
}
private static void ppp(String s) {
System.out.println("[Invoker]" + s);
}
private void log(int level, String s) {
Utils.log("Invoker " + Thread.currentThread().hashCode(), level, s);
}
}