Testng 测试框架源码阅读(二)

TestNG 测试框架详解
本文深入剖析了 TestNG 测试框架的内部运行机制,详细介绍了其核心组件 Surefire、SuiteRunner、TestMethodWorker 和 Invoker 的工作流程。此外,还探讨了配置方法的执行时机、并行测试执行策略及异常处理机制。

surefire接着调入testng中的方法,testNG.run -> runSuites -> runSutiesLocally -> runSuitesSequentially

-> SuiteRunnerWorker.run -> runSuites

-> SuiteRunner.run -> privateRun -> invokeTestMethods 

->testng.internal.TestMethodWorker.run -> invokeTestMethods

->testng.internal.Invoker.invokeTestMethods -> invokeMethod

->testng.internal.MethodInvocationHelper.invokeMethod

前面源码贴到了SuiteRunner,接下来TestMethodWorker类:

@Override
  public void run() {
    for (IMethodInstance testMthdInst : m_methodInstances) {
      ITestNGMethod testMethod = testMthdInst.getMethod();
      ITestClass testClass = testMethod.getTestClass();

      invokeBeforeClassMethods(testClass, testMthdInst);

      // Invoke test method
      try {
        invokeTestMethods(testMethod, testMthdInst.getInstance(), m_testContext);
      }
      finally {
        invokeAfterClassMethods(testClass, testMthdInst);
      }
    }
  }

  protected void invokeTestMethods(ITestNGMethod tm, Object instance,
      ITestContext testContext)
  {
    // Potential bug here:  we look up the method index of tm among all
    // the test methods (not very efficient) but if this method appears
    // several times and these methods are run in parallel, the results
    // are unpredictable...  Need to think about this more (and make it
    // more efficient)
    List<ITestResult> testResults =
        m_invoker.invokeTestMethods(tm,
            m_suite,
            m_parameters,
            m_groupMethods,
            instance,
            testContext);

    if (testResults != null) {
      m_testResults.addAll(testResults);
    }
  }
还有其他两个关键方法:

protected void invokeBeforeClassMethods(ITestClass testClass, IMethodInstance mi) {
    // if no BeforeClass than return immediately
    // used for parallel case when BeforeClass were already invoked
    if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedBeforeClassMethods())) {
      return;
    }
    ITestNGMethod[] classMethods= testClass.getBeforeClassMethods();
    if(null == classMethods || classMethods.length == 0) {
      return;
    }

    // the whole invocation must be synchronized as other threads must
    // get a full initialized test object (not the same for @After)
    Map<ITestClass, Set<Object>> invokedBeforeClassMethods =
        m_classMethodMap.getInvokedBeforeClassMethods();
//    System.out.println("SYNCHRONIZING ON " + testClass
//        + " thread:" + Thread.currentThread().getId()
//        + " invokedMap:" + invokedBeforeClassMethods.hashCode() + " "
//        + invokedBeforeClassMethods);
    synchronized(testClass) {
      Set<Object> instances= invokedBeforeClassMethods.get(testClass);
      if(null == instances) {
        instances= new HashSet<Object>();
        invokedBeforeClassMethods.put(testClass, instances);
      }
      for(Object instance: mi.getInstances()) {
        if (! instances.contains(instance)) {
          instances.add(instance);
          m_invoker.invokeConfigurations(testClass,
                                         testClass.getBeforeClassMethods(),
                                         m_suite,
                                         m_parameters,
                                         null, /* no parameter values */
                                         instance);
        }
      }
    }
  }

  /**
   * Invoke the @AfterClass methods if not done already
   * @param testClass
   * @param mi
   */
  protected void invokeAfterClassMethods(ITestClass testClass, IMethodInstance mi) {
    // if no BeforeClass than return immediately
    // used for parallel case when BeforeClass were already invoked
    if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedAfterClassMethods()) ) {
      return;
    }
    ITestNGMethod[] afterClassMethods= testClass.getAfterClassMethods();

    if(null == afterClassMethods || afterClassMethods.length == 0) {
      return;
    }

    //
    // Invoke after class methods if this test method is the last one
    //
    List<Object> invokeInstances= Lists.newArrayList();
    ITestNGMethod tm= mi.getMethod();
    if (m_classMethodMap.removeAndCheckIfLast(tm, mi.getInstance())) {
      Map<ITestClass, Set<Object>> invokedAfterClassMethods
          = m_classMethodMap.getInvokedAfterClassMethods();
      synchronized(invokedAfterClassMethods) {
        Set<Object> instances = invokedAfterClassMethods.get(testClass);
        if(null == instances) {
          instances= new HashSet<Object>();
          invokedAfterClassMethods.put(testClass, instances);
        }
        for(Object inst: mi.getInstances()) {
          if(! instances.contains(inst)) {
            invokeInstances.add(inst);
          }
        }
      }

      for(Object inst: invokeInstances) {
        m_invoker.invokeConfigurations(testClass,
                                       afterClassMethods,
                                       m_suite,
                                       m_parameters,
                                       null, /* no parameter values */
                                       inst);
      }
    }
  }
