Skip to content

Commit

Permalink
feat: Support running tests from Java Project explorer (#1125)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdneo authored Jan 19, 2021
1 parent f014c8a commit 8f16b71
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,41 @@
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.Launch;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;
import org.eclipse.jdt.internal.junit.JUnitMessages;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.VMRunnerConfiguration;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

public class JUnitLaunchConfigurationDelegate extends org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate {

private boolean fIsHierarchicalPackage;

public JUnitLaunchArguments getJUnitLaunchArguments(ILaunchConfiguration configuration, String mode,
IProgressMonitor monitor) throws CoreException {
boolean isHierarchicalPackage, IProgressMonitor monitor) throws CoreException {
fIsHierarchicalPackage = isHierarchicalPackage;
final ILaunch launch = new Launch(configuration, mode, null);

// TODO: Make the getVMRunnerConfiguration() in super class protected.
Expand All @@ -48,11 +71,85 @@ public JUnitLaunchArguments getJUnitLaunchArguments(ILaunchConfiguration configu
launchArguments.modulepath = config.getModulepath();
launchArguments.vmArguments = getVmArguments(config);
launchArguments.programArguments = config.getProgramArguments();

// The JUnit 5 launcher only supports run a single package, here we add all the sub-package names
// to the package name file as a workaround
if (isHierarchicalPackage &&
TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(getTestRunnerKind(configuration).getId())) {
appendPackageNames(launchArguments.programArguments, configuration);
}
return launchArguments;
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
return null;
} finally {
fIsHierarchicalPackage = false;
}
}

/*
* Override the super implementation when it is launched in hierarchical mode and starts from
* the package level
*
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#evaluateTests(
* org.eclipse.debug.core.ILaunchConfiguration, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IMember[] evaluateTests(ILaunchConfiguration configuration, IProgressMonitor monitor)
throws CoreException {
if (!fIsHierarchicalPackage) {
return super.evaluateTests(configuration, monitor);
}

final IPackageFragment testPackage = getTestPackage(configuration);
if (testPackage == null) {
return super.evaluateTests(configuration, monitor);
}

final IPackageFragment[] packages = JavaElementUtil.getPackageAndSubpackages(testPackage);

final HashSet<IType> result = new HashSet<>();
final ITestKind testKind = getTestRunnerKind(configuration);
for (final IPackageFragment packageFragment : packages) {
testKind.getFinder().findTestsInContainer(packageFragment, result, monitor);
}

if (result.isEmpty()) {
final String msg = Messages.format(JUnitMessages.JUnitLaunchConfigurationDelegate_error_notests_kind,
testKind.getDisplayName());
abort(msg, null, IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
}
return result.toArray(new IMember[result.size()]);
}

private IPackageFragment getTestPackage(ILaunchConfiguration configuration) throws CoreException {
final String containerHandle = configuration.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, "");
if (containerHandle.length() != 0) {
final IJavaElement element = JavaCore.create(containerHandle);
if (element == null || !element.exists()) {
abort(JUnitMessages.JUnitLaunchConfigurationDelegate_error_input_element_deosn_not_exist, null,
IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
}
if (element instanceof IPackageFragment) {
return (IPackageFragment) element;
}
}
return null;
}

/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#getTestRunnerKind(
* org.eclipse.debug.core.ILaunchConfiguration)
*/
private ITestKind getTestRunnerKind(ILaunchConfiguration configuration) {
ITestKind testKind = JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration);
if (testKind.isNull()) {
testKind = TestKindRegistry.getDefault().getKind(TestKindRegistry.JUNIT4_TEST_KIND_ID);
}
return testKind;
}

private String[] getVmArguments(VMRunnerConfiguration config) {
Expand All @@ -69,6 +166,34 @@ private String[] getVmArguments(VMRunnerConfiguration config) {
return vmArgs.toArray(new String[vmArgs.size()]);
}

/**
* JUnit5's runner will run packages defined in a file, we can add more packages into that file when it's
* run from hierarchical mode to let the runner run test in all the sub-packages.
*/
private void appendPackageNames(String[] programArguments, ILaunchConfiguration configuration) {
for (int i = 0; i < programArguments.length; i++) {
if ("-packageNameFile".equals(programArguments[i]) && i + 1 < programArguments.length) {
final String packageNameFilePath = programArguments[i + 1];
final File file = new File(packageNameFilePath);
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),
StandardCharsets.UTF_8))) {
final IPackageFragment testPackage = getTestPackage(configuration);
if (testPackage == null) {
return;
}
final IPackageFragment[] packages = JavaElementUtil.getPackageAndSubpackages(testPackage);
for (final IPackageFragment pkg : packages) {
bw.write(pkg.getElementName());
bw.newLine();
}
} catch (IOException | CoreException e) {
// do nothing
}
return;
}
}
}

public static class JUnitLaunchArguments {
String workingDirectory;
String mainClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public static JUnitLaunchArguments resolveLaunchArgument(List<Object> arguments,
return resolveTestNGLaunchArguments(configuration, javaProject, delegate);
}

return delegate.getJUnitLaunchArguments(configuration, "run", monitor);
return delegate.getJUnitLaunchArguments(configuration, "run", args.isHierarchicalPackage, monitor);
}

public static void addOverrideDependencies(List<String> vmArgs, String dependencies) {
Expand Down Expand Up @@ -305,5 +305,6 @@ class Argument {
public TestKind testKind;
public Position start;
public Position end;
public boolean isHierarchicalPackage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@ public class SearchTestItemParams {

private String fullName;

private boolean isHierarchicalPackage;

public TestLevel getLevel() {
return level;
}

public boolean isHierarchicalPackage() {
return isHierarchicalPackage;
}

public void setHierarchicalPackage(boolean isHierarchicalPackage) {
this.isHierarchicalPackage = isHierarchicalPackage;
}

public void setLevel(TestLevel level) {
this.level = level;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler;
Expand Down Expand Up @@ -293,7 +294,7 @@ private static boolean isInTestScope(IJavaElement element) throws JavaModelExcep
}

private static IJavaSearchScope createSearchScope(SearchTestItemParams params)
throws JavaModelException, URISyntaxException {
throws URISyntaxException, CoreException {
switch (params.getLevel()) {
case ROOT:
final IJavaProject[] projects = Stream.of(ProjectUtils.getJavaProjects())
Expand All @@ -306,9 +307,14 @@ private static IJavaSearchScope createSearchScope(SearchTestItemParams params)
return SearchEngine.createJavaSearchScope(projectSet.toArray(new IJavaElement[projectSet.size()]),
IJavaSearchScope.SOURCES);
case PACKAGE:
final IJavaElement[] elements;
final IJavaElement packageElement = resolvePackage(params.getUri(), params.getFullName());
return SearchEngine.createJavaSearchScope(new IJavaElement[] { packageElement },
IJavaSearchScope.SOURCES);
if (params.isHierarchicalPackage()) {
elements = JavaElementUtil.getPackageAndSubpackages((IPackageFragment) packageElement);
} else {
elements = new IJavaElement[] { packageElement };
}
return SearchEngine.createJavaSearchScope(elements, IJavaSearchScope.SOURCES);
case CLASS:
final ICompilationUnit compilationUnit = JDTUtils.resolveCompilationUnit(params.getUri());
final IType[] types = compilationUnit.getAllTypes();
Expand Down Expand Up @@ -354,36 +360,37 @@ private static void searchInFolder(List<TestItem> resultList, SearchTestItemPara

private static void searchInPackage(List<TestItem> resultList, SearchTestItemParams params)
throws JavaModelException {
final IPackageFragment packageFragment = resolvePackage(params.getUri(), params.getFullName());
if (packageFragment == null) {
final IJavaElement packageFragment = resolvePackage(params.getUri(), params.getFullName());
if (packageFragment == null || !(packageFragment instanceof IPackageFragment)) {
return;
}

for (final ICompilationUnit unit : packageFragment.getCompilationUnits()) {
for (final ICompilationUnit unit : ((IPackageFragment) packageFragment).getCompilationUnits()) {
for (final IType type : unit.getTypes()) {
resultList.add(TestItemUtils.constructTestItem(type, TestLevel.CLASS));
}
}
}

private static IPackageFragment resolvePackage(String uriString, String fullName) throws JavaModelException {
if (TestItemUtils.DEFAULT_PACKAGE_NAME.equals(fullName)) {
final IFolder resource = (IFolder) JDTUtils.findResource(JDTUtils.toURI(uriString),
private static IJavaElement resolvePackage(String uriString, String fullName) throws JavaModelException {
final IFolder resource = (IFolder) JDTUtils.findResource(JDTUtils.toURI(uriString),
ResourcesPlugin.getWorkspace().getRoot()::findContainersForLocationURI);
final IJavaElement element = JavaCore.create(resource);
if (element instanceof IPackageFragmentRoot) {
final IJavaElement element = JavaCore.create(resource);
if (element == null) {
return null;
}
if (element instanceof IPackageFragmentRoot) {
if (TestItemUtils.DEFAULT_PACKAGE_NAME.equals(fullName)) {
final IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) element;
for (final IJavaElement child : packageRoot.getChildren()) {
if (child instanceof IPackageFragment && ((IPackageFragment) child).isDefaultPackage()) {
return (IPackageFragment) child;
}
}
}
} else {
return JDTUtils.resolvePackage(uriString);
}

return null;
return element;
}

private static void searchInClass(List<TestItem> resultList, SearchTestItemParams params)
Expand Down
34 changes: 34 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@
"command": "java.test.explorer.debug",
"when": "view == testExplorer && viewItem != UNTESTABLE_NODE",
"group": "inline@1"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"when": "view == javaProjectExplorer && viewItem =~ /java:(type|package|packageRoot)(?=.*?\\b\\+uri\\b)(?=.*?\\b\\+test\\b)/",
"group": "8_execution@10"
},
{
"command": "java.test.debugFromJavaProjectExplorer",
"when": "view == javaProjectExplorer && viewItem =~ /java:(type|package|packageRoot)(?=.*?\\b\\+uri\\b)(?=.*?\\b\\+test\\b)/",
"group": "8_execution@20"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"when": "view == javaProjectExplorer && viewItem =~ /java:(type|package|packageRoot)(?=.*?\\b\\+uri\\b)(?=.*?\\b\\+test\\b)/",
"group": "inline@run_0"
}
],
"commandPalette": [
Expand All @@ -138,6 +153,14 @@
"command": "java.test.explorer.refresh",
"when": "false"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"when": "false"
},
{
"command": "java.test.debugFromJavaProjectExplorer",
"when": "false"
},
{
"command": "java.test.config.migrate",
"when": "java:hasDeprecatedTestConfig"
Expand Down Expand Up @@ -201,6 +224,17 @@
"icon": "$(run-all)",
"category": "Java"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"title": "%contributes.commands.java.test.runFromJavaProjectExplorer%",
"icon": "$(play)",
"category": "Java"
},
{
"command": "java.test.debugFromJavaProjectExplorer",
"title": "%contributes.commands.java.test.debugFromJavaProjectExplorer%",
"category": "Java"
},
{
"command": "java.test.explorer.debugAll",
"title": "%contributes.commands.java.test.explorer.debugAll.title%",
Expand Down
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"contributes.commands.java.test.show.report.title": "Show Test Report",
"contributes.commands.java.test.editor.run.title": "Run Tests",
"contributes.commands.java.test.editor.debug.title": "Debug Tests",
"contributes.commands.java.test.runFromJavaProjectExplorer": "Run Tests",
"contributes.commands.java.test.debugFromJavaProjectExplorer": "Debug Tests",
"contributes.commands.java.test.relaunch.title": "Relaunch the Tests",
"contributes.commands.java.test.cancel.title": "Cancel Test Job",
"contributes.commands.java.test.explorer.refresh.title": "Refresh",
Expand Down
2 changes: 2 additions & 0 deletions package.nls.zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"contributes.commands.java.test.editor.debug.title": "调试测试用例",
"contributes.commands.java.test.relaunch.title": "重新执行测试任务",
"contributes.commands.java.test.cancel.title": "取消测试任务",
"contributes.commands.java.test.runFromJavaProjectExplorer": "运行测试",
"contributes.commands.java.test.debugFromJavaProjectExplorer": "调试测试",
"contributes.commands.java.test.explorer.refresh.title": "刷新",
"contributes.commands.java.test.config.migrate.title": "迁移已弃用的 'launch.test.json' 文件",
"configuration.java.test.report.showAfterExecution.description": "设定测试报告是否会在测试完成后自动显示",
Expand Down
Loading

0 comments on commit 8f16b71

Please sign in to comment.