mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 11:53:49 +07:00
[jvm] IDEA-207545 Convert TestFailedLine inspection to UAST
GitOrigin-RevId: 921c7fe92dd5b27b9fe0105fffd9f93ffb0a3e98
This commit is contained in:
committed by
intellij-monorepo-bot
parent
dfc4c07815
commit
32a57be862
@@ -1,104 +0,0 @@
|
||||
// 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.testframework;
|
||||
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.debugger.DebuggerManagerEx;
|
||||
import com.intellij.execution.*;
|
||||
import com.intellij.execution.actions.ConfigurationContext;
|
||||
import com.intellij.execution.executors.DefaultDebugExecutor;
|
||||
import com.intellij.execution.executors.DefaultRunExecutor;
|
||||
import com.intellij.execution.runners.ExecutionUtil;
|
||||
import com.intellij.execution.stacktrace.StackTraceLine;
|
||||
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Iconable;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.testIntegration.TestFailedLineManager;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class TestFailedLineInspection extends LocalInspectionTool {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression call) {
|
||||
|
||||
PsiElement nameElement = call.getMethodExpression().getReferenceNameElement();
|
||||
if (nameElement == null) return;
|
||||
|
||||
TestStateStorage.Record state = TestFailedLineManager.getInstance(holder.getProject()).getFailedLineState(call);
|
||||
if (state == null) return;
|
||||
if (state.magnitude <= TestStateInfo.Magnitude.IGNORED_INDEX.getValue())
|
||||
return;
|
||||
|
||||
LocalQuickFix[] fixes = {new DebugFailedTestFix(call, state.topStacktraceLine),
|
||||
new RunActionFix(call, DefaultRunExecutor.EXECUTOR_ID)};
|
||||
ProblemDescriptor descriptor = InspectionManager.getInstance(holder.getProject())
|
||||
.createProblemDescriptor(nameElement, state.errorMessage, isOnTheFly, fixes,
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
|
||||
descriptor.setTextAttributes(CodeInsightColors.RUNTIME_ERROR);
|
||||
holder.registerProblem(descriptor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class DebugFailedTestFix extends RunActionFix{
|
||||
|
||||
private final String myTopStacktraceLine;
|
||||
|
||||
DebugFailedTestFix(PsiElement element, String topStacktraceLine) {
|
||||
super(element, DefaultDebugExecutor.EXECUTOR_ID);
|
||||
myTopStacktraceLine = topStacktraceLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
StackTraceLine line = new StackTraceLine(project, myTopStacktraceLine);
|
||||
Location<PsiMethod> location = line.getMethodLocation(project);
|
||||
if (location != null) {
|
||||
Document document = PsiDocumentManager.getInstance(project).getDocument(location.getPsiElement().getContainingFile());
|
||||
if (document != null) {
|
||||
DebuggerManagerEx.getInstanceEx(project).getBreakpointManager().addLineBreakpoint(document, line.getLineNumber());
|
||||
}
|
||||
}
|
||||
super.applyFix(project, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RunActionFix implements LocalQuickFix, Iconable {
|
||||
private final Executor myExecutor;
|
||||
private final RunnerAndConfigurationSettings myConfiguration;
|
||||
|
||||
RunActionFix(PsiElement element, String executorId) {
|
||||
myExecutor = ExecutorRegistry.getInstance().getExecutorById(executorId);
|
||||
ConfigurationContext context = new ConfigurationContext(element);
|
||||
myConfiguration = context.getConfiguration();
|
||||
}
|
||||
|
||||
@Nls(capitalization = Nls.Capitalization.Sentence)
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
String text = myExecutor.getStartActionText(ProgramRunnerUtil.shortenName(myConfiguration.getName(), 0));
|
||||
return UIUtil.removeMnemonic(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
ExecutionUtil.runConfiguration(myConfiguration, myExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(int flags) {
|
||||
return myExecutor.getIcon();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,7 +345,6 @@
|
||||
<projectService serviceImplementation="com.intellij.jarRepository.RemoteRepositoriesConfiguration"/>
|
||||
<projectService serviceImplementation="com.intellij.jarRepository.services.MavenRepositoryServicesManager"/>
|
||||
<projectService serviceImplementation="com.intellij.psi.util.ProjectIconsAccessor"/>
|
||||
<projectService serviceImplementation="com.intellij.testIntegration.TestFailedLineManager"/>
|
||||
<projectService serviceImplementation="com.intellij.codeInsight.JavaProjectCodeInsightSettings"/>
|
||||
<projectService serviceImplementation="com.intellij.javadoc.JavadocGenerationManager"/>
|
||||
<projectService serviceInterface="com.intellij.codeInspection.ex.EntryPointsManager"
|
||||
@@ -1314,11 +1313,6 @@
|
||||
<java.error.fix errorCode="lambda.variable.must.be.final" implementationClass="com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassJava10Fix"/>
|
||||
<stripTrailingSpacesFilterFactory implementation="com.intellij.codeEditor.JavaStripTrailingSpacesFilterFactory"/>
|
||||
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="TestFailedLine"
|
||||
enabledByDefault="true" level="WARNING"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.junit.issues"
|
||||
implementationClass="com.intellij.execution.testframework.TestFailedLineInspection"
|
||||
key="inspection.test.failed.line.display.name" bundle="messages.JavaBundle"/>
|
||||
<localInspection language="JAVA" shortName="MoveFieldAssignmentToInitializer" enabledByDefault="true" level="INFORMATION"
|
||||
groupPath="Java" groupBundle="messages.InspectionsBundle" groupKey="group.names.code.style.issues"
|
||||
implementationClass="com.intellij.codeInspection.MoveFieldAssignmentToInitializerInspection"
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2000-2021 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.testIntegration;
|
||||
|
||||
import com.intellij.codeInsight.TestFrameworks;
|
||||
import com.intellij.execution.TestStateStorage;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.ClassUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TestFailedLineManager implements FileEditorManagerListener {
|
||||
|
||||
private final TestStateStorage myStorage;
|
||||
private final Map<VirtualFile, Map<String, TestInfo>> myMap;
|
||||
|
||||
public static TestFailedLineManager getInstance(@NotNull Project project) {
|
||||
return project.getService(TestFailedLineManager.class);
|
||||
}
|
||||
|
||||
public TestFailedLineManager(@NotNull Project project) {
|
||||
myStorage = TestStateStorage.getInstance(project);
|
||||
myMap = FactoryMap.create(o -> new HashMap<>());
|
||||
project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this);
|
||||
}
|
||||
|
||||
public TestInfo getTestInfo(@NotNull PsiMethod psiMethod) {
|
||||
PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class);
|
||||
if (psiClass == null) return null;
|
||||
TestFramework framework = TestFrameworks.detectFramework(psiClass);
|
||||
if (framework == null || !framework.isTestMethod(psiMethod, false)) return null;
|
||||
|
||||
String url = "java:test://" + ClassUtil.getJVMClassName(psiClass) + "/" + psiMethod.getName();
|
||||
TestStateStorage.Record state = myStorage.getState(url);
|
||||
if (state == null) return new TestInfo(null);
|
||||
|
||||
VirtualFile file = psiMethod.getContainingFile().getVirtualFile();
|
||||
Map<String, TestInfo> map = myMap.get(file);
|
||||
TestInfo info = map.get(url);
|
||||
if (info == null || !state.date.equals(info.myRecord.date)) {
|
||||
info = new TestInfo(state);
|
||||
map.put(url, info);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
public TestStateStorage.Record getFailedLineState(PsiMethodCallExpression call) {
|
||||
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(call, PsiMethod.class);
|
||||
if (psiMethod == null) return null;
|
||||
TestInfo info = getTestInfo(psiMethod);
|
||||
if (info == null || info.myRecord == null) return null;
|
||||
Document document = PsiDocumentManager.getInstance(call.getProject()).getDocument(call.getContainingFile());
|
||||
if (document == null) return null;
|
||||
if (info.myPointer != null) {
|
||||
PsiElement element = info.myPointer.getElement();
|
||||
if (element != null) {
|
||||
if (call == element) {
|
||||
info.myRecord.failedLine = document.getLineNumber(call.getTextOffset()) + 1;
|
||||
return info.myRecord;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
TestStateStorage.Record state = info.myRecord;
|
||||
if (state.failedLine == -1 || StringUtil.isEmpty(state.failedMethod)) return null;
|
||||
if (!state.failedMethod.equals(call.getMethodExpression().getText())) return null;
|
||||
if (state.failedLine != document.getLineNumber(call.getTextOffset()) + 1) return null;
|
||||
info.myPointer = SmartPointerManager.createPointer(call);
|
||||
return info.myRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
|
||||
Map<String, TestInfo> map = myMap.remove(file);
|
||||
if (map != null) {
|
||||
map.forEach((s, info) -> myStorage.writeState(s, info.myRecord));
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestInfo {
|
||||
public TestStateStorage.Record myRecord;
|
||||
public SmartPsiElementPointer<PsiElement> myPointer;
|
||||
|
||||
public TestInfo(TestStateStorage.Record record) {
|
||||
myRecord = record;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiIdentifier;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.util.ClassUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -24,21 +25,33 @@ public class TestRunLineMarkerProvider extends RunLineMarkerContributor {
|
||||
if (isIdentifier(e)) {
|
||||
PsiElement element = e.getParent();
|
||||
if (element instanceof PsiClass) {
|
||||
TestFramework framework = TestFrameworks.detectFramework((PsiClass)element);
|
||||
if (framework != null && framework.isTestClass(element)) {
|
||||
String url = "java:suite://" + ClassUtil.getJVMClassName((PsiClass)element);
|
||||
TestStateStorage.Record state = TestStateStorage.getInstance(e.getProject()).getState(url);
|
||||
return getInfo(state, true);
|
||||
}
|
||||
if (!isTestClass((PsiClass)element)) return null;
|
||||
String url = "java:suite://" + ClassUtil.getJVMClassName((PsiClass)element);
|
||||
TestStateStorage.Record state = TestStateStorage.getInstance(e.getProject()).getState(url);
|
||||
return getInfo(state, true);
|
||||
}
|
||||
if (element instanceof PsiMethod) {
|
||||
TestFailedLineManager.TestInfo testInfo = TestFailedLineManager.getInstance(e.getProject()).getTestInfo((PsiMethod)element);
|
||||
return testInfo == null ? null : getInfo(testInfo.myRecord, false);
|
||||
PsiClass containingClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
|
||||
if (!isTestMethod(containingClass, (PsiMethod)element)) return null;
|
||||
String url = "java:test://" + ClassUtil.getJVMClassName(containingClass) + "/" + ((PsiMethod)element).getName();
|
||||
TestStateStorage.Record state = TestStateStorage.getInstance(e.getProject()).getState(url);
|
||||
return getInfo(state, false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isTestClass(PsiClass clazz) {
|
||||
TestFramework framework = TestFrameworks.detectFramework(clazz);
|
||||
return framework != null && framework.isTestClass(clazz);
|
||||
}
|
||||
|
||||
private static boolean isTestMethod(PsiClass containingClass, PsiMethod method) {
|
||||
if (containingClass == null) return false;
|
||||
TestFramework framework = TestFrameworks.detectFramework(containingClass);
|
||||
return framework != null && framework.isTestMethod(method, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Info getInfo(TestStateStorage.Record state, boolean isClass) {
|
||||
return new Info(getTestStateIcon(state, isClass), ExecutorAction.getActions(1), element -> ExecutionBundle.message("run.text"));
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports failed method calls or assertions in tests.
|
||||
It helps to detect the failed line in code faster and start debugging it immediately if needed.
|
||||
<!-- tooltip end -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright 2000-2020 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.java.codeInsight.navigation;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.execution.TestStateStorage;
|
||||
import com.intellij.execution.executors.DefaultDebugExecutor;
|
||||
import com.intellij.execution.testframework.JavaTestLocator;
|
||||
import com.intellij.execution.testframework.TestConsoleProperties;
|
||||
import com.intellij.execution.testframework.TestFailedLineInspection;
|
||||
import com.intellij.execution.testframework.sm.runner.MockRuntimeConfiguration;
|
||||
import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties;
|
||||
import com.intellij.execution.testframework.sm.runner.SMTestProxy;
|
||||
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
|
||||
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
|
||||
import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
|
||||
import com.intellij.execution.testframework.sm.runner.ui.TestStackTraceParser;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.markup.EffectType;
|
||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
||||
import com.intellij.openapi.project.DumbServiceImpl;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtilBase;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.testIntegration.TestFailedLineManager;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FailedLineTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
|
||||
public void testFailedLineManager() {
|
||||
|
||||
configure();
|
||||
myFixture.enableInspections(new TestFailedLineInspection());
|
||||
myFixture.testHighlighting();
|
||||
PsiElement element = PsiUtilBase.getElementAtCaret(getEditor());
|
||||
PsiMethodCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
|
||||
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(callExpression, PsiMethod.class);
|
||||
TestFailedLineManager manager = TestFailedLineManager.getInstance(getProject());
|
||||
assertNotNull(manager.getTestInfo(psiMethod));
|
||||
TestStateStorage.Record record = manager.getFailedLineState(callExpression);
|
||||
assertNotNull(record);
|
||||
}
|
||||
|
||||
public void testFailedLineInspection() {
|
||||
configure();
|
||||
myFixture.enableInspections(new TestFailedLineInspection());
|
||||
myFixture.testHighlighting();
|
||||
List<HighlightInfo> infos = myFixture.doHighlighting(HighlightSeverity.WARNING);
|
||||
assertEquals(1, infos.size());
|
||||
TextAttributes attributes = infos.get(0).forcedTextAttributes;
|
||||
assertNotNull(attributes);
|
||||
assertEquals(EffectType.BOLD_DOTTED_LINE, attributes.getEffectType());
|
||||
}
|
||||
|
||||
public void testTopStacktraceLine() {
|
||||
TestStateStorage.Record record = configure();
|
||||
assertEquals("\tat MainTest.assertEquals(Assert.java:207)", record.topStacktraceLine);
|
||||
}
|
||||
|
||||
public void testDumbMode() throws InterruptedException {
|
||||
TestConsoleProperties consoleProperties = new SMTRunnerConsoleProperties(new MockRuntimeConfiguration(getProject()), "SMRunnerTests",
|
||||
DefaultDebugExecutor.getDebugExecutorInstance());
|
||||
SMTRunnerConsoleView view = new SMTRunnerConsoleView(consoleProperties);
|
||||
try {
|
||||
DumbServiceImpl.getInstance(getProject()).setDumb(true);
|
||||
String url = "schema://url";
|
||||
SMTestProxy test = new SMTestProxy("foo", false, url);
|
||||
test.setLocator(JavaTestLocator.INSTANCE);
|
||||
|
||||
view.initUI();
|
||||
SMTestRunnerResultsForm form = view.getResultsViewer();
|
||||
form.getTestsRootNode().addChild(test);
|
||||
test.setTestFailed("oops", "stacktrace", true);
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(() -> DumbServiceImpl.getInstance(getProject()).setDumb(false));
|
||||
form.onTestingFinished(form.getTestsRootNode());
|
||||
AppExecutorUtil.getAppExecutorService().awaitTermination(1, TimeUnit.SECONDS);
|
||||
}
|
||||
finally {
|
||||
Disposer.dispose(view);
|
||||
DumbServiceImpl.getInstance(getProject()).setDumb(false);
|
||||
}
|
||||
}
|
||||
|
||||
private TestStateStorage.Record configure() {
|
||||
myFixture.addClass("package junit.framework; public class TestCase {}");
|
||||
myFixture.configureByText("MainTest.java", " public class MainTest extends junit.framework.TestCase {\n" +
|
||||
" public void testFoo() {\n" +
|
||||
" <warning descr=\"oops\">assertE<caret>quals</warning>();\n" +
|
||||
" assertEquals();\n" +
|
||||
" }\n" +
|
||||
" public void assertEquals() {}\n" +
|
||||
" }");
|
||||
|
||||
String url = "java:test://MainTest/testFoo";
|
||||
TestStackTraceParser pair = new TestStackTraceParser(url, "\tat junit.framework.Assert.fail(Assert.java:47)\n" +
|
||||
"\tat MainTest.assertEquals(Assert.java:207)\n" +
|
||||
"\tat MainTest.testFoo(MainTest.java:3)", "oops", JavaTestLocator.INSTANCE, getProject());
|
||||
assertEquals(3, pair.getFailedLine());
|
||||
assertEquals("assertEquals", pair.getFailedMethodName());
|
||||
TestStateStorage.Record record = new TestStateStorage.Record(TestStateInfo.Magnitude.FAILED_INDEX.getValue(), new Date(),
|
||||
0, pair.getFailedLine(), pair.getFailedMethodName(),
|
||||
pair.getErrorMessage(), pair.getTopLocationLine());
|
||||
TestStateStorage.getInstance(getProject()).writeState(url, record);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,10 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="intellij.jvm.analysis" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.java" />
|
||||
<orderEntry type="module" module-name="intellij.java.debugger.impl" />
|
||||
<orderEntry type="module" module-name="intellij.java.execution.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.debugger" />
|
||||
<orderEntry type="library" name="StreamEx" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
</component>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
interface="com.intellij.codeInspection.blockingCallsDetection.NonBlockingContextChecker" dynamic="true"/>
|
||||
</extensionPoints>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<projectService serviceImplementation="com.intellij.codeInspection.TestFailedLineManager"/>
|
||||
<localInspection language="UAST" enabledByDefault="true" level="WARNING" shortName="UnstableApiUsage"
|
||||
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"
|
||||
groupKey="jvm.inspections.group.name" key="jvm.inspections.unstable.api.usage.display.name"
|
||||
@@ -48,6 +49,10 @@
|
||||
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"
|
||||
groupKey="jvm.inspections.group.name" key="jvm.inspections.testonly.display.name"
|
||||
implementationClass="com.intellij.codeInspection.TestOnlyInspection"/>
|
||||
<localInspection language="UAST" enabledByDefault="false" level="WARNING" shortName="TestFailedLine"
|
||||
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"
|
||||
groupKey="jvm.inspections.group.name" key="jvm.inspection.test.failed.line.display.name"
|
||||
implementationClass="com.intellij.codeInspection.TestFailedLineInspection"/>
|
||||
<notificationGroup id="UAST" displayType="BALLOON"/>
|
||||
</extensions>
|
||||
<extensions defaultExtensionNs="com.intellij.codeInsight">
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports failed method calls or assertions in tests. It helps to detect the failed line in code faster and start debugging it immediately if
|
||||
needed.
|
||||
<p>Example:</p>
|
||||
<pre>
|
||||
@Test
|
||||
fun foo() {
|
||||
assertEquals(1, 0) // highlighted
|
||||
}
|
||||
</pre>
|
||||
<!-- tooltip end -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -60,6 +60,8 @@ jvm.inspections.testonly.field.reference=Test-only field is referenced in produc
|
||||
jvm.inspections.testonly.method.call=Test-only method is called in production code
|
||||
jvm.inspections.testonly.visiblefortesting=@VisibleForTesting makes little sense on @TestOnly code
|
||||
|
||||
jvm.inspection.test.failed.line.display.name=Failed line in test
|
||||
|
||||
jvm.inspections.string.touppercase.tolowercase.without.locale.description=<code>String.{0}()</code> called without specifying a Locale using internationalized strings #loc
|
||||
can.t.build.uast.tree.for.file=Can't build UAST tree for file
|
||||
title.uast=UAST
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2000-2021 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.codeInspection
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.execution.ExecutorRegistry
|
||||
import com.intellij.execution.ProgramRunnerUtil
|
||||
import com.intellij.execution.RunnerAndConfigurationSettings
|
||||
import com.intellij.execution.actions.ConfigurationContext
|
||||
import com.intellij.execution.executors.DefaultDebugExecutor
|
||||
import com.intellij.execution.executors.DefaultRunExecutor
|
||||
import com.intellij.execution.runners.ExecutionUtil
|
||||
import com.intellij.execution.stacktrace.StackTraceLine
|
||||
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
|
||||
import com.intellij.lang.java.JavaLanguage
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Iconable
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.uast.UastVisitorAdapter
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.UQualifiedReferenceExpression
|
||||
import org.jetbrains.uast.sourcePsiElement
|
||||
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
|
||||
import javax.swing.Icon
|
||||
|
||||
class TestFailedLineInspection : AbstractBaseUastLocalInspectionTool() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor =
|
||||
UastVisitorAdapter(TestFailedVisitor(holder, isOnTheFly), true)
|
||||
|
||||
class TestFailedVisitor(private val holder: ProblemsHolder, private val isOnTheFly: Boolean) : AbstractUastNonRecursiveVisitor() {
|
||||
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression): Boolean {
|
||||
if (node.lang is JavaLanguage) return false // java doesn't pass in the UCallExpression selector as a standalone element
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visitCallExpression(node: UCallExpression): Boolean {
|
||||
val sourcePsi = node.sourcePsi ?: return true
|
||||
val state = TestFailedLineManager.getInstance(holder.project).getFailedLineState(node) ?: return true
|
||||
if (state.magnitude <= TestStateInfo.Magnitude.IGNORED_INDEX.value) return true
|
||||
val fixes = arrayOf<LocalQuickFix>(
|
||||
DebugActionFix(sourcePsi, state.topStacktraceLine),
|
||||
RunActionFix(sourcePsi, DefaultRunExecutor.EXECUTOR_ID)
|
||||
)
|
||||
val identifier = node.methodIdentifier.sourcePsiElement ?: return true
|
||||
val descriptor = InspectionManager.getInstance(holder.project).createProblemDescriptor(
|
||||
identifier, state.errorMessage, isOnTheFly, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING
|
||||
).apply { setTextAttributes(CodeInsightColors.RUNTIME_ERROR) }
|
||||
holder.registerProblem(descriptor)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private class DebugActionFix(
|
||||
element: PsiElement,
|
||||
private val myTopStacktraceLine: String
|
||||
) : RunActionFix(element, DefaultDebugExecutor.EXECUTOR_ID) {
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val line = StackTraceLine(project, myTopStacktraceLine)
|
||||
line.getMethodLocation(project)?.let { location ->
|
||||
PsiDocumentManager.getInstance(project).getDocument(location.psiElement.containingFile)?.let { document ->
|
||||
DebuggerManagerEx.getInstanceEx(project).breakpointManager.addLineBreakpoint(document, line.lineNumber)
|
||||
}
|
||||
}
|
||||
super.applyFix(project, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private open class RunActionFix(element: PsiElement, executorId: String) : LocalQuickFix, Iconable {
|
||||
private val myExecutor = ExecutorRegistry.getInstance().getExecutorById(executorId) ?: throw IllegalStateException(
|
||||
"Could not create action because executor $executorId was not found.")
|
||||
|
||||
private val myConfiguration: RunnerAndConfigurationSettings =
|
||||
ConfigurationContext(element).configuration ?: throw IllegalStateException(
|
||||
"Could not create action because configuration context was not found for element $element.")
|
||||
|
||||
override fun getFamilyName(): @Nls(capitalization = Nls.Capitalization.Sentence) String =
|
||||
UIUtil.removeMnemonic(myExecutor.getStartActionText(ProgramRunnerUtil.shortenName(myConfiguration.name, 0)))
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
ExecutionUtil.runConfiguration(myConfiguration, myExecutor)
|
||||
}
|
||||
|
||||
override fun getIcon(flags: Int): Icon = myExecutor.icon
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2000-2021 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.codeInspection
|
||||
|
||||
import com.intellij.codeInsight.TestFrameworks
|
||||
import com.intellij.execution.TestStateStorage
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.SmartPointerManager
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
import com.intellij.psi.util.ClassUtil
|
||||
import com.intellij.util.containers.FactoryMap
|
||||
import org.jetbrains.uast.*
|
||||
|
||||
class TestFailedLineManager(project: Project) : FileEditorManagerListener {
|
||||
private val testStorage = TestStateStorage.getInstance(project)
|
||||
|
||||
private val cache = FactoryMap.create<VirtualFile, MutableMap<String, TestInfo>> { hashMapOf() }
|
||||
|
||||
init {
|
||||
project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this)
|
||||
}
|
||||
|
||||
class TestInfo(var record: TestStateStorage.Record) {
|
||||
var pointer: SmartPsiElementPointer<PsiElement>? = null
|
||||
}
|
||||
|
||||
private fun getTestInfo(method: UMethod): TestInfo? {
|
||||
val containingClass = method.getContainingUClass() ?: return null
|
||||
val javaClazz = containingClass.javaPsi
|
||||
val framework = TestFrameworks.detectFramework(javaClazz) ?: return null
|
||||
if (!framework.isTestMethod(method.javaPsi, false)) return null
|
||||
val url = "java:test://" + ClassUtil.getJVMClassName(javaClazz) + "/" + method.name
|
||||
val state = testStorage.getState(url) ?: return null
|
||||
val vFile = method.getContainingUFile()?.sourcePsi?.virtualFile ?: return null
|
||||
val infoInFile = cache[vFile] ?: return null
|
||||
var info = infoInFile[url]
|
||||
if (info == null || state.date != info.record.date) {
|
||||
info = TestInfo(state)
|
||||
infoInFile[url] = info
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
fun getFailedLineState(call: UCallExpression): TestStateStorage.Record? {
|
||||
val containingMethod = call.getContainingUMethod() ?: return null
|
||||
val callSourcePsi = call.sourcePsi ?: return null
|
||||
val file = call.getContainingUFile()?.sourcePsi ?: return null
|
||||
val info = getTestInfo(containingMethod) ?: return null
|
||||
val document = PsiDocumentManager.getInstance(callSourcePsi.project).getDocument(file) ?: return null
|
||||
info.pointer?.element?.let { pointerElem ->
|
||||
if (callSourcePsi == pointerElem) {
|
||||
info.record.failedLine = document.getLineNumber(callSourcePsi.textOffset) + 1
|
||||
return info.record
|
||||
}
|
||||
}
|
||||
if (info.record.failedLine == -1 || StringUtil.isEmpty(info.record.failedMethod)) return null
|
||||
if (info.record.failedLine != document.getLineNumber(callSourcePsi.textOffset) + 1) return null
|
||||
if (info.record.failedMethod != call.methodName) return null
|
||||
info.pointer = SmartPointerManager.createPointer(callSourcePsi)
|
||||
return info.record
|
||||
}
|
||||
|
||||
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
|
||||
cache.remove(file)?.forEach { (s: String, info: TestInfo) -> testStorage.writeState(s, info.record) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): TestFailedLineManager = project.getService(TestFailedLineManager::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
public class MainTest extends junit.framework.TestCase {
|
||||
public void testFoo() {
|
||||
<warning descr="junit.framework.AssertionFailedError:">assertEquals</warning>();
|
||||
assertEquals();
|
||||
}
|
||||
|
||||
public void assertEquals() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
public class QualifiedTest extends junit.framework.TestCase {
|
||||
public void testFoo() {
|
||||
QualifiedTest.<warning descr="junit.framework.AssertionFailedError:">assertEquals</warning>();
|
||||
}
|
||||
|
||||
public static void assertEquals() {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.intellij.codeInspection.tests.java
|
||||
|
||||
import com.intellij.codeInspection.tests.TestFailedLineInspectionTestBase
|
||||
import com.intellij.jvm.analysis.JvmAnalysisTestsUtil
|
||||
|
||||
class JavaTestFailedLineInspectionTest : TestFailedLineInspectionTestBase() {
|
||||
override fun getTestDataPath() = JvmAnalysisTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + "/codeInspection/testfailedline"
|
||||
|
||||
fun testMainTest() {
|
||||
doTest(
|
||||
fileName = "MainTest",
|
||||
fileExt = "java",
|
||||
methodName = "testFoo",
|
||||
errorLn = 3,
|
||||
errorMessage = "junit.framework.AssertionFailedError:"
|
||||
)
|
||||
}
|
||||
|
||||
fun testQualifiedTest() {
|
||||
doTest(
|
||||
fileName = "QualifiedTest",
|
||||
fileExt = "java",
|
||||
methodName = "testFoo",
|
||||
errorLn = 3,
|
||||
errorMessage = "junit.framework.AssertionFailedError:"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
class MainTest : junit.framework.TestCase() {
|
||||
fun testFoo() {
|
||||
<warning descr="junit.framework.AssertionFailedError:">assertEquals</warning>()
|
||||
assertEquals()
|
||||
}
|
||||
|
||||
fun assertEquals() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class QualifiedTest : junit.framework.TestCase() {
|
||||
fun testFoo() {
|
||||
Assertions.<warning descr="junit.framework.AssertionFailedError:">assertEquals</warning>()
|
||||
}
|
||||
|
||||
object Assertions {
|
||||
fun assertEquals() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.intellij.codeInspection
|
||||
|
||||
import com.intellij.codeInspection.tests.TestFailedLineInspectionTestBase
|
||||
import com.intellij.jvm.analysis.JvmAnalysisKtTestsUtil
|
||||
|
||||
class KotlinTestFailedLineInspectionTest : TestFailedLineInspectionTestBase() {
|
||||
override fun getBasePath() = JvmAnalysisKtTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + "/codeInspection/testfailedline"
|
||||
|
||||
fun testMainTest() {
|
||||
doTest(
|
||||
fileName = "MainTest",
|
||||
fileExt = "kt",
|
||||
methodName = "testFoo",
|
||||
errorLn = 3,
|
||||
errorMessage = "junit.framework.AssertionFailedError:"
|
||||
)
|
||||
}
|
||||
|
||||
fun testQualifiedTest() {
|
||||
doTest(
|
||||
fileName = "QualifiedTest",
|
||||
fileExt = "kt",
|
||||
methodName = "testFoo",
|
||||
errorLn = 3,
|
||||
errorMessage = "junit.framework.AssertionFailedError:"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="intellij.jvm.analysis.impl" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.java.testFramework" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.platform.smRunner" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.intellij.codeInspection.tests
|
||||
|
||||
import com.intellij.codeInspection.TestFailedLineInspection
|
||||
import com.intellij.execution.TestStateStorage
|
||||
import com.intellij.execution.testframework.JavaTestLocator
|
||||
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
|
||||
import com.intellij.execution.testframework.sm.runner.ui.TestStackTraceParser
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.editor.markup.EffectType
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
|
||||
import java.util.*
|
||||
|
||||
abstract class TestFailedLineInspectionTestBase : JavaCodeInsightFixtureTestCase() {
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.enableInspections(inspection)
|
||||
myFixture.addClass("package junit.framework; public class TestCase {}")
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
try {
|
||||
myFixture.disableInspections(inspection)
|
||||
}
|
||||
finally {
|
||||
super.tearDown()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doTest(fileName: String, fileExt: String, methodName: String, errorLn: Int, errorMessage: String) {
|
||||
val url = "java:test://$fileName/$methodName"
|
||||
val pair = TestStackTraceParser(url, """
|
||||
|${'\t'}at junit.framework.Assert.fail(Assert.java:47)
|
||||
|${'\t'}at $fileName.assertEquals(Assert.java:207)
|
||||
|${'\t'}at $fileName.$methodName($fileName.$fileExt:$errorLn)""".trimMargin(), errorMessage, JavaTestLocator.INSTANCE, project)
|
||||
assertEquals(errorLn, pair.failedLine)
|
||||
assertEquals("assertEquals", pair.failedMethodName)
|
||||
val record = TestStateStorage.Record(
|
||||
TestStateInfo.Magnitude.FAILED_INDEX.value, Date(), 0,
|
||||
pair.failedLine, pair.failedMethodName, pair.errorMessage, pair.topLocationLine
|
||||
)
|
||||
TestStateStorage.getInstance(project).writeState(url, record)
|
||||
myFixture.testHighlighting("$fileName.$fileExt")
|
||||
val infos = myFixture.doHighlighting(HighlightSeverity.WARNING)
|
||||
assertEquals(1, infos.size)
|
||||
val attributes = infos[0].forcedTextAttributes
|
||||
assertNotNull(attributes)
|
||||
assertEquals(EffectType.BOLD_DOTTED_LINE, attributes.effectType)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val inspection = TestFailedLineInspection()
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class TestStateStorage implements Disposable {
|
||||
public final long configurationHash;
|
||||
public final Date date;
|
||||
public int failedLine;
|
||||
public final String failedMethod;
|
||||
public String failedMethod;
|
||||
public final @NlsSafe String errorMessage;
|
||||
public final String topStacktraceLine;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user