blob: baf2d39fcdd40d271c643d26970783e29482d5b5 [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.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;
}