Invoker类代码:

@Override
  public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod,
                                             XmlSuite suite,
                                             Map<String, String> testParameters,
                                             ConfigurationGroupMethods groupMethods,
                                             Object instance,
                                             ITestContext testContext)
  {
    // Potential bug here if the test method was declared on a parent class
    assert null != testMethod.getTestClass()
        : "COULDN'T FIND TESTCLASS FOR " + testMethod.getRealClass();

    if (!MethodHelper.isEnabled(testMethod.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
    final String okToProceed = checkDependencies(testMethod, testContext.getAllTestMethods());

    if (okToProceed != null) {
      //
      // Not okToProceed. Test is being skipped
      //
      ITestResult result = registerSkippedTestResult(testMethod, null, System.currentTimeMillis(),
          new Throwable(okToProceed));
      m_notifier.addSkippedTest(testMethod, result);
      return Collections.singletonList(result);
    }


    final Map<String, String> 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();

    ExpectedExceptionsHolder expectedExceptionHolder =
        MethodHelper.findExpectedExceptions(m_annotationFinder, testMethod.getMethod());
    final ITestClass testClass= testMethod.getTestClass();
    final List<ITestResult> result = Lists.newArrayList();
    final FailureContext failure = new FailureContext();
    final ITestNGMethod[] beforeMethods = filterMethods(testClass, testClass.getBeforeTestMethods(), CAN_RUN_FROM_CLASS);
    final ITestNGMethod[] afterMethods = filterMethods(testClass, testClass.getAfterTestMethods(), CAN_RUN_FROM_CLASS);
    while(invocationCount-- > 0) {
      if(false) {
        // Prevent code formatting
      }
      //
      // No threads, regular invocation
      //
      else {
        // Used in catch statement
        long start = System.currentTimeMillis();

        Map<String, String> allParameterNames = Maps.newHashMap();
        ParameterBag bag = createParameters(testMethod,
            parameters, allParameterNames, suite, testContext, instance);

        if (bag.hasErrors()) {
          final ITestResult tr = bag.errorResult;
          tr.setStatus(ITestResult.SKIP);
          runTestListeners(tr);
          m_notifier.addSkippedTest(testMethod, tr);
          result.add(tr);
          continue;
        }

        Iterator<Object[]> allParameterValues = bag.parameterHolder.parameters;
        int parametersIndex = 0;

        try {
          List<TestMethodWithDataProviderMethodWorker> workers = Lists.newArrayList();

          if (bag.parameterHolder.origin == ParameterOrigin.ORIGIN_DATA_PROVIDER &&
              bag.parameterHolder.dataProviderHolder.annotation.isParallel()) {
            while (allParameterValues.hasNext()) {
              Object[] parameterValues = injectParameters(allParameterValues.next(),
                  testMethod.getMethod(), testContext, null /* test result */);
              TestMethodWithDataProviderMethodWorker w =
                new TestMethodWithDataProviderMethodWorker(this,
                    testMethod, parametersIndex,
                    parameterValues, instance, suite, parameters, testClass,
                    beforeMethods, afterMethods, groupMethods,
                    expectedExceptionHolder, testContext, m_skipFailedInvocationCounts,
                    invocationCount, failure.count, m_notifier);
              workers.add(w);
              // testng387: increment the param index in the bag.
              parametersIndex++;
            }
            PoolService<List<ITestResult>> ps =
                new PoolService<List<ITestResult>>(suite.getDataProviderThreadCount());
            List<List<ITestResult>> r = ps.submitTasksAndWait(workers);
            for (List<ITestResult> l2 : r) {
              result.addAll(l2);
            }

          } else {
            while (allParameterValues.hasNext()) {
              Object[] parameterValues = injectParameters(allParameterValues.next(),
                  testMethod.getMethod(), testContext, null /* test result */);

              List<ITestResult> tmpResults = Lists.newArrayList();

              try {
                tmpResults.add(invokeTestMethod(instance,
                    testMethod,
                    parameterValues,
                    parametersIndex,
                    suite,
                    parameters,
                    testClass,
                    beforeMethods,
                    afterMethods,
                    groupMethods, failure));
              }
              finally {
                if (failure.instances.isEmpty()) {
                  result.addAll(tmpResults);
                } else {
                  for (Object failedInstance : failure.instances) {
                    List<ITestResult> retryResults = Lists.newArrayList();

                    failure.count = retryFailed(
                            failedInstance, testMethod, suite, testClass, beforeMethods,
                     afterMethods, groupMethods, retryResults,
                     failure.count, expectedExceptionHolder,
                     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) {
                    result.add(registerSkippedTestResult(testMethod, instance, System.currentTimeMillis(), null));
                  }
                  break;
                }
              }// end finally
              parametersIndex++;
            }
          }
        }
        catch (Throwable cause) {
          ITestResult r =
              new TestResult(testMethod.getTestClass(),
                instance,
                testMethod,
                cause,
                start,
                System.currentTimeMillis(),
                m_testContext);
            r.setStatus(TestResult.FAILURE);
            result.add(r);
            runTestListeners(r);
            m_notifier.addFailedTest(testMethod, r);
        } // catch
      }
    }

    return result;

  } // invokeTestMethod

  private ITestResult registerSkippedTestResult(ITestNGMethod testMethod, Object instance,
      long start, Throwable throwable) {
    ITestResult result =
      new TestResult(testMethod.getTestClass(),
        instance,
        testMethod,
        throwable,
        start,
        System.currentTimeMillis(),
        m_testContext);
    result.setStatus(TestResult.SKIP);
    runTestListeners(result);

    return result;
  }

  /**
   * Gets an array of parameter values returned by data provider or the ones that
   * are injected based on parameter type. The method also checks for {@code NoInjection}
   * annotation
   * @param parameterValues parameter values from a data provider
   * @param method method to be invoked
   * @param context test context
   * @param testResult test result
   * @return
   */
  private Object[] injectParameters(Object[] parameterValues, Method method,
      ITestContext context, ITestResult testResult)
    throws TestNGException {
    List<Object> vResult = Lists.newArrayList();
    int i = 0;
    int numValues = parameterValues.length;
    int numParams = method.getParameterTypes().length;

    if (numValues > numParams && ! method.isVarArgs()) {
      throw new TestNGException("The data provider is trying to pass " + numValues
          + " parameters but the method "
          + method.getDeclaringClass().getName() + "#" + method.getName()
          + " takes " + numParams);
    }

    // beyond this, numValues <= numParams
    for (Class<?> cls : method.getParameterTypes()) {
      Annotation[] annotations = method.getParameterAnnotations()[i];
      boolean noInjection = false;
      for (Annotation a : annotations) {
        if (a instanceof NoInjection) {
          noInjection = true;
          break;
        }
      }
      Object injected = Parameters.getInjectedParameter(cls, method, context, testResult);
      if (injected != null && ! noInjection) {
        vResult.add(injected);
      } else {
        try {
          if (method.isVarArgs()) vResult.add(parameterValues);
          else vResult.add(parameterValues[i++]);
        } catch (ArrayIndexOutOfBoundsException ex) {
          throw new TestNGException("The data provider is trying to pass " + numValues
              + " parameters but the method "
              + method.getDeclaringClass().getName() + "#" + method.getName()
              + " takes " + numParams
              + " and TestNG is unable in inject a suitable object", ex);
        }
      }
    }
    return vResult.toArray(new Object[vResult.size()]);
  }

  private ParameterBag handleParameters(ITestNGMethod testMethod,
      Object instance,
      Map<String, String> allParameterNames,
      Map<String, String> parameters,
      Object[] parameterValues,
      XmlSuite suite,
      ITestContext testContext,
      Object fedInstance,
      ITestResult testResult)
  {
    try {
      return new ParameterBag(
          Parameters.handleParameters(testMethod,
            allParameterNames,
            instance,
            new Parameters.MethodParameters(parameters,
                testMethod.findMethodParameters(testContext.getCurrentXmlTest()),
                parameterValues,
                testMethod.getMethod(), testContext, testResult),
            suite,
            m_annotationFinder,
            fedInstance));
    }
//    catch(TestNGException ex) {
//      throw ex;
//    }
    catch(Throwable cause) {
      return new ParameterBag(
          new TestResult(
              testMethod.getTestClass(),
              instance,
              testMethod,
              cause,
              System.currentTimeMillis(),
              System.currentTimeMillis(),
              m_testContext));
    }
  }

  /**
   * Invokes a method that has a specified threadPoolSize.
   */
  private List<ITestResult> invokePooledTestMethods(ITestNGMethod testMethod,
                                                    XmlSuite suite,
                                                    Map<String, String> parameters,
                                                    ConfigurationGroupMethods groupMethods,
                                                    ITestContext testContext)
  {
    //
    // Create the workers
    //
    List<IWorker<ITestNGMethod>> 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,
          suite,
          parameters,
          testContext));
    }

    return runWorkers(testMethod, workers, testMethod.getThreadPoolSize(), groupMethods, suite, parameters);
  }

  static class FailureContext {
    int count = 0;
    List<Object> instances = Lists.newArrayList();
  }

  /**
   * @param testMethod
   * @param result
   * @param expectedExceptionsHolder
   * @param failure
   * @return
   */
  void handleInvocationResults(ITestNGMethod testMethod,
                               List<ITestResult> result,
                               ExpectedExceptionsHolder expectedExceptionsHolder,
                               boolean triggerListeners,
                               boolean collectResults,
                               FailureContext failure)
  {
    //
    // Go through all the results and create a TestResult for each of them
    //
    List<ITestResult> resultsToRetry = Lists.newArrayList();

    for (ITestResult testResult : result) {
      Throwable ite= testResult.getThrowable();
      int status= testResult.getStatus();

      boolean handled = false;

      // Exception thrown?
      if (ite != null) {

        //  Invocation caused an exception, see if the method was annotated with @ExpectedException
        if (isExpectedException(ite, expectedExceptionsHolder)) {
          if (messageRegExpMatches(expectedExceptionsHolder.messageRegExp, ite)) {
            testResult.setStatus(ITestResult.SUCCESS);
            status= ITestResult.SUCCESS;
          }
          else {
            testResult.setThrowable(
                new TestException("The exception was thrown with the wrong message:" +
                    " expected \"" + expectedExceptionsHolder.messageRegExp + "\"" +
                    " but got \"" + ite.getMessage() + "\"", ite));
            status= ITestResult.FAILURE;
          }
        } else if (isSkipExceptionAndSkip(ite)){
          status = ITestResult.SKIP;
        } else if (expectedExceptionsHolder != null) {
          testResult.setThrowable(
              new TestException("Expected exception of " +
                  getExpectedExceptionsPluralize(expectedExceptionsHolder)
                  + " but got " + ite, ite));
          status= ITestResult.FAILURE;
        } else {
          handleException(ite, testMethod, testResult, failure.count++);
          handled = true;
          status = testResult.getStatus();
        }
      }

      // No exception thrown, make sure we weren't expecting one
      else if(status != ITestResult.SKIP && expectedExceptionsHolder != null) {
        Class<?>[] classes = expectedExceptionsHolder.expectedClasses;
        if (classes != null && classes.length > 0) {
          testResult.setThrowable(
              new TestException("Method " + testMethod + " should have thrown an exception of "
                  + getExpectedExceptionsPluralize(expectedExceptionsHolder)));
          status= ITestResult.FAILURE;
        }
      }

      testResult.setStatus(status);

      if (status == ITestResult.FAILURE && !handled) {
        handleException(ite, testMethod, testResult, failure.count++);
        status = testResult.getStatus();
      }

      if (status == ITestResult.FAILURE) {
        IRetryAnalyzer retryAnalyzer = testMethod.getRetryAnalyzer();

        if (retryAnalyzer != null &&  failure.instances != null && retryAnalyzer.retry(testResult)) {
          resultsToRetry.add(testResult);
          failure.instances.add(testResult.getInstance());
        }
      }
      if (collectResults) {
        // Collect the results
        collectResults(testMethod, Collections.singleton(testResult));
//        if (triggerListeners && status != ITestResult.SUCCESS) {
//          runTestListeners(testResult);
//        }
      }
    } // for results

    removeResultsToRetryFromResult(resultsToRetry, result, failure);
  }

  private String getExpectedExceptionsPluralize(final ExpectedExceptionsHolder holder) {
    StringBuilder sb = new StringBuilder();
    if (holder.expectedClasses.length > 1) {
      sb.append("any of types ");
      sb.append(Arrays.toString(holder.expectedClasses));
    } else {
      sb.append("type ");
      sb.append(holder.expectedClasses[0]);
    }
    return sb.toString();
  }
