| // 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.common.truth.Truth.assertWithMessage; |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.analysis.util.MockRule; |
| import com.google.devtools.build.lib.analysis.util.MockRuleDefaults; |
| import com.google.devtools.build.lib.packages.Attribute.ComputedDefault; |
| import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault; |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; |
| import com.google.testing.junit.testparameterinjector.TestParameter; |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.stream.IntStream; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| /** Tests for {@link Rule}'s attribute storage behavior. */ |
| @RunWith(TestParameterInjector.class) |
| public final class RuleAttributeStorageTest extends BuildViewTestCase { |
| |
| private static final String STRING_DEFAULT = Type.STRING.getDefaultValue(); |
| private static final int COMPUTED_DEFAULT_OFFSET = 1; |
| private static final int LATE_BOUND_DEFAULT_OFFSET = 2; |
| |
| private enum ContainerSize { |
| SMALL(16), |
| LARGE(128); |
| |
| private final int numAttrs; |
| |
| ContainerSize(int numAttrs) { |
| this.numAttrs = numAttrs; |
| } |
| } |
| |
| @TestParameter private ContainerSize containerSize; |
| |
| private Rule rule; |
| private int firstCustomAttrIndex; |
| private Attribute firstCustomAttr; |
| private int lastCustomAttrIndex; |
| private Attribute lastCustomAttr; |
| private int computedDefaultIndex; |
| private Attribute computedDefaultAttr; |
| private int lateBoundDefaultIndex; |
| private Attribute lateBoundDefaultAttr; |
| |
| @Override |
| protected ConfiguredRuleClassProvider createRuleClassProvider() { |
| int numDefaultAttrs = MockRuleDefaults.DEFAULT_ATTRIBUTES.size() + 1; // +1 for name. |
| int numCustomAttrs = containerSize.numAttrs - numDefaultAttrs; |
| MockRule exampleRule = |
| () -> |
| MockRule.define( |
| "example_rule", |
| IntStream.range(0, numCustomAttrs) |
| .mapToObj( |
| i -> { |
| // Make one attribute a computed default and one a late bound default. |
| if (i == COMPUTED_DEFAULT_OFFSET) { |
| return attr("attr" + i + "_computed_default", Type.STRING) |
| .value( |
| new ComputedDefault() { |
| @Override |
| public Object getDefault(AttributeMap rule) { |
| return "computed"; |
| } |
| }); |
| } |
| if (i == LATE_BOUND_DEFAULT_OFFSET) { |
| return attr(":attr" + i + "_late_bound_default", Type.STRING) |
| .value( |
| new LateBoundDefault<>(Void.class, "late_bound") { |
| @Override |
| public String resolve( |
| Rule rule, AttributeMap attributes, Void input) { |
| return "late_bound"; |
| } |
| }); |
| } |
| return attr("attr" + i, Type.STRING); |
| }) |
| .toArray(Attribute.Builder[]::new)); |
| var builder = new ConfiguredRuleClassProvider.Builder().addRuleDefinition(exampleRule); |
| TestRuleClassProvider.addStandardRules(builder); |
| return builder.build(); |
| } |
| |
| @Before |
| public void setUpForRule() throws Exception { |
| scratch.file("foo/BUILD", "example_rule(name = 'example')"); |
| |
| // Make a mutable copy of the rule so we can set attributes. |
| Rule actualRule = (Rule) getTarget("//foo:example"); |
| rule = |
| new Rule( |
| actualRule.getPackage(), |
| actualRule.getLabel(), |
| actualRule.getRuleClassObject(), |
| actualRule.getLocation(), |
| actualRule.getInteriorCallStack()); |
| |
| firstCustomAttrIndex = rule.getRuleClassObject().getAttributeIndex("attr0"); |
| firstCustomAttr = attrAt(firstCustomAttrIndex); |
| lastCustomAttrIndex = rule.getRuleClassObject().getAttributeCount() - 1; |
| lastCustomAttr = attrAt(lastCustomAttrIndex); |
| computedDefaultIndex = firstCustomAttrIndex + COMPUTED_DEFAULT_OFFSET; |
| computedDefaultAttr = attrAt(computedDefaultIndex); |
| lateBoundDefaultIndex = firstCustomAttrIndex + LATE_BOUND_DEFAULT_OFFSET; |
| lateBoundDefaultAttr = attrAt(lateBoundDefaultIndex); |
| } |
| |
| @Test |
| public void attributeSettingAndRetrieval(@TestParameter boolean frozen) { |
| rule.setAttributeValue(firstCustomAttr, "val1", /* explicit= */ true); |
| rule.setAttributeValue(lastCustomAttr, "val2", /* explicit= */ true); |
| |
| if (frozen) { |
| rule.freeze(); |
| } |
| |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex)).isEqualTo("val1"); |
| assertThat(rule.isAttributeValueExplicitlySpecified(firstCustomAttr)).isTrue(); |
| assertThat(rule.getAttrIfStored(lastCustomAttrIndex)).isEqualTo("val2"); |
| assertThat(rule.isAttributeValueExplicitlySpecified(lastCustomAttr)).isTrue(); |
| } |
| |
| @Test |
| public void indexOutOfBounds_throws(@TestParameter boolean frozen) { |
| if (frozen) { |
| rule.freeze(); |
| } |
| assertThrows( |
| IndexOutOfBoundsException.class, () -> rule.getAttrIfStored(lastCustomAttrIndex + 1)); |
| } |
| |
| @Test |
| public void testForOffByOneError(@TestParameter boolean frozen) { |
| // Set an index explicitly and check neighbouring indices don't leak that. |
| rule.setAttributeValue(firstCustomAttr, "val", true); |
| |
| if (frozen) { |
| rule.freeze(); |
| } |
| |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex - 1)).isNull(); |
| assertThat(rule.isAttributeValueExplicitlySpecified(attrAt(firstCustomAttrIndex - 1))) |
| .isFalse(); |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex + 1)).isNull(); |
| assertThat(rule.isAttributeValueExplicitlySpecified(attrAt(firstCustomAttrIndex + 1))) |
| .isFalse(); |
| } |
| |
| @Test |
| public void testFreezeWorks() { |
| rule.setAttributeValue(firstCustomAttr, "val1", /* explicit= */ true); |
| rule.setAttributeValue(lastCustomAttr, "val2", /* explicit= */ false); |
| assertThat(rule.isFrozen()).isFalse(); |
| |
| rule.freeze(); |
| |
| assertThat(rule.isFrozen()).isTrue(); |
| // Double freezing is a no-op |
| rule.freeze(); |
| // reads/explicit bits work as expected |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex)).isEqualTo("val1"); |
| assertThat(rule.isAttributeValueExplicitlySpecified(firstCustomAttr)).isTrue(); |
| assertThat(rule.getAttrIfStored(lastCustomAttrIndex)).isEqualTo("val2"); |
| assertThat(rule.isAttributeValueExplicitlySpecified(lastCustomAttr)).isFalse(); |
| // writes no longer work. |
| assertThrows( |
| IllegalStateException.class, |
| () -> rule.setAttributeValue(lastCustomAttr, "different", true)); |
| } |
| |
| @Test |
| public void allAttributesSet(@TestParameter boolean frozen) { |
| int size = rule.getRuleClassObject().getAttributeCount(); |
| rule.setAttributeValue(attrAt(0), rule.getName(), /* explicit= */ true); |
| for (int i = 1; i < size; i++) { |
| rule.setAttributeValue(attrAt(i), "value " + i, i % 2 == 0); |
| } |
| |
| if (frozen) { |
| rule.freeze(); |
| } |
| |
| for (int i = 1; i < size; i++) { // Skip attribute 0 (name) which is never stored. |
| assertThat(rule.getAttrIfStored(i)).isEqualTo("value " + i); |
| assertWithMessage("attribute " + i) |
| .that(rule.isAttributeValueExplicitlySpecified(attrAt(i))) |
| .isEqualTo(i % 2 == 0); |
| } |
| } |
| |
| @Test |
| public void getRawAttrValues_mutable_nullSafe() { |
| assertThat(rule.getRawAttrValues()) |
| .containsAtLeastElementsIn( |
| Collections.nCopies(rule.getRuleClassObject().getAttributeCount(), null)); |
| } |
| |
| @Test |
| public void getRawAttrValues_frozen_noNulls() { |
| rule.setAttributeValue(firstCustomAttr, "hi", /* explicit= */ true); |
| rule.setAttributeValue(lastCustomAttr, null, /* explicit= */ false); |
| rule.freeze(); |
| assertThat(rule.getRawAttrValues()).containsExactly("hi"); |
| } |
| |
| @Test |
| public void getRawAttrValues_unmodifiable(@TestParameter boolean frozen) { |
| rule.setAttributeValue(firstCustomAttr, "hi", /* explicit= */ true); |
| |
| if (frozen) { |
| rule.freeze(); |
| } |
| |
| Iterator<Object> it = rule.getRawAttrValues().iterator(); |
| it.next(); |
| assertThrows(UnsupportedOperationException.class, it::remove); |
| } |
| |
| /** Regression test for b/269593252. */ |
| @Test |
| public void boundaryOfFrozenContainer() { |
| String ruleName = rule.getName(); |
| rule.setAttributeValue(attrAt(0), ruleName, /* explicit= */ true); |
| rule.setAttributeValue(lastCustomAttr, "last", /* explicit= */ true); |
| |
| rule.freeze(); |
| |
| assertThat(rule.getAttr("name")).isEqualTo(ruleName); |
| assertThat(rule.isAttributeValueExplicitlySpecified("name")).isTrue(); |
| assertThat(rule.getAttrIfStored(lastCustomAttrIndex)).isEqualTo("last"); |
| assertThat(rule.isAttributeValueExplicitlySpecified(lastCustomAttr)).isTrue(); |
| } |
| |
| @Test |
| public void nameNotStoredAsRawAttr(@TestParameter boolean frozen) { |
| String ruleName = rule.getName(); |
| rule.setAttributeValue(attrAt(0), ruleName, /* explicit= */ true); |
| |
| if (frozen) { |
| rule.freeze(); |
| } |
| |
| assertThat(rule.getAttrIfStored(0)).isNull(); |
| assertThat(rule.getRawAttrValues()).doesNotContain(ruleName); |
| assertThat(rule.getAttr("name")).isEqualTo(ruleName); |
| assertThat(rule.isAttributeValueExplicitlySpecified("name")).isTrue(); |
| } |
| |
| @Test |
| public void explicitDefaultValue_stored(@TestParameter boolean frozen) { |
| rule.setAttributeValue(firstCustomAttr, STRING_DEFAULT, /* explicit= */ true); |
| |
| if (frozen) { |
| rule.freeze(); |
| } |
| |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex)).isNotNull(); |
| assertThat(rule.isAttributeValueExplicitlySpecified(firstCustomAttr)).isTrue(); |
| } |
| |
| @Test |
| public void nonExplicitDefaultValue_mutable_stored() { |
| rule.setAttributeValue(firstCustomAttr, STRING_DEFAULT, /* explicit= */ false); |
| |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex)).isNotNull(); |
| assertThat(rule.isAttributeValueExplicitlySpecified(firstCustomAttr)).isFalse(); |
| } |
| |
| @Test |
| public void nonExplicitDefaultValue_frozen_notStored() { |
| rule.setAttributeValue(firstCustomAttr, STRING_DEFAULT, /* explicit= */ false); |
| |
| rule.freeze(); |
| |
| assertThat(rule.getAttrIfStored(firstCustomAttrIndex)).isNull(); |
| assertThat(rule.isAttributeValueExplicitlySpecified(firstCustomAttr)).isFalse(); |
| } |
| |
| @Test |
| public void computedDefault_mutable_stored() { |
| var computedDefault = computedDefaultAttr.getDefaultValue(); |
| assertThat(computedDefaultAttr.hasComputedDefault()).isTrue(); |
| assertThat(computedDefault).isInstanceOf(ComputedDefault.class); |
| |
| rule.setAttributeValue(computedDefaultAttr, computedDefault, /* explicit= */ false); |
| |
| assertThat(rule.getAttrIfStored(computedDefaultIndex)).isEqualTo(computedDefault); |
| assertThat(rule.getAttr(computedDefaultAttr.getName())).isEqualTo(computedDefault); |
| assertThat(rule.isAttributeValueExplicitlySpecified(computedDefaultAttr)).isFalse(); |
| } |
| |
| @Test |
| public void computedDefault_frozen_notStored() { |
| var computedDefault = computedDefaultAttr.getDefaultValue(); |
| assertThat(computedDefaultAttr.hasComputedDefault()).isTrue(); |
| assertThat(computedDefault).isInstanceOf(ComputedDefault.class); |
| |
| rule.setAttributeValue(computedDefaultAttr, computedDefault, /* explicit= */ false); |
| rule.freeze(); |
| |
| assertThat(rule.getAttrIfStored(computedDefaultIndex)).isNull(); |
| assertThat(rule.getAttr(computedDefaultAttr.getName())).isEqualTo(computedDefault); |
| assertThat(rule.isAttributeValueExplicitlySpecified(computedDefaultAttr)).isFalse(); |
| } |
| |
| @Test |
| public void lateBoundDefault_mutable_stored() { |
| var lateBoundDefault = lateBoundDefaultAttr.getLateBoundDefault(); |
| |
| rule.setAttributeValue(lateBoundDefaultAttr, lateBoundDefault, /* explicit= */ false); |
| |
| assertThat(rule.getAttrIfStored(lateBoundDefaultIndex)).isEqualTo(lateBoundDefault); |
| assertThat(rule.getAttr(lateBoundDefaultAttr.getName())).isEqualTo(lateBoundDefault); |
| assertThat(rule.isAttributeValueExplicitlySpecified(lateBoundDefaultAttr)).isFalse(); |
| } |
| |
| @Test |
| public void lateBoundDefault_frozen_notStored() { |
| var lateBoundDefault = lateBoundDefaultAttr.getLateBoundDefault(); |
| |
| rule.setAttributeValue(lateBoundDefaultAttr, lateBoundDefault, /* explicit= */ false); |
| rule.freeze(); |
| |
| assertThat(rule.getAttrIfStored(lateBoundDefaultIndex)).isNull(); |
| assertThat(rule.getAttr(lateBoundDefaultAttr.getName())).isEqualTo(lateBoundDefault); |
| assertThat(rule.isAttributeValueExplicitlySpecified(lateBoundDefaultAttr)).isFalse(); |
| } |
| |
| private Attribute attrAt(int attrIndex) { |
| return rule.getRuleClassObject().getAttribute(attrIndex); |
| } |
| } |