diff --git a/CHANGELOG.md b/CHANGELOG.md index 3feaebf428..db6edd97d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ END_UNRELEASED_TEMPLATE default to `true`. * (pypi) The data files of a wheel (bin, includes, etc) are now always included as a library's data dependencies. +* (coverage) When `configure_coverage_tool = True` is set but the bundled + `coverage.py` wheel set has no entry for the requested python version and + platform, a warning is now printed instead of silently producing an empty + coverage report. {#v0-0-0-fixed} ### Fixed diff --git a/python/private/coverage_deps.bzl b/python/private/coverage_deps.bzl index cd813196b5..3f6fe8a759 100644 --- a/python/private/coverage_deps.bzl +++ b/python/private/coverage_deps.bzl @@ -166,30 +166,60 @@ _coverage_deps = { _coverage_patch = Label("//python/private:coverage.patch") -def coverage_dep(name, python_version, platform, visibility): - """Register a single coverage dependency based on the python version and platform. +def coverage_url_sha256(python_version, platform): + """Look up the bundled coverage wheel URL and sha256 for a python version + platform. + + Pure function exported for testing. Returns None for any combination not + covered by the bundled wheel set, including the windows branch (which is + intentionally unsupported because the upstream coverage wrapper does not + work there). Args: - name: The name of the registered repository. python_version: The full python version. - platform: The platform, which can be found in //python:versions.bzl PLATFORMS dict. - visibility: The visibility of the coverage tool. + platform: The platform, from //python:versions.bzl PLATFORMS. Returns: - The label of the coverage tool if the platform is supported, otherwise - None. + A (url, sha256) tuple if the (version, platform) is in the bundled + set, otherwise None. """ if "windows" in platform: - # NOTE @aignas 2023-01-19: currently we do not support windows as the - # upstream coverage wrapper is written in shell. Do not log any warning - # for now as it is not actionable. return None abi = "cp" + version_label(python_version) url, sha256 = _coverage_deps.get(abi, {}).get(platform, (None, "")) - if url == None: - # Some wheels are not present for some builds, so let's silently ignore those. return None + return (url, sha256) + +def coverage_dep(name, python_version, platform, visibility): + """Register a single coverage dependency based on the python version and platform. + + Args: + name: The name of the registered repository. + python_version: The full python version. + platform: The platform, which can be found in //python:versions.bzl PLATFORMS dict. + visibility: The visibility of the coverage tool. + + Returns: + The label of the coverage tool if the platform is supported, otherwise - None. + """ + found = coverage_url_sha256(python_version, platform) + if found == None: + if "windows" not in platform: + # NOTE: the windows branch is intentionally silent because the + # upstream coverage wrapper is written in shell and does not + # support windows; warning there is not actionable. + # buildifier: disable=print + print(( + "WARNING: rules_python's bundled coverage tool has no wheel for " + + "python_version={}, platform={}. `bazel coverage` will produce " + + "empty lcov for py_test targets in this configuration. Either " + + "pin python_version to a version in the bundled set (see " + + "python/private/coverage_deps.bzl), or configure coverage " + + "manually via py_runtime.coverage_tool. See docs/coverage.md." + ).format(python_version, platform)) + return None + url, sha256 = found maybe( http_archive, diff --git a/tests/coverage_deps/BUILD.bazel b/tests/coverage_deps/BUILD.bazel new file mode 100644 index 0000000000..8ec6025902 --- /dev/null +++ b/tests/coverage_deps/BUILD.bazel @@ -0,0 +1,17 @@ +# Copyright 2026 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. + +load(":coverage_deps_test.bzl", "coverage_deps_test_suite") + +coverage_deps_test_suite(name = "coverage_deps_tests") diff --git a/tests/coverage_deps/coverage_deps_test.bzl b/tests/coverage_deps/coverage_deps_test.bzl new file mode 100644 index 0000000000..747ec62ec2 --- /dev/null +++ b/tests/coverage_deps/coverage_deps_test.bzl @@ -0,0 +1,77 @@ +# Copyright 2026 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 coverage_url_sha256 lookups against the bundled wheel set." + +load("@rules_testing//lib:test_suite.bzl", "test_suite") +load("//python/private:coverage_deps.bzl", "coverage_url_sha256") # buildifier: disable=bzl-visibility + +_tests = [] + +def _test_supported_version_and_platform_returns_url_and_sha(env): + result = coverage_url_sha256("3.10", "aarch64-apple-darwin") + env.expect.that_bool(result != None).equals(True) + url, sha256 = result + env.expect.that_str(url).contains("coverage-") + env.expect.that_str(url).contains("cp310") + env.expect.that_str(url).contains("macosx_11_0_arm64") + env.expect.that_int(len(sha256)).equals(64) + +_tests.append(_test_supported_version_and_platform_returns_url_and_sha) + +def _test_cp314_is_in_bundled_set(env): + # Regression guard: cp314 was the motivation for adding the warning. + # If a future regen accidentally drops it, this test fires. + result = coverage_url_sha256("3.14", "aarch64-apple-darwin") + env.expect.that_bool(result != None).equals(True) + url, _ = result + env.expect.that_str(url).contains("cp314") + +_tests.append(_test_cp314_is_in_bundled_set) + +def _test_freethreaded_variant_is_in_bundled_set(env): + # Regression guard: freethreaded variants for cp313+ are part of the + # bundled set; ensure regen does not drop them. + result = coverage_url_sha256("3.14", "aarch64-apple-darwin-freethreaded") + env.expect.that_bool(result != None).equals(True) + url, _ = result + env.expect.that_str(url).contains("cp314t") + +_tests.append(_test_freethreaded_variant_is_in_bundled_set) + +def _test_unsupported_version_returns_none(env): + # Python 3.7 is not in the bundled wheel set (and is far below the + # current floor). This is the path that triggers the warning in + # coverage_dep. + result = coverage_url_sha256("3.7", "aarch64-apple-darwin") + env.expect.that_bool(result == None).equals(True) + +_tests.append(_test_unsupported_version_returns_none) + +def _test_windows_returns_none(env): + # Windows is intentionally not supported by the bundled coverage tool; + # the lookup must return None so the caller can keep the windows + # branch silent. + result = coverage_url_sha256("3.10", "x86_64-pc-windows-msvc") + env.expect.that_bool(result == None).equals(True) + +_tests.append(_test_windows_returns_none) + +def coverage_deps_test_suite(name): + """Create the test suite. + + Args: + name: the name of the test suite + """ + test_suite(name = name, basic_tests = _tests)