/org.springframework.orm/src/test/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java
Java | 311 lines | 166 code | 31 blank | 114 comment | 34 complexity | 12e2647cc5a159b6b70e7af09ca30cb3 MD5 | raw file
- /*
- * 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.annotation;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.util.Map;
- import javax.sql.DataSource;
- import junit.framework.AssertionFailedError;
- import org.springframework.context.ApplicationContext;
- import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
- import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
- import org.springframework.transaction.TransactionDefinition;
- import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
- import org.springframework.transaction.interceptor.TransactionAttributeSource;
- import org.springframework.util.Assert;
- /**
- * <p>
- * Java 5 specific subclass of
- * {@link AbstractTransactionalDataSourceSpringContextTests}, exposing a
- * {@link SimpleJdbcTemplate} and obeying annotations for transaction control.
- * </p>
- * <p>
- * For example, test methods can be annotated with the regular Spring
- * {@link org.springframework.transaction.annotation.Transactional @Transactional}
- * annotation (e.g., to force execution in a read-only transaction) or with the
- * {@link NotTransactional @NotTransactional} annotation to prevent any
- * transaction being created at all. In addition, individual test methods can be
- * annotated with {@link Rollback @Rollback} to override the
- * {@link #isDefaultRollback() default rollback} settings.
- * </p>
- * <p>
- * The following list constitutes all annotations currently supported by
- * AbstractAnnotationAwareTransactionalTests:
- * </p>
- * <ul>
- * <li>{@link DirtiesContext @DirtiesContext}</li>
- * <li>{@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
- * <li>{@link IfProfileValue @IfProfileValue}</li>
- * <li>{@link ExpectedException @ExpectedException}</li>
- * <li>{@link Timed @Timed}</li>
- * <li>{@link Repeat @Repeat}</li>
- * <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
- * <li>{@link NotTransactional @NotTransactional}</li>
- * <li>{@link Rollback @Rollback}</li>
- * </ul>
- *
- * @author Rod Johnson
- * @author Sam Brannen
- * @author Juergen Hoeller
- * @since 2.0
- * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
- * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
- */
- public abstract class AbstractAnnotationAwareTransactionalTests extends
- AbstractTransactionalDataSourceSpringContextTests {
- protected SimpleJdbcTemplate simpleJdbcTemplate;
- private final TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();
- /**
- * {@link ProfileValueSource} available to subclasses but primarily intended
- * for use in {@link #isDisabledInThisEnvironment(Method)}.
- * <p>Set to {@link SystemProfileValueSource} by default for backwards
- * compatibility; however, the value may be changed in the
- * {@link #AbstractAnnotationAwareTransactionalTests(String)} constructor.
- */
- protected ProfileValueSource profileValueSource = SystemProfileValueSource.getInstance();
- /**
- * Default constructor for AbstractAnnotationAwareTransactionalTests, which
- * delegates to {@link #AbstractAnnotationAwareTransactionalTests(String)}.
- */
- public AbstractAnnotationAwareTransactionalTests() {
- this(null);
- }
- /**
- * Constructs a new AbstractAnnotationAwareTransactionalTests instance with
- * the specified JUnit <code>name</code> and retrieves the configured (or
- * default) {@link ProfileValueSource}.
- * @param name the name of the current test
- * @see ProfileValueUtils#retrieveProfileValueSource(Class)
- */
- public AbstractAnnotationAwareTransactionalTests(String name) {
- super(name);
- this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
- }
- @Override
- public void setDataSource(DataSource dataSource) {
- super.setDataSource(dataSource);
- // JdbcTemplate will be identically configured
- this.simpleJdbcTemplate = new SimpleJdbcTemplate(this.jdbcTemplate);
- }
- /**
- * Search for a unique {@link ProfileValueSource} in the supplied
- * {@link ApplicationContext}. If found, the
- * <code>profileValueSource</code> for this test will be set to the unique
- * {@link ProfileValueSource}.
- * @param applicationContext the ApplicationContext in which to search for
- * the ProfileValueSource; may not be <code>null</code>
- * @deprecated Use {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} instead.
- */
- @Deprecated
- protected void findUniqueProfileValueSourceFromContext(ApplicationContext applicationContext) {
- Assert.notNull(applicationContext, "Can not search for a ProfileValueSource in a null ApplicationContext.");
- ProfileValueSource uniqueProfileValueSource = null;
- Map<?, ?> beans = applicationContext.getBeansOfType(ProfileValueSource.class);
- if (beans.size() == 1) {
- uniqueProfileValueSource = (ProfileValueSource) beans.values().iterator().next();
- }
- if (uniqueProfileValueSource != null) {
- this.profileValueSource = uniqueProfileValueSource;
- }
- }
- /**
- * Overridden to populate transaction definition from annotations.
- */
- @Override
- public void runBare() throws Throwable {
- // getName will return the name of the method being run.
- if (isDisabledInThisEnvironment(getName())) {
- // Let superclass log that we didn't run the test.
- super.runBare();
- return;
- }
- final Method testMethod = getTestMethod();
- if (isDisabledInThisEnvironment(testMethod)) {
- recordDisabled();
- this.logger.info("**** " + getClass().getName() + "." + getName() + " is disabled in this environment: "
- + "Total disabled tests=" + getDisabledTestCount());
- return;
- }
- TransactionDefinition explicitTransactionDefinition =
- this.transactionAttributeSource.getTransactionAttribute(testMethod, getClass());
- if (explicitTransactionDefinition != null) {
- this.logger.info("Custom transaction definition [" + explicitTransactionDefinition + "] for test method ["
- + getName() + "].");
- setTransactionDefinition(explicitTransactionDefinition);
- }
- else if (testMethod.isAnnotationPresent(NotTransactional.class)) {
- // Don't have any transaction...
- preventTransaction();
- }
- // Let JUnit handle execution. We're just changing the state of the test class first.
- runTestTimed(new TestExecutionCallback() {
- public void run() throws Throwable {
- try {
- AbstractAnnotationAwareTransactionalTests.super.runBare();
- }
- finally {
- // Mark the context to be blown away if the test was
- // annotated to result in setDirty being invoked
- // automatically.
- if (testMethod.isAnnotationPresent(DirtiesContext.class)) {
- AbstractAnnotationAwareTransactionalTests.this.setDirty();
- }
- }
- }
- }, testMethod);
- }
- /**
- * Determine if the test for the supplied <code>testMethod</code> should
- * run in the current environment.
- * <p>The default implementation is based on
- * {@link IfProfileValue @IfProfileValue} semantics.
- * @param testMethod the test method
- * @return <code>true</code> if the test is <em>disabled</em> in the current environment
- * @see ProfileValueUtils#isTestEnabledInThisEnvironment
- */
- protected boolean isDisabledInThisEnvironment(Method testMethod) {
- return !ProfileValueUtils.isTestEnabledInThisEnvironment(this.profileValueSource, testMethod, getClass());
- }
- /**
- * Get the current test method.
- */
- protected Method getTestMethod() {
- assertNotNull("TestCase.getName() cannot be null", getName());
- Method testMethod = null;
- try {
- // Use same algorithm as JUnit itself to retrieve the test method
- // about to be executed (the method name is returned by getName). It
- // has to be public so we can retrieve it.
- testMethod = getClass().getMethod(getName(), (Class[]) null);
- }
- catch (NoSuchMethodException ex) {
- fail("Method '" + getName() + "' not found");
- }
- if (!Modifier.isPublic(testMethod.getModifiers())) {
- fail("Method '" + getName() + "' should be public");
- }
- return testMethod;
- }
- /**
- * Determine whether or not to rollback transactions for the current test
- * by taking into consideration the
- * {@link #isDefaultRollback() default rollback} flag and a possible
- * method-level override via the {@link Rollback @Rollback} annotation.
- * @return the <em>rollback</em> flag for the current test
- */
- @Override
- protected boolean isRollback() {
- boolean rollback = isDefaultRollback();
- Rollback rollbackAnnotation = getTestMethod().getAnnotation(Rollback.class);
- if (rollbackAnnotation != null) {
- boolean rollbackOverride = rollbackAnnotation.value();
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback ["
- + rollback + "] for test [" + getName() + "].");
- }
- rollback = rollbackOverride;
- }
- else {
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("No method-level @Rollback override: using default rollback [" + rollback
- + "] for test [" + getName() + "].");
- }
- }
- return rollback;
- }
- private void runTestTimed(TestExecutionCallback tec, Method testMethod) throws Throwable {
- Timed timed = testMethod.getAnnotation(Timed.class);
- if (timed == null) {
- runTest(tec, testMethod);
- }
- else {
- long startTime = System.currentTimeMillis();
- try {
- runTest(tec, testMethod);
- }
- finally {
- long elapsed = System.currentTimeMillis() - startTime;
- if (elapsed > timed.millis()) {
- fail("Took " + elapsed + " ms; limit was " + timed.millis());
- }
- }
- }
- }
- private void runTest(TestExecutionCallback tec, Method testMethod) throws Throwable {
- ExpectedException expectedExceptionAnnotation = testMethod.getAnnotation(ExpectedException.class);
- boolean exceptionIsExpected = (expectedExceptionAnnotation != null && expectedExceptionAnnotation.value() != null);
- Class<? extends Throwable> expectedException = (exceptionIsExpected ? expectedExceptionAnnotation.value() : null);
- Repeat repeat = testMethod.getAnnotation(Repeat.class);
- int runs = ((repeat != null) && (repeat.value() > 1)) ? repeat.value() : 1;
- for (int i = 0; i < runs; i++) {
- try {
- if (runs > 1 && this.logger != null && this.logger.isInfoEnabled()) {
- this.logger.info("Repetition " + (i + 1) + " of test " + testMethod.getName());
- }
- tec.run();
- if (exceptionIsExpected) {
- fail("Expected exception: " + expectedException.getName());
- }
- }
- catch (Throwable t) {
- if (!exceptionIsExpected) {
- throw t;
- }
- if (!expectedException.isAssignableFrom(t.getClass())) {
- // Wrap the unexpected throwable with an explicit message.
- AssertionFailedError assertionError = new AssertionFailedError("Unexpected exception, expected<" +
- expectedException.getName() + "> but was<" + t.getClass().getName() + ">");
- assertionError.initCause(t);
- throw assertionError;
- }
- }
- }
- }
- private static interface TestExecutionCallback {
- void run() throws Throwable;
- }
- }