summaryrefslogtreecommitdiff
path: root/coverage/multiproc.py
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2016-07-12 20:04:34 -0400
committerNed Batchelder <ned@nedbatchelder.com>2016-07-12 20:04:34 -0400
commit4727fe25ceda84b47ffdc8e84da52caae5b8795c (patch)
tree2f4f1a89bea6f1b8eb98b07c7679671a01997b11 /coverage/multiproc.py
parent9a69a43b43f89408fe7afcea76954cbdcf1c09b9 (diff)
downloadpython-coveragepy-git-4727fe25ceda84b47ffdc8e84da52caae5b8795c.tar.gz
Rename to multiproc.py, since it only does one thing.
--HG-- rename : coverage/monkey.py => coverage/multiproc.py
Diffstat (limited to 'coverage/multiproc.py')
-rw-r--r--coverage/multiproc.py80
1 files changed, 80 insertions, 0 deletions
diff --git a/coverage/multiproc.py b/coverage/multiproc.py
new file mode 100644
index 00000000..d0bdf009
--- /dev/null
+++ b/coverage/multiproc.py
@@ -0,0 +1,80 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+"""Monkey-patching to make coverage.py work right in some cases."""
+
+import multiprocessing
+import multiprocessing.process
+import sys
+
+# An attribute that will be set on modules to indicate that they have been
+# monkey-patched.
+PATCHED_MARKER = "_coverage$patched"
+
+if sys.version_info >= (3, 4):
+ klass = multiprocessing.process.BaseProcess
+else:
+ klass = multiprocessing.Process
+
+original_bootstrap = klass._bootstrap
+
+
+class ProcessWithCoverage(klass):
+ """A replacement for multiprocess.Process that starts coverage."""
+ def _bootstrap(self):
+ """Wrapper around _bootstrap to start coverage."""
+ from coverage import Coverage
+ cov = Coverage(data_suffix=True)
+ cov.start()
+ try:
+ return original_bootstrap(self)
+ finally:
+ cov.stop()
+ cov.save()
+
+
+class Stowaway(object):
+ """An object to pickle, so when it is unpickled, it can apply the monkey-patch."""
+ def __getstate__(self):
+ return {}
+
+ def __setstate__(self, state_unused):
+ patch_multiprocessing()
+
+
+def patch_multiprocessing():
+ """Monkey-patch the multiprocessing module.
+
+ This enables coverage measurement of processes started by multiprocessing.
+ This is wildly experimental!
+
+ """
+ if hasattr(multiprocessing, PATCHED_MARKER):
+ return
+
+ if sys.version_info >= (3, 4):
+ klass._bootstrap = ProcessWithCoverage._bootstrap
+ else:
+ multiprocessing.Process = ProcessWithCoverage
+
+ # 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 sub-process. When
+ # the Stowaway is unpickled, it's __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):
+ """Get the original preparation data, and also insert our stowaway."""
+ d = original_get_preparation_data(name)
+ d['stowaway'] = Stowaway()
+ return d
+
+ spawn.get_preparation_data = get_preparation_data_with_stowaway
+
+ setattr(multiprocessing, PATCHED_MARKER, True)