From dc17de7c7bc302fe47dfe512dd97f8909d78971b Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Fri, 12 Jul 2024 05:58:04 -0700 Subject: [PATCH] Test proto_common.compile Rewrite compile tests from BazelProtoCommonTest to Starlark. This is using rules_analysis for testing. The tests are super fast (cca. 1s for all of the to run). The tests work either with a redirect (calling native rule) or with actual implementation in the protobuf repository. PiperOrigin-RevId: 651748083 --- MODULE.bazel | 3 + WORKSPACE | 9 + bazel/tests/BUILD | 3 + bazel/tests/proto_common_compile_tests.bzl | 361 +++++++++++++++++++++ bazel/tests/testdata/BUILD | 130 ++++++++ bazel/tests/testdata/compile_rule.bzl | 50 +++ 6 files changed, 556 insertions(+) create mode 100644 bazel/tests/BUILD create mode 100644 bazel/tests/proto_common_compile_tests.bzl create mode 100644 bazel/tests/testdata/BUILD create mode 100644 bazel/tests/testdata/compile_rule.bzl diff --git a/MODULE.bazel b/MODULE.bazel index b873411150..b09503c620 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -71,3 +71,6 @@ crate.spec( ) crate.from_specs() use_repo(crate, crate_index = "crates") + +# Development dependencies +bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True) diff --git a/WORKSPACE b/WORKSPACE index 314bb92a19..7083fbefca 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -241,3 +241,12 @@ http_archive( # Needed as a dependency of @com_google_protobuf_v25.0 load("@com_google_protobuf_v25.0//:protobuf_deps.bzl", protobuf_v25_deps="protobuf_deps") protobuf_v25_deps() + +# Needed for testing only +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +http_archive( + name = "rules_testing", + sha256 = "02c62574631876a4e3b02a1820cb51167bb9cdcdea2381b2fa9d9b8b11c407c4", + strip_prefix = "rules_testing-0.6.0", + url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.6.0/rules_testing-v0.6.0.tar.gz", +) diff --git a/bazel/tests/BUILD b/bazel/tests/BUILD new file mode 100644 index 0000000000..54cc42150e --- /dev/null +++ b/bazel/tests/BUILD @@ -0,0 +1,3 @@ +load(":proto_common_compile_tests.bzl", "proto_common_compile_test_suite") + +proto_common_compile_test_suite(name = "proto_common_compile_test_suite") diff --git a/bazel/tests/proto_common_compile_tests.bzl b/bazel/tests/proto_common_compile_tests.bzl new file mode 100644 index 0000000000..58ecc899ce --- /dev/null +++ b/bazel/tests/proto_common_compile_tests.bzl @@ -0,0 +1,361 @@ +"""Tests for `proto_common.compile` function.""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load("//bazel:proto_library.bzl", "proto_library") +load("//bazel/tests/testdata:compile_rule.bzl", "compile_rule") + +protocol_compiler = "/protoc" + +def proto_common_compile_test_suite(name): + util.helper_target( + proto_library, + name = "simple_proto", + srcs = ["A.proto"], + ) + test_suite( + name = name, + tests = [ + _test_compile_basic, + _test_compile_noplugin, + _test_compile_with_plugin_output, + _test_compile_with_directory_plugin_output, + _test_compile_additional_args, + _test_compile_additional_tools, + _test_compile_additional_tools_no_plugin, + _test_compile_additional_inputs, + _test_compile_resource_set, + _test_compile_protoc_opts, + _test_compile_direct_generated_protos, + _test_compile_indirect_generated_protos, + ], + ) + +# Verifies basic usage of `proto_common.compile`. +def _test_compile_basic(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_basic_impl, + ) + +def _test_compile_basic_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) + action.mnemonic().equals("MyMnemonic") + +# Verifies usage of proto_common.generate_code with no plugin specified by toolchain. +def _test_compile_noplugin(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + toolchain = "//bazel/tests/testdata:toolchain_noplugin", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_noplugin_impl, + ) + +def _test_compile_noplugin_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) + +# Verifies usage of `proto_common.compile` with `plugin_output` parameter set to file. +def _test_compile_with_plugin_output(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + plugin_output = "single", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_with_plugin_output_impl, + ) + +def _test_compile_with_plugin_output_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.str_matches("--java_out=param1,param2:b*-out/*/test_compile_with_plugin_output_compile"), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) + +# Verifies usage of `proto_common.compile` with `plugin_output` parameter set to file. +def _test_compile_with_directory_plugin_output(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + plugin_output = "multiple", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_with_directory_plugin_output_impl, + ) + +def _test_compile_with_directory_plugin_output_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.str_matches("--java_out=param1,param2:b*-out/*/bin"), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) + +# Verifies usage of `proto_common.compile` with `additional_args` parameter +def _test_compile_additional_args(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + additional_args = ["--a", "--b"], + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_additional_args_impl, + ) + +def _test_compile_additional_args_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.equals_wrapper("--a"), + matching.equals_wrapper("--b"), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) + +# Verifies usage of `proto_common.compile` with `additional_tools` parameter +def _test_compile_additional_tools(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + additional_tools = [ + "//bazel/tests/testdata:_tool1", + "//bazel/tests/testdata:_tool2", + ], + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_additional_tools_impl, + ) + +def _test_compile_additional_tools_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.inputs().contains_at_least_predicates( + [ + matching.file_basename_equals("_tool1"), + matching.file_basename_equals("_tool2"), + matching.file_basename_equals("plugin"), + ], + ) + +# Verifies usage of `proto_common.compile` with `additional_tools` parameter and no plugin on the toolchain. +def _test_compile_additional_tools_no_plugin(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + additional_tools = [ + "//bazel/tests/testdata:_tool1", + "//bazel/tests/testdata:_tool2", + ], + toolchain = "//bazel/tests/testdata:toolchain_noplugin", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_additional_tools_no_plugin_impl, + ) + +def _test_compile_additional_tools_no_plugin_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.inputs().contains_at_least_predicates( + [ + matching.file_basename_equals("_tool1"), + matching.file_basename_equals("_tool2"), + ], + ) + action.inputs().not_contains_predicate(matching.file_basename_equals("plugin")) + +# Verifies usage of `proto_common.compile` with `additional_inputs` parameter. +def _test_compile_additional_inputs(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + additional_inputs = ["input1.txt", "input2.txt"], + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_additional_inputs_impl, + ) + +def _test_compile_additional_inputs_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.inputs().contains_at_least_predicates( + [ + matching.file_basename_equals("input1.txt"), + matching.file_basename_equals("input2.txt"), + ], + ) + +# Verifies usage of `proto_common.compile` with `additional_tools` parameter and no plugin on the toolchain. +def _test_compile_resource_set(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + use_resource_set = True, + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_resource_set_impl, + ) + +def _test_compile_resource_set_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") # @unused + # We can't check the specification of the resource set, but we at least verify analysis passes + +# Verifies `--protocopts` are passed to command line. +def _test_compile_protoc_opts(name): + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = ":simple_proto", + ) + + analysis_test( + name = name, + target = name + "_compile", + config_settings = {"//command_line_option:protocopt": ["--foo", "--bar"]}, + impl = _test_compile_protoc_opts_impl, + ) + +def _test_compile_protoc_opts_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.equals_wrapper("--foo"), + matching.equals_wrapper("--bar"), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) + +# Verifies `proto_common.compile`> correctly handles direct generated `.proto` files. +def _test_compile_direct_generated_protos(name): + util.helper_target(native.genrule, name = name + "_generate_G", cmd = "", outs = ["G.proto"]) + util.helper_target( + proto_library, + name = name + "_directly_generated_proto", + srcs = ["A.proto", "G.proto"], + ) + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = name + "_directly_generated_proto", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_direct_generated_protos_impl, + ) + +def _test_compile_direct_generated_protos_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.str_matches("-Ib*-out/*/*"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + matching.str_matches("*-out/*/*/*/G.proto"), + ], + ) + +# Verifies usage of `proto_common.compile` with `plugin_output` parameter +def _test_compile_indirect_generated_protos(name): + util.helper_target(native.genrule, name = "_generate_h", srcs = ["A.txt"], cmd = "", outs = ["H.proto"]) + util.helper_target(proto_library, name = "_generated_proto", srcs = ["H.proto"]) + util.helper_target( + proto_library, + name = name + "_indirectly_generated_proto", + srcs = ["A.proto"], + deps = [":_generated_proto"], + ) + util.helper_target( + compile_rule, + name = name + "_compile", + proto_dep = name + "_indirectly_generated_proto", + ) + + analysis_test( + name = name, + target = name + "_compile", + impl = _test_compile_indirect_generated_protos_impl, + ) + +def _test_compile_indirect_generated_protos_impl(env, target): + action = env.expect.that_target(target).action_named("MyMnemonic") + action.argv().contains_exactly_predicates( + [ + matching.str_endswith(protocol_compiler), + matching.str_matches("--plugin=b*-out/*-exec-*/bin/*/testdata/plugin"), + matching.str_matches("-Ib*-out/*/*"), + matching.equals_wrapper("-I."), + matching.str_endswith("/A.proto"), + ], + ) diff --git a/bazel/tests/testdata/BUILD b/bazel/tests/testdata/BUILD new file mode 100644 index 0000000000..6ae81e08c2 --- /dev/null +++ b/bazel/tests/testdata/BUILD @@ -0,0 +1,130 @@ +package(default_visibility = ["//visibility:public"]) + +proto_lang_toolchain( + name = "toolchain", + blacklisted_protos = [":denied"], + command_line = "--java_out=param1,param2:$(OUT)", + mnemonic = "MyMnemonic", + plugin = ":plugin", + plugin_format_flag = "--plugin=%s", + progress_message = "Progress Message %{label}", + runtime = ":runtime", + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +proto_lang_toolchain( + name = "toolchain_noplugin", + blacklisted_protos = [":denied"], + command_line = "--java_out=param1,param2:$(OUT)", + mnemonic = "MyMnemonic", + progress_message = "Progress Message %{label}", + runtime = ":runtime", + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +cc_binary( + name = "plugin", + srcs = ["plugin.cc"], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +cc_library( + name = "runtime", + srcs = ["runtime.cc"], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +filegroup( + name = "descriptors", + srcs = [ + "descriptor.proto", + "metadata.proto", + ], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +filegroup( + name = "any", + srcs = ["any.proto"], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +filegroup( + name = "something", + srcs = ["something.proto"], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +proto_library( + name = "mixed", + srcs = [ + ":descriptors", + ":something", + ], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +proto_library( + name = "denied", + srcs = [ + ":any", + ":descriptors", + ], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +cc_binary( + name = "_tool1", + srcs = ["tool1.cc"], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +cc_binary( + name = "_tool2", + srcs = ["tool2.cc"], + tags = [ + "manual", + "nobuilder", + "notap", + ], +) diff --git a/bazel/tests/testdata/compile_rule.bzl b/bazel/tests/testdata/compile_rule.bzl new file mode 100644 index 0000000000..65bf99b68d --- /dev/null +++ b/bazel/tests/testdata/compile_rule.bzl @@ -0,0 +1,50 @@ +"""Testing function for proto_common module""" + +load("//bazel/common:proto_common.bzl", "proto_common") + +def _resource_set_callback(_os, inputs_size): + return {"memory": 25 + 0.15 * inputs_size, "cpu": 1} + +def _impl(ctx): + outfile = ctx.actions.declare_file(ctx.attr.name) + kwargs = {} + if ctx.attr.plugin_output == "single": + kwargs["plugin_output"] = outfile.path + elif ctx.attr.plugin_output == "multiple": + kwargs["plugin_output"] = ctx.bin_dir.path + elif ctx.attr.plugin_output == "wrong": + kwargs["plugin_output"] = ctx.bin_dir.path + "///" + if ctx.attr.additional_args: + additional_args = ctx.actions.args() + additional_args.add_all(ctx.attr.additional_args) + kwargs["additional_args"] = additional_args + if ctx.files.additional_tools: + kwargs["additional_tools"] = ctx.files.additional_tools + if ctx.files.additional_inputs: + kwargs["additional_inputs"] = depset(ctx.files.additional_inputs) + if ctx.attr.use_resource_set: + kwargs["resource_set"] = _resource_set_callback + if ctx.attr.progress_message: + kwargs["experimental_progress_message"] = ctx.attr.progress_message + proto_common.compile( + ctx.actions, + ctx.attr.proto_dep[ProtoInfo], + ctx.attr.toolchain[proto_common.ProtoLangToolchainInfo], + [outfile], + **kwargs + ) + return [DefaultInfo(files = depset([outfile]))] + +compile_rule = rule( + _impl, + attrs = { + "proto_dep": attr.label(), + "plugin_output": attr.string(), + "toolchain": attr.label(default = ":toolchain"), + "additional_args": attr.string_list(), + "additional_tools": attr.label_list(cfg = "exec"), + "additional_inputs": attr.label_list(allow_files = True), + "use_resource_set": attr.bool(), + "progress_message": attr.string(), + }, +)