org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powermock-module-junit4 Show documentation
Show all versions of powermock-module-junit4 Show documentation
PowerMock support module for JUnit 4.x.
/*
* Copyright 2008 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.modules.junit4.internal.impl;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Before;
import org.junit.internal.runners.ClassRoadie;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.internal.runners.MethodRoadie;
import org.junit.internal.runners.MethodValidator;
import org.junit.internal.runners.TestClass;
import org.junit.internal.runners.TestMethod;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.powermock.core.classloader.annotations.MockPolicy;
import org.powermock.core.classloader.annotations.PrepareEverythingForTest;
import org.powermock.core.spi.PowerMockTestListener;
import org.powermock.modules.junit4.common.internal.PowerMockJUnitRunnerDelegate;
import org.powermock.modules.junit4.internal.impl.testcaseworkaround.PowerMockJUnit4MethodValidator;
import org.powermock.reflect.Whitebox;
import org.powermock.tests.utils.PowerMockTestNotifier;
import org.powermock.tests.utils.impl.MockPolicyInitializerImpl;
import org.powermock.tests.utils.impl.PowerMockTestNotifierImpl;
import org.powermock.tests.utils.impl.PrepareForTestExtractorImpl;
import org.powermock.tests.utils.impl.StaticConstructorSuppressExtractorImpl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
*
* A JUnit4 test runner that only runs a specified set of test methods in a test
* class.
*
*
* Many parts of this class are essentially a rip off from
* {@link JUnit4ClassRunner} used in JUnit 4.4. It does however not extend this
* class because we cannot let it perform the stuff it does in its constructor.
* Another thing that different is that if an exception is thrown in the test we
* add a tip to error message asking the user if they've not forgot to add a
* class to test. Yet another difference is that this runner notifies the
* PowerMock listeners of certain events.
*
* @see JUnit4ClassRunner
*/
@SuppressWarnings("deprecation")
public class PowerMockJUnit44RunnerDelegateImpl extends Runner implements Filterable, Sortable, PowerMockJUnitRunnerDelegate {
private final List testMethods;
private final TestClass testClass;
private final PowerMockTestNotifier powerMockTestNotifier;
public PowerMockJUnit44RunnerDelegateImpl(Class> klass, String[] methodsToRun, PowerMockTestListener[] listeners) throws InitializationError {
this.powerMockTestNotifier = new PowerMockTestNotifierImpl(listeners == null ? new PowerMockTestListener[0] : listeners);
testClass = new TestClass(klass);
testMethods = getTestMethods(klass, methodsToRun);
validate();
}
public PowerMockJUnit44RunnerDelegateImpl(Class> klass, String[] methodsToRun) throws InitializationError {
this(klass, methodsToRun, null);
}
public PowerMockJUnit44RunnerDelegateImpl(Class> klass) throws InitializationError {
this(klass, null);
}
@SuppressWarnings("unchecked")
protected final List getTestMethods(Class> klass, String[] methodsToRun) {
if (methodsToRun == null || methodsToRun.length == 0) {
// The getTestMethods of TestClass is not visible so we need to look
// it invoke it using reflection.
try {
return (List) Whitebox.invokeMethod(testClass, "getTestMethods");
} catch (Throwable e) {
throw new RuntimeException(e);
}
} else {
List foundMethods = new LinkedList();
Method[] methods = klass.getMethods();
for (Method method : methods) {
for (String methodName : methodsToRun) {
if (method.getName().equals(methodName)) {
foundMethods.add(method);
}
}
}
return foundMethods;
}
}
protected final void validate() throws InitializationError {
if (!TestCase.class.isAssignableFrom(testClass.getJavaClass())) {
MethodValidator methodValidator = new PowerMockJUnit4MethodValidator(testClass);
methodValidator.validateMethodsForDefaultRunner();
methodValidator.assertValid();
}
}
@Override
public void run(final RunNotifier notifier) {
new ClassRoadie(notifier, testClass, getDescription(), new Runnable() {
@Override
public void run() {
runMethods(notifier);
}
}).runProtected();
}
protected void runMethods(final RunNotifier notifier) {
final StaticConstructorSuppressExtractorImpl staticConstructorSuppressExtractorImpl = new StaticConstructorSuppressExtractorImpl();
Class> testType = getTestClass();
final ClassLoader thisClassLoader = getClass().getClassLoader();
if (!thisClassLoader.equals(testType.getClassLoader())) {
/*
* The test is loaded from another classloader, this means that we
* cannot get the correct annotations if we don't load the class
* from the correct class loader
*/
try {
testType = thisClassLoader.loadClass(testType.getName());
} catch (ClassNotFoundException e) {
// This should never happen
throw new RuntimeException("Internal error in PowerMock", e);
}
}
for (Method method : testMethods) {
if (staticConstructorSuppressExtractorImpl.getTestClasses(method) == null) {
staticConstructorSuppressExtractorImpl.getTestClasses(testType);
}
invokeTestMethod(method, notifier);
}
}
@Override
public Description getDescription() {
Description spec = Description.createSuiteDescription(getName(), classAnnotations());
List testMethods = this.testMethods;
for (Method method : testMethods)
spec.addChild(methodDescription(method));
return spec;
}
protected Annotation[] classAnnotations() {
return getTestClass().getAnnotations();
}
protected String getName() {
return getTestWrappedClass().getName();
}
protected Object createTest() throws Exception {
return createTestInstance();
}
private Object createTestInstance() throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
final TestClass testWrappedClass = getTestWrappedClass();
Constructor> constructor = null;
final Class> javaClass = testWrappedClass.getJavaClass();
if (TestCase.class.isAssignableFrom(javaClass)) {
constructor = TestSuite.getTestConstructor(javaClass.asSubclass(TestCase.class));
if (constructor.getParameterTypes().length == 1) {
return constructor.newInstance(javaClass.getSimpleName());
}
} else {
constructor = testWrappedClass.getConstructor();
}
return constructor.newInstance();
}
protected void invokeTestMethod(final Method method, RunNotifier notifier) {
Description description = methodDescription(method);
final Object testInstance;
try {
testInstance = createTest();
} catch (InvocationTargetException e) {
testAborted(notifier, description, e.getTargetException());
return;
} catch (Exception e) {
testAborted(notifier, description, e);
return;
}
// Check if we extend from TestClass, in that case we must run the setUp
// and tearDown methods.
final boolean extendsFromTestCase = TestCase.class.isAssignableFrom(testClass.getJavaClass());
final TestMethod testMethod = wrapMethod(method);
createPowerMockRunner(testInstance, testMethod, notifier, description, extendsFromTestCase).run();
}
protected PowerMockJUnit44MethodRunner createPowerMockRunner(final Object testInstance, final TestMethod testMethod, RunNotifier notifier,
Description description, final boolean extendsFromTestCase) {
return new PowerMockJUnit44MethodRunner(testInstance, testMethod, notifier, description, extendsFromTestCase);
}
private void testAborted(RunNotifier notifier, Description description, Throwable e) {
notifier.fireTestStarted(description);
notifier.fireTestFailure(new Failure(description, e));
notifier.fireTestFinished(description);
}
protected TestMethod wrapMethod(Method method) {
return new TestMethod(method, testClass);
}
protected String testName(Method method) {
return method.getName();
}
protected Description methodDescription(Method method) {
return Description.createTestDescription(getTestWrappedClass().getJavaClass(), testName(method), testAnnotations(method));
}
protected Annotation[] testAnnotations(Method method) {
return method.getAnnotations();
}
@Override
public void filter(Filter filter) throws NoTestsRemainException {
for (Iterator iter = testMethods.iterator(); iter.hasNext(); ) {
Method method = iter.next();
if (!filter.shouldRun(methodDescription(method)))
iter.remove();
}
if (testMethods.isEmpty())
throw new NoTestsRemainException();
}
@Override
public void sort(final Sorter sorter) {
Collections.sort(testMethods, new Comparator() {
@Override
public int compare(Method o1, Method o2) {
return sorter.compare(methodDescription(o1), methodDescription(o2));
}
});
}
protected TestClass getTestWrappedClass() {
return testClass;
}
@Override
public int getTestCount() {
return testMethods.size();
}
@Override
public Class> getTestClass() {
return testClass.getJavaClass();
}
protected class PowerMockJUnit44MethodRunner extends MethodRoadie {
private final Object testInstance;
private final boolean extendsFromTestCase;
protected final TestMethod testMethod;
protected PowerMockJUnit44MethodRunner(Object testInstance, TestMethod method, RunNotifier notifier, Description description,
boolean extendsFromTestCase) {
super(testInstance, method, notifier, description);
this.testInstance = testInstance;
this.extendsFromTestCase = extendsFromTestCase;
this.testMethod = method;
}
@Override
public void runBeforesThenTestThenAfters(final Runnable test) {
executeTest(Whitebox.getInternalState(testMethod, Method.class), testInstance, test);
}
public void executeTest(final Method method, final Object testInstance, final Runnable test) {
// Initialize mock policies for each test
final ClassLoader classloader = this.getClass().getClassLoader();
final Thread currentThread = Thread.currentThread();
final ClassLoader originalClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(classloader);
new MockPolicyInitializerImpl(testClass.getJavaClass()).initialize(classloader);
powerMockTestNotifier.notifyBeforeTestMethod(testInstance, method, new Object[0]);
try {
super.runBeforesThenTestThenAfters(test);
} finally {
currentThread.setContextClassLoader(originalClassLoader);
}
}
@Override
protected void runTestMethod() {
try {
try {
if (extendsFromTestCase) {
final Method setUp = Whitebox.getMethod(testInstance.getClass(), "setUp");
if (!setUp.isAnnotationPresent(Before.class)) {
Whitebox.invokeMethod(testInstance, "setUp");
}
}
testMethod.invoke(testInstance);
if (Whitebox.invokeMethod(testMethod, "expectsException")) {
addFailure(new AssertionError("Expected exception: " + getExpectedExceptionName(testMethod)));
}
} catch (InvocationTargetException e) {
handleInvocationTargetException(testMethod, e);
} catch (Throwable e) {
addFailure(e);
} finally {
if (extendsFromTestCase) {
try {
Whitebox.invokeMethod(testInstance, "tearDown");
} catch (Throwable tearingDown) {
addFailure(tearingDown);
}
}
}
} catch (Throwable e) {
throw new RuntimeException("Internal error in PowerMock.", e);
}
}
private void handleInvocationTargetException(final TestMethod testMethod, InvocationTargetException e) throws Exception {
Throwable actual = e.getTargetException();
while (actual instanceof InvocationTargetException) {
actual = ((InvocationTargetException) actual).getTargetException();
}
handleException(testMethod, actual);
}
protected void handleException(final TestMethod testMethod, Throwable actualFailure) {
try {
final String throwableName = actualFailure.getClass().getName();
if (throwableName.equals("org.junit.internal.AssumptionViolatedException") || throwableName.startsWith("org.junit.Assume$AssumptionViolatedException")) {
return;
} else if (!(Boolean) Whitebox.invokeMethod(testMethod, "expectsException")) {
final String className = actualFailure.getStackTrace()[0].getClassName();
final Class> testClassAsJavaClass = testClass.getJavaClass();
if (actualFailure instanceof NullPointerException
&& !testClassAsJavaClass.getName().equals(className)
&& !className.startsWith("java.lang")
&& !className.startsWith("org.powermock")
&& !className.startsWith("org.junit")
&& !new PrepareForTestExtractorImpl().isPrepared(testClassAsJavaClass, className)
&& !testClassAsJavaClass.isAnnotationPresent(PrepareEverythingForTest.class)
&& !new MockPolicyInitializerImpl(testClassAsJavaClass.isAnnotationPresent(MockPolicy.class) ? testClassAsJavaClass
.getAnnotation(MockPolicy.class).value() : null).isPrepared(className)) {
Whitebox.setInternalState(actualFailure, "detailMessage", "Perhaps the class " + className + " must be prepared for test?",
Throwable.class);
}
addFailure(actualFailure);
} else if (Whitebox.invokeMethod(testMethod, "isUnexpected", actualFailure)) {
String message = "Unexpected exception, expected<" + getExpectedExceptionName(testMethod) + "> but was<"
+ actualFailure.getClass().getName() + ">";
addFailure(new Exception(message, actualFailure));
}
} catch (Exception e) {
throw new RuntimeException("PowerMock internal error: Should never throw exception at this level", e);
}
}
@SuppressWarnings("unchecked")
private String getExpectedExceptionName(TestMethod fTestMethod) throws Exception {
return ((Class extends Throwable>) Whitebox.invokeMethod(fTestMethod, "getExpectedException")).getName();
}
}
}