You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ma...@apache.org on 2026/03/26 08:18:57 UTC

(grails-core) 01/01: test: migrate `GrailsPluginManagerTests` to Spock

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

matrei pushed a commit to branch matrei/plugin-manager-tests
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 5eed19f25a547b077fd2d85891848290199acda7
Author: Mattias Reichel <ma...@gmail.com>
AuthorDate: Thu Mar 26 09:18:19 2026 +0100

    test: migrate `GrailsPluginManagerTests` to Spock
    
    Replaces legacy JUnit 3 test, which was previously not running,
    as there is currently no support for JUnit 3 in the build.
---
 .../grails/commons/GrailsPluginManagerTests.groovy | 360 +++++++++++++--------
 1 file changed, 222 insertions(+), 138 deletions(-)

diff --git a/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsPluginManagerTests.groovy b/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsPluginManagerTests.groovy
index 02a7fb4e71..fdf897ad16 100644
--- a/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsPluginManagerTests.groovy
+++ b/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsPluginManagerTests.groovy
@@ -16,220 +16,304 @@
  */
 package org.grails.commons
 
-import grails.core.GrailsApplication
-import grails.plugins.GrailsPlugin
-import grails.plugins.GrailsPluginManager
-import org.grails.commons.test.AbstractGrailsMockTests
-import grails.plugins.DefaultGrailsPluginManager
-import org.grails.web.servlet.context.support.WebRuntimeSpringConfiguration
+import spock.lang.Shared
+import spock.lang.Specification
+
 import org.springframework.beans.factory.support.RootBeanDefinition
 import org.springframework.beans.propertyeditors.ClassEditor
 import org.springframework.context.ApplicationContext
-import org.springframework.core.env.StandardEnvironment
 import org.springframework.web.servlet.i18n.CookieLocaleResolver
 
