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

paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 6fde012454 GROOVY-11997: Add @ForkedJvm and @ExpectedToFail JUnit 
extensions to groovy-test-junit6 (additional tests converted)
6fde012454 is described below

commit 6fde0124541cf9c6ca2b659635b4bf226cf8261c
Author: Paul King <[email protected]>
AuthorDate: Wed May 6 14:34:05 2026 +1000

    GROOVY-11997: Add @ForkedJvm and @ExpectedToFail JUnit extensions to 
groovy-test-junit6 (additional tests converted)
---
 .github/workflows/groovy-build-test.yml            |  5 +++-
 .../main/groovy/org.apache.groovy-tested.gradle    | 16 +++++++++++
 src/test/groovy/bugs/Groovy11046.groovy            |  8 ++++--
 .../groovy/groovy/grape/GrabResolverTest.groovy    | 29 ++++++-------------
 .../groovy/lang/GroovyClassLoaderTest.groovy       | 27 +++++++++---------
 .../groovy/control/CompilerConfigurationTest.java  | 33 ++++++++--------------
 .../traitx/TraitASTTransformationTest.groovy       |  3 +-
 subprojects/groovy-grape-ivy/build.gradle          |  1 +
 .../groovy/groovy/grape/ivy/GrapeIvyTest.groovy    |  3 ++
 subprojects/groovy-grape-maven/build.gradle        |  5 ++++
 subprojects/groovy-grape-test/build.gradle         |  4 +--
 .../grape/GrapeConfiguredMavenSelectionTest.groovy |  3 +-
 .../grape/GrapeImplementationSelectionTest.groovy  |  7 +++--
 subprojects/groovy-jmx/build.gradle                |  1 +
 .../builder/JmxClientConnectorFactoryTest.groovy   |  9 ++++++
 .../main/java/groovy/junit6/plugin/ForkedJvm.java  | 17 +++++++++++
 versions.properties                                |  1 +
 17 files changed, 106 insertions(+), 66 deletions(-)

diff --git a/.github/workflows/groovy-build-test.yml 
b/.github/workflows/groovy-build-test.yml
index ebca716378..06b695822f 100644
--- a/.github/workflows/groovy-build-test.yml
+++ b/.github/workflows/groovy-build-test.yml
@@ -36,6 +36,9 @@ jobs:
             env-no-color: '1'
           - java: 17
             os: macos-latest
+          - java: 25
+            os: ubuntu-latest
+            junit-network: '-Djunit.network=true'
     runs-on: ${{ matrix.os }}
     env:
       NO_COLOR: ${{ matrix.env-no-color }}
@@ -55,7 +58,7 @@ jobs:
       - name: "๐Ÿ” Setup TestLens"
         uses: testlens-app/setup-testlens@v1
       - name: "๐ŸƒTest with Gradle"
-        run: ./gradlew test -Ptarget.java.home="$JAVA_HOME_${{ matrix.java 
}}_${{ runner.arch }}"
+        run: ./gradlew test ${{ matrix.junit-network }} 
-Ptarget.java.home="$JAVA_HOME_${{ matrix.java }}_${{ runner.arch }}"
         shell: bash
         timeout-minutes: 60
       - name: "๐Ÿš€Upload reports"
diff --git a/build-logic/src/main/groovy/org.apache.groovy-tested.gradle 
b/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
index 14e566f7dc..b0b76b9660 100644
--- a/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
+++ b/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
@@ -177,6 +177,22 @@ Closure buildExcludeFilter(boolean legacyTestSuite) {
     }
 }
 
