blob: 179a222f1b94e2a97d52436315c8b0c1a06f9ae3 [file] [log] [blame]
// 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.packages;
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 java.util.HashMap;
import java.util.Map;
import net.starlark.java.annot.Param;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkInt;
import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.StarlarkValue;
import net.starlark.java.eval.Tuple;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@code native.existing_rule} and {@code native.existing_rules} functions.
*
* <p>This class covers the legacy behavior where the {@code
* --incompatible_existing_rules_immutable_view} flag is disabled. The enabled case is covered by
* the subclass, {@link WithImmutableView}.
*/
@RunWith(JUnit4.class)
public class NativeExistingRulesTest extends BuildViewTestCase {
private TestStarlarkBuiltin testStarlarkBuiltin; // initialized by createRuleClassProvider()
// Intended to be overridden by this test case's subclasses. Note that overriding of JUnit's
// @Before methods is not recommended.
protected void setupOptions() throws Exception {
// --noincompatible_existing_rules_immutable_view is the default; set it explicitly for clarity.
setBuildLanguageOptions("--noincompatible_existing_rules_immutable_view");
}
@Before
public final void setUp() throws Exception {
setupOptions();
}
@StarlarkBuiltin(name = "test")
private static final class TestStarlarkBuiltin implements StarlarkValue {
private final Map<String, Object> saved = new HashMap<>();
@StarlarkMethod(
name = "save",
parameters = {
@Param(name = "name", doc = "Name under which to save the value"),
@Param(name = "value", doc = "Value to save")
},
doc = "Saves a Starlark value for testing from Java")
public synchronized void save(String name, Object value) {
saved.put(name, value);
}
}
@Override
protected ConfiguredRuleClassProvider createRuleClassProvider() {
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
testStarlarkBuiltin = new TestStarlarkBuiltin();
builder.addStarlarkAccessibleTopLevels("test", testStarlarkBuiltin);
return builder.build();
}
private Object getSaved(String name) {
return testStarlarkBuiltin.saved.get(name);
}
@Test
public void existingRule_handlesSelect() throws Exception {
scratch.file("test/starlark/BUILD");
scratch.file(
"test/starlark/rulestr.bzl", //
"def rule_dict(name):",
" return native.existing_rule(name)");
scratch.file(
"test/getrule/BUILD",
"load('//test/starlark:rulestr.bzl', 'rule_dict')",
"cc_library(",
" name ='x',",
" srcs = select({'//conditions:default': []}),",
")",
"rule_dict('x')");
// Parse the BUILD file, to make sure select() makes it out of native.existing_rule().
assertThat(getConfiguredTarget("//test/getrule:x")).isNotNull();
}
@Test
public void existingRule_returnsNone() throws Exception {
scratch.file(
"test/rulestr.bzl",
"def test_rule(name, x):",
" print(native.existing_rule(x))",
" if native.existing_rule(x) == None:",
" native.cc_library(name = name)");
scratch.file(
"test/BUILD",
"load('//test:rulestr.bzl', 'test_rule')",
"test_rule('a', 'does not exist')",
"test_rule('b', 'BUILD')"); // exists, but as a target and not a rule
assertThat(getConfiguredTarget("//test:a")).isNotNull();
assertThat(getConfiguredTarget("//test:b")).isNotNull();
}
@Test
public void existingRule_roundTripsSelect() throws Exception {
scratch.file(
"test/existing_rule.bzl",
"def macro():",
" s = select({'//foo': ['//bar']})",
" print('Passed: ' + repr(s))",
" native.cc_library(name = 'x', srcs = s)",
" print('Returned: ' + repr(native.existing_rule('x')['srcs']))",
// The value returned here should round-trip fine.
" native.cc_library(name = 'y', srcs = native.existing_rule('x')['srcs'])");
scratch.file(
"test/BUILD",
"load('//test:existing_rule.bzl', 'macro')",
"macro()",
"cc_library(name = 'a', srcs = [])");
getConfiguredTarget("//test:a");
assertContainsEvent("Passed: select({\"//foo\": [\"//bar\"]}");
// The short labels are now in their canonical form, and the sequence is represented as
// tuple instead of list, but the meaning is unchanged.
assertContainsEvent("Returned: select({\"//foo:foo\": (\"//bar:bar\",)}");
}
@Test
public void existingRule_shortensLabelsInSamePackage() throws Exception {
scratch.file(
"test/existing_rule.bzl",
"def save_deps():",
" r = native.existing_rule('b')",
" test.save(\"r['deps']\", r['deps'])");
scratch.file(
"test/BUILD",
"load('//test:existing_rule.bzl', 'save_deps')",
"cc_library(name = 'a', srcs = [])",
"cc_binary(name = 'b', deps = ['//test:a'])",
"save_deps()");
getConfiguredTarget("//test:b");
assertThat(Starlark.toIterable(getSaved("r['deps']")))
.containsExactly(":a"); // as opposed to "//test:a"
}
@Test
public void existingRules_findsRulesAndAttributes() throws Exception {
scratch.file("test/BUILD");
scratch.file("test/starlark/BUILD");
scratch.file(
"test/starlark/rulestr.bzl",
"def rule_dict(name):",
" return native.existing_rule(name)",
"def rules_dict():",
" return native.existing_rules()",
"def nop(ctx):",
" pass",
"nop_rule = rule(attrs = {'x': attr.label()}, implementation = nop)",
"def test_save(name, value):",
" test.save(name, value)");
scratch.file(
"test/getrule/BUILD",
"load('//test/starlark:rulestr.bzl', 'rules_dict', 'rule_dict', 'nop_rule', 'test_save')",
"genrule(name = 'a', outs = ['a.txt'], ",
" licenses = ['notice'],",
" output_to_bindir = False,",
" tools = [ '//test:bla' ], cmd = 'touch $@')",
"nop_rule(name = 'c', x = ':a')",
"rlist = rules_dict()",
"test_save('all_str', [rlist['a']['kind'], rlist['a']['name'],",
" rlist['c']['kind'], rlist['c']['name']])",
"adict = rule_dict('a')",
"cdict = rule_dict('c')",
"test_save('a_str', [adict['kind'], adict['name'], adict['outs'][0], adict['tools'][0]])",
"test_save('c_str', [cdict['kind'], cdict['name'], cdict['x']])",
"test_save('adict.keys()', adict.keys())");
getConfiguredTarget("//test/getrule:BUILD");
assertThat(Starlark.toIterable(getSaved("all_str")))
.containsExactly("genrule", "a", "nop_rule", "c")
.inOrder();
assertThat(Starlark.toIterable(getSaved("a_str")))
.containsExactly("genrule", "a", ":a.txt", "//test:bla")
.inOrder();
assertThat(Starlark.toIterable(getSaved("c_str")))
.containsExactly("nop_rule", "c", ":a")
.inOrder();
assertThat(Starlark.toIterable(getSaved("adict.keys()")))
.containsAtLeast(
"name",
"visibility",
"transitive_configs",
"tags",
"generator_name",
"generator_function",
"generator_location",
"features",
"compatible_with",
"target_compatible_with",
"restricted_to",
"srcs",
"tools",
"toolchains",
"outs",
"cmd",
"output_to_bindir",
"local",
"message",
"executable",
"stamp",
"heuristic_label_expansion",
"kind");
}
@Test
public void existingRule_ignoresHiddenAttributes() throws Exception {
scratch.file(
"test/inc.bzl", //
"def _check_hidden_attr_exists(ctx):",
" if ctx.attr._hidden_attr != 'hidden_val':",
" fail('ctx.attr._hidden_attr != \"hidden_val\"')",
" pass",
"my_rule = rule(",
" attrs = {",
" '_hidden_attr': attr.string(default = 'hidden_val'),",
" 'normal_attr': attr.string(default = 'normal_val'),",
" },",
" implementation = _check_hidden_attr_exists",
")",
"def f():",
" my_rule(name = 'rulename')",
" r = native.existing_rule('rulename')",
" test.save('r.keys()', r.keys())",
" test.save('r.values()', r.values())",
" test.save('\"_hidden_attr\" in r', \"_hidden_attr\" in r)");
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
assertThat(getConfiguredTarget("//test:rulename")).isNotNull();
assertThat(Starlark.toIterable(getSaved("r.keys()")))
.containsAtLeast("name", "kind", "normal_attr");
assertThat(Starlark.toIterable(getSaved("r.keys()"))).doesNotContain("_hidden_attr");
assertThat(Starlark.toIterable(getSaved("r.values()")))
.containsAtLeast("rulename", "my_rule", "normal_val");
assertThat(Starlark.toIterable(getSaved("r.values()"))).doesNotContain("hidden_val");
assertThat((Boolean) getSaved("\"_hidden_attr\" in r")).isFalse();
}
@Test
public void existingRule_returnsObjectWithCorrectMutability() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key': 'value'})",
" r = native.existing_rule('x')",
" r['no_such_attribute'] = 'foo'",
" r['define_values']['key'] = 123"); // mutate the dict
assertThat(getConfiguredTarget("//test:BUILD")).isNotNull(); // no error on mutation
}
@Test
public void existingRule_returnsDictLikeObject() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key': 'value'})",
" r = native.existing_rule('x')",
" print('r == %s' % repr(r))",
" test.save('[key for key in r]', [key for key in r])",
" test.save('list(r)', list(r))",
" test.save('r.keys()', r.keys())",
" test.save('r.values()', r.values())",
" test.save('r.items()', r.items())",
" test.save(\"r['define_values']\", r['define_values'])",
" test.save(\"r.get('define_values', 123)\", r.get('define_values', 123))",
" test.save(\"r.get('invalid_attr', 123)\", r.get('invalid_attr', 123))",
" test.save(\"'define_values' in r\", 'define_values' in r)",
" test.save(\"'invalid_attr' in r\", 'invalid_attr' in r)");
Dict<?, ?> expectedDefineValues = Dict.builder().put("key", "value").buildImmutable();
assertThat(getConfiguredTarget("//test:BUILD")).isNotNull(); // no error
assertThat(Starlark.toIterable(getSaved("[key for key in r]")))
.containsAtLeast("define_values", "name", "kind");
assertThat(Starlark.toIterable(getSaved("list(r)")))
.containsAtLeast("define_values", "name", "kind");
assertThat(Starlark.toIterable(getSaved("r.keys()")))
.containsAtLeast("define_values", "name", "kind");
assertThat(Starlark.toIterable(getSaved("r.values()")))
.containsAtLeast(expectedDefineValues, "x", "config_setting");
assertThat(Starlark.toIterable(getSaved("r.items()")))
.containsAtLeast(
Tuple.of("define_values", expectedDefineValues),
Tuple.of("name", "x"),
Tuple.of("kind", "config_setting"));
assertThat(getSaved("r['define_values']")).isEqualTo(expectedDefineValues);
assertThat(getSaved("r.get('define_values', 123)")).isEqualTo(expectedDefineValues);
assertThat(getSaved("r.get('invalid_attr', 123)")).isEqualTo(StarlarkInt.of(123));
assertThat(getSaved("'define_values' in r")).isEqualTo(true);
assertThat(getSaved("'invalid_attr' in r")).isEqualTo(false);
}
@Test
public void existingRule_asDictArgument() throws Exception {
scratch.file(
"test/test.bzl",
"def save_as_dict(r):",
" test.save('type(dict(r))', type(dict(r)))",
" test.save('dict(r)[\"name\"]', dict(r)[\"name\"])",
" test.save('dict(r)[\"kind\"]', dict(r)[\"kind\"])");
scratch.file(
"test/BUILD",
"load('//test:test.bzl', 'save_as_dict')", //
"cc_library(",
" name ='rulename',",
")",
"save_as_dict(existing_rule('rulename'))");
getConfiguredTarget("//test:rulename");
assertThat(getSaved("type(dict(r))")).isEqualTo("dict");
assertThat(getSaved("dict(r)[\"name\"]")).isEqualTo("rulename");
assertThat(getSaved("dict(r)[\"kind\"]")).isEqualTo("cc_library");
}
@Test
public void existingRule_asDictUpdateArgument() throws Exception {
// We do not test `existing_rule(r).update({...})` because `existing_rule(r)` may be immutable
// (as verified by other test cases).
scratch.file(
"test/test.bzl",
"def save_as_updated_dict(r):",
" updated_dict = {'name': 'dictname', 'dictkey': 1}",
" updated_dict.update(r)",
" test.save('updated_dict[\"name\"]', updated_dict[\"name\"])",
" test.save('updated_dict[\"kind\"]', updated_dict[\"kind\"])",
" test.save('updated_dict[\"dictkey\"]', updated_dict[\"dictkey\"])");
scratch.file(
"test/BUILD",
"load('//test:test.bzl', 'save_as_updated_dict')", //
"cc_library(",
" name ='rulename',",
")",
"save_as_updated_dict(existing_rule('rulename'))");
getConfiguredTarget("//test:rulename");
assertThat(getSaved("updated_dict[\"name\"]")).isEqualTo("rulename");
assertThat(getSaved("updated_dict[\"kind\"]")).isEqualTo("cc_library");
assertThat(getSaved("updated_dict[\"dictkey\"]")).isEqualTo(StarlarkInt.of(1));
}
@Test
public void existingRule_unionableWithDict() throws Exception {
scratch.file(
"test/test.bzl",
"def save_as_union(dict_val, r):",
" test.save('dict_val | r', dict_val | r)",
" test.save('r | dict_val', r | dict_val)",
" dict_val |= r",
" test.save('dict_val |= r', dict_val)");
scratch.file(
"test/BUILD",
"load('//test:test.bzl', 'save_as_union')",
"cc_library(",
" name ='rulename',",
")",
"save_as_union({'name': 'dictname', 'dictkey': 1}, existing_rule('rulename'))");
getConfiguredTarget("//test:rulename");
Map<String, Object> unionDictWithExistingRule =
Dict.cast(getSaved("dict_val | r"), String.class, Object.class, "dict_val | r");
assertThat(unionDictWithExistingRule)
.containsAtLeast("name", "rulename", "dictkey", StarlarkInt.of(1), "kind", "cc_library");
Map<String, Object> unionExistingRuleWithDict =
Dict.cast(getSaved("r | dict_val"), String.class, Object.class, "r | dict_val");
assertThat(unionExistingRuleWithDict)
.containsAtLeast("name", "dictname", "dictkey", StarlarkInt.of(1), "kind", "cc_library");
Map<String, Object> inPlaceUnionDictWithExistingRule =
Dict.cast(getSaved("dict_val |= r"), String.class, Object.class, "dict_val | r");
assertThat(inPlaceUnionDictWithExistingRule)
.containsAtLeast("name", "rulename", "dictkey", StarlarkInt.of(1), "kind", "cc_library");
}
@Test
public void existingRule_asKwargs() throws Exception {
scratch.file(
"test/test.bzl",
"def save_kwargs(**kwargs):",
" test.save('kwargs[\"name\"]', kwargs[\"name\"])",
" test.save('kwargs[\"kind\"]', kwargs[\"kind\"])",
"def save_kwargs_of_existing_rule(name):",
" save_kwargs(**native.existing_rule(name))");
scratch.file(
"test/BUILD",
"load('//test:test.bzl', 'save_kwargs_of_existing_rule')", //
"cc_library(",
" name ='rulename',",
")",
"save_kwargs_of_existing_rule('rulename')");
getConfiguredTarget("//test:rulename");
assertThat(getSaved("kwargs[\"name\"]")).isEqualTo("rulename");
assertThat(getSaved("kwargs[\"kind\"]")).isEqualTo("cc_library");
}
// Regression test for https://github.com/bazelbuild/bazel/issues/16256
@Test
public void existingRule_encodesToJson() throws Exception {
// We need a Starlark rule - native rules can have attribute values that the json encoder
// doesn't handle.
scratch.file(
"test/test.bzl",
"def _dummy_impl(ctx):",
" pass",
"test_library = rule(",
" implementation = _dummy_impl,",
" attrs = {'srcs': attr.label_list(allow_files = True)},",
")",
// TODO(b/249397668): simplifying this to `json_encode = json.encode` etc. causes a
// NoCodecException. Need to investigate.
"def json_encode(value):",
" return json.encode(value)",
"def json_decode(text):",
" return json.decode(text)",
"def save(name, object):",
" test.save(name, object)");
scratch.file(
"test/BUILD",
"load('//test:test.bzl', 'test_library', 'json_decode', 'json_encode', 'save')", //
"test_library(",
" name ='foo',",
" srcs = ['foo.cc'],",
")",
"save('foo', json_decode(json_encode(existing_rule('foo'))))");
scratch.file("test/foo.cc");
getConfiguredTarget("//test:foo");
// We test a subset of attributes after an encode-decode round trip because the rule also has
// default attributes with default values, which will get encoded to json and which will change
// whenever default attributes get introduced, making string comparison of encoded json fragile.
Map<String, Object> jsonRoundTripValue =
Dict.cast(
getSaved("foo"), String.class, Object.class, "json round trip of existing_rule('foo')");
assertThat(jsonRoundTripValue)
.containsAtLeast(
"name", "foo", "kind", "test_library", "srcs", StarlarkList.immutableOf(":foo.cc"));
}
@Test
public void existingRules_returnsObjectWithCorrectMutability() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key': 'value'})",
" rs = native.existing_rules()",
" rs['no_such_rule'] = {'name': 'no_such_rule', 'kind': 'config_setting'}"); // mutate
assertThat(getConfiguredTarget("//test:BUILD")).isNotNull(); // no error on mutation
}
@Test
public void existingRules_returnsDictLikeObject() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key_x': 'value_x'})",
" native.config_setting(name='y', define_values={'key_y': 'value_y'})",
" rs = native.existing_rules()",
" print('rs == %s' % repr(rs))",
" test.save('[key for key in rs]', [key for key in rs])",
" test.save('list(rs)', list(rs))",
" test.save('rs.keys()', rs.keys())",
" test.save(\"[v['name'] for v in rs.values()]\", [v['name'] for v in rs.values()])",
" test.save(\"[(i[0], i[1]['name']) for i in rs.items()]\", [(i[0], i[1]['name']) for i in"
+ " rs.items()])",
" test.save(\"rs['x']['define_values']\", rs['x']['define_values'])",
" test.save(\"rs.get('x', {'name': 'z'})['name']\", rs.get('x', {'name': 'z'})['name'])",
" test.save(\"rs.get('invalid_rule', {'name': 'invalid_rule'})\", rs.get('invalid_rule',"
+ " {'name': 'invalid_rule'}))",
" test.save(\"'x' in rs\", 'x' in rs)",
" test.save(\"'invalid_rule' in rs\", 'invalid_rule' in rs)");
assertThat(getConfiguredTarget("//test:BUILD")).isNotNull(); // no error
assertThat(Starlark.toIterable(getSaved("[key for key in rs]"))).containsExactly("x", "y");
assertThat(Starlark.toIterable(getSaved("list(rs)"))).containsExactly("x", "y");
assertThat(Starlark.toIterable(getSaved("rs.keys()"))).containsExactly("x", "y");
assertThat(Starlark.toIterable(getSaved("[v['name'] for v in rs.values()]")))
.containsExactly("x", "y");
assertThat(Starlark.toIterable(getSaved("[(i[0], i[1]['name']) for i in rs.items()]")))
.containsExactly(Tuple.of("x", "x"), Tuple.of("y", "y"));
assertThat(getSaved("rs['x']['define_values']"))
.isEqualTo(Dict.builder().put("key_x", "value_x").buildImmutable());
assertThat(getSaved("rs.get('x', {'name': 'z'})['name']")).isEqualTo("x");
assertThat(getSaved("rs.get('invalid_rule', {'name': 'invalid_rule'})"))
.isEqualTo(Dict.builder().put("name", "invalid_rule").buildImmutable());
assertThat(getSaved("'x' in rs")).isEqualTo(true);
assertThat(getSaved("'invalid_rule' in rs")).isEqualTo(false);
}
@Test
public void existingRules_returnsSnapshotOfOnlyRulesInstantiatedUpToThatPoint() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key_x': 'value_x'})",
" rs1 = native.existing_rules()",
" native.config_setting(name='y', define_values={'key_y': 'value_y'})",
" rs2 = native.existing_rules()",
" native.config_setting(name='z', define_values={'key_z': 'value_z'})",
" rs3 = native.existing_rules()",
" test.save('rs1.keys()', rs1.keys())",
" test.save('rs2.keys()', rs2.keys())",
" test.save('rs3.keys()', rs3.keys())");
assertThat(getConfiguredTarget("//test:BUILD")).isNotNull(); // no error
assertThat(Starlark.toIterable(getSaved("rs1.keys()"))).containsExactly("x");
assertThat(Starlark.toIterable(getSaved("rs2.keys()"))).containsExactly("x", "y");
assertThat(Starlark.toIterable(getSaved("rs3.keys()"))).containsExactly("x", "y", "z");
}
// Regression test for https://github.com/bazelbuild/bazel/issues/16256
@Test
public void existingRules_encodeToJson() throws Exception {
// We need a Starlark rule - native rules can have attribute values that the json encoder
// doesn't handle.
scratch.file(
"test/test.bzl",
"def _dummy_impl(ctx):",
" pass",
"test_library = rule(",
" implementation = _dummy_impl,",
" attrs = {'srcs': attr.label_list(allow_files = True)},",
")",
// TODO(b/249397668): simplifying this to `json_encode = json.encode` etc. causes a
// NoCodecException. Need to investigate.
"def json_encode(value):",
" return json.encode(value)",
"def json_decode(text):",
" return json.decode(text)",
"def save(name, object):",
" test.save(name, object)");
scratch.file(
"test/BUILD",
"load('//test:test.bzl', 'test_library', 'json_decode', 'json_encode', 'save')", //
"test_library(",
" name ='foo',",
" srcs = ['foo.cc'],",
")",
"test_library(",
" name ='bar',",
" srcs = ['bar.cc'],",
")",
"save('rules', json_decode(json_encode(existing_rules())))");
scratch.file("test/foo.cc");
getConfiguredTarget("//test:bar");
// We test a subset of attributes after an encode-decode round trip because the rule also has
// default attributes with default values, which will get encoded to json and which will change
// whenever default attributes get introduced, making string comparison of encoded json fragile.
Dict<String, Object> jsonRoundTripRulesValue =
Dict.cast(
getSaved("rules"), String.class, Object.class, "json round trip of `existing_rules()`");
assertThat(jsonRoundTripRulesValue.keySet()).containsExactly("foo", "bar");
Map<String, Object> jsonRoundTripFooValue =
Dict.cast(
jsonRoundTripRulesValue.get("foo"),
String.class,
Object.class,
"json round trip of `existing_rule('foo')`");
assertThat(jsonRoundTripFooValue)
.containsAtLeast(
"name", "foo", "kind", "test_library", "srcs", StarlarkList.immutableOf(":foo.cc"));
Map<String, Object> jsonRoundTripBarValue =
Dict.cast(
jsonRoundTripRulesValue.get("bar"),
String.class,
Object.class,
"json round trip of `existing_rule('bar')`");
assertThat(jsonRoundTripBarValue)
.containsAtLeast(
"name", "bar", "kind", "test_library", "srcs", StarlarkList.immutableOf(":bar.cc"));
}
/**
* Tests for {@code native.existing_rule} and {@code native.existing_rules} Starlark functions
* with the {@code --incompatible_existing_rules_immutable_view} flag set.
*/
@RunWith(JUnit4.class)
public static final class WithImmutableView extends NativeExistingRulesTest {
@Override
protected void setupOptions() throws Exception {
setBuildLanguageOptions("--incompatible_existing_rules_immutable_view");
}
@Test
@Override
public void existingRule_returnsObjectWithCorrectMutability() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key': 'value'})",
" r = native.existing_rule('x')",
" r['no_such_attribute'] = 123"); // mutate the view
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//test:BUILD")).isNull(); // mutation fails
assertContainsEvent("can only assign an element in a dictionary or a list");
}
@Test
@Override
public void existingRules_returnsObjectWithCorrectMutability() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key': 'value'})",
" rs = native.existing_rules()",
" rs['no_such_rule'] = {'name': 'no_such_rule', 'kind': 'config_setting'}"); // mutate
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//test:BUILD")).isNull(); // mutation fails
assertContainsEvent("can only assign an element in a dictionary or a list");
}
@Test
public void existingRules_returnsDeeplyImmutableView() throws Exception {
scratch.file(
"test/BUILD",
"load('inc.bzl', 'f')", //
"f()");
scratch.file(
"test/inc.bzl", //
"def f():",
" native.config_setting(name='x', define_values={'key': 'value'})",
" rs = native.existing_rules()",
" rs['x']['define_values']['key'] = 123"); // mutate an attribute value within the view
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//test:BUILD")).isNull();
assertContainsEvent("trying to mutate a frozen dict value");
}
}
}