package org.powermock.tests.utils.impl; import org.powermock.core.classloader.MockClassLoader; import org.powermock.core.classloader.annotations.PrepareEverythingForTest; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.core.transformers.MockTransformer; import org.powermock.core.transformers.impl.TestClassTransformer; import org.powermock.tests.utils.ArrayMerger; import org.powermock.tests.utils.IgnorePackagesExtractor; import org.powermock.tests.utils.TestChunk; import org.powermock.tests.utils.TestClassesExtractor; import org.powermock.tests.utils.TestSuiteChunker; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; /** * */ public abstract class AbstractCommonTestSuiteChunkerImpl implements TestSuiteChunker { protected static final int DEFAULT_TEST_LISTENERS_SIZE = 1; protected static final int NOT_INITIALIZED = -1; protected static final int INTERNAL_INDEX_NOT_FOUND = NOT_INITIALIZED; /* * Maps between a specific class and a map of test methods loaded by a * specific mock class loader. */ private final List<TestCaseEntry> internalSuites = new LinkedList<TestCaseEntry>(); private final TestClassesExtractor prepareForTestExtractor = new PrepareForTestExtractorImpl(); private final TestClassesExtractor suppressionExtractor = new StaticConstructorSuppressExtractorImpl(); /* * Maps the list of test indexes that is assigned to a specific test suite * index. */ protected final LinkedHashMap<Integer, List<Integer>> testAtDelegateMapper = new LinkedHashMap<Integer, List<Integer>>(); protected final Class<?>[] testClasses; private final IgnorePackagesExtractor ignorePackagesExtractor = new PowerMockIgnorePackagesExtractorImpl(); private final ArrayMerger arrayMerger = new ArrayMergerImpl(); private int currentTestIndex = NOT_INITIALIZED; protected AbstractCommonTestSuiteChunkerImpl(Class<?> testClass) throws Exception { this(new Class[]{testClass}); } protected AbstractCommonTestSuiteChunkerImpl(Class<?>... testClasses) throws Exception { this.testClasses = testClasses; for (Class<?> clazz : testClasses) { chunkClass(clazz); } } @Override public int getChunkSize() { return getTestChunks().size(); } public List<TestChunk> getTestChunks() { List<TestChunk> allChunks = new LinkedList<TestChunk>(); for (TestCaseEntry entry : internalSuites) { for (TestChunk chunk : entry.getTestChunks()) { allChunks.add(chunk); } } return allChunks; } /** * {@inheritDoc} */ public List<TestChunk> getTestChunksEntries(Class<?> testClass) { for (TestCaseEntry entry : internalSuites) { if (entry.getTestClass().equals(testClass)) { return entry.getTestChunks(); } } return null; } public TestChunk getTestChunk(Method method) { for (TestChunk testChunk : getTestChunks()) { if (testChunk.isMethodToBeExecutedByThisClassloader(method)) { return testChunk; } } return null; } protected void chunkClass(final Class<?> testClass) throws Exception { List<Method> testMethodsForOtherClassLoaders = new ArrayList<Method>(); MockTransformer[] extraMockTransformers = createDefaultExtraMockTransformers(testClass, testMethodsForOtherClassLoaders); final String[] ignorePackages = ignorePackagesExtractor.getPackagesToIgnore(testClass); final ClassLoader defaultMockLoader = createDefaultMockLoader(testClass, extraMockTransformers, ignorePackages); List<Method> currentClassloaderMethods = new LinkedList<Method>(); // Put the first suite in the map of internal suites. TestChunk defaultTestChunk = new TestChunkImpl(defaultMockLoader, currentClassloaderMethods); List<TestChunk> testChunks = new LinkedList<TestChunk>(); testChunks.add(defaultTestChunk); internalSuites.add(new TestCaseEntry(testClass, testChunks)); initEntries(internalSuites); if (!currentClassloaderMethods.isEmpty()) { List<TestChunk> allTestChunks = internalSuites.get(0).getTestChunks(); for (TestChunk chunk : allTestChunks.subList(1, allTestChunks.size())) { for (Method m : chunk.getTestMethodsToBeExecutedByThisClassloader()) { testMethodsForOtherClassLoaders.add(m); } } } else if (2 <= internalSuites.size() || 1 == internalSuites.size() && 2 <= internalSuites.get(0).getTestChunks().size()) { /* * If we don't have any test that should be executed by the default * class loader remove it to avoid duplicate test print outs. */ internalSuites.get(0).getTestChunks().remove(0); } //else{ /*Delegation-runner maybe doesn't use test-method annotations!*/ } } private ClassLoader createDefaultMockLoader(Class<?> testClass, MockTransformer[] extraMockTransformers, String[] ignorePackages) { final ClassLoader defaultMockLoader; if (testClass.isAnnotationPresent(PrepareEverythingForTest.class)) { defaultMockLoader = createNewClassloader(testClass, new String[]{MockClassLoader.MODIFY_ALL_CLASSES}, ignorePackages, extraMockTransformers); } else { final String[] prepareForTestClasses = prepareForTestExtractor.getTestClasses(testClass); final String[] suppressStaticClasses = suppressionExtractor.getTestClasses(testClass); defaultMockLoader = createNewClassloader(testClass, arrayMerger.mergeArrays(String.class, prepareForTestClasses, suppressStaticClasses), ignorePackages, extraMockTransformers); } return defaultMockLoader; } private ClassLoader createNewClassloader(Class<?> testClass, String[] classesToLoadByMockClassloader, final String[] packagesToIgnore, MockTransformer... extraMockTransformers) { final MockClassLoaderFactory classLoaderFactory = getMockClassLoaderFactory(testClass, classesToLoadByMockClassloader, packagesToIgnore, extraMockTransformers); return classLoaderFactory.create(); } protected MockClassLoaderFactory getMockClassLoaderFactory(Class<?> testClass, String[] preliminaryClassesToLoadByMockClassloader, String[] packagesToIgnore, MockTransformer[] extraMockTransformers) { return new MockClassLoaderFactory(testClass, preliminaryClassesToLoadByMockClassloader, packagesToIgnore, extraMockTransformers); } private MockTransformer[] createDefaultExtraMockTransformers(Class<?> testClass, List<Method> testMethodsThatRunOnOtherClassLoaders) { if (null == testMethodAnnotation()) { return new MockTransformer[0]; } else { return new MockTransformer[]{ TestClassTransformer .forTestClass(testClass) .removesTestMethodAnnotation(testMethodAnnotation()) .fromMethods(testMethodsThatRunOnOtherClassLoaders) }; } } protected Class<? extends Annotation> testMethodAnnotation() { return null; } private void initEntries(List<TestCaseEntry> entries) { for (TestCaseEntry testCaseEntry : entries) { final Class<?> testClass = testCaseEntry.getTestClass(); findMethods(testCaseEntry, testClass); } } private void findMethods(TestCaseEntry testCaseEntry, Class<?> testClass) { Method[] allMethods = testClass.getMethods(); for (Method method : allMethods) { putMethodToChunk(testCaseEntry, testClass, method); } testClass = testClass.getSuperclass(); if (!Object.class.equals(testClass)) { findMethods(testCaseEntry, testClass); } } private void putMethodToChunk(TestCaseEntry testCaseEntry, Class<?> testClass, Method method) { if (shouldExecuteTestForMethod(testClass, method)) { currentTestIndex++; if (hasChunkAnnotation(method)) { LinkedList<Method> methodsInThisChunk = new LinkedList<Method>(); methodsInThisChunk.add(method); final String[] staticSuppressionClasses = getStaticSuppressionClasses(testClass, method); TestClassTransformer[] extraTransformers = null == testMethodAnnotation() ? new TestClassTransformer[0] : new TestClassTransformer[]{ TestClassTransformer.forTestClass(testClass) .removesTestMethodAnnotation(testMethodAnnotation()) .fromAllMethodsExcept(method) }; final ClassLoader mockClassloader; if (method.isAnnotationPresent(PrepareEverythingForTest.class)) { mockClassloader = createNewClassloader(testClass, new String[]{MockClassLoader.MODIFY_ALL_CLASSES}, ignorePackagesExtractor.getPackagesToIgnore(testClass), extraTransformers); } else { mockClassloader = createNewClassloader(testClass, arrayMerger.mergeArrays(String.class, prepareForTestExtractor.getTestClasses(method), staticSuppressionClasses), ignorePackagesExtractor.getPackagesToIgnore(testClass), extraTransformers); } TestChunkImpl chunk = new TestChunkImpl(mockClassloader, methodsInThisChunk); testCaseEntry.getTestChunks().add(chunk); updatedIndexes(); } else { testCaseEntry.getTestChunks().get(0).getTestMethodsToBeExecutedByThisClassloader().add(method); // currentClassloaderMethods.add(method); final int currentDelegateIndex = internalSuites.size() - 1; /* * Add this test index to the main junit runner * delegator. */ List<Integer> testList = testAtDelegateMapper.get(currentDelegateIndex); if (testList == null) { testList = new LinkedList<Integer>(); testAtDelegateMapper.put(currentDelegateIndex, testList); } testList.add(currentTestIndex); } } } private boolean hasChunkAnnotation(Method method) { return method.isAnnotationPresent(PrepareForTest.class) || method.isAnnotationPresent(SuppressStaticInitializationFor.class) || method.isAnnotationPresent(PrepareOnlyThisForTest.class) || method.isAnnotationPresent(PrepareEverythingForTest.class); } private String[] getStaticSuppressionClasses(Class<?> testClass, Method method) { final String[] testClasses; if (method.isAnnotationPresent(SuppressStaticInitializationFor.class)) { testClasses = suppressionExtractor.getTestClasses(method); } else { testClasses = suppressionExtractor.getTestClasses(testClass); } return testClasses; } private void updatedIndexes() { final List<Integer> testIndexesForThisClassloader = new LinkedList<Integer>(); testIndexesForThisClassloader.add(currentTestIndex); testAtDelegateMapper.put(internalSuites.size(), testIndexesForThisClassloader); } }