Skip to content

Commit 6af8a5d

Browse files
committed
feat: patch=fork
1 parent 0eb292d commit 6af8a5d

File tree

4 files changed

+34
-7
lines changed

4 files changed

+34
-7
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26+
- Added ``patch = fork`` for times when the built-in forking support is
27+
insufficient.
28+
2629
- Fix: ``patch = execv`` also inherits the entire coverage configuration now.
2730

2831

coverage/control.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,7 +1410,7 @@ def plugin_info(plugins: list[Any]) -> list[str]:
14101410
)(Coverage)
14111411

14121412

1413-
def process_startup() -> Coverage | None:
1413+
def process_startup(*, force: bool = False) -> Coverage | None:
14141414
"""Call this at Python start-up to perhaps measure coverage.
14151415
14161416
If the environment variable COVERAGE_PROCESS_START is defined, coverage
@@ -1442,7 +1442,7 @@ def process_startup() -> Coverage | None:
14421442
#
14431443
# https://github.com/nedbat/coveragepy/issues/340 has more details.
14441444

1445-
if hasattr(process_startup, "coverage"):
1445+
if not force and hasattr(process_startup, "coverage"):
14461446
# We've annotated this function before, so we must have already
14471447
# auto-started coverage.py in this process. Nothing to do.
14481448
return None
@@ -1459,6 +1459,13 @@ def process_startup() -> Coverage | None:
14591459
return cov
14601460

14611461

1462+
def _after_fork_in_child() -> None:
1463+
"""Used by patch=fork in the child process to restart coverage."""
1464+
if cov := Coverage.current():
1465+
cov.stop()
1466+
process_startup(force=True)
1467+
1468+
14621469
def _prevent_sub_process_measurement() -> None:
14631470
"""Stop any subprocess auto-measurement from writing data."""
14641471
auto_created_coverage = getattr(process_startup, "coverage", None)

coverage/patch.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ def apply_patches(
3838
elif patch == "execv":
3939
_patch_execv(cov, config, debug)
4040

41+
elif patch == "fork":
42+
_patch_fork(debug)
43+
4144
elif patch == "subprocess":
4245
_patch_subprocess(config, debug, make_pth_file)
4346

@@ -101,6 +104,16 @@ def coverage_execv_patch(*args: Any, **kwargs: Any) -> Any:
101104
os.execve = make_execv_patch("execve", os.execve)
102105

103106

107+
def _patch_fork(debug: TDebugCtl) -> None:
108+
"""Ensure Coverage is properly reset after a fork."""
109+
from coverage.control import _after_fork_in_child
110+
if env.WINDOWS:
111+
raise CoverageException("patch=fork isn't supported yet on Windows.")
112+
113+
debug.write("Patching fork")
114+
os.register_at_fork(after_in_child=_after_fork_in_child)
115+
116+
104117
def _patch_subprocess(config: CoverageConfig, debug: TDebugCtl, make_pth_file: bool) -> None:
105118
"""Write .pth files and set environment vars to measure subprocesses."""
106119
debug.write("Patching subprocess")

doc/config.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,19 @@ side-effects.
443443

444444
Available patches:
445445

446+
- ``execv``: The :func:`execv <python:os.execl>` family of functions end the
447+
current program without giving coverage a chance to write collected data.
448+
This patch adjusts those functions to save the data before starting the next
449+
executable. Not available on Windows.
450+
446451
- ``_exit``: The :func:`os._exit() <python:os._exit>` function exits the
447452
process immediately without calling cleanup handlers. This patch saves
448453
coverage data before exiting.
449454

455+
- ``fork``: Forking a process normally continues measuring coverage properly.
456+
This patch stops the previous coverage and starts a new one in the child
457+
process for those times when the default handling isn't enough.
458+
450459
- ``subprocess``: Python sub-processes normally won't get coverage measurement.
451460
This patch configures Python to start coverage automatically, and will apply
452461
to processes created with :mod:`subprocess`, :func:`os.system`, or one of the
@@ -459,11 +468,6 @@ Available patches:
459468
and will require combining data files before reporting. See
460469
:ref:`cmd_combine` for more details.
461470

462-
- ``execv``: The :func:`execv <python:os.execl>` family of functions end the
463-
current program without giving coverage a chance to write collected data.
464-
This patch adjusts those functions to save the data before starting the next
465-
executable. Not available on Windows.
466-
467471
.. versionadded:: 7.10
468472

469473

0 commit comments

Comments
 (0)