DebugClients/Python/coverage/multiproc.py

changeset 6219
d6c795b5ce33
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3
4 """Monkey-patching to add multiprocessing support for coverage.py"""
5
6 import multiprocessing
7 import multiprocessing.process
8 import os
9 import sys
10
11 from coverage.misc import contract
12
13 # An attribute that will be set on the module to indicate that it has been
14 # monkey-patched.
15 PATCHED_MARKER = "_coverage$patched"
16
17 # The environment variable that specifies the rcfile for subprocesses.
18 COVERAGE_RCFILE_ENV = "_COVERAGE_RCFILE"
19
20
21 if sys.version_info >= (3, 4):
22 OriginalProcess = multiprocessing.process.BaseProcess
23 else:
24 OriginalProcess = multiprocessing.Process
25
26 original_bootstrap = OriginalProcess._bootstrap
27
28 class ProcessWithCoverage(OriginalProcess):
29 """A replacement for multiprocess.Process that starts coverage."""
30
31 def _bootstrap(self):
32 """Wrapper around _bootstrap to start coverage."""
33 from coverage import Coverage # avoid circular import
34 rcfile = os.environ[COVERAGE_RCFILE_ENV]
35 cov = Coverage(data_suffix=True, config_file=rcfile)
36 cov.start()
37 debug = cov.debug
38 try:
39 if debug.should("multiproc"):
40 debug.write("Calling multiprocessing bootstrap")
41 return original_bootstrap(self)
42 finally:
43 if debug.should("multiproc"):
44 debug.write("Finished multiprocessing bootstrap")
45 cov.stop()
46 cov.save()
47 if debug.should("multiproc"):
48 debug.write("Saved multiprocessing data")
49
50 class Stowaway(object):
51 """An object to pickle, so when it is unpickled, it can apply the monkey-patch."""
52 def __init__(self, rcfile):
53 self.rcfile = rcfile
54
55 def __getstate__(self):
56 return {'rcfile': self.rcfile}
57
58 def __setstate__(self, state):
59 patch_multiprocessing(state['rcfile'])
60
61
62 @contract(rcfile=str)
63 def patch_multiprocessing(rcfile):
64 """Monkey-patch the multiprocessing module.
65
66 This enables coverage measurement of processes started by multiprocessing.
67 This involves aggressive monkey-patching.
68
69 `rcfile` is the path to the rcfile being used.
70
71 """
72
73 if hasattr(multiprocessing, PATCHED_MARKER):
74 return
75
76 if sys.version_info >= (3, 4):
77 OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap
78 else:
79 multiprocessing.Process = ProcessWithCoverage
80
81 # Set the value in ProcessWithCoverage that will be pickled into the child
82 # process.
83 os.environ[COVERAGE_RCFILE_ENV] = rcfile
84
85 # When spawning processes rather than forking them, we have no state in the
86 # new process. We sneak in there with a Stowaway: we stuff one of our own
87 # objects into the data that gets pickled and sent to the sub-process. When
88 # the Stowaway is unpickled, it's __setstate__ method is called, which
89 # re-applies the monkey-patch.
90 # Windows only spawns, so this is needed to keep Windows working.
91 try:
92 from multiprocessing import spawn
93 original_get_preparation_data = spawn.get_preparation_data
94 except (ImportError, AttributeError):
95 pass
96 else:
97 def get_preparation_data_with_stowaway(name):
98 """Get the original preparation data, and also insert our stowaway."""
99 d = original_get_preparation_data(name)
100 d['stowaway'] = Stowaway(rcfile)
101 return d
102
103 spawn.get_preparation_data = get_preparation_data_with_stowaway
104
105 setattr(multiprocessing, PATCHED_MARKER, True)

eric ide

mercurial