diff --git a/.bazelrc b/.bazelrc index ada5c5a0a7..75749c27d8 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,8 +4,8 @@ # (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it) # To update these lines, execute # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` -build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered -query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered +build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered +query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered test --test_output=errors diff --git a/CHANGELOG.md b/CHANGELOG.md index 00624db01f..a3691ac186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,7 +66,9 @@ Unreleased changes template. {#v0-0-0-added} ### Added -* Nothing added. +* (toolchain) Using testing toolchain to configure py_test coverage. + This opens the potential to configure differnt test runners. + ([#2246](https://github.com/bazelbuild/rules_python/pull/2246)). {#v0-0-0-removed} ### Removed diff --git a/examples/bzlmod/.coveragerc b/examples/bzlmod/.coveragerc new file mode 100644 index 0000000000..5d834b366d --- /dev/null +++ b/examples/bzlmod/.coveragerc @@ -0,0 +1,6 @@ +[report] +include_namespace_packages=True +skip_covered=True +[run] +relative_files=True +branch=True diff --git a/examples/bzlmod/BUILD.bazel b/examples/bzlmod/BUILD.bazel index df07385690..9435bed0b7 100644 --- a/examples/bzlmod/BUILD.bazel +++ b/examples/bzlmod/BUILD.bazel @@ -13,6 +13,8 @@ load("@rules_python//python:py_binary.bzl", "py_binary") load("@rules_python//python:py_library.bzl", "py_library") load("@rules_python//python:py_test.bzl", "py_test") +exports_files([".coveragerc"]) + # This stanza calls a rule that generates targets for managing pip dependencies # with pip-compile for a particular python version. compile_pip_requirements_3_10( diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel index 536e3b2b67..fa58ad00ab 100644 --- a/examples/bzlmod/MODULE.bazel +++ b/examples/bzlmod/MODULE.bazel @@ -7,6 +7,11 @@ module( bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "platforms", version = "0.0.4") bazel_dep(name = "rules_python", version = "0.0.0") +bazel_dep(name = "bazel_features", version = "1.9.1") + +version = use_extension("@bazel_features//private:extensions.bzl", "version_extension") +use_repo(version, "bazel_features_globals", "bazel_features_version") + local_path_override( module_name = "rules_python", path = "../..", @@ -104,6 +109,14 @@ python.single_version_platform_override( # rules based on the `python_version` arg values. use_repo(python, "python_3_10", "python_3_9", "python_versions", "pythons_hub") +python_test = use_extension("@rules_python//python/extensions:python_test.bzl", "python_test") +python_test.configure( + coveragerc = ".coveragerc", +) +use_repo(python_test, "py_test_toolchain") + +register_toolchains("@py_test_toolchain//:all") + # EXPERIMENTAL: This is experimental and may be removed without notice uv = use_extension("@rules_python//python/uv:extensions.bzl", "uv") uv.toolchain(uv_version = "0.4.25") diff --git a/examples/bzlmod/tests/BUILD.bazel b/examples/bzlmod/tests/BUILD.bazel index 4650fb8788..dddab6540d 100644 --- a/examples/bzlmod/tests/BUILD.bazel +++ b/examples/bzlmod/tests/BUILD.bazel @@ -49,11 +49,23 @@ py_test( ) py_test( - name = "my_lib_3_9_test", - srcs = ["my_lib_test.py"], - main = "my_lib_test.py", + name = "coverage_rc_is_set_test", + srcs = ["coverage_rc_is_set_test.py"], + main = "coverage_rc_is_set_test.py", +) + +py_test( + name = "coverage_rc_is_set_3_9_test", + srcs = ["coverage_rc_is_set_test.py"], + main = "coverage_rc_is_set_test.py", python_version = "3.9", - deps = ["//libs/my_lib"], +) + +py_test( + name = "coverage_rc_is_set_3_11_test", + srcs = ["coverage_rc_is_set_test.py"], + main = "coverage_rc_is_set_test.py", + python_version = "3.11", ) py_test( diff --git a/examples/bzlmod/tests/coverage_rc_is_set_test.py b/examples/bzlmod/tests/coverage_rc_is_set_test.py new file mode 100644 index 0000000000..6bb0fde1e9 --- /dev/null +++ b/examples/bzlmod/tests/coverage_rc_is_set_test.py @@ -0,0 +1,51 @@ +# Copyright 2024 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. +import os +import tempfile +import unittest + + +class TestEnvironmentVariables(unittest.TestCase): + def test_coverage_rc_file_exists(self): + # Assert that the environment variable is set and points to a valid file + coverage_rc_path = os.environ.get("COVERAGE_RC") + if coverage_rc_path: + coverage_rc_path = os.path.abspath(coverage_rc_path) + self.assertTrue( + os.path.isfile(coverage_rc_path), + f"COVERAGE_RC does not point to a valid file, {coverage_rc_path}", + ) + + # Read the content of the file and assert it matches the expected content + expected_content = ( + "[report]\n" + "include_namespace_packages=True\n" + "skip_covered=True\n" + "[run]\n" + "relative_files=True\n" + "branch=True\n" + ) + + with open(coverage_rc_path, "r") as file: + file_content = file.read() + + self.assertEqual( + file_content, + expected_content, + "COVERAGE_RC file content does not match the expected content", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/BUILD.bazel b/python/BUILD.bazel index b747e2fbc7..b4cfe9d72b 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -363,3 +363,8 @@ exports_files([ current_py_toolchain( name = "current_py_toolchain", ) + +toolchain_type( + name = "py_test_toolchain_type", + visibility = ["//visibility:public"], +) diff --git a/python/extensions/python_test.bzl b/python/extensions/python_test.bzl new file mode 100644 index 0000000000..c437b5e431 --- /dev/null +++ b/python/extensions/python_test.bzl @@ -0,0 +1,33 @@ +# Copyright 2023 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. + +"""Python toolchain module extensions for use with bzlmod. + +::::{topic} Basic usage + +The simplest way to configure the toolchain with `rules_python` is as follows. + +```starlark +python_test = use_extension("@rules_python//python/extensions:python_test.bzl", "python_test") +python_test.configure( + coveragerc = ".coveragerc", +) +use_repo(python_test, "py_test_toolchain") +register_toolchains("@py_test_toolchain//:all") +``` +""" + +load("//python/private:python_test.bzl", _python_test = "python_test") + +python_test = _python_test diff --git a/python/private/py_binary_rule.bzl b/python/private/py_binary_rule.bzl index f1c8eb1325..ce5bc6172b 100644 --- a/python/private/py_binary_rule.bzl +++ b/python/private/py_binary_rule.bzl @@ -39,11 +39,28 @@ _PY_TEST_ATTRS = { } def _py_binary_impl(ctx): - return py_executable_impl( + providers, binary_info, environment_info = py_executable_impl( ctx = ctx, is_test = False, inherited_environment = [], ) + providers.extend( + [ + # We construct DefaultInfo and RunEnvironmentInfo here, as other py_binary-like + # rules (py_test) need a different DefaultInfo and RunEnvironmentInfo. + DefaultInfo( + executable = binary_info.executable, + files = binary_info.files, + default_runfiles = binary_info.default_runfiles, + data_runfiles = binary_info.data_runfiles, + ), + RunEnvironmentInfo( + environment = environment_info.environment, + inherited_environment = environment_info.inherited_environment, + ), + ], + ) + return providers py_binary = create_executable_rule( implementation = _py_binary_impl, diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index da7127e070..9b7dff8af9 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -69,6 +69,7 @@ load( load( ":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", + "PY_TEST_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE", ) @@ -1615,20 +1616,7 @@ def _create_providers( A list of modern providers. """ providers = [ - DefaultInfo( - executable = executable, - files = default_outputs, - default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( - ctx, - runfiles_details.default_runfiles, - ), - data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( - ctx, - runfiles_details.data_runfiles, - ), - ), create_instrumented_files_info(ctx), - _create_run_environment_info(ctx, inherited_environment), PyExecutableInfo( main = main_py, runfiles_without_exe = runfiles_details.runfiles_without_exe, @@ -1699,7 +1687,20 @@ def _create_providers( runtime_details = runtime_details, ) providers.extend(extra_providers) - return providers + environemnt_info = _create_run_environment_info(ctx, inherited_environment) + binary_info = struct( + executable = executable, + files = default_outputs, + default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( + ctx, + runfiles_details.default_runfiles, + ), + data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles( + ctx, + runfiles_details.data_runfiles, + ), + ) + return providers, binary_info, environemnt_info def _create_run_environment_info(ctx, inherited_environment): expanded_env = {} @@ -1710,7 +1711,7 @@ def _create_run_environment_info(ctx, inherited_environment): expression = value, targets = ctx.attr.data, ) - return RunEnvironmentInfo( + return struct( environment = expanded_env, inherited_environment = inherited_environment, ) diff --git a/python/private/py_test_rule.bzl b/python/private/py_test_rule.bzl index 63000c7255..5559f44845 100644 --- a/python/private/py_test_rule.bzl +++ b/python/private/py_test_rule.bzl @@ -14,6 +14,7 @@ """Implementation of py_test rule.""" load("@bazel_skylib//lib:dicts.bzl", "dicts") +load("//python/private:toolchain_types.bzl", "PY_TEST_TOOLCHAIN_TYPE") load(":attributes.bzl", "AGNOSTIC_TEST_ATTRS") load(":common.bzl", "maybe_add_test_execution_info") load( @@ -40,16 +41,44 @@ _BAZEL_PY_TEST_ATTRS = { } def _py_test_impl(ctx): - providers = py_executable_impl( + providers, binary_info, environment_info = py_executable_impl( ctx = ctx, is_test = True, inherited_environment = ctx.attr.env_inherit, ) maybe_add_test_execution_info(providers, ctx) - return providers + py_test_toolchain = ctx.exec_groups["test"].toolchains[PY_TEST_TOOLCHAIN_TYPE] + if py_test_toolchain: + py_test_info = py_test_toolchain.py_test_info + else: + providers.extend( + [ + DefaultInfo( + executable = binary_info.executable, + files = binary_info.files, + default_runfiles = binary_info.default_runfiles, + data_runfiles = binary_info.data_runfiles, + ), + RunEnvironmentInfo( + environment = environment_info.environment, + inherited_environment = environment_info.inherited_environment, + ), + ], + ) + return providers + test_providers = py_test_info.get_runner.func( + ctx, + binary_info, + environment_info, + **py_test_info.get_runner.args + ) + return test_providers + providers py_test = create_executable_rule( implementation = _py_test_impl, attrs = dicts.add(AGNOSTIC_TEST_ATTRS, _BAZEL_PY_TEST_ATTRS), test = True, + exec_groups = { + "test": exec_group(toolchains = [config_common.toolchain_type(PY_TEST_TOOLCHAIN_TYPE, mandatory = False)]), + }, ) diff --git a/python/private/py_test_toolchain.bzl b/python/private/py_test_toolchain.bzl new file mode 100644 index 0000000000..052f07c67d --- /dev/null +++ b/python/private/py_test_toolchain.bzl @@ -0,0 +1,160 @@ +# Copyright 2024 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. + +""" +Simple toolchain which overrides env and exec requirements. +""" + +load(":text_util.bzl", "render") +load( + ":toolchain_types.bzl", + "PY_TEST_TOOLCHAIN_TYPE", +) + +PyTestProviderInfo = provider( + doc = "Information about the pytest toolchain", + fields = [ + "get_runner", + ], +) + +def _get_runner(ctx, binary_info, environment_info, coverage_rc): + """ + Constructs and returns a list containing `DefaultInfo` and `RunEnvironmentInfo` for a test runner setup. + + Args: + ctx: The rule context, providing access to actions, inputs, outputs, and more. + binary_info: A `struct` with defaultinfo details. + - `executable`: The executable binary. + - `files`: The files associated with the binary. + - `default_runfiles`: The default runfiles of the binary. + - `data_runfiles`: Additional runfiles for data dependencies. + environment_info: A `struct` with environment details. + - `environment`: A dictionary of key-value pairs for the test environment. + - `inherited_environment`: A list of environment variables inherited from the host. + coverage_rc: A `File` or `File`-like target containing coverage configuration files. + """ + + test_env = {"COVERAGE_RC": coverage_rc.files.to_list()[0].short_path} + test_env.update(environment_info.environment) + + return [ + DefaultInfo( + # Opportunity to override the executable in the binary_info with a new testrunner. + executable = binary_info.executable, + files = binary_info.files, + default_runfiles = binary_info.default_runfiles.merge( + ctx.runfiles( + transitive_files = coverage_rc.files, + ), + ), + data_runfiles = binary_info.data_runfiles, + ), + RunEnvironmentInfo( + environment = test_env, + inherited_environment = environment_info.inherited_environment, + ), + ] + +def _py_test_toolchain_impl(ctx): + return [ + platform_common.ToolchainInfo( + py_test_info = PyTestProviderInfo( + get_runner = struct( + func = _get_runner, + args = { + "coverage_rc": ctx.attr.coverage_rc, + }, + ), + ), + ), + ] + +py_test_toolchain = rule( + implementation = _py_test_toolchain_impl, + attrs = { + "coverage_rc": attr.label(allow_single_file = True), + }, +) + +_TOOLCHAIN_TEMPLATE = """ +load("@rules_python//python/private:py_test_toolchain.bzl", "py_test_toolchain_macro") +py_test_toolchain_macro( + {kwargs} +) +""" + +def py_test_toolchain_macro(*, name, coverage_rc, toolchain_type): + """ + Macro to create a py_test_toolchain rule and a native toolchain rule. + + name: The name of the toolchain. + coverage_rc: The coverage rc file. + toolchain_type: The toolchain type. + """ + py_test_toolchain( + name = "{}_toolchain".format(name), + coverage_rc = coverage_rc, + ) + native.toolchain( + name = name, + target_compatible_with = [], + exec_compatible_with = [], + toolchain = "{}_toolchain".format(name), + toolchain_type = toolchain_type, + ) + +def _toolchains_repo_impl(repository_ctx): + kwargs = dict( + name = repository_ctx.name, + coverage_rc = str(repository_ctx.attr.coverage_rc), + toolchain_type = str(repository_ctx.attr.toolchain_type), + ) + + build_content = _TOOLCHAIN_TEMPLATE.format( + kwargs = render.indent("\n".join([ + "{} = {},".format(k, render.str(v)) + for k, v in kwargs.items() + ])), + ) + repository_ctx.file("BUILD.bazel", build_content) + +py_test_toolchain_repo = repository_rule( + _toolchains_repo_impl, + doc = "Generates a toolchain hub repository", + attrs = { + "coverage_rc": attr.label( + doc = "The coverage rc file", + mandatory = True, + allow_single_file = True, + ), + "toolchain_type": attr.label(doc = "Toolchain type", mandatory = True), + }, +) + +def register_py_test_toolchain(name, coverage_rc, register_toolchains = True): + """ Register the py_test_toolchain and native toolchain rules. + + name: The name of the toolchain. + coverage_rc: The coverage rc file. + register_toolchains: Whether to register the toolchains. + + """ + py_test_toolchain_repo( + name = name, + coverage_rc = coverage_rc, + toolchain_type = PY_TEST_TOOLCHAIN_TYPE, + ) + if register_toolchains: + native.toolchain(name = "py_test_toolchain") diff --git a/python/private/python_test.bzl b/python/private/python_test.bzl new file mode 100644 index 0000000000..3371b55a4b --- /dev/null +++ b/python/private/python_test.bzl @@ -0,0 +1,55 @@ +# Copyright 2023 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. + +"Python test toolchain module extensions for use with bzlmod." + +load("@bazel_features//:features.bzl", "bazel_features") +load("//python/private:py_test_toolchain.bzl", "register_py_test_toolchain") + +def _python_test_impl(module_ctx): + """Implementation of the `coverage` extension. + + Configure the test toolchain for setting coverage resource file. + + """ + for mod in module_ctx.modules: + for tag in mod.tags.configure: + register_py_test_toolchain( + name = "py_test_toolchain", + coverage_rc = tag.coveragerc, + register_toolchains = False, + ) + if bazel_features.external_deps.extension_metadata_has_reproducible: + return module_ctx.extension_metadata(reproducible = True) + else: + return None + +configure = tag_class( + doc = """Tag class used to register Python toolchains.""", + attrs = { + # TODO: Add testrunner and potentially coverage_tool + "coveragerc": attr.label( + doc = """Tag class used to register Python toolchains.""", + mandatory = True, + ), + }, +) + +python_test = module_extension( + doc = """Bzlmod extension that is used to register test toolchains. """, + implementation = _python_test_impl, + tag_classes = { + "configure": configure, + }, +) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index 1e19a71b64..e4dbcb62b0 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -283,13 +283,19 @@ def _maybe_collect_coverage(enable): unique_id = uuid.uuid4() # We need for coveragepy to use relative paths. This can only be configured - rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id)) - with open(rcfile_name, "w") as rcfile: - rcfile.write( - """[run] + if os.environ.get("COVERAGE_RC"): + rcfile_name = (os.environ["COVERAGE_RC"]) + assert ( + os.path.exists(rcfile_name) == True + ), f"Coverage rc {rcfile_name} file does not exist" + else: + rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id)) + with open(rcfile_name, "w") as rcfile: + rcfile.write( + """[run] relative_files = True """ - ) + ) try: cov = coverage.Coverage( config_file=rcfile_name, diff --git a/python/private/toolchain_types.bzl b/python/private/toolchain_types.bzl index ef81bf3bd4..fbdb4f972d 100644 --- a/python/private/toolchain_types.bzl +++ b/python/private/toolchain_types.bzl @@ -21,3 +21,4 @@ implementation of the toolchain. TARGET_TOOLCHAIN_TYPE = Label("//python:toolchain_type") EXEC_TOOLS_TOOLCHAIN_TYPE = Label("//python:exec_tools_toolchain_type") PY_CC_TOOLCHAIN_TYPE = Label("//python/cc:toolchain_type") +PY_TEST_TOOLCHAIN_TYPE = Label("//python:py_test_toolchain_type")