| // Copyright 2021 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.rules.config; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Starlark-integration Tests for the ConfigFeatureFlagTransitionFactory. */ |
| @RunWith(JUnit4.class) |
| public final class StarlarkConfigFeatureFlagTransitionFactoryTest extends BuildViewTestCase { |
| |
| @Override |
| protected ConfiguredRuleClassProvider createRuleClassProvider() { |
| ConfiguredRuleClassProvider.Builder builder = |
| new ConfiguredRuleClassProvider.Builder().addRuleDefinition(new FeatureFlagSetterRule()); |
| TestRuleClassProvider.addStandardRules(builder); |
| return builder.build(); |
| } |
| |
| private void setupRulesBzl() throws Exception { |
| scratch.file("rules/BUILD", ""); |
| scratch.file( |
| "rules/rule.bzl", |
| "def _blank_impl(ctx):", |
| " return []", |
| "def _check_impl(ctx):", |
| " if ctx.attr.succeed:", |
| " return []", |
| " else:", |
| " fail('Rule has failed intentionally.')", |
| "feature_flag_setter = rule(", |
| " attrs = {", |
| " 'flag_values': attr.label_keyed_string_dict(),", |
| " 'deps': attr.label_list(),", |
| " },", |
| " cfg = config_common.config_feature_flag_transition('flag_values'),", |
| " implementation = _blank_impl,", |
| ")", |
| "check_something = rule(", |
| " attrs = {", |
| " 'succeed': attr.bool(),", |
| " },", |
| " implementation = _check_impl,", |
| ")"); |
| } |
| |
| @Test |
| public void setsFeatureFlagSuccessfully() throws Exception { |
| setupRulesBzl(); |
| scratch.file( |
| "foo/BUILD", |
| "load('//rules:rule.bzl', 'feature_flag_setter', 'check_something')", |
| "config_feature_flag(", |
| " name = 'fruit',", |
| " allowed_values = ['orange', 'apple', 'lemon'],", |
| " default_value = 'orange',", |
| ")", |
| "config_setting(", |
| " name = 'is_apple',", |
| " flag_values = {':fruit': 'apple'},", |
| " transitive_configs = [':fruit'],", |
| ")", |
| "feature_flag_setter(", |
| " name = 'top',", |
| " flag_values = {':fruit': 'apple'},", |
| " deps = [':some_dep'],", |
| " transitive_configs = [':fruit'],", |
| ")", |
| "check_something(", |
| " name = 'some_dep',", |
| " succeed = select({", |
| " ':is_apple': True,", |
| " '//conditions:default': False,", |
| " }),", |
| " transitive_configs = [':fruit'],", |
| ")"); |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| "package_group(", |
| " name = 'config_feature_flag',", |
| " packages = ['//foo/...'],", |
| ")", |
| "package_group(", |
| " name = 'config_feature_flag_setter',", |
| " packages = ['//rules/...'],", |
| ")"); |
| assertThat(getConfiguredTarget("//foo:top")).isNotNull(); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void failsWhenFeatureFlagSuccessfullySetToBadValue() throws Exception { |
| // This is mostly a test of the testing infrastructure itself. |
| // Want to ensure check_something isn't spuriously passing for whatever reason. |
| setupRulesBzl(); |
| scratch.file( |
| "foo/BUILD", |
| "load('//rules:rule.bzl', 'feature_flag_setter', 'check_something')", |
| "config_feature_flag(", |
| " name = 'fruit',", |
| " allowed_values = ['orange', 'apple', 'lemon'],", |
| " default_value = 'orange',", |
| ")", |
| "config_setting(", |
| " name = 'is_apple',", |
| " flag_values = {':fruit': 'apple'},", |
| " transitive_configs = [':fruit'],", |
| ")", |
| "feature_flag_setter(", |
| " name = 'top',", |
| " flag_values = {':fruit': 'orange'},", |
| " deps = [':some_dep'],", |
| " transitive_configs = [':fruit'],", |
| ")", |
| "check_something(", |
| " name = 'some_dep',", |
| " succeed = select({", |
| " ':is_apple': True,", |
| " '//conditions:default': False,", |
| " }),", |
| " transitive_configs = [':fruit'],", |
| ")"); |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| "package_group(", |
| " name = 'config_feature_flag',", |
| " packages = ['//foo/...'],", |
| ")", |
| "package_group(", |
| " name = 'config_feature_flag_setter',", |
| " packages = ['//rules/...'],", |
| ")"); |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//foo:top"); |
| assertContainsEvent("Error in fail: Rule has failed intentionally."); |
| } |
| |
| @Test |
| public void failsWhenInstanceNotInAllowlist() throws Exception { |
| setupRulesBzl(); |
| scratch.file( |
| "bar/BUILD", |
| "config_feature_flag(", |
| " name = 'fruit',", |
| " allowed_values = ['orange', 'apple', 'lemon'],", |
| " default_value = 'orange',", |
| ")"); |
| scratch.file( |
| "foo/BUILD", |
| "load('//rules:rule.bzl', 'feature_flag_setter')", |
| "feature_flag_setter(", |
| " name = 'top',", |
| " flag_values = {'//bar:fruit': 'apple'},", |
| " transitive_configs = ['//bar:fruit'],", |
| ")"); |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| "package_group(", |
| " name = 'config_feature_flag',", |
| " packages = ['//bar/...'],", |
| ")", |
| "package_group(", |
| " name = 'config_feature_flag_setter',", |
| " packages = ['//rules/...'],", |
| ")"); |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//foo:top"); |
| assertContainsEvent("the attribute flag_values is not available in this package"); |
| } |
| |
| @Test |
| public void failsWhenRuleClassNotInSetterAllowlist() throws Exception { |
| setupRulesBzl(); |
| scratch.file( |
| "foo/BUILD", |
| "load('//rules:rule.bzl', 'feature_flag_setter')", |
| "config_feature_flag(", |
| " name = 'fruit',", |
| " allowed_values = ['orange', 'apple', 'lemon'],", |
| " default_value = 'orange',", |
| ")", |
| "feature_flag_setter(", |
| " name = 'top',", |
| " flag_values = {':fruit': 'apple'},", |
| " transitive_configs = [':fruit'],", |
| ")"); |
| scratch.overwriteFile( |
| "tools/allowlists/config_feature_flag/BUILD", |
| "package_group(", |
| " name = 'config_feature_flag',", |
| " packages = ['//foo/...'],", |
| ")", |
| "package_group(", |
| " name = 'config_feature_flag_setter',", |
| " packages = [],", |
| ")"); |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//foo:top"); |
| assertContainsEvent("rule class is not allowed access to feature flags setter transition"); |
| } |
| } |