| // Copyright 2015 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.devtools.build.lib.packages; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; |
| import static com.google.devtools.build.lib.syntax.Type.INTEGER; |
| import static com.google.devtools.build.lib.syntax.Type.STRING; |
| import static com.google.devtools.build.lib.syntax.Type.STRING_LIST; |
| import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; |
| |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.HostTransition; |
| import com.google.devtools.build.lib.analysis.config.TransitionFactories; |
| import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory; |
| import com.google.devtools.build.lib.analysis.util.TestAspects; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassNamePredicate; |
| import com.google.devtools.build.lib.syntax.Type; |
| import com.google.devtools.build.lib.testutil.FakeAttributeMapper; |
| import com.google.devtools.build.lib.util.FileType; |
| import com.google.devtools.build.lib.util.FileTypeSet; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests of Attribute code. */ |
| @RunWith(JUnit4.class) |
| public class AttributeTest { |
| |
| private void assertDefaultValue(Object expected, Attribute attr) { |
| assertThat(attr.getDefaultValue(null)).isEqualTo(expected); |
| } |
| |
| private void assertType(Type<?> expectedType, Attribute attr) { |
| assertThat(attr.getType()).isEqualTo(expectedType); |
| } |
| |
| @Test |
| public void testBasics() throws Exception { |
| Attribute attr = attr("foo", Type.INTEGER).mandatory().value(3).build(); |
| assertThat(attr.getName()).isEqualTo("foo"); |
| assertThat(attr.getDefaultValue(null)).isEqualTo(3); |
| assertThat(attr.getType()).isEqualTo(Type.INTEGER); |
| assertThat(attr.isMandatory()).isTrue(); |
| assertThat(attr.isDocumented()).isTrue(); |
| attr = attr("$foo", Type.INTEGER).build(); |
| assertThat(attr.isDocumented()).isFalse(); |
| } |
| |
| @Test |
| public void testNonEmptyReqiresListType() throws Exception { |
| NullPointerException e = |
| assertThrows( |
| NullPointerException.class, |
| () -> attr("foo", Type.INTEGER).nonEmpty().value(3).build()); |
| assertThat(e).hasMessageThat().isEqualTo("attribute 'foo' must be a list"); |
| } |
| |
| @Test |
| public void testNonEmpty() throws Exception { |
| Attribute attr = attr("foo", BuildType.LABEL_LIST).nonEmpty().legacyAllowAnyFileType().build(); |
| assertThat(attr.getName()).isEqualTo("foo"); |
| assertThat(attr.getType()).isEqualTo(BuildType.LABEL_LIST); |
| assertThat(attr.isNonEmpty()).isTrue(); |
| } |
| |
| @Test |
| public void testSingleArtifactReqiresLabelType() throws Exception { |
| IllegalStateException e = |
| assertThrows( |
| IllegalStateException.class, |
| () -> attr("foo", Type.INTEGER).singleArtifact().value(3).build()); |
| assertThat(e).hasMessageThat().isEqualTo("attribute 'foo' must be a label-valued type"); |
| } |
| |
| @Test |
| public void testDoublePropertySet() { |
| Attribute.Builder<String> builder = |
| attr("x", STRING) |
| .mandatory() |
| .cfg(HostTransition.createFactory()) |
| .undocumented("") |
| .value("y"); |
| assertThrows(IllegalStateException.class, () -> builder.mandatory()); |
| assertThrows(IllegalStateException.class, () -> builder.cfg(HostTransition.createFactory())); |
| assertThrows(IllegalStateException.class, () -> builder.undocumented("")); |
| assertThrows(IllegalStateException.class, () -> builder.value("z")); |
| |
| Attribute.Builder<String> builder2 = attr("$x", STRING); |
| assertThrows(IllegalStateException.class, () -> builder2.undocumented("")); |
| } |
| |
| /** |
| * Tests the "convenience factories" (string, label, etc) for default |
| * values. |
| */ |
| @Test |
| public void testConvenienceFactoriesDefaultValues() throws Exception { |
| assertDefaultValue(0, |
| attr("x", INTEGER).build()); |
| assertDefaultValue(42, |
| attr("x", INTEGER).value(42).build()); |
| |
| assertDefaultValue("", |
| attr("x", STRING).build()); |
| assertDefaultValue("foo", |
| attr("x", STRING).value("foo").build()); |
| |
| Label label = Label.parseAbsolute("//foo:bar", ImmutableMap.of()); |
| assertDefaultValue(null, |
| attr("x", LABEL).legacyAllowAnyFileType().build()); |
| assertDefaultValue(label, |
| attr("x", LABEL).legacyAllowAnyFileType().value(label).build()); |
| |
| List<String> slist = Arrays.asList("foo", "bar"); |
| assertDefaultValue(Collections.emptyList(), |
| attr("x", STRING_LIST).build()); |
| assertDefaultValue(slist, |
| attr("x", STRING_LIST).value(slist).build()); |
| |
| List<Label> llist = |
| Arrays.asList( |
| Label.parseAbsolute("//foo:bar", ImmutableMap.of()), |
| Label.parseAbsolute("//foo:wiz", ImmutableMap.of())); |
| assertDefaultValue(Collections.emptyList(), |
| attr("x", LABEL_LIST).legacyAllowAnyFileType().build()); |
| assertDefaultValue(llist, |
| attr("x", LABEL_LIST).legacyAllowAnyFileType().value(llist).build()); |
| } |
| |
| /** |
| * Tests the "convenience factories" (string, label, etc) for types. |
| */ |
| @Test |
| public void testConvenienceFactoriesTypes() throws Exception { |
| assertType(INTEGER, |
| attr("x", INTEGER).build()); |
| assertType(INTEGER, |
| attr("x", INTEGER).value(42).build()); |
| |
| assertType(STRING, |
| attr("x", STRING).build()); |
| assertType(STRING, |
| attr("x", STRING).value("foo").build()); |
| |
| Label label = Label.parseAbsolute("//foo:bar", ImmutableMap.of()); |
| assertType(LABEL, |
| attr("x", LABEL).legacyAllowAnyFileType().build()); |
| assertType(LABEL, |
| attr("x", LABEL).legacyAllowAnyFileType().value(label).build()); |
| |
| List<String> slist = Arrays.asList("foo", "bar"); |
| assertType(STRING_LIST, |
| attr("x", STRING_LIST).build()); |
| assertType(STRING_LIST, |
| attr("x", STRING_LIST).value(slist).build()); |
| |
| List<Label> llist = |
| Arrays.asList( |
| Label.parseAbsolute("//foo:bar", ImmutableMap.of()), |
| Label.parseAbsolute("//foo:wiz", ImmutableMap.of())); |
| assertType(LABEL_LIST, |
| attr("x", LABEL_LIST).legacyAllowAnyFileType().build()); |
| assertType(LABEL_LIST, |
| attr("x", LABEL_LIST).legacyAllowAnyFileType().value(llist).build()); |
| } |
| |
| @Test |
| public void testCloneBuilder() { |
| FileTypeSet txtFiles = FileTypeSet.of(FileType.of("txt")); |
| RuleClassNamePredicate ruleClasses = RuleClassNamePredicate.only("mock_rule"); |
| |
| Attribute parentAttr = |
| attr("x", LABEL_LIST) |
| .allowedFileTypes(txtFiles) |
| .mandatory() |
| .aspect(TestAspects.SIMPLE_ASPECT) |
| .build(); |
| |
| { |
| Attribute childAttr1 = parentAttr.cloneBuilder().build(); |
| assertThat(childAttr1.getName()).isEqualTo("x"); |
| assertThat(childAttr1.getAllowedFileTypesPredicate()).isEqualTo(txtFiles); |
| assertThat(childAttr1.getAllowedRuleClassesPredicate()).isEqualTo(Predicates.alwaysTrue()); |
| assertThat(childAttr1.isMandatory()).isTrue(); |
| assertThat(childAttr1.isNonEmpty()).isFalse(); |
| assertThat(childAttr1.getAspects(/* rule= */ null)).hasSize(1); |
| } |
| |
| { |
| Attribute childAttr2 = |
| parentAttr |
| .cloneBuilder() |
| .nonEmpty() |
| .allowedRuleClasses(ruleClasses) |
| .aspect(TestAspects.ERROR_ASPECT) |
| .build(); |
| assertThat(childAttr2.getName()).isEqualTo("x"); |
| assertThat(childAttr2.getAllowedFileTypesPredicate()).isEqualTo(txtFiles); |
| assertThat(childAttr2.getAllowedRuleClassesPredicate()) |
| .isEqualTo(ruleClasses.asPredicateOfRuleClass()); |
| assertThat(childAttr2.isMandatory()).isTrue(); |
| assertThat(childAttr2.isNonEmpty()).isTrue(); |
| assertThat(childAttr2.getAspects(/* rule= */ null)).hasSize(2); |
| } |
| |
| // Check if the parent attribute is unchanged |
| assertThat(parentAttr.isNonEmpty()).isFalse(); |
| assertThat(parentAttr.getAllowedRuleClassesPredicate()).isEqualTo(Predicates.alwaysTrue()); |
| } |
| |
| /** |
| * Tests that configurability settings are properly received. |
| */ |
| @Test |
| public void testConfigurability() { |
| assertThat( |
| attr("foo_configurable", BuildType.LABEL_LIST) |
| .legacyAllowAnyFileType() |
| .build() |
| .isConfigurable()) |
| .isTrue(); |
| assertThat( |
| attr("foo_nonconfigurable", BuildType.LABEL_LIST) |
| .legacyAllowAnyFileType() |
| .nonconfigurable("test") |
| .build() |
| .isConfigurable()) |
| .isFalse(); |
| } |
| |
| @Test |
| public void testSplitTransition() throws Exception { |
| TestSplitTransition splitTransition = new TestSplitTransition(); |
| Attribute attr = |
| attr("foo", LABEL).cfg(TransitionFactories.of(splitTransition)).allowedFileTypes().build(); |
| assertThat(attr.getTransitionFactory().isSplit()).isTrue(); |
| ConfigurationTransition transition = |
| attr.getTransitionFactory() |
| .create( |
| AttributeTransitionData.builder().attributes(FakeAttributeMapper.empty()).build()); |
| assertThat(transition).isEqualTo(splitTransition); |
| } |
| |
| @Test |
| public void testSplitTransitionProvider() throws Exception { |
| TestSplitTransitionProvider splitTransitionProvider = new TestSplitTransitionProvider(); |
| Attribute attr = |
| attr("foo", LABEL).cfg(splitTransitionProvider).allowedFileTypes().build(); |
| assertThat(attr.getTransitionFactory().isSplit()).isTrue(); |
| ConfigurationTransition transition = |
| attr.getTransitionFactory() |
| .create( |
| AttributeTransitionData.builder().attributes(FakeAttributeMapper.empty()).build()); |
| assertThat(transition).isInstanceOf(TestSplitTransition.class); |
| } |
| |
| @Test |
| public void testHostTransition() throws Exception { |
| Attribute attr = |
| attr("foo", LABEL).cfg(HostTransition.createFactory()).allowedFileTypes().build(); |
| assertThat(attr.getTransitionFactory().isHost()).isTrue(); |
| assertThat(attr.getTransitionFactory().isSplit()).isFalse(); |
| } |
| |
| private static class TestSplitTransition implements SplitTransition { |
| @Override |
| public List<BuildOptions> split(BuildOptions buildOptions) { |
| return ImmutableList.of(buildOptions.clone(), buildOptions.clone()); |
| } |
| } |
| |
| private static class TestSplitTransitionProvider |
| implements TransitionFactory<AttributeTransitionData> { |
| @Override |
| public SplitTransition create(AttributeTransitionData data) { |
| return new TestSplitTransition(); |
| } |
| |
| @Override |
| public boolean isSplit() { |
| return true; |
| } |
| } |
| |
| @Test |
| public void allowedRuleClassesAndAllowedRuleClassesWithWarningsCannotOverlap() throws Exception { |
| IllegalStateException e = |
| assertThrows( |
| IllegalStateException.class, |
| () -> |
| attr("x", LABEL_LIST) |
| .allowedRuleClasses("foo", "bar", "baz") |
| .allowedRuleClassesWithWarning("bar") |
| .allowedFileTypes() |
| .build()); |
| assertThat(e).hasMessageThat().contains("may not contain the same rule classes"); |
| } |
| } |