Package org.powermock.tests.utils.impl

Source Code of org.powermock.tests.utils.impl.AbstractTestSuiteChunkerImpl

/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.powermock.tests.utils.impl;

import org.powermock.core.classloader.MockClassLoader;
import org.powermock.core.classloader.annotations.*;
import org.powermock.core.spi.PowerMockPolicy;
import org.powermock.core.spi.PowerMockTestListener;
import org.powermock.core.transformers.MockTransformer;
import org.powermock.core.transformers.impl.MainMockTransformer;
import org.powermock.reflect.Whitebox;
import org.powermock.reflect.proxyframework.RegisterProxyFramework;
import org.powermock.tests.utils.*;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.Map.Entry;

/**
* Abstract base class for test suite chunking, i.e. a suite is chunked into
* several smaller pieces which are ran with different classloaders. A chunk is
* defined by the {@link PrepareForTest} annotation. This to make sure that you
* can byte-code manipulate classes in tests without impacting on other tests.
*
*/
public abstract class AbstractTestSuiteChunkerImpl<T> implements TestSuiteChunker {
    private static final int DEFAULT_TEST_LISTENERS_SIZE = 1;

    protected static final int NOT_INITIALIZED = -1;

    private static final int INTERNAL_INDEX_NOT_FOUND = NOT_INITIALIZED;

    protected final TestClassesExtractor prepareForTestExtractor = new PrepareForTestExtractorImpl();

    protected final TestClassesExtractor suppressionExtractor = new StaticConstructorSuppressExtractorImpl();

    private final IgnorePackagesExtractor ignorePackagesExtractor = new PowerMockIgnorePackagesExtractorImpl();

    private final ArrayMerger arrayMerger = new ArrayMergerImpl();

    private final Class<?>[] testClasses;

    /*
     * The classes listed in this set has been chunked and its delegates has
     * been created.
     */
    protected final Set<Class<?>> delegatesCreatedForTheseClasses = new LinkedHashSet<Class<?>>();

    // A list of junit delegates.
    protected final List<T> delegates = new ArrayList<T>();

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

    private int currentTestIndex = 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;

    protected volatile int testCount = NOT_INITIALIZED;

    protected AbstractTestSuiteChunkerImpl(Class<?> testClass) throws Exception {
        this(new Class[] { testClass });
    }

    protected AbstractTestSuiteChunkerImpl(Class<?>... testClasses) throws Exception {
        this.testClasses = testClasses;
        internalSuites = new LinkedList<TestCaseEntry>();
        for (Class<?> clazz : testClasses) {
            chunkClass(clazz);
        }
    }

    protected Object getPowerMockTestListenersLoadedByASpecificClassLoader(Class<?> clazz, ClassLoader classLoader) {
        try {
            int defaultListenerSize = DEFAULT_TEST_LISTENERS_SIZE;
            Class<?> annotationEnablerClass = null;
            try {
                annotationEnablerClass = Class.forName("org.powermock.api.extension.listener.AnnotationEnabler", false, classLoader);
            } catch (ClassNotFoundException e) {
                // Annotation enabler wasn't found in class path
                defaultListenerSize = 0;
            }

            registerProxyframework(classLoader);

            final Class<?> powerMockTestListenerType = Class.forName(PowerMockTestListener.class.getName(), false, classLoader);
            Object testListeners = null;
            if (clazz.isAnnotationPresent(PowerMockListener.class)) {
                PowerMockListener annotation = clazz.getAnnotation(PowerMockListener.class);
                final Class<? extends PowerMockTestListener>[] powerMockTestListeners = annotation.value();
                if (powerMockTestListeners.length > 0) {
                    testListeners = Array.newInstance(powerMockTestListenerType, powerMockTestListeners.length + defaultListenerSize);
                    for (int i = 0; i < powerMockTestListeners.length; i++) {
                        String testListenerClassName = powerMockTestListeners[i].getName();
                        final Class<?> listenerTypeLoadedByClassLoader = Class.forName(testListenerClassName, false, classLoader);
                        Array.set(testListeners, i, Whitebox.newInstance(listenerTypeLoadedByClassLoader));
                    }
                }
            } else {
                testListeners = Array.newInstance(powerMockTestListenerType, defaultListenerSize);
            }

            // Add default annotation enabler listener
            if (annotationEnablerClass != null) {
                Array.set(testListeners, Array.getLength(testListeners) - 1, Whitebox.newInstance(annotationEnablerClass));
            }

            return testListeners;
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("PowerMock internal error: Failed to load class.", e);
        }
    }

