Skip to content

Commit e03d82d

Browse files
committed
Add Surefire Provider Classpath to Eclipse test classpath
Deprecate duplicate constants (defined in org.eclipse.jdt.core.IClasspathAttribute) This closes #2112
1 parent 3785255 commit e03d82d

File tree

5 files changed

+162
-14
lines changed

5 files changed

+162
-14
lines changed

org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/IClasspathManager.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.eclipse.core.resources.IProject;
1717
import org.eclipse.core.runtime.CoreException;
1818
import org.eclipse.core.runtime.IProgressMonitor;
19+
import org.eclipse.jdt.core.IClasspathAttribute;
1920
import org.eclipse.jdt.core.IClasspathEntry;
2021
import org.eclipse.jdt.core.IPackageFragmentRoot;
2122

@@ -75,17 +76,21 @@ public interface IClasspathManager {
7576
* org.eclipse.jdt.core.IClasspathAttribute.TEST, copied here to allow running with older jdt.core version.
7677
*
7778
* @since 1.9
79+
* @deprecated use {@link org.eclipse.jdt.core.IClasspathAttribute#TEST} instead
7880
*/
79-
String TEST_ATTRIBUTE = "test";
81+
@Deprecated
82+
String TEST_ATTRIBUTE = IClasspathAttribute.TEST;
8083

8184
/**
8285
* Name of IClasspathEntry attribute that is to limit the imported code of project by jdt.core. Same as
8386
* org.eclipse.jdt.core.IClasspathAttribute.WITHOUT_TEST_CODE, copied here to allow running with older jdt.core
8487
* version.
8588
*
8689
* @since 1.9
90+
* @deprecated use {@link org.eclipse.jdt.core.IClasspathAttribute#WITHOUT_TEST_CODE} instead
8791
*/
88-
String WITHOUT_TEST_CODE = "without_test_code";
92+
@Deprecated
93+
String WITHOUT_TEST_CODE = IClasspathAttribute.WITHOUT_TEST_CODE;
8994

9095
/**
9196
* Maven dependency resolution scope constant indicating test scope.

org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
import org.eclipse.m2e.core.project.configurator.ProjectConfigurationRequest;
6767
import org.eclipse.m2e.jdt.IClasspathDescriptor;
6868
import org.eclipse.m2e.jdt.IClasspathEntryDescriptor;
69-
import org.eclipse.m2e.jdt.IClasspathManager;
7069
import org.eclipse.m2e.jdt.IJavaProjectConfigurator;
7170
import org.eclipse.m2e.jdt.JreSystemVersion;
7271
import org.eclipse.m2e.jdt.MavenJdtPlugin;
@@ -528,7 +527,7 @@ protected void addSourceDirs(IClasspathDescriptor classpath, IProject project, L
528527
// all source entries are marked as generated (a.k.a. optional)
529528
IClasspathEntryDescriptor descriptor = classpath.addSourceEntry(sourceFolder.getFullPath(), outputPath,
530529
inclusion, exclusion, true /*generated*/);
531-
descriptor.setClasspathAttribute(IClasspathManager.TEST_ATTRIBUTE, addTestFlag ? "true" : null);
530+
descriptor.setClasspathAttribute(IClasspathAttribute.TEST, addTestFlag ? "true" : null);
532531
} else {
533532
log.info("Not adding source folder " + sourceFolder.getFullPath() + " because it overlaps with "
534533
+ enclosing.getPath());
@@ -682,7 +681,7 @@ private void addResourceFolder(IClasspathDescriptor classpath, IPath resourceFol
682681
log.info("Adding resource folder " + resourceFolder);
683682
IClasspathEntryDescriptor descriptor = classpath.addSourceEntry(resourceFolder, outputPath, DEFAULT_INCLUSIONS,
684683
new IPath[] {IPath.fromOSString("**")}, false /*optional*/);
685-
descriptor.setClasspathAttribute(IClasspathManager.TEST_ATTRIBUTE, addTestFlag ? "true" : null);
684+
descriptor.setClasspathAttribute(IClasspathAttribute.TEST, addTestFlag ? "true" : null);
686685
descriptor.setClasspathAttribute(IClasspathAttribute.OPTIONAL, "true"); //$NON-NLS-1$
687686
}
688687

org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/DefaultClasspathManagerDelegate.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.core.runtime.CoreException;
2424
import org.eclipse.core.runtime.IPath;
2525
import org.eclipse.core.runtime.IProgressMonitor;
26+
import org.eclipse.jdt.core.IClasspathAttribute;
2627

2728
import org.apache.maven.artifact.Artifact;
2829
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
@@ -48,6 +49,7 @@
4849
* @author igor
4950
*/
5051
public class DefaultClasspathManagerDelegate implements IClasspathManagerDelegate {
52+
5153
private final IProjectConfigurationManager configurationManager;
5254

5355
private final IMavenProjectRegistry projectManager;
@@ -139,7 +141,7 @@ void addClasspathEntries(IClasspathDescriptor classpath, IMavenProjectFacade fac
139141
File artifactFile = a.getFile();
140142
if(artifactFile != null /*&& artifactFile.canRead()*/) {
141143
entry = classpath.addLibraryEntry(IPath.fromOSString(artifactFile.getAbsolutePath()));
142-
entry.setClasspathAttribute(IClasspathManager.TEST_ATTRIBUTE, addTestFlag ? "true" : null);
144+
entry.setClasspathAttribute(IClasspathAttribute.TEST, addTestFlag ? "true" : null);
143145
}
144146
}
145147

@@ -158,8 +160,8 @@ void addClasspathEntries(IClasspathDescriptor classpath, IMavenProjectFacade fac
158160
projectTestAttributes.forEach((entryPath, testAttributes) -> {
159161
//the classpath definitely has an entry matching the path
160162
IClasspathEntryDescriptor descriptor = findClasspathDescriptor(classpath, entryPath);
161-
descriptor.setClasspathAttribute(IClasspathManager.TEST_ATTRIBUTE, (testAttributes.isTest) ? "true" : null);
162-
descriptor.setClasspathAttribute(IClasspathManager.WITHOUT_TEST_CODE,
163+
descriptor.setClasspathAttribute(IClasspathAttribute.TEST, (testAttributes.isTest) ? "true" : null);
164+
descriptor.setClasspathAttribute(IClasspathAttribute.WITHOUT_TEST_CODE,
163165
(testAttributes.excludeTestSources) ? "true" : null);
164166
});
165167
}

org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/MavenClasspathHelpers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static IClasspathEntry newContainerEntry(IPath path, IClasspathAttribute.
7070
}
7171

7272
public static boolean isTestSource(IClasspathEntry entry) {
73-
return "true".equals(getAttribute(entry, IClasspathManager.TEST_ATTRIBUTE));
73+
return "true".equals(getAttribute(entry, IClasspathAttribute.TEST));
7474
}
7575

7676
public static String getAttribute(IClasspathEntry entry, String key) {

org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/launch/MavenRuntimeClasspathProvider.java

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
package org.eclipse.m2e.jdt.internal.launch;
1515

1616
import java.io.File;
17+
import java.lang.reflect.InvocationTargetException;
18+
import java.lang.reflect.Method;
1719
import java.util.ArrayList;
1820
import java.util.Arrays;
21+
import java.util.Collection;
1922
import java.util.Collections;
2023
import java.util.HashSet;
2124
import java.util.LinkedHashSet;
@@ -26,6 +29,9 @@
2629
import org.slf4j.Logger;
2730
import org.slf4j.LoggerFactory;
2831

32+
import org.eclipse.aether.util.version.GenericVersionScheme;
33+
import org.eclipse.aether.version.InvalidVersionSpecificationException;
34+
import org.eclipse.aether.version.Version;
2935
import org.eclipse.core.resources.IProject;
3036
import org.eclipse.core.resources.IWorkspaceRoot;
3137
import org.eclipse.core.resources.ResourcesPlugin;
@@ -49,6 +55,8 @@
4955
import org.eclipse.jdt.launching.StandardClasspathProvider;
5056

5157
import org.apache.maven.artifact.Artifact;
58+
import org.apache.maven.plugin.Mojo;
59+
import org.apache.maven.plugin.MojoExecution;
5260
import org.apache.maven.project.MavenProject;
5361

5462
import org.eclipse.m2e.core.MavenPlugin;
@@ -101,12 +109,30 @@ public class MavenRuntimeClasspathProvider extends StandardClasspathProvider {
101109

102110
private static final String PROPERTY_M2E_DISABLE_ADD_MISSING_J_UNIT5_EXECUTION_DEPENDENCIES = "m2e.disableAddMissingJUnit5ExecutionDependencies";
103111

112+
private static final String GROUP_ID_SUREFIRE_PLUGIN = "org.apache.maven.plugins"; //$NON-NLS-1$
113+
114+
private static final String ARTIFACT_ID_SUREFIRE_PLUGIN = "maven-surefire-plugin"; //$NON-NLS-1$
115+
116+
/**
117+
* Minimum Surefire version to support adding provider dependencies
118+
* (https://issues.apache.org/jira/browse/SUREFIRE-1564)
119+
*/
120+
private static final Version MIN_SUREFIRE_VERSION; //$NON-NLS-1$
121+
122+
private static final String GOAL_TEST = "test"; //$NON-NLS-1$
123+
104124
private static final Set<String> supportedTypes = new HashSet<>();
105125
static {
106126
// not exactly nice, but works with eclipse 3.2, 3.3 and 3.4M3
107127
supportedTypes.add(MavenRuntimeClasspathProvider.JDT_JAVA_APPLICATION);
108128
supportedTypes.add(MavenRuntimeClasspathProvider.JDT_JUNIT_TEST);
109129
supportedTypes.add(MavenRuntimeClasspathProvider.JDT_TESTNG_TEST);
130+
131+
try {
132+
MIN_SUREFIRE_VERSION = new GenericVersionScheme().parseVersion("2.22.1");
133+
} catch(InvalidVersionSpecificationException ex) {
134+
throw new IllegalArgumentException("Could not parse hardcoded version 2.22.1", ex);
135+
}
110136
}
111137

112138
IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry();
@@ -167,11 +193,11 @@ public IRuntimeClasspathEntry[] resolveClasspath(final IRuntimeClasspathEntry[]
167193
} else {
168194
context = projectFacade.createExecutionContext();
169195
}
170-
return context.execute((ctx, monitor1) -> resolveClasspath0(entries, configuration, monitor1), monitor);
196+
return context.execute((ctx, monitor1) -> resolveClasspath0(entries, configuration, monitor1, ctx), monitor);
171197
}
172198

173199
IRuntimeClasspathEntry[] resolveClasspath0(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration,
174-
IProgressMonitor monitor) throws CoreException {
200+
IProgressMonitor monitor, IMavenExecutionContext context) throws CoreException {
175201
IJavaProject javaProject = JavaRuntime.getJavaProject(configuration);
176202

177203
boolean isModularConfiguration = JavaRuntime.isModularConfiguration(configuration);
@@ -184,7 +210,7 @@ IRuntimeClasspathEntry[] resolveClasspath0(IRuntimeClasspathEntry[] entries, ILa
184210
for(IRuntimeClasspathEntry entry : entries) {
185211
if(entry.getType() == IRuntimeClasspathEntry.CONTAINER
186212
&& MavenClasspathHelpers.isMaven2ClasspathContainer(entry.getPath())) {
187-
addMavenClasspathEntries(all, entry, configuration, scope, monitor, isModularConfiguration);
213+
addMavenClasspathEntries(all, entry, configuration, scope, monitor, isModularConfiguration, context);
188214
} else if(entry.getType() == IRuntimeClasspathEntry.PROJECT) {
189215
if(javaProject.getPath().equals(entry.getPath())) {
190216
addProjectEntries(all, entry.getPath(), scope, THIS_PROJECT_CLASSIFIER, configuration, monitor,
@@ -207,7 +233,7 @@ private void addStandardClasspathEntries(Set<IRuntimeClasspathEntry> all, IRunti
207233

208234
private void addMavenClasspathEntries(Set<IRuntimeClasspathEntry> resolved,
209235
IRuntimeClasspathEntry runtimeClasspathEntry, ILaunchConfiguration configuration, int scope,
210-
IProgressMonitor monitor, boolean isModularConfiguration) throws CoreException {
236+
IProgressMonitor monitor, boolean isModularConfiguration, IMavenExecutionContext context) throws CoreException {
211237
IJavaProject javaProject = JavaRuntime.getJavaProject(configuration);
212238
MavenJdtPlugin plugin = MavenJdtPlugin.getDefault();
213239
IClasspathManager buildpathManager = plugin.getBuildpathManager();
@@ -229,12 +255,128 @@ private void addMavenClasspathEntries(Set<IRuntimeClasspathEntry> resolved,
229255
}
230256
}
231257

232-
if(scope == IClasspathManager.CLASSPATH_TEST && TESTKIND_ORG_ECLIPSE_JDT_JUNIT_LOADER_JUNIT5
258+
if(scope == IClasspathManager.CLASSPATH_TEST) {
259+
// TODO: distinguish between junit and testng?
260+
addMavenSurefirePluginProviderDependencies(resolved, configuration, monitor, context, javaProject);
261+
}
262+
}
263+
264+
/**
265+
* Add Maven Surefire Plugin dependencies to the classpath which are required for the selected provider
266+
*
267+
* @param classpath
268+
* @param facade
269+
* @param maven
270+
* @param monitor
271+
* @throws CoreException
272+
*/
273+
private void addMavenSurefirePluginProviderDependencies(Set<IRuntimeClasspathEntry> resolved,
274+
ILaunchConfiguration configuration, IProgressMonitor monitor, IMavenExecutionContext context,
275+
IJavaProject javaProject) throws CoreException {
276+
277+
IMavenProjectFacade projectFacade = projectManager.create(javaProject.getProject(), monitor);
278+
if(Boolean.parseBoolean(projectFacade.getMavenProject().getProperties()
279+
.getProperty(PROPERTY_M2E_DISABLE_ADD_MISSING_J_UNIT5_EXECUTION_DEPENDENCIES, "false"))) { //$NON-NLS-1$
280+
log.debug("Skipping adding Maven Surefire Plugin dependencies as property {} is set to true",
281+
PROPERTY_M2E_DISABLE_ADD_MISSING_J_UNIT5_EXECUTION_DEPENDENCIES);
282+
return;
283+
}
284+
285+
boolean surefireDependenciesAdded = false;
286+
for(MojoExecution mojoExecution : projectFacade.getMojoExecutions(GROUP_ID_SUREFIRE_PLUGIN,
287+
ARTIFACT_ID_SUREFIRE_PLUGIN, monitor, GOAL_TEST)) {
288+
// only add dependencies if the surefire plugin version is above 2.22.1 (https://issues.apache.org/jira/browse/SUREFIRE-1564)
289+
try {
290+
Version version = new GenericVersionScheme().parseVersion(mojoExecution.getPlugin().getVersion());
291+
if(version.compareTo(MIN_SUREFIRE_VERSION) < 0) {
292+
log.debug(
293+
"Skipping adding Maven Surefire Plugin dependencies for MojoExecution id {} of plugin {} as its version {} is below 2.22.1",
294+
mojoExecution.getExecutionId(), mojoExecution.getPlugin().getId(),
295+
mojoExecution.getPlugin().getVersion());
296+
} else {
297+
log.debug("Adding Maven Surefire Plugin dependencies for MojoExecution id {} of plugin {}",
298+
mojoExecution.getExecutionId(), mojoExecution.getPlugin().getId());
299+
addMavenSurefirePluginProviderDependencies(resolved, mojoExecution, context, monitor);
300+
surefireDependenciesAdded = true;
301+
}
302+
} catch(InvalidVersionSpecificationException ex) {
303+
log.warn("Could not parse Maven Surefire Plugin version " + mojoExecution.getPlugin().getVersion()
304+
+ " for MojoExecution id " + mojoExecution.getExecutionId() + ", skipping adding its provider dependencies",
305+
ex);
306+
}
307+
308+
}
309+
310+
// use legacy mechanism to add junit5 dependencies for junit5 launches when surefire plugin is not used or below 2.22.1
311+
if(!surefireDependenciesAdded && TESTKIND_ORG_ECLIPSE_JDT_JUNIT_LOADER_JUNIT5
233312
.equals(configuration.getAttribute(ATTRIBUTE_ORG_ECLIPSE_JDT_JUNIT_TEST_KIND, ""))) {
313+
log.debug(
314+
"Adding missing JUnit5 execution dependencies for JUnit5 launch configuration via legacy method as no suitable Maven Surefire Plugin execution was found");
234315
addMissingJUnit5ExecutionDependencies(resolved, monitor, javaProject);
235316
}
236317
}
237318

319+
@SuppressWarnings("unchecked")
320+
Boolean addMavenSurefirePluginProviderDependencies(Set<IRuntimeClasspathEntry> resolved, MojoExecution mojoExecution,
321+
IMavenExecutionContext context, IProgressMonitor monitor) throws CoreException {
322+
Mojo mojo = MavenPlugin.getMaven().getConfiguredMojo(context.getSession(), mojoExecution, Mojo.class);
323+
final Class<?> abstractSurefireMojoClass;
324+
try {
325+
abstractSurefireMojoClass = Class.forName("org.apache.maven.plugin.surefire.AbstractSurefireMojo", false,
326+
mojoExecution.getMojoDescriptor().getRealm());
327+
} catch(ClassNotFoundException ex) {
328+
throw new IllegalStateException("Could not load AbstractSurefireMojo class from surefire plugin realm", ex);
329+
}
330+
Boolean isClasspathModified = false;
331+
try {
332+
// look for surefire test classpath: https://github.com/apache/maven-surefire/blob/18f0c9f9dd850f98377032693fa5ec460f42b65b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java#L1156C34-L1156C49
333+
Object testClassPath;
334+
try {
335+
// available since Surefire 2.22.1 (https://github.com/apache/maven-surefire/commit/242c0e8a70be5a2a839ab00062c645f0d7b81137)
336+
Method generateTestClasspathMethod = abstractSurefireMojoClass.getDeclaredMethod("generateTestClasspath");
337+
generateTestClasspathMethod.setAccessible(true);
338+
testClassPath = generateTestClasspathMethod.invoke(mojo);
339+
} catch(NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) {
340+
log.warn("Could not access generateTestClasspath() method on AbstractSurefireMojo", e);
341+
return false;
342+
}
343+
// and determine the providers used by this project with the given test classpath: https://github.com/apache/maven-surefire/blob/18f0c9f9dd850f98377032693fa5ec460f42b65b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java#L1156
344+
try {
345+
Class<?> providerInfoClass = Class.forName("org.apache.maven.surefire.providerapi.ProviderInfo", false,
346+
mojoExecution.getMojoDescriptor().getRealm());
347+
Class<?> testClassPathClass = Class.forName("org.apache.maven.plugin.surefire.TestClassPath", false,
348+
mojoExecution.getMojoDescriptor().getRealm());
349+
Method getProviderNameMethod = providerInfoClass.getDeclaredMethod("getProviderName");
350+
Method getProviderClasspathMethod = providerInfoClass.getDeclaredMethod("getProviderClasspath");
351+
Method createProvidersMethod = abstractSurefireMojoClass.getDeclaredMethod("createProviders",
352+
testClassPathClass);
353+
createProvidersMethod.setAccessible(true);
354+
Collection<?> providerInfos = (Collection<?>) createProvidersMethod.invoke(mojo, testClassPath);
355+
for(Object providerInfo : providerInfos) {
356+
String providerName = (String) getProviderNameMethod.invoke(providerInfo);
357+
log.debug("Adding Surefire dependencies for provider: {}", providerName);
358+
for(Artifact artifact : ((Collection<Artifact>) getProviderClasspathMethod.invoke(providerInfo))) {
359+
// all returned artifacts are already resolved via the call to "getProviderClasspath()"
360+
File artifactFile = artifact.getFile();
361+
resolved.add(JavaRuntime.newArchiveRuntimeClasspathEntry(IPath.fromOSString(artifactFile.getAbsolutePath()),
362+
IRuntimeClasspathEntry.USER_CLASSES));
363+
log.debug("Added Surefire provider dependency: {}:{}:{} for provider {}", artifact.getGroupId(),
364+
artifact.getArtifactId(), artifact.getVersion(), providerName);
365+
isClasspathModified = true;
366+
}
367+
}
368+
} catch(NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException
369+
| ClassNotFoundException e) {
370+
log.warn(
371+
"Could not access createProviders(org.apache.maven.plugin.surefire.TestClassPath) method on AbstractSurefireMojo",
372+
e);
373+
}
374+
} finally {
375+
MavenPlugin.getMaven().releaseMojo(mojo, mojoExecution);
376+
}
377+
return isClasspathModified;
378+
}
379+
238380
private void addMissingJUnit5ExecutionDependencies(Set<IRuntimeClasspathEntry> resolved, IProgressMonitor monitor,
239381
IJavaProject javaProject) throws CoreException {
240382
IMavenProjectFacade facade = projectManager.create(javaProject.getProject(), monitor);

0 commit comments

Comments
 (0)