| // 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 org.junit.Assert.assertThrows; | |
| import com.google.devtools.build.lib.packages.AttributeContainer.Mutable; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.Random; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Unit tests for {@link AttributeContainer}. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class AttributeContainerTest { | |
| private static final int ATTR1 = 2; | |
| private static final int ATTR2 = 6; | |
| @Test | |
| public void testAttributeSettingAndRetrieval() { | |
| AttributeContainer container = new Mutable((short) 10); | |
| Object someValue1 = new Object(); | |
| Object someValue2 = new Object(); | |
| container.setAttributeValue(ATTR1, someValue1, /*explicit=*/ true); | |
| container.setAttributeValue(ATTR2, someValue2, /*explicit=*/ true); | |
| assertThat(container.getAttributeValue(ATTR1)).isEqualTo(someValue1); | |
| assertThat(container.getAttributeValue(ATTR2)).isEqualTo(someValue2); | |
| assertThrows(IndexOutOfBoundsException.class, () -> container.getAttributeValue(10)); | |
| } | |
| @Test | |
| public void testAttributeSettingAndRetrieval_afterFreezing() { | |
| AttributeContainer container = new Mutable((short) 10); | |
| Object someValue1 = new Object(); | |
| Object someValue2 = new Object(); | |
| container.setAttributeValue(ATTR1, someValue1, /*explicit=*/ true); | |
| container.setAttributeValue(ATTR2, someValue2, /*explicit=*/ true); | |
| AttributeContainer frozen = container.freeze(); | |
| assertThat(frozen.getAttributeValue(ATTR1)).isEqualTo(someValue1); | |
| assertThat(frozen.getAttributeValue(ATTR2)).isEqualTo(someValue2); | |
| assertThrows(IndexOutOfBoundsException.class, () -> container.getAttributeValue(10)); | |
| } | |
| @Test | |
| public void testExplicitSpecificationsByInstance() { | |
| AttributeContainer container = new Mutable((short) 10); | |
| Object someValue = new Object(); | |
| container.setAttributeValue(ATTR1, someValue, true); | |
| container.setAttributeValue(ATTR2, someValue, false); | |
| assertThat(container.isAttributeValueExplicitlySpecified(ATTR1)).isTrue(); | |
| assertThat(container.isAttributeValueExplicitlySpecified(ATTR2)).isFalse(); | |
| } | |
| @Test | |
| public void testExplicitStateForOffByOneError() { | |
| AttributeContainer container = new Mutable((short) 30); | |
| // Set index 3 explicitly and check neighbouring indices dont leak that. | |
| Object valA = new Object(); | |
| Object valB = new Object(); | |
| Object valC = new Object(); | |
| container.setAttributeValue(2, valA, true); | |
| container.setAttributeValue(3, valB, true); | |
| container.setAttributeValue(4, valC, false); | |
| assertThat(container.isAttributeValueExplicitlySpecified(2)).isTrue(); | |
| assertThat(container.isAttributeValueExplicitlySpecified(3)).isTrue(); | |
| assertThat(container.isAttributeValueExplicitlySpecified(4)).isFalse(); | |
| } | |
| @Test | |
| public void testPackedState() throws Exception { | |
| Random rng = new Random(); | |
| // The state packing machinery has special behavior at multiples of 8, | |
| // so set enough explicit values to exercise that. | |
| final int numAttributes = 17; | |
| List<Integer> attrIndices = new ArrayList<>(); | |
| for (int attrIndex = 0; attrIndex < numAttributes; ++attrIndex) { | |
| attrIndices.add(attrIndex); | |
| } | |
| Object someValue = new Object(); | |
| for (int explicitCount = 0; explicitCount <= numAttributes; ++explicitCount) { | |
| AttributeContainer container = new Mutable((short) 20); | |
| // Shuffle the attributes each time through, to exercise | |
| // different stored indices and orderings. | |
| Collections.shuffle(attrIndices); | |
| // Also randomly interleave calls to the two setters. | |
| int valuePassKey = rng.nextInt(1 << numAttributes); | |
| for (int pass = 0; pass <= 1; ++pass) { | |
| for (int i = 0; i < explicitCount; ++i) { | |
| if (pass == ((valuePassKey >> i) & 1)) { | |
| container.setAttributeValue(i, someValue, true); | |
| } | |
| } | |
| } | |
| for (int i = 0; i < numAttributes; ++i) { | |
| boolean expected = i < explicitCount; | |
| assertThat(container.isAttributeValueExplicitlySpecified(i)).isEqualTo(expected); | |
| } | |
| } | |
| } | |
| private void checkFreezeWorks( | |
| short maxAttrCount, Class<? extends AttributeContainer> expectedImplClass) { | |
| AttributeContainer container = new Mutable(maxAttrCount); | |
| Object someValue1 = new Object(); | |
| Object someValue2 = new Object(); | |
| container.setAttributeValue(ATTR1, someValue1, /*explicit=*/ true); | |
| container.setAttributeValue(ATTR2, someValue2, /*explicit=*/ false); | |
| AttributeContainer frozen = container.freeze(); | |
| assertThat(frozen).isInstanceOf(expectedImplClass); | |
| // freezing returned something else. | |
| assertThat(frozen).isNotSameInstanceAs(container); | |
| // Double freezing is a no-op | |
| assertThat(frozen.freeze()).isSameInstanceAs(frozen); | |
| // reads/explicit bits work as expected | |
| assertThat(frozen.getAttributeValue(ATTR1)).isEqualTo(someValue1); | |
| assertThat(frozen.isAttributeValueExplicitlySpecified(ATTR1)).isTrue(); | |
| assertThat(frozen.getAttributeValue(ATTR2)).isEqualTo(someValue2); | |
| assertThat(frozen.isAttributeValueExplicitlySpecified(ATTR2)).isFalse(); | |
| // Invalid attribute index. | |
| assertThrows(IndexOutOfBoundsException.class, () -> frozen.getAttributeValue(maxAttrCount)); | |
| // writes no longer work. | |
| assertThrows( | |
| UnsupportedOperationException.class, | |
| () -> frozen.setAttributeValue(ATTR2, new Object(), true)); | |
| // Updates to the original container no longer reflected in new container. | |
| Object newValue = new Object(); | |
| container.setAttributeValue(ATTR2, newValue, true); | |
| assertThat(container.getAttributeValue(ATTR2)).isEqualTo(newValue); | |
| assertThat(frozen.getAttributeValue(ATTR2)).isEqualTo(someValue2); | |
| } | |
| @Test | |
| public void testFreezeWorks_smallImplementation() { | |
| checkFreezeWorks((short) 20, AttributeContainer.Small.class); | |
| } | |
| @Test | |
| public void testFreezeWorks_largeImplementation() { | |
| checkFreezeWorks((short) 150, AttributeContainer.Large.class); | |
| } | |
| private void testContainerSize(int size) { | |
| AttributeContainer container = new Mutable(size); | |
| for (int i = 0; i < size; i++) { | |
| container.setAttributeValue(i, "value " + i, i % 2 == 0); | |
| } | |
| AttributeContainer frozen = container.freeze(); | |
| // Check values. | |
| for (int i = 0; i < size; i++) { | |
| assertThat(frozen.getAttributeValue(i)).isEqualTo("value " + i); | |
| assertThat(frozen.isAttributeValueExplicitlySpecified(i)).isEqualTo(i % 2 == 0); | |
| } | |
| } | |
| @Test | |
| public void testSmallContainer() { | |
| // At 127 attributes, we shift from AttributeContainer.Small to AttributeContainer.Large. | |
| testContainerSize(126); | |
| } | |
| @Test | |
| public void testLargeContainer() { | |
| // AttributeContainer.Large can handle at max 254 attributes. | |
| testContainerSize(254); | |
| } | |
| @Test | |
| public void testMutableGetRawAttributeValuesReturnsNullSafeCopy() { | |
| AttributeContainer container = new Mutable(1); | |
| assertThat(container.getRawAttributeValues()).containsExactly((Object) null); | |
| container.getRawAttributeValues().set(0, "foo"); | |
| assertThat(container.getRawAttributeValues()).containsExactly((Object) null); | |
| } | |
| @Test | |
| public void testGetRawAttributeValuesReturnsCopy() { | |
| AttributeContainer mutable = new Mutable(2); | |
| mutable.setAttributeValue(0, "hi", /*explicit=*/ true); | |
| mutable.setAttributeValue(1, null, /*explicit=*/ false); | |
| AttributeContainer container = mutable.freeze(); | |
| // Nulls don't make it into the frozen representation. | |
| assertThat(container.getRawAttributeValues()).containsExactly("hi"); | |
| container.getRawAttributeValues().set(0, "foo"); | |
| assertThat(container.getRawAttributeValues()).containsExactly("hi"); | |
| } | |
| } |