Skip to content

Commit 6bdd60d

Browse files
committed
Support autodetected Jupiter extensions in native tests
1 parent fb710bb commit 6bdd60d

7 files changed

Lines changed: 147 additions & 4 deletions

File tree

common/junit-platform-native/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ tasks.named('test') {
7474
delete { delete testIdsDir.get().asFile }
7575
}
7676
useJUnitPlatform() {
77+
systemProperty('junit.jupiter.extensions.autodetection.enabled', true)
7778
systemProperty('junit.platform.listeners.uid.tracking.enabled', true)
7879
systemProperty('junit.platform.listeners.uid.tracking.output.dir', "${testIdsDir.get().asFile.absolutePath}/test_ids")
7980
}

common/junit-platform-native/gradle/native-image-testing.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider
109109
"--no-fallback",
110110
"--features=org.graalvm.junit.platform.JUnitPlatformFeature",
111111
"-o", "native-image-tests",
112+
"-Djunit.jupiter.extensions.autodetection.enabled=true",
112113
"-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}"
113114
]
114115

@@ -159,5 +160,8 @@ tasks.register("nativeTest", Exec) {
159160
dependsOn nativeTestCompile
160161
workingDir = "${buildDir}"
161162
executable = "${buildDir}/native-image-tests"
162-
args = ["-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}"]
163+
args = [
164+
"-Djunit.jupiter.extensions.autodetection.enabled=true",
165+
"-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}"
166+
]
163167
}

common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.junit.jupiter.api.condition.DisabledIf;
5252
import org.junit.jupiter.api.condition.EnabledIf;
5353
import org.junit.jupiter.api.extension.ExtendWith;
54+
import org.junit.jupiter.api.extension.Extension;
5455
import org.junit.jupiter.params.aggregator.AggregateWith;
5556
import org.junit.jupiter.params.converter.ConvertWith;
5657
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -61,17 +62,20 @@
6162
import java.lang.reflect.Method;
6263
import java.util.ArrayList;
6364
import java.util.List;
65+
import java.util.ServiceLoader;
6466

6567
import static org.graalvm.junit.platform.JUnitPlatformFeatureUtils.debug;
6668

6769
public class JupiterConfigProvider extends PluginConfigProvider {
70+
private static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled";
6871

6972
@Override
7073
public void onLoad() {
7174
/* Provide support for Timeout annotation */
7275
JUnitPlatformFeatureUtils.registerAllClassMembersForReflection(Utils.toClasses(
7376
"org.junit.jupiter.engine.extension.TimeoutExtension$ExecutorResource",
7477
"org.junit.jupiter.engine.extension.TimeoutInvocationFactory$SingleThreadExecutorResource"));
78+
registerAutoDetectedExtensionsForReflection();
7579
}
7680

7781
@Override
@@ -155,6 +159,23 @@ private static Class<?>[] handleMethodReference(String... methodNames) {
155159
return classList.toArray(new Class<?>[0]);
156160
}
157161

162+
private void registerAutoDetectedExtensionsForReflection() {
163+
try {
164+
if (Boolean.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME)) {
165+
ServiceLoader.load(Extension.class, applicationClassLoader)
166+
.stream()
167+
.map(ServiceLoader.Provider::type)
168+
.forEach(extensionType -> {
169+
// Service-registered Jupiter extensions need runtime reflection metadata in native tests. §root/FS-native-tests.2.
170+
debug("Registering auto-detected Jupiter extension for reflection: %s", extensionType.getName());
171+
JUnitPlatformFeatureUtils.registerAllClassMembersForReflection(extensionType);
172+
});
173+
}
174+
} catch (NoClassDefFoundError e) {
175+
debug("Cannot register auto-detected Jupiter extensions. Please verify that you have dependency that includes 'org.junit.jupiter.api' if you want to use these extensions.");
176+
}
177+
}
178+
158179
public static void handleEnumSource(Method method, EnumSource source) {
159180
JUnitPlatformFeatureUtils.registerAllClassMembersForReflection(source.value());
160181
if (method.getParameterCount() > 0) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
42+
package org.graalvm.junit.jupiter;
43+
44+
import org.junit.jupiter.api.extension.BeforeEachCallback;
45+
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
46+
import org.junit.jupiter.api.extension.ExtensionContext;
47+
48+
public class AutodetectedExtension implements BeforeEachCallback, BeforeTestExecutionCallback {
49+
50+
@Override
51+
public void beforeEach(ExtensionContext context) {
52+
System.clearProperty(AutodetectedExtensionTests.EXTENSION_PROPERTY);
53+
}
54+
55+
@Override
56+
public void beforeTestExecution(ExtensionContext context) {
57+
System.setProperty(AutodetectedExtensionTests.EXTENSION_PROPERTY, "true");
58+
}
59+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
42+
package org.graalvm.junit.jupiter;
43+
44+
import org.junit.jupiter.api.Assertions;
45+
import org.junit.jupiter.api.Test;
46+
47+
class AutodetectedExtensionTests {
48+
static final String EXTENSION_PROPERTY = "org.graalvm.junit.jupiter.AutodetectedExtension.invoked";
49+
50+
@Test
51+
void appliesServiceRegisteredExtension() {
52+
// Verifies native-test support for Jupiter service-registered extensions. §root/FS-native-tests.2.
53+
Assertions.assertEquals("true", System.getProperty(EXTENSION_PROPERTY));
54+
}
55+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.graalvm.junit.jupiter.AutodetectedExtension

docs/spec/functional/native-tests.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ following must happen during image build:
5656
- Provider classes for JUnit Platform, Jupiter, and Vintage must contribute their Native Image
5757
metadata when the corresponding engine is on the test classpath. New providers may be added
5858
when the repository supports new JUnit Platform behavior.
59+
- When Jupiter extension autodetection is enabled, service-registered
60+
`org.junit.jupiter.api.extension.Extension` providers must be available to the native test image.
5961

6062
The repository's native-test fixtures cover nested tests, method sources, CSV sources, enum
61-
sources, converters, aggregators, class ordering, and display-name generation. The shared launcher
62-
and feature must preserve JUnit Platform semantics for any scenario the repository's fixtures
63-
exercise.
63+
sources, converters, aggregators, class ordering, display-name generation, and service-registered
64+
Jupiter extensions. The shared launcher and feature must preserve JUnit Platform semantics for
65+
any scenario the repository's fixtures exercise.
6466

6567
## 3. Native launcher and feature
6668

0 commit comments

Comments
 (0)