Skip to content

Commit f999bc4

Browse files
committed
Run tests in the compatibility mode
1 parent 10a82d4 commit f999bc4

5 files changed

Lines changed: 79 additions & 64 deletions

File tree

native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/CompatibilityModeNativeTestsFunctionalTest.groovy

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class CompatibilityModeNativeTestsFunctionalTest extends AbstractFunctionalTest
5959
tasks {
6060
succeeded ':testClasses', ':nativeTestCompile', ':nativeTest'
6161
}
62-
outputDoesNotContain "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test build/run"
62+
outputDoesNotContain "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test run"
6363
}
6464

6565
@Unroll
@@ -81,10 +81,10 @@ class CompatibilityModeNativeTestsFunctionalTest extends AbstractFunctionalTest
8181

8282
then:
8383
tasks {
84-
succeeded ':testClasses', ':test'
85-
skipped ':nativeTestCompile', ':nativeTest'
84+
succeeded ':testClasses', ':test', ':nativeTestCompile'
85+
skipped ':nativeTest'
8686
}
87-
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test build/run, JVM tests will run instead."
87+
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test run, JVM tests will run instead."
8888
}
8989

9090
def "ON via NATIVE_IMAGE_OPTIONS env map: native test image build/run are skipped and message logged"() {
@@ -105,9 +105,9 @@ class CompatibilityModeNativeTestsFunctionalTest extends AbstractFunctionalTest
105105

106106
then:
107107
tasks {
108-
succeeded ':testClasses', ':test'
109-
skipped ':nativeTestCompile', ':nativeTest'
108+
succeeded ':testClasses', ':test', ':nativeTestCompile'
109+
skipped ':nativeTest'
110110
}
111-
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test build/run, JVM tests will run instead."
111+
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test run, JVM tests will run instead."
112112
}
113113
}

