| // 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.addBzlToplevel("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"); |
| } |
| } |
| } |