Invoker类代码较多,还有:

 private void invokeConfigurations(IClass testClass,
                                   ITestNGMethod currentTestMethod,
                                   ITestNGMethod[] allMethods,
                                   XmlSuite suite,
                                   Map<String, String> params,
                                   Object[] parameterValues,
                                   Object instance,
                                   ITestResult testMethodResult)
  {
    if(null == allMethods) {
      log(5, "No configuration methods found");

      return;
    }

    ITestNGMethod[] methods= filterMethods(testClass, allMethods, SAME_CLASS);

    for(ITestNGMethod tm : methods) {
      if(null == testClass) {
        testClass= tm.getTestClass();
      }

      ITestResult testResult= new TestResult(testClass,
                                             instance,
                                             tm,
                                             null,
                                             System.currentTimeMillis(),
                                             System.currentTimeMillis(),
                                             m_testContext);

      IConfigurationAnnotation configurationAnnotation= null;
      try {
        Object inst = tm.getInstance();
        if (inst == null) {
          inst = instance;
        }
        Class<?> objectClass= inst.getClass();
        Method method= tm.getMethod();

        // Only run the configuration if
        // - the test is enabled and
        // - the Configuration method belongs to the same class or a parent
        if(MethodHelper.isEnabled(objectClass, m_annotationFinder)) {
          configurationAnnotation = AnnotationHelper.findConfiguration(m_annotationFinder, method);

          if (MethodHelper.isEnabled(configurationAnnotation)) {
            boolean alwaysRun= isAlwaysRun(configurationAnnotation);

            if (!confInvocationPassed(tm, currentTestMethod, testClass, instance) && !alwaysRun) {
              handleConfigurationSkip(tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
              continue;
            }

            log(3, "Invoking " + Utils.detailedMethodName(tm, true));

            Object[] parameters = Parameters.createConfigurationParameters(tm.getMethod(),
                params,
                parameterValues,
                currentTestMethod,
                m_annotationFinder,
                suite,
                m_testContext,
                testMethodResult);
            testResult.setParameters(parameters);

            Object newInstance = null != instance ? instance: inst;

            runConfigurationListeners(testResult, true /* before */);

            invokeConfigurationMethod(newInstance, tm,
              parameters, testResult);

            // TODO: probably we should trigger the event for each instance???
            testResult.setEndMillis(System.currentTimeMillis());
            runConfigurationListeners(testResult, false /* after */);
          }
          else {
            log(3,
                "Skipping "
                + Utils.detailedMethodName(tm, true)
                + " because it is not enabled");
          }
        } // if is enabled
        else {
          log(3,
              "Skipping "
              + Utils.detailedMethodName(tm, true)
              + " because "
              + objectClass.getName()
              + " is not enabled");
        }
      }
      catch(InvocationTargetException ex) {
        handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
      }
      catch(TestNGException ex) {
        // Don't wrap TestNGExceptions, it could be a missing parameter on a
        // @Configuration method
        handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
      }
      catch(Throwable ex) { // covers the non-wrapper exceptions
        handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
      }
    } // for methods
  }

  /**
   * Marks the current <code>TestResult</code> as skipped and invokes the listeners.
   */
  private void handleConfigurationSkip(ITestNGMethod tm,
                                       ITestResult testResult,
                                       IConfigurationAnnotation annotation,
                                       ITestNGMethod currentTestMethod,
                                       Object instance,
                                       XmlSuite suite) {
    recordConfigurationInvocationFailed(tm, testResult.getTestClass(), annotation, currentTestMethod, instance, suite);
    testResult.setStatus(ITestResult.SKIP);
    runConfigurationListeners(testResult, false /* after */);
  }

  /**
   * Is the <code>IConfiguration</code> marked as alwaysRun.
   */
  private boolean isAlwaysRun(IConfigurationAnnotation configurationAnnotation) {
    if(null == configurationAnnotation) {
      return false;
    }

    boolean alwaysRun= false;
    if ((configurationAnnotation.getAfterSuite()
        || configurationAnnotation.getAfterTest()
        || configurationAnnotation.getAfterTestClass()
        || configurationAnnotation.getAfterTestMethod())
        && configurationAnnotation.getAlwaysRun())
    {
        alwaysRun= true;
    }

    return alwaysRun;
  }

  private void handleConfigurationFailure(Throwable ite,
                                          ITestNGMethod tm,
                                          ITestResult testResult,
                                          IConfigurationAnnotation annotation,
                                          ITestNGMethod currentTestMethod,
                                          Object instance,
                                          XmlSuite suite)
  {
    Throwable cause= ite.getCause() != null ? ite.getCause() : ite;

    if(isSkipExceptionAndSkip(cause)) {
      testResult.setThrowable(cause);
      handleConfigurationSkip(tm, testResult, annotation, currentTestMethod, instance, suite);
      return;
    }
    Utils.log("", 3, "Failed to invoke configuration method "
        + tm.getRealClass().getName() + "." + tm.getMethodName() + ":" + cause.getMessage());
    handleException(cause, tm, testResult, 1);
    runConfigurationListeners(testResult, false /* after */);

    //
    // If in TestNG mode, need to take a look at the annotation to figure out
    // what kind of @Configuration method we're dealing with
    //
    if (null != annotation) {
      recordConfigurationInvocationFailed(tm, testResult.getTestClass(), annotation, currentTestMethod, instance, suite);
    }
  }

  /**
   * @return All the classes that belong to the same <test> tag as @param cls
   */
  private XmlClass[] findClassesInSameTest(Class<?> cls, XmlSuite suite) {
    Map<String, XmlClass> vResult= Maps.newHashMap();
    String className= cls.getName();
    for(XmlTest test : suite.getTests()) {
      for(XmlClass testClass : test.getXmlClasses()) {
        if(testClass.getName().equals(className)) {

          // Found it, add all the classes in this test in the result
          for(XmlClass thisClass : test.getXmlClasses()) {
            vResult.put(thisClass.getName(), thisClass);
          }
          // Note:  we need to iterate through the entire suite since the same
          // class might appear in several <test> tags
        }
      }
    }

    XmlClass[] result= vResult.values().toArray(new XmlClass[vResult.size()]);

    return result;
  }

  /**
   * Record internally the failure of a Configuration, so that we can determine
   * later if @Test should be skipped.
   */
  private void recordConfigurationInvocationFailed(ITestNGMethod tm,
                                                   IClass testClass,
                                                   IConfigurationAnnotation annotation,
                                                   ITestNGMethod currentTestMethod,
                                                   Object instance,
                                                   XmlSuite suite) {
    // If beforeTestClass or afterTestClass failed, mark either the config method's
    // entire class as failed, or the class under tests as failed, depending on
    // the configuration failure policy
    if (annotation.getBeforeTestClass() || annotation.getAfterTestClass()) {
      // tm is the configuration method, and currentTestMethod is null for BeforeClass
      // methods, so we need testClass
      if (m_continueOnFailedConfiguration) {
        setClassInvocationFailure(testClass.getRealClass(), instance);
      } else {
        setClassInvocationFailure(tm.getRealClass(), instance);
      }
    }

    // If before/afterTestMethod failed, mark either the config method's entire
    // class as failed, or just the current test method as failed, depending on
    // the configuration failure policy
    else if (annotation.getBeforeTestMethod() || annotation.getAfterTestMethod()) {
      if (m_continueOnFailedConfiguration) {
        setMethodInvocationFailure(currentTestMethod, instance);
      } else {
        setClassInvocationFailure(tm.getRealClass(), instance);
      }
    }

    // If beforeSuite or afterSuite failed, mark *all* the classes as failed
    // for configurations.  At this point, the entire Suite is screwed
    else if (annotation.getBeforeSuite() || annotation.getAfterSuite()) {
      m_suiteState.failed();
    }

    // beforeTest or afterTest:  mark all the classes in the same
    // <test> stanza as failed for configuration
    else if (annotation.getBeforeTest() || annotation.getAfterTest()) {
      setClassInvocationFailure(tm.getRealClass(), instance);
      XmlClass[] classes= findClassesInSameTest(tm.getRealClass(), suite);
      for(XmlClass xmlClass : classes) {
        setClassInvocationFailure(xmlClass.getSupportClass(), instance);
      }
    }
    String[] beforeGroups= annotation.getBeforeGroups();
    if(null != beforeGroups && beforeGroups.length > 0) {
      for(String group: beforeGroups) {
        m_beforegroupsFailures.put(group, Boolean.FALSE);
      }
    }
  }

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,
                                          params,
                                          System.currentTimeMillis(),
                                          testResult);

      runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult);
      m_notifier.addInvokedMethod(invokedMethod);
      try {
        Reporter.setCurrentTestResult(testResult);
        Method method = tm.getMethod();

        //
        // If this method is a IConfigurable, invoke its run() method
        //
        IConfigurable configurableInstance =
          IConfigurable.class.isAssignableFrom(tm.getMethod().getDeclaringClass()) ?
          (IConfigurable) targetInstance : m_configuration.getConfigurable();
        if (configurableInstance != null) {
          MethodInvocationHelper.invokeConfigurable(targetInstance, params, configurableInstance, method,
              testResult);
        }
        else {
          //
          // Not a IConfigurable, invoke directly
          //
          if (MethodHelper.calculateTimeOut(tm) <= 0) {
            MethodInvocationHelper.invokeMethod(method, targetInstance, params);
          }
          else {
            MethodInvocationHelper.invokeWithTimeout(tm, targetInstance, params, testResult);
            if (!testResult.isSuccess()) {
              // A time out happened
              throwConfigurationFailure(testResult, testResult.getThrowable());
              throw testResult.getThrowable();
            }
          }
        }
      }
      catch (InvocationTargetException ex) {
       throwConfigurationFailure(testResult, ex);
       throw ex;
      }
      catch (IllegalAccessException ex) {
        throwConfigurationFailure(testResult, ex);
        throw ex;
      }
      catch (NoSuchMethodException ex) {
        throwConfigurationFailure(testResult, ex);
        throw new TestNGException(ex);
      }
      catch (Throwable ex) {
        throwConfigurationFailure(testResult, ex);
        throw new TestNGException(ex);
      }
      finally {
        Reporter.setCurrentTestResult(testResult);
        runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult);
        Reporter.setCurrentTestResult(null);
      }
    }
  }

  private void throwConfigurationFailure(ITestResult testResult, Throwable ex)
  {
    testResult.setStatus(ITestResult.FAILURE);;
    testResult.setThrowable(ex.getCause() == null ? ex : ex.getCause());
  }

  private void runInvokedMethodListeners(InvokedMethodListenerMethod listenerMethod, IInvokedMethod invokedMethod,
      ITestResult testResult)
  {
    if ( noListenersPresent() ) {
      return;
    }

    InvokedMethodListenerInvoker invoker = new InvokedMethodListenerInvoker(listenerMethod, testResult, m_testContext);
    for (IInvokedMethodListener currentListener : m_invokedMethodListeners) {
      invoker.invokeListener(currentListener, invokedMethod);
    }
  }

  private boolean noListenersPresent() {
    return (m_invokedMethodListeners == null) || (m_invokedMethodListeners.size() == 0);
  }

  // pass both paramValues and paramIndex to be thread safe in case parallel=true + dataprovider.
  private ITestResult invokeMethod(Object instance,
                                   final ITestNGMethod tm,
                                   Object[] parameterValues,
                                   int parametersIndex,
                                   XmlSuite suite,
                                   Map<String, String> params,
                                   ITestClass testClass,
                                   ITestNGMethod[] beforeMethods,
                                   ITestNGMethod[] afterMethods,
                                   ConfigurationGroupMethods groupMethods,
                                   FailureContext failureContext) {
    TestResult testResult = new TestResult();

    //
    // Invoke beforeGroups configurations
    //
    invokeBeforeGroupsConfigurations(testClass, tm, groupMethods, suite, params,
        instance);

    //
    // Invoke beforeMethods only if
    // - firstTimeOnly is not set
    // - firstTimeOnly is set, and we are reaching at the first invocationCount
    //
    invokeConfigurations(testClass, tm,
      filterConfigurationMethods(tm, beforeMethods, true /* beforeMethods */),
      suite, params, parameterValues,
      instance, testResult);

    //
    // Create the ExtraOutput for this method
    //
    InvokedMethod invokedMethod = null;
    try {
      testResult.init(testClass, instance,
                                 tm,
                                 null,
                                 System.currentTimeMillis(),
                                 0,
                                 m_testContext);
      testResult.setParameters(parameterValues);
      testResult.setHost(m_testContext.getHost());
      testResult.setStatus(ITestResult.STARTED);

      invokedMethod= new InvokedMethod(instance,
          tm,
          parameterValues,
          System.currentTimeMillis(),
          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
      runTestListeners(testResult);

      runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult);

      m_notifier.addInvokedMethod(invokedMethod);

      Method thisMethod = tm.getConstructorOrMethod().getMethod();

      if(confInvocationPassed(tm, tm, testClass, instance)) {
        log(3, "Invoking " + tm.getRealClass().getName() + "." + tm.getMethodName());

        // If no timeOut, just invoke the method
        if (MethodHelper.calculateTimeOut(tm) <= 0) {
          Reporter.setCurrentTestResult(testResult);
          //
          // If this method is a IHookable, invoke its run() method
          //
          IHookable hookableInstance =
              IHookable.class.isAssignableFrom(tm.getRealClass()) ?
            (IHookable) instance : m_configuration.getHookable();
          if (hookableInstance != null) {
            MethodInvocationHelper.invokeHookable(instance,
                parameterValues, hookableInstance, thisMethod, testResult);
          }
          //
          // Not a IHookable, invoke directly
          //
          else {
            MethodInvocationHelper.invokeMethod(thisMethod, instance,
                parameterValues);
          }
          testResult.setStatus(ITestResult.SUCCESS);
        }
        else {
          //
          // Method with a timeout
          //
          Reporter.setCurrentTestResult(testResult);
          MethodInvocationHelper.invokeWithTimeout(tm, instance, parameterValues, testResult);
        }
      }
      else {
        testResult.setStatus(ITestResult.SKIP);
      }
    }
    catch(InvocationTargetException ite) {
      testResult.setThrowable(ite.getCause());
      testResult.setStatus(ITestResult.FAILURE);
    }
    catch(ThreadExecutionException tee) { // wrapper for TestNGRuntimeException
      Throwable cause= tee.getCause();
      if(TestNGRuntimeException.class.equals(cause.getClass())) {
        testResult.setThrowable(cause.getCause());
      }
      else {
        testResult.setThrowable(cause);
      }
      testResult.setStatus(ITestResult.FAILURE);
    }
    catch(Throwable thr) { // covers the non-wrapper exceptions
      testResult.setThrowable(thr);
      testResult.setStatus(ITestResult.FAILURE);
    }
    finally {
      // Set end time ASAP
      testResult.setEndMillis(System.currentTimeMillis());

      ExpectedExceptionsHolder expectedExceptionClasses
          = MethodHelper.findExpectedExceptions(m_annotationFinder, tm.getMethod());
      List<ITestResult> results = Lists.<ITestResult>newArrayList(testResult);
      handleInvocationResults(tm, results, expectedExceptionClasses, false,
          false /* collect results */, failureContext);

      // 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();

      // Run invokedMethodListeners after updating TestResult
      runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult);
      runTestListeners(testResult);
      // Do not notify if will retry.
      if (!results.isEmpty()) {
        collectResults(tm, Collections.<ITestResult>singleton(testResult));
      }

      //
      // Invoke afterMethods only if
      // - lastTimeOnly is not set
      // - lastTimeOnly is set, and we are reaching the last invocationCount
      //
      invokeConfigurations(testClass, tm,
          filterConfigurationMethods(tm, afterMethods, false /* beforeMethods */),
          suite, params, parameterValues,
          instance,
          testResult);

      //
      // Invoke afterGroups configurations
      //
      invokeAfterGroupsConfigurations(testClass, tm, groupMethods, suite,
          params, instance);

      // Reset the test result last. If we do this too early, Reporter.log()
      // invocations from listeners will be discarded
      Reporter.setCurrentTestResult(null);
    }

    return testResult;
  }

  void collectResults(ITestNGMethod testMethod, Collection<ITestResult> results) {
    for (ITestResult result : results) {
      // Collect the results
      final 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;
      }
    }
  }
