| /* |
| * 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.base.lang.buildfile.validation; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase; |
| import com.google.idea.blaze.base.lang.buildfile.language.semantics.AttributeDefinition; |
| import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec; |
| import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider; |
| import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition; |
| import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile; |
| import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression; |
| import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils; |
| import com.google.idea.blaze.base.model.primitives.WorkspacePath; |
| import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Discriminator; |
| import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl; |
| import com.intellij.lang.annotation.Annotation; |
| import com.intellij.lang.annotation.AnnotationHolder; |
| import com.intellij.lang.annotation.AnnotationSession; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiFile; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| import javax.annotation.Nullable; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Integration tests for {@link BuiltInRuleAnnotator}. */ |
| @RunWith(JUnit4.class) |
| public class BuiltInRuleAnnotatorTest extends BuildFileIntegrationTestCase { |
| |
| private static final AttributeDefinition NAME_ATTRIBUTE = |
| new AttributeDefinition("name", Discriminator.STRING, true, null, null); |
| |
| private static final AttributeDefinition SRCS_ATTRIBUTE = |
| new AttributeDefinition("srcs", Discriminator.LABEL_LIST, false, null, null); |
| |
| private static final AttributeDefinition NEVERLINK_ATTRIBUTE = |
| new AttributeDefinition("neverlink", Discriminator.BOOLEAN, false, null, null); |
| |
| private static final AttributeDefinition VALUES_ATTRIBUTE = |
| new AttributeDefinition("values", Discriminator.STRING_DICT, true, null, null); |
| |
| private static final RuleDefinition JAVA_TEST = |
| new RuleDefinition( |
| "java_test", |
| ImmutableMap.of( |
| "name", NAME_ATTRIBUTE, "srcs", SRCS_ATTRIBUTE, "neverlink", NEVERLINK_ATTRIBUTE), |
| null); |
| |
| private static final RuleDefinition CONFIG_SETTING = |
| new RuleDefinition( |
| "config_setting", |
| ImmutableMap.of("name", NAME_ATTRIBUTE, "values", VALUES_ATTRIBUTE), |
| null); |
| |
| private MockBuildLanguageSpecProvider specProvider; |
| |
| @Before |
| public final void before() { |
| specProvider = new MockBuildLanguageSpecProvider(); |
| registerApplicationService(BuildLanguageSpecProvider.class, specProvider); |
| } |
| |
| @Test |
| public void testUnrecognizedRuleTypeIgnored() { |
| setRules("java_library", "java_binary"); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), "java_import(name = 'import',)"); |
| |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testNoErrorsForValidStandardRule() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = 'import',", |
| " srcs = ['src'],", |
| " neverlink = 0,", |
| ")"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testNoErrorsForValidStringDict() { |
| specProvider.setRules(ImmutableMap.of(CONFIG_SETTING.name, CONFIG_SETTING)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "config_setting(", |
| " name = 'setting',", |
| " values = {'key1', 'value1', 'key2', 'value2'},", |
| ")"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testErrorForInvalidDict() { |
| specProvider.setRules(ImmutableMap.of(CONFIG_SETTING.name, CONFIG_SETTING)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "config_setting(", |
| " name = 'setting',", |
| " values = 1,", |
| ")"); |
| assertHasError( |
| file, "Invalid value for attribute 'values'. Expected a value of type 'STRING_DICT'"); |
| } |
| |
| @Test |
| public void testGlobTreatedAsList() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = 'import',", |
| " srcs = glob(['src']),", |
| " neverlink = 0,", |
| ")"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testMissingMandatoryAttributeError() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " srcs = ['src'],", |
| " neverlink = 0,", |
| ")"); |
| assertHasError(file, "Target missing required attribute(s): name"); |
| } |
| |
| @Test |
| public void testInvalidAttributeTypeError() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = 'import',", |
| " srcs = 'src',", |
| " neverlink = 0,", |
| ")"); |
| assertHasError( |
| file, |
| String.format( |
| "Invalid value for attribute 'srcs'. Expected a value of type '%s'", |
| Discriminator.LABEL_LIST)); |
| } |
| |
| @Test |
| public void testInvalidAttributeTypeError2() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = ['import'],", |
| " srcs = ['src'],", |
| " neverlink = 0,", |
| ")"); |
| assertHasError( |
| file, |
| String.format( |
| "Invalid value for attribute 'name'. Expected a value of type '%s'", |
| Discriminator.STRING)); |
| } |
| |
| @Test |
| public void testNoErrorForIfStatement() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = 'import',", |
| " srcs = ['src'],", |
| " neverlink = if test : a else b", |
| ")"); |
| // we don't know what the if statement evaluates to |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testUnknownReferenceIgnored() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = 'import',", |
| " srcs = ['src'],", |
| " neverlink = ref,", |
| ")"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testKnownIncorrectReference() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "ref = []", |
| "java_test(", |
| " name = 'import',", |
| " srcs = ['src'],", |
| " neverlink = ref,", |
| ")"); |
| assertHasError( |
| file, |
| String.format( |
| "Invalid value for attribute 'neverlink'. Expected a value of type '%s'", |
| Discriminator.BOOLEAN)); |
| } |
| |
| @Test |
| public void testValidValueInsideParenthesizedExpression() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "java_test(", |
| " name = ('import' + '_suffix'),", |
| ")"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testInvalidValueInsideParenthesizedExpression() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), "java_test(", " name = (1),", ")"); |
| assertHasError( |
| file, |
| String.format( |
| "Invalid value for attribute 'name'. Expected a value of type '%s'", |
| Discriminator.STRING)); |
| } |
| |
| @Test |
| public void testUnresolvedValueInsideParenthesizedExpression() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), "java_test(", " name = (ref),", ")"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testNoMissingMandatoryArgErrorIfKwargsPresent() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/google/BUILD"), |
| "def java_test(srcs=[], **kwargs):", |
| " native.java_test(srcs = srcs, **kwargs)"); |
| assertNoErrors(file); |
| } |
| |
| @Test |
| public void testNoMissingAttributeErrorsForOverriddenBuiltIns() { |
| specProvider.setRules(ImmutableMap.of(JAVA_TEST.name, JAVA_TEST)); |
| BuildFile file = |
| createBuildFile( |
| new WorkspacePath("java/com/foo/BUILD"), |
| "java_test(name = 'test', srcs = [':src'], extra_arg = [])"); |
| assertHasError(file, "Unrecognized attribute 'extra_arg' for rule type 'java_test'"); |
| |
| file = |
| createBuildFile( |
| new WorkspacePath("java/com/bar/BUILD"), |
| "def java_test(srcs=[], **kwargs, extra_arg=[]):", |
| " native.java_test(srcs = srcs, **kwargs)", |
| "java_test(name = 'test', srcs = [':src'], extra_arg = [])"); |
| assertNoErrors(file); |
| } |
| |
| private void setRules(String... ruleNames) { |
| ImmutableMap.Builder<String, RuleDefinition> rules = ImmutableMap.builder(); |
| for (String name : ruleNames) { |
| rules.put(name, new RuleDefinition(name, ImmutableMap.of(), null)); |
| } |
| specProvider.setRules(rules.build()); |
| } |
| |
| private static class MockBuildLanguageSpecProvider implements BuildLanguageSpecProvider { |
| |
| BuildLanguageSpec languageSpec = new BuildLanguageSpec(ImmutableMap.of()); |
| |
| void setRules(ImmutableMap<String, RuleDefinition> rules) { |
| languageSpec = new BuildLanguageSpec(rules); |
| } |
| |
| @Nullable |
| @Override |
| public BuildLanguageSpec getLanguageSpec(Project project) { |
| return languageSpec; |
| } |
| } |
| |
| private void assertNoErrors(BuildFile file) { |
| assertThat(validateFile(file)).isEmpty(); |
| } |
| |
| private void assertHasError(BuildFile file, String error) { |
| assertHasError(validateFile(file), error); |
| } |
| |
| private static void assertHasError(List<Annotation> annotations, String error) { |
| List<String> messages = |
| annotations.stream().map(Annotation::getMessage).collect(Collectors.toList()); |
| |
| assertThat(messages).contains(error); |
| } |
| |
| private List<Annotation> validateFile(BuildFile file) { |
| BuiltInRuleAnnotator annotator = createAnnotator(file); |
| PsiUtils.findAllChildrenOfClassRecursive(file, FuncallExpression.class) |
| .forEach(annotator::visitFuncallExpression); |
| return annotationHolder; |
| } |
| |
| private BuiltInRuleAnnotator createAnnotator(PsiFile file) { |
| annotationHolder = new AnnotationHolderImpl(new AnnotationSession(file)); |
| return new BuiltInRuleAnnotator() { |
| @Override |
| protected AnnotationHolder getHolder() { |
| return annotationHolder; |
| } |
| }; |
| } |
| |
| private AnnotationHolderImpl annotationHolder = null; |
| } |