-class GrailsPluginManagerTests extends AbstractGrailsMockTests {
+import grails.core.DefaultGrailsApplication
+import grails.core.GrailsApplication
+import grails.plugins.DefaultGrailsPluginManager
+import org.grails.support.MockApplicationContext
 
-    private static final String RESOURCE_PATH = "classpath:org/grails/plugins/ClassEditorGrailsPlugin.groovy"
+class GrailsPluginManagerTests extends Specification {
 
-    protected void onSetUp() {
-        super.onSetUp()
+    private static final String TEST_PLUGIN_RESOURCE_PATH = 'classpath:org/grails/plugins/ClassEditorGrailsPlugin.groovy'
 
-        gcl.parseClass '''
-dataSource {
-    pooled = false
-    driverClassName = "org.h2.Driver"
-    username = "sa"
-    password = ""
-    dbCreate = "create-drop" // one of 'create', 'create-drop','update'
-    url = "jdbc:h2:mem:devDB"
-}
-hibernate {
-    cache.use_second_level_cache=true
-    cache.use_query_cache=true
-    cache.provider_class='org.hibernate.cache.OSCacheProvider'
-}'''
+    @Shared ApplicationContext ctx = new MockApplicationContext()
+    @Shared GrailsApplication grailsApplication = new DefaultGrailsApplication().tap {
+        applicationContext = ctx
+        initialise()
     }
 
-//    void testObservablePlugin() {
-//        def manager = new DefaultGrailsPluginManager(
-//            [MyGrailsPlugin, AnotherGrailsPlugin, ObservingGrailsPlugin] as Class[], ga)
-//
-//        manager.loadPlugins()
-//
-//        assertTrue manager.hasGrailsPlugin("another")
-//
-//        // Get the "another" plugin and all the plugins that are observing it.
-//        def plugin = manager.getGrailsPlugin("another")
-//        def observers = manager.getPluginObservers(plugin)
-//
-//        // Check that the observers are what we expect.
-//        def expectedObservers = ["observing"]
-//        assert observers*.name.containsAll(expectedObservers)
-//        assertEquals expectedObservers.size(), observers.size()
-//
-//        // the "my" plugin (is not observed by any other plugins).
-//        observers = manager.getPluginObservers(manager.getGrailsPlugin("my"))
-//        expectedObservers = []
-//
-//        assertTrue observers*.name.containsAll(expectedObservers)
-//        assertEquals expectedObservers.size(), observers.size()
-//
-//        // Make sure the observers are being notified of changes to the observed plugin.
-//        def event = [source:"foo"]
-//        manager.informObservers("another", event)
-//
-//        assertEquals "bar", event.source
-//    }
-
-    void testNoSelfObserving() {
-        def manager = new DefaultGrailsPluginManager([AnotherGrailsPlugin,ObservingAllGrailsPlugin] as Class[], ga)
-
-        manager.loadPlugins()
-
-        // Get the "another" plugin and all the plugins that are observing it.
-        def plugin = manager.getGrailsPlugin("another")
-        def observers = manager.getPluginObservers(plugin)
-
-        // Check that the observers are what we expect.
-        def expectedObservers = ["observingAll"]
-
-        assertTrue observers*.name.containsAll(expectedObservers)
-        assertEquals expectedObservers.size(), observers.size()
-
-        // Now check that the "observingAll" plugin is *not* observing itself.
-        observers = manager.getPluginObservers(manager.getGrailsPlugin("observingAll"))
-        expectedObservers = []
-
-        assertTrue observers*.name.containsAll(expectedObservers)
-        assertEquals expectedObservers.size(), observers.size()
+    def 'registers observers and informs them of plugin events'() {
+        given: 'a plugin manager with an observed plugin and an observer plugin'
+        def pluginManager = new DefaultGrailsPluginManager([
+                MyGrailsPlugin,
+                AnotherGrailsPlugin,
+                ObservingGrailsPlugin
+        ] as Class[], grailsApplication).tap {
+            applicationContext = ctx
+        }
+
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
+
+        then: 'the observed plugin is registered'
+        pluginManager.hasGrailsPlugin('another')
+
+        when: 'the observers for the observed plugin are requested'
+        def plugin = pluginManager.getGrailsPlugin('another')
+        def observers = pluginManager.getPluginObservers(plugin)
+
+        then: 'the expected observer is returned'
+        observers*.name.contains('observing')
+        observers.size() == 1
+
+        when: 'the observers for an unobserved plugin are requested'
+        observers = pluginManager.getPluginObservers(pluginManager.getGrailsPlugin('my'))
+
+        then: 'no observers are returned'
+        observers.empty
+
+        when: 'notifying the "another" plugin of an event'
+        def event = [source: 'foo']
+        pluginManager.informObservers('another', event)
+
+        then: 'the "another" plugin should have been notified and modified the event object'
+        event.source == 'bar'
     }
 
-    void testDisabledPlugin() {
-        def manager = new DefaultGrailsPluginManager([MyGrailsPlugin,AnotherGrailsPlugin,DisabledGrailsPlugin] as Class[], ga)
+    def 'wildcard observers do not observe themselves'() {
+        given: 'a wildcard observer and an observed plugin'
+        def pluginManager = new DefaultGrailsPluginManager([
+                AnotherGrailsPlugin,
+                ObservingAllGrailsPlugin
+        ] as Class[], grailsApplication)
+
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
 
-        manager.loadPlugins()
+        and: 'observers are requested for the observed plugin'
+        def plugin = pluginManager.getGrailsPlugin('another')
+        def observers = pluginManager.getPluginObservers(plugin)
 
-        assertTrue manager.hasGrailsPlugin("my")
-        assertNotNull manager.getGrailsPlugin("my").instance
-        assertFalse manager.hasGrailsPlugin("disabled")
+        then: 'the wildcard observer is returned'
+        observers*.name.contains('observingAll')
+        observers.size() == 1
+
+        when: 'the wildcard observer asks for its own observers'
+        observers = pluginManager.getPluginObservers(pluginManager.getGrailsPlugin('observingAll'))
+
+        then: 'it does not observe itself'
+        observers.empty
     }
 
-    void testDefaultGrailsPluginManager() {
-        DefaultGrailsPluginManager manager = new DefaultGrailsPluginManager(RESOURCE_PATH,ga)
-        assertEquals(1, manager.getPluginResources().length)
+    def 'skips disabled plugins during loading'() {
+        given: 'a plugin manager with one disabled plugin'
+        def pluginManager = new DefaultGrailsPluginManager([
+                AnotherGrailsPlugin,
+                DisabledGrailsPlugin
+        ] as Class[], grailsApplication)
+
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
+
+        then: 'enabled plugins are loaded and disabled plugins are skipped'
+        with(pluginManager) {
+            hasGrailsPlugin('another')
+            getGrailsPlugin('another').instance
+            !hasGrailsPlugin('disabled')
+        }
     }
 
-    void testLoadPlugins() {
-        GrailsPluginManager manager = new DefaultGrailsPluginManager(RESOURCE_PATH,ga)
-        manager.loadPlugins()
+    def 'initializes the plugin manager from an explicit plugin resource path'() {
+        given: 'a plugin manager constructed with an explicit test plugin resource path'
+        def pluginManager = new DefaultGrailsPluginManager(TEST_PLUGIN_RESOURCE_PATH, grailsApplication)
+        
+        expect: 'the plugin resource path is resolved into a single plugin resource'
+        pluginManager.pluginResources.length == 1
+    }
+
+    def 'loads discovered plugins and resolves them by name and version'() {
+        given: 'the plugin manager has been initialized from the resource path'
+        def pluginManager = new DefaultGrailsPluginManager(TEST_PLUGIN_RESOURCE_PATH, grailsApplication)
+
+        when: 'plugins are loaded and looked up by name'
+        pluginManager.loadPlugins()
+
+        and: 'the classEditor plugin is requested'
+        def plugin = pluginManager.getGrailsPlugin('classEditor')
+
+        then: 'the plugin is available with the expected version'
+        plugin
+        plugin.name == 'classEditor'
+        plugin.version == '1.1'
+
+        when: 'the plugin is requested with the matching version'
+        plugin = pluginManager.getGrailsPlugin('classEditor', '1.1')
 
-        GrailsPlugin plugin = manager.getGrailsPlugin("classEditor")
-        assertNotNull(plugin)
-        assertEquals("classEditor",plugin.getName())
-        assertEquals("1.1", plugin.getVersion())
+        then: 'the plugin is returned'
+        plugin
 
-        plugin = manager.getGrailsPlugin("classEditor", "1.1")
-        assertNotNull(plugin)
+        when: 'the plugin is requested with a non-matching version'
+        plugin = pluginManager.getGrailsPlugin('classEditor', '1.2')
 
-        plugin = manager.getGrailsPlugin("classEditor", "1.2")
-        assertNull(plugin)
+        then: 'no plugin is returned'
+        !plugin
     }
 
-    void testWithLoadLastPlugin() {
-        def manager = new DefaultGrailsPluginManager([MyGrailsPlugin,AnotherGrailsPlugin,ShouldLoadLastGrailsPlugin] as Class[], ga)
-        manager.loadPlugins()
+    def 'loads plugins successfully when a plugin declares loadAfter dependencies'() {
+        given: 'a plugin manager with a plugin that declares loadAfter dependencies'
+        def pluginManager = new DefaultGrailsPluginManager([
+                ShouldLoadLastGrailsPlugin,
+                MyGrailsPlugin,
+                AnotherGrailsPlugin
+            ] as Class[], grailsApplication
+        )
+
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
+        def plugins = pluginManager.getAllPlugins()*.name
+
+        then: 'the plugin with loadAfter dependencies is loaded last'
+        plugins.indexOf('shouldLoadLast') > plugins.indexOf('my')
+        plugins.indexOf('shouldLoadLast') > plugins.indexOf('someOther')
     }
 
-    void testDependencyResolutionFailure() {
-        def manager = new DefaultGrailsPluginManager([MyGrailsPlugin] as Class[], ga)
+    def 'does not load a plugin when required dependencies are missing'() {
+        given: 'a plugin manager missing a required dependency'
+        def pluginManager = new DefaultGrailsPluginManager([
+                MyGrailsPlugin // depends on 'another' plugin which is not included here
+        ] as Class[], grailsApplication)
 
-        manager.loadPlugins()
-        assert !manager.hasGrailsPlugin("my")
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
+
+        then: 'the dependent plugin is not loaded'
+        !pluginManager.hasGrailsPlugin('my')
     }
 
-    void testDependencyResolutionSuccess() {
-        def manager = new DefaultGrailsPluginManager([MyGrailsPlugin,AnotherGrailsPlugin, SomeOtherGrailsPlugin] as Class[], ga)
+    def 'loads plugins when required dependencies are satisfied'() {
+        given: 'a plugin manager with all required dependencies present'
+        def pluginManager = new DefaultGrailsPluginManager([
+                MyGrailsPlugin,
+                AnotherGrailsPlugin,
+                SomeOtherGrailsPlugin
+        ] as Class[], grailsApplication)
+
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
+
+        then: 'the dependent plugin is loaded'
+        pluginManager.hasGrailsPlugin('my')
+    }
 
-        manager.loadPlugins()
+    def 'evicts superseded plugins during loading'() {
+        given: 'a plugin manager with an evicting plugin'
+        def pluginManager = new DefaultGrailsPluginManager([
+                MyGrailsPlugin,
+                AnotherGrailsPlugin,
+                SomeOtherGrailsPlugin,
+                ShouldEvictSomeOtherGrailsPlugin
+        ] as Class[], grailsApplication)
+
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
+
+        then: 'the evicted plugin is removed while the others remain loaded'
+        with(pluginManager) {
+            !hasGrailsPlugin('someOther')
+            hasGrailsPlugin('my')
+            hasGrailsPlugin('another')
+            hasGrailsPlugin('shouldEvictSomeOther')
+        }
     }
 
-    void testEviction() {
-        def manager = new DefaultGrailsPluginManager([MyGrailsPlugin,AnotherGrailsPlugin,SomeOtherGrailsPlugin,ShouldEvictSomeOtherGrailsPlugin] as Class[], ga)
+    def 'invokes plugin shutdown callbacks when the manager shuts down'() {
+        given: 'a plugin manager with a plugin that defines an onShutdown callback'
+        def pluginManager = new DefaultGrailsPluginManager([
+                MyGrailsPlugin,
+                AnotherGrailsPlugin, // needed to satisfy the dependency of MyGrailsPlugin
+        ] as Class[], grailsApplication).tap {
+             applicationContext = ctx
+        }
 
-        manager.loadPlugins()
+        when: 'plugins are loaded'
+        pluginManager.loadPlugins()
 
-        assertFalse manager.hasGrailsPlugin("someOther")
-        assertTrue manager.hasGrailsPlugin("my")
-        assertTrue manager.hasGrailsPlugin("another")
-        assertTrue manager.hasGrailsPlugin("shouldEvictSomeOther")
-    }
+        then: 'the plugin is loaded'
+        pluginManager.hasGrailsPlugin('my')
 
-    void testShutdownCalled() {
-        def manager = new DefaultGrailsPluginManager([MyGrailsPlugin,AnotherGrailsPlugin] as Class[], ga)
-        manager.applicationContext = [getBeansOfType: { Class c -> [:] }, getEnvironment: {-> new StandardEnvironment() } ] as ApplicationContext
+        and: 'its shutdown marker is unchanged'
+        MyGrailsPlugin.SHUTDOWN_FIELD == 'not-updated'
 
-        manager.loadPlugins()
+        when: 'the plugin manager is shut down'
+        pluginManager.shutdown()
 
-        assertEquals "nullme",MyGrailsPlugin.SHUTDOWN_FIELD
-        manager.shutdown()
-        assertNull MyGrailsPlugin.SHUTDOWN_FIELD
+        then: 'the shutdown callback updates the marker'
+        MyGrailsPlugin.SHUTDOWN_FIELD == 'updated'
     }
 }
 
 class MyGrailsPlugin {
 
-    static SHUTDOWN_FIELD = "nullme"
-    def dependsOn = [another:1.2]
+    static SHUTDOWN_FIELD = 'not-updated'
+
+    def dependsOn = [another: 1.2]
     def version = 1.1
     def doWithSpring = {
-        classEditor(ClassEditor,application.classLoader)
+        classEditor(ClassEditor, grailsApplication.classLoader)
     }
     def onShutdown = {
-        SHUTDOWN_FIELD = null
+        SHUTDOWN_FIELD = 'updated'
     }
 }
 
 class AnotherGrailsPlugin {
+
     def version = 1.2
     def watchedResources = ['classpath:org/codehaus/groovy/grails/plugins/*.xml']
     def doWithApplicationContext = { ctx ->
-        RootBeanDefinition bd = new RootBeanDefinition(CookieLocaleResolver)
-        ctx.registerBeanDefinition("localeResolver", bd)
+        ctx.registerBeanDefinition('localeResolver', new RootBeanDefinition(CookieLocaleResolver))
     }
 }
 
 class SomeOtherGrailsPlugin {
+
     def version = 1.4
-    def dependsOn = [my:1.1, another:1.2]
+    def dependsOn = [my: 1.1, another: 1.2]
 }
 
 class ShouldLoadLastGrailsPlugin {
-    def loadAfter = ["my", "someOther"]
+
+    def loadAfter = ['my', 'someOther']
     def version = 1.5
 }
 
 class ShouldEvictSomeOtherGrailsPlugin {
+
     def evict = ['someOther']
     def version = 1.1
 }
 
-class DisabledGrailsPlugin {
-    def version = 1.0
-    def status = "disabled"
-}
-
 class ObservingGrailsPlugin {
-    def version = "1.0-RC1"
+
+    def version = '1.0-RC1'
     def observe = ['another']
 
     def onChange = { event ->
         assert event.source != null
-        event.source = "bar"
+        event.source = 'bar'
     }
 }
 
 class ObservingAllGrailsPlugin {
-    def version = "1.0"
+
+    def version = '1.0'
     def observe = ['*']
 }
+
+/**
+ * This plugin should be the last one defined here because because a grails-plugin.xml file will be generated
+ * in build/classes/groovy/test/META-INF for the last plugin defined and the plugin manager will always
+ * load it via finding that file. This fixes problem described in commit 3aaeaa95
+ */
+class DisabledGrailsPlugin {
+
+    def version = 1.0
+    def status = 'disabled'
+}