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>
+ * @Test
+ * @ForkedJvm(jvmArgs =
{"-Djava.system.class.loader=org.codehaus.groovy.tools.RootLoader"})
+ * void scriptUsesGrabConfigSystemClassLoader() {
+ * assertScript '''
+ * @GrabConfig(systemClassLoader=true)
+ * @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