diff --git a/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF b/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF index e1d9cf35..0cb13cb5 100644 --- a/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF +++ b/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF @@ -15,6 +15,7 @@ Require-Bundle: org.eclipse.jdt.core, org.eclipse.jdt.ls.core, org.eclipse.core.runtime, org.eclipse.core.resources, - org.eclipse.jdt.core.manipulation + org.eclipse.jdt.core.manipulation, + org.eclipse.debug.core Bundle-ClassPath: ., lib/gson-2.7.jar diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/RuntimeClassPathUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/RuntimeClassPathUtils.java index af300cc1..aa5ab926 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/RuntimeClassPathUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/RuntimeClassPathUtils.java @@ -15,15 +15,31 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.internal.core.LaunchConfiguration; +import org.eclipse.debug.internal.core.LaunchConfigurationInfo; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.ls.core.internal.ProjectUtils; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -58,22 +74,91 @@ public static String[] resolveRuntimeClassPath(List arguments) throws Co final List classPathList = new ArrayList<>(); for (final IJavaProject project : projectsToTest) { - final String[] classPathArray = JavaRuntime.computeDefaultRuntimeClassPath(project); - final Set testEntriePaths = ProjectTestUtils.getTestOutputPath(project); - Arrays.sort(classPathArray, Comparator.comparing((String pathStr) -> { - final Path path = new Path(pathStr); - if (path.toFile().isFile()) { - return 1; - } - for (final IPath testPath: testEntriePaths) { - if (testPath.isPrefixOf(path)) { - return -1; - } - } - return 1; - })); - classPathList.addAll(Arrays.asList(classPathArray)); + classPathList.addAll(Arrays.asList(resolveClasspath(project))); } return classPathList.toArray(new String[classPathList.size()]); } + + private static String[] resolveClasspath(IJavaProject javaProject) throws CoreException { + // Use launch configuration to resolve the classpath. + // See: https://github.com/microsoft/vscode-java-test/issues/623. + final ILaunchConfiguration launchConfig = new JUnitLaunchConfiguration(javaProject.getProject()); + final IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(launchConfig); + final IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveRuntimeClasspath(unresolved, launchConfig); + final Set classpaths = new LinkedHashSet<>(); + for (final IRuntimeClasspathEntry entry : resolved) { + final String location = entry.getLocation(); + if (location != null) { + if (entry.getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES || + entry.getClasspathProperty() == IRuntimeClasspathEntry.CLASS_PATH) { + classpaths.add(location); + } + // TODO: support module path + } + } + return classpaths.toArray(new String[classpaths.size()]); + } + + private static class JUnitLaunchConfiguration extends LaunchConfiguration { + public static final String JUNIT_LAUNCH = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + private final IProject project; + private String classpathProvider; + private String sourcepathProvider; + private final LaunchConfigurationInfo launchInfo; + + protected JUnitLaunchConfiguration(IProject project) throws CoreException { + super(String.valueOf(new Date().getTime()), null, false); + this.project = project; + if (ProjectUtils.isMavenProject(project)) { + classpathProvider = "org.eclipse.m2e.launchconfig.classpathProvider"; + sourcepathProvider = "org.eclipse.m2e.launchconfig.sourcepathProvider"; + } else if (ProjectUtils.isGradleProject(project)) { + classpathProvider = "org.eclipse.buildship.core.classpathprovider"; + } + this.launchInfo = new JavaLaunchConfigurationInfo(JUNIT_LAUNCH); + } + + @Override + public String getAttribute(String attributeName, String defaultValue) throws CoreException { + if (IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME.equalsIgnoreCase(attributeName)) { + return project.getName(); + } else if (IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER.equalsIgnoreCase(attributeName)) { + return classpathProvider; + } else if (IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH_PROVIDER.equalsIgnoreCase(attributeName)) { + return sourcepathProvider; + } + + return super.getAttribute(attributeName, defaultValue); + } + + @Override + protected LaunchConfigurationInfo getInfo() throws CoreException { + return this.launchInfo; + } + } + + private static class JavaLaunchConfigurationInfo extends LaunchConfigurationInfo { + public JavaLaunchConfigurationInfo(String launchXml) { + super(); + try { + final DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + parser.setErrorHandler(new DefaultHandler()); + final StringReader reader = new StringReader(launchXml); + final InputSource source = new InputSource(reader); + final Element root = parser.parse(source).getDocumentElement(); + initializeFromXML(root); + } catch (ParserConfigurationException | SAXException | IOException | CoreException e) { + // do nothing + } + } + } }