MethodInvocationHelper类代码:
protected static Object invokeMethod(Method thisMethod, Object instance, Object[] parameters)
      throws InvocationTargetException, IllegalAccessException {
    Utils.checkInstanceOrStatic(instance, thisMethod);

    // TESTNG-326, allow IObjectFactory to load from non-standard classloader
    // If the instance has a different classloader, its class won't match the
    // method's class
    if (instance == null || !thisMethod.getDeclaringClass().isAssignableFrom(instance.getClass())) {
      // for some reason, we can't call this method on this class
      // is it static?
      boolean isStatic = Modifier.isStatic(thisMethod.getModifiers());
      if (!isStatic) {
        // not static, so grab a method with the same name and signature in this case
        Class<?> clazz = instance.getClass();
        try {
          thisMethod = clazz.getMethod(thisMethod.getName(), thisMethod.getParameterTypes());
        } catch (Exception e) {
          // ignore, the method may be private
          boolean found = false;
          for (; clazz != null; clazz = clazz.getSuperclass()) {
            try {
              thisMethod = clazz.getDeclaredMethod(thisMethod.getName(),
                  thisMethod.getParameterTypes());
              found = true;
              break;
            } catch (Exception e2) {
            }
          }
          if (!found) {
            // should we assert here? Or just allow it to fail on invocation?
            if (thisMethod.getDeclaringClass().getName().equals(instance.getClass().getName())) {
              throw new RuntimeException("Can't invoke method " + thisMethod
                  + ", probably due to classloader mismatch");
            }
            throw new RuntimeException("Can't invoke method " + thisMethod
                + " on this instance of " + instance.getClass() + " due to class mismatch");
          }
        }
      }
    }

    synchronized(thisMethod) {
      if (! Modifier.isPublic(thisMethod.getModifiers())) {
        thisMethod.setAccessible(true);
      }
    }
    return thisMethod.invoke(instance, parameters);
  }

  protected static Iterator<Object[]> invokeDataProvider(Object instance, Method dataProvider,
      ITestNGMethod method, ITestContext testContext, Object fedInstance,
      IAnnotationFinder annotationFinder) {
    Iterator<Object[]> result;
    final ConstructorOrMethod com = method.getConstructorOrMethod();

    // If it returns an Object[][], convert it to an Iterable<Object[]>
    try {
      List<Object> lParameters = Lists.newArrayList();

      // Go through all the parameters declared on this Data Provider and
      // make sure we have at most one Method and one ITestContext.
      // Anything else is an error
      Class<?>[] parameterTypes = dataProvider.getParameterTypes();

      final Collection<Pair<Integer, Class<?>>> unresolved = new ArrayList<Pair<Integer, Class<?>>>(parameterTypes.length);
      int i = 0;
      for (Class<?> cls : parameterTypes) {
        boolean isTestInstance = annotationFinder.hasTestInstance(dataProvider, i++);
        if (cls.equals(Method.class)) {
          lParameters.add(com.getMethod());
        } else if (cls.equals(Constructor.class)) {
          lParameters.add(com.getConstructor());
        } else if (cls.equals(ConstructorOrMethod.class)) {
          lParameters.add(com);
        } else if (cls.equals(ITestNGMethod.class)) {
          lParameters.add(method);
        } else if (cls.equals(ITestContext.class)) {
          lParameters.add(testContext);
        } else if (isTestInstance) {
          lParameters.add(fedInstance);
        } else {
          unresolved.add(new Pair<Integer, Class<?>>(i, cls));
        }
      }
      if (!unresolved.isEmpty()) {
        final StringBuilder sb = new StringBuilder();
        sb.append("Some DataProvider ").append(dataProvider).append(" parameters unresolved: ");
        for (Pair<Integer, Class<?>> pair : unresolved) {
          sb.append(" at ").append(pair.first()).append(" type ").append(pair.second()).append("\n");
        }
        throw new TestNGException(sb.toString());
      }

      Object[] parameters = lParameters.toArray(new Object[lParameters.size()]);

      Class<?> returnType = dataProvider.getReturnType();
      if (Object[][].class.isAssignableFrom(returnType)) {
        Object[][] originalResult = (Object[][]) invokeMethod(dataProvider, instance, parameters);

        // If the data provider is restricting the indices to return, filter them out
        int[] indices = dataProvider.getAnnotation(DataProvider.class).indices();
        Object[][] oResult;
        if (indices.length > 0) {
          oResult = new Object[indices.length][];
          for (int j = 0; j < indices.length; j++) {
            oResult[j] = originalResult[indices[j]];
          }
        } else {
          oResult = originalResult;
        }

        method.setParameterInvocationCount(oResult.length);
        result = MethodHelper.createArrayIterator(oResult);
      } else if (Iterator.class.isAssignableFrom(returnType)) {
        // Already an Iterator<Object[]>, assign it directly
        result = (Iterator<Object[]>) invokeMethod(dataProvider, instance, parameters);
      } else {
        throw new TestNGException("Data Provider " + dataProvider + " must return"
            + " either Object[][] or Iterator<Object>[], not " + returnType);
      }
    } catch (InvocationTargetException e) {
      // Don't throw TestNGException here or this test won't be reported as a
      // skip or failure
      throw new RuntimeException(e.getCause());
    } catch (IllegalAccessException e) {
      // Don't throw TestNGException here or this test won't be reported as a
      // skip or failure
      throw new RuntimeException(e.getCause());
    }

    return result;
  }




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值