You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/04/10 18:18:29 UTC

(grails-core) 01/01: fix testcontainer harness

This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 1896fbcee53c34e25d187c20c6e42464d7961ec1
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Fri Apr 10 13:15:18 2026 -0400

    fix testcontainer harness
    
    # Conflicts:
    #       grails-data-mongodb/core/src/test/groovy/org/apache/grails/data/mongo/core/GrailsDataMongoTckManager.groovy
---
 .../apache/grails/buildsrc/GrailsTestPlugin.groovy |  14 +-
 .../mongo/core/GrailsDataMongoTckManager.groovy    | 182 ++++++++-------------
 2 files changed, 71 insertions(+), 125 deletions(-)

diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsTestPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsTestPlugin.groovy
index a1414a6eab..1e7f251ff9 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsTestPlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsTestPlugin.groovy
@@ -36,7 +36,7 @@ class GrailsTestPlugin implements Plugin<Project> {
 
         project.tasks.register('aggregateTestFailures') { task ->
             task.group = 'verification'
-            task.description = 'Aggregates all test failures into a single Markdown report'
+            task.description = 'Aggregates all test failures across all modules into TEST_FAILURES.md'
 
             project.subprojects.each { subproject ->
                 subproject.tasks.withType(Test).configureEach { testTask ->
@@ -78,21 +78,15 @@ class GrailsTestPlugin implements Plugin<Project> {
                     }
                 }
 
-                // 1. All subprojects
                 project.subprojects.each { subproject ->
                     def testResultsDir = subproject.layout.buildDirectory.dir("test-results").get().asFile
                     processTestResults(testResultsDir, subproject.name)
                 }
 
-                // 2. Any other directory in root that might have test results (like grails-docs or build-logic)
                 project.layout.projectDirectory.asFile.eachDir { dir ->
                     if (!dir.name.startsWith('.') && dir.name != 'build' && dir.name != 'node_modules') {
-                        def testResultsDir = new File(dir, "build/test-results")
-                        processTestResults(testResultsDir, dir.name)
-                        
-                        // Also check for results in the directory itself (if it's a build output)
-                        def directTestResultsDir = new File(dir, "test-results")
-                        processTestResults(directTestResultsDir, dir.name)
+                        processTestResults(new File(dir, "build/test-results"), dir.name)
+                        processTestResults(new File(dir, "test-results"), dir.name)
                     }
                 }
 
@@ -124,6 +118,6 @@ class GrailsTestPlugin implements Plugin<Project> {
             }
         }
         reportFile.text = out.toString()
-        project.logger.lifecycle("Aggregated test failures report generated: ${reportFile.absolutePath}")
+        project.logger.lifecycle("Test failure report: ${reportFile.absolutePath}")
     }
 }
diff --git a/grails-data-mongodb/core/src/test/groovy/org/apache/grails/data/mongo/core/GrailsDataMongoTckManager.groovy b/grails-data-mongodb/core/src/test/groovy/org/apache/grails/data/mongo/core/GrailsDataMongoTckManager.groovy
index 9ed75cf991..022637f6c0 100644
--- a/grails-data-mongodb/core/src/test/groovy/org/apache/grails/data/mongo/core/GrailsDataMongoTckManager.groovy
+++ b/grails-data-mongodb/core/src/test/groovy/org/apache/grails/data/mongo/core/GrailsDataMongoTckManager.groovy
@@ -18,43 +18,43 @@
  */
 package org.apache.grails.data.mongo.core
 
-import groovy.util.logging.Slf4j
-
 import com.mongodb.BasicDBObject
 import com.mongodb.client.MongoClient
-import org.bson.Document
-import org.testcontainers.containers.MongoDBContainer
-
-import org.springframework.context.support.GenericApplicationContext
-import org.springframework.context.support.StaticMessageSource
-import org.springframework.validation.Validator
-
 import grails.core.DefaultGrailsApplication
 import grails.core.GrailsApplication
 import grails.gorm.validation.PersistentEntityValidator
