Open source a few more analysis tests.

--
MOS_MIGRATED_REVID=92715161
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java
index a773cd0..59a01f3 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.analysis;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableMap;
@@ -76,7 +77,9 @@
    * <p>If you don't know how to do this, you really want to use one of the "do-it-all"
    * constructors.
    */
-  static ConfiguredAttributeMapper of(Rule rule, Set<ConfigMatchingProvider> configConditions) {
+  @VisibleForTesting
+  public static ConfiguredAttributeMapper of(
+      Rule rule, Set<ConfigMatchingProvider> configConditions) {
     return new ConfiguredAttributeMapper(rule, configConditions);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java
index 2742b1e..f142c96 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.packages;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.syntax.Label;
 
@@ -74,7 +75,8 @@
    * @throws IllegalArgumentException if the given attribute doesn't exist with the specified
    *         type. This happens whether or not it's a computed default.
    */
-  protected <T> Attribute.ComputedDefault getComputedDefault(String attributeName, Type<T> type) {
+  @VisibleForTesting // Should be protected
+  public <T> Attribute.ComputedDefault getComputedDefault(String attributeName, Type<T> type) {
     int index = getIndexWithTypeCheck(attributeName, type);
     Object value = attributes.getAttributeValue(index);
     if (value instanceof Attribute.ComputedDefault) {
@@ -177,7 +179,7 @@
     if (!(attrValue instanceof Type.SelectorList)) {
       return null;
     }
-    if (((Type.SelectorList) attrValue).getOriginalType() != type) {
+    if (((Type.SelectorList<?>) attrValue).getOriginalType() != type) {
       throw new IllegalArgumentException("Attribute " + attributeName
           + " is not of type " + type + " in rule " + ruleLabel);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java
index b6328a3..1b4db38 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Package.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java
@@ -528,8 +528,8 @@
    * for walking through the dependency graph of a target.
    * Fails if the target is not a Rule.
    */
-  @VisibleForTesting
-  Rule getRule(String targetName) {
+  @VisibleForTesting // Should be package-private
+  public Rule getRule(String targetName) {
     return (Rule) targets.get(targetName);
   }
 
diff --git a/src/test/java/BUILD b/src/test/java/BUILD
index 7eed6d2..ef8e053 100644
--- a/src/test/java/BUILD
+++ b/src/test/java/BUILD
@@ -293,6 +293,25 @@
 )
 
 java_test(
+    name = "analysis_select_test",
+    srcs = glob([
+        "com/google/devtools/build/lib/analysis/select/*.java",
+    ]),
+    args = ["com.google.devtools.build.lib.AllTests"],
+    deps = [
+        ":actions_testutil",
+        ":analysis_testutil",
+        ":foundations_testutil",
+        ":test_runner",
+        ":testutil",
+        "//src/main/java:bazel-core",
+        "//third_party:guava",
+        "//third_party:junit4",
+        "//third_party:truth",
+    ],
+)
+
+java_test(
     name = "analysis_test",
     srcs = glob([
         "com/google/devtools/build/lib/analysis/*.java",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapperTest.java
new file mode 100644
index 0000000..3a854c5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapperTest.java
@@ -0,0 +1,196 @@
+// Copyright 2015 Google Inc. 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.analysis;
+
+import static com.google.devtools.build.lib.testutil.MoreAsserts.assertSameContents;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ConfiguredAttributeMapper}.
+ *
+ * <p>This is distinct from {@link
+ * com.google.devtools.build.lib.analysis.select.ConfiguredAttributeMapperCommonTest} because the
+ * latter needs to inherit from {@link
+ * com.google.devtools.build.lib.analysis.select.AbstractAttributeMapperTest} to run tests common to
+ * all attribute mappers.
+ */
+public class ConfiguredAttributeMapperTest extends BuildViewTestCase {
+
+  /**
+   * Returns a ConfiguredAttributeMapper bound to the given rule with the target configuration.
+   */
+  private ConfiguredAttributeMapper getMapper(String label) throws Exception {
+    return ConfiguredAttributeMapper.of(
+        (RuleConfiguredTarget) getConfiguredTarget(label));
+  }
+
+  private void writeConfigRules() throws Exception {
+    scratch.file("conditions/BUILD",
+        "config_setting(",
+        "    name = 'a',",
+        "    values = {'compilation_mode': 'opt'})",
+        "config_setting(",
+        "    name = 'b',",
+        "    values = {'compilation_mode': 'dbg'})");
+  }
+
+  /**
+   * Tests that {@link ConfiguredAttributeMapper#get} only gets the configuration-appropriate
+   * value.
+   */
+  public void testGetAttribute() throws Exception {
+    writeConfigRules();
+    scratch.file("a/BUILD",
+        "genrule(",
+        "    name = 'gen',",
+        "    srcs = [],",
+        "    outs = ['out'],",
+        "    cmd = select({",
+        "        '//conditions:a': 'a command',",
+        "        '//conditions:b': 'b command',",
+        "        '" + Type.Selector.DEFAULT_CONDITION_KEY + "': 'default command',",
+        "    }))");
+
+    useConfiguration("-c", "opt");
+    assertEquals("a command", getMapper("//a:gen").get("cmd", Type.STRING));
+
+    useConfiguration("-c", "dbg");
+    assertEquals("b command", getMapper("//a:gen").get("cmd", Type.STRING));
+
+    useConfiguration("-c", "fastbuild");
+    assertEquals("default command", getMapper("//a:gen").get("cmd", Type.STRING));
+  }
+
+  /**
+   * Tests that label visitation only travels down configuration-appropriate paths.
+   */
+  public void testLabelVisitation() throws Exception {
+    writeConfigRules();
+    scratch.file("a/BUILD",
+        "sh_binary(",
+        "    name = 'bin',",
+        "    srcs = ['bin.sh'],",
+        "    deps = select({",
+        "        '//conditions:a': [':adep'],",
+        "        '//conditions:b': [':bdep'],",
+        "        '" + Type.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],",
+        "    }))",
+        "sh_library(",
+        "    name = 'adep',",
+        "    srcs = ['adep.sh'])",
+        "sh_library(",
+        "    name = 'bdep',",
+        "    srcs = ['bdep.sh'])",
+        "sh_library(",
+        "    name = 'defaultdep',",
+        "    srcs = ['defaultdep.sh'])");
+
+    final List<Label> visitedLabels = new ArrayList<>();
+    AttributeMap.AcceptsLabelAttribute testVisitor =
+        new AttributeMap.AcceptsLabelAttribute() {
+          @Override
+          public void acceptLabelAttribute(Label label, Attribute attribute) {
+            if (label.toString().contains("//a:")) { // Ignore implicit common dependencies.
+              visitedLabels.add(label);
+            }
+          }
+        };
+
+    final Label binSrc = Label.parseAbsolute("//a:bin.sh");
+
+    useConfiguration("-c", "opt");
+    getMapper("//a:bin").visitLabels(testVisitor);
+    assertSameContents(ImmutableList.of(binSrc, Label.parseAbsolute("//a:adep")), visitedLabels);
+
+    visitedLabels.clear();
+    useConfiguration("-c", "dbg");
+    getMapper("//a:bin").visitLabels(testVisitor);
+    assertSameContents(ImmutableList.of(binSrc, Label.parseAbsolute("//a:bdep")), visitedLabels);
+
+    visitedLabels.clear();
+    useConfiguration("-c", "fastbuild");
+    getMapper("//a:bin").visitLabels(testVisitor);
+    assertSameContents(
+        ImmutableList.of(binSrc, Label.parseAbsolute("//a:defaultdep")), visitedLabels);
+  }
+
+  /**
+   * Tests that for configurable attributes where the *values* are evaluated in different
+   * configurations, the configuration checking still uses the original configuration.
+   */
+  public void testConfigurationTransitions() throws Exception {
+    writeConfigRules();
+    scratch.file("a/BUILD",
+        "genrule(",
+        "    name = 'gen',",
+        "    srcs = [],",
+        "    outs = ['out'],",
+        "    cmd = 'nothing',",
+        "    tools = select({",
+        "        '//conditions:a': [':adep'],",
+        "        '//conditions:b': [':bdep'],",
+        "        '" + Type.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],",
+        "    }))",
+        "sh_binary(",
+        "    name = 'adep',",
+        "    srcs = ['adep.sh'])",
+        "sh_binary(",
+        "    name = 'bdep',",
+        "    srcs = ['bdep.sh'])",
+        "sh_binary(",
+        "    name = 'defaultdep',",
+        "    srcs = ['defaultdep.sh'])");
+    useConfiguration("-c", "dbg");
+
+    // Target configuration is in dbg mode, so we should match //conditions:b:
+    assertSameContents(
+        ImmutableList.of(Label.parseAbsolute("//a:bdep")),
+        getMapper("//a:gen").get("tools", Type.LABEL_LIST));
+
+    // Verify the "tools" dep uses a different configuration that's not also in "dbg":
+    assertEquals(Attribute.ConfigurationTransition.HOST,
+        getTarget("//a:gen").getAssociatedRule().getRuleClassObject()
+            .getAttributeByName("tools").getConfigurationTransition());
+    assertEquals(CompilationMode.OPT, getHostConfiguration().getCompilationMode());
+  }
+
+  public void testConcatenatedSelects() throws Exception {
+    scratch.file("hello/BUILD",
+        "config_setting(name = 'a', values = {'define': 'foo=a'})",
+        "config_setting(name = 'b', values = {'define': 'foo=b'})",
+        "config_setting(name = 'c', values = {'define': 'bar=c'})",
+        "config_setting(name = 'd', values = {'define': 'bar=d'})",
+        "genrule(",
+        "    name = 'gen',",
+        "    srcs = select({':a': ['a.in'], ':b': ['b.in']})",
+        "         + select({':c': ['c.in'], ':d': ['d.in']}),",
+        "    outs = ['out'],",
+        "    cmd = 'nothing',",
+        ")");
+    useConfiguration("--define", "foo=a", "--define", "bar=d");
+    assertSameContents(
+         ImmutableList.of(Label.parseAbsolute("//hello:a.in"), Label.parseAbsolute("//hello:d.in")),
+         getMapper("//hello:gen").get("srcs", Type.LABEL_LIST));
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/select/AbstractAttributeMapperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/select/AbstractAttributeMapperTest.java
new file mode 100644
index 0000000..f15d1a6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/select/AbstractAttributeMapperTest.java
@@ -0,0 +1,146 @@
+// Copyright 2015 Google Inc. 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.analysis.select;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
+import com.google.devtools.build.lib.packages.AbstractAttributeMapper;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeContainer;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.packages.util.PackageFactoryApparatus;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.testutil.FoundationTestCase;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.testutil.Scratch;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link AbstractAttributeMapper}.
+ */
+public class AbstractAttributeMapperTest extends FoundationTestCase {
+
+  private Package pkg;
+  protected Rule rule;
+  protected AbstractAttributeMapper mapper;
+
+  private static class TestMapper extends AbstractAttributeMapper {
+    public TestMapper(Package pkg, RuleClass ruleClass, Label ruleLabel,
+        AttributeContainer attributes) {
+      super(pkg, ruleClass, ruleLabel, attributes);
+    }
+  }
+
+  protected Rule createRule(String pkgPath, String ruleName, String... ruleDef) throws Exception  {
+    Scratch scratch = new Scratch();
+    EventCollectionApparatus events = new EventCollectionApparatus();
+    PackageFactoryApparatus packages = new PackageFactoryApparatus(events, scratch);
+
+    Path buildFile = scratch.file(pkgPath + "/BUILD", ruleDef);
+    pkg = packages.createPackage(pkgPath, buildFile);
+    return pkg.getRule(ruleName);
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    rule = createRule("x", "myrule",
+        "cc_binary(name = 'myrule',",
+        "          srcs = ['a', 'b', 'c'])");
+    RuleClass ruleClass = rule.getRuleClassObject();
+    mapper = new TestMapper(pkg, ruleClass, rule.getLabel(), rule.getAttributeContainer());
+  }
+
+  public void testRuleProperties() throws Exception {
+    assertEquals(rule.getName(), mapper.getName());
+    assertEquals(rule.getLabel(), mapper.getLabel());
+  }
+
+  public void testPackageDefaultProperties() throws Exception {
+    assertEquals(pkg.getDefaultHdrsCheck(), mapper.getPackageDefaultHdrsCheck());
+    assertEquals(pkg.getDefaultTestOnly(), mapper.getPackageDefaultTestOnly());
+    assertEquals(pkg.getDefaultDeprecation(), mapper.getPackageDefaultDeprecation());
+  }
+
+  public void testAttributeTypeChecking() throws Exception {
+    // Good typing:
+    mapper.get("srcs", Type.LABEL_LIST);
+
+    // Bad typing:
+    try {
+      mapper.get("srcs", Type.BOOLEAN);
+      fail("Expected type mismatch to trigger an exception");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+    }
+
+    // Unknown attribute:
+    try {
+      mapper.get("nonsense", Type.BOOLEAN);
+      fail("Expected non-existent type to trigger an exception");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+    }
+  }
+
+  public void testGetAttributeType() throws Exception {
+    assertEquals(Type.LABEL_LIST, mapper.getAttributeType("srcs"));
+    assertNull(mapper.getAttributeType("nonsense"));
+  }
+
+  public void testGetAttributeDefinition() {
+    assertEquals("srcs", mapper.getAttributeDefinition("srcs").getName());
+    assertNull(mapper.getAttributeDefinition("nonsense"));
+  }
+
+  public void testIsAttributeExplicitlySpecified() throws Exception {
+    assertTrue(mapper.isAttributeValueExplicitlySpecified("srcs"));
+    assertFalse(mapper.isAttributeValueExplicitlySpecified("deps"));
+    assertFalse(mapper.isAttributeValueExplicitlySpecified("nonsense"));
+  }
+
+  protected static class VisitationRecorder implements AttributeMap.AcceptsLabelAttribute {
+    public List<String> labelsVisited = Lists.newArrayList();
+    @Override
+    public void acceptLabelAttribute(Label label, Attribute attribute) {
+      if (attribute.getName().equals("srcs")) {
+        labelsVisited.add(label.toString());
+      }
+    }
+  }
+
+  public void testVisitation() throws Exception {
+    VisitationRecorder recorder = new VisitationRecorder();
+    mapper.visitLabels(recorder);
+    MoreAsserts.assertSameContents(
+        ImmutableList.of("//x:a", "//x:b", "//x:c"), recorder.labelsVisited);
+  }
+
+  public void testComputedDefault() throws Exception {
+    // Should return a valid ComputedDefault instance since this is a computed default:
+    assertThat(mapper.getComputedDefault("$stl", Type.LABEL))
+        .isInstanceOf(Attribute.ComputedDefault.class);
+    // Should return null since this *isn't* a computed default:
+    assertNull(mapper.getComputedDefault("srcs", Type.LABEL_LIST));
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/select/AggregatingAttributeMapperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/select/AggregatingAttributeMapperTest.java
new file mode 100644
index 0000000..e894939
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/select/AggregatingAttributeMapperTest.java
@@ -0,0 +1,155 @@
+// Copyright 2015 Google Inc. 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.analysis.select;
+
+import static com.google.devtools.build.lib.testutil.MoreAsserts.assertSameContents;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.testutil.TestConstants;
+
+/**
+ * Unit tests for {@link AggregatingAttributeMapper}.
+ */
+public class AggregatingAttributeMapperTest extends AbstractAttributeMapperTest {
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    // Run AbstractAttributeMapper tests through an AggregatingAttributeMapper.
+    mapper = AggregatingAttributeMapper.of(rule);
+  }
+
+  /**
+   * Tests that {@link AggregatingAttributeMapper#visitAttribute} returns an
+   * attribute's sole value when declared directly (i.e. not as a configurable dict).
+   */
+  public void testGetPossibleValuesDirectAttribute() throws Exception {
+    Rule rule = createRule("a", "myrule",
+        "sh_binary(name = 'myrule',",
+        "          srcs = ['a.sh'])");
+    assertSameContents(
+        ImmutableList.of(ImmutableList.of(Label.create("a", "a.sh"))),
+        AggregatingAttributeMapper.of(rule).visitAttribute("srcs", Type.LABEL_LIST));
+  }
+
+  /**
+   * Tests that {@link AggregatingAttributeMapper#visitAttribute} returns
+   * every possible value that a configurable attribute can resolve to.
+   */
+  public void testGetPossibleValuesConfigurableAttribute() throws Exception {
+    Rule rule = createRule("a", "myrule",
+        "sh_binary(name = 'myrule',",
+        "          srcs = select({",
+        "              '//conditions:a': ['a.sh'],",
+        "              '//conditions:b': ['b.sh'],",
+        "              '" + Type.Selector.DEFAULT_CONDITION_KEY + "': ['default.sh'],",
+        "          }))");
+    assertSameContents(
+        ImmutableList.of(
+            ImmutableList.of(Label.create("a", "a.sh")),
+            ImmutableList.of(Label.create("a", "b.sh")),
+            ImmutableList.of(Label.create("a", "default.sh"))),
+        AggregatingAttributeMapper.of(rule).visitAttribute("srcs", Type.LABEL_LIST));
+  }
+
+  public void testGetPossibleValuesWithConcatenatedSelects() throws Exception {
+    Rule rule = createRule("a", "myrule",
+        "sh_binary(name = 'myrule',",
+        "          srcs = select({",
+        "                  '//conditions:a1': ['a1.sh'],",
+        "                  '//conditions:b1': ['b1.sh']})",
+        "              + select({",
+        "                  '//conditions:a2': ['a2.sh'],",
+        "                  '//conditions:b2': ['b2.sh']})",
+        "          )");
+    assertSameContents(
+        ImmutableList.of(
+            ImmutableList.of(Label.create("a", "a1.sh"), Label.create("a", "a2.sh")),
+            ImmutableList.of(Label.create("a", "a1.sh"), Label.create("a", "b2.sh")),
+            ImmutableList.of(Label.create("a", "b1.sh"), Label.create("a", "a2.sh")),
+            ImmutableList.of(Label.create("a", "b1.sh"), Label.create("a", "b2.sh"))),
+        AggregatingAttributeMapper.of(rule).visitAttribute("srcs", Type.LABEL_LIST));
+  }
+
+  /**
+   * Tests that, on rule visitation, {@link AggregatingAttributeMapper} visits *every* possible
+   * value in a configurable attribute (including configuration key labels).
+   */
+  public void testVisitationConfigurableAttribute() throws Exception {
+    Rule rule = createRule("a", "myrule",
+        "sh_binary(name = 'myrule',",
+        "          srcs = select({",
+        "              '//conditions:a': ['a.sh'],",
+        "              '//conditions:b': ['b.sh'],",
+        "              '" + Type.Selector.DEFAULT_CONDITION_KEY + "': ['default.sh'],",
+        "          }))");
+
+    VisitationRecorder recorder = new VisitationRecorder();
+    AggregatingAttributeMapper.of(rule).visitLabels(recorder);
+    assertSameContents(
+        ImmutableList.of(
+            "//a:a.sh", "//a:b.sh", "//a:default.sh", "//conditions:a", "//conditions:b"),
+        recorder.labelsVisited);
+  }
+
+  public void testGetReachableLabels() throws Exception {
+    Rule rule = createRule("x", "main",
+        "cc_binary(",
+        "    name = 'main',",
+        "    srcs = select({",
+        "        '//conditions:a': ['a.cc'],",
+        "        '//conditions:b': ['b.cc']})",
+        "    + ",
+        "        ['always.cc']",
+        "    + ",
+        "         select({",
+        "        '//conditions:c': ['c.cc'],",
+        "        '//conditions:d': ['d.cc'],",
+        "        '" + Type.Selector.DEFAULT_CONDITION_KEY + "': ['default.cc'],",
+        "    }))");
+
+    ImmutableList<Label> valueLabels = ImmutableList.of(
+        Label.create("x", "a.cc"), Label.create("x", "b.cc"), Label.create("x", "always.cc"),
+        Label.create("x", "c.cc"), Label.create("x", "d.cc"), Label.create("x", "default.cc"));
+    ImmutableList<Label> keyLabels = ImmutableList.of(
+        Label.create("conditions", "a"), Label.create("conditions", "b"),
+        Label.create("conditions", "c"), Label.create("conditions", "d"));
+
+    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
+    assertSameContents(
+        Iterables.concat(valueLabels, keyLabels),
+        mapper.getReachableLabels("srcs", true));
+    assertSameContents(valueLabels, mapper.getReachableLabels("srcs", false));
+  }
+
+  public void testDuplicateCheckOnNullValues() throws Exception {
+    if (TestConstants.THIS_IS_BAZEL) {
+      return;
+    }
+    Rule rule = createRule("x", "main",
+        "java_binary(",
+        "    name = 'main',",
+        "    srcs = ['main.java'])");
+    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
+    Attribute launcherAttribute = mapper.getAttributeDefinition("launcher");
+    assertNull(mapper.get(launcherAttribute.getName(), launcherAttribute.getType()));
+    assertSameContents(ImmutableList.of(), mapper.checkForDuplicateLabels(launcherAttribute));
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/select/ConfiguredAttributeMapperCommonTest.java b/src/test/java/com/google/devtools/build/lib/analysis/select/ConfiguredAttributeMapperCommonTest.java
new file mode 100644
index 0000000..041cdbe
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/select/ConfiguredAttributeMapperCommonTest.java
@@ -0,0 +1,36 @@
+// Copyright 2015 Google Inc. 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.analysis.select;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.ConfiguredAttributeMapper;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.packages.AbstractAttributeMapper;
+
+/**
+ * Tests that {@link ConfiguredAttributeMapper} fulfills all behavior expected
+ * of {@link AbstractAttributeMapper}.
+ *
+ * <p>This is distinct from {@link
+ * com.google.devtools.build.lib.analysis.ConfiguredAttributeMapperTest} because the latter needs to
+ * inherit from {@link com.google.devtools.build.lib.analysis.util.BuildViewTestCase} to run tests
+ * with build configurations.
+ */
+public class ConfiguredAttributeMapperCommonTest extends AbstractAttributeMapperTest {
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    mapper = ConfiguredAttributeMapper.of(rule, ImmutableSet.<ConfigMatchingProvider>of());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/select/NonconfigurableAttributeMapperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/select/NonconfigurableAttributeMapperTest.java
new file mode 100644
index 0000000..01ef88d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/select/NonconfigurableAttributeMapperTest.java
@@ -0,0 +1,52 @@
+// Copyright 2015 Google Inc. 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.analysis.select;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
+import com.google.devtools.build.lib.packages.Type;
+
+/**
+ * Unit tests for {@link NonconfigurableAttributeMapper}.
+ */
+public class NonconfigurableAttributeMapperTest extends AbstractAttributeMapperTest {
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    rule = createRule("x", "myrule",
+        "cc_binary(",
+        "    name = 'myrule',",
+        "    srcs = ['a', 'b', 'c'],",
+        "    linkstatic = 1,",
+        "  deprecation = \"this rule is deprecated!\")");
+  }
+
+  public void testGetNonconfigurableAttribute() throws Exception {
+    assertEquals("this rule is deprecated!",
+        NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING));
+  }
+
+  public void testGetConfigurableAttribute() throws Exception {
+    try {
+      NonconfigurableAttributeMapper.of(rule).get("linkstatic", Type.BOOLEAN);
+      fail("Expected NonconfigurableAttributeMapper to fail on a configurable attribute type");
+    } catch (IllegalStateException e) {
+      // Expected outcome.
+      assertThat(e).hasMessage(
+          "Attribute 'linkstatic' is potentially configurable - not allowed here");
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/select/RawAttributeMapperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/select/RawAttributeMapperTest.java
new file mode 100644
index 0000000..b1c02d4
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/select/RawAttributeMapperTest.java
@@ -0,0 +1,149 @@
+// Copyright 2015 Google Inc. 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.analysis.select;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.testutil.MoreAsserts.assertSameContents;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link RawAttributeMapper}.
+ */
+public class RawAttributeMapperTest extends AbstractAttributeMapperTest {
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    // Run AbstractAttributeMapper tests through a RawAttributeMapper.
+    mapper = RawAttributeMapper.of(rule);
+  }
+
+  private Rule setupGenRule() throws Exception {
+    return createRule("x", "myrule",
+        "sh_binary(",
+        "    name = 'myrule',",
+        "    srcs = select({",
+        "        '//conditions:a': ['a.sh'],",
+        "        '//conditions:b': ['b.sh'],",
+        "        '" + Type.Selector.DEFAULT_CONDITION_KEY + "': ['default.sh'],",
+        "    }),",
+        "    data = [ ':data_a', ':data_b' ])");
+  }
+
+  public void testGetAttribute() throws Exception {
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(setupGenRule());
+    List<Label> value = rawMapper.get("data", Type.LABEL_LIST);
+    assertNotNull(value);
+    assertThat(value).containsExactly(Label.create("x", "data_a"), Label.create("x", "data_b"));
+
+    // Configurable attribute: trying to directly access from a RawAttributeMapper throws a
+    // type mismatch exception.
+    try {
+      rawMapper.get("srcs", Type.LABEL_LIST);
+      fail("Expected srcs lookup to fail since the returned type is a SelectorList and not a list");
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getCause().getMessage())
+          .contains("SelectorList cannot be cast to java.util.List");
+    }
+  }
+
+  @Override
+  public void testGetAttributeType() throws Exception {
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(setupGenRule());
+    assertEquals(Type.LABEL_LIST, rawMapper.getAttributeType("data")); // not configurable
+    assertEquals(Type.LABEL_LIST, rawMapper.getAttributeType("srcs")); // configurable
+  }
+
+  public void testConfigurabilityCheck() throws Exception {
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(setupGenRule());
+    assertFalse(rawMapper.isConfigurable("data", Type.LABEL_LIST));
+    assertTrue(rawMapper.isConfigurable("srcs", Type.LABEL_LIST));
+  }
+
+  /**
+   * Tests that RawAttributeMapper can't handle label visitation with configurable attributes.
+   */
+  public void testVisitLabels() throws Exception {
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(setupGenRule());
+    try {
+      rawMapper.visitLabels(new AttributeMap.AcceptsLabelAttribute() {
+        @Override
+        public void acceptLabelAttribute(Label label, Attribute attribute) {
+          // Nothing to do.
+        }
+      });
+      fail("Expected label visitation to fail since one attribute is configurable");
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getCause().getMessage())
+          .contains("SelectorList cannot be cast to java.util.List");
+    }
+  }
+
+  public void testGetConfigurabilityKeys() throws Exception {
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(setupGenRule());
+    assertSameContents(
+        ImmutableSet.of(
+            Label.parseAbsolute("//conditions:a"),
+            Label.parseAbsolute("//conditions:b"),
+            Label.parseAbsolute("//conditions:default")),
+        rawMapper.getConfigurabilityKeys("srcs", Type.LABEL_LIST));
+    assertThat(rawMapper.getConfigurabilityKeys("data", Type.LABEL_LIST)).isEmpty();
+  }
+
+  public void testGetMergedValues() throws Exception {
+    Rule rule = createRule("x", "myrule",
+        "sh_binary(",
+        "    name = 'myrule',",
+        "    srcs = select({",
+        "        '//conditions:a': ['a.sh', 'b.sh'],",
+        "        '//conditions:b': ['b.sh', 'c.sh'],",
+        "    }))");
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(rule);
+    assertThat(rawMapper.getMergedValues("srcs", Type.LABEL_LIST)).containsExactly(
+        Label.parseAbsolute("//x:a.sh"),
+        Label.parseAbsolute("//x:b.sh"),
+        Label.parseAbsolute("//x:c.sh"))
+        .inOrder();
+  }
+
+  public void testMergedValuesWithConcatenatedSelects() throws Exception {
+    Rule rule = createRule("x", "myrule",
+        "sh_binary(",
+        "    name = 'myrule',",
+        "    srcs = select({",
+        "            '//conditions:a1': ['a1.sh'],",
+        "            '//conditions:b1': ['b1.sh', 'another_b1.sh']})",
+        "        + select({",
+        "            '//conditions:a2': ['a2.sh'],",
+        "            '//conditions:b2': ['b2.sh']})",
+        "    )");
+    RawAttributeMapper rawMapper = RawAttributeMapper.of(rule);
+    assertThat(rawMapper.getMergedValues("srcs", Type.LABEL_LIST)).containsExactly(
+        Label.parseAbsolute("//x:a1.sh"),
+        Label.parseAbsolute("//x:b1.sh"),
+        Label.parseAbsolute("//x:another_b1.sh"),
+        Label.parseAbsolute("//x:a2.sh"),
+        Label.parseAbsolute("//x:b2.sh"))
+        .inOrder();
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/PackageFactoryApparatus.java b/src/test/java/com/google/devtools/build/lib/packages/util/PackageFactoryApparatus.java
new file mode 100644
index 0000000..5370cea
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/PackageFactoryApparatus.java
@@ -0,0 +1,168 @@
+// Copyright 2007-2015 Google Inc. 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.util;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
+import com.google.devtools.build.lib.packages.CachingPackageLocator;
+import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
+import com.google.devtools.build.lib.packages.ExternalPackage;
+import com.google.devtools.build.lib.packages.GlobCache;
+import com.google.devtools.build.lib.packages.MakeEnvironment;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.Package.LegacyBuilder;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.PackageFactory.LegacyGlobber;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.RuleClassProvider;
+import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.testutil.Scratch;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+
+/**
+ * An apparatus that creates / maintains a {@link PackageFactory}.
+ */
+public class PackageFactoryApparatus {
+
+  private final EventCollectionApparatus events;
+  private final Scratch scratch;
+  private final CachingPackageLocator locator;
+
+  private final PackageFactory factory;
+
+  public PackageFactoryApparatus(EventCollectionApparatus events, Scratch scratch,
+      PackageFactory.EnvironmentExtension... environmentExtensions) {
+    this.events = events;
+    this.scratch = scratch;
+    RuleClassProvider ruleClassProvider = TestRuleClassProvider.getRuleClassProvider();
+
+    // This is used only in globbing and will cause us to always traverse
+    // subdirectories.
+    this.locator = createEmptyLocator();
+
+    factory = new PackageFactory(ruleClassProvider, null,
+        ImmutableList.copyOf(environmentExtensions));
+  }
+
+  /**
+   * Returns the package factory maintained by this apparatus.
+   */
+  public PackageFactory factory() {
+    return factory;
+  }
+
+  CachingPackageLocator getPackageLocator() {
+    return locator;
+  }
+
+  /**
+   * Parses and evaluates {@code buildFile} and returns the resulting {@link Package} instance.
+   */
+  public Package createPackage(String packageName, Path buildFile)
+      throws Exception {
+    return createPackage(packageName, buildFile, events.reporter());
+  }
+
+  /**
+   * Parses and evaluates {@code buildFile} with custom {@code reporter} and returns the resulting
+   * {@link Package} instance.
+   */
+  public Package createPackage(String packageName, Path buildFile,
+      Reporter reporter)
+      throws Exception {
+    try {
+      Package pkg = factory.createPackageForTesting(
+          PackageIdentifier.createInDefaultRepo(packageName), buildFile, locator, reporter);
+      return pkg;
+    } catch (InterruptedException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  /**
+   * Parses the {@code buildFile} into a {@link BuildFileAST}.
+   */
+  public BuildFileAST ast(Path buildFile) throws IOException {
+    ParserInputSource inputSource = ParserInputSource.create(buildFile);
+    return BuildFileAST.parseBuildFile(inputSource, events.reporter(), locator,
+        /*parsePython=*/false);
+  }
+
+  /**
+   * Parses the {@code lines} into a {@link BuildFileAST}.
+   */
+  public BuildFileAST ast(String fileName, String... lines)
+      throws IOException {
+    Path file = scratch.file(fileName, lines);
+    return ast(file);
+  }
+
+  /**
+   * Evaluates the {@code buildFileAST} into a {@link Package}.
+   */
+  public Pair<Package, GlobCache> evalAndReturnGlobCache(String packageName, Path buildFile,
+      BuildFileAST buildFileAST) throws InterruptedException {
+    PackageIdentifier packageId = PackageIdentifier.createInDefaultRepo(packageName);
+    GlobCache globCache = new GlobCache(
+        buildFile.getParentDirectory(), packageId, locator, null, TestUtils.getPool());
+    LegacyGlobber globber = new LegacyGlobber(globCache);
+    ExternalPackage externalPkg = (new ExternalPackage.Builder(
+        buildFile.getParentDirectory().getRelative("WORKSPACE"))).build();
+    LegacyBuilder resultBuilder = factory.evaluateBuildFile(
+        externalPkg, packageId, buildFileAST, buildFile,
+        globber, ImmutableList.<Event>of(), ConstantRuleVisibility.PUBLIC, false, false,
+        new MakeEnvironment.Builder(), ImmutableMap.<PathFragment, SkylarkEnvironment>of(),
+        ImmutableList.<Label>of());
+    Package result = resultBuilder.build();
+    Event.replayEventsOn(events.reporter(), result.getEvents());
+    return Pair.of(result, globCache);
+  }
+
+  public Package eval(String packageName, Path buildFile, BuildFileAST buildFileAST)
+      throws InterruptedException {
+    return evalAndReturnGlobCache(packageName, buildFile, buildFileAST).first;
+  }
+
+  /**
+   * Evaluates the {@code buildFileAST} into a {@link Package}.
+   */
+  public Package eval(String packageName, Path buildFile)
+      throws InterruptedException, IOException {
+    return eval(packageName, buildFile, ast(buildFile));
+  }
+
+  /**
+   * Creates a package locator that finds no packages.
+   */
+  public static CachingPackageLocator createEmptyLocator() {
+    return new CachingPackageLocator() {
+      @Override
+      public Path getBuildFileForPackage(String packageName) {
+        return null;
+      }
+    };
+  }
+}