This is an automated email from the ASF dual-hosted git repository. jdaugherty pushed a commit to branch database-cleanup-feature in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 1a91738cdf34006589d55c60789494bac9e2d799 Author: Test <[email protected]> AuthorDate: Sat Feb 21 23:20:37 2026 -0500 Ensure cleanup runs even if the clean methods fail on the spec --- .../cleanup/core/DatabaseCleanupInterceptor.groovy | 50 +++++++------- .../core/DatabaseCleanupInterceptorSpec.groovy | 76 ++++++++++++++++++++++ 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/grails-testing-support-cleanup-core/src/main/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptor.groovy b/grails-testing-support-cleanup-core/src/main/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptor.groovy index 1e0127a207..e9616913e5 100644 --- a/grails-testing-support-cleanup-core/src/main/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptor.groovy +++ b/grails-testing-support-cleanup-core/src/main/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptor.groovy @@ -62,34 +62,40 @@ class DatabaseCleanupInterceptor extends AbstractMethodInterceptor { @Override void interceptCleanupMethod(IMethodInvocation invocation) throws Throwable { - invocation.proceed() - - if (classLevelCleanup) { - ensureApplicationContext(invocation) - log.debug('Performing database cleanup after test method: {}', - invocation.feature?.name ?: 'unknown') - List<DatabaseCleanupStats> stats = context.performCleanup(mapping) - logStats(stats) + try { + invocation.proceed() } - else if (isCurrentFeatureAnnotated(invocation)) { - ensureApplicationContext(invocation) - DatasourceCleanupMapping methodMapping = getMethodMapping(invocation) - log.debug('Performing database cleanup after test method: {}', - invocation.feature?.name ?: 'unknown') - List<DatabaseCleanupStats> stats = context.performCleanup(methodMapping) - logStats(stats) + finally { + if (classLevelCleanup) { + ensureApplicationContext(invocation) + log.debug('Performing database cleanup after test method: {}', + invocation.feature?.name ?: 'unknown') + List<DatabaseCleanupStats> stats = context.performCleanup(mapping) + logStats(stats) + } + else if (isCurrentFeatureAnnotated(invocation)) { + ensureApplicationContext(invocation) + DatasourceCleanupMapping methodMapping = getMethodMapping(invocation) + log.debug('Performing database cleanup after test method: {}', + invocation.feature?.name ?: 'unknown') + List<DatabaseCleanupStats> stats = context.performCleanup(methodMapping) + logStats(stats) + } } } @Override void interceptCleanupSpecMethod(IMethodInvocation invocation) throws Throwable { - invocation.proceed() - - if (classLevelCleanup) { - ensureApplicationContext(invocation) - log.debug('Performing database cleanup after spec: {}', invocation.spec?.name ?: 'unknown') - List<DatabaseCleanupStats> stats = context.performCleanup(mapping) - logStats(stats) + try { + invocation.proceed() + } + finally { + if (classLevelCleanup) { + ensureApplicationContext(invocation) + log.debug('Performing database cleanup after spec: {}', invocation.spec?.name ?: 'unknown') + List<DatabaseCleanupStats> stats = context.performCleanup(mapping) + logStats(stats) + } } } diff --git a/grails-testing-support-cleanup-core/src/test/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptorSpec.groovy b/grails-testing-support-cleanup-core/src/test/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptorSpec.groovy index 9cfcb82adf..6341e81c17 100644 --- a/grails-testing-support-cleanup-core/src/test/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptorSpec.groovy +++ b/grails-testing-support-cleanup-core/src/test/groovy/org/apache/grails/testing/cleanup/core/DatabaseCleanupInterceptorSpec.groovy @@ -447,6 +447,82 @@ class DatabaseCleanupInterceptorSpec extends Specification { System.out = originalOut } + def "cleanup still runs even if test method fails"() { + given: + def dataSource = Mock(DataSource) + def appCtx = Mock(ApplicationContext) { + getBeansOfType(DataSource) >> ['dataSource': dataSource] + } + def cleaner = Mock(DatabaseCleaner) { + databaseType() >> 'h2' + supports(dataSource) >> true + cleanup(appCtx, dataSource) >> new DatabaseCleanupStats() + } + def context = new DatabaseCleanupContext([cleaner]) + context.applicationContext = appCtx + + def mapping = DatasourceCleanupMapping.parse(new String[0]) + def resolver = new DefaultApplicationContextResolver() + def interceptor = new DatabaseCleanupInterceptor(context, true, mapping, resolver) + + def testFailure = new RuntimeException('Test failed') + def invocation = Mock(IMethodInvocation) { + getInstance() >> new InstanceWithAppCtx(applicationContext: appCtx) + getFeature() >> Mock(FeatureInfo) { + getName() >> 'test feature' + } + proceed() >> { throw testFailure } + } + + when: + interceptor.interceptCleanupMethod(invocation) + + then: 'cleanup was performed before exception is re-thrown' + 1 * cleaner.cleanup(appCtx, dataSource) >> new DatabaseCleanupStats() + + and: 'the original test exception is re-thrown' + RuntimeException ex = thrown(RuntimeException) + ex.is(testFailure) + } + + def "cleanup still runs even if cleanupSpec method fails"() { + given: + def dataSource = Mock(DataSource) + def appCtx = Mock(ApplicationContext) { + getBeansOfType(DataSource) >> ['dataSource': dataSource] + } + def cleaner = Mock(DatabaseCleaner) { + databaseType() >> 'h2' + supports(dataSource) >> true + cleanup(appCtx, dataSource) >> new DatabaseCleanupStats() + } + def context = new DatabaseCleanupContext([cleaner]) + context.applicationContext = appCtx + + def mapping = DatasourceCleanupMapping.parse(new String[0]) + def resolver = new DefaultApplicationContextResolver() + def interceptor = new DatabaseCleanupInterceptor(context, true, mapping, resolver) + + def specFailure = new RuntimeException('Cleanup spec failed') + def invocation = Mock(IMethodInvocation) { + getInstance() >> new InstanceWithAppCtx(applicationContext: appCtx) + getSpec() >> Mock(SpecInfo) { + getName() >> 'TestSpec' + } + proceed() >> { throw specFailure } + } + + when: + interceptor.interceptCleanupSpecMethod(invocation) + + then: 'cleanup was performed before exception is re-thrown' + 1 * cleaner.cleanup(appCtx, dataSource) >> new DatabaseCleanupStats() + + and: 'the original spec exception is re-thrown' + RuntimeException ex = thrown(RuntimeException) + ex.is(specFailure) + } + // --- Helper classes --- static class InstanceWithAppCtx {
