diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java new file mode 100644 index 00000000000..c0b390a17fd --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java @@ -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 merged SQL script configuration + * declared via {@link SqlConfig @SqlConfig} on a test method and its enclosing + * test class. + *
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}. + *
Local configuration overrides class-level configuration. + *
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(); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index e64603bd222..bc9af8a9777 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -37,12 +37,13 @@ import static java.lang.annotation.RetentionPolicy.*; *
Script execution is performed by the {@link SqlScriptsTestExecutionListener}, * which is enabled by default. * - *
The configuration options provided by this annotation are equivalent to - * those supported by {@link ScriptUtils} and + *
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 
Beginning with Java 8, {@code @Sql} can be used as a * {@linkplain Repeatable repeatable} 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. - *
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. - *
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. - *
Defaults to an empty string, requiring that one of the following is - * true: - *
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. - *
Defaults to an empty string, requiring that one of the following is - * true: - *
Defaults to {@code false}, meaning that the SQL scripts will be executed - * within the current transaction if present. The current transaction - * will typically be managed by the - * {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener - * TransactionalTestExecutionListener}. - *
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. - *
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. - *
Defaults to {@code "--"}. - */ - String commentPrefix() default ScriptUtils.DEFAULT_COMMENT_PREFIX; - - /** - * The start delimiter that identifies block comments within the SQL scripts. - *
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. - *
Defaults to "*/".
-	 * @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.
-	 * 
Defaults to {@code false}. - * @see #ignoreFailedDrops - */ - boolean continueOnError() default false; - - /** - * Flag to indicate that a failed SQL {@code DROP} statement can be ignored. - *
This is useful for a non-embedded database whose SQL dialect does not - * support an {@code IF EXISTS} clause in a {@code DROP} statement. - *
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. *
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. + *
See the class-level javadocs for {@link SqlConfig} for explanations of + * local vs. global configuration, inheritance, overrides, etc. + *
Defaults to an empty {@link SqlConfig @SqlConfig} instance. + */ + SqlConfig config() default @SqlConfig(); + } diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java new file mode 100644 index 00000000000..e9ea581310d --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java @@ -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. + * + *
When declared as a class-level annotation on an integration test class, + * {@code @SqlConfig} serves as global 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 local configuration + * for the SQL scripts declared within the enclosing {@code @Sql} annotation. + * + *
Every attribute in {@code @SqlConfig} has an implicit 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 inherited global + * configuration, {@code @SqlConfig} attributes have an explicit + * {@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}. + * + *
Global {@code @SqlConfig} attributes are inherited whenever local + * {@code @SqlConfig} attributes do not supply an explicit value other than + * {@code ""} or {@code DEFAULT}. Explicit local configuration therefore + * overrides global configuration. + * + * @author Sam Brannen + * @author Tadaya Tsuyukubo + * @since 4.1 + * @see Sql + */ +@Documented +@Inherited +@Retention(RUNTIME) +@Target(TYPE) +public @interface SqlConfig { + + /** + * Enumeration of modes 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 default transaction mode should be used. + *
The meaning of default depends on the context in which + * {@code @SqlConfig} is declared: + *
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. + *
The current 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, + * isolated transaction that will be immediately committed. + */ + ISOLATED + } + + /** + * Enumeration of modes that dictate how errors are handled while + * executing SQL statements. + */ + static enum ErrorMode { + + /** + * Indicates that the default error mode should be used. + *
The meaning of default depends on the context in which + * {@code @SqlConfig} is declared: + *
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. + *
{@code CONTINUE_ON_ERROR} is the logical opposite of + * {@code FAIL_ON_ERROR} and a superset 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. + *
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. + *
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. + *
Defaults to an empty string, requiring that one of the following is + * true: + *
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. + *
Defaults to an empty string, requiring that one of the following is + * true: + *
Defaults to {@link TransactionMode#DEFAULT DEFAULT}. + *
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. + *
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. + *
Implicitly 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. + * @see ScriptUtils#DEFAULT_STATEMENT_SEPARATOR + */ + String separator() default ""; + + /** + * The prefix that identifies single-line comments within the SQL scripts. + *
Implicitly defaults to {@code "--"}. + * @see ScriptUtils#DEFAULT_COMMENT_PREFIX + */ + String commentPrefix() default ""; + + /** + * The start delimiter that identifies block comments within the SQL scripts. + *
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. + *
Implicitly defaults to "*/".
+	 * @see #blockCommentStartDelimiter
+	 * @see ScriptUtils#DEFAULT_BLOCK_COMMENT_END_DELIMITER
+	 */
+	String blockCommentEndDelimiter() default "";
+
+	/**
+	 * The mode to use when an error is encountered while executing an
+	 * SQL statement.
+	 * 
Defaults to {@link ErrorMode#DEFAULT DEFAULT}. + * @see ErrorMode + */ + ErrorMode errorMode() default ErrorMode.DEFAULT; + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 1c8a82b31f2..968c4289bf2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -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( diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java index ac46634df59..5b41a8be5cd 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java @@ -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); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java new file mode 100644 index 00000000000..ce6a6c1479b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java @@ -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")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java new file mode 100644 index 00000000000..48edb90d493 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java @@ -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() { + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersSqlScriptsTests.java index 3761370ce3c..08a514fc40b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersSqlScriptsTests.java @@ -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. + *
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"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersTransactionalSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersTransactionalSqlScriptsTests.java index b5450c6d5df..12bd50dec75 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersTransactionalSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MultipleDataSourcesAndTransactionManagersTransactionalSqlScriptsTests.java @@ -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"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/RequiresNewTransactionSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/RequiresNewTransactionSqlScriptsTests.java index c15a2e72f58..093b9aa6d99 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/RequiresNewTransactionSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/RequiresNewTransactionSqlScriptsTests.java @@ -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); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalAfterTestMethodSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalAfterTestMethodSqlScriptsTests.java index 6f6f58cd5fb..c9a402f4284 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalAfterTestMethodSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalAfterTestMethodSqlScriptsTests.java @@ -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.