| /* |
| * 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.python.run.producers; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.idea.blaze.base.command.BlazeCommandName; |
| import com.google.idea.blaze.base.command.BlazeFlags; |
| import com.google.idea.blaze.base.model.primitives.Label; |
| import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; |
| import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; |
| import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; |
| import com.google.idea.blaze.base.run.TestTargetHeuristic; |
| import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer; |
| import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; |
| import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; |
| import com.google.idea.blaze.python.run.PyTestUtils; |
| import com.intellij.execution.Location; |
| import com.intellij.execution.actions.ConfigurationContext; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.jetbrains.python.psi.PyClass; |
| import com.jetbrains.python.psi.PyFile; |
| import com.jetbrains.python.psi.PyFunction; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| |
| /** Producer for run configurations related to python test classes in Blaze. */ |
| public class BlazePyTestConfigurationProducer |
| extends BlazeRunConfigurationProducer<BlazeCommandRunConfiguration> { |
| |
| public BlazePyTestConfigurationProducer() { |
| super(BlazeCommandRunConfigurationType.getInstance()); |
| } |
| |
| private static class TestLocation { |
| @Nullable private final PyClass testClass; |
| @Nullable private final PyFunction testFunction; |
| |
| private TestLocation(@Nullable PyClass testClass, @Nullable PyFunction testFunction) { |
| this.testClass = testClass; |
| this.testFunction = testFunction; |
| } |
| |
| @Nullable |
| private String testFilter() { |
| if (testClass == null) { |
| return null; |
| } |
| return testFunction == null |
| ? testClass.getName() |
| : testClass.getName() + "." + testFunction.getName(); |
| } |
| |
| @Nullable |
| PsiElement sourceElement(PsiFile file) { |
| if (testFunction != null) { |
| return testFunction; |
| } |
| return testClass != null ? testClass : file; |
| } |
| } |
| |
| /** |
| * The single selected {@link PsiElement}. Returns null if we're in a SM runner tree UI context |
| * (handled by a different producer). |
| */ |
| @Nullable |
| private static PsiElement selectedPsiElement(ConfigurationContext context) { |
| List<Location<?>> selectedTestUiElements = |
| SmRunnerUtils.getSelectedSmRunnerTreeElements(context); |
| if (!selectedTestUiElements.isEmpty()) { |
| return null; |
| } |
| Location<?> location = context.getLocation(); |
| return location != null ? location.getPsiElement() : null; |
| } |
| |
| @Override |
| protected boolean doSetupConfigFromContext( |
| BlazeCommandRunConfiguration configuration, |
| ConfigurationContext context, |
| Ref<PsiElement> sourceElement) { |
| |
| PsiElement element = selectedPsiElement(context); |
| if (element == null) { |
| return false; |
| } |
| PsiFile file = element.getContainingFile(); |
| if (!(file instanceof PyFile) || !PyTestUtils.isTestFile((PyFile) file)) { |
| return false; |
| } |
| Label testTarget = TestTargetHeuristic.testTargetForPsiElement(element); |
| if (testTarget == null) { |
| return false; |
| } |
| configuration.setTarget(testTarget); |
| |
| TestLocation testLocation = testLocation(element); |
| sourceElement.set(testLocation.sourceElement(file)); |
| BlazeCommandRunConfigurationCommonState handlerState = |
| configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class); |
| if (handlerState == null) { |
| return false; |
| } |
| handlerState.getCommandState().setCommand(BlazeCommandName.TEST); |
| |
| ImmutableList.Builder<String> flags = ImmutableList.builder(); |
| String filter = testLocation.testFilter(); |
| if (filter != null) { |
| flags.add(BlazeFlags.TEST_FILTER + "=" + filter); |
| } |
| // remove conflicting flags from initial configuration |
| List<String> oldFlags = new ArrayList<>(handlerState.getBlazeFlagsState().getRawFlags()); |
| oldFlags.removeIf((flag) -> flag.startsWith(BlazeFlags.TEST_FILTER)); |
| flags.addAll(oldFlags); |
| handlerState.getBlazeFlagsState().setRawFlags(flags.build()); |
| |
| BlazeConfigurationNameBuilder nameBuilder = new BlazeConfigurationNameBuilder(configuration); |
| nameBuilder.setTargetString( |
| filter != null |
| ? String.format("%s (%s)", filter, testTarget.toString()) |
| : testTarget.toString()); |
| configuration.setName(nameBuilder.build()); |
| configuration.setNameChangedByUser(true); // don't revert to generated name |
| return true; |
| } |
| |
| @Override |
| protected boolean doIsConfigFromContext( |
| BlazeCommandRunConfiguration configuration, ConfigurationContext context) { |
| |
| BlazeCommandRunConfigurationCommonState handlerState = |
| configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class); |
| if (handlerState == null |
| || !BlazeCommandName.TEST.equals(handlerState.getCommandState().getCommand())) { |
| return false; |
| } |
| |
| PsiElement element = selectedPsiElement(context); |
| if (element == null) { |
| return false; |
| } |
| if (!(element.getContainingFile() instanceof PyFile)) { |
| return false; |
| } |
| Label testTarget = TestTargetHeuristic.testTargetForPsiElement(element); |
| if (testTarget == null || !testTarget.equals(configuration.getTarget())) { |
| return false; |
| } |
| String filter = testLocation(element).testFilter(); |
| |
| String filterFlag = handlerState.getTestFilterFlag(); |
| return (filterFlag == null && filter == null) |
| || Objects.equals(filterFlag, BlazeFlags.TEST_FILTER + "=" + filter); |
| } |
| |
| private static TestLocation testLocation(PsiElement element) { |
| PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); |
| if (pyClass == null || !PyTestUtils.isTestClass(pyClass)) { |
| return new TestLocation(null, null); |
| } |
| PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); |
| if (pyFunction != null && PyTestUtils.isTestFunction(pyFunction)) { |
| return new TestLocation(pyClass, pyFunction); |
| } |
| return new TestLocation(pyClass, null); |
| } |
| } |