Browse Source
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1073 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/head
4 changed files with 306 additions and 601 deletions
@ -1,354 +0,0 @@
@@ -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 @@
@@ -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()); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue