| # Copyright 2025 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. | |
| """Tests for rule_builders.""" | |
| load("@rules_testing//lib:analysis_test.bzl", "analysis_test") | |
| load("@rules_testing//lib:test_suite.bzl", "test_suite") | |
| load("@rules_testing//lib:util.bzl", "TestingAspectInfo") | |
| load("//python/private:attr_builders.bzl", "attrb") # buildifier: disable=bzl-visibility | |
| load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility | |
| load("//python/private:rule_builders.bzl", "ruleb") # buildifier: disable=bzl-visibility | |
| RuleInfo = provider(doc = "test provider", fields = []) | |
| _tests = [] # analysis-phase tests | |
| _basic_tests = [] # loading-phase tests | |
| fruit = ruleb.Rule( | |
| implementation = lambda ctx: [RuleInfo()], | |
| attrs = { | |
| "color": attrb.String(default = "yellow"), | |
| "fertilizers": attrb.LabelList( | |
| allow_files = True, | |
| ), | |
| "flavors": attrb.StringList(), | |
| "nope": attr.label( | |
| # config.none is Bazel 8+ | |
| cfg = config.none() if hasattr(config, "none") else None, | |
| ), | |
| "organic": lambda: attrb.Bool(), | |
| "origin": lambda: attrb.Label(), | |
| "size": lambda: attrb.Int(default = 10), | |
| }, | |
| ).build() | |
| def _test_fruit_rule(name): | |
| fruit( | |
| name = name + "_subject", | |
| flavors = ["spicy", "sweet"], | |
| organic = True, | |
| size = 5, | |
| origin = labels.NONE, | |
| fertilizers = [ | |
| "nitrogen.txt", | |
| "phosphorus.txt", | |
| ], | |
| ) | |
| analysis_test( | |
| name = name, | |
| target = name + "_subject", | |
| impl = _test_fruit_rule_impl, | |
| ) | |
| def _test_fruit_rule_impl(env, target): | |
| attrs = target[TestingAspectInfo].attrs | |
| env.expect.that_str(attrs.color).equals("yellow") | |
| env.expect.that_collection(attrs.flavors).contains_exactly(["spicy", "sweet"]) | |
| env.expect.that_bool(attrs.organic).equals(True) | |
| env.expect.that_int(attrs.size).equals(5) | |
| # //python:none is an alias to //python/private:sentinel; we see the | |
| # resolved value, not the intermediate alias | |
| env.expect.that_target(attrs.origin).label().equals(Label("//python/private:sentinel")) | |
| env.expect.that_collection(attrs.fertilizers).transform( | |
| desc = "target.label", | |
| map_each = lambda t: t.label, | |
| ).contains_exactly([ | |
| Label(":nitrogen.txt"), | |
| Label(":phosphorus.txt"), | |
| ]) | |
| _tests.append(_test_fruit_rule) | |
| # NOTE: `Rule.build()` can't be called because it's not during the top-level | |
| # bzl evaluation. | |
| def _test_rule_api(env): | |
| subject = ruleb.Rule() | |
| expect = env.expect | |
| expect.that_dict(subject.attrs.map).contains_exactly({}) | |
| expect.that_collection(subject.cfg.outputs()).contains_exactly([]) | |
| expect.that_collection(subject.cfg.inputs()).contains_exactly([]) | |
| expect.that_bool(subject.cfg.implementation()).equals(None) | |
| expect.that_str(subject.doc()).equals("") | |
| expect.that_dict(subject.exec_groups()).contains_exactly({}) | |
| expect.that_bool(subject.executable()).equals(False) | |
| expect.that_collection(subject.fragments()).contains_exactly([]) | |
| expect.that_bool(subject.implementation()).equals(None) | |
| expect.that_collection(subject.provides()).contains_exactly([]) | |
| expect.that_bool(subject.test()).equals(False) | |
| expect.that_collection(subject.toolchains()).contains_exactly([]) | |
| subject.attrs.update({ | |
| "builder": attrb.String(), | |
| "factory": lambda: attrb.String(), | |
| }) | |
| subject.attrs.put("put_factory", lambda: attrb.Int()) | |
| subject.attrs.put("put_builder", attrb.Int()) | |
| expect.that_dict(subject.attrs.map).keys().contains_exactly([ | |
| "factory", | |
| "builder", | |
| "put_factory", | |
| "put_builder", | |
| ]) | |
| expect.that_collection(subject.attrs.map.values()).transform( | |
| desc = "type() of attr value", | |
| map_each = type, | |
| ).contains_exactly(["struct", "struct", "struct", "struct"]) | |
| subject.set_doc("doc") | |
| expect.that_str(subject.doc()).equals("doc") | |
| subject.exec_groups()["eg"] = ruleb.ExecGroup() | |
| expect.that_dict(subject.exec_groups()).keys().contains_exactly(["eg"]) | |
| subject.set_executable(True) | |
| expect.that_bool(subject.executable()).equals(True) | |
| subject.fragments().append("frag") | |
| expect.that_collection(subject.fragments()).contains_exactly(["frag"]) | |
| impl = lambda: None | |
| subject.set_implementation(impl) | |
| expect.that_bool(subject.implementation()).equals(impl) | |
| subject.provides().append(RuleInfo) | |
| expect.that_collection(subject.provides()).contains_exactly([RuleInfo]) | |
| subject.set_test(True) | |
| expect.that_bool(subject.test()).equals(True) | |
| subject.toolchains().append(ruleb.ToolchainType()) | |
| expect.that_collection(subject.toolchains()).has_size(1) | |
| expect.that_collection(subject.cfg.outputs()).contains_exactly([]) | |
| expect.that_collection(subject.cfg.inputs()).contains_exactly([]) | |
| expect.that_bool(subject.cfg.implementation()).equals(None) | |
| subject.cfg.set_implementation(impl) | |
| expect.that_bool(subject.cfg.implementation()).equals(impl) | |
| subject.cfg.add_inputs(Label("//some:input")) | |
| expect.that_collection(subject.cfg.inputs()).contains_exactly([ | |
| str(Label("//some:input")), | |
| ]) | |
| subject.cfg.add_outputs(Label("//some:output")) | |
| expect.that_collection(subject.cfg.outputs()).contains_exactly([ | |
| str(Label("//some:output")), | |
| ]) | |
| _basic_tests.append(_test_rule_api) | |
| def _test_exec_group(env): | |
| subject = ruleb.ExecGroup() | |
| env.expect.that_collection(subject.toolchains()).contains_exactly([]) | |
| env.expect.that_collection(subject.exec_compatible_with()).contains_exactly([]) | |
| env.expect.that_str(str(subject.build())).contains("ExecGroup") | |
| subject.toolchains().append(ruleb.ToolchainType(labels.NONE)) | |
| subject.exec_compatible_with().append("//some:constraint") | |
| env.expect.that_str(str(subject.build())).contains("ExecGroup") | |
| _basic_tests.append(_test_exec_group) | |
| def _test_toolchain_type(env): | |
| subject = ruleb.ToolchainType() | |
| env.expect.that_str(subject.name()).equals(None) | |
| env.expect.that_bool(subject.mandatory()).equals(True) | |
| subject.set_name("//some:toolchain_type") | |
| env.expect.that_str(str(subject.build())).contains("ToolchainType") | |
| subject.set_name("//some:toolchain_type") | |
| subject.set_mandatory(False) | |
| env.expect.that_str(subject.name()).equals("//some:toolchain_type") | |
| env.expect.that_bool(subject.mandatory()).equals(False) | |
| env.expect.that_str(str(subject.build())).contains("ToolchainType") | |
| _basic_tests.append(_test_toolchain_type) | |
| rule_with_toolchains = ruleb.Rule( | |
| implementation = lambda ctx: [], | |
| toolchains = [ | |
| ruleb.ToolchainType("//tests/builders:tct_1", mandatory = False), | |
| lambda: ruleb.ToolchainType("//tests/builders:tct_2", mandatory = False), | |
| "//tests/builders:tct_3", | |
| Label("//tests/builders:tct_4"), | |
| ], | |
| exec_groups = { | |
| "eg1": ruleb.ExecGroup( | |
| toolchains = [ | |
| ruleb.ToolchainType("//tests/builders:tct_1", mandatory = False), | |
| lambda: ruleb.ToolchainType("//tests/builders:tct_2", mandatory = False), | |
| "//tests/builders:tct_3", | |
| Label("//tests/builders:tct_4"), | |
| ], | |
| ), | |
| "eg2": lambda: ruleb.ExecGroup(), | |
| }, | |
| ).build() | |
| def _test_rule_with_toolchains(name): | |
| rule_with_toolchains( | |
| name = name + "_subject", | |
| tags = ["manual"], # Can't be built without extra_toolchains set | |
| ) | |
| analysis_test( | |
| name = name, | |
| impl = lambda env, target: None, | |
| target = name + "_subject", | |
| config_settings = { | |
| "//command_line_option:extra_toolchains": [ | |
| Label("//tests/builders:all"), | |
| ], | |
| }, | |
| ) | |
| _tests.append(_test_rule_with_toolchains) | |
| rule_with_immutable_attrs = ruleb.Rule( | |
| implementation = lambda ctx: [], | |
| attrs = { | |
| "foo": attr.string(), | |
| }, | |
| ).build() | |
| def _test_rule_with_immutable_attrs(name): | |
| rule_with_immutable_attrs(name = name + "_subject") | |
| analysis_test( | |
| name = name, | |
| target = name + "_subject", | |
| impl = lambda env, target: None, | |
| ) | |
| _tests.append(_test_rule_with_immutable_attrs) | |
| def rule_builders_test_suite(name): | |
| test_suite( | |
| name = name, | |
| basic_tests = _basic_tests, | |
| tests = _tests, | |
| ) |