diff options
author | mstaib <mstaib@google.com> | 2017-06-28 23:45:56 +0200 |
---|---|---|
committer | Marcel Hlopko <hlopko@google.com> | 2017-06-29 09:33:43 +0200 |
commit | 199624bdc59a36cda9920f804e9933954de6ce95 (patch) | |
tree | 39ac46a0d2004afd98000699c51818e11c4cd207 /src | |
parent | 1b9933a96e6cc4217e8d8c3e6fe0646a345c680e (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')
15 files changed, 1138 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index fe6e13701f..9be7d831d6 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -37,6 +37,7 @@ filegroup( "//src/main/java/com/google/devtools/build/lib/rules/genquery:srcs", "//src/main/java/com/google/devtools/build/lib/rules/genrule:srcs", "//src/main/java/com/google/devtools/build/lib/rules/objc:srcs", + "//src/main/java/com/google/devtools/build/lib/analysis/featurecontrol:srcs", "//src/main/java/com/google/devtools/build/lib/analysis/platform:srcs", "//src/main/java/com/google/devtools/build/lib/rules/platform:srcs", "//src/main/java/com/google/devtools/build/lib/sandbox:srcs", @@ -701,6 +702,7 @@ java_library( ":util", ":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/buildeventstream/proto:build_event_stream_java_proto", "//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/main/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD new file mode 100644 index 0000000000..a20987fbe4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/BUILD @@ -0,0 +1,26 @@ +# Description: +# Configuration mechanisms for rolling out and deprecating pieces of Bazel functionality. + +package( + default_visibility = ["//src:__subpackages__"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) + +java_library( + name = "featurecontrol", + srcs = glob(["*.java"]), + 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:util", + "//src/main/java/com/google/devtools/build/lib/cmdline", + "//src/main/java/com/google/devtools/common/options", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + ], +) diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java new file mode 100644 index 0000000000..66e91ca127 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java @@ -0,0 +1,124 @@ +// 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 com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.util.Preconditions; + +/** + * A configuration fragment which controls access to features being rolled out or deprecated, + * allowing such features to be limited to particular packages to avoid excessive spread when + * rolling out or deprecating a feature. It's controlled by {@link FeaturePolicyOptions}. + * + * <p>A "feature", for this package's purposes, is any combination of related functionality which + * should be limited to specified packages. Because this is part of the configuration, it can only + * be accessed during the analysis phase; decisions which are made during the loading phase can't + * use this information. Some examples of use cases for this fragment: + * <ul> + * <li>A new rule class which could have a major impact on Blaze's memory usage is added. To + * limit this impact during the experimental phase, a feature policy is added which makes it + * an error for rules of that class to be created - or used by other rules - in any package + * other than those defined by the policy. The policy is populated with projects who are + * doing guided experimentation with the feature, and gradually expands as the feature rolls + * out. Then the feature's policy can be removed, making it generally available. + * <li>An attribute is being deprecated. To prevent rollback, a feature policy is added which + * makes it an error for rules in packages other than those defined by the policy to specify + * a value for that attribute. The policy is populated with existing users, and as those + * users are migrated, they are removed from the policy until it is completely empty. Then + * the attribute can be removed entirely. + * </ul> + * + * <p>To use this package: + * <ol> + * <li>Define a feature ID in the {@link FeaturePolicyLoader}'s constructor (in the + * RuleClassProvider). This is the string that will be used when checking for the feature in + * rule code, as well as the string used in the flag value for {@link FeaturePolicyOptions}. + * <li>In the RuleClass(es) which will change based on the feature's state, declare + * {@link FeaturePolicyConfiguration} as a required configuration fragment. + * <li>In the ConfiguredTargetFactory of those rules, get the FeaturePolicyConfiguration and + * check {@link #isFeatureEnabledForRule(String,Label)} with the feature ID created in + * step 1 and the label of the current rule. In the event that an error needs to be + * displayed, use {@link #getPolicyForFeature(String)} to show the user where the policy is. + * <li>Create a package_group containing the list of packages which should have access to this + * feature. It can be empty (no packages can access the feature) or contain //... (all + * packages can access the feature) to begin with. + * <li>After the a release containing the feature ID has been pushed, update the global RC file + * with a --feature_control_policy=(your_feature)=(your_package_group) flag. You can now + * alter access to your feature by changing the package_group. + * </ol> + * + * <p>To stop using this package: + * <ol> + * <li>Your policy should be at an end state - containing all packages (a rollout which has + * become generally available) or no packages (a deprecated feature which has been totally + * cleaned up). + * <li>Make the behavior the policy controlled permanent - remove a deprecated feature, or + * remove the check on a feature which is being rolled out. + * <li>After this new version is released, remove the flag from the global rc file, and remove + * the feature ID from the constructor for {@link FeaturePolicyLoader}. + * </ol> + * + * @see FeaturePolicyLoader + */ +public final class FeaturePolicyConfiguration extends BuildConfiguration.Fragment { + + private final ImmutableSetMultimap<String, PackageSpecification> features; + private final ImmutableMap<String, String> policies; + + /** + * Creates a new FeaturePolicyConfiguration. + * + * @param features Map mapping from a feature ID to the packages which are able to access it. If a + * feature ID is not present in this mapping, it is not accessible from any package. + * @param policies Map mapping from a feature ID to a string policy to be shown to the user. A + * string must be present as a key in this mapping to be considered a valid feature ID. + */ + public FeaturePolicyConfiguration( + ImmutableSetMultimap<String, PackageSpecification> features, + ImmutableMap<String, String> policies) { + this.features = features; + this.policies = policies; + } + + /** + * Returns whether, according to the current policy, the given feature is enabled for the given + * rule. + */ + public boolean isFeatureEnabledForRule(String feature, Label rule) { + Preconditions.checkArgument(policies.containsKey(feature), "No such feature: %s", feature); + ImmutableSet<PackageSpecification> result = features.get(feature); + for (PackageSpecification spec : result) { + if (spec.containsPackage(rule.getPackageIdentifier())) { + return true; + } + } + return false; + } + + /** + * Returns a String suitable for presenting to the user which represents the policy used for the + * given feature. + */ + public String getPolicyForFeature(String feature) { + String result = policies.get(feature); + Preconditions.checkArgument(result != null, "No such feature: %s", feature); + return result; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java new file mode 100644 index 0000000000..9e2c856835 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java @@ -0,0 +1,165 @@ +// 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 com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.devtools.build.lib.analysis.RedirectChaser; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +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.ConfigurationFragmentFactory; +import com.google.devtools.build.lib.analysis.config.FragmentOptions; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.NoSuchPackageException; +import com.google.devtools.build.lib.packages.NoSuchTargetException; +import com.google.devtools.build.lib.packages.PackageGroup; +import com.google.devtools.build.lib.packages.PackageSpecification; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.util.Preconditions; +import java.util.ArrayDeque; +import java.util.LinkedHashSet; +import java.util.Queue; +import java.util.Set; +import javax.annotation.Nullable; + +/** A loader for the FeaturePolicyConfiguration fragment. */ +public final class FeaturePolicyLoader implements ConfigurationFragmentFactory { + + private final ImmutableSet<String> permittedFeatures; + + public FeaturePolicyLoader(Iterable<String> permittedFeatures) { + this.permittedFeatures = ImmutableSet.copyOf(permittedFeatures); + for (String permittedFeature : this.permittedFeatures) { + Preconditions.checkArgument( + !permittedFeature.contains("="), "Feature names cannot contain ="); + } + } + + @Override + @Nullable + public BuildConfiguration.Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions) + throws InvalidConfigurationException, InterruptedException { + ImmutableSetMultimap.Builder<String, PackageSpecification> features = + new ImmutableSetMultimap.Builder<>(); + ImmutableMap.Builder<String, String> policies = new ImmutableMap.Builder<>(); + LinkedHashSet<String> unseenFeatures = new LinkedHashSet<>(permittedFeatures); + + for (PolicyEntry entry : buildOptions.get(FeaturePolicyOptions.class).policies) { + if (!permittedFeatures.contains(entry.getFeature())) { + // It would be so nice to be able to do this during parsing... but because options are + // parsed statically through reflection, we have no way of doing this until we get to the + // configuration loader. + throw new InvalidConfigurationException("No such feature: " + entry.getFeature()); + } + if (!unseenFeatures.remove(entry.getFeature())) { + // TODO(mstaib): Perhaps we should allow for overriding or concatenation here? + throw new InvalidConfigurationException( + "Multiple definitions of the rollout policy for feature " + + entry.getFeature() + + ". To use multiple package_groups, use a package_group with the includes " + + "attribute instead."); + } + + Iterable<PackageSpecification> packageSpecifications = + getAllPackageSpecificationsForPackageGroup( + env, entry.getPackageGroupLabel(), entry.getFeature()); + + if (packageSpecifications == null) { + return null; + } + + features.putAll(entry.getFeature(), packageSpecifications); + policies.put(entry.getFeature(), entry.getPackageGroupLabel().toString()); + } + + // Default to universal access for all features not declared. + for (String unseenFeature : unseenFeatures) { + features.put(unseenFeature, PackageSpecification.everything()); + policies.put(unseenFeature, "//..."); + } + + return new FeaturePolicyConfiguration(features.build(), policies.build()); + } + + /** + * Evaluates, recursively, the given package group. Returns {@code null} in the case of missing + * Skyframe dependencies. + */ + @Nullable + private static Iterable<PackageSpecification> getAllPackageSpecificationsForPackageGroup( + ConfigurationEnvironment env, Label packageGroupLabel, String feature) + throws InvalidConfigurationException, InterruptedException { + String context = feature + " feature policy"; + Label actualPackageGroupLabel = RedirectChaser.followRedirects(env, packageGroupLabel, context); + if (actualPackageGroupLabel == null) { + return null; + } + + ImmutableSet.Builder<PackageSpecification> packages = new ImmutableSet.Builder<>(); + Set<Label> alreadyVisitedPackageGroups = new LinkedHashSet<>(); + Queue<Label> packageGroupsToVisit = new ArrayDeque<>(); + packageGroupsToVisit.add(actualPackageGroupLabel); + alreadyVisitedPackageGroups.add(actualPackageGroupLabel); + + while (!packageGroupsToVisit.isEmpty()) { + Target target; + try { + // This is guaranteed to succeed, because the RedirectChaser was used to get this label, + // and it will throw an InvalidConfigurationException if the target doesn't exist. + target = env.getTarget(packageGroupsToVisit.remove()); + } catch (NoSuchPackageException | NoSuchTargetException impossible) { + throw new AssertionError(impossible); + } + if (target == null) { + return null; + } + + if (!(target instanceof PackageGroup)) { + throw new InvalidConfigurationException( + target.getLabel() + " is not a package_group in " + context); + } + + PackageGroup packageGroup = (PackageGroup) target; + + packages.addAll(packageGroup.getPackageSpecifications()); + + for (Label include : packageGroup.getIncludes()) { + Label actualInclude = RedirectChaser.followRedirects(env, include, context); + if (actualInclude == null) { + return null; + } + + if (alreadyVisitedPackageGroups.add(actualInclude)) { + packageGroupsToVisit.add(actualInclude); + } + } + } + + return packages.build(); + } + + @Override + public Class<? extends BuildConfiguration.Fragment> creates() { + return FeaturePolicyConfiguration.class; + } + + @Override + public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() { + return ImmutableSet.<Class<? extends FragmentOptions>>of(FeaturePolicyOptions.class); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java new file mode 100644 index 0000000000..2b43fc2e06 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java @@ -0,0 +1,48 @@ +// 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 com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.analysis.config.FragmentOptions; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsParser.OptionUsageRestrictions; +import java.util.List; + +/** The options fragment which defines {@link FeaturePolicyConfiguration}. */ +public final class FeaturePolicyOptions extends FragmentOptions { + /** The mapping from features to their associated package groups. */ + @Option( + name = "feature_control_policy", + help = + "Policy used to limit the rollout or deprecation of features within the Bazel binary to " + + "specific packages. Pass a mapping from a feature name to the package group used to " + + "control access to that feature, in the form feature=//label/of:package_group (note " + + "that unlike visibility, packages cannot be directly specified a la " + + "//package:__pkg__ or //visibility:public). Can be repeated to specify multiple " + + "features, but each feature must be specified only once.", + valueHelp = "a feature=label pair", + optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED, + converter = PolicyEntryConverter.class, + defaultValue = "n/a (default ignored for allowMultiple)", + allowMultiple = true + ) + public List<PolicyEntry> policies = ImmutableList.<PolicyEntry>of(); + + @Override + public FeaturePolicyOptions getHost(boolean fallback) { + // host options are the same as target options + return (FeaturePolicyOptions) clone(); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java new file mode 100644 index 0000000000..e0982a25df --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java @@ -0,0 +1,33 @@ +// 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 com.google.auto.value.AutoValue; +import com.google.devtools.build.lib.cmdline.Label; + +/** Policy value object encoding the package group which can access a given feature. */ +@AutoValue +public abstract class PolicyEntry { + /** Creates a new PolicyEntry for the given feature and package_group label. */ + public static PolicyEntry create(String feature, Label packageGroupLabel) { + return new AutoValue_PolicyEntry(feature, packageGroupLabel); + } + + /** Gets the feature identifier this policy is for. */ + public abstract String getFeature(); + + /** Gets the label for the package group which controls access to this feature. */ + public abstract Label getPackageGroupLabel(); +} diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java new file mode 100644 index 0000000000..a8f997b76b --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java @@ -0,0 +1,53 @@ +// 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 com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.common.options.Converter; +import com.google.devtools.common.options.OptionsParsingException; + +/** A converter which creates a PolicyEntry from a flag value. */ +public final class PolicyEntryConverter implements Converter<PolicyEntry> { + @Override + public PolicyEntry convert(String input) throws OptionsParsingException { + int divider = input.indexOf('='); + if (divider == -1) { + throw new OptionsParsingException( + "value must be of the form feature=label; missing ="); + } + String feature = input.substring(0, divider); + if (feature.isEmpty()) { + throw new OptionsParsingException( + "value must be of the form feature=label; feature cannot be empty"); + } + String label = input.substring(divider + 1); + if (label.isEmpty()) { + throw new OptionsParsingException( + "value must be of the form feature=label; label cannot be empty"); + } + try { + return PolicyEntry.create(feature, Label.parseAbsolute(label)); + } catch (LabelSyntaxException ex) { + throw new OptionsParsingException( + "value must be of the form feature=label; " + ex.getMessage()); + } + } + + @Override + public String getTypeDescription() { + return "a feature=label pair"; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java index ff1542b565..354442c871 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java @@ -27,6 +27,8 @@ import com.google.devtools.build.lib.analysis.PlatformConfigurationLoader; import com.google.devtools.build.lib.analysis.PlatformOptions; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.constraints.EnvironmentRule; +import com.google.devtools.build.lib.analysis.featurecontrol.FeaturePolicyLoader; +import com.google.devtools.build.lib.analysis.featurecontrol.FeaturePolicyOptions; import com.google.devtools.build.lib.bazel.rules.BazelToolchainType.BazelToolchainTypeRule; import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryRule; import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule; @@ -222,10 +224,15 @@ public class BazelRuleClassProvider { } }; + public static final ImmutableSet<String> FEATURE_POLICY_FEATURES = ImmutableSet.<String>of(); + public static final RuleSet CORE_RULES = new RuleSet() { @Override public void init(Builder builder) { + builder.addConfigurationOptions(FeaturePolicyOptions.class); + builder.addConfigurationFragment(new FeaturePolicyLoader(FEATURE_POLICY_FEATURES)); + builder.addRuleDefinition(new BaseRuleClasses.RootRule()); builder.addRuleDefinition(new BaseRuleClasses.BaseRule()); builder.addRuleDefinition(new BaseRuleClasses.RuleBase()); 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()); } |