+// Warn (rather than silently NO-SOURCE) when the groovy/grape exclusion
+// swallows the entirety of a project's test source set. A plain
+// `:groovy-grape-test:test` invocation otherwise reports BUILD SUCCESSFUL
+// with zero tests executed โ€” easy to mistake for a passing run.
+afterEvaluate {
+    if (!providers.systemProperty('junit.network').getOrNull()) {
+        boolean hasGrapeTests = sourceSets.test.allSource.srcDirs.any {
+            new File(it, 'groovy/grape').isDirectory()
+        }
+        if (hasGrapeTests) {
+            String testPath = (project.path == ':' ? '' : project.path) + 
':test'
+            logger.warn("WARNING: ${testPath} will skip groovy/grape/* tests; 
set -Djunit.network=true to include them")
+        }
+    }
+}
+
 interface TestServices {
     @javax.inject.Inject
     FileSystemOperations getFileSystemOperations()
diff --git a/src/test/groovy/bugs/Groovy11046.groovy 
b/src/test/groovy/bugs/Groovy11046.groovy
index b960f6b2d4..e11c30f76e 100644
--- a/src/test/groovy/bugs/Groovy11046.groovy
+++ b/src/test/groovy/bugs/Groovy11046.groovy
@@ -18,6 +18,7 @@
  */
 package bugs
 
+import groovy.junit6.plugin.ForkedJvm
 import org.junit.jupiter.api.Test
 
 import static groovy.test.GroovyAssert.assertScript
@@ -35,12 +36,15 @@ final class Groovy11046 {
     }
 
     @Test
+    @ForkedJvm(systemProperties = [
+            
'Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector'])
     void testMissingDependency2() {
+        // Log4jContextSelector is read by log4j at class-load time, so it must
+        // be set on the JVM command line โ€” we run in a forked child to keep
+        // the rest of the suite from inheriting log4j routing changes.
         def err = shouldFail '''
             @Grab('org.apache.logging.log4j:log4j-core:2.22.0')
             org.apache.logging.log4j.core.async.AsyncLogger log
-            System.setProperty('Log4jContextSelector',
-                
'org.apache.logging.log4j.core.async.AsyncLoggerContextSelector')
             log = org.apache.logging.log4j.LogManager.getLogger(getClass()) 
//XXX
         '''
         assert err instanceof NoClassDefFoundError // 
CompilationFailedException (previously)
diff --git a/src/test/groovy/groovy/grape/GrabResolverTest.groovy 
b/src/test/groovy/groovy/grape/GrabResolverTest.groovy
index b36e39e779..3cd99bcc8c 100644
--- a/src/test/groovy/groovy/grape/GrabResolverTest.groovy
+++ b/src/test/groovy/groovy/grape/GrabResolverTest.groovy
@@ -18,11 +18,10 @@
  */
 package groovy.grape
 
+import groovy.junit6.plugin.ForkedJvm
 import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilationFailedException
-import org.junit.jupiter.api.AfterAll
-import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 
@@ -30,17 +29,18 @@ import static groovy.test.GroovyAssert.assertScript
 import static groovy.test.GroovyAssert.shouldFail
 
 @CompileStatic
+@ForkedJvm(inheritProperties = ['grape.root', 'user.home', 'gradle.home'])
 final class GrabResolverTest {
 
-    private static String originalGrapeRoot
-
-    @BeforeAll
-    static void setUpTestSuite() {
-        originalGrapeRoot = System.getProperty('grape.root')
-    }
-
     @BeforeEach @CompileDynamic
     void setUp() {
+        // Lifecycle hooks fire in BOTH the parent test JVM and each forked
+        // child; the parent never runs the test bodies, so its Grape state
+        // and grape.root must stay untouched. The previous BeforeAll/AfterAll
+        // save/restore dance becomes redundant once nothing in the parent
+        // mutates grape.root.
+        if (!Boolean.parseBoolean(System.getProperty('groovy.junit6.forked'))) 
return
+
         Grape.@instance = null // isolate each test
 
         // create a new grape root directory for each test for isolation
@@ -62,17 +62,6 @@ final class GrabResolverTest {
         }
     }
 
-    @AfterAll
-    static void tearDownTestSuite() {
-        if (originalGrapeRoot == null) {
-            System.clearProperty('grape.root')
-        } else {
-            System.setProperty('grape.root', originalGrapeRoot)
-        }
-
-        Grape.@instance = null // isolate tests
-    }
-
     @Test
     void testResolverDefinitionIsRequired() {
         shouldFail CompilationFailedException, '''
diff --git a/src/test/groovy/groovy/lang/GroovyClassLoaderTest.groovy 
b/src/test/groovy/groovy/lang/GroovyClassLoaderTest.groovy
index af43c56d0a..d4deff0901 100644
--- a/src/test/groovy/groovy/lang/GroovyClassLoaderTest.groovy
+++ b/src/test/groovy/groovy/lang/GroovyClassLoaderTest.groovy
@@ -18,6 +18,7 @@
  */
 package groovy.lang
 
+import groovy.junit6.plugin.ForkedJvm
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.ast.CompileUnit
@@ -207,21 +208,19 @@ final class GroovyClassLoaderTest {
     }
 
     @Test
+    @ForkedJvm(systemProperties = ['file.encoding=US-ASCII'])
     void testSourceEncoding() {
-        String oldEncoding = System.getProperty('file.encoding')
-        System.setProperty('file.encoding', 'US-ASCII')
-        try {
-            def gcl = new GroovyClassLoader(this.class.classLoader, new 
CompilerConfiguration().tap{sourceEncoding = 'UTF-8'})
-            def clazz = gcl.parseClass('return "\u20AC"') // EURO currency 
symbol
-            def result = clazz.getDeclaredConstructor().newInstance().run()
-            int i = result[0]
-            // 0xFFFD is used if the original character was not found,
-            // it is the famous ? that can often be seen. So if this here
-            // fails, then the String conversion failed at one point
-            assert i != 0xFFFD
-        } finally {
-            System.setProperty('file.encoding', oldEncoding)
-        }
+        // file.encoding is JVM-global (and consulted by JDK internals at
+        // class-load time on some platforms), so we set it via the forked
+        // command line rather than mutating the running JVM.
+        def gcl = new GroovyClassLoader(this.class.classLoader, new 
CompilerConfiguration().tap{sourceEncoding = 'UTF-8'})
+        def clazz = gcl.parseClass('return "\u20AC"') // EURO currency symbol
+        def result = clazz.getDeclaredConstructor().newInstance().run()
+        int i = result[0]
+        // 0xFFFD is used if the original character was not found,
+        // it is the famous ? that can often be seen. So if this here
+        // fails, then the String conversion failed at one point
+        assert i != 0xFFFD
     }
 
     // GROOVY-3537
diff --git 
a/src/test/groovy/org/codehaus/groovy/control/CompilerConfigurationTest.java 
b/src/test/groovy/org/codehaus/groovy/control/CompilerConfigurationTest.java
index 5f8c79d42f..44d93e17df 100644
--- a/src/test/groovy/org/codehaus/groovy/control/CompilerConfigurationTest.java
+++ b/src/test/groovy/org/codehaus/groovy/control/CompilerConfigurationTest.java
@@ -18,16 +18,14 @@
  */
 package org.codehaus.groovy.control;
 
+import groovy.junit6.plugin.ForkedJvm;
 import org.codehaus.groovy.control.customizers.ImportCustomizer;
 import org.codehaus.groovy.control.messages.WarningMessage;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import java.io.File;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Properties;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -42,19 +40,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 public final class CompilerConfigurationTest {
 
-    private Properties savedProperties;
-
-    @BeforeEach
-    public void setUp() {
-        savedProperties = System.getProperties();
-        System.setProperties(new Properties(savedProperties));
-    }
-
-    @AfterEach
-    public void tearDown() {
-        System.setProperties(savedProperties);
-    }
-
     @Test
     public void testDefaultConstructor() {
         CompilerConfiguration config = CompilerConfiguration.DEFAULT;
@@ -76,13 +61,17 @@ public final class CompilerConfigurationTest {
     }
 
     @Test
+    @ForkedJvm(systemProperties = {
+            "groovy.warnings=PaRaNoiA",
+            "groovy.output.verbose=trUE",
+            "groovy.mem.stub=true",
+            "groovy.generate.stub.in.memory=true",
+            "groovy.recompile.minimumInterval=867892345"})
     public void testSetViaSystemProperties() {
-        System.setProperty("groovy.warnings", "PaRaNoiA");
-        System.setProperty("groovy.output.verbose", "trUE");
-        System.setProperty("groovy.mem.stub", "true");
-        System.setProperty("groovy.generate.stub.in.memory", "true");
-        System.setProperty("groovy.recompile.minimumInterval", "867892345");
-
+        // Properties are set on the JVM command line via @ForkedJvm; the
+        // forked child reads them through the normal System.getProperties()
+        // path the production code uses, and parent-JVM state stays clean
+        // for all the other (read-only) tests in this class.
         assertEquals("PaRaNoiA", System.getProperty("groovy.warnings"));
 
         CompilerConfiguration config = new 
CompilerConfiguration(System.getProperties());
diff --git 
a/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
 
b/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
index bcb96bb858..11706c5b2b 100644
--- 
a/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
+++ 
b/src/test/groovy/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -18,6 +18,7 @@
  */
 package org.codehaus.groovy.transform.traitx
 
+import groovy.junit6.plugin.ForkedJvm
 import groovy.transform.AnnotationCollector
 import groovy.transform.SelfType
 import org.codehaus.groovy.ast.ClassHelper
@@ -3376,6 +3377,7 @@ final class TraitASTTransformationTest {
 
     // GROOVY-10521
     @Test
+    @ForkedJvm(systemProperties = 
['spock.iKnowWhatImDoing.disableGroovyVersionCheck=true'])
     void testVariadicMethodOfPrecompiledTrait() {
         assertScript shell, """import org.codehaus.groovy.ast.*
             class CT implements ${T10521.name} {
@@ -3390,7 +3392,6 @@ final class TraitASTTransformationTest {
             assert td == 
'm(java.lang.Class,[java.lang.Object):java.lang.Object'
         """
 
-        
System.setProperty('spock.iKnowWhatImDoing.disableGroovyVersionCheck','true')
         assertScript shell, """
             @Grab('org.spockframework:spock-core:2.4-groovy-5.0')
             @GrabExclude('org.codehaus.groovy:groovy-all')
diff --git a/subprojects/groovy-grape-ivy/build.gradle 
b/subprojects/groovy-grape-ivy/build.gradle
index 94d72fac73..c1d2cff566 100644
--- a/subprojects/groovy-grape-ivy/build.gradle
+++ b/subprojects/groovy-grape-ivy/build.gradle
@@ -26,6 +26,7 @@ dependencies {
         transitive = false
     }
     testImplementation projects.groovyTest
+    testImplementation projects.groovyTestJunit6
 }
 
 // The only Java source is a package-private implementation class;
diff --git 
a/subprojects/groovy-grape-ivy/src/test/groovy/groovy/grape/ivy/GrapeIvyTest.groovy
 
b/subprojects/groovy-grape-ivy/src/test/groovy/groovy/grape/ivy/GrapeIvyTest.groovy
index 2490cb33b9..ffde771076 100644
--- 
a/subprojects/groovy-grape-ivy/src/test/groovy/groovy/grape/ivy/GrapeIvyTest.groovy
+++ 
b/subprojects/groovy-grape-ivy/src/test/groovy/groovy/grape/ivy/GrapeIvyTest.groovy
@@ -19,6 +19,7 @@
 package groovy.grape.ivy
 
 import groovy.grape.Grape
+import groovy.junit6.plugin.ForkedJvm
 
 import org.codehaus.groovy.control.CompilationFailedException
 import org.junit.jupiter.api.BeforeAll
@@ -264,6 +265,7 @@ final class GrapeIvyTest {
     }
 
     @Test // GROOVY-8372
+    @ForkedJvm(inheritProperties = ['grape.root', 'user.home', 'gradle.home'])
     void testConf2() {
         assumeTrue(Grape.instance instanceof GrapeIvy) // only GrapeIvy uses 
<ivysettings>
         def tempDir = File.createTempDir()
@@ -496,6 +498,7 @@ final class GrapeIvyTest {
     }
 
     @Test // GROOVY-7548
+    @ForkedJvm(inheritProperties = ['grape.root', 'user.home', 'gradle.home'])
     void testSystemProperties() {
         System.setProperty('groovy7548prop', 'x')
         assert System.getProperty('groovy7548prop') == 'x'
diff --git a/subprojects/groovy-grape-maven/build.gradle 
b/subprojects/groovy-grape-maven/build.gradle
index e14e60dffd..f2a3277b25 100644
--- a/subprojects/groovy-grape-maven/build.gradle
+++ b/subprojects/groovy-grape-maven/build.gradle
@@ -24,6 +24,11 @@ dependencies {
     api rootProject
     implementation 
"org.apache.maven:maven-resolver-provider:${versions.mavenResolverProvider}"
     implementation 
"org.apache.maven.resolver:maven-resolver-supplier-mvn4:${versions.mavenResolverSupplier}"
+    // Maven Resolver's supplier instantiates components annotated with 
javax.inject (JSR-330);
+    // without javax.inject on the classpath, RepositorySystemSupplier.get() 
fails with
+    // NoClassDefFoundError: javax/inject/Provider. The supplier's POM doesn't 
declare this
+    // transitively as of 2.0.17, so we provide it explicitly.
+    implementation "javax.inject:javax.inject:${versions.javaxInject}"
 }
 
 tasks.withType(Javadoc).configureEach {
diff --git a/subprojects/groovy-grape-test/build.gradle 
b/subprojects/groovy-grape-test/build.gradle
index 25df03f911..eca19ee3a7 100644
--- a/subprojects/groovy-grape-test/build.gradle
+++ b/subprojects/groovy-grape-test/build.gradle
@@ -26,7 +26,7 @@ plugins {
 dependencies {
     testImplementation rootProject
     testImplementation projects.groovyTest
-    testImplementation projects.groovyTestJunit5
+    testImplementation projects.groovyTestJunit6
     testRuntimeOnly projects.groovyGrapeIvy
     testRuntimeOnly projects.groovyGrapeMaven
     testRuntimeOnly "org.slf4j:slf4j-simple:${versions.slf4j}"
@@ -34,7 +34,5 @@ dependencies {
 
 tasks.named('test', Test) {
     useJUnitPlatform()
-    forkEvery = 1
-    maxParallelForks = 1
 }
 
diff --git 
a/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeConfiguredMavenSelectionTest.groovy
 
b/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeConfiguredMavenSelectionTest.groovy
index 281a038566..7a870e2076 100644
--- 
a/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeConfiguredMavenSelectionTest.groovy
+++ 
b/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeConfiguredMavenSelectionTest.groovy
@@ -18,12 +18,13 @@
  */
 package groovy.grape
 
+import groovy.junit6.plugin.ForkedJvm
 import org.junit.jupiter.api.Test
 
 final class GrapeConfiguredMavenSelectionTest {
     @Test
+    @ForkedJvm(systemProperties = 
['groovy.grape.impl=groovy.grape.maven.GrapeMaven'])
     void testConfiguredMavenImplementationIgnoresIvyProvider() {
-        System.setProperty('groovy.grape.impl', 
'groovy.grape.maven.GrapeMaven')
         String output = GrapeSelectionTestSupport.captureStderr {
             assert Grape.instance != null
         }
diff --git 
a/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeImplementationSelectionTest.groovy
 
b/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeImplementationSelectionTest.groovy
index 68f74078a1..cf2303fc33 100644
--- 
a/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeImplementationSelectionTest.groovy
+++ 
b/subprojects/groovy-grape-test/src/test/groovy/groovy/grape/GrapeImplementationSelectionTest.groovy
@@ -18,14 +18,17 @@
  */
 package groovy.grape
 
+import groovy.junit6.plugin.ForkedJvm
 import org.junit.jupiter.api.Test
 
 final class GrapeImplementationSelectionTest {
     @Test
+    @ForkedJvm(systemProperties = 
['groovy.grape.impl=groovy.grape.nonexistent.DoesNotExist'])
     void testConfiguredImplementationMismatchSkipsServiceLoad() {
         // This module runs with both implementations available; selecting an 
implementation
-        // that is not on this test runtime classpath should disable Grapes.
-        System.setProperty('groovy.grape.impl', 
'groovy.grape.nonexistent.DoesNotExist')
+        // that is not on this test runtime classpath should disable Grapes. 
The property is
+        // set on the JVM command line so it precedes any class load of 
groovy.grape.Grape,
+        // which caches its chosen instance after the first lookup.
         String output = GrapeSelectionTestSupport.captureStderr {
             assert Grape.instance == null
         }
diff --git a/subprojects/groovy-jmx/build.gradle 
b/subprojects/groovy-jmx/build.gradle
index 35feabec6c..fa46dd2487 100644
--- a/subprojects/groovy-jmx/build.gradle
+++ b/subprojects/groovy-jmx/build.gradle
@@ -23,6 +23,7 @@ plugins {
 dependencies {
     api rootProject // JmxBuilder extends FactoryBuilderSupport...
     testImplementation projects.groovyTest
+    testImplementation projects.groovyTestJunit6
     testRuntimeOnly(project(':')) {
         because 'Tests are using Grapes'
         capabilities {
diff --git 
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
 
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
index 2e25a9e361..28cd12b0c5 100644
--- 
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
+++ 
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
@@ -18,6 +18,7 @@
  */
 package groovy.jmx.builder
 
+import groovy.junit6.plugin.ForkedJvm
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.Assumptions
 import org.junit.jupiter.api.BeforeEach
@@ -28,6 +29,7 @@ import javax.management.remote.rmi.RMIConnectorServer
 
 import static groovy.test.GroovyAssert.shouldFail
 
+@ForkedJvm
 final class JmxClientConnectorFactoryTest {
 
     private JmxBuilder builder
@@ -35,6 +37,12 @@ final class JmxClientConnectorFactoryTest {
 
     @BeforeEach
     void setUp() {
+        // ForkedJvm only intercepts @Test invocations; @BeforeEach still
+        // fires in the parent JVM (where the test body itself never runs).
+        // Skip the side-effecting setup there, otherwise the parent grabs
+        // port 10990 first and the child's createRmiRegistry collides.
+        if (!Boolean.parseBoolean(System.getProperty('groovy.junit6.forked'))) 
return
+
         System.setProperty('java.rmi.server.hostname', hostAddress)
 
         final int defaultPort = 10990
@@ -50,6 +58,7 @@ final class JmxClientConnectorFactoryTest {
 
     @AfterEach
     void tearDown() {
+        if (!Boolean.parseBoolean(System.getProperty('groovy.junit6.forked'))) 
return
         System.clearProperty('java.rmi.server.hostname')
         JmxConnectorHelper.destroyRmiRegistry(rmi.registry)
     }
diff --git 
a/subprojects/groovy-test-junit6/src/main/java/groovy/junit6/plugin/ForkedJvm.java
 
b/subprojects/groovy-test-junit6/src/main/java/groovy/junit6/plugin/ForkedJvm.java
index dda239df1a..3ba396d40f 100644
--- 
a/subprojects/groovy-test-junit6/src/main/java/groovy/junit6/plugin/ForkedJvm.java
+++ 
b/subprojects/groovy-test-junit6/src/main/java/groovy/junit6/plugin/ForkedJvm.java
@@ -53,6 +53,23 @@ import java.lang.annotation.Target;
  * void withModuleAccess() { ... }
  * </pre>
  * <p>
+ * <b>Recipe โ€” {@code @GrabConfig(systemClassLoader=true)} tests:</b>
+ * scripts that use {@code @GrabConfig(systemClassLoader=true)} to add
+ * resolved JARs to the JVM <em>system</em> classloader only behave
+ * correctly when that classloader is Groovy's {@code RootLoader}, which
+ * cannot be retrofitted after JVM startup. Set it via {@code jvmArgs}:
+ * <pre>
+ * &#64;Test
+ * &#64;ForkedJvm(jvmArgs = 
{"-Djava.system.class.loader=org.codehaus.groovy.tools.RootLoader"})
+ * void scriptUsesGrabConfigSystemClassLoader() {
+ *     assertScript '''
+ *         &#64;GrabConfig(systemClassLoader=true)
+ *         &#64;Grab('org.example:some-lib:1.0')
+ *         // ... library is now visible to anything resolving via the system 
classloader ...
+ *     '''
+ * }
+ * </pre>
+ * <p>
  * Properties from the parent JVM can be propagated to the child via
  * {@link #inheritProperties()}. Each entry is either an exact property
  * name or a prefix pattern ending in {@code *}. This is useful when the
diff --git a/versions.properties b/versions.properties
index 787f345ee3..fb5a4a7d82 100644
--- a/versions.properties
+++ b/versions.properties
@@ -50,6 +50,7 @@ junit6=6.0.3
 latest3=3.0.25
 latest4=4.0.31
 latest5=5.0.5
+javaxInject=1
 log4j=1.2.17
 log4j2=2.25.4
 logback=1.5.32

Reply via email to