Coverage for coverage / patch.py: 62.651%
63 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Invasive patches for coverage.py."""
6from __future__ import annotations 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
8import contextlib 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
9import os 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
10from typing import TYPE_CHECKING, Any, NoReturn 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
12from coverage import env 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
13from coverage.debug import DevNullDebug 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
14from coverage.exceptions import ConfigError, CoverageException 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
16if TYPE_CHECKING: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
17 from coverage import Coverage
18 from coverage.config import CoverageConfig
19 from coverage.types import TDebugCtl
22def apply_patches( 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
23 cov: Coverage,
24 config: CoverageConfig,
25 debug: TDebugCtl,
26) -> None:
27 """Apply invasive patches requested by `[run] patch=`."""
28 debug = debug if debug.should("patch") else DevNullDebug() 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
29 for patch in sorted(set(config.patch)): 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
30 match patch: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
31 case "_exit": 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
32 _patch__exit(cov, debug) 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
34 case "execv": 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
35 _patch_execv(cov, config, debug) 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
37 case "fork": 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
38 _patch_fork(debug) 1a
40 case "subprocess": 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
41 _patch_subprocess(config, debug) 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
43 case _: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
44 raise ConfigError(f"Unknown patch {patch!r}") 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45BCDEFG67HIJKLM89NOPQRS!#aT$
47def _patch__exit(cov: Coverage, debug: TDebugCtl) -> None: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
48 """Patch os._exit."""
49 debug.write("Patching _exit") 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
51 old_exit = os._exit 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
53 def coverage_os_exit_patch(status: int) -> NoReturn: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
54 with contextlib.suppress(Exception):
55 debug.write(f"Using _exit patch with {cov = }")
56 with contextlib.suppress(Exception):
57 cov.save()
58 old_exit(status)
60 os._exit = coverage_os_exit_patch 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
63def _patch_execv(cov: Coverage, config: CoverageConfig, debug: TDebugCtl) -> None: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
64 """Patch the execv family of functions."""
65 if env.WINDOWS: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
66 raise CoverageException("patch=execv isn't supported yet on Windows.")
68 debug.write("Patching execv") 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
70 def make_execv_patch(fname: str, old_execv: Any) -> Any: 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
71 def coverage_execv_patch(*args: Any, **kwargs: Any) -> Any: 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
72 with contextlib.suppress(Exception):
73 debug.write(f"Using execv patch for {fname} with {cov = }")
74 with contextlib.suppress(Exception):
75 cov.save()
77 if fname.endswith("e"):
78 # Assume the `env` argument is passed positionally.
79 new_env = args[-1]
80 # Pass our configuration in the new environment.
81 new_env["COVERAGE_PROCESS_CONFIG"] = config.serialize()
82 if env.TESTING:
83 # The subprocesses need to use the same core as the main process.
84 new_env["COVERAGE_CORE"] = os.getenv("COVERAGE_CORE")
86 # When testing locally, we need to honor the pyc file location
87 # or they get written to the .tox directories and pollute the
88 # next run with a different core.
89 if (cache_prefix := os.getenv("PYTHONPYCACHEPREFIX")) is not None:
90 new_env["PYTHONPYCACHEPREFIX"] = cache_prefix
92 # Without this, it fails on PyPy and Ubuntu.
93 new_env["PATH"] = os.getenv("PATH")
94 old_execv(*args, **kwargs)
96 return coverage_execv_patch 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
98 # All the exec* and spawn* functions eventually call execv or execve.
99 os.execv = make_execv_patch("execv", os.execv) 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
100 os.execve = make_execv_patch("execve", os.execve) 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
103def _patch_fork(debug: TDebugCtl) -> None: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
104 """Ensure Coverage is properly reset after a fork."""
105 from coverage.control import _after_fork_in_child
107 if env.WINDOWS:
108 raise CoverageException("patch=fork isn't supported yet on Windows.")
110 debug.write("Patching fork")
111 os.register_at_fork(after_in_child=_after_fork_in_child)
114def _patch_subprocess(config: CoverageConfig, debug: TDebugCtl) -> None: 1bcdeUVfghiWXjklmYZnopq01rstu23vwxyzA45%BCDEFG67'HIJKLM89(NOPQRS!#)aT$
115 """Write .pth files and set environment vars to measure subprocesses."""
116 debug.write("Patching subprocess") 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
117 assert config.config_file is not None 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT
118 os.environ["COVERAGE_PROCESS_CONFIG"] = config.serialize() 1bcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSaT