Merge from sbrannen/SPR-11896

* SPR-11896:
  Introduce @SqlConfig for common SQL script config
This commit is contained in:
Sam Brannen 2014-07-16 17:23:23 +02:00
commit bbfc31b11f
11 changed files with 770 additions and 143 deletions

View File

@ -0,0 +1,232 @@
/*
* Copyright 2002-2014 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.jdbc;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import org.springframework.util.Assert;
/**
* {@code MergedSqlConfig} encapsulates the <em>merged</em> SQL script configuration
* declared via {@link SqlConfig @SqlConfig} on a test method and its enclosing
* test class.
* <p>Explicit method-level configuration overrides class-level configuration.
*
* @author Sam Brannen
* @since 4.1
* @see SqlConfig
*/
class MergedSqlConfig {
private final String dataSource;
private final String transactionManager;
private final TransactionMode transactionMode;
private final String encoding;
private final String separator;
private final String commentPrefix;
private final String blockCommentStartDelimiter;
private final String blockCommentEndDelimiter;
private final ErrorMode errorMode;
private static TransactionMode retrieveTransactionMode(AnnotationAttributes attributes) {
TransactionMode transactionMode = attributes.getEnum("transactionMode");
if (transactionMode == TransactionMode.DEFAULT) {
transactionMode = TransactionMode.INFERRED;
}
return transactionMode;
}
private static ErrorMode retrieveErrorMode(AnnotationAttributes attributes) {
ErrorMode errorMode = attributes.getEnum("errorMode");
if (errorMode == ErrorMode.DEFAULT) {
errorMode = ErrorMode.FAIL_ON_ERROR;
}
return errorMode;
}
private static String retrieveSeparator(AnnotationAttributes attributes) {
String separator = attributes.getString("separator");
if (separator != null && separator.equals("")) {
separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
}
return separator;
}
private static String retrieveCommentPrefix(AnnotationAttributes attributes) {
String commentPrefix = attributes.getString("commentPrefix");
if (commentPrefix != null && commentPrefix.equals("")) {
commentPrefix = ScriptUtils.DEFAULT_COMMENT_PREFIX;
}
return commentPrefix;
}
private static String retrieveBlockCommentStartDelimiter(AnnotationAttributes attributes) {
String blockCommentStartDelimiter = attributes.getString("blockCommentStartDelimiter");
if (blockCommentStartDelimiter != null && blockCommentStartDelimiter.equals("")) {
blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
}
return blockCommentStartDelimiter;
}
private static String retrieveBlockCommentEndDelimiter(AnnotationAttributes attributes) {
String blockCommentEndDelimiter = attributes.getString("blockCommentEndDelimiter");
if (blockCommentEndDelimiter != null && blockCommentEndDelimiter.equals("")) {
blockCommentEndDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
}
return blockCommentEndDelimiter;
}
/**
* Construct a {@code MergedSqlConfig} instance by merging the configuration
* from the supplied local (potentially method-level) {@code @SqlConfig} annotation
* with class-level configuration discovered on the supplied {@code testClass}.
* <p>Local configuration overrides class-level configuration.
* <p>If the test class is not annotated with {@code @SqlConfig}, no merging
* takes place and the local configuration is used "as is".
*/
MergedSqlConfig(SqlConfig localSqlConfig, Class<?> testClass) {
Assert.notNull(localSqlConfig, "Local @SqlConfig must not be null");
Assert.notNull(testClass, "testClass must not be null");
// Get global attributes, if any.
AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes(testClass,
SqlConfig.class.getName());
// Override global attributes with local attributes.
if (attributes != null) {
for (String key : attributes.keySet()) {
Object value = AnnotationUtils.getValue(localSqlConfig, key);
if (value != null) {
// Is the value explicit (i.e., not a 'default')?
if (!value.equals("") && (value != TransactionMode.DEFAULT) && (value != ErrorMode.DEFAULT)) {
attributes.put(key, value);
}
}
}
}
else {
// Otherwise, use local attributes only.
attributes = AnnotationUtils.getAnnotationAttributes(localSqlConfig, false, false);
}
this.dataSource = attributes.getString("dataSource");
this.transactionManager = attributes.getString("transactionManager");
this.transactionMode = retrieveTransactionMode(attributes);
this.encoding = attributes.getString("encoding");
this.separator = retrieveSeparator(attributes);
this.commentPrefix = retrieveCommentPrefix(attributes);
this.blockCommentStartDelimiter = retrieveBlockCommentStartDelimiter(attributes);
this.blockCommentEndDelimiter = retrieveBlockCommentEndDelimiter(attributes);
this.errorMode = retrieveErrorMode(attributes);
}
/**
* @see SqlConfig#dataSource()
*/
String getDataSource() {
return this.dataSource;
}
/**
* @see SqlConfig#transactionManager()
*/
String getTransactionManager() {
return this.transactionManager;
}
/**
* @see SqlConfig#transactionMode()
*/
TransactionMode getTransactionMode() {
return this.transactionMode;
}
/**
* @see SqlConfig#encoding()
*/
String getEncoding() {
return this.encoding;
}
/**
* @see SqlConfig#separator()
*/
String getSeparator() {
return this.separator;
}
/**
* @see SqlConfig#commentPrefix()
*/
String getCommentPrefix() {
return this.commentPrefix;
}
/**
* @see SqlConfig#blockCommentStartDelimiter()
*/
String getBlockCommentStartDelimiter() {
return this.blockCommentStartDelimiter;
}
/**
* @see SqlConfig#blockCommentEndDelimiter()
*/
String getBlockCommentEndDelimiter() {
return this.blockCommentEndDelimiter;
}
/**
* @see SqlConfig#errorMode()
*/
ErrorMode getErrorMode() {
return this.errorMode;
}
/**
* Provide a String representation of the merged SQL script configuration.
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("dataSource", dataSource)//
.append("transactionManager", transactionManager)//
.append("transactionMode", transactionMode)//
.append("encoding", encoding)//
.append("separator", separator)//
.append("commentPrefix", commentPrefix)//
.append("blockCommentStartDelimiter", blockCommentStartDelimiter)//
.append("blockCommentEndDelimiter", blockCommentEndDelimiter)//
.append("errorMode", errorMode)//
.toString();
}
}

View File

@ -37,12 +37,13 @@ import static java.lang.annotation.RetentionPolicy.*;
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
* which is enabled by default.
*
* <p>The configuration options provided by this annotation are equivalent to
* those supported by {@link ScriptUtils} and
* <p>The configuration options provided by this annotation and
* {@link SqlConfig @SqlConfig} are equivalent to those supported by
* {@link ScriptUtils} and
* {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}
* but are a superset of those provided by the {@code <jdbc:initialize-database/>}
* XML namespace element. Consult the javadocs of individual attributes in this
* annotation for details.
* annotation and {@link SqlConfig @SqlConfig} for details.
*
* <p>Beginning with Java 8, {@code @Sql} can be used as a
* <em>{@linkplain Repeatable repeatable}</em> annotation. Otherwise,
@ -55,7 +56,6 @@ import static java.lang.annotation.RetentionPolicy.*;
* meta-annotations.
*
* @author Sam Brannen
* @author Tadaya Tsuyukubo
* @since 4.1
* @see SqlGroup
* @see SqlScriptsTestExecutionListener
@ -67,7 +67,7 @@ import static java.lang.annotation.RetentionPolicy.*;
@Documented
@Inherited
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@Target({ TYPE, METHOD })
@Repeatable(SqlGroup.class)
public @interface Sql {
@ -130,114 +130,19 @@ public @interface Sql {
*/
String[] scripts() default {};
/**
* The encoding for the supplied SQL scripts, if different from the platform
* encoding.
* <p>An empty string denotes that the platform encoding should be used.
*/
String encoding() default "";
/**
* The bean name of the {@link javax.sql.DataSource} against which the scripts
* should be executed.
* <p>The name is only used if there is more than one bean of type
* {@code DataSource} in the test's {@code ApplicationContext}. If there is
* only one such bean, it is not necessary to specify a bean name.
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code DataSource} in the test's
* {@code ApplicationContext}.</li>
* <li>The {@code DataSource} to use is named {@code "dataSource"}.</li>
* </ol>
*/
String dataSource() default "";
/**
* The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager} that should be used to drive transactions.
* <p>The name is only used if there is more than one bean of type
* {@code PlatformTransactionManager} in the test's {@code ApplicationContext}.
* If there is only one such bean, it is not necessary to specify a bean name.
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code PlatformTransactionManager} in
* the test's {@code ApplicationContext}.</li>
* <li>{@link org.springframework.transaction.annotation.TransactionManagementConfigurer
* TransactionManagementConfigurer} has been implemented to specify which
* {@code PlatformTransactionManager} bean should be used for annotation-driven
* transaction management.</li>
* <li>The {@code PlatformTransactionManager} to use is named
* {@code "transactionManager"}.</li>
* </ol>
*/
String transactionManager() default "";
/**
* Flag to indicate that the SQL scripts must be executed in a new transaction.
* <p>Defaults to {@code false}, meaning that the SQL scripts will be executed
* within the current transaction if present. The <em>current</em> transaction
* will typically be managed by the
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* TransactionalTestExecutionListener}.
* <p>Can be set to {@code true} to ensure that the scripts are executed in
* a new, isolated transaction that will be immediately committed.
*/
boolean requireNewTransaction() default false;
/**
* The character string used to separate individual statements within the
* SQL scripts.
* <p>Defaults to {@code ";"} if not specified and falls back to {@code "\n"}
* as a last resort; may be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR}
* to signal that each script contains a single statement without a separator.
*/
String separator() default ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
/**
* The prefix that identifies single-line comments within the SQL scripts.
* <p>Defaults to {@code "--"}.
*/
String commentPrefix() default ScriptUtils.DEFAULT_COMMENT_PREFIX;
/**
* The start delimiter that identifies block comments within the SQL scripts.
* <p>Defaults to {@code "/*"}.
* @see #blockCommentEndDelimiter
*/
String blockCommentStartDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
/**
* The end delimiter that identifies block comments within the SQL scripts.
* <p>Defaults to <code>"*&#47;"</code>.
* @see #blockCommentStartDelimiter
*/
String blockCommentEndDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
/**
* Flag to indicate that all failures in SQL should be logged but not cause
* a failure.
* <p>Defaults to {@code false}.
* @see #ignoreFailedDrops
*/
boolean continueOnError() default false;
/**
* Flag to indicate that a failed SQL {@code DROP} statement can be ignored.
* <p>This is useful for a non-embedded database whose SQL dialect does not
* support an {@code IF EXISTS} clause in a {@code DROP} statement.
* <p>The default is {@code false} so that if a script is accidentally
* executed, it will fail fast if the script starts with a {@code DROP}
* statement.
* @see #continueOnError
*/
boolean ignoreFailedDrops() default false;
/**
* When the SQL scripts should be executed.
* <p>Defaults to {@link ExecutionPhase#BEFORE_TEST_METHOD BEFORE_TEST_METHOD}.
*/
ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD;
/**
* Local configuration for the SQL scripts declared within this
* {@code @Sql} annotation.
* <p>See the class-level javadocs for {@link SqlConfig} for explanations of
* local vs. global configuration, inheritance, overrides, etc.
* <p>Defaults to an empty {@link SqlConfig @SqlConfig} instance.
*/
SqlConfig config() default @SqlConfig();
}

View File

@ -0,0 +1,263 @@
/*
* Copyright 2002-2014 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.jdbc;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* {@code @SqlConfig} defines metadata that is used to determine how to parse
* and execute SQL scripts configured via the {@link Sql @Sql} annotation.
*
* <h3>Configuration Scope</h3>
* <p>When declared as a class-level annotation on an integration test class,
* {@code @SqlConfig} serves as <strong><em>global</em></strong> configuration
* for all SQL scripts within the test class hierarchy. When declared directly
* via the {@link Sql#config config} attribute of the {@code @Sql} annotation,
* {@code @SqlConfig} serves as <strong><em>local</em></strong> configuration
* for the SQL scripts declared within the enclosing {@code @Sql} annotation.
*
* <h3>Default Values</h3>
* <p>Every attribute in {@code @SqlConfig} has an <em>implicit</em> default value
* which is documented in the javadocs of the corresponding attribute. Due to the
* rules defined for annotation attributes in the Java Language Specification, it
* is unfortunately not possible to assign a value of {@code null} to an annotation
* attribute. Thus, in order to support overrides of <em>inherited</em> global
* configuration, {@code @SqlConfig} attributes have an <em>explicit</em>
* {@code default} value of either {@code ""} for Strings or {@code DEFAULT} for
* Enums. This approach allows local declarations {@code @SqlConfig} to
* selectively override individual attributes from global declarations of
* {@code @SqlConfig} by providing a value other than {@code ""} or {@code DEFAULT}.
*
* <h3>Inheritance and Overrides</h3>
* <p>Global {@code @SqlConfig} attributes are <em>inherited</em> whenever local
* {@code @SqlConfig} attributes do not supply an explicit value other than
* {@code ""} or {@code DEFAULT}. Explicit local configuration therefore
* <em>overrides</em> global configuration.
*
* @author Sam Brannen
* @author Tadaya Tsuyukubo
* @since 4.1
* @see Sql
*/
@Documented
@Inherited
@Retention(RUNTIME)
@Target(TYPE)
public @interface SqlConfig {
/**
* Enumeration of <em>modes</em> that dictate whether SQL scripts should be
* executed within a transaction and what the transaction propagation behavior
* should be.
*/
static enum TransactionMode {
/**
* Indicates that the <em>default</em> transaction mode should be used.
* <p>The meaning of <em>default</em> depends on the context in which
* {@code @SqlConfig} is declared:
* <ul>
* <li>If {@code @SqlConfig} is declared <strong>only</strong> locally,
* the default transaction mode is {@link #INFERRED}.</li>
* <li>If {@code @SqlConfig} is declared globally, the default transaction
* mode is {@link #INFERRED}.</li>
* <li>If {@code @SqlConfig} is declared globally <strong>and</strong>
* locally, the default transaction mode for the local declaration is
* inherited from the global declaration.</li>
* </ul>
*/
DEFAULT,
/**
* Indicates that the transaction mode to use when executing SQL scripts
* should be <em>inferred</em> based on whether or not a Spring-managed
* transaction is currently present.
* <p>SQL scripts will be executed within the current transaction if present;
* otherwise, scripts will be executed in a new transaction that will be
* immediately committed.
* <p>The <em>current</em> transaction will typically be managed by the
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* TransactionalTestExecutionListener}.
* @see #ISOLATED
*/
INFERRED,
/**
* Indicates that SQL scripts should always be executed in a new,
* <em>isolated</em> transaction that will be immediately committed.
*/
ISOLATED
}
/**
* Enumeration of <em>modes</em> that dictate how errors are handled while
* executing SQL statements.
*/
static enum ErrorMode {
/**
* Indicates that the <em>default</em> error mode should be used.
* <p>The meaning of <em>default</em> depends on the context in which
* {@code @SqlConfig} is declared:
* <ul>
* <li>If {@code @SqlConfig} is declared <strong>only</strong> locally,
* the default error mode is {@link #FAIL_ON_ERROR}.</li>
* <li>If {@code @SqlConfig} is declared globally, the default error
* mode is {@link #FAIL_ON_ERROR}.</li>
* <li>If {@code @SqlConfig} is declared globally <strong>and</strong>
* locally, the default error mode for the local declaration is
* inherited from the global declaration.</li>
* </ul>
*/
DEFAULT,
/**
* Indicates that script execution will fail if an error is encountered.
* In other words, no errors should be ignored.
* <p>This is effectively the default error mode so that if a script
* is accidentally executed, it will fail fast if any SQL statement in
* the script results in an error.
* @see #CONTINUE_ON_ERROR
*/
FAIL_ON_ERROR,
/**
* Indicates that all errors in SQL scripts should be logged but not
* propagated as exceptions.
* <p>{@code CONTINUE_ON_ERROR} is the logical <em>opposite</em> of
* {@code FAIL_ON_ERROR} and a <em>superset</em> of {@code IGNORE_FAILED_DROPS}.
* @see #FAIL_ON_ERROR
* @see #IGNORE_FAILED_DROPS
*/
CONTINUE_ON_ERROR,
/**
* Indicates that failed SQL {@code DROP} statements can be ignored.
* <p>This is useful for a non-embedded database whose SQL dialect does
* not support an {@code IF EXISTS} clause in a {@code DROP} statement.
* @see #CONTINUE_ON_ERROR
*/
IGNORE_FAILED_DROPS
}
/**
* The bean name of the {@link javax.sql.DataSource} against which the scripts
* should be executed.
* <p>The name is only used if there is more than one bean of type
* {@code DataSource} in the test's {@code ApplicationContext}. If there is
* only one such bean, it is not necessary to specify a bean name.
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code DataSource} in the test's
* {@code ApplicationContext}.</li>
* <li>The {@code DataSource} to use is named {@code "dataSource"}.</li>
* </ol>
*/
String dataSource() default "";
/**
* The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager} that should be used to drive transactions.
* <p>The name is only used if there is more than one bean of type
* {@code PlatformTransactionManager} in the test's {@code ApplicationContext}.
* If there is only one such bean, it is not necessary to specify a bean name.
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code PlatformTransactionManager} in
* the test's {@code ApplicationContext}.</li>
* <li>{@link org.springframework.transaction.annotation.TransactionManagementConfigurer
* TransactionManagementConfigurer} has been implemented to specify which
* {@code PlatformTransactionManager} bean should be used for annotation-driven
* transaction management.</li>
* <li>The {@code PlatformTransactionManager} to use is named
* {@code "transactionManager"}.</li>
* </ol>
*/
String transactionManager() default "";
/**
* The <em>mode</em> to use when determining whether SQL scripts should be
* executed within a transaction.
* <p>Defaults to {@link TransactionMode#DEFAULT DEFAULT}.
* <p>Can be set to {@link TransactionMode#ISOLATED} to ensure that the SQL
* scripts are executed in a new, isolated transaction that will be immediately
* committed.
* @see TransactionMode
*/
TransactionMode transactionMode() default TransactionMode.DEFAULT;
/**
* The encoding for the supplied SQL scripts, if different from the platform
* encoding.
* <p>An empty string denotes that the platform encoding should be used.
*/
String encoding() default "";
/**
* The character string used to separate individual statements within the
* SQL scripts.
* <p>Implicitly defaults to {@code ";"} if not specified and falls back to
* {@code "\n"} as a last resort.
* <p>May be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR} to signal
* that each script contains a single statement without a separator.
* @see ScriptUtils#DEFAULT_STATEMENT_SEPARATOR
*/
String separator() default "";
/**
* The prefix that identifies single-line comments within the SQL scripts.
* <p>Implicitly defaults to {@code "--"}.
* @see ScriptUtils#DEFAULT_COMMENT_PREFIX
*/
String commentPrefix() default "";
/**
* The start delimiter that identifies block comments within the SQL scripts.
* <p>Implicitly defaults to {@code "/*"}.
* @see #blockCommentEndDelimiter
* @see ScriptUtils#DEFAULT_BLOCK_COMMENT_START_DELIMITER
*/
String blockCommentStartDelimiter() default "";
/**
* The end delimiter that identifies block comments within the SQL scripts.
* <p>Implicitly defaults to <code>"*&#47;"</code>.
* @see #blockCommentStartDelimiter
* @see ScriptUtils#DEFAULT_BLOCK_COMMENT_END_DELIMITER
*/
String blockCommentEndDelimiter() default "";
/**
* The <em>mode</em> to use when an error is encountered while executing an
* SQL statement.
* <p>Defaults to {@link ErrorMode#DEFAULT DEFAULT}.
* @see ErrorMode
*/
ErrorMode errorMode() default ErrorMode.DEFAULT;
}

