From 4a5464a0c466c5cce21c47b2c6dfa12cf4aa20ba Mon Sep 17 00:00:00 2001 From: Joshua Yanchar Date: Sun, 10 May 2026 10:24:26 -0700 Subject: [PATCH 1/3] feat(coverage): warn when bundled coverage tool has no wheel for requested python_version/platform Previously, when `configure_coverage_tool = True` was set but the bundled `coverage.py` wheel set had no entry for the requested (python_version, platform), `coverage_dep` returned None silently. The result was that `bazel coverage` produced empty per-test lcov files for `py_test` targets with no signal to the user that coverage was unconfigured. Print a WARNING in that path so the misconfiguration is visible. Preserve the existing silent return for the windows branch, which is intentionally quiet because the upstream coverage wrapper does not support windows. --- CHANGELOG.md | 4 ++++ python/private/coverage_deps.bzl | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) 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..46fd51e861 100644 --- a/python/private/coverage_deps.bzl +++ b/python/private/coverage_deps.bzl @@ -188,7 +188,15 @@ def coverage_dep(name, python_version, platform, visibility): 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. + # 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 maybe( From b30713091c7c96be63cd2c6f9484fcd1c21889b5 Mon Sep 17 00:00:00 2001 From: Joshua Yanchar Date: Sun, 10 May 2026 10:25:05 -0700 Subject: [PATCH 2/3] refactor(coverage): extract coverage_url_sha256 lookup helper Pull the (python_version, platform) -> (url, sha256) lookup out of coverage_dep into a pure function so it can be unit-tested. The side-effecting maybe(http_archive, ...) registration stays in coverage_dep; the warning continues to fire from coverage_dep. No behavior change. --- python/private/coverage_deps.bzl | 60 ++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/python/private/coverage_deps.bzl b/python/private/coverage_deps.bzl index 46fd51e861..3f6fe8a759 100644 --- a/python/private/coverage_deps.bzl +++ b/python/private/coverage_deps.bzl @@ -166,38 +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: - # 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 + 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, From 2dc3dc30e8bddc0f0b01afa42703fba195a29d55 Mon Sep 17 00:00:00 2001 From: Joshua Yanchar Date: Sun, 10 May 2026 10:26:18 -0700 Subject: [PATCH 3/3] test(coverage): unit-test coverage_url_sha256 lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five cases: - supported (version, platform) returns a (url, sha256) pair with the expected wheel-filename fragments and a 64-char sha - cp314 is present in the bundled set (regression guard for the motivation behind the warning) - the cp314 freethreaded variant is present (regression guard for freethreaded entries) - an unsupported version (3.7) returns None — the path that triggers the warning in coverage_dep - a windows platform returns None — the path that stays silent --- tests/coverage_deps/BUILD.bazel | 17 +++++ tests/coverage_deps/coverage_deps_test.bzl | 77 ++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tests/coverage_deps/BUILD.bazel create mode 100644 tests/coverage_deps/coverage_deps_test.bzl 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)