blob: 9133e7bcf4cbfd950ebdf730573a085f4e3e8768 [file] [log] [blame]
/*
* Copyright 2017 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.scala.run.producers;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.execution.TestStateStorage;
import com.intellij.execution.lineMarker.ExecutorAction;
import com.intellij.execution.lineMarker.RunLineMarkerContributor;
import com.intellij.execution.testframework.TestIconMapper;
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testIntegration.TestFramework;
import com.intellij.testIntegration.TestRunLineMarkerProvider;
import java.util.Arrays;
import java.util.function.Function;
import javax.annotation.Nullable;
import javax.swing.Icon;
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScInfixExpr;
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScReferenceExpression;
import org.jetbrains.plugins.scala.lang.psi.api.statements.ScFunctionDefinition;
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScClass;
import org.jetbrains.plugins.scala.testingSupport.test.ScalaTestRunLineMarkerProvider;
/**
* Generates run/debug gutter icons for scala_test, and scala_junit_tests.
*
* <p>{@link ScalaTestRunLineMarkerProvider} exists in the scala plugin, but it does not currently
* try to handle scalatest and specs2 at all. For JUnit tests, it has a bug that causes it to not
* generate icons for a test without run state (i.e., newly written tests).
*/
public class BlazeScalaTestRunLineMarkerContributor extends ScalaTestRunLineMarkerProvider {
@Nullable
@Override
public Info getInfo(PsiElement e) {
if (isIdentifier(e)) {
PsiElement element = e.getParent();
if (element instanceof ScClass) {
return getInfo((ScClass) element, null, super.getInfo(e));
} else if (element instanceof ScFunctionDefinition) {
ScClass testClass = PsiTreeUtil.getParentOfType(element, ScClass.class);
if (testClass != null) {
return getInfo(testClass, element, super.getInfo(e));
}
} else if (element instanceof ScReferenceExpression) {
// TODO: handle infix expressions. E.g., "foo" should "bar" in { baz }
return null;
}
}
return null;
}
@Nullable
private static Info getInfo(ScClass testClass, @Nullable PsiElement testCase, Info toReplace) {
TestFramework framework = TestFrameworks.detectFramework(testClass);
if (framework == null) {
return null;
}
boolean isClass = testCase == null;
String url;
if (isClass) {
if (!framework.isTestClass(testClass)) {
return null;
}
url = "java:suite://" + testClass.getQualifiedName();
} else if (testCase instanceof ScFunctionDefinition) {
ScFunctionDefinition method = (ScFunctionDefinition) testCase;
if (!framework.isTestMethod(method)) {
return null;
}
url = "java:test://" + testClass.getQualifiedName() + "." + method.getName();
} else if (testCase instanceof ScInfixExpr) {
// TODO: handle this case.
return null;
} else {
return null;
}
return getInfo(url, testClass.getProject(), isClass, toReplace);
}
private static Info getInfo(String url, Project project, boolean isClass, Info toReplace) {
Icon icon = getTestStateIcon(url, project, isClass);
return new ReplacementInfo(
icon,
ExecutorAction.getActions(1),
RunLineMarkerContributor.RUN_TEST_TOOLTIP_PROVIDER,
toReplace);
}
/** Copied from {@link TestRunLineMarkerProvider#getTestStateIcon(String, Project, boolean)} */
private static Icon getTestStateIcon(String url, Project project, boolean isClass) {
TestStateStorage.Record state = TestStateStorage.getInstance(project).getState(url);
if (state != null) {
TestStateInfo.Magnitude magnitude = TestIconMapper.getMagnitude(state.magnitude);
if (magnitude != null) {
switch (magnitude) {
case ERROR_INDEX:
case FAILED_INDEX:
return AllIcons.RunConfigurations.TestState.Red2;
case PASSED_INDEX:
case COMPLETE_INDEX:
return AllIcons.RunConfigurations.TestState.Green2;
default:
}
}
}
return isClass
? AllIcons.RunConfigurations.TestState.Run_run
: AllIcons.RunConfigurations.TestState.Run;
}
private static class ReplacementInfo extends Info {
private final Info toReplace;
ReplacementInfo(
Icon icon,
AnAction[] actions,
Function<PsiElement, String> tooltipProvider,
Info toReplace) {
super(icon, actions, tooltipProvider);
this.toReplace = toReplace;
}
@Override
public boolean shouldReplace(Info other) {
return toReplace != null && Arrays.equals(toReplace.actions, other.actions);
}
}
}