View File

@ -29,6 +29,8 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.transaction.TestContextTransactionUtils;
import org.springframework.test.context.util.TestContextResourceUtils;
@ -138,23 +140,24 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
@SuppressWarnings("serial")
private void executeSqlScripts(Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel)
throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Processing %s for execution phase [%s] and test context %s.", sql,
executionPhase, testContext));
}
if (executionPhase != sql.executionPhase()) {
return;
}
MergedSqlConfig mergedSqlConfig = new MergedSqlConfig(sql.config(), testContext.getTestClass());
if (logger.isDebugEnabled()) {
logger.debug(String.format("Processing %s for execution phase [%s] and test context %s.", mergedSqlConfig,
executionPhase, testContext));
}
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setSqlScriptEncoding(sql.encoding());
populator.setSeparator(sql.separator());
populator.setCommentPrefix(sql.commentPrefix());
populator.setBlockCommentStartDelimiter(sql.blockCommentStartDelimiter());
populator.setBlockCommentEndDelimiter(sql.blockCommentEndDelimiter());
populator.setContinueOnError(sql.continueOnError());
populator.setIgnoreFailedDrops(sql.ignoreFailedDrops());
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
populator.setSeparator(mergedSqlConfig.getSeparator());
populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix());
populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter());
populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter());
populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR);
populator.setIgnoreFailedDrops(mergedSqlConfig.getErrorMode() == ErrorMode.IGNORE_FAILED_DROPS);
String[] scripts = getScripts(sql, testContext, classLevel);
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
@ -163,11 +166,12 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scripts));
}
final DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext, sql.dataSource());
final DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext,
mergedSqlConfig.getDataSource());
final PlatformTransactionManager transactionManager = TestContextTransactionUtils.retrieveTransactionManager(
testContext, sql.transactionManager());
testContext, mergedSqlConfig.getTransactionManager());
int propagation = sql.requireNewTransaction() ? TransactionDefinition.PROPAGATION_REQUIRES_NEW
int propagation = (mergedSqlConfig.getTransactionMode() == TransactionMode.ISOLATED) ? TransactionDefinition.PROPAGATION_REQUIRES_NEW
: TransactionDefinition.PROPAGATION_REQUIRED;
TransactionAttribute transactionAttribute = TestContextTransactionUtils.createDelegatingTransactionAttribute(

View File

@ -25,7 +25,7 @@ import static org.junit.Assert.*;
/**
* Integration tests that verify support for custom SQL script syntax
* configured via {@link Sql @Sql}.
* configured via {@link SqlConfig @SqlConfig}.
*
* @author Sam Brannen
* @since 4.1
@ -35,16 +35,9 @@ import static org.junit.Assert.*;
public class CustomScriptSyntaxSqlScriptsTests extends AbstractTransactionalJUnit4SpringContextTests {
@Test
@SqlGroup({//
@Sql("schema.sql"),//
@Sql(//
scripts = "data-add-users-with-custom-script-syntax.sql",//
commentPrefix = "`",//
blockCommentStartDelimiter = "#$",//
blockCommentEndDelimiter = "$#",//
separator = "@@"//
) //
})
@Sql("schema.sql")
@Sql(scripts = "data-add-users-with-custom-script-syntax.sql",//
config = @SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@"))
public void methodLevelScripts() {
assertNumUsers(3);
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-2014 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.jdbc;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import static org.junit.Assert.*;
/**
* Modified copy of {@link CustomScriptSyntaxSqlScriptsTests} with
* {@link SqlConfig @SqlConfig} defined at the class level.
*
* @author Sam Brannen
* @since 4.1
*/
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DirtiesContext
@SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@")
public class GlobalCustomScriptSyntaxSqlScriptsTests extends AbstractTransactionalJUnit4SpringContextTests {
@Test
@Sql(scripts = "schema.sql", config = @SqlConfig(separator = ";"))
@Sql("data-add-users-with-custom-script-syntax.sql")
public void methodLevelScripts() {
assertNumUsers(3);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@ -0,0 +1,177 @@
/*
* Copyright 2002-2014 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.jdbc;
import java.lang.reflect.Method;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.springframework.jdbc.datasource.init.ScriptUtils.*;
import static org.springframework.test.context.jdbc.SqlConfig.ErrorMode.*;
import static org.springframework.test.context.jdbc.SqlConfig.TransactionMode.*;
/**
* Unit tests for {@link MergedSqlConfig}.
*
* @author Sam Brannen
* @since 4.1
*/
public class MergedSqlConfigTests {
private void assertDefaults(MergedSqlConfig cfg) {
assertNotNull(cfg);
assertEquals("dataSource", "", cfg.getDataSource());
assertEquals("transactionManager", "", cfg.getTransactionManager());
assertEquals("transactionMode", INFERRED, cfg.getTransactionMode());
assertEquals("encoding", "", cfg.getEncoding());
assertEquals("separator", DEFAULT_STATEMENT_SEPARATOR, cfg.getSeparator());
assertEquals("commentPrefix", DEFAULT_COMMENT_PREFIX, cfg.getCommentPrefix());
assertEquals("blockCommentStartDelimiter", DEFAULT_BLOCK_COMMENT_START_DELIMITER,
cfg.getBlockCommentStartDelimiter());
assertEquals("blockCommentEndDelimiter", DEFAULT_BLOCK_COMMENT_END_DELIMITER, cfg.getBlockCommentEndDelimiter());
assertEquals("errorMode", FAIL_ON_ERROR, cfg.getErrorMode());
}
@Test
public void localConfigWithDefaults() throws Exception {
Method method = getClass().getMethod("localConfigMethodWithDefaults");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass());
assertDefaults(cfg);
}
@Test
public void globalConfigWithDefaults() throws Exception {
Method method = GlobalConfigWithDefaultsClass.class.getMethod("globalConfigMethod");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigWithDefaultsClass.class);
assertDefaults(cfg);
}
@Test
public void localConfigWithCustomValues() throws Exception {
Method method = getClass().getMethod("localConfigMethodWithCustomValues");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass());
assertNotNull(cfg);
assertEquals("dataSource", "ds", cfg.getDataSource());
assertEquals("transactionManager", "txMgr", cfg.getTransactionManager());
assertEquals("transactionMode", ISOLATED, cfg.getTransactionMode());
assertEquals("encoding", "enigma", cfg.getEncoding());
assertEquals("separator", "\n", cfg.getSeparator());
assertEquals("commentPrefix", "`", cfg.getCommentPrefix());
assertEquals("blockCommentStartDelimiter", "<<", cfg.getBlockCommentStartDelimiter());
assertEquals("blockCommentEndDelimiter", ">>", cfg.getBlockCommentEndDelimiter());
assertEquals("errorMode", IGNORE_FAILED_DROPS, cfg.getErrorMode());
}
@Test
public void localConfigWithContinueOnError() throws Exception {
Method method = getClass().getMethod("localConfigMethodWithContinueOnError");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass());
assertNotNull(cfg);
assertEquals("errorMode", CONTINUE_ON_ERROR, cfg.getErrorMode());
}
@Test
public void localConfigWithIgnoreFailedDrops() throws Exception {
Method method = getClass().getMethod("localConfigMethodWithIgnoreFailedDrops");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass());
assertNotNull(cfg);
assertEquals("errorMode", IGNORE_FAILED_DROPS, cfg.getErrorMode());
}
@Test
public void globalConfig() throws Exception {
Method method = GlobalConfigClass.class.getMethod("globalConfigMethod");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigClass.class);
assertNotNull(cfg);
assertEquals("dataSource", "", cfg.getDataSource());
assertEquals("transactionManager", "", cfg.getTransactionManager());
assertEquals("transactionMode", INFERRED, cfg.getTransactionMode());
assertEquals("encoding", "global", cfg.getEncoding());
assertEquals("separator", "\n", cfg.getSeparator());
assertEquals("commentPrefix", DEFAULT_COMMENT_PREFIX, cfg.getCommentPrefix());
assertEquals("blockCommentStartDelimiter", DEFAULT_BLOCK_COMMENT_START_DELIMITER,
cfg.getBlockCommentStartDelimiter());
assertEquals("blockCommentEndDelimiter", DEFAULT_BLOCK_COMMENT_END_DELIMITER, cfg.getBlockCommentEndDelimiter());
assertEquals("errorMode", IGNORE_FAILED_DROPS, cfg.getErrorMode());
}
@Test
public void globalConfigWithLocalOverrides() throws Exception {
Method method = GlobalConfigClass.class.getMethod("globalConfigWithLocalOverridesMethod");
SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config();
MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigClass.class);
assertNotNull(cfg);
assertEquals("dataSource", "", cfg.getDataSource());
assertEquals("transactionManager", "", cfg.getTransactionManager());
assertEquals("transactionMode", INFERRED, cfg.getTransactionMode());
assertEquals("encoding", "local", cfg.getEncoding());
assertEquals("separator", "@@", cfg.getSeparator());
assertEquals("commentPrefix", DEFAULT_COMMENT_PREFIX, cfg.getCommentPrefix());
assertEquals("blockCommentStartDelimiter", DEFAULT_BLOCK_COMMENT_START_DELIMITER,
cfg.getBlockCommentStartDelimiter());
assertEquals("blockCommentEndDelimiter", DEFAULT_BLOCK_COMMENT_END_DELIMITER, cfg.getBlockCommentEndDelimiter());
assertEquals("errorMode", CONTINUE_ON_ERROR, cfg.getErrorMode());
}
// -------------------------------------------------------------------------
@Sql
public static void localConfigMethodWithDefaults() {
}
@Sql(config = @SqlConfig(dataSource = "ds", transactionManager = "txMgr", transactionMode = ISOLATED, encoding = "enigma", separator = "\n", commentPrefix = "`", blockCommentStartDelimiter = "<<", blockCommentEndDelimiter = ">>", errorMode = IGNORE_FAILED_DROPS))
public static void localConfigMethodWithCustomValues() {
}
@Sql(config = @SqlConfig(errorMode = CONTINUE_ON_ERROR))
public static void localConfigMethodWithContinueOnError() {
}
@Sql(config = @SqlConfig(errorMode = IGNORE_FAILED_DROPS))
public static void localConfigMethodWithIgnoreFailedDrops() {
}
@SqlConfig
public static class GlobalConfigWithDefaultsClass {
@Sql("foo.sql")
public void globalConfigMethod() {
}
}
@SqlConfig(encoding = "global", separator = "\n", errorMode = IGNORE_FAILED_DROPS)
public static class GlobalConfigClass {
@Sql("foo.sql")
public void globalConfigMethod() {
}
@Sql(scripts = "foo.sql", config = @SqlConfig(encoding = "local", separator = "@@", errorMode = CONTINUE_ON_ERROR))
public void globalConfigWithLocalOverridesMethod() {
}
}
}

View File

@ -40,6 +40,7 @@ import static org.junit.Assert.*;
/**
* Integration tests for {@link Sql @Sql} that verify support for multiple
* {@link DataSource}s and {@link PlatformTransactionManager}s.
* <p>Simultaneously tests for method-level overrides via {@code @SqlConfig}.
*
* @author Sam Brannen
* @since 4.1
@ -48,6 +49,7 @@ import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext
@SqlConfig(dataSource = "dataSource1", transactionManager = "txMgr1")
public class MultipleDataSourcesAndTransactionManagersSqlScriptsTests {
@Autowired
@ -58,13 +60,13 @@ public class MultipleDataSourcesAndTransactionManagersSqlScriptsTests {
@Test
@Sql(scripts = "data-add-dogbert.sql", dataSource = "dataSource1", transactionManager = "txMgr1")
@Sql("data-add-dogbert.sql")
public void database1() {
assertUsers(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
}
@Test
@Sql(scripts = "data-add-catbert.sql", dataSource = "dataSource2", transactionManager = "txMgr2")
@Sql(scripts = "data-add-catbert.sql", config = @SqlConfig(dataSource = "dataSource2", transactionManager = "txMgr2"))
public void database2() {
assertUsers(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
}

View File

@ -48,6 +48,8 @@ import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext
@Transactional("txMgr1")
@SqlConfig(dataSource = "dataSource1", transactionManager = "txMgr1")
public class MultipleDataSourcesAndTransactionManagersTransactionalSqlScriptsTests {
@Autowired
@ -58,15 +60,14 @@ public class MultipleDataSourcesAndTransactionManagersTransactionalSqlScriptsTes
@Test
@Transactional("txMgr1")
@Sql(scripts = "data-add-dogbert.sql", dataSource = "dataSource1", transactionManager = "txMgr1")
@Sql("data-add-dogbert.sql")
public void database1() {
assertUsers(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
}
@Test
@Transactional("txMgr2")
@Sql(scripts = "data-add-catbert.sql", dataSource = "dataSource2", transactionManager = "txMgr2")
@Sql(scripts = "data-add-catbert.sql", config = @SqlConfig(dataSource = "dataSource2", transactionManager = "txMgr2"))
public void database2() {
assertUsers(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
}

View File

@ -19,6 +19,7 @@ package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
@ -27,7 +28,7 @@ import static org.junit.Assert.*;
/**
* Transactional integration tests that verify commit semantics for
* {@link Sql#requireNewTransaction}.
* {@link SqlConfig#requireNewTransaction}.
*
* @author Sam Brannen
* @since 4.1
@ -42,7 +43,7 @@ public class RequiresNewTransactionSqlScriptsTests extends AbstractTransactional
}
@Test
@SqlGroup(@Sql(scripts = "data-add-dogbert.sql", requireNewTransaction = true))
@SqlGroup(@Sql(scripts = "data-add-dogbert.sql", config = @SqlConfig(transactionMode = TransactionMode.ISOLATED)))
public void methodLevelScripts() {
assertNumUsers(1);
}

View File

@ -62,7 +62,7 @@ public class TransactionalAfterTestMethodSqlScriptsTests extends AbstractTransac
@Test
@SqlGroup({//
@Sql({ "schema.sql", "data.sql" }),//
@Sql({ "schema.sql", "data.sql" }),//
@Sql(scripts = "drop-schema.sql", executionPhase = AFTER_TEST_METHOD) //
})
// test## is required for @FixMethodOrder.