| // 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", "check_something", "feature_flag_setter") |
| |
| 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"}, |
| transitive_configs = [":fruit"], |
| deps = [":some_dep"], |
| ) |
| |
| 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", "check_something", "feature_flag_setter") |
| |
| 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"}, |
| transitive_configs = [":fruit"], |
| deps = [":some_dep"], |
| ) |
| |
| 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"); |
| } |
| } |