Major fixes and new features
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
120
venv/lib/python3.12/site-packages/coverage/multiproc.py
Normal file
120
venv/lib/python3.12/site-packages/coverage/multiproc.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
||||
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
||||
|
||||
"""Monkey-patching to add multiprocessing support for coverage.py"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import multiprocessing
|
||||
import multiprocessing.process
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Any
|
||||
|
||||
from coverage.debug import DebugControl
|
||||
|
||||
# An attribute that will be set on the module to indicate that it has been
|
||||
# monkey-patched.
|
||||
PATCHED_MARKER = "_coverage$patched"
|
||||
|
||||
|
||||
OriginalProcess = multiprocessing.process.BaseProcess
|
||||
original_bootstrap = OriginalProcess._bootstrap # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method
|
||||
"""A replacement for multiprocess.Process that starts coverage."""
|
||||
|
||||
def _bootstrap(self, *args, **kwargs): # type: ignore[no-untyped-def]
|
||||
"""Wrapper around _bootstrap to start coverage."""
|
||||
debug: DebugControl | None = None
|
||||
try:
|
||||
from coverage import Coverage # avoid circular import
|
||||
|
||||
cov = Coverage(data_suffix=True, auto_data=True)
|
||||
cov._warn_preimported_source = False
|
||||
cov.start()
|
||||
_debug = cov._debug
|
||||
assert _debug is not None
|
||||
if _debug.should("multiproc"):
|
||||
debug = _debug
|
||||
if debug:
|
||||
debug.write("Calling multiprocessing bootstrap")
|
||||
except Exception:
|
||||
print("Exception during multiprocessing bootstrap init:", file=sys.stderr)
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
raise
|
||||
try:
|
||||
return original_bootstrap(self, *args, **kwargs)
|
||||
finally:
|
||||
if debug:
|
||||
debug.write("Finished multiprocessing bootstrap")
|
||||
try:
|
||||
cov.stop()
|
||||
cov.save()
|
||||
except Exception as exc:
|
||||
if debug:
|
||||
debug.write("Exception during multiprocessing bootstrap cleanup", exc=exc)
|
||||
raise
|
||||
if debug:
|
||||
debug.write("Saved multiprocessing data")
|
||||
|
||||
|
||||
class Stowaway:
|
||||
"""An object to pickle, so when it is unpickled, it can apply the monkey-patch."""
|
||||
|
||||
def __init__(self, rcfile: str) -> None:
|
||||
self.rcfile = rcfile
|
||||
|
||||
def __getstate__(self) -> dict[str, str]:
|
||||
return {"rcfile": self.rcfile}
|
||||
|
||||
def __setstate__(self, state: dict[str, str]) -> None:
|
||||
patch_multiprocessing(state["rcfile"])
|
||||
|
||||
|
||||
def patch_multiprocessing(rcfile: str) -> None:
|
||||
"""Monkey-patch the multiprocessing module.
|
||||
|
||||
This enables coverage measurement of processes started by multiprocessing.
|
||||
This involves aggressive monkey-patching.
|
||||
|
||||
`rcfile` is the path to the rcfile being used.
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(multiprocessing, PATCHED_MARKER):
|
||||
return
|
||||
|
||||
OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap # type: ignore[attr-defined]
|
||||
|
||||
# Set the value in ProcessWithCoverage that will be pickled into the child
|
||||
# process.
|
||||
os.environ["COVERAGE_RCFILE"] = os.path.abspath(rcfile)
|
||||
|
||||
# When spawning processes rather than forking them, we have no state in the
|
||||
# new process. We sneak in there with a Stowaway: we stuff one of our own
|
||||
# objects into the data that gets pickled and sent to the subprocess. When
|
||||
# the Stowaway is unpickled, its __setstate__ method is called, which
|
||||
# re-applies the monkey-patch.
|
||||
# Windows only spawns, so this is needed to keep Windows working.
|
||||
try:
|
||||
from multiprocessing import spawn
|
||||
|
||||
original_get_preparation_data = spawn.get_preparation_data
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
|
||||
def get_preparation_data_with_stowaway(name: str) -> dict[str, Any]:
|
||||
"""Get the original preparation data, and also insert our stowaway."""
|
||||
d = original_get_preparation_data(name)
|
||||
d["stowaway"] = Stowaway(rcfile)
|
||||
return d
|
||||
|
||||
spawn.get_preparation_data = get_preparation_data_with_stowaway
|
||||
|
||||
setattr(multiprocessing, PATCHED_MARKER, True)
|
||||
Reference in New Issue
Block a user