blob: ae68268c8327cdffce3a57dda091928c27627ab2 [file] [log] [blame]
// 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.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");
@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)
.addStarlarkAccessibleTopLevels("overridable_symbol", "original_value")
.addStarlarkAccessibleTopLevels("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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
Set<String> buildBzlNames = starlarkEnv.getUninjectedBuildBzlEnv().keySet();
Set<String> workspaceBzlNames = starlarkEnv.getUninjectedWorkspaceBzlEnv().keySet();
assertThat(buildBzlNames).isEqualTo(workspaceBzlNames);
}
@Test
public void buildAndWorkspaceBzlEnvsAreSameExceptForNative() throws Exception {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 = pkgFactory.getBazelStarlarkEnvironment().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) {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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) {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
InjectionException ex =
assertThrows(
InjectionException.class,
() ->
starlarkEnv.createBuildEnvUsingInjection(
exportedRules, /*overridesList=*/ ImmutableList.of()));
assertThat(ex).hasMessageThat().contains(message);
}
@Test
public void buildBzlInjection() throws Exception {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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 {
BazelStarlarkEnvironment starlarkEnv = pkgFactory.getBazelStarlarkEnvironment();
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");
}
}