aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build
diff options
context:
space:
mode:
authorGravatar mstaib <mstaib@google.com>2017-06-28 23:45:56 +0200
committerGravatar Marcel Hlopko <hlopko@google.com>2017-06-29 09:33:43 +0200
commit199624bdc59a36cda9920f804e9933954de6ce95 (patch)
tree39ac46a0d2004afd98000699c51818e11c4cd207 /src/test/java/com/google/devtools/build
parent1b9933a96e6cc4217e8d8c3e6fe0646a345c680e (diff)
Add a FeaturePolicyConfiguration for supporting limited feature rollouts.
A common need is to limit access to a rule or feature - restricting a feature which is being deprecated or controlling the rollout of a new feature. This change adds the feature_control_policy flag, which allows developers to easily manage this process. RELNOTES: None. PiperOrigin-RevId: 160454166
Diffstat (limited to 'src/test/java/com/google/devtools/build')
-rw-r--r--src/test/java/com/google/devtools/build/lib/BUILD2
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD32
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfigurationTest.java121
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoaderTest.java322
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptionsTest.java57
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverterTest.java142
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java4
7 files changed, 680 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index e90994734e..a33d48ffe1 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -38,6 +38,7 @@ filegroup(
"//src/test/java/com/google/devtools/build/lib/skyframe:srcs",
"//src/test/java/com/google/devtools/build/lib/rules/android:srcs",
"//src/test/java/com/google/devtools/build/lib/rules/config:srcs",
+ "//src/test/java/com/google/devtools/build/lib/analysis/featurecontrol:srcs",
"//src/test/java/com/google/devtools/build/lib/analysis/platform:srcs",
"//src/test/java/com/google/devtools/build/lib/rules/platform:srcs",
"//src/test/java/com/google/devtools/build/lib/rules/repository:srcs",
@@ -359,6 +360,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib:util",
"//src/main/java/com/google/devtools/build/lib:vfs",
"//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/analysis/featurecontrol",
"//src/main/java/com/google/devtools/build/lib/query2",
"//src/main/java/com/google/devtools/build/lib/query2:query-output",
"//src/main/java/com/google/devtools/build/lib/rules/apple",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD
new file mode 100644
index 0000000000..2f26a91fd6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD
@@ -0,0 +1,32 @@
+# Description:
+# Tests for configuration mechanisms for rolling out and deprecating pieces of Bazel functionality.
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ srcs = glob(
+ ["**"],
+ ),
+ visibility = ["//src/test/java/com/google/devtools/build/lib:__pkg__"],
+)
+
+java_test(
+ name = "FeatureRestrictionTests",
+ srcs = glob(["*.java"]),
+ test_class = "com.google.devtools.build.lib.AllTests",
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
+ "//src/main/java/com/google/devtools/build/lib/analysis/featurecontrol",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/test/java/com/google/devtools/build/lib:analysis_testutil",
+ "//src/test/java/com/google/devtools/build/lib:packages_testutil",
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ "//third_party:guava",
+ "//third_party:guava-testlib",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfigurationTest.java b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfigurationTest.java
new file mode 100644
index 0000000000..eaaf438215
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfigurationTest.java
@@ -0,0 +1,121 @@
+// 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.devtools.build.lib.analysis.featurecontrol;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the FeaturePolicyConfiguration. */
+@RunWith(JUnit4.class)
+public final class FeaturePolicyConfigurationTest {
+
+ @Test
+ public void isFeatureEnabledForRule_FalseIfAbsentFromFeatureMap() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.<String, PackageSpecification>of(),
+ ImmutableMap.of("newFeature", "//package_group:empty"));
+
+ assertThat(config.isFeatureEnabledForRule("newFeature", Label.parseAbsolute("//:rule")))
+ .isFalse();
+ }
+
+ @Test
+ public void isFeatureEnabledForRule_TrueIfMappedToEverything() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.of("newFeature", PackageSpecification.everything()),
+ ImmutableMap.of("newFeature", "//..."));
+
+ assertThat(config.isFeatureEnabledForRule("newFeature", Label.parseAbsolute("//:rule")))
+ .isTrue();
+ }
+
+ @Test
+ public void isFeatureEnabledForRule_TrueIfInPackageSpecification() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.of(
+ "newFeature",
+ PackageSpecification.fromString(RepositoryName.MAIN, "//allowed/...")),
+ ImmutableMap.of("newFeature", "//allowed/..."));
+
+ assertThat(config.isFeatureEnabledForRule("newFeature", Label.parseAbsolute("//allowed:rule")))
+ .isTrue();
+ }
+
+ @Test
+ public void isFeatureEnabledForRule_FalseIfNotInPackageSpecification() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.of(
+ "newFeature",
+ PackageSpecification.fromString(RepositoryName.MAIN, "//allowed/...")),
+ ImmutableMap.of("newFeature", "//allowed/..."));
+
+ assertThat(
+ config.isFeatureEnabledForRule("newFeature", Label.parseAbsolute("//forbidden:rule")))
+ .isFalse();
+ }
+
+ @Test
+ public void isFeatureEnabledForRule_FailsIfNotPresentInPolicyList() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.of("newFeature", PackageSpecification.everything()),
+ ImmutableMap.<String, String>of());
+
+ try {
+ config.isFeatureEnabledForRule("newFeature", Label.parseAbsolute("//:rule"));
+ fail("Expected an exception.");
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().contains("No such feature: newFeature");
+ }
+ }
+
+ @Test
+ public void getPolicyForFeature_ReturnsValueFromPolicy() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.of("newFeature", PackageSpecification.everything()),
+ ImmutableMap.<String, String>of("newFeature", "my policy"));
+
+ assertThat(config.getPolicyForFeature("newFeature")).isEqualTo("my policy");
+ }
+
+ @Test
+ public void getPolicyForFeature_FailsIfNotPresentInPolicyList() throws Exception {
+ FeaturePolicyConfiguration config =
+ new FeaturePolicyConfiguration(
+ ImmutableSetMultimap.of("newFeature", PackageSpecification.everything()),
+ ImmutableMap.<String, String>of());
+
+ try {
+ config.getPolicyForFeature("newFeature");
+ fail("Expected an exception.");
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().contains("No such feature: newFeature");
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoaderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoaderTest.java
new file mode 100644
index 0000000000..a28752a0fd
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoaderTest.java
@@ -0,0 +1,322 @@
+// 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.devtools.build.lib.analysis.featurecontrol;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the FeaturePolicyLoader. */
+@RunWith(JUnit4.class)
+public final class FeaturePolicyLoaderTest extends AnalysisTestCase {
+
+ private FeaturePolicyConfiguration getFragmentWithFeatures(
+ Iterable<String> allowedFeatures, Collection<String> args) throws Exception {
+ ConfigurationEnvironment env =
+ new ConfigurationEnvironment.TargetProviderEnvironment(
+ skyframeExecutor.getPackageManager(), reporter, directories);
+ BuildOptions options =
+ BuildOptions.of(
+ ImmutableList.<Class<? extends FragmentOptions>>of(FeaturePolicyOptions.class),
+ args.toArray(new String[0]));
+ return (FeaturePolicyConfiguration)
+ new FeaturePolicyLoader(allowedFeatures).create(env, options);
+ }
+
+ @Test
+ public void specifiedFeaturesGetListedAccessPolicy() throws Exception {
+ scratch.file(
+ "policy/BUILD",
+ "package_group(",
+ " name='featured',",
+ " packages=[",
+ " '//direct',",
+ " '//recursive/...',",
+ " ])");
+ FeaturePolicyConfiguration fragment =
+ getFragmentWithFeatures(
+ ImmutableSet.of("defaultFeature", "policiedFeature"),
+ ImmutableList.of("--feature_control_policy=policiedFeature=//policy:featured"));
+ assertThat(fragment.getPolicyForFeature("policiedFeature")).isEqualTo("//policy:featured");
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//:rule")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//arbitrary:rule")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//policy:featured")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//direct:allow")))
+ .isTrue();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//direct/child:allow")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//recursive:allow")))
+ .isTrue();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//recursive/child:allow")))
+ .isTrue();
+ }
+
+ @Test
+ public void resolvesIncludedPackageGroups() throws Exception {
+ scratch.file(
+ "policy/BUILD",
+ "package_group(",
+ " name='main',",
+ " packages=['//a'],",
+ " includes=[':include'])",
+ "package_group(",
+ " name='include',",
+ " packages=['//b'])");
+ FeaturePolicyConfiguration fragment =
+ getFragmentWithFeatures(
+ ImmutableSet.of("defaultFeature", "policiedFeature"),
+ ImmutableList.of("--feature_control_policy=policiedFeature=//policy:main"));
+ assertThat(fragment.getPolicyForFeature("policiedFeature")).isEqualTo("//policy:main");
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//arbitrary:rule")))
+ .isFalse();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//a:a")))
+ .isTrue();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//b:b")))
+ .isTrue();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//c:c")))
+ .isFalse();
+ }
+
+ @Test
+ public void resolvesAliasToPolicy() throws Exception {
+ scratch.file(
+ "policy/BUILD",
+ "alias(",
+ " name='aliased',",
+ " actual=':featured')",
+ "package_group(",
+ " name='featured',",
+ " packages=[",
+ " '//direct',",
+ " '//recursive/...',",
+ " ])");
+ FeaturePolicyConfiguration fragment =
+ getFragmentWithFeatures(
+ ImmutableSet.of("defaultFeature", "policiedFeature"),
+ ImmutableList.of("--feature_control_policy=policiedFeature=//policy:aliased"));
+ assertThat(fragment.getPolicyForFeature("policiedFeature")).isEqualTo("//policy:aliased");
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//arbitrary:rule")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//policy:aliased")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//policy:featured")))
+ .isFalse();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//direct:allow")))
+ .isTrue();
+ }
+
+ @Test
+ public void resolvesAliasToIncludesInPackageGroups() throws Exception {
+ scratch.file(
+ "policy/BUILD",
+ "package_group(",
+ " name='main',",
+ " packages=['//a'],",
+ " includes=[':aliased'])",
+ "alias(",
+ " name='aliased',",
+ " actual=':include')",
+ "package_group(",
+ " name='include',",
+ " packages=['//b'])");
+ FeaturePolicyConfiguration fragment =
+ getFragmentWithFeatures(
+ ImmutableSet.of("defaultFeature", "policiedFeature"),
+ ImmutableList.of("--feature_control_policy=policiedFeature=//policy:main"));
+ assertThat(fragment.getPolicyForFeature("policiedFeature")).isEqualTo("//policy:main");
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//arbitrary:rule")))
+ .isFalse();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//a:a")))
+ .isTrue();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//b:b")))
+ .isTrue();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//c:c")))
+ .isFalse();
+ }
+
+ @Test
+ public void allowsCyclesInPackageGroupsAndCoversAllMembersOfCycle() throws Exception {
+ scratch.file(
+ "policy/BUILD",
+ "package_group(",
+ " name='cycle',",
+ " packages=['//a'],",
+ " includes=[':elcyc'])",
+ "package_group(",
+ " name='elcyc',",
+ " packages=['//b'],",
+ " includes=[':cycle'])");
+ FeaturePolicyConfiguration fragment =
+ getFragmentWithFeatures(
+ ImmutableSet.of("defaultFeature", "policiedFeature"),
+ ImmutableList.of("--feature_control_policy=policiedFeature=//policy:cycle"));
+ assertThat(fragment.getPolicyForFeature("policiedFeature")).isEqualTo("//policy:cycle");
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "policiedFeature", Label.parseAbsolute("//arbitrary:rule")))
+ .isFalse();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//a:a")))
+ .isTrue();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//b:b")))
+ .isTrue();
+ assertThat(fragment.isFeatureEnabledForRule("policiedFeature", Label.parseAbsolute("//c:c")))
+ .isFalse();
+ }
+
+ @Test
+ public void unspecifiedFeaturesGetUniversalAccessPolicy() throws Exception {
+ scratch.file("null/BUILD", "package_group(name='null', packages=[])");
+ FeaturePolicyConfiguration fragment =
+ getFragmentWithFeatures(
+ ImmutableSet.of("defaultFeature", "policiedFeature"),
+ ImmutableList.of("--feature_control_policy=policiedFeature=//null:null"));
+ assertThat(fragment.getPolicyForFeature("defaultFeature")).isEqualTo("//...");
+ assertThat(fragment.isFeatureEnabledForRule("defaultFeature", Label.parseAbsolute("//:rule")))
+ .isTrue();
+ assertThat(
+ fragment.isFeatureEnabledForRule(
+ "defaultFeature", Label.parseAbsolute("//arbitrary:rule")))
+ .isTrue();
+ }
+
+ @Test
+ public void throwsForFeatureWithMultiplePolicyDefinitions() throws Exception {
+ scratch.file(
+ "null/BUILD",
+ "package_group(name='null', packages=[])",
+ "package_group(name='empty', packages=[])");
+ try {
+ getFragmentWithFeatures(
+ ImmutableSet.of("duplicateFeature"),
+ ImmutableList.of(
+ "--feature_control_policy=duplicateFeature=//null:null",
+ "--feature_control_policy=duplicateFeature=//null:empty"));
+ fail("Expected an exception");
+ } catch (InvalidConfigurationException expected) {
+ assertThat(expected).hasMessageThat().contains("Multiple definitions");
+ assertThat(expected).hasMessageThat().contains("duplicateFeature");
+ }
+ }
+
+ @Test
+ public void throwsForFeatureNotSpecifiedInLoader() throws Exception {
+ scratch.file("null/BUILD", "package_group(name='null', packages=[])");
+ try {
+ getFragmentWithFeatures(
+ ImmutableSet.of("otherFeature"),
+ ImmutableList.of("--feature_control_policy=missingFeature=//null:null"));
+ fail("Expected an exception");
+ } catch (InvalidConfigurationException expected) {
+ assertThat(expected).hasMessageThat().contains("No such feature");
+ assertThat(expected).hasMessageThat().contains("missingFeature");
+ }
+ }
+
+ @Test
+ public void throwsForFeatureWithNonexistentPolicy() throws Exception {
+ scratch.file("null/BUILD", "package_group(name='null', packages=[])");
+ try {
+ getFragmentWithFeatures(
+ ImmutableSet.of("brokenFeature"),
+ ImmutableList.of("--feature_control_policy=brokenFeature=//null:missing"));
+ fail("Expected an exception");
+ } catch (InvalidConfigurationException expected) {
+ assertThat(expected).hasMessageThat().contains("no such target '//null:missing'");
+ }
+ }
+
+ @Test
+ public void throwsForFeatureWithPolicyInNonexistentPackage() throws Exception {
+ try {
+ getFragmentWithFeatures(
+ ImmutableSet.of("brokenFeature"),
+ ImmutableList.of("--feature_control_policy=brokenFeature=//missing:missing"));
+ fail("Expected an exception");
+ } catch (InvalidConfigurationException expected) {
+ assertThat(expected).hasMessageThat().contains("no such package 'missing'");
+ }
+ }
+
+ @Test
+ public void throwsForFeatureWithNonPackageGroupPolicy() throws Exception {
+ scratch.file("policy/BUILD", "filegroup(name='non_package_group')");
+ try {
+ getFragmentWithFeatures(
+ ImmutableSet.of("brokenFeature"),
+ ImmutableList.of("--feature_control_policy=brokenFeature=//policy:non_package_group"));
+ fail("Expected an exception");
+ } catch (InvalidConfigurationException expected) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "//policy:non_package_group is not a package_group in brokenFeature feature policy");
+ }
+ }
+
+ @Test
+ public void throwsForFeatureWithNonRulePolicy() throws Exception {
+ scratch.file("policy/BUILD", "exports_files(['not_even_a_rule'])");
+ try {
+ getFragmentWithFeatures(
+ ImmutableSet.of("brokenFeature"),
+ ImmutableList.of("--feature_control_policy=brokenFeature=//policy:not_even_a_rule"));
+ fail("Expected an exception");
+ } catch (InvalidConfigurationException expected) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "//policy:not_even_a_rule is not a package_group in brokenFeature feature policy");
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptionsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptionsTest.java
new file mode 100644
index 0000000000..72f17c66d2
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptionsTest.java
@@ -0,0 +1,57 @@
+// 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.devtools.build.lib.analysis.featurecontrol;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import com.google.devtools.common.options.Options;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the FeaturePolicyOptions. */
+@RunWith(JUnit4.class)
+public final class FeaturePolicyOptionsTest {
+
+ @Test
+ public void testEquality() throws Exception {
+ new EqualsTester()
+ .addEqualityGroup(
+ Options.getDefaults(FeaturePolicyOptions.class),
+ Options.getDefaults(FeaturePolicyOptions.class))
+ .addEqualityGroup(
+ Options.parse(
+ FeaturePolicyOptions.class,
+ "--feature_control_policy=feature=//test:rest",
+ "--feature_control_policy=future=//nest:best")
+ .getOptions())
+ .testEquals();
+ }
+
+ @Test
+ public void testHostVersionCopiesPolicies() throws Exception {
+ FeaturePolicyOptions base =
+ Options.parse(
+ FeaturePolicyOptions.class,
+ "--feature_control_policy=feature=//test:rest",
+ "--feature_control_policy=future=//nest:best")
+ .getOptions();
+ FeaturePolicyOptions host = base.getHost(false);
+ FeaturePolicyOptions hostFallback = base.getHost(true);
+ assertThat(host.policies).containsExactlyElementsIn(base.policies);
+ assertThat(hostFallback.policies).containsExactlyElementsIn(base.policies);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverterTest.java b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverterTest.java
new file mode 100644
index 0000000000..d673450efb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverterTest.java
@@ -0,0 +1,142 @@
+// 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.devtools.build.lib.analysis.featurecontrol;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.common.options.OptionsParsingException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the policy entry option converter. */
+@RunWith(JUnit4.class)
+public final class PolicyEntryConverterTest {
+
+ @Test
+ public void convert_successfullyConvertsValidPair() throws Exception {
+ assertThat(new PolicyEntryConverter().convert("jeepers=//creepers:peepers"))
+ .isEqualTo(PolicyEntry.create("jeepers", Label.parseAbsolute("//creepers:peepers")));
+ }
+
+ @Test
+ public void convert_successfullyConvertsValidLabelWithEqualsSign() throws Exception {
+ assertThat(new PolicyEntryConverter().convert("slamjam=//whoomp:it=there"))
+ .isEqualTo(PolicyEntry.create("slamjam", Label.parseAbsolute("//whoomp:it=there")));
+ }
+
+ @Test
+ public void convert_acceptsBarePackageNameAsDefaultTarget() throws Exception {
+ assertThat(new PolicyEntryConverter().convert("two=//go"))
+ .isEqualTo(PolicyEntry.create("two", Label.parseAbsolute("//go:go")));
+ }
+
+ @Test
+ public void convert_acceptsRepositoryPrefixedLabel() throws Exception {
+ assertThat(new PolicyEntryConverter().convert("here=@somewhere//else:where"))
+ .isEqualTo(PolicyEntry.create("here", Label.parseAbsolute("@somewhere//else:where")));
+ }
+
+ @Test
+ public void convert_acceptsBareRepository() throws Exception {
+ assertThat(new PolicyEntryConverter().convert("aliens=@space"))
+ .isEqualTo(PolicyEntry.create("aliens", Label.parseAbsolute("@space//:space")));
+ }
+
+ @Test
+ public void convert_failsToConvertWithoutDivider() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("hack//sign:on");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat().contains("missing =");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertLabelAlone() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("//plain:label");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat().contains("missing =");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertFeatureIdAlone() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("plainFeature");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat().contains("missing =");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertEmptyFeature() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("=//R1:C1");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat().contains("feature cannot be empty");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertEmptyLabel() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("warp=");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat().contains("label cannot be empty");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertInvalidLabel() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("wrong=//wrong:wrong//wrong");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat()
+ .contains("target names may not contain '//' path separators");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertRelativeLabel() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("wrong=wrong:wrong");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat()
+ .contains("invalid label: wrong:wrong");
+ }
+ }
+
+ @Test
+ public void convert_failsToConvertFilesystemPathLabel() throws Exception {
+ try {
+ new PolicyEntryConverter().convert("wrong=/usr/local/google/tmp/filename.ext");
+ fail("Expected an exception.");
+ } catch (OptionsParsingException expected) {
+ assertThat(expected).hasMessageThat()
+ .contains("invalid label: /usr/local/google/tmp/filename.ext");
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 1c8b7fcd45..34cfc71313 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.lib.analysis.mock;
+import static com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider.FEATURE_POLICY_FEATURES;
+
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -22,6 +24,7 @@ import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.PlatformConfigurationLoader;
import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.featurecontrol.FeaturePolicyLoader;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.bazel.rules.BazelConfiguration;
import com.google.devtools.build.lib.bazel.rules.BazelConfigurationCollection;
@@ -271,6 +274,7 @@ public final class BazelAnalysisMock extends AnalysisMock {
new J2ObjcConfiguration.Loader(),
new ProtoConfiguration.Loader(),
new ConfigFeatureFlagConfiguration.Loader(),
+ new FeaturePolicyLoader(FEATURE_POLICY_FEATURES),
new AndroidConfiguration.Loader(),
new PlatformConfigurationLoader());
}