Skip to content

Avoid Adding Duplicated JUnit Entries on Classpath #712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ def matrixDefault = [

// # Following versions are disabled temporarily in order to speed up PR testing "7.3.3", "7.2", "7.1", "6.8.3",
def gradleVersions = [
"gradle-version": ["current", "7.4", "8.13"],
"gradle-version": ["current", "8.4", "8.14.2"],
]

def gradleCachedVersions = [
"gradle-config-cache-version": ["current", "8.0.1"]
"gradle-config-cache-version": ["current", "8.14.2"]
]

sourceSets.configureEach { sourceSet ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ def graalVm = javaToolchains.launcherFor {

def fullFunctionalTest = tasks.register("fullFunctionalTest")

['functionalTest', 'configCacheFunctionalTest'].each { baseName ->
["current", "7.4", "7.6.2", "8.0.1", "8.2.1", "8.13"].each { gradleVersion ->
['functionalTest': ["current", "8.4", "8.14.2"],
'configCacheFunctionalTest': ['current', "8.14.2"]
].each { baseName, gradleVersions ->
gradleVersions.each { gradleVersion ->
String taskName = gradleVersion == 'current' ? baseName : "gradle${gradleVersion}${baseName.capitalize()}"
// Add a task to run the functional tests
def testTask = tasks.register(taskName, Test) {
Expand Down
15 changes: 11 additions & 4 deletions common/junit-platform-native/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,19 @@ maven {
}
dependencies {
compileOnly libs.graalvm.svm
implementation(platform(libs.test.junit.bom))
implementation libs.test.junit.platform.console
implementation libs.test.junit.platform.launcher
implementation libs.test.junit.jupiter.core
compileOnly(platform(libs.test.junit.bom))
compileOnly libs.test.junit.platform.console
compileOnly libs.test.junit.platform.launcher
compileOnly libs.test.junit.jupiter.core
testImplementation libs.test.junit.vintage

testImplementation libs.test.junit.jupiter.core

testRuntimeOnly(platform(libs.test.junit.bom))
testCompileOnly(platform(libs.test.junit.bom))
testRuntimeOnly libs.test.junit.platform.console
testRuntimeOnly libs.test.junit.platform.launcher
testRuntimeOnly libs.test.junit.jupiter.core
}

apply from: "gradle/native-image-testing.gradle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,13 @@ private static void initializeClassesForJDK21OrEarlier() {
try (InputStream is = JUnitPlatformFeature.class.getResourceAsStream("/initialize-at-buildtime")) {
if (is != null) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
br.lines().forEach(RuntimeClassInitialization::initializeAtBuildTime);
br.lines().forEach(cls -> {
try {
RuntimeClassInitialization.initializeAtBuildTime(cls);
} catch (NoClassDefFoundError e) {
// if users use older JUnit versions some classes might not be available
}
});
}
}
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ public static void main(String... args) {
out.println("JUnit Platform on Native Image - report");
out.println("----------------------------------------\n");
out.flush();
launcher.registerTestExecutionListeners(new PrintTestExecutionListener(out));
configurePrintTestExecutionListener(launcher, out);
}

SummaryGeneratingListener summaryListener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(summaryListener);
launcher.registerTestExecutionListeners(new LegacyXmlReportGeneratingListener(Paths.get(xmlOutput), out));
configureLegacyXMLReport(launcher, xmlOutput, out);
launcher.execute(testPlan);

TestExecutionSummary summary = summaryListener.getSummary();
Expand Down Expand Up @@ -271,4 +271,21 @@ private static boolean testIdsDirectoryExists(Path directory) {
return directory != null && Files.exists(directory);
}


private static void configurePrintTestExecutionListener(Launcher launcher, PrintWriter out) {
try {
Class.forName("org.junit.platform.reporting.legacy.LegacyReportingUtils");
launcher.registerTestExecutionListeners(new PrintTestExecutionListener(out));
} catch (NoClassDefFoundError | ClassNotFoundException e) {
// intentionally ignored
}
}

private static void configureLegacyXMLReport(Launcher launcher, String xmlOutput, PrintWriter out) {
try {
launcher.registerTestExecutionListeners(new LegacyXmlReportGeneratingListener(Paths.get(xmlOutput), out));
} catch (NoClassDefFoundError e) {
// intentionally ignored
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.FieldSource;
import org.junit.jupiter.params.provider.MethodSource;

import java.lang.reflect.Method;
import java.util.ArrayList;
Expand Down Expand Up @@ -86,9 +86,15 @@ public void onTestClassRegistered(Class<?> testClass, NativeImageConfiguration r
AnnotationUtils.forEachAnnotatedMethodParameter(testClass, AggregateWith.class, annotation -> registry.registerAllClassMembersForReflection(annotation.value()));
AnnotationUtils.forEachAnnotatedMethod(testClass, EnumSource.class, (m, annotation) -> handleEnumSource(m, annotation, registry));
AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, MethodSource.class, JupiterConfigProvider::handleMethodSource);
AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, FieldSource.class, JupiterConfigProvider::handleFieldSource);
AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, EnabledIf.class, JupiterConfigProvider::handleEnabledIf);
AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, DisabledIf.class, JupiterConfigProvider::handleDisabledIf);

try {
AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, FieldSource.class, JupiterConfigProvider::handleFieldSource);
} catch (NoClassDefFoundError e) {
// if users use JUnit version older than 5.11, FieldSource class won't be available
}

}

private static Class<?>[] handleMethodSource(MethodSource annotation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$3
org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests
org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod
org.junit.jupiter.engine.execution.ConditionEvaluator
org.junit.jupiter.engine.execution.ExecutableInvoker
org.junit.jupiter.engine.execution.InterceptingExecutableInvoker
org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall
org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall
Expand Down Expand Up @@ -78,6 +79,7 @@ org.junit.platform.launcher.core.DiscoveryIssueNotifier
org.junit.platform.launcher.core.EngineDiscoveryOrchestrator
org.junit.platform.launcher.core.EngineExecutionOrchestrator
org.junit.platform.launcher.core.EngineFilterer
org.junit.platform.launcher.core.EngineIdValidator
org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider
org.junit.platform.launcher.core.InternalTestPlan
org.junit.platform.launcher.core.LauncherConfig
Expand All @@ -91,6 +93,8 @@ org.junit.platform.launcher.core.LauncherDiscoveryResult$EngineResultInfo
org.junit.platform.launcher.core.LauncherListenerRegistry
org.junit.platform.launcher.core.LauncherPhase
org.junit.platform.launcher.core.ListenerRegistry
org.junit.platform.launcher.core.ServiceLoaderRegistry
org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry
org.junit.platform.launcher.core.SessionPerRequestLauncher
org.junit.platform.launcher.EngineDiscoveryResult
org.junit.platform.launcher.LauncherSessionListener$1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2003-2021 the original author or authors.
*
* Licensed 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 org.graalvm.buildtools.utils;

import java.util.List;

public abstract class JUnitPlatformNativeDependenciesHelper {

private static final DependencyNotation JUNIT_PLATFORM_LAUNCHER = new DependencyNotation(
"org.junit.platform", "junit-platform-launcher", ""
);
private static final DependencyNotation JUNIT_PLATFORM_ENGINE = new DependencyNotation(
"org.junit.platform", "junit-platform-engine", ""
);
private static final DependencyNotation JUNIT_PLATFORM_CONSOLE = new DependencyNotation(
"org.junit.platform", "junit-platform-console", ""
);
private static final DependencyNotation JUNIT_PLATFORM_REPORTING = new DependencyNotation(
"org.junit.platform", "junit-platform-reporting", ""
);

private static final List<DependencyNotation> JUNIT_PLATFORM_DEPENDENCIES = List.of(
JUNIT_PLATFORM_LAUNCHER,
JUNIT_PLATFORM_CONSOLE,
JUNIT_PLATFORM_REPORTING
);

private JUnitPlatformNativeDependenciesHelper() {

}

/**
* Returns the list of dependencies which should be added to the
* native test classpath in order for tests to execute.
* @param input the current list of dependencies
* @return a list of dependencies which need to be added
*/
public static List<DependencyNotation> inferMissingDependenciesForTestRuntime(
List<DependencyNotation> input
) {
var junitPlatformVersion = input.stream()
.filter(d -> d.equalsIgnoreVersion(JUNIT_PLATFORM_ENGINE))
.findFirst()
.map(DependencyNotation::version)
.orElse("");
var list = JUNIT_PLATFORM_DEPENDENCIES.stream()
.filter(d -> input.stream().noneMatch(o -> o.equalsIgnoreVersion(d)))
.map(d -> d.withVersion(junitPlatformVersion))
.toList();
return list;
}


public record DependencyNotation(
String groupId,
String artifactId,
String version
) {
public boolean equalsIgnoreVersion(DependencyNotation other) {
return other.groupId.equals(groupId) &&
other.artifactId.equals(artifactId);
}

public DependencyNotation withVersion(String version) {
return new DependencyNotation(groupId, artifactId, version);
}
}
}
2 changes: 1 addition & 1 deletion docs/src/docs/asciidoc/changelog.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

== Release 0.11.0

This version introduces a breaking change: the plugin now requires Java 17 to run.
This version introduces a breaking change: the plugin now requires Gradle 8.3+ and Java 17 to run.

=== Gradle plugin

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ package org.graalvm.buildtools.gradle
import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest

class JUnitFunctionalTests extends AbstractFunctionalTest {
def "test if JUint support works with various annotations, reflection and resources"() {
def "test if JUnit support works with various annotations, reflection and resources"() {
debug=true
given:
withSample("junit-tests")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.graalvm.buildtools.gradle.tasks.UseLayerOptions;
import org.graalvm.buildtools.gradle.tasks.actions.CleanupAgentFilesAction;
import org.graalvm.buildtools.gradle.tasks.actions.MergeAgentFilesAction;
import org.graalvm.buildtools.utils.JUnitPlatformNativeDependenciesHelper;
import org.graalvm.buildtools.utils.JUnitUtils;
import org.graalvm.buildtools.gradle.tasks.scanner.JarAnalyzerTransform;
import org.graalvm.buildtools.utils.SharedConstants;
Expand Down Expand Up @@ -124,9 +125,11 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -683,7 +686,7 @@ public void registerTestBinary(Project project,
});

// Following ensures that required feature jar is on classpath for every project
injectTestPluginDependencies(project, graalExtension.getTestSupport());
injectTestPluginDependencies(project, name, graalExtension.getTestSupport());
TaskProvider<BuildNativeImageTask> testImageBuilder = tasks.named(deriveTaskName(name, "native", "Compile"), BuildNativeImageTask.class, task -> {
task.setOnlyIf(t -> graalExtension.getTestSupport().get() && testListDirectory.getAsFile().get().exists());
task.getTestListDirectory().set(testListDirectory);
Expand Down Expand Up @@ -930,11 +933,57 @@ public void execute(@Nonnull Task task) {
taskToInstrument.doLast(new CleanupAgentFilesAction(mergeInputDirs, fileOperations));
}

private static void injectTestPluginDependencies(Project project, Property<Boolean> testSupportEnabled) {
private static void injectTestPluginDependencies(Project project, String binaryName, Property<Boolean> testSupportEnabled) {
project.afterEvaluate(p -> {
if (testSupportEnabled.get()) {
project.getDependencies().add("testImplementation", "org.graalvm.buildtools:junit-platform-native:"
+ VersionInfo.JUNIT_PLATFORM_NATIVE_VERSION);
var configurations = project.getConfigurations();
var inferConfigName = imageClasspathConfigurationNameFor(binaryName) + "Internal";
var missingDependenciesConfig = configurations.create(compileOnlyClasspathConfigurationNameFor(binaryName) + "Auto", cnf -> {
cnf.setCanBeResolved(false);
cnf.setCanBeConsumed(false);
});
var imageClasspath = configurations.getByName(imageClasspathConfigurationNameFor(binaryName));
var inferConfig = configurations.create(inferConfigName, cnf -> {
cnf.setDescription("Infers missing dependencies which are required to run native tests");
cnf.setExtendsFrom(imageClasspath.getExtendsFrom());
cnf.setCanBeConsumed(false);
cnf.setCanBeResolved(true);
var attributes = imageClasspath.getAttributes().keySet();
cnf.attributes(attrs -> {
for (var attribute : attributes) {
var key = (Attribute<Object>) attribute;
Object value = imageClasspath.getAttributes().getAttribute(key);
attrs.attribute(key, value);
}
});
});
// Do not move this up or resolution will fail because we'll create the infer
// configuration with an unwanted parent config!
imageClasspath.extendsFrom(missingDependenciesConfig);

project.getDependencies().add(compileOnlyClasspathConfigurationNameFor(binaryName), "org.graalvm.buildtools:junit-platform-native:" + VersionInfo.JUNIT_PLATFORM_NATIVE_VERSION);
missingDependenciesConfig
.getDependencies()
.addAllLater(inferConfig.getIncoming()
.getResolutionResult()
.getRootComponent()
.map(root -> {
var allDependencies = new ArrayList<JUnitPlatformNativeDependenciesHelper.DependencyNotation>();
var visited = new HashSet<DependencyResult>();
var queue = new ArrayDeque<DependencyResult>(root.getDependencies());
while (!queue.isEmpty()) {
var current = queue.pop();
if (visited.add(current) && current instanceof ResolvedDependencyResult resolved) {
if (resolved.getSelected().getId() instanceof ModuleComponentIdentifier mci) {
allDependencies.add(new JUnitPlatformNativeDependenciesHelper.DependencyNotation(mci.getGroup(), mci.getModule(), mci.getVersion()));
}
}
}
return JUnitPlatformNativeDependenciesHelper.inferMissingDependenciesForTestRuntime(allDependencies)
.stream()
.map(notation -> project.getDependencies().create(notation.groupId() + ":" + notation.artifactId() + ":" + notation.version()))
.toList();
}));
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion native-maven-plugin/reproducers/issue-144/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencies>

<profiles>
<profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ protected void addDependenciesToClasspath() throws MojoExecutionException {
}
}

protected void addInferredDependenciesToClasspath() {

}

@Override
protected void maybeAddDependencyMetadata(Artifact dependency, Consumer<File> excludeAction) {
if (isExcluded(dependency)) {
Expand Down Expand Up @@ -422,6 +426,7 @@ protected void populateClasspath() throws MojoExecutionException {
populateApplicationClasspath();
addDependenciesToClasspath();
}
addInferredDependenciesToClasspath();
imageClasspath.removeIf(entry -> !entry.toFile().exists());
}

Expand Down
Loading
Loading