+import groovy.util.logging.Slf4j
 import org.apache.grails.data.testing.tck.base.GrailsDataTckManager
 import org.apache.grails.testing.mongo.AbstractMongoGrailsExtension
+import org.bson.Document
 import org.grails.datastore.bson.query.BsonQuery
 import org.grails.datastore.gorm.GormEnhancer
 import org.grails.datastore.gorm.mongo.Birthday
 import org.grails.datastore.gorm.validation.constraints.eval.DefaultConstraintEvaluator
 import org.grails.datastore.gorm.validation.constraints.registry.DefaultConstraintRegistry
-import org.grails.datastore.mapping.core.DatastoreUtils
 import org.grails.datastore.mapping.core.Session
+import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
+import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver
 import org.grails.datastore.mapping.engine.types.AbstractMappingAwareCustomTypeMarshaller
 import org.grails.datastore.mapping.model.MappingContext
 import org.grails.datastore.mapping.model.PersistentEntity
 import org.grails.datastore.mapping.model.PersistentProperty
+import org.grails.datastore.mapping.mongo.AbstractMongoSession
+import org.grails.datastore.mapping.core.DatastoreUtils
 import org.grails.datastore.mapping.mongo.MongoDatastore
 import org.grails.datastore.mapping.mongo.config.MongoSettings
-import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
-import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver
 import org.grails.datastore.mapping.query.Query
