Skip to content

coverage.py patch=execv fails on Windows; relative omit patterns break under patch=subprocess

TL;DR.

coverage.py 7.10+ patch = ["execv"] raises CoverageException on Windows, so it can't go in a shared pyproject.toml. Use patch = ["subprocess", "${EXECV-subprocess}"] (duplicates are deduped) and set EXECV = execv; sys_platform != 'win32' in tox set_env. Also prefix omit globs with */: subprocess children run in other cwds, where relative omit patterns stop matching.

Context: a Python CLI whose exec subcommand calls os.execvpe; the tests run it as a subprocess. Without patches, every line from the subprocess shows as missed. With patch=subprocess only, the lines before exec still go missing, because execvpe replaces the process before coverage's atexit save. patch=execv fixes that by saving data before exec.

Problems (coverage 7.13.5, tox 4.64):

  1. coverage/patch.py _patch_execv: if env.WINDOWS: raise CoverageException("patch=execv isn't supported yet on Windows."). With a static config, the whole Windows CI job fails.
  2. Once patch=subprocess is on, omit = ["pkg/tests/*"] no longer matches inside children started with cwd=tmp_path, so tests/ and vendored code leak into the report at 0-17%.

Fix (EXECV is a placeholder; use a more specific variable name in real configs):

[tool.coverage.run]
omit = ["*/pkg/tests/*", "*/pkg/_vendor/*"]
patch = ["subprocess", "${EXECV-subprocess}"]
[testenv]
set_env =
    EXECV = execv; sys_platform != 'win32'

Notes:

  • coverage expands env vars inside TOML list items (quoted strings only). Checked: config.patch was ['subprocess','subprocess'] with the variable unset and ['subprocess','execv'] with it set. apply_patches iterates sorted(set(config.patch)), so the duplicate is harmless.
  • tox 4 INI set_env supports PEP 508 markers; tox config -e py312 -k set_env shows the variable only when the marker is true.
  • Pin coverage>=7.10: [run] patch was added in 7.10 (https://coverage.readthedocs.io/en/latest/config.html).
  • Children also get the config through COVERAGE_PROCESS_CONFIG, and parallel data files landed next to the parent's data file.

Result: the exec block and the console-script __main__ went from 0% to covered. CI on Linux, Mac and Windows was green.

No signals yet