introduce test discovery producer API

This commit is contained in:
Sergey Ignatov
2018-02-16 17:40:59 +03:00
parent 3a054b2d4d
commit 9cab5f1b12
6 changed files with 208 additions and 142 deletions
@@ -1,10 +1,6 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.testDiscovery;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.annotations.SerializedName;
import com.intellij.codeInsight.navigation.ListBackgroundUpdaterTask;
import com.intellij.execution.Executor;
import com.intellij.execution.actions.ConfigurationContext;
@@ -20,14 +16,12 @@ import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.IconButton;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
@@ -38,27 +32,15 @@ import com.intellij.ui.popup.AbstractPopup;
import com.intellij.ui.popup.HintUpdateSupply;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PsiNavigateUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.HttpRequests;
import com.intellij.util.io.RequestBuilder;
import com.intellij.util.ui.EdtInvocationManager;
import com.intellij.util.ui.JBDimension;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR;
import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_FILE;
public class FindTestsInTestDiscoveryServerAction extends AnAction {
private static final Logger LOG = Logger.getInstance(FindTestsInTestDiscoveryServerAction.class);
@Override
public void update(AnActionEvent e) {
Editor editor = e.getData(EDITOR);
@@ -157,10 +139,9 @@ public class FindTestsInTestDiscoveryServerAction extends AnAction {
loadTestsTask.init((AbstractPopup)popup, list, new Ref<>());
ApplicationManager.getApplication().executeOnPooledThread(() -> {
Map<String, String> map = fetchDataFromDiscoveryServer(fqn, methodName);
map.forEach((classFqn, testMethodName) -> {
TestDiscoveryProducer.consumeTestClassesAndMethods(project, fqn, methodName, "j", (testClassFqn, testMethodName) -> {
PsiMethod psiMethod = ReadAction.compute(() -> {
PsiClass cc = classFqn == null ? null : javaFacade.findClass(classFqn, scope);
PsiClass cc = testClassFqn == null ? null : javaFacade.findClass(testClassFqn, scope);
return cc == null ? null : ArrayUtil.getFirstElement(cc.findMethodsByName(testMethodName, false));
});
if (psiMethod != null) {
@@ -174,100 +155,4 @@ public class FindTestsInTestDiscoveryServerAction extends AnAction {
});
});
}
private static final String INTELLIJ_TEST_DISCOVERY_HOST = "http://intellij-test-discovery";
private static Map<String, String> fetchDataFromDiscoveryServer(@NotNull String classFQName, @NotNull String methodName) {
String methodFqn = classFQName + "." + methodName;
RequestBuilder r = HttpRequests.request(INTELLIJ_TEST_DISCOVERY_HOST + "/search/tests/by-method/" + methodFqn);
try {
return r.connect(request -> {
Map<String, String> map = ContainerUtil.newLinkedHashMap();
ObjectMapper mapper = new ObjectMapper();
TestsSearchResult result = mapper.readValue(request.getInputStream(), TestsSearchResult.class);
result.getTests().forEach(s -> {
s = s.length() > 1 && s.charAt(0) == 'j' ? s.substring(1) : s;
String classFqn = StringUtil.substringBefore(s, "-");
String testMethodName = StringUtil.substringAfter(s, "-");
map.put(classFqn, testMethodName);
});
return map;
});
}
catch (HttpRequests.HttpStatusException http) {
LOG.debug("No tests found for " + methodFqn);
}
catch (IOException e) {
LOG.debug(e);
}
return Collections.emptyMap();
}
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public static class TestsSearchResult {
@Nullable
private String method;
@SerializedName("class")
@JsonProperty("class")
@Nullable
private String className;
private int found;
@NotNull
private List<String> tests = new ArrayList<>();
@Nullable
private String message;
@Nullable
public String getMethod() {
return method;
}
public TestsSearchResult setMethod(String method) {
this.method = method;
return this;
}
@Nullable
public String getClassName() {
return className;
}
public TestsSearchResult setClassName(String name) {
this.className = name;
return this;
}
public int getFound() {
return found;
}
@NotNull
public List<String> getTests() {
return tests;
}
public TestsSearchResult setTests(List<String> tests) {
this.tests = tests;
this.found = tests.size();
return this;
}
@Nullable
public String getMessage() {
return message;
}
public TestsSearchResult setMessage(String message) {
this.message = message;
return this;
}
}
}
@@ -0,0 +1,118 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.testDiscovery;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.annotations.SerializedName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.HttpRequests;
import com.intellij.util.io.RequestBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class IntellijTestDiscoveryProducer implements TestDiscoveryProducer {
private static final String INTELLIJ_TEST_DISCOVERY_HOST = "http://intellij-test-discovery";
@NotNull
@Override
public Map<String, String> getTestClassesAndMethodNames(Project project, String classFQName, String methodName, String frameworkId) {
String methodFqn = classFQName + "." + methodName;
RequestBuilder r = HttpRequests.request(INTELLIJ_TEST_DISCOVERY_HOST + "/search/tests/by-method/" + methodFqn);
try {
return r.connect(request -> {
Map<String, String> map = ContainerUtil.newLinkedHashMap();
ObjectMapper mapper = new ObjectMapper();
TestsSearchResult result = mapper.readValue(request.getInputStream(), TestsSearchResult.class);
result.getTests().forEach(s -> {
s = s.length() > 1 && s.charAt(0) == 'j' ? s.substring(1) : s;
String classFqn = StringUtil.substringBefore(s, "-");
String testMethodName = StringUtil.substringAfter(s, "-");
map.put(classFqn, testMethodName);
});
return map;
});
}
catch (HttpRequests.HttpStatusException http) {
LOG.debug("No tests found for " + methodFqn);
}
catch (IOException e) {
LOG.debug(e);
}
return Collections.emptyMap();
}
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public static class TestsSearchResult {
@Nullable
private String method;
@SerializedName("class")
@JsonProperty("class")
@Nullable
private String className;
private int found;
@NotNull
private List<String> tests = new ArrayList<>();
@Nullable
private String message;
@Nullable
public String getMethod() {
return method;
}
public TestsSearchResult setMethod(String method) {
this.method = method;
return this;
}
@Nullable
public String getClassName() {
return className;
}
public TestsSearchResult setClassName(String name) {
this.className = name;
return this;
}
public int getFound() {
return found;
}
@NotNull
public List<String> getTests() {
return tests;
}
public TestsSearchResult setTests(List<String> tests) {
this.tests = tests;
this.found = tests.size();
return this;
}
@Nullable
public String getMessage() {
return message;
}
public TestsSearchResult setMessage(String message) {
this.message = message;
return this;
}
}
}
@@ -0,0 +1,41 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.testDiscovery;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class LocalTestDiscoveryProducer implements TestDiscoveryProducer {
@Override
@NotNull
public Map<String, String> getTestClassesAndMethodNames(@NotNull Project project,
@NotNull String classFQName,
@NotNull String methodName,
@NotNull String frameworkId) {
try {
Map<String, String> map = ContainerUtil.newLinkedHashMap();
TestDiscoveryIndex discoveryIndex = TestDiscoveryIndex.getInstance(project);
Collection<String> testsByMethodName = discoveryIndex.getTestsByMethodName(classFQName, methodName, frameworkId);
if (testsByMethodName != null) {
for (String pattern : testsByMethodName) { // todo[batkovich]: filter by framework id
List<String> split = StringUtil.split(pattern, "-");
if (split.size() == 2) {
map.put(split.get(0), split.get(1));
}
}
}
return map;
}
catch (IOException io) {
TestDiscoveryProducer.LOG.warn(io);
}
return Collections.emptyMap();
}
}
@@ -0,0 +1,35 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.testDiscovery;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Map;
import java.util.function.BiConsumer;
@ApiStatus.Experimental
public interface TestDiscoveryProducer {
Logger LOG = Logger.getInstance(LocalTestDiscoveryProducer.class);
@NotNull
Map<String, String> getTestClassesAndMethodNames(Project project,
String classFQName,
String methodName,
String frameworkId);
ExtensionPointName<TestDiscoveryProducer> EP = ExtensionPointName.create("com.intellij.testDiscoveryProducer");
static void consumeTestClassesAndMethods(@NotNull Project project,
@NotNull String classFQName,
@NotNull String methodName,
@NotNull String frameworkId,
@NotNull BiConsumer<String, String> consumer) {
for (TestDiscoveryProducer producer : EP.getExtensions()) {
producer.getTestClassesAndMethodNames(project, classFQName, methodName, frameworkId).forEach(consumer);
}
}
}
@@ -20,7 +20,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.diff.FilesTooBigForDiffException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
@@ -32,11 +31,7 @@ public class TestDiscoverySearchHelper {
final String frameworkPrefix) {
final Set<String> patterns = new LinkedHashSet<>();
if (position != null) {
try {
collectPatterns(project, patterns, position.first, position.second, frameworkPrefix);
}
catch (IOException ignore) {
}
collectPatterns(project, patterns, position.first, position.second, frameworkPrefix);
}
final List<VirtualFile> files = getAffectedFiles(changeList, project);
final PsiManager psiManager = PsiManager.getInstance(project);
@@ -92,18 +87,12 @@ public class TestDiscoverySearchHelper {
return new HashSet<>(ContainerUtil.filter(patterns, fqn -> ReadAction.compute(() -> psiFacade.findClass(StringUtil.getPackageName(fqn, ','), searchScope) != null)));
}
private static void collectPatterns(final Project project,
final Set<String> patterns,
final String classFQName,
final String methodName,
final String frameworkId) throws IOException {
final TestDiscoveryIndex discoveryIndex = TestDiscoveryIndex.getInstance(project);
final Collection<String> testsByMethodName = discoveryIndex.getTestsByMethodName(classFQName, methodName, frameworkId);
if (testsByMethodName != null) {
for (String pattern : testsByMethodName) {
patterns.add(pattern.replace('-', ','));
}
}
private static void collectPatterns(@NotNull Project project,
@NotNull Set<String> patterns,
@NotNull String classFQName,
@NotNull String methodName,
@NotNull String frameworkId) {
TestDiscoveryProducer.consumeTestClassesAndMethods(project, classFQName, methodName, frameworkId, (c, m) -> patterns.add(c + "," + m));
}
@NotNull
@@ -130,19 +119,14 @@ public class TestDiscoverySearchHelper {
return Collections.emptyList();
}
@Nullable
@NotNull
private static LinkedHashSet<String> collectPatterns(PsiMethod psiMethod, String frameworkId) {
LinkedHashSet<String> patterns = new LinkedHashSet<>();
final PsiClass containingClass = psiMethod.getContainingClass();
if (containingClass != null) {
final String qualifiedName = containingClass.getQualifiedName();
if (qualifiedName != null) {
try {
collectPatterns(psiMethod.getProject(), patterns, qualifiedName, psiMethod.getName(), frameworkId);
}
catch (IOException e) {
return null;
}
collectPatterns(psiMethod.getProject(), patterns, qualifiedName, psiMethod.getName(), frameworkId);
}
}
return patterns;
+3
View File
@@ -77,6 +77,8 @@
<codeInsight.externalLibraryResolver implementation="com.intellij.execution.junit.codeInsight.JUnit5ExternalLibraryResolver"/>
<junitListener implementation="com.intellij.junit4.JUnitTestDiscoveryListener"/>
<runConfigurationProducer implementation="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer"/>
<testDiscoveryProducer implementation="com.intellij.execution.testDiscovery.LocalTestDiscoveryProducer"/>
<testDiscoveryProducer implementation="com.intellij.execution.testDiscovery.IntellijTestDiscoveryProducer"/>
<implicitUsageProvider implementation="com.intellij.execution.junit2.inspection.JUnitImplicitUsageProvider"/>
<predefinedMigrationMapProvider implementation="com.intellij.execution.junit2.refactoring.JUnit5Migration"/>
<psi.referenceContributor implementation="com.intellij.execution.junit.codeInsight.references.JUnitReferenceContributor"/>
@@ -95,6 +97,7 @@
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.junitListener" interface="com.intellij.rt.execution.junit.IDEAJUnitListener"/>
<extensionPoint qualifiedName="com.intellij.testDiscoveryProducer" interface="com.intellij.execution.testDiscovery.TestDiscoveryProducer"/>
</extensionPoints>
<actions>