+import org.slf4j.LoggerFactory
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.context.support.StaticMessageSource
+import org.springframework.validation.Validator
+import org.testcontainers.containers.MongoDBContainer
+import org.testcontainers.containers.output.Slf4jLogConsumer
 
 @Slf4j
 class GrailsDataMongoTckManager extends GrailsDataTckManager {
 
-    static MongoDBContainer mongoDBContainer
+    MongoDBContainer mongoDBContainer
 
     MongoDatastore mongoDatastore
     MongoClient mongoClient
@@ -68,19 +68,16 @@ class GrailsDataMongoTckManager extends GrailsDataTckManager {
     @Override
     void setupSpec() {
         super.setupSpec()
-        if (isDockerAvailable()) {
-            if (mongoDBContainer == null) {
-                mongoDBContainer = new MongoDBContainer(AbstractMongoGrailsExtension.desiredMongoDockerName)
-                mongoDBContainer.start()
-                // mongoDBContainer.followOutput(new Slf4jLogConsumer(LoggerFactory.getLogger("testcontainers")))
-            }
-
-            configuration = [
-                    (MongoSettings.SETTING_DATABASE_NAME): 'test',
-                    (MongoSettings.SETTING_HOST)         : mongoDBContainer.host,
-                    (MongoSettings.SETTING_PORT)         : mongoDBContainer.getMappedPort(AbstractMongoGrailsExtension.DEFAULT_MONGO_PORT) as String,
-            ]
-        }
+        mongoDBContainer = new MongoDBContainer(AbstractMongoGrailsExtension.desiredMongoDockerName)
+        mongoDBContainer.start()
+        mongoDBContainer.followOutput(new Slf4jLogConsumer(LoggerFactory.getLogger("testcontainers")))
+
+        configuration = [
+                (MongoSettings.SETTING_DATABASE_NAME): 'test',
+                (MongoSettings.SETTING_HOST)         : mongoDBContainer.host,
+                (MongoSettings.SETTING_PORT)         : mongoDBContainer.getMappedPort(AbstractMongoGrailsExtension.DEFAULT_MONGO_PORT) as String,
+                //TODO: 'grails.mongodb.url': "mongodb://${host}:${port as String}/myDb" as String
+        ]
     }
 
     @Override
@@ -91,10 +88,6 @@ class GrailsDataMongoTckManager extends GrailsDataTckManager {
 
     @Override
     Session createSession() {
-        if (mongoDBContainer == null || !mongoDBContainer.isRunning()) {
-            return null
-        }
-        System.setProperty('mongodb.gorm.suite', 'true')
         def allClasses = getDomainClasses() as Class[]
         def ctx = new GenericApplicationContext()
         ctx.refresh()
@@ -102,7 +95,6 @@ class GrailsDataMongoTckManager extends GrailsDataTckManager {
         mongoDatastore = new MongoDatastore(configuration)
         mappingContext = mongoDatastore.mappingContext
         mappingContext.mappingFactory.registerCustomType(new AbstractMappingAwareCustomTypeMarshaller<Birthday, Document, Document>(Birthday) {
-
             @Override
             protected Object writeInternal(PersistentProperty property, String key, Birthday value, Document nativeTarget) {
 
@@ -144,58 +136,38 @@ class GrailsDataMongoTckManager extends GrailsDataTckManager {
 
     @Override
     void destroy() {
-        if (mongoDatastore != null) {
-            mongoDatastore.getMongoClient().listDatabaseNames().findAll { !(it in ['admin', 'config', 'local']) }.each {
-                try {
-                    mongoDatastore?.mongoClient?.listDatabaseNames()
-                            ?.findAll { !(it in ['admin', 'config', 'local']) }
-                            ?.each {
-                                try {
-                                    mongoDatastore.mongoClient.getDatabase(it as String).drop()
-                                }
-                                catch (ignored) {
-                                    log.warn("Could not drop ${it}")
-                                }
-                            }
-                    for (cls in domainClasses) {
-                        GormEnhancer.findValidationApi(cls).validator = null
-                    }
-                }
-                finally {
-                    try {
-                        mongoDatastore?.close()
-                    }
-                    catch (ignored) {
-                    }
-                    mongoDatastore = null
-                    mongoClient = null
-                    grailsApplication = null
-                    mappingContext = null
-                }
+        mongoDatastore.getMongoClient().listDatabaseNames().findAll {!(it in ['admin', 'config', 'local']) }.each {
+            try {
+                mongoDatastore.getMongoClient().getDatabase(it).drop()
+            }
+            catch(e) {
+                log.warn("Could not drop ${it}")
             }
         }
+        mongoDatastore.buildIndex()
+        for (cls in getDomainClasses()) {
+            GormEnhancer.findValidationApi(cls).setValidator(null)
+        }
 
         super.destroy()
     }
 
     @Override
     boolean supportsMultipleDataSources() {
-        mongoDBContainer != null && mongoDBContainer.isRunning()
+        true
     }
 
     @Override
     void setupMultiDataSource(Class... domainClasses) {
-        if (mongoDBContainer != null && mongoDBContainer.isRunning()) {
-            String host = mongoDBContainer.host
-            int port = mongoDBContainer.getMappedPort(AbstractMongoGrailsExtension.DEFAULT_MONGO_PORT)
-            Map config = [
-                    'grails.mongodb.url'        : "mongodb://${host}:${port}/tckDefaultDB" as String,
-                    'grails.mongodb.connections': [
-                            'secondary': ['url': "mongodb://${host}:${port}/tckSecondaryDB" as String],
-                    ],
-            ]
-            multiDataSourceDatastore = new MongoDatastore(DatastoreUtils.createPropertyResolver(config), domainClasses)
-        }
+        String host = mongoDBContainer.host
+        int port = mongoDBContainer.getMappedPort(AbstractMongoGrailsExtension.DEFAULT_MONGO_PORT)
+        Map config = [
+                'grails.mongodb.url'       : "mongodb://${host}:${port}/tckDefaultDB" as String,
+                'grails.mongodb.connections': [
+                        'secondary': ['url': "mongodb://${host}:${port}/tckSecondaryDB" as String],
+                ],
+        ]
+        multiDataSourceDatastore = new MongoDatastore(DatastoreUtils.createPropertyResolver(config), domainClasses)
     }
 
     @Override
@@ -212,32 +184,30 @@ class GrailsDataMongoTckManager extends GrailsDataTckManager {
     @Override
     def getServiceForConnection(Class serviceType, String connectionName) {
         multiDataSourceDatastore
-                ?.getDatastoreForConnection(connectionName)
-                ?.getService(serviceType)
+                .getDatastoreForConnection(connectionName)
+                .getService(serviceType)
     }
 
     @Override
     boolean supportsMultiTenantMultiDataSource() {
-        mongoDBContainer != null && mongoDBContainer.isRunning()
+        true
     }
 
     @Override
     void setupMultiTenantMultiDataSource(Class... domainClasses) {
-        if (mongoDBContainer != null && mongoDBContainer.isRunning()) {
-            String host = mongoDBContainer.host
-            int port = mongoDBContainer.getMappedPort(AbstractMongoGrailsExtension.DEFAULT_MONGO_PORT)
-            Map config = [
-                    'grails.gorm.multiTenancy.mode'               : MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR,
-                    'grails.gorm.multiTenancy.tenantResolverClass': SystemPropertyTenantResolver,
-                    'grails.mongodb.url'                          : "mongodb://${host}:${port}/tckMtDefaultDB" as String,
-                    'grails.mongodb.connections'                  : [
-                            'secondary': ['url': "mongodb://${host}:${port}/tckMtSecondaryDB" as String],
-                    ],
-            ]
-            multiTenantMultiDataSourceDatastore = new MongoDatastore(
-                    DatastoreUtils.createPropertyResolver(config), domainClasses
-            )
-        }
+        String host = mongoDBContainer.host
+        int port = mongoDBContainer.getMappedPort(AbstractMongoGrailsExtension.DEFAULT_MONGO_PORT)
+        Map config = [
+                'grails.gorm.multiTenancy.mode'               : MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR,
+                'grails.gorm.multiTenancy.tenantResolverClass' : SystemPropertyTenantResolver,
+                'grails.mongodb.url'                           : "mongodb://${host}:${port}/tckMtDefaultDB" as String,
+                'grails.mongodb.connections'                   : [
+                        'secondary': ['url': "mongodb://${host}:${port}/tckMtSecondaryDB" as String],
+                ],
+        ]
+        multiTenantMultiDataSourceDatastore = new MongoDatastore(
+                DatastoreUtils.createPropertyResolver(config), domainClasses
+        )
     }
 
     @Override
@@ -254,35 +224,17 @@ class GrailsDataMongoTckManager extends GrailsDataTckManager {
     @Override
     def getServiceForMultiTenantConnection(Class serviceType, String connectionName) {
         multiTenantMultiDataSourceDatastore
-                ?.getDatastoreForConnection(connectionName)
-                ?.getService(serviceType)
+                .getDatastoreForConnection(connectionName)
+                .getService(serviceType)
     }
 
     void setupValidator(Class entityClass, Validator validator = null) {
-        if (mappingContext != null) {
-            PersistentEntity entity = mappingContext.persistentEntities.find { PersistentEntity e -> e.javaClass == entityClass }
-            def messageSource = new StaticMessageSource()
-            def evaluator = new DefaultConstraintEvaluator(new DefaultConstraintRegistry(messageSource), mappingContext, Collections.emptyMap())
-            if (entity) {
-                mappingContext.addEntityValidator(entity, validator ?:
-                        new PersistentEntityValidator(entity, messageSource, evaluator))
-            }
-        }
-    }
-
-    static boolean isDockerAvailable() {
-        def candidates = [
-                System.getProperty('user.home') + '/.docker/run/docker.sock',
-                '/var/run/docker.sock',
-                System.getenv('DOCKER_HOST') ?: ''
-        ]
-        if (candidates.any { it && new File(it).exists() }) {
-            try {
-                return org.testcontainers.DockerClientFactory.instance().isDockerAvailable()
-            } catch (Throwable e) {
-                return false
-            }
+        PersistentEntity entity = mappingContext.persistentEntities.find { PersistentEntity e -> e.javaClass == entityClass }
+        def messageSource = new StaticMessageSource()
+        def evaluator = new DefaultConstraintEvaluator(new DefaultConstraintRegistry(messageSource), mappingContext, Collections.emptyMap())
+        if (entity) {
+            mappingContext.addEntityValidator(entity, validator ?:
+                    new PersistentEntityValidator(entity, messageSource, evaluator))
         }
-        return false
     }
 }