Skip to content

feat: collect coverage data from Python subprocesses#1055

Open
ctcjab wants to merge 1 commit into
aspect-build:mainfrom
chicagotrading:subprocess-coverage
Open

feat: collect coverage data from Python subprocesses#1055
ctcjab wants to merge 1 commit into
aspect-build:mainfrom
chicagotrading:subprocess-coverage

Conversation

@ctcjab
Copy link
Copy Markdown
Contributor

@ctcjab ctcjab commented May 24, 2026

Summary

When coverage is enabled (bazel coverage), propagate the configuration to Python subprocesses so that pytest-xdist workers, subprocess.Popen calls, and other child processes sharing the same venv automatically measure coverage.

Problem: pytest_main.py only collects coverage from the in-process test code. Code executing in subprocesses (e.g. pytest-xdist workers, or tests that spawn Python subprocesses) is invisible to coverage.py, which can cause NoDataError crashes or incomplete coverage reports.

Fix (~15 lines in pytest_main.py):

  • Setup: Write a .pth file to the venv's site-packages so every subprocess calls coverage.process_startup() at interpreter init. Set COVERAGE_PROCESS_CONFIG with the serialized config. Use data_suffix=True so per-process data files don't collide.
  • Teardown: save() + combine() merges all per-process data files before generating the LCOV report.

Also bumps coverage minimum to >=7.10.3 for CoverageConfig.serialize().

Scope

This covers subprocesses that share the test's venv (pytest-xdist workers, same-venv subprocess calls). Cross-binary coverage (subprocess of a separate Bazel binary with its own venv) remains unsupported — that would require changes to binary build rules.

Test plan

  • Existing coverage_lcov_shape_test passes (no regression)
  • New subprocess_coverage_lcov_test — calls foo.subtract() in a subprocess, asserts the LCOV contains DA:5,<nonzero> proving subprocess coverage was captured
  • All //examples/pytest/... tests pass (10/10)

🤖 Generated with Claude Code

When coverage is enabled, propagate the configuration to subprocesses
so that pytest-xdist workers, subprocess.Popen calls, and other child
processes sharing the same venv automatically measure coverage.

At setup time, write a .pth file into the venv's site-packages so
every subprocess calls coverage.process_startup() at interpreter init,
and set COVERAGE_PROCESS_CONFIG with the serialized config.  At
teardown, save() + combine() merges per-process data files before
generating the LCOV report.

Requires coverage >= 7.10.3 for CoverageConfig.serialize().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aspect-workflows
Copy link
Copy Markdown

aspect-workflows Bot commented May 24, 2026

Bazel 8 (Test)

⚠️ Buildkite build #4017 failed.

Failed tests (4)
//uv/private/uv_hub:snapshots_5_test [k8-fastbuild]                 🔗
//uv/private/uv_hub:snapshots_6_test [k8-fastbuild]                 🔗
//uv/private/uv_hub:snapshots_7_test [k8-fastbuild]                 🔗
//uv/private/uv_hub:snapshots_8_test [k8-fastbuild]                 🔗

💡 To reproduce the test failures, run

bazel test //uv/private/uv_hub:snapshots_7_test //uv/private/uv_hub:snapshots_6_test //uv/private/uv_hub:snapshots_8_test //uv/private/uv_hub:snapshots_5_test

Bazel 9 (Test)

⚠️ Buildkite build #4017 failed.

Failed tests (4)
//uv/private/uv_hub:snapshots_5_test [k8-fastbuild]                 🔗
//uv/private/uv_hub:snapshots_6_test [k8-fastbuild]                 🔗
//uv/private/uv_hub:snapshots_7_test [k8-fastbuild]                 🔗
//uv/private/uv_hub:snapshots_8_test [k8-fastbuild]                 🔗

💡 To reproduce the test failures, run

bazel test //uv/private/uv_hub:snapshots_8_test //uv/private/uv_hub:snapshots_6_test //uv/private/uv_hub:snapshots_7_test //uv/private/uv_hub:snapshots_5_test

Bazel 8 (Test)

e2e

10 test targets passed

Targets
//cases/current-py-toolchain-bazel-env-896:test [k8-fastbuild]                 211ms
//cases/pytest-main-867:test_collection_check [k8-fastbuild]                   2s
//cases/pytest-main-867:test_example [k8-fastbuild]                            853ms
//cases/pytest-main-867:test_multi_src [k8-fastbuild]                          992ms
//cases/pytest-main-867:test_naming_regression [k8-fastbuild]                  1s
//cases/pytest-mock-530:test_mock_py_test [k8-fastbuild]                       1s
//cases/pytest-subdir-imports:test_subdir_import [k8-fastbuild]                846ms
//cases/pytest-xdist-integration:test_xdist [k8-fastbuild]                     2s
//cases/unconstrained-dependencies:get_python_package_version_humble_test [k8-fastbuild]948ms
//cases/unconstrained-dependencies:get_python_package_version_main_test [k8-fastbuild]1s

Total test execution time was 11s. 127 tests (92.7%) were fully cached saving 2m 1s.


Bazel 9 (Test)

e2e

10 test targets passed

Targets
//cases/current-py-toolchain-bazel-env-896:test [k8-fastbuild]                 100ms
//cases/pytest-main-867:test_collection_check [k8-fastbuild]                   2s
//cases/pytest-main-867:test_example [k8-fastbuild]                            793ms
//cases/pytest-main-867:test_multi_src [k8-fastbuild]                          958ms
//cases/pytest-main-867:test_naming_regression [k8-fastbuild]                  955ms
//cases/pytest-mock-530:test_mock_py_test [k8-fastbuild]                       947ms
//cases/pytest-subdir-imports:test_subdir_import [k8-fastbuild]                704ms
//cases/pytest-xdist-integration:test_xdist [k8-fastbuild]                     2s
//cases/unconstrained-dependencies:get_python_package_version_humble_test [k8-fastbuild]871ms
//cases/unconstrained-dependencies:get_python_package_version_main_test [k8-fastbuild]996ms

Total test execution time was 11s. 122 tests (92.4%) were fully cached saving 1m 32s.


Bazel 8 (Test)

examples/uv_pip_compile

All tests were cache hits

1 test (100.0%) was fully cached saving 444ms.


Buildifier      Gazelle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant