
...29import org.apache.maven.surefire.util.internal.StringUtils;30import org.testng.TestNG;31import org.testng.annotations.Test;32import org.testng.xml.XmlClass;33import org.testng.xml.XmlMethodSelector;34import org.testng.xml.XmlSuite;35import org.testng.xml.XmlTest;36import java.io.File;37import java.lang.annotation.Annotation;38import java.lang.reflect.Constructor;39import java.lang.reflect.InvocationTargetException;40import java.lang.reflect.Method;41import java.util.ArrayList;42import java.util.HashMap;43import java.util.List;44import java.util.Map;45import java.util.concurrent.atomic.AtomicInteger;46import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;47import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;48import static org.apache.maven.surefire.util.ReflectionUtils.instantiate;49import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass;50import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;51/**52 * Contains utility methods for executing TestNG.53 *54 * @author <a href="mailto:brett@apache.org">Brett Porter</a>55 * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>56 */57final class TestNGExecutor58{59    /** The default name for a suite launched from the maven surefire plugin */60    private static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";61    /** The default name for a test launched from the maven surefire plugin */62    private static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";63    private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =64            tryLoadClass( TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test" ) != null;65    private TestNGExecutor()66    {67        throw new IllegalStateException( "not instantiable constructor" );68    }69    @SuppressWarnings( "checkstyle:parameternumbercheck" )70    static void run( Iterable<Class<?>> testClasses, String testSourceDirectory,71                            Map<String, String> options, // string,string because TestNGMapConfigurator#configure()72                            RunListener reportManager, File reportsDirectory,73                            TestListResolver methodFilter, List<CommandLineOption> mainCliOptions,74                            int skipAfterFailureCount )75        throws TestSetFailedException76    {77        TestNG testng = new TestNG( true );78        Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );79        if ( isCliDebugOrShowErrors( mainCliOptions ) )80        {81            System.out.println( "Configuring TestNG with: " + configurator.getClass().getSimpleName() );82        }83        XmlMethodSelector groupMatchingSelector = createGroupMatchingSelector( options );84        XmlMethodSelector methodNameFilteringSelector = createMethodNameFilteringSelector( methodFilter );85        Map<String, SuiteAndNamedTests> suitesNames = new HashMap<>();86        List<XmlSuite> xmlSuites = new ArrayList<>();87        for ( Class<?> testClass : testClasses )88        {89            TestMetadata metadata = findTestMetadata( testClass );90            SuiteAndNamedTests suiteAndNamedTests = suitesNames.get( metadata.suiteName );91            if ( suiteAndNamedTests == null )92            {93                suiteAndNamedTests = new SuiteAndNamedTests();94                suiteAndNamedTests.xmlSuite.setName( metadata.suiteName );95                configurator.configure( suiteAndNamedTests.xmlSuite, options );96                xmlSuites.add( suiteAndNamedTests.xmlSuite );97                suitesNames.put( metadata.suiteName, suiteAndNamedTests );98            }99            XmlTest xmlTest = suiteAndNamedTests.testNameToTest.get( metadata.testName );100            if ( xmlTest == null )101            {102                xmlTest = new XmlTest( suiteAndNamedTests.xmlSuite );103                xmlTest.setName( metadata.testName );104                addSelector( xmlTest, groupMatchingSelector );105                addSelector( xmlTest, methodNameFilteringSelector );106                xmlTest.setXmlClasses( new ArrayList<XmlClass>() );107                suiteAndNamedTests.testNameToTest.put( metadata.testName, xmlTest );108            }109            xmlTest.getXmlClasses().add( new XmlClass( testClass.getName() ) );110        }111        testng.setXmlSuites( xmlSuites );112        configurator.configure( testng, options );113        postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,114                       extractVerboseLevel( options ) );115        testng.run();116    }117    private static boolean isCliDebugOrShowErrors( List<CommandLineOption> mainCliOptions )118    {119        return mainCliOptions.contains( LOGGING_LEVEL_DEBUG ) || mainCliOptions.contains( SHOW_ERRORS );120    }121    private static TestMetadata findTestMetadata( Class<?> testClass )122    {123        TestMetadata result = new TestMetadata();124        if ( HAS_TEST_ANNOTATION_ON_CLASSPATH )125        {126            Test testAnnotation = findAnnotation( testClass, Test.class );127            if ( null != testAnnotation )128            {129                if ( !StringUtils.isBlank( testAnnotation.suiteName() ) )130                {131                    result.suiteName = testAnnotation.suiteName();132                }133                if ( !StringUtils.isBlank( testAnnotation.testName() ) )134                {135                    result.testName = testAnnotation.testName();136                }137            }138        }139        return result;140    }141    private static <T extends Annotation> T findAnnotation( Class<?> clazz, Class<T> annotationType )142    {143        if ( clazz == null )144        {145            return null;146        }147        T result = clazz.getAnnotation( annotationType );148        if ( result != null )149        {150            return result;151        }152        return findAnnotation( clazz.getSuperclass(), annotationType );153    }154    private static class TestMetadata155    {156        private String testName = DEFAULT_SUREFIRE_TEST_NAME;157        private String suiteName = DEFAULT_SUREFIRE_SUITE_NAME;158    }159    private static class SuiteAndNamedTests160    {161        private XmlSuite xmlSuite = new XmlSuite();162        private Map<String, XmlTest> testNameToTest = new HashMap<>();163    }164    private static void addSelector( XmlTest xmlTest, XmlMethodSelector selector )165    {166        if ( selector != null )167        {168            xmlTest.getMethodSelectors().add( selector );169        }170    }171    @SuppressWarnings( "checkstyle:magicnumber" )172    private static XmlMethodSelector createMethodNameFilteringSelector( TestListResolver methodFilter )173        throws TestSetFailedException174    {175        if ( methodFilter != null && !methodFilter.isEmpty() )176        {177            // the class is available in the testClassPath178            String clazzName = "org.apache.maven.surefire.testng.utils.MethodSelector";179            try180            {181                Class<?> clazz = Class.forName( clazzName );182                Method method = clazz.getMethod( "setTestListResolver", TestListResolver.class );183                method.invoke( null, methodFilter );184            }185            catch ( Exception e )186            {187                throw new TestSetFailedException( e.getMessage(), e );188            }189            XmlMethodSelector xms = new XmlMethodSelector();190            xms.setName( clazzName );191            // looks to need a high value192            xms.setPriority( 10000 );193            return xms;194        }195        else196        {197            return null;198        }199    }200    @SuppressWarnings( "checkstyle:magicnumber" )201    private static XmlMethodSelector createGroupMatchingSelector( Map<String, String> options )202        throws TestSetFailedException203    {204        final String groups = options.get( ProviderParameterNames.TESTNG_GROUPS_PROP );205        final String excludedGroups = options.get( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );206        if ( groups == null && excludedGroups == null )207        {208            return null;209        }210        // the class is available in the testClassPath211        final String clazzName = "org.apache.maven.surefire.testng.utils.GroupMatcherMethodSelector";212        try213        {214            Class<?> clazz = Class.forName( clazzName );215            // HORRIBLE hack, but TNG doesn't allow us to setup a method selector instance directly.216            Method method = clazz.getMethod( "setGroups", String.class, String.class );217            method.invoke( null, groups, excludedGroups );218        }219        catch ( Exception e )220        {221            throw new TestSetFailedException( e.getMessage(), e );222        }223        XmlMethodSelector xms = new XmlMethodSelector();224        xms.setName( clazzName );225        // looks to need a high value226        xms.setPriority( 9999 );227        return xms;228    }229    static void run( List<String> suiteFiles, String testSourceDirectory,230                            Map<String, String> options, // string,string because TestNGMapConfigurator#configure()231                            RunListener reportManager, File reportsDirectory, int skipAfterFailureCount )232        throws TestSetFailedException233    {234        TestNG testng = new TestNG( true );235        Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );236        configurator.configure( testng, options );237        postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,...
