From add23c7a418ec8072ed4646e9f7ceb14ea7e18f0 Mon Sep 17 00:00:00 2001 From: Maximilian Birkner Date: Thu, 18 Jun 2026 17:09:41 +0200 Subject: [PATCH 1/3] fix(coverage): skip lcov report when no data was collected coverage.py raises NoDataError instead of writing a report when no instrumented Python code ran (e.g. a binary that only spawns a subprocess). That error currently propagates and fails an otherwise passing test under 'bazel coverage'. Catch NoDataError and skip writing the empty report instead. Fixes #2762 --- python/private/stage2_bootstrap_template.py | 31 +++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index a66b6428b0..8f7e89c820 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -408,15 +408,28 @@ def _maybe_collect_coverage(enable): cov.stop() lcov_path = os.path.join(coverage_dir, "pylcov_{}.dat".format(unique_id)) print_verbose_coverage("generating lcov from:", lcov_path) - cov.lcov_report( - outfile=lcov_path, - # Ignore errors because sometimes instrumented files aren't - # readable afterwards. e.g. if they come from /dev/fd or if - # they were transient code-under-test in /tmp - ignore_errors=True, - ) - if os.path.isfile(lcov_path): - unresolve_symlinks(lcov_path) + try: + cov.lcov_report( + outfile=lcov_path, + # Ignore errors because sometimes instrumented files aren't + # readable afterwards. e.g. if they come from /dev/fd or if + # they were transient code-under-test in /tmp + ignore_errors=True, + ) + except coverage.exceptions.NoDataError: + # coverage.py raises NoDataError instead of writing a report when + # no instrumented Python code was executed. This is common and + # benign, e.g. a binary that only spawns another process, or a + # test whose instrumented sources happen not to run any Python. + # Letting the error propagate would fail an otherwise passing + # test, so skip writing the (empty) report instead. + # See https://github.com/bazel-contrib/rules_python/issues/2762. + print_verbose_coverage( + "no coverage data collected; skipping lcov report:", lcov_path + ) + else: + if os.path.isfile(lcov_path): + unresolve_symlinks(lcov_path) finally: try: os.unlink(rcfile_name) From a59e43ef548ea1610f98546213e2ec6051b5d721 Mon Sep 17 00:00:00 2001 From: Maximilian Birkner Date: Thu, 18 Jun 2026 17:39:39 +0200 Subject: [PATCH 2/3] Import NoDataError explicitly instead of via coverage.exceptions Don't rely on the coverage.exceptions submodule being loaded as a side effect of 'import coverage'; import the exception name directly at the same lazy import site. --- python/private/stage2_bootstrap_template.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index 8f7e89c820..1e3c1899ce 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -357,6 +357,7 @@ def _maybe_collect_coverage(enable): print_verbose_coverage("Sources:\n" + "\n".join(unique_dirs)) import coverage + from coverage.exceptions import NoDataError coverage_dir = os.environ["COVERAGE_DIR"] unique_id = uuid.uuid4() @@ -416,7 +417,7 @@ def _maybe_collect_coverage(enable): # they were transient code-under-test in /tmp ignore_errors=True, ) - except coverage.exceptions.NoDataError: + except NoDataError: # coverage.py raises NoDataError instead of writing a report when # no instrumented Python code was executed. This is common and # benign, e.g. a binary that only spawns another process, or a From 9b5bb90f5e753fbb3635990e6b2b507f1dcbe3d4 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Fri, 19 Jun 2026 16:45:26 +0000 Subject: [PATCH 3/3] chore(coverage): shorten comment and add news fragment Address review feedback for PR #3832. Make the comment explaining `NoDataError` handling in the bootstrap template more concise, and add the required news fragment to document the fix for the upcoming release. * Shorten NoDataError comment in stage2_bootstrap_template.py. * Create news/3832.fixed.md. --- news/3832.fixed.md | 1 + python/private/stage2_bootstrap_template.py | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 news/3832.fixed.md diff --git a/news/3832.fixed.md b/news/3832.fixed.md new file mode 100644 index 0000000000..f1dd32df06 --- /dev/null +++ b/news/3832.fixed.md @@ -0,0 +1 @@ +(coverage) Skip lcov report when no data was collected. diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index 1e3c1899ce..b11fc76093 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -418,12 +418,9 @@ def _maybe_collect_coverage(enable): ignore_errors=True, ) except NoDataError: - # coverage.py raises NoDataError instead of writing a report when - # no instrumented Python code was executed. This is common and - # benign, e.g. a binary that only spawns another process, or a - # test whose instrumented sources happen not to run any Python. - # Letting the error propagate would fail an otherwise passing - # test, so skip writing the (empty) report instead. + # coverage.py raises NoDataError if no instrumented Python code ran + # (e.g. tests not running Python, or subprocess-only binaries). + # Skip the report to avoid failing otherwise passing tests. # See https://github.com/bazel-contrib/rules_python/issues/2762. print_verbose_coverage( "no coverage data collected; skipping lcov report:", lcov_path