You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@grails.apache.org by "jamesfredley (via GitHub)" <gi...@apache.org> on 2026/07/17 19:29:14 UTC

[PR] Add Spring AOT context smoke and pending gap probes [grails-core]

jamesfredley opened a new pull request, #16001:
URL: https://github.com/apache/grails-core/pull/16001

   ## Summary
   
   - add a test-only Spring AOT compatibility spike (`GrailsAotSmokeSpec`)
   - one passing baseline: a minimal Grails context is processed by `ApplicationContextAotGenerator` without error
   - two `@PendingFeature` probes document genuine, currently-unsupported Grails AOT gaps (dynamic artefact registry preservation and plugin DSL bean preservation), with preconditions verified non-pending so only the final AOT assertion is pending
   
   ## Scope note
   
   This is a draft/test-only seed - no production or native-image changes. The pending probes are expected to fail today and are structured to flip to passing when the corresponding Grails AOT support is implemented. `@PendingFeature` is scoped to `SpockAssertionError` so unrelated regressions still surface; both class loaders and contexts are closed in cleanup.
   
   ## Verification
   
   - :grails-core:test --tests grails.aot.GrailsAotSmokeSpec (1 passed, 2 expected-pending)
   - :grails-core:test
   - git diff --check
   
   This is an AI-generated starting point for Spring AOT support investigation.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5815482032

   Scott did a lot of work on the AOT front and added testing as part of commit 79bd5b9488, Aug 13.  This test is now covered by his tests, so I'm going to close this (the pending features actually fail now because they're implemented)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5006821564

   <!-- Bito Reply -->
   The observation is correct. In the test case, `dynamicPluginBean` is registered into `runtimeContext` (line 123), but the `ApplicationContextAotGenerator` is invoked using `context` (line 127), which does not contain the bean definition. Consequently, the AOT generator cannot see or record the bean, causing the final assertion to fail as expected.
   
   **grails-core/src/test/groovy/grails/aot/GrailsAotSmokeSpec.groovy**
   ```
   springConfiguration.registerBeansWithContext(runtimeContext)
               if (!runtimeContext.beanFactory.containsBeanDefinition('dynamicPluginBean')) {
                   throw new IllegalStateException('Plugin doWithSpring did not register dynamicPluginBean before AOT processing')
               }
               new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext)
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5006999553

   ## [Codecov](https://app.codecov.io/gh/apache/grails-core/pull/16001?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   :white_check_mark: All modified and coverable lines are covered by tests.
   :white_check_mark: Project coverage is 51.1158%. Comparing base ([`b980413`](https://app.codecov.io/gh/apache/grails-core/commit/b980413820757347744aa6c10c44d7103babea39?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`69efcb6`](https://app.codecov.io/gh/apache/grails-core/commit/69efcb67a2163ce8f2d7cc399c975daa76111202?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   [![Impacted file tree graph](https://app.codecov.io/gh/apache/grails-core/pull/16001/graphs/tree.svg?width=650&height=150&src=pr&token=qJrryhpk7u&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)](https://app.codecov.io/gh/apache/grails-core/pull/16001?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   
   ```diff
   @@               Coverage Diff                @@
   ##             8.0.x     #16001         +/-   ##
   ================================================
   + Coverage         0   51.1158%   +51.1158%     
   - Complexity       0      17597      +17597     
   ================================================
     Files            0       2041       +2041     
     Lines            0      95493      +95493     
     Branches         0      16587      +16587     
   ================================================
   + Hits             0      48812      +48812     
   - Misses           0      39393      +39393     
   - Partials         0       7288       +7288     
   ```
   [see 2041 files with indirect coverage changes](https://app.codecov.io/gh/apache/grails-core/pull/16001/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   </details>
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
   - :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty closed pull request #16001: Add Spring AOT context smoke and pending gap probes
URL: https://github.com/apache/grails-core/pull/16001


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5085219089

   ## 🚨 TestLens detected 1 failed test 🚨
   
   Here is what you can do:
   
   1) Inspect the test failures carefully.
   2) If you are convinced that some of the tests are flaky, you can mute them below.
   3) Finally, trigger a rerun by checking the rerun checkbox.
   
   ### Test Summary
   
   #### [CI / Functional Tests \(Java 25, indy\=false\)](https://github.com/apache/grails-core/actions/runs/30217624061/job/89834485899?pr=16001) > :grails-test-examples-app1:integrationTest
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | AsyncPromiseSpec > async task with timeout completes within time limit > async task with timeout completes within time limit [delay: 100, timeout: 500, elapsedMin: 100, status: completed, result: Completed in time, <span>#</span>0] | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   
   🏷️ Commit: 1f290474c078fc571ea7698ecfe99c462f03c50d
   ▶️ Tests:  21823 executed
   🟡 Checks: 26/54 completed
   
   ### Test Failures
   
   <details>
   
   <summary><strong>AsyncPromiseSpec > async task with timeout completes within time limit > async task with timeout completes within time limit [delay: 100, timeout: 500, elapsedMin: 100, status: completed, result: Completed in time, <span>#</span>0]</strong> (:grails-test-examples-app1:integrationTest in <a href="https://github.com/apache/grails-core/actions/runs/30217624061/job/89834485899?pr=16001">CI / Functional Tests (Java 25, indy=false)</a>)</summary>
   
   ```
   java.net.http.HttpTimeoutException: request timed out
   	at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:921)
   	at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
   	at org.apache.grails.testing.http.client.HttpClientSupport$Trait$Helper.send(HttpClientSupport.groovy:1133)
   	at org.apache.grails.testing.http.client.HttpClientSupport$Trait$Helper.send(HttpClientSupport.groovy:1122)
   	at org.apache.grails.testing.http.client.HttpClientSupport$Trait$Helper.http(HttpClientSupport.groovy:203)
   	at org.apache.grails.testing.http.client.HttpClientSupport$Trait$Helper.http(HttpClientSupport.groovy:169)
   	at functionaltests.async.AsyncPromiseSpec.async task with timeout completes within time limit(AsyncPromiseSpec.groovy:116)
   ```
   
   </details>
   
   ### Muted Tests
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 AsyncPromiseSpec > async task with timeout completes within time limit <!-- uniqueId=[engine:spock]/[spec:functionaltests.async.AsyncPromiseSpec]/[feature:$spock_feature_0_5] -->
   
   Reuse successful test results:
   
   🔲 ♻️ Only rerun the tests that failed or were muted before
   
   Click the checkbox to trigger a rerun:
   
   🔲 **Rerun jobs**
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5007347084

   ## ✅ All tests passed ✅
   
   🏷️ Commit: 69efcb67a2163ce8f2d7cc399c975daa76111202
   ▶️ Tests:  9432 executed
   ⚪️ Checks: 61/61 completed
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#discussion_r3605677189


##########
grails-core/src/test/groovy/grails/aot/GrailsAotSmokeSpec.groovy:
##########
@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *    https://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 grails.aot
+
+import groovy.lang.GroovyClassLoader
+
+import org.springframework.aot.generate.ClassNameGenerator
+import org.springframework.aot.generate.DefaultGenerationContext
+import org.springframework.aot.generate.GeneratedFiles
+import org.springframework.aot.generate.InMemoryGeneratedFiles
+import org.springframework.context.aot.ApplicationContextAotGenerator
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.javapoet.ClassName
+
+import grails.core.DefaultGrailsApplication
+import grails.core.GrailsApplication
+import grails.plugins.DefaultGrailsPluginManager
+import grails.plugins.GrailsPluginManager
+import org.apache.grails.core.plugins.DefaultPluginDiscovery
+import org.apache.grails.core.plugins.PluginDiscovery
+import org.grails.spring.DefaultRuntimeSpringConfiguration
+import org.spockframework.runtime.SpockAssertionError
+import spock.lang.PendingFeature
+import spock.lang.Specification
+
+class GrailsAotSmokeSpec extends Specification {
+
+    void 'Spring AOT generates an initializer for a minimal Grails context'() {
+        given: 'a non-refreshed context with the minimal Grails application bean'
+            def context = new GenericApplicationContext()
+            context.registerBean(DefaultGrailsApplication)
+
+        when: 'Spring processes the context through its public AOT API'
+            ClassName initializer = new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext())
+
+        then: 'an initializer entry point is generated'
+            initializer != null
+            initializer.simpleName().contains('ApplicationContextInitializer')
+
+        cleanup:
+            context.close()
+    }
+
+    @PendingFeature(exceptions = [SpockAssertionError], reason = 'Blocker: Grails discovers artefact classes at runtime, and no AOT contribution currently records that runtime artefact registry as generated source.')
+    void 'Spring AOT records a dynamically discovered Grails artefact'() {
+        given: 'an artefact discovered from a runtime Groovy class loader'
+            def classLoader = new GroovyClassLoader()
+            Class<?> dynamicArtefact = classLoader.parseClass('''
+                package grails.aot.dynamic
+                class AotDynamicController { }
+            ''')
+            def application = new DefaultGrailsApplication([dynamicArtefact] as Class<?>[], classLoader)
+            def context = new GenericApplicationContext()
+            context.beanFactory.registerSingleton(GrailsApplication.APPLICATION_ID, application)
+            def generationContext = generationContext()
+
+        when: 'Grails initializes its runtime artefact registry before Spring processes the context through its public AOT API'
+            application.initialise()
+            if (!application.allArtefacts.contains(dynamicArtefact)) {
+                throw new IllegalStateException('Grails artefact registry did not contain the dynamically loaded controller before AOT processing')
+            }
+            new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext)
+            generationContext.writeGeneratedContent()
+
+        then: 'the generated source preserves the runtime-discovered artefact type'
+            generatedSource(generationContext).contains(dynamicArtefact.name)
+
+        cleanup:
+            context.close()
+            classLoader.close()
+    }
+
+    @PendingFeature(exceptions = [SpockAssertionError], reason = 'Blocker: plugin doWithSpring closures are evaluated from runtime Groovy classes, with no AOT contribution that converts their bean definitions into build-time generated source.')
+    void 'Spring AOT records a dynamically loaded plugin doWithSpring bean'() {
+        given: 'a plugin class loaded at runtime with a Groovy bean-definition closure'
+            def classLoader = new GroovyClassLoader()
+            Class<?> dynamicPlugin = classLoader.parseClass('''
+                class AotDynamicGrailsPlugin {
+                    def version = '1.0'
+                    def doWithSpring = {
+                        dynamicPluginBean(Object)
+                    }
+                }
+            ''')
+            def application = new DefaultGrailsApplication([] as Class<?>[], classLoader)
+            def context = new GenericApplicationContext()
+            def runtimeContext = new GenericApplicationContext()
+            application.mainContext = context
+            def discovery = new DefaultPluginDiscovery([dynamicPlugin] as Class<?>[])
+            discovery.loadPluginsFromClasspath = false
+            discovery.init(context.environment)
+            def pluginManager = new DefaultGrailsPluginManager(application, discovery)
+            pluginManager.loadPlugins()
+            context.beanFactory.registerSingleton(PluginDiscovery.BEAN_NAME, discovery)
+            context.beanFactory.registerSingleton(GrailsPluginManager.BEAN_NAME, pluginManager)
+            def generationContext = generationContext()
+
+        when: 'the runtime plugin configuration phase registers its DSL bean before Spring processes the context through its public AOT API'
+            def springConfiguration = new DefaultRuntimeSpringConfiguration()
+            pluginManager.doRuntimeConfiguration(springConfiguration)
+            springConfiguration.registerBeansWithContext(runtimeContext)
+            if (!runtimeContext.beanFactory.containsBeanDefinition('dynamicPluginBean')) {
+                throw new IllegalStateException('Plugin doWithSpring did not register dynamicPluginBean before AOT processing')
+            }
+            new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext)

Review Comment:
   In this pending probe, `dynamicPluginBean` is registered into `runtimeContext`, but AOT generation is run against `context`. As written, the final assertion is guaranteed to fail (even after future AOT support) because the processed context never contains the plugin bean definition.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5084352143

   ## 🚨 TestLens detected 1 failed test 🚨
   
   Here is what you can do:
   
   1) Inspect the test failures carefully.
   2) If you are convinced that some of the tests are flaky, you can mute them below.
   3) Finally, trigger a rerun by checking the rerun checkbox.
   
   ### Test Summary
   
   #### [CI / Functional Tests \(Java 25, indy\=false\)](https://github.com/apache/grails-core/actions/runs/30208693519/job/89811146547?pr=16001) > :grails-test-examples-micronaut:integrationTest
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | MicronautErsatzRoundtripSpec > full roundtrip: ersatz mocks empty response body | ❌ | 0%&nbsp;🟢 |
   
   🏷️ Commit: eae3711480262fea4651d1a19056abe0d870ee0d
   ▶️ Tests:  33511 executed
   🟡 Checks: 37/54 completed
   
   ### Test Failures
   
   <details>
   
   <summary><strong>MicronautErsatzRoundtripSpec > full roundtrip: ersatz mocks empty response body</strong> (:grails-test-examples-micronaut:integrationTest in <a href="https://github.com/apache/grails-core/actions/runs/30208693519/job/89811146547?pr=16001">CI / Functional Tests (Java 25, indy=false)</a>)</summary>
   
   ```
   io.micronaut.http.client.exceptions.HttpClientException: Client 'grails-self': Error occurred reading HTTP response: Connection reset
   	at io.micronaut.http.client.netty.NettyHttpClient.handleResponseError(NettyHttpClient.java:1911)
   	at io.micronaut.http.client.netty.NettyHttpClient$6.fail(NettyHttpClient.java:1587)
   	at io.micronaut.http.client.netty.Http1ResponseHandler$BeforeResponse.exceptionCaught(Http1ResponseHandler.java:150)
   	at io.micronaut.http.client.netty.Http1ResponseHandler.exceptionCaught(Http1ResponseHandler.java:90)
   	at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:290)
   	at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:272)
   	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireExceptionCaught(CombinedChannelDuplexHandler.java:422)
   	at io.netty.channel.ChannelHandlerAdapter.exceptionCaught(ChannelHandlerAdapter.java:92)
   	at io.netty.channel.CombinedChannelDuplexHandler$1.fireExceptionCaught(CombinedChannelDuplexHandler.java:144)
   	at io.netty.channel.ChannelInboundHandlerAdapter.exceptionCaught(ChannelInboundHandlerAdapter.java:143)
   	at io.netty.channel.CombinedChannelDuplexHandler.exceptionCaught(CombinedChannelDuplexHandler.java:229)
   	at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:290)
   	at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:272)
   	at io.netty.channel.DefaultChannelPipeline$HeadContext.exceptionCaught(DefaultChannelPipeline.java:1396)
   	at io.netty.channel.DefaultChannelPipeline.fireExceptionCaught(DefaultChannelPipeline.java:890)
   	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.handleReadException(AbstractNioByteChannel.java:132)
   	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:187)
   	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445)
   	at io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:388)
   	at io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:596)
   	at io.netty.channel.nio.NioIoHandler.processSelectedKeysOptimized(NioIoHandler.java:571)
   	at io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:512)
   	at io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:484)
   	at io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225)
   	at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196)
   	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1195)
   	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
   	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
   	at java.base/java.lang.Thread.run(Thread.java:1474)
   	Suppressed: java.lang.Exception: #block terminated with an error
   		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:104)
   		at reactor.core.publisher.Mono.block(Mono.java:1773)
   		at io.micronaut.http.client.netty.NettyHttpClient$1.exchange(NettyHttpClient.java:426)
   		at io.micronaut.http.client.netty.NettyHttpClient$1.retrieve(NettyHttpClient.java:435)
   		at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.lambda$handleSynchronous$2(HttpClientIntroductionAdvice.java:245)
   		at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleBlockingCall(HttpClientIntroductionAdvice.java:641)
   		at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleSynchronous(HttpClientIntroductionAdvice.java:244)
   		at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.intercept(HttpClientIntroductionAdvice.java:196)
   		at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:142)
   		at micronaut.ExternalApiService.fetchById(ExternalApiService.groovy:56)
   		at micronaut.MicronautErsatzRoundtripSpec.full roundtrip: ersatz mocks empty response body(MicronautErsatzRoundtripSpec.groovy:394)
   Caused by: java.net.SocketException: Connection reset
   	at java.base/sun.nio.ch.SocketChannelImpl.throwConnectionReset(SocketChannelImpl.java:399)
   	at java.base/sun.nio.ch.SocketChannelImpl.implRead(SocketChannelImpl.java:431)
   	at java.base/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:489)
   	at io.netty.buffer.AdaptivePoolingAllocator$AdaptiveByteBuf.setBytes(AdaptivePoolingAllocator.java:2018)
   	at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1139)
   	at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:350)
   	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:161)
   	... 12 more
   ```
   
   </details>
   
   ### Muted Tests
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 MicronautErsatzRoundtripSpec > full roundtrip: ersatz mocks empty response body <!-- uniqueId=[engine:spock]/[spec:micronaut.MicronautErsatzRoundtripSpec]/[feature:$spock_feature_0_14] -->
   
   Reuse successful test results:
   
   🔲 ♻️ Only rerun the tests that failed or were muted before
   
   Click the checkbox to trigger a rerun:
   
   🔲 **Rerun jobs**
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#discussion_r3610590322


##########
grails-core/src/test/groovy/grails/aot/GrailsAotSmokeSpec.groovy:
##########
@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *    https://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 grails.aot
+
+import groovy.lang.GroovyClassLoader
+
+import org.springframework.aot.generate.ClassNameGenerator
+import org.springframework.aot.generate.DefaultGenerationContext
+import org.springframework.aot.generate.GeneratedFiles
+import org.springframework.aot.generate.InMemoryGeneratedFiles
+import org.springframework.context.aot.ApplicationContextAotGenerator
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.javapoet.ClassName
+
+import grails.core.DefaultGrailsApplication
+import grails.core.GrailsApplication
+import grails.plugins.DefaultGrailsPluginManager
+import grails.plugins.GrailsPluginManager
+import org.apache.grails.core.plugins.DefaultPluginDiscovery
+import org.apache.grails.core.plugins.PluginDiscovery
+import org.grails.spring.DefaultRuntimeSpringConfiguration
+import org.spockframework.runtime.SpockAssertionError
+import spock.lang.PendingFeature
+import spock.lang.Specification
+
+class GrailsAotSmokeSpec extends Specification {
+
+    void 'Spring AOT generates an initializer for a minimal Grails context'() {
+        given: 'a non-refreshed context with the minimal Grails application bean'
+            def context = new GenericApplicationContext()
+            context.registerBean(DefaultGrailsApplication)
+
+        when: 'Spring processes the context through its public AOT API'
+            ClassName initializer = new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext())
+
+        then: 'an initializer entry point is generated'
+            initializer != null
+            initializer.simpleName().contains('ApplicationContextInitializer')
+
+        cleanup:
+            context.close()
+    }
+
+    @PendingFeature(exceptions = [SpockAssertionError], reason = 'Blocker: Grails discovers artefact classes at runtime, and no AOT contribution currently records that runtime artefact registry as generated source.')
+    void 'Spring AOT records a dynamically discovered Grails artefact'() {
+        given: 'an artefact discovered from a runtime Groovy class loader'
+            def classLoader = new GroovyClassLoader()
+            Class<?> dynamicArtefact = classLoader.parseClass('''
+                package grails.aot.dynamic
+                class AotDynamicController { }
+            ''')
+            def application = new DefaultGrailsApplication([dynamicArtefact] as Class<?>[], classLoader)
+            def context = new GenericApplicationContext()
+            context.beanFactory.registerSingleton(GrailsApplication.APPLICATION_ID, application)

Review Comment:
   Similar wiring concern: the `GrailsApplication` goes in via `registerSingleton`, and Spring AOT only processes *bean definitions* — manually registered singletons are skipped entirely. So the generated source can't reference the artefact registry through any implementation, and the probe conflates two gaps: (1) singleton registration being invisible to AOT, and (2) no Grails AOT contribution for the artefact registry. Registering it as a bean definition (like the first test's `registerBean`) would isolate gap (2), which is the one the `reason` text claims to document.
   
   Also worth noting somewhere: a class parsed by a runtime `GroovyClassLoader` has no build-time bytecode, so "generated source contains the class name" is about the most a future AOT contribution could do (registry metadata), and this string match on joined sources is how the probe would flip — fine, but a comment saying so would help whoever implements it.



##########
grails-core/src/test/groovy/grails/aot/GrailsAotSmokeSpec.groovy:
##########
@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *    https://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 grails.aot
+
+import groovy.lang.GroovyClassLoader
+
+import org.springframework.aot.generate.ClassNameGenerator
+import org.springframework.aot.generate.DefaultGenerationContext
+import org.springframework.aot.generate.GeneratedFiles
+import org.springframework.aot.generate.InMemoryGeneratedFiles
+import org.springframework.context.aot.ApplicationContextAotGenerator
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.javapoet.ClassName
+
+import grails.core.DefaultGrailsApplication
+import grails.core.GrailsApplication
+import grails.plugins.DefaultGrailsPluginManager
+import grails.plugins.GrailsPluginManager
+import org.apache.grails.core.plugins.DefaultPluginDiscovery
+import org.apache.grails.core.plugins.PluginDiscovery
+import org.grails.spring.DefaultRuntimeSpringConfiguration
+import org.spockframework.runtime.SpockAssertionError
+import spock.lang.PendingFeature
+import spock.lang.Specification
+
+class GrailsAotSmokeSpec extends Specification {
+
+    void 'Spring AOT generates an initializer for a minimal Grails context'() {
+        given: 'a non-refreshed context with the minimal Grails application bean'
+            def context = new GenericApplicationContext()
+            context.registerBean(DefaultGrailsApplication)
+
+        when: 'Spring processes the context through its public AOT API'
+            ClassName initializer = new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext())
+
+        then: 'an initializer entry point is generated'
+            initializer != null
+            initializer.simpleName().contains('ApplicationContextInitializer')
+
+        cleanup:
+            context.close()
+    }
+
+    @PendingFeature(exceptions = [SpockAssertionError], reason = 'Blocker: Grails discovers artefact classes at runtime, and no AOT contribution currently records that runtime artefact registry as generated source.')

Review Comment:
   Small correction to the scoping claim in the PR description: `@PendingFeature` treats *any* `AssertionError` as pending regardless of the `exceptions` list (the list only extends handling to non-assertion throwables). So `exceptions = [SpockAssertionError]` is effectively redundant — its real effect is that non-assertion exceptions (the `IllegalStateException` guards, or `processAheadOfTime` starting to throw) hard-fail, which is the behavior you want. But an unrelated `AssertionError` from Spring internals would still be silently swallowed as pending — the "unrelated regressions still surface" guarantee only holds for exceptions, not assertion errors.



##########
grails-core/src/test/groovy/grails/aot/GrailsAotSmokeSpec.groovy:
##########
@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *    https://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 grails.aot
+
+import groovy.lang.GroovyClassLoader
+
+import org.springframework.aot.generate.ClassNameGenerator
+import org.springframework.aot.generate.DefaultGenerationContext
+import org.springframework.aot.generate.GeneratedFiles
+import org.springframework.aot.generate.InMemoryGeneratedFiles
+import org.springframework.context.aot.ApplicationContextAotGenerator
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.javapoet.ClassName
+
+import grails.core.DefaultGrailsApplication
+import grails.core.GrailsApplication
+import grails.plugins.DefaultGrailsPluginManager
+import grails.plugins.GrailsPluginManager
+import org.apache.grails.core.plugins.DefaultPluginDiscovery
+import org.apache.grails.core.plugins.PluginDiscovery
+import org.grails.spring.DefaultRuntimeSpringConfiguration
+import org.spockframework.runtime.SpockAssertionError
+import spock.lang.PendingFeature
+import spock.lang.Specification
+
+class GrailsAotSmokeSpec extends Specification {
+
+    void 'Spring AOT generates an initializer for a minimal Grails context'() {
+        given: 'a non-refreshed context with the minimal Grails application bean'
+            def context = new GenericApplicationContext()
+            context.registerBean(DefaultGrailsApplication)
+
+        when: 'Spring processes the context through its public AOT API'
+            ClassName initializer = new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext())
+
+        then: 'an initializer entry point is generated'
+            initializer != null
+            initializer.simpleName().contains('ApplicationContextInitializer')
+
+        cleanup:
+            context.close()
+    }
+
+    @PendingFeature(exceptions = [SpockAssertionError], reason = 'Blocker: Grails discovers artefact classes at runtime, and no AOT contribution currently records that runtime artefact registry as generated source.')
+    void 'Spring AOT records a dynamically discovered Grails artefact'() {
+        given: 'an artefact discovered from a runtime Groovy class loader'
+            def classLoader = new GroovyClassLoader()
+            Class<?> dynamicArtefact = classLoader.parseClass('''
+                package grails.aot.dynamic
+                class AotDynamicController { }
+            ''')
+            def application = new DefaultGrailsApplication([dynamicArtefact] as Class<?>[], classLoader)
+            def context = new GenericApplicationContext()
+            context.beanFactory.registerSingleton(GrailsApplication.APPLICATION_ID, application)
+            def generationContext = generationContext()
+
+        when: 'Grails initializes its runtime artefact registry before Spring processes the context through its public AOT API'
+            application.initialise()
+            if (!application.allArtefacts.contains(dynamicArtefact)) {
+                throw new IllegalStateException('Grails artefact registry did not contain the dynamically loaded controller before AOT processing')
+            }
+            new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext)
+            generationContext.writeGeneratedContent()
+
+        then: 'the generated source preserves the runtime-discovered artefact type'
+            generatedSource(generationContext).contains(dynamicArtefact.name)
+
+        cleanup:
+            context.close()
+            classLoader.close()
+    }
+
+    @PendingFeature(exceptions = [SpockAssertionError], reason = 'Blocker: plugin doWithSpring closures are evaluated from runtime Groovy classes, with no AOT contribution that converts their bean definitions into build-time generated source.')
+    void 'Spring AOT records a dynamically loaded plugin doWithSpring bean'() {
+        given: 'a plugin class loaded at runtime with a Groovy bean-definition closure'
+            def classLoader = new GroovyClassLoader()
+            Class<?> dynamicPlugin = classLoader.parseClass('''
+                class AotDynamicGrailsPlugin {
+                    def version = '1.0'
+                    def doWithSpring = {
+                        dynamicPluginBean(Object)
+                    }
+                }
+            ''')
+            def application = new DefaultGrailsApplication([] as Class<?>[], classLoader)
+            def context = new GenericApplicationContext()
+            def runtimeContext = new GenericApplicationContext()
+            application.mainContext = context
+            def discovery = new DefaultPluginDiscovery([dynamicPlugin] as Class<?>[])
+            discovery.loadPluginsFromClasspath = false
+            discovery.init(context.environment)
+            def pluginManager = new DefaultGrailsPluginManager(application, discovery)
+            pluginManager.loadPlugins()
+            context.beanFactory.registerSingleton(PluginDiscovery.BEAN_NAME, discovery)
+            context.beanFactory.registerSingleton(GrailsPluginManager.BEAN_NAME, pluginManager)
+            def generationContext = generationContext()
+
+        when: 'the runtime plugin configuration phase registers its DSL bean before Spring processes the context through its public AOT API'
+            def springConfiguration = new DefaultRuntimeSpringConfiguration()
+            pluginManager.doRuntimeConfiguration(springConfiguration)
+            springConfiguration.registerBeansWithContext(runtimeContext)

Review Comment:
   This probe can never flip to passing, even with full Grails AOT support: `doRuntimeConfiguration` registers `dynamicPluginBean` into `runtimeContext`, but `processAheadOfTime` runs against `context`, which never sees the bean. No AOT contribution could preserve a bean definition that was registered into a *different* context.
   
   Conversely, if the plugin's bean definitions were registered into the processed `context` before `processAheadOfTime` (i.e. `springConfiguration.registerBeansWithContext(context)`), Spring AOT would likely convert them to generated source *today* — bean definitions are exactly what it processes. That would show the real gap is lifecycle (Grails runs `doWithSpring` during refresh, after the AOT snapshot), not Spring's inability to convert plugin DSL beans. Suggest registering into `context` and letting the probe characterize the actual behavior — if it passes, the pending reason needs rewording anyway.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Add Spring AOT context smoke and pending gap probes [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16001:
URL: https://github.com/apache/grails-core/pull/16001#issuecomment-5544012798

   ## 🚨 TestLens detected 1 failed test 🚨
   
   Here is what you can do:
   
   1) Inspect the test failures carefully.
   2) If you are convinced that some of the tests are flaky, you can mute them below.
   3) Finally, trigger a rerun by checking the rerun checkbox.
   
   ### Test Summary
   
   #### [CI / Build Grails-Core \(Windows JDK 25 shard 1\)](https://github.com/apache/grails-core/actions/runs/33898017844/job/101105189747?pr=16001) > :grails-core:test
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | GrailsAotSmokeSpec > Spring AOT records a dynamically discovered Grails artefact | ❌ | 0%&nbsp;🟢 |
   
   🏷️ Commit: 8ebf2c952ef0613d55ad701ce2f41bd0377c1c78
   ▶️ Tests:  2696 executed
   🟡 Checks: 11/82 completed
   
   ### Test Failures
   
   <details><summary><strong>GrailsAotSmokeSpec > Spring AOT records a dynamically discovered Grails artefact</strong> (:grails-core:test in <a href="https://github.com/apache/grails-core/actions/runs/33898017844/job/101105189747?pr=16001">CI / Build Grails-Core (Windows JDK 25 shard 1)</a>)</summary>
   
   ```
   org.spockframework.runtime.extension.builtin.PendingFeatureSuccessfulError: Feature is marked with @PendingFeature but passes unexpectedly
   	at org.spockframework.runtime.extension.builtin.PendingFeatureBaseInterceptor.featurePassedUnexpectedly(PendingFeatureBaseInterceptor.java:28)
   	at org.spockframework.runtime.extension.builtin.PendingFeatureInterceptor.intercept(PendingFeatureInterceptor.java:30)
   	at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:139)
   	at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:157)
   	at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:157)
   	at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
   	at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:157)
   	at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
   ```
   
   </details>
   
   ### Rerun Controls
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 GrailsAotSmokeSpec > Spring AOT records a dynamically discovered Grails artefact <!-- uniqueId=[engine:spock]/[spec:grails.aot.GrailsAotSmokeSpec]/[feature:$spock_feature_0_1] -->
   
   Reuse successful test results:
   
   🔲 ♻️ Only rerun the tests that failed or were muted before
   
   Click the checkbox to trigger a rerun:
   
   🔲 **Rerun jobs**
   
   ---
   _Learn more about TestLens at [testlens.app/docs](https://testlens.app/docs/features/pr-comment/)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org