| // Copyright 2020 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.packages; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static org.junit.Assert.assertThrows; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.analysis.util.MockRule; | |
| import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment.InjectionException; | |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import net.starlark.java.eval.Structure; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Unit tests for PackageFactory's management of the predeclared Starlark symbols. */ | |
| @RunWith(JUnit4.class) | |
| public final class BazelStarlarkEnvironmentTest extends BuildViewTestCase { | |
| private static final MockRule OVERRIDABLE_RULE = () -> MockRule.define("overridable_rule"); | |
| private BazelStarlarkEnvironment starlarkEnv; | |
| @Before | |
| public void setUp() { | |
| this.starlarkEnv = ruleClassProvider.getBazelStarlarkEnvironment(); | |
| } | |
| @Override | |
| protected ConfiguredRuleClassProvider createRuleClassProvider() { | |
| // Add a fake rule and top-level symbol to override. | |
| ConfiguredRuleClassProvider.Builder builder = | |
| new ConfiguredRuleClassProvider.Builder() | |
| // While reading, feel free to mentally substitute overridable_rule -> cc_library and | |
| // overridable_symbol -> CcInfo. | |
| .addRuleDefinition(OVERRIDABLE_RULE) | |
| .addBzlToplevel("overridable_symbol", "original_value") | |
| .addBzlToplevel("another_overridable_symbol", "another_original_value"); | |
| TestRuleClassProvider.addStandardRules(builder); | |
| return builder.build(); | |
| } | |
| // TODO(#11954): We want BUILD- and WORKSPACE-loaded bzl files to have the exact same environment. | |
| // In the meantime these two tests help avoid regressions. | |
| // This property is important for BzlCompileFunction, which relies on the symbol names in the env | |
| // matching even if the symbols themselves differ. | |
| @Test | |
| public void buildAndWorkspaceBzlEnvsDeclareSameNames() throws Exception { | |
| Set<String> buildBzlNames = starlarkEnv.getUninjectedBuildBzlEnv().keySet(); | |
| Set<String> workspaceBzlNames = starlarkEnv.getUninjectedWorkspaceBzlEnv().keySet(); | |
| assertThat(buildBzlNames).isEqualTo(workspaceBzlNames); | |
| } | |
| @Test | |
| public void buildAndWorkspaceBzlEnvsAreSameExceptForNative() throws Exception { | |
| Map<String, Object> buildBzlEnv = new HashMap<>(); | |
| buildBzlEnv.putAll(starlarkEnv.getUninjectedBuildBzlEnv()); | |
| buildBzlEnv.remove("native"); | |
| Map<String, Object> workspaceBzlEnv = new HashMap<>(); | |
| workspaceBzlEnv.putAll(starlarkEnv.getUninjectedWorkspaceBzlEnv()); | |
| workspaceBzlEnv.remove("native"); | |
| assertThat(buildBzlEnv).isEqualTo(workspaceBzlEnv); | |
| } | |
| @Test | |
| public void builtinsBzlEnv() throws Exception { | |
| ImmutableMap<String, Object> env = starlarkEnv.getBuiltinsBzlEnv(); | |
| // Can see general toplevel symbols. | |
| assertThat(env).containsKey("rule"); | |
| // Cannot see rule-specific toplevel symbols. | |
| assertThat(env).doesNotContainKey("overridable_symbol"); | |
| // Has the special builtins-internal module. | |
| assertThat(env).containsKey("_builtins"); | |
| } | |
| /** | |
| * Asserts that injection for a BUILD-loaded .bzl file fails, using the given maps and expecting | |
| * the given error substring. The overrides list is empty. | |
| */ | |
| private void assertBuildBzlInjectionFailure( | |
| Map<String, Object> exportedToplevels, Map<String, Object> exportedRules, String message) { | |
| InjectionException ex = | |
| assertThrows( | |
| InjectionException.class, | |
| () -> | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| exportedToplevels, exportedRules, /*overridesList=*/ ImmutableList.of())); | |
| assertThat(ex).hasMessageThat().contains(message); | |
| } | |
| /** | |
| * Asserts that injection for a BUILD file fails, using the given map and expecting the given | |
| * error substring. The overrides list is empty. | |
| */ | |
| private void assertBuildInjectionFailure(Map<String, Object> exportedRules, String message) { | |
| InjectionException ex = | |
| assertThrows( | |
| InjectionException.class, | |
| () -> | |
| starlarkEnv.createBuildEnvUsingInjection( | |
| exportedRules, /*overridesList=*/ ImmutableList.of())); | |
| assertThat(ex).hasMessageThat().contains(message); | |
| } | |
| @Test | |
| public void buildBzlInjection() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of("overridable_symbol", "new_value"), | |
| ImmutableMap.of("overridable_rule", "new_rule"), | |
| /*overridesList=*/ ImmutableList.of()); | |
| assertThat(env).containsEntry("overridable_symbol", "new_value"); | |
| assertThat(((Structure) env.get("native")).getValue("overridable_rule")).isEqualTo("new_rule"); | |
| } | |
| @Test | |
| public void buildInjection() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildEnvUsingInjection( | |
| ImmutableMap.of("overridable_rule", "new_rule"), /*overridesList=*/ ImmutableList.of()); | |
| assertThat(env).containsEntry("overridable_rule", "new_rule"); | |
| } | |
| @Test | |
| public void injectedNameMustOverrideExistingName_toplevel() throws Exception { | |
| assertBuildBzlInjectionFailure( | |
| ImmutableMap.of("brand_new_toplevel", "foo"), | |
| ImmutableMap.of(), | |
| "Injected top-level symbol 'brand_new_toplevel' must override an existing one by that" | |
| + " name"); | |
| } | |
| @Test | |
| public void injectedNameMustOverrideExistingName_rule() throws Exception { | |
| assertBuildBzlInjectionFailure( | |
| ImmutableMap.of(), | |
| ImmutableMap.of("brand_new_rule", "foo"), | |
| "Injected rule 'brand_new_rule' must override an existing one by that name"); | |
| assertBuildInjectionFailure( | |
| ImmutableMap.of("brand_new_rule", "foo"), | |
| "Injected rule 'brand_new_rule' must override an existing one by that name"); | |
| } | |
| @Test | |
| public void cannotInjectGeneralSymbol_toplevel() { | |
| assertBuildBzlInjectionFailure( | |
| ImmutableMap.of("provider", "new_builtin"), | |
| ImmutableMap.of(), | |
| "Cannot override 'provider' with an injected top-level symbol"); | |
| } | |
| @Test | |
| public void cannotInjectGeneralSymbol_nativeField() { | |
| // (Native field for bzl files, toplevel for BUILD files.) | |
| assertBuildBzlInjectionFailure( | |
| ImmutableMap.of(), | |
| ImmutableMap.of("glob", "new_builtin"), | |
| "Cannot override 'glob' with an injected rule"); | |
| assertBuildInjectionFailure( | |
| ImmutableMap.of("glob", "new_builtin"), "Cannot override 'glob' with an injected rule"); | |
| } | |
| @Test | |
| public void cannotInjectGeneralSymbol_nativeModuleItself() { | |
| assertBuildBzlInjectionFailure( | |
| ImmutableMap.of("native", "new_builtin"), | |
| ImmutableMap.of(), | |
| "Cannot override 'native' with an injected top-level symbol"); | |
| } | |
| @Test | |
| public void cannotInjectGeneralSymbol_universe() { | |
| assertBuildBzlInjectionFailure( | |
| ImmutableMap.of("len", "new_builtin"), | |
| ImmutableMap.of(), | |
| "Cannot override 'len' with an injected top-level symbol"); | |
| assertBuildInjectionFailure( | |
| ImmutableMap.of("len", "new_builtin"), "Cannot override 'len' with an injected rule"); | |
| } | |
| @Test | |
| public void injectionStatus_respectsDefault() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of( | |
| "+overridable_symbol", | |
| "new_value", | |
| "-another_overridable_symbol", | |
| "another_new_value"), | |
| ImmutableMap.of("-overridable_rule", "new_rule"), | |
| /*overridesList=*/ ImmutableList.of()); | |
| assertThat(env).containsEntry("overridable_symbol", "new_value"); | |
| assertThat(env).containsEntry("another_overridable_symbol", "another_original_value"); | |
| // Match the original rule's toString since the actual specific object is not easily accessible. | |
| Object overridableRuleValue = ((Structure) env.get("native")).getValue("overridable_rule"); | |
| assertThat(overridableRuleValue.toString()).contains("overridable_rule"); | |
| } | |
| @Test | |
| public void injectionStatus_canBeOverridden() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of( | |
| "+overridable_symbol", | |
| "new_value", | |
| "-another_overridable_symbol", | |
| "another_new_value"), | |
| ImmutableMap.of("-overridable_rule", "new_rule"), | |
| ImmutableList.of( | |
| "-overridable_symbol", "+another_overridable_symbol", "+overridable_rule")); | |
| assertThat(env).containsEntry("overridable_symbol", "original_value"); | |
| assertThat(env).containsEntry("another_overridable_symbol", "another_new_value"); | |
| assertThat(((Structure) env.get("native")).getValue("overridable_rule")).isEqualTo("new_rule"); | |
| } | |
| @Test | |
| public void injectionStatus_cannotBeOverriddenForUnprefixedKeys() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of( | |
| "overridable_symbol", | |
| "new_value", | |
| "another_overridable_symbol", | |
| "another_new_value"), | |
| ImmutableMap.of(), | |
| ImmutableList.of("+overridable_symbol", "-another_overridable_symbol")); | |
| // Both the + and - are no-ops since the keys aren't prefixed. | |
| assertThat(env).containsEntry("overridable_symbol", "new_value"); | |
| assertThat(env).containsEntry("another_overridable_symbol", "another_new_value"); | |
| } | |
| @Test | |
| public void injectionStatus_overridingUnknownKeysIsNoop() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of("-overridable_symbol", "new_value"), | |
| ImmutableMap.of(), | |
| ImmutableList.of("+overridable_symbol", "+unknown_symbol", "-another_unknown_symbol")); | |
| // Both the + and - are no-ops since the keys aren't prefixed. | |
| assertThat(env).containsEntry("overridable_symbol", "new_value"); | |
| } | |
| @Test | |
| public void injectionStatus_lastOverrideTakesPrecedence() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of("-overridable_symbol", "new_value"), | |
| ImmutableMap.of(), | |
| ImmutableList.of("+overridable_symbol", "-overridable_symbol")); | |
| // Both the + and - are no-ops since the keys aren't prefixed. | |
| assertThat(env).containsEntry("overridable_symbol", "original_value"); | |
| } | |
| @Test | |
| public void injectionStatus_invalidOverrideItem_empty() throws Exception { | |
| InjectionException ex = | |
| assertThrows( | |
| InjectionException.class, | |
| () -> | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of(""))); | |
| assertThat(ex).hasMessageThat().contains("Invalid injection override item: ''"); | |
| } | |
| @Test | |
| public void injectionStatus_invalidOverrideItem_unprefixed() throws Exception { | |
| InjectionException ex = | |
| assertThrows( | |
| InjectionException.class, | |
| () -> | |
| starlarkEnv.createBuildBzlEnvUsingInjection( | |
| ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of("foo"))); | |
| assertThat(ex).hasMessageThat().contains("Invalid injection override item: 'foo'"); | |
| } | |
| @Test | |
| public void injectionStatus_appliesToBuildFiles() throws Exception { | |
| Map<String, Object> env = | |
| starlarkEnv.createBuildEnvUsingInjection( | |
| ImmutableMap.of("+overridable_rule", "new_rule"), | |
| ImmutableList.of("-overridable_rule")); | |
| // Match the original rule's toString since the actual specific object is not easily accessible. | |
| assertThat(env.get("overridable_rule").toString()).contains("overridable_rule"); | |
| } | |
| } |