[SPR-5145] Completed migration to JUnit 4.5: SpringJUnit4ClassRunner now extends BlockJUnit4ClassRunner and uses custom Statements; removed obsolete SpringMethodRoadie and SpringTestMethod classes.
This commit is contained in:
@@ -16,41 +16,56 @@
|
||||
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.internal.runners.InitializationError;
|
||||
import org.junit.internal.runners.JUnit4ClassRunner;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.AssumptionViolatedException;
|
||||
import org.junit.internal.runners.model.EachTestNotifier;
|
||||
import org.junit.internal.runners.model.ReflectiveCallable;
|
||||
import org.junit.internal.runners.statements.ExpectException;
|
||||
import org.junit.internal.runners.statements.Fail;
|
||||
import org.junit.internal.runners.statements.FailOnTimeout;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runner.notification.Failure;
|
||||
import org.junit.runner.notification.RunNotifier;
|
||||
import org.junit.runners.BlockJUnit4ClassRunner;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.test.annotation.ExpectedException;
|
||||
import org.springframework.test.annotation.ProfileValueUtils;
|
||||
import org.springframework.test.annotation.Repeat;
|
||||
import org.springframework.test.annotation.Timed;
|
||||
import org.springframework.test.context.TestContextManager;
|
||||
import org.springframework.test.context.junit4.statements.RunSpringTestContextAfters;
|
||||
import org.springframework.test.context.junit4.statements.RunSpringTestContextBefores;
|
||||
import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
|
||||
import org.springframework.test.context.junit4.statements.SpringRepeat;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* SpringJUnit4ClassRunner is a custom extension of {@link JUnit4ClassRunner}
|
||||
* which provides functionality of the <em>Spring TestContext Framework</em> to
|
||||
* standard JUnit 4.5+ tests by means of the {@link TestContextManager} and
|
||||
* associated support classes and annotations.
|
||||
* <code>SpringJUnit4ClassRunner</code> is a custom extension of
|
||||
* {@link BlockJUnit4ClassRunner} which provides functionality of the
|
||||
* <em>Spring TestContext Framework</em> to standard JUnit 4.5+ tests by means
|
||||
* of the {@link TestContextManager} and associated support classes and
|
||||
* annotations.
|
||||
* </p>
|
||||
* <p>
|
||||
* The following list constitutes all annotations currently supported directly
|
||||
* by SpringJUnit4ClassRunner. <em>(Note that additional annotations
|
||||
* may be supported by various
|
||||
* by <code>SpringJUnit4ClassRunner</code>.
|
||||
* <em>(Note that additional annotations may be supported by various
|
||||
* {@link org.springframework.test.context.TestExecutionListener
|
||||
* TestExecutionListeners})</em>
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>{@link org.junit.Test#expected() @Test(expected=...)}</li>
|
||||
* <li>{@link org.springframework.test.annotation.ExpectedException
|
||||
* @ExpectedException}</li>
|
||||
* <li>{@link org.junit.Test#timeout() @Test(timeout=...)}</li>
|
||||
* <li>{@link org.springframework.test.annotation.Timed @Timed}</li>
|
||||
* <li>{@link org.springframework.test.annotation.Repeat @Repeat}</li>
|
||||
* <li>{@link org.junit.Ignore @Ignore}</li>
|
||||
* <li>{@link Test#expected() @Test(expected=...)}</li>
|
||||
* <li>{@link ExpectedException @ExpectedException}</li>
|
||||
* <li>{@link Test#timeout() @Test(timeout=...)}</li>
|
||||
* <li>{@link Timed @Timed}</li>
|
||||
* <li>{@link Repeat @Repeat}</li>
|
||||
* <li>{@link Ignore @Ignore}</li>
|
||||
* <li>
|
||||
* {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
|
||||
* @ProfileValueSourceConfiguration}</li>
|
||||
@@ -58,9 +73,8 @@ import org.springframework.test.context.TestContextManager;
|
||||
* @IfProfileValue}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <b>NOTE:</b> As of Spring 3.0 M1, SpringJUnit4ClassRunner requires JUnit 4.5,
|
||||
* while internally still being based on JUnit 4.4 SPI. This will be rewritten
|
||||
* based on JUnit 4.5's BlockJUnit4ClassRunner in a later Spring 3.0 release.
|
||||
* <b>NOTE:</b> As of Spring 3.0, <code>SpringJUnit4ClassRunner</code> requires
|
||||
* JUnit 4.5.
|
||||
* </p>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
@@ -68,7 +82,7 @@ import org.springframework.test.context.TestContextManager;
|
||||
* @since 2.5
|
||||
* @see TestContextManager
|
||||
*/
|
||||
public class SpringJUnit4ClassRunner extends JUnit4ClassRunner {
|
||||
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class);
|
||||
|
||||
@@ -91,40 +105,6 @@ public class SpringJUnit4ClassRunner extends JUnit4ClassRunner {
|
||||
this.testContextManager = createTestContextManager(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the test is enabled in the first place. This prevents
|
||||
* classes with a non-matching <code>@IfProfileValue</code> annotation
|
||||
* from running altogether, even skipping the execution of
|
||||
* <code>prepareTestInstance</code> listener methods.
|
||||
*
|
||||
* @see org.springframework.test.annotation.IfProfileValue
|
||||
* @see org.springframework.test.context.TestExecutionListener
|
||||
*/
|
||||
@Override
|
||||
public void run(RunNotifier notifier) {
|
||||
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
|
||||
notifier.fireTestIgnored(getDescription());
|
||||
return;
|
||||
}
|
||||
super.run(notifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link JUnit4ClassRunner#createTest()} to create the test
|
||||
* instance and then to a {@link TestContextManager} to
|
||||
* {@link TestContextManager#prepareTestInstance(Object) prepare} the test
|
||||
* instance for Spring testing functionality.
|
||||
*
|
||||
* @see JUnit4ClassRunner#createTest()
|
||||
* @see TestContextManager#prepareTestInstance(Object)
|
||||
*/
|
||||
@Override
|
||||
protected Object createTest() throws Exception {
|
||||
Object testInstance = super.createTest();
|
||||
getTestContextManager().prepareTestInstance(testInstance);
|
||||
return testInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TestContextManager}. Can be overridden by
|
||||
* subclasses.
|
||||
@@ -144,37 +124,299 @@ public class SpringJUnit4ClassRunner extends JUnit4ClassRunner {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the supplied {@link Method test method} and notifies the supplied
|
||||
* {@link RunNotifier} of the appropriate events.
|
||||
* Delegates to the parent implementation for creating the test instance and
|
||||
* then allows the {@link #getTestContextManager() TestContextManager} to
|
||||
* prepare the test instance before returning it.
|
||||
*
|
||||
* @see #createTest()
|
||||
* @see JUnit4ClassRunner#invokeTestMethod(Method,RunNotifier)
|
||||
* @see TestContextManager#prepareTestInstance(Object)
|
||||
*/
|
||||
@Override
|
||||
protected void invokeTestMethod(Method method, RunNotifier notifier) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invoking test method [" + method.toGenericString() + "]");
|
||||
protected Object createTest() throws Exception {
|
||||
Object testInstance = super.createTest();
|
||||
getTestContextManager().prepareTestInstance(testInstance);
|
||||
return testInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description suitable for an ignored test class if the test is
|
||||
* disabled via <code>@IfProfileValue</code> at the class-level, and
|
||||
* otherwise delegates to the parent implementation.
|
||||
*
|
||||
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
|
||||
*/
|
||||
@Override
|
||||
public Description getDescription() {
|
||||
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
|
||||
return Description.createSuiteDescription(getTestClass().getJavaClass());
|
||||
}
|
||||
return super.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the test is enabled in the first place. This prevents
|
||||
* classes with a non-matching <code>@IfProfileValue</code> annotation
|
||||
* from running altogether, even skipping the execution of
|
||||
* <code>prepareTestInstance()</code> <code>TestExecutionListener</code>
|
||||
* methods.
|
||||
*
|
||||
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
|
||||
* @see org.springframework.test.annotation.IfProfileValue
|
||||
* @see org.springframework.test.context.TestExecutionListener
|
||||
*/
|
||||
@Override
|
||||
public void run(RunNotifier notifier) {
|
||||
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
|
||||
notifier.fireTestIgnored(getDescription());
|
||||
return;
|
||||
}
|
||||
super.run(notifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the same logic as
|
||||
* {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
|
||||
* except that tests are determined to be <em>ignored</em> by
|
||||
* {@link #isTestMethodIgnored(FrameworkMethod)}.
|
||||
*/
|
||||
@Override
|
||||
protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
|
||||
EachTestNotifier eachNotifier = makeNotifier(frameworkMethod, notifier);
|
||||
if (isTestMethodIgnored(frameworkMethod)) {
|
||||
eachNotifier.fireTestIgnored();
|
||||
return;
|
||||
}
|
||||
|
||||
// The following is a 1-to-1 copy of the original JUnit 4.4 code, except
|
||||
// that we use custom implementations for TestMethod and MethodRoadie.
|
||||
eachNotifier.fireTestStarted();
|
||||
try {
|
||||
methodBlock(frameworkMethod).evaluate();
|
||||
}
|
||||
catch (AssumptionViolatedException e) {
|
||||
eachNotifier.addFailedAssumption(e);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
eachNotifier.addFailure(e);
|
||||
}
|
||||
finally {
|
||||
eachNotifier.fireTestFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private EachTestNotifier makeNotifier(FrameworkMethod frameworkMethod, RunNotifier notifier) {
|
||||
Description description = describeChild(frameworkMethod);
|
||||
return new EachTestNotifier(notifier, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the default JUnit behavior
|
||||
* {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with
|
||||
* potential repeats} of the entire execution chain.
|
||||
* <p>
|
||||
* Furthermore, support for timeouts has been moved down the execution chain
|
||||
* in order to include execution of {@link org.junit.Before @Before}
|
||||
* and {@link org.junit.After @After} methods within the timed
|
||||
* execution. Note that this differs from the default JUnit behavior of
|
||||
* executing <code>@Before</code> and <code>@After</code> methods
|
||||
* in the main thread while executing the actual test method in a separate
|
||||
* thread. Thus, the end effect is that <code>@Before</code> and
|
||||
* <code>@After</code> methods will be executed in the same thread as
|
||||
* the test method. As a consequence, JUnit-specified timeouts will work
|
||||
* fine in combination with Spring transactions. Note that JUnit-specific
|
||||
* timeouts still differ from Spring-specific timeouts in that the former
|
||||
* execute in a separate thread while the latter simply execute in the main
|
||||
* thread (like regular tests).
|
||||
* </p>
|
||||
*
|
||||
* @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement)
|
||||
* @see #withBefores(FrameworkMethod, Object, Statement)
|
||||
* @see #withAfters(FrameworkMethod, Object, Statement)
|
||||
* @see #withPotentialTimeout(FrameworkMethod, Object, Statement)
|
||||
* @see #withPotentialRepeat(FrameworkMethod, Object, Statement)
|
||||
*/
|
||||
@Override
|
||||
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
|
||||
|
||||
Description description = methodDescription(method);
|
||||
Object testInstance;
|
||||
try {
|
||||
testInstance = createTest();
|
||||
testInstance = new ReflectiveCallable() {
|
||||
|
||||
@Override
|
||||
protected Object runReflectiveCall() throws Throwable {
|
||||
return createTest();
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
notifier.fireTestFailure(new Failure(description, ex.getCause()));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
notifier.fireTestFailure(new Failure(description, ex));
|
||||
return;
|
||||
catch (Throwable e) {
|
||||
return new Fail(e);
|
||||
}
|
||||
|
||||
SpringTestMethod testMethod = new SpringTestMethod(method, getTestClass());
|
||||
new SpringMethodRoadie(getTestContextManager(), testInstance, testMethod, notifier, description).run();
|
||||
Statement statement = methodInvoker(frameworkMethod, testInstance);
|
||||
statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
|
||||
statement = withBefores(frameworkMethod, testInstance, statement);
|
||||
statement = withAfters(frameworkMethod, testInstance, statement);
|
||||
statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
|
||||
statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if {@link Ignore @Ignore} is present for
|
||||
* the supplied {@link FrameworkMethod test method} or if the test method is
|
||||
* disabled via <code>@IfProfileValue</code>.
|
||||
*
|
||||
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class)
|
||||
*/
|
||||
protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) {
|
||||
Method method = frameworkMethod.getMethod();
|
||||
return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method,
|
||||
getTestClass().getJavaClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the same logic as
|
||||
* {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)}
|
||||
* except that the <em>expected exception</em> is retrieved using
|
||||
* {@link #getExpectedException(FrameworkMethod)}.
|
||||
*/
|
||||
@Override
|
||||
protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
|
||||
Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod);
|
||||
return expectedException != null ? new ExpectException(next, expectedException) : next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <code>exception</code> that the supplied {@link FrameworkMethod
|
||||
* test method} is expected to throw.
|
||||
* <p>
|
||||
* Supports both Spring's {@link ExpectedException @ExpectedException(...)}
|
||||
* and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but
|
||||
* not both simultaneously.
|
||||
* </p>
|
||||
*
|
||||
* @return the expected exception, or <code>null</code> if none was
|
||||
* specified
|
||||
*/
|
||||
protected Class<? extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) {
|
||||
Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
|
||||
Class<? extends Throwable> junitExpectedException = testAnnotation != null
|
||||
&& testAnnotation.expected() != Test.None.class ? testAnnotation.expected() : null;
|
||||
|
||||
ExpectedException expectedExAnn = frameworkMethod.getAnnotation(ExpectedException.class);
|
||||
Class<? extends Throwable> springExpectedException = (expectedExAnn != null ? expectedExAnn.value() : null);
|
||||
|
||||
if (springExpectedException != null && junitExpectedException != null) {
|
||||
String msg = "Test method [" + frameworkMethod.getMethod()
|
||||
+ "] has been configured with Spring's @ExpectedException(" + springExpectedException.getName()
|
||||
+ ".class) and JUnit's @Test(expected=" + junitExpectedException.getName()
|
||||
+ ".class) annotations. "
|
||||
+ "Only one declaration of an 'expected exception' is permitted per test method.";
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
|
||||
return springExpectedException != null ? springExpectedException : junitExpectedException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports both Spring's {@link Timed @Timed} and JUnit's
|
||||
* {@link Test#timeout() @Test(timeout=...)} annotations, but not both
|
||||
* simultaneously. Returns either a {@link SpringFailOnTimeout}, a
|
||||
* {@link FailOnTimeout}, or the unmodified, supplied {@link Statement} as
|
||||
* appropriate.
|
||||
*
|
||||
* @see #getSpringTimeout(FrameworkMethod)
|
||||
* @see #getJUnitTimeout(FrameworkMethod)
|
||||
*/
|
||||
@Override
|
||||
protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
|
||||
Statement statement = null;
|
||||
long springTimeout = getSpringTimeout(frameworkMethod);
|
||||
long junitTimeout = getJUnitTimeout(frameworkMethod);
|
||||
if (springTimeout > 0 && junitTimeout > 0) {
|
||||
String msg = "Test method [" + frameworkMethod.getMethod()
|
||||
+ "] has been configured with Spring's @Timed(millis=" + springTimeout
|
||||
+ ") and JUnit's @Test(timeout=" + junitTimeout
|
||||
+ ") annotations. Only one declaration of a 'timeout' is permitted per test method.";
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
else if (springTimeout > 0) {
|
||||
statement = new SpringFailOnTimeout(next, springTimeout);
|
||||
}
|
||||
else if (junitTimeout > 0) {
|
||||
statement = new FailOnTimeout(next, junitTimeout);
|
||||
}
|
||||
else {
|
||||
statement = next;
|
||||
}
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configured JUnit <code>timeout</code> from the {@link Test
|
||||
* @Test} annotation on the supplied {@link FrameworkMethod test
|
||||
* method}.
|
||||
*
|
||||
* @return the timeout, or <code>0</code> if none was specified.
|
||||
*/
|
||||
protected long getJUnitTimeout(FrameworkMethod frameworkMethod) {
|
||||
Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
|
||||
return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configured Spring-specific <code>timeout</code> from the
|
||||
* {@link Timed @Timed} annotation on the supplied
|
||||
* {@link FrameworkMethod test method}.
|
||||
*
|
||||
* @return the timeout, or <code>0</code> if none was specified.
|
||||
*/
|
||||
protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
|
||||
Timed timedAnnotation = frameworkMethod.getAnnotation(Timed.class);
|
||||
return (timedAnnotation != null && timedAnnotation.millis() > 0 ? timedAnnotation.millis() : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the {@link Statement} returned by the parent implementation with a
|
||||
* {@link RunSpringTestContextBefores} statement, thus preserving the
|
||||
* default functionality but adding support for the Spring TestContext
|
||||
* Framework.
|
||||
*
|
||||
* @see RunSpringTestContextBefores
|
||||
*/
|
||||
@Override
|
||||
protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
|
||||
Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
|
||||
return new RunSpringTestContextBefores(junitBefores, testInstance, frameworkMethod.getMethod(),
|
||||
getTestContextManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the {@link Statement} returned by the parent implementation with a
|
||||
* {@link RunSpringTestContextAfters} statement, thus preserving the default
|
||||
* functionality but adding support for the Spring TestContext Framework.
|
||||
*
|
||||
* @see RunSpringTestContextAfters
|
||||
*/
|
||||
@Override
|
||||
protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
|
||||
Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
|
||||
return new RunSpringTestContextAfters(junitAfters, testInstance, frameworkMethod.getMethod(),
|
||||
getTestContextManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports Spring's {@link Repeat @Repeat} annotation by returning a
|
||||
* {@link SpringRepeat} statement initialized with the configured repeat
|
||||
* count or <code>1</code> if no repeat count is configured.
|
||||
*
|
||||
* @see SpringRepeat
|
||||
*/
|
||||
protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
|
||||
Repeat repeatAnnotation = frameworkMethod.getAnnotation(Repeat.class);
|
||||
int repeat = (repeatAnnotation != null ? repeatAnnotation.value() : 1);
|
||||
return new SpringRepeat(next, frameworkMethod.getMethod(), repeat);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-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.springframework.test.context.junit4;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.internal.AssumptionViolatedException;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runner.notification.Failure;
|
||||
import org.junit.runner.notification.RunNotifier;
|
||||
|
||||
import org.springframework.test.annotation.Repeat;
|
||||
import org.springframework.test.annotation.Timed;
|
||||
import org.springframework.test.context.TestContextManager;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* <code>SpringMethodRoadie</code> is a custom implementation of JUnit 4.4's
|
||||
* {@link org.junit.internal.runners.MethodRoadie MethodRoadie}, which provides
|
||||
* the following enhancements:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Notifies a {@link TestContextManager} of
|
||||
* {@link TestContextManager#beforeTestMethod(Object,Method) before} and
|
||||
* {@link TestContextManager#afterTestMethod(Object,Method,Throwable) after}
|
||||
* events.</li>
|
||||
* <li>Uses a {@link SpringTestMethod} instead of JUnit 4.4's
|
||||
* {@link org.junit.internal.runners.TestMethod TestMethod}.</li>
|
||||
* <li>Tracks the exception thrown during execution of the test method.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Due to method and field visibility constraints, the code of
|
||||
* <code>MethodRoadie</code> has been duplicated here instead of subclassing
|
||||
* <code>MethodRoadie</code> directly.
|
||||
* </p>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
*/
|
||||
class SpringMethodRoadie {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(SpringMethodRoadie.class);
|
||||
|
||||
private final TestContextManager testContextManager;
|
||||
|
||||
private final Object testInstance;
|
||||
|
||||
private final SpringTestMethod testMethod;
|
||||
|
||||
private final RunNotifier notifier;
|
||||
|
||||
private final Description description;
|
||||
|
||||
private Throwable testException;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new <code>SpringMethodRoadie</code>.
|
||||
* @param testContextManager the TestContextManager to notify
|
||||
* @param testInstance the test instance upon which to invoke the test method
|
||||
* @param testMethod the test method to invoke
|
||||
* @param notifier the RunNotifier to notify
|
||||
* @param description the test description
|
||||
*/
|
||||
public SpringMethodRoadie(TestContextManager testContextManager, Object testInstance,
|
||||
SpringTestMethod testMethod, RunNotifier notifier, Description description) {
|
||||
|
||||
this.testContextManager = testContextManager;
|
||||
this.testInstance = testInstance;
|
||||
this.testMethod = testMethod;
|
||||
this.notifier = notifier;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the <em>test</em>, including notification of events to the
|
||||
* {@link RunNotifier} and {@link TestContextManager} as well as proper
|
||||
* handling of {@link org.junit.Ignore @Ignore},
|
||||
* {@link org.junit.Test#expected() expected exceptions},
|
||||
* {@link org.junit.Test#timeout() test timeouts}, and
|
||||
* {@link org.junit.Assume.AssumptionViolatedException assumptions}.
|
||||
*/
|
||||
public void run() {
|
||||
if (this.testMethod.isIgnored()) {
|
||||
this.notifier.fireTestIgnored(this.description);
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifier.fireTestStarted(this.description);
|
||||
try {
|
||||
Timed timedAnnotation = this.testMethod.getMethod().getAnnotation(Timed.class);
|
||||
long springTimeout = (timedAnnotation != null && timedAnnotation.millis() > 0 ?
|
||||
timedAnnotation.millis() : 0);
|
||||
long junitTimeout = this.testMethod.getTimeout();
|
||||
if (springTimeout > 0 && junitTimeout > 0) {
|
||||
throw new IllegalStateException("Test method [" + this.testMethod.getMethod() +
|
||||
"] has been configured with Spring's @Timed(millis=" + springTimeout +
|
||||
") and JUnit's @Test(timeout=" + junitTimeout +
|
||||
") annotations. Only one declaration of a 'timeout' is permitted per test method.");
|
||||
}
|
||||
else if (springTimeout > 0) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
runTest();
|
||||
}
|
||||
finally {
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
if (elapsed > springTimeout) {
|
||||
addFailure(new TimeoutException("Took " + elapsed + " ms; limit was " + springTimeout));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (junitTimeout > 0) {
|
||||
runWithTimeout(junitTimeout);
|
||||
}
|
||||
else {
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.notifier.fireTestFinished(this.description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test method on the test instance with the specified
|
||||
* <code>timeout</code>.
|
||||
* @param timeout the timeout in milliseconds
|
||||
* @see #runWithRepetitions(Runnable)
|
||||
* @see #runTestMethod()
|
||||
*/
|
||||
protected void runWithTimeout(final long timeout) throws CancellationException {
|
||||
runWithRepetitions(new Runnable() {
|
||||
public void run() {
|
||||
ExecutorService service = Executors.newSingleThreadExecutor();
|
||||
Future result = service.submit(new RunBeforesThenTestThenAfters());
|
||||
service.shutdown();
|
||||
try {
|
||||
boolean terminated = service.awaitTermination(timeout, TimeUnit.MILLISECONDS);
|
||||
if (!terminated) {
|
||||
service.shutdownNow();
|
||||
}
|
||||
// Throws the exception if one occurred during the invocation.
|
||||
result.get(0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (TimeoutException ex) {
|
||||
String message = "Test timed out after " + timeout + " milliseconds";
|
||||
addFailure(new TimeoutException(message));
|
||||
// We're cancelling repetitions here since we don't want
|
||||
// the abandoned test method execution to conflict with
|
||||
// further execution attempts of the same test method.
|
||||
throw new CancellationException(message);
|
||||
}
|
||||
catch (ExecutionException ex) {
|
||||
addFailure(ex.getCause());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
addFailure(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test, including {@link #runBefores() @Before} and
|
||||
* {@link #runAfters() @After} methods.
|
||||
* @see #runWithRepetitions(Runnable)
|
||||
* @see #runTestMethod()
|
||||
*/
|
||||
protected void runTest() {
|
||||
runWithRepetitions(new RunBeforesThenTestThenAfters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the supplied <code>test</code> with repetitions. Checks for the
|
||||
* presence of {@link Repeat @Repeat} to determine if the test should be run
|
||||
* more than once. The test will be run at least once.
|
||||
* @param test the runnable test
|
||||
* @see Repeat
|
||||
*/
|
||||
protected void runWithRepetitions(Runnable test) {
|
||||
Method method = this.testMethod.getMethod();
|
||||
Repeat repeat = method.getAnnotation(Repeat.class);
|
||||
int runs = (repeat != null && repeat.value() > 1 ? repeat.value() : 1);
|
||||
|
||||
for (int i = 0; i < runs; i++) {
|
||||
if (runs > 1 && logger.isInfoEnabled()) {
|
||||
logger.info("Repetition " + (i + 1) + " of test " + method.getName());
|
||||
}
|
||||
try {
|
||||
test.run();
|
||||
}
|
||||
catch (CancellationException ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the test method on the test instance, processing exceptions
|
||||
* (both expected and unexpected), assumptions, and registering
|
||||
* failures as necessary.
|
||||
*/
|
||||
protected void runTestMethod() {
|
||||
this.testException = null;
|
||||
try {
|
||||
this.testMethod.invoke(this.testInstance);
|
||||
if (this.testMethod.expectsException()) {
|
||||
addFailure(new AssertionError("Expected exception: " + this.testMethod.getExpectedException().getName()));
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
this.testException = ex.getTargetException();
|
||||
if (!(this.testException instanceof AssumptionViolatedException)) {
|
||||
if (!this.testMethod.expectsException()) {
|
||||
addFailure(this.testException);
|
||||
}
|
||||
else if (this.testMethod.isUnexpected(this.testException)) {
|
||||
addFailure(new Exception("Unexpected exception, expected <" +
|
||||
this.testMethod.getExpectedException().getName() + "> but was <" +
|
||||
this.testException.getClass().getName() + ">", this.testException));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
addFailure(ex);
|
||||
}
|
||||
finally {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Test method [" + this.testMethod.getMethod() + "] threw exception: " +
|
||||
this.testException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link TestContextManager#beforeTestMethod} and then runs
|
||||
* {@link org.junit.Before @Before methods}, registering failures
|
||||
* and throwing {@link FailedBefore} exceptions as necessary.
|
||||
* @throws FailedBefore if an error occurs while executing a <em>before</em> method
|
||||
*/
|
||||
protected void runBefores() throws FailedBefore {
|
||||
try {
|
||||
this.testContextManager.beforeTestMethod(this.testInstance, this.testMethod.getMethod());
|
||||
List<Method> befores = this.testMethod.getBefores();
|
||||
for (Method before : befores) {
|
||||
before.invoke(this.testInstance);
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
Throwable targetEx = ex.getTargetException();
|
||||
if (!(targetEx instanceof AssumptionViolatedException)) {
|
||||
addFailure(targetEx);
|
||||
}
|
||||
throw new FailedBefore();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
addFailure(ex);
|
||||
throw new FailedBefore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs {@link org.junit.After @After methods}, registering failures as
|
||||
* necessary, and then calls {@link TestContextManager#afterTestMethod}.
|
||||
*/
|
||||
protected void runAfters() {
|
||||
List<Method> afters = this.testMethod.getAfters();
|
||||
for (Method after : afters) {
|
||||
try {
|
||||
after.invoke(this.testInstance);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
addFailure(ex.getTargetException());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
addFailure(ex);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.testContextManager.afterTestMethod(this.testInstance, this.testMethod.getMethod(), this.testException);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
addFailure(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a failure for the supplied <code>exception</code> with the
|
||||
* {@link RunNotifier}.
|
||||
* @param exception the exception upon which to base the failure
|
||||
*/
|
||||
protected void addFailure(Throwable exception) {
|
||||
this.notifier.fireTestFailure(new Failure(this.description, exception));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the test method, executing <code>@Before</code> and <code>@After</code>
|
||||
* methods accordingly.
|
||||
*/
|
||||
private class RunBeforesThenTestThenAfters implements Runnable {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
runBefores();
|
||||
runTestMethod();
|
||||
}
|
||||
catch (FailedBefore ex) {
|
||||
// ignore
|
||||
}
|
||||
finally {
|
||||
runAfters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marker exception to signal that an exception was encountered while
|
||||
* executing an {@link org.junit.Before @Before} method.
|
||||
*/
|
||||
private static class FailedBefore extends Exception {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-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.springframework.test.context.junit4;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.Test.None;
|
||||
import org.junit.internal.runners.TestClass;
|
||||
|
||||
import org.springframework.test.annotation.ExpectedException;
|
||||
import org.springframework.test.annotation.ProfileValueSource;
|
||||
import org.springframework.test.annotation.ProfileValueUtils;
|
||||
|
||||
/**
|
||||
* SpringTestMethod is a custom implementation of JUnit 4.4's
|
||||
* {@link org.junit.internal.runners.TestMethod TestMethod}. Due to method and
|
||||
* field visibility constraints, the code of TestMethod has been duplicated here
|
||||
* instead of subclassing TestMethod directly.
|
||||
*
|
||||
* <p>SpringTestMethod also provides support for
|
||||
* {@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}
|
||||
* and {@link ExpectedException @ExpectedException}. See {@link #isIgnored()}
|
||||
* and {@link #getExpectedException()} for further details.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
class SpringTestMethod {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpringTestMethod.class);
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final TestClass testClass;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a test method for the supplied {@link Method method} and
|
||||
* {@link TestClass test class}; and retrieves the configured (or default)
|
||||
* {@link ProfileValueSource}.
|
||||
* @param method The test method
|
||||
* @param testClass the test class
|
||||
*/
|
||||
public SpringTestMethod(Method method, TestClass testClass) {
|
||||
this.method = method;
|
||||
this.testClass = testClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if this test method is {@link Test#expected() expected} to
|
||||
* throw an exception.
|
||||
*/
|
||||
public boolean expectsException() {
|
||||
return (getExpectedException() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link After @After} methods for this test method.
|
||||
*/
|
||||
public List<Method> getAfters() {
|
||||
return getTestClass().getAnnotatedMethods(After.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Before @Before} methods for this test method.
|
||||
*/
|
||||
public List<Method> getBefores() {
|
||||
return getTestClass().getAnnotatedMethods(Before.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <code>exception</code> that this test method is expected to throw.
|
||||
* <p>Supports both Spring's {@link ExpectedException @ExpectedException(...)}
|
||||
* and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but
|
||||
* not both simultaneously.
|
||||
* @return the expected exception, or <code>null</code> if none was specified
|
||||
*/
|
||||
public Class<? extends Throwable> getExpectedException() throws IllegalStateException {
|
||||
ExpectedException expectedExAnn = getMethod().getAnnotation(ExpectedException.class);
|
||||
Test testAnnotation = getMethod().getAnnotation(Test.class);
|
||||
|
||||
Class<? extends Throwable> expectedException = null;
|
||||
Class<? extends Throwable> springExpectedException =
|
||||
(expectedExAnn != null && expectedExAnn.value() != null ? expectedExAnn.value() : null);
|
||||
Class<? extends Throwable> junitExpectedException =
|
||||
(testAnnotation != null && testAnnotation.expected() != None.class ? testAnnotation.expected() : null);
|
||||
|
||||
if (springExpectedException != null && junitExpectedException != null) {
|
||||
String msg = "Test method [" + getMethod() + "] has been configured with Spring's @ExpectedException(" +
|
||||
springExpectedException.getName() + ".class) and JUnit's @Test(expected=" +
|
||||
junitExpectedException.getName() + ".class) annotations. " +
|
||||
"Only one declaration of an 'expected exception' is permitted per test method.";
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
else if (springExpectedException != null) {
|
||||
expectedException = springExpectedException;
|
||||
}
|
||||
else if (junitExpectedException != null) {
|
||||
expectedException = junitExpectedException;
|
||||
}
|
||||
|
||||
return expectedException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual {@link Method method} referenced by this test method.
|
||||
*/
|
||||
public final Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link TestClass test class} for this test method.
|
||||
*/
|
||||
public final TestClass getTestClass() {
|
||||
return this.testClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured <code>timeout</code> for this test method.
|
||||
* <p>Supports JUnit's {@link Test#timeout() @Test(timeout=...)} annotation.
|
||||
* @return the timeout, or <code>0</code> if none was specified
|
||||
*/
|
||||
public long getTimeout() {
|
||||
Test testAnnotation = getMethod().getAnnotation(Test.class);
|
||||
return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for {@link Method#invoke(Object,Object...) invoking}
|
||||
* the method associated with this test method. Throws exceptions consistent
|
||||
* with {@link Method#invoke(Object,Object...) Method.invoke()}.
|
||||
* @param testInstance the test instance upon which to invoke the method
|
||||
*/
|
||||
public void invoke(Object testInstance) throws IllegalAccessException, InvocationTargetException {
|
||||
getMethod().invoke(testInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this test method should be ignored.
|
||||
* @return <code>true</code> if this test method should be ignored
|
||||
* @see ProfileValueUtils#isTestEnabledInThisEnvironment
|
||||
*/
|
||||
public boolean isIgnored() {
|
||||
return (getMethod().isAnnotationPresent(Ignore.class) ||
|
||||
!ProfileValueUtils.isTestEnabledInThisEnvironment(this.method, this.testClass.getJavaClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this test method {@link Test#expected() expects} exceptions
|
||||
* of the type of the supplied <code>exception</code> to be thrown.
|
||||
* @param exception the thrown exception
|
||||
* @return <code>true</code> if the supplied exception was of an expected type
|
||||
*/
|
||||
public boolean isUnexpected(Throwable exception) {
|
||||
return !getExpectedException().isAssignableFrom(exception.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<body>
|
||||
|
||||
<p>Support classes for ApplicationContext-based and transactional
|
||||
tests run with JUnit 4.4 and the <em>Spring TestContext Framework</em>.</p>
|
||||
tests run with JUnit 4.5 and the <em>Spring TestContext Framework</em>.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user