native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -691,16 +691,15 @@ public void registerTestBinary(Project project,
691691
// Compute and expose the Compatibility Mode detection provider for test binary
692692
this.testCompatibilityModeEnabled = computeCompatibilityModeEnabledProvider(project, testOptions);
693693

694-
// Gate JUnit-specific wiring of the main class for the test binary based on Compatibility Mode.
695-
// If Compatibility Mode is enabled, do not wire the native JUnit launcher main class.
694+
// Gate JUnit-specific wiring reference for later task skipping decisions.
696695
Provider<Boolean> isCompat = this.testCompatibilityModeEnabled;
697696

698-
// Conditionally set the JUnit native launcher as main class when NOT in compatibility mode.
699-
testOptions.getMainClass().convention(isCompat.map(enabled ->
700-
Boolean.TRUE.equals(enabled) ? null : "org.graalvm.junit.platform.NativeImageJUnitLauncher"
701-
));
697+
// Wire main class based on Compatibility Mode, mirroring Maven plugin behavior.
698+
testOptions.getMainClass().convention(isCompat.map(c -> c
699+
? "org.junit.platform.console.ConsoleLauncher"
700+
: "org.graalvm.junit.platform.NativeImageJUnitLauncher"));
702701

703-
// Add the JUnit Platform Feature flag only when Compatibility Mode is NOT enabled.
702+
// Add the JUnit Platform Feature flag and exclude JUnit class init files only when NOT in Compatibility Mode.
704703
final String junitPlatformFeatureFlag = "--features=org.graalvm.junit.platform.JUnitPlatformFeature";
705704
project.afterEvaluate(p -> {
706705
boolean compat = isCompat.getOrElse(false);
@@ -709,10 +708,27 @@ public void registerTestBinary(Project project,
709708
if (!current.contains(junitPlatformFeatureFlag)) {
710709
testOptions.getBuildArgs().add(junitPlatformFeatureFlag);
711710
}
711+
/* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */
712+
testOptions.getBuildArgs().addAll(JUnitUtils.excludeJUnitClassInitializationFiles());
712713
}
713-
// Note: we intentionally do NOT remove the feature flag when compatibility mode is enabled,
714-
// in order to not override explicit user configuration. We only avoid wiring it by default.
715714
});
715+
// Add XML output dir only in regular mode (not in Compatibility Mode)
716+
Provider<String> xmlOutputDir = project.getLayout().getBuildDirectory()
717+
.dir("test-results/" + name + "-native")
718+
.map(d -> d.getAsFile().getAbsolutePath());
719+
testOptions.getRuntimeArgs().addAll(
720+
isCompat.zip(xmlOutputDir, serializableBiFunctionOf((compat, dir) ->
721+
compat ? Collections.<String>emptyList() : Arrays.asList("--xml-output-dir", dir)
722+
))
723+
);
724+
// In Compatibility Mode, pass classpath and scan directive to the JUnit ConsoleLauncher to avoid
725+
// "Please specify an explicit selector option or use --scan-class-path or --scan-modules"
726+
Provider<String> cpString = project.getProviders().provider(() -> testOptions.getClasspath().getAsPath());
727+
testOptions.getRuntimeArgs().addAll(
728+
isCompat.zip(cpString, serializableBiFunctionOf((compat, cp) ->
729+
compat ? Arrays.asList("-cp", cp, "--scan-classpath") : Collections.<String>emptyList()
730+
))
731+
);
716732

717733
TaskProvider<Test> testTask = config.validate().getTestTask();
718734
testTask.configure(test -> {
@@ -735,12 +751,7 @@ public void registerTestBinary(Project project,
735751
task.setOnlyIf(t -> {
736752
boolean support = graalExtension.getTestSupport().get();
737753
boolean hasList = testListDirectory.getAsFile().get().exists();
738-
boolean compat = isCompat.getOrElse(false);
739-
boolean enabled = support && hasList && !compat;
740-
if (!enabled && compat) {
741-
logger.logOnce("Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test build/run, JVM tests will run instead. [binary=" + name + "]");
742-
}
743-
return enabled;
754+
return support && hasList;
744755
});
745756
task.getTestListDirectory().set(testListDirectory);
746757
testTask.get();
@@ -767,10 +778,9 @@ public void registerTestBinary(Project project,
767778
task.setOnlyIf(t -> {
768779
boolean support = graalExtension.getTestSupport().get();
769780
boolean hasList = testListDirectory.getAsFile().get().exists();
770-
boolean compat = isCompat.getOrElse(false);
771-
boolean enabled = support && hasList && !compat;
772-
if (!enabled && compat) {
773-
logger.logOnce("Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test build/run, JVM tests will run instead. [binary=" + name + "]");
781+
boolean enabled = support && hasList;
782+
if (isCompat.getOrElse(false)) {
783+
logger.logOnce("Compatibility Mode detected (-H:+CompatibilityMode); The native test image will be built using the original JUnit ConsoleLauncher.");
774784
}
775785
return enabled;
776786
});
@@ -882,19 +892,13 @@ private NativeImageOptions createTestOptions(GraalVMExtension graalExtension,
882892
// mainClass is conditionally wired in registerTestBinary based on Compatibility Mode
883893
testExtension.getImageName().convention(mainExtension.getImageName().map(name -> name + SharedConstants.NATIVE_TESTS_SUFFIX));
884894

885-
ListProperty<String> runtimeArgs = testExtension.getRuntimeArgs();
886-
runtimeArgs.add("--xml-output-dir");
887-
runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/" + binaryName + "-native").map(d -> d.getAsFile().getAbsolutePath()));
888895

889896
// Classpath setup remains unchanged
890897
ConfigurableFileCollection classpath = testExtension.getClasspath();
891898
classpath.from(configurations.getByName(imageClasspathConfigurationNameFor(binaryName)));
892899
classpath.from(sourceSet.getOutput().getClassesDirs());
893900
classpath.from(sourceSet.getOutput().getResourcesDir());
894901

895-
/* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */
896-
testExtension.getBuildArgs().addAll(JUnitUtils.excludeJUnitClassInitializationFiles());
897-
898902
return testExtension;
899903
}
900904

native-gradle-plugin/src/test/groovy/org/graalvm/buildtools/gradle/CompatibilityModeGatingTest.groovy

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ import spock.lang.Specification
5252
@Requires({ JavaVersion.current().isCompatibleWith(JavaVersion.toVersion(25)) })
5353
class CompatibilityModeGatingTest extends Specification {
5454

55-
private static final String LAUNCHER_CLASS = "org.graalvm.junit.platform.NativeImageJUnitLauncher"
55+
private static final String NATIVE_LAUNCHER = "org.graalvm.junit.platform.NativeImageJUnitLauncher"
56+
private static final String JVM_LAUNCHER = "org.junit.platform.console.ConsoleLauncher"
5657

5758
private Project project
5859
private GraalVMExtension graal
@@ -66,15 +67,15 @@ class CompatibilityModeGatingTest extends Specification {
6667
assert graal != null
6768
}
6869

69-
def "default (Compatibility Mode OFF) wires JUnit launcher main class"() {
70+
def "default (Compatibility Mode OFF) wires JUnit native launcher main class"() {
7071
when:
7172
NativeImageOptions testOptions = graal.binaries.getByName("test")
7273

7374
then:
74-
testOptions.getMainClass().getOrNull() == LAUNCHER_CLASS
75+
testOptions.getMainClass().getOrNull() == NATIVE_LAUNCHER
7576
}
7677

77-
def "Compatibility Mode via buildArgs disables JUnit launcher wiring"() {
78+
def "Compatibility Mode via buildArgs wires JVM ConsoleLauncher main class"() {
7879
given:
7980
NativeImageOptions testOptions = graal.binaries.getByName("test")
8081
// Toggle compatibility mode via explicit build arg
@@ -84,10 +85,10 @@ class CompatibilityModeGatingTest extends Specification {
8485
String main = testOptions.getMainClass().getOrNull()
8586

8687
then:
87-
main == null
88+
main == JVM_LAUNCHER
8889
}
8990

90-
def "Compatibility Mode via NATIVE_IMAGE_OPTIONS in task options env disables JUnit launcher wiring"() {
91+
def "Compatibility Mode via NATIVE_IMAGE_OPTIONS in task options env wires JVM ConsoleLauncher main class"() {
9192
given:
9293
NativeImageOptions testOptions = graal.binaries.getByName("test")
9394
// Toggle compatibility mode via environment map on the options
@@ -97,6 +98,6 @@ class CompatibilityModeGatingTest extends Specification {
9798
String main = testOptions.getMainClass().getOrNull()
9899

99100
then:
100-
main == null
101+
main == JVM_LAUNCHER
101102
}
102103
}

native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/CompatibilityModeNativeTestsFunctionalTest.groovy

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class CompatibilityModeNativeTestsFunctionalTest extends AbstractGraalVMMavenFun
5858

5959
then:
6060
buildSucceeded
61-
outputDoesNotContain "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test goal, JVM tests will run instead."
61+
outputDoesNotContain "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test execution, JVM tests will run instead. The native test image will still be built using the original JUnit ConsoleLauncher."
6262
// Native test runner executed
6363
outputContains "[junit-platform-native] Running in 'test listener' mode"
6464
outputContains "[ 0 containers skipped ]"
@@ -75,9 +75,9 @@ class CompatibilityModeNativeTestsFunctionalTest extends AbstractGraalVMMavenFun
7575

7676
then:
7777
buildSucceeded
78-
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test goal, JVM tests will run instead."
79-
// Ensure native-image build/run was not invoked
80-
outputDoesNotContain "GraalVM Native Image: Generating 'native-tests"
78+
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test execution, JVM tests will run instead. The native test image will still be built using the original JUnit ConsoleLauncher."
79+
// Ensure native-image image is built but tests weren't executed
80+
outputContainsPattern ".*GraalVM Native Image: Generating 'native-tests.*"
8181
outputDoesNotContain "containers found"
8282
outputDoesNotContain "tests found"
8383
}
@@ -104,9 +104,9 @@ class CompatibilityModeNativeTestsFunctionalTest extends AbstractGraalVMMavenFun
104104

105105
then:
106106
buildSucceeded
107-
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test goal, JVM tests will run instead."
108-
// Ensure native-image build/run was not invoked
109-
outputDoesNotContain "GraalVM Native Image: Generating 'native-tests"
107+
outputContains "Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test execution, JVM tests will run instead. The native test image will still be built using the original JUnit ConsoleLauncher."
108+
// Ensure native-image image is built but tests weren't executed
109+
outputContainsPattern ".*GraalVM Native Image: Generating 'native-tests.*"
110110
outputDoesNotContain "containers found"
111111
outputDoesNotContain "tests found"
112112
}

native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,17 @@ public void execute() throws MojoExecutionException {
187187

188188
configureEnvironment();
189189

190-
// Short-circuit native tests if Compatibility Mode is enabled
191-
if (isCompatibilityModeEnabled()) {
192-
logger.info("Compatibility Mode detected (-H:+CompatibilityMode); skipping native-image test goal, JVM tests will run instead.");
193-
return;
190+
// Detect Compatibility Mode; do not short-circuit the build anymore.
191+
boolean compatibilityMode = isCompatibilityModeEnabled();
192+
if (compatibilityMode) {
193+
logger.info("Compatibility Mode detected (-H:+CompatibilityMode); The native test image will be built using the original JUnit ConsoleLauncher.");
194194
}
195195

196-
buildArgs.add("--features=org.graalvm.junit.platform.JUnitPlatformFeature");
197-
198-
/* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */
199-
buildArgs.addAll(JUnitUtils.excludeJUnitClassInitializationFiles());
196+
if (!compatibilityMode) {
197+
buildArgs.add("--features=org.graalvm.junit.platform.JUnitPlatformFeature");
198+
/* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */
199+
buildArgs.addAll(JUnitUtils.excludeJUnitClassInitializationFiles());
200+
}
200201

201202
if (systemProperties == null) {
202203
systemProperties = new HashMap<>();
@@ -208,12 +209,17 @@ public void execute() throws MojoExecutionException {
208209
}
209210

210211
imageName = NATIVE_TESTS_EXE;
211-
mainClass = "org.graalvm.junit.platform.NativeImageJUnitLauncher";
212+
if (compatibilityMode) {
213+
// Use the original JUnit ConsoleLauncher as the main class in Compatibility Mode
214+
mainClass = "org.junit.platform.console.ConsoleLauncher";
215+
} else {
216+
mainClass = "org.graalvm.junit.platform.NativeImageJUnitLauncher";
217+
}
212218

213219
buildImage();
214220

215221
if (!skipTestExecution) {
216-
runNativeTests(outputDirectory.toPath().resolve(NATIVE_TESTS_EXE));
222+
runNativeTests(outputDirectory.toPath().resolve(NATIVE_TESTS_EXE), compatibilityMode);
217223
}
218224
}
219225

@@ -300,20 +306,24 @@ private boolean hasTests() {
300306
return false;
301307
}
302308

303-
private void runNativeTests(Path executable) throws MojoExecutionException {
304-
Path xmlLocation = outputDirectory.toPath().resolve("native-test-reports");
305-
if (!xmlLocation.toFile().exists() && !xmlLocation.toFile().mkdirs()) {
306-
throw new MojoExecutionException("Failed creating xml output directory");
307-
}
308-
309+
private void runNativeTests(Path executable, boolean compatibilityMode) throws MojoExecutionException {
309310
try {
310311
ProcessBuilder processBuilder = new ProcessBuilder(executable.toAbsolutePath().toString());
311312
processBuilder.inheritIO();
312313
processBuilder.directory(session.getCurrentProject().getBasedir());
313314

314315
List<String> command = new ArrayList<>();
315-
command.add("--xml-output-dir");
316-
command.add(xmlLocation.toString());
316+
if (compatibilityMode) {
317+
command.add("-cp=" + getClasspath());
318+
command.add("--scan-classpath");
319+
} else {
320+
Path xmlLocation = outputDirectory.toPath().resolve("native-test-reports");
321+
if (!xmlLocation.toFile().exists() && !xmlLocation.toFile().mkdirs()) {
322+
throw new MojoExecutionException("Failed creating xml output directory");
323+
}
324+
command.add("--xml-output-dir");
325+
command.add(xmlLocation.toString());
326+
}
317327
systemProperties.forEach((key, value) -> command.add("-D" + key + "=" + value));
318328
command.addAll(runtimeArgs);
319329

0 commit comments

Comments
 (0)