summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2015-01-30 21:07:25 -0500
committerNed Batchelder <ned@nedbatchelder.com>2015-01-30 21:07:25 -0500
commit6c631d76f22c50220bba51ec3191260d7e74b11f (patch)
tree13d62e18be0149d11b6f772172b712a5a827ecb3 /coverage
parentcab3262dd815c454e8db967f97afce8a4b071d4c (diff)
downloadpython-coveragepy-git-6c631d76f22c50220bba51ec3191260d7e74b11f.tar.gz
Wildly experimental multiprocessing support. Covers most of #117.
Diffstat (limited to 'coverage')
-rw-r--r--coverage/cmdline.py4
-rw-r--r--coverage/control.py10
-rw-r--r--coverage/monkey.py45
3 files changed, 57 insertions, 2 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index ff3de4ca..4ef08f1d 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -24,7 +24,9 @@ class Opts(object):
'', '--branch', action='store_true',
help="Measure branch coverage in addition to statement coverage."
)
- CONCURRENCY_CHOICES = ["thread", "gevent", "greenlet", "eventlet"]
+ CONCURRENCY_CHOICES = [
+ "thread", "gevent", "greenlet", "eventlet", "multiprocessing",
+ ]
concurrency = optparse.make_option(
'', '--concurrency', action='store', metavar="LIB",
choices=CONCURRENCY_CHOICES,
diff --git a/coverage/control.py b/coverage/control.py
index 319f56dc..f422d7c0 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -21,6 +21,7 @@ from coverage.files import ModuleMatcher
from coverage.html import HtmlReporter
from coverage.misc import CoverageException, bool_or_none, join_regex
from coverage.misc import file_be_gone, overrides
+from coverage.monkey import patch_multiprocessing
from coverage.plugin import CoveragePlugin, FileReporter
from coverage.python import PythonCodeUnit
from coverage.results import Analysis, Numbers
@@ -219,13 +220,18 @@ class Coverage(object):
self.omit = prep_patterns(self.config.omit)
self.include = prep_patterns(self.config.include)
+ concurrency = self.config.concurrency
+ if concurrency == "multiprocessing":
+ patch_multiprocessing()
+ concurrency = None
+
self.collector = Collector(
should_trace=self._should_trace,
check_include=self._check_include_omit_etc,
timid=self.config.timid,
branch=self.config.branch,
warn=self._warn,
- concurrency=self.config.concurrency,
+ concurrency=concurrency,
)
# Suffixes are a bit tricky. We want to use the data suffix only when
@@ -544,6 +550,8 @@ class Coverage(object):
def _warn(self, msg):
"""Use `msg` as a warning."""
self._warnings.append(msg)
+ if self.debug.should("pid"):
+ msg = "[%d] %s" % (os.getpid(), msg)
sys.stderr.write("Coverage.py warning: %s\n" % msg)
def use_cache(self, usecache):
diff --git a/coverage/monkey.py b/coverage/monkey.py
new file mode 100644
index 00000000..42f185ea
--- /dev/null
+++ b/coverage/monkey.py
@@ -0,0 +1,45 @@
+"""Monkey-patching to make coverage 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.
+MARKER = "_coverage$patched"
+
+
+def patch_multiprocessing():
+ """Monkey-patch the multiprocessing module.
+
+ This enables coverage measurement of processes started by multiprocessing.
+ This is wildly experimental!
+
+ """
+ if hasattr(multiprocessing, MARKER):
+ return
+
+ if sys.version_info >= (3, 4):
+ klass = multiprocessing.process.BaseProcess
+ else:
+ klass = multiprocessing.Process
+
+ original_bootstrap = klass._bootstrap
+
+ class ProcessWithCoverage(klass):
+ def _bootstrap(self):
+ from coverage import Coverage
+ cov = Coverage(data_suffix=True)
+ cov.start()
+ try:
+ return original_bootstrap(self)
+ finally:
+ cov.stop()
+ cov.save()
+
+ if sys.version_info >= (3, 4):
+ klass._bootstrap = ProcessWithCoverage._bootstrap
+ else:
+ multiprocessing.Process = ProcessWithCoverage
+
+ setattr(multiprocessing, MARKER, 1)