    private void registerProxyframework(ClassLoader classLoader) {
        Class<?> proxyFrameworkClass = null;
        try {
            proxyFrameworkClass = Class.forName("org.powermock.api.extension.proxyframework.ProxyFrameworkImpl", false, classLoader);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "Extension API internal error: org.powermock.api.extension.proxyframework.ProxyFrameworkImpl could not be located in classpath.");
        }

        Class<?> proxyFrameworkRegistrar = null;
        try {
            proxyFrameworkRegistrar = Class.forName(RegisterProxyFramework.class.getName(), false, classLoader);
        } catch (ClassNotFoundException e) {
            // Should never happen
            throw new RuntimeException(e);
        }
        try {
            Whitebox.invokeMethod(proxyFrameworkRegistrar, "registerProxyFramework", Whitebox.newInstance(proxyFrameworkClass));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected void chunkClass(final Class<?> testClass) throws Exception {
        ClassLoader defaultMockLoader = null;
        final String[] ignorePackages = ignorePackagesExtractor.getPackagesToIgnore(testClass);
        if (testClass.isAnnotationPresent(PrepareEverythingForTest.class)) {
            defaultMockLoader = createNewClassloader(testClass, new String[] { MockClassLoader.MODIFY_ALL_CLASSES }, ignorePackages);
        } else {
            final String[] prepareForTestClasses = prepareForTestExtractor.getTestClasses(testClass);
            final String[] suppressStaticClasses = suppressionExtractor.getTestClasses(testClass);
            defaultMockLoader = createNewClassloader(testClass, arrayMerger.mergeArrays(String.class, prepareForTestClasses, suppressStaticClasses),
                    ignorePackages);
        }
        registerProxyframework(defaultMockLoader);
        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 we don't have any test that should be executed by the default
         * class loader remove it to avoid duplicate test print outs.
         */
        if (currentClassloaderMethods.isEmpty()) {
            internalSuites.get(0).getTestChunks().remove(0);
        }
    }

    public ClassLoader createNewClassloader(Class<?> testClass, final String[] classesToLoadByMockClassloader, final String[] packagesToIgnore) {
        ClassLoader mockLoader = null;
        if ((classesToLoadByMockClassloader == null || classesToLoadByMockClassloader.length == 0) && !hasMockPolicyProvidedClasses(testClass)) {
            mockLoader = Thread.currentThread().getContextClassLoader();
        } else {
            List<MockTransformer> mockTransformerChain = new ArrayList<MockTransformer>();
            final MainMockTransformer mainMockTransformer = new MainMockTransformer();
            mockTransformerChain.add(mainMockTransformer);
            final UseClassPathAdjuster useClassPathAdjuster = testClass.getAnnotation(UseClassPathAdjuster.class);
            mockLoader = AccessController.doPrivileged(new PrivilegedAction<MockClassLoader>() {
                public MockClassLoader run() {
                    return new MockClassLoader(classesToLoadByMockClassloader, packagesToIgnore, useClassPathAdjuster);
                }
            });
            MockClassLoader mockClassLoader = (MockClassLoader) mockLoader;
            mockClassLoader.setMockTransformerChain(mockTransformerChain);
            new MockPolicyInitializerImpl(testClass).initialize(mockLoader);
        }
        return mockLoader;
    }

    /**
     * {@inheritDoc}
     */
    public void createTestDelegators(Class<?> testClass, List<TestChunk> chunks) throws Exception {
        for (TestChunk chunk : chunks) {
            ClassLoader classLoader = chunk.getClassLoader();
            List<Method> methodsToTest = chunk.getTestMethodsToBeExecutedByThisClassloader();
            T runnerDelegator = createDelegatorFromClassloader(classLoader, testClass, methodsToTest);
            delegates.add(runnerDelegator);
        }
        delegatesCreatedForTheseClasses.add(testClass);
    }

    protected abstract T createDelegatorFromClassloader(ClassLoader classLoader, Class<?> testClass, final List<Method> methodsToTest)
            throws Exception;

    private void initEntries(List<TestCaseEntry> entries) throws Exception {
        for (TestCaseEntry testCaseEntry : entries) {
            final Class<?> testClass = testCaseEntry.getTestClass();
            Method[] allMethods = testClass.getMethods();
            for (Method method : allMethods) {
                if (shouldExecuteTestForMethod(testClass, method)) {
                    currentTestIndex++;
                    if (hasChunkAnnotation(method)) {
                        LinkedList<Method> methodsInThisChunk = new LinkedList<Method>();
                        methodsInThisChunk.add(method);
                        final String[] staticSuppressionClasses = getStaticSuppressionClasses(testClass, method);
                        ClassLoader mockClassloader = null;
                        if (method.isAnnotationPresent(PrepareEverythingForTest.class)) {
                            mockClassloader = createNewClassloader(testClass, new String[] { MockClassLoader.MODIFY_ALL_CLASSES },
                                    ignorePackagesExtractor.getPackagesToIgnore(testClass));
                        } else {
                            mockClassloader = createNewClassloader(testClass, arrayMerger.mergeArrays(String.class, prepareForTestExtractor
                                    .getTestClasses(method), staticSuppressionClasses), ignorePackagesExtractor.getPackagesToIgnore(testClass));
                        }
                        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);
    }

    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;
    }

    /**
     * Get the internal test index for a junit runner delegate based on the
     * "real" original test index. For example, the test may need to run a
     * single test, for example the test with index 3. However since PowerMock
     * may have chunked the test suite to use many classloaders and junit
     * delegators the index (3) must be mapped to an internal representation for
     * the specific junit runner delegate. This is what this method does. I.e.
     * it will iterate through all junit runner delegates and see if they
     * contain the test with index 3, in the internal index of this test
     * delegator is returned.
     *
     * @param originalTestIndex
     *            The original test index as seen by the test runner.
     * @return The internal test index as seen by PowerMock or <code>-1</code>
     *         if no index was found.
     *
     */
    public int getInternalTestIndex(int originalTestIndex) {
        Set<Entry<Integer, List<Integer>>> delegatorEntrySet = testAtDelegateMapper.entrySet();
        for (Entry<Integer, List<Integer>> entry : delegatorEntrySet) {
            final List<Integer> testIndexesForThisDelegate = entry.getValue();
            final int internalIndex = testIndexesForThisDelegate.indexOf(originalTestIndex);
            if (internalIndex != INTERNAL_INDEX_NOT_FOUND) {
                return internalIndex;
            }
        }
        return INTERNAL_INDEX_NOT_FOUND;
    }

    /**
     * Get the junit runner delegate that handles the test at index
     * <code>testIndex</code>. Throws a {@link RuntimeException} if a delegator
     * is not found for the specific test index.
     *
     * @param testIndex
     *            The test index that a delegator should hold.
     * @return The index for of the junit runner delegate as seen by JTestRack.
     */
    public int getDelegatorIndex(int testIndex) {
        int delegatorIndex = -1;
        Set<Entry<Integer, List<Integer>>> entrySet = testAtDelegateMapper.entrySet();
        for (Entry<Integer, List<Integer>> entry : entrySet) {
            // If the delegator contains the test case, return the index of the
            // delegator.
            if (entry.getValue().contains(testIndex)) {
                delegatorIndex = entry.getKey();
                break;
            }
        }

        if (delegatorIndex == -1) {
            throw new RuntimeException("Internal error: Failed to find the delgator index.");
        }
        return delegatorIndex;
    }

    /**
     * {@inheritDoc}
     */
    public List<TestChunk> getTestChunksEntries(Class<?> testClass) {
        for (TestCaseEntry entry : internalSuites) {
            if (entry.getTestClass().equals(testClass)) {
                return entry.getTestChunks();
            }
        }
        return null;
    }

    public Class<?>[] getTestClasses() {
        return testClasses;
    }

    /**
     * @return <code>true</code> if there are some mock policies that
     *         contributes with classes that should be loaded by the mock
     *         classloader, <code>false</code> otherwise.
     */
    protected boolean hasMockPolicyProvidedClasses(Class<?> testClass) {
        boolean hasMockPolicyProvidedClasses = false;
        if (testClass.isAnnotationPresent(MockPolicy.class)) {
            MockPolicy annotation = testClass.getAnnotation(MockPolicy.class);
            Class<? extends PowerMockPolicy>[] value = annotation.value();
            hasMockPolicyProvidedClasses = new MockPolicyInitializerImpl(value).needsInitialization();
        }
        return hasMockPolicyProvidedClasses;
    }
}
TOP

Related Classes of org.powermock.tests.utils.impl.AbstractTestSuiteChunkerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.