DebugClients/Python/coverage/collector.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
5 5
6 import os 6 import os
7 import sys 7 import sys
8 8
9 from coverage import env 9 from coverage import env
10 from coverage.backward import iitems 10 from coverage.backward import litems, range # pylint: disable=redefined-builtin
11 from coverage.debug import short_stack
11 from coverage.files import abs_file 12 from coverage.files import abs_file
12 from coverage.misc import CoverageException, isolate_module 13 from coverage.misc import CoverageException, isolate_module
13 from coverage.pytracer import PyTracer 14 from coverage.pytracer import PyTracer
14 15
15 os = isolate_module(os) 16 os = isolate_module(os)
16 17
17 18
18 try: 19 try:
19 # Use the C extension code when we can, for speed. 20 # Use the C extension code when we can, for speed.
20 from coverage.tracer import CTracer, CFileDisposition # pylint: disable=no-name-in-module 21 from coverage.tracer import CTracer, CFileDisposition
21 except ImportError: 22 except ImportError:
22 # Couldn't import the C extension, maybe it isn't built. 23 # Couldn't import the C extension, maybe it isn't built.
23 if os.getenv('COVERAGE_TEST_TRACER') == 'c': 24 if os.getenv('COVERAGE_TEST_TRACER') == 'c':
24 # During testing, we use the COVERAGE_TEST_TRACER environment variable 25 # During testing, we use the COVERAGE_TEST_TRACER environment variable
25 # to indicate that we've fiddled with the environment to test this 26 # to indicate that we've fiddled with the environment to test this
40 def should_start_context(frame): 41 def should_start_context(frame):
41 """Who-Tests-What hack: Determine whether this frame begins a new who-context.""" 42 """Who-Tests-What hack: Determine whether this frame begins a new who-context."""
42 fn_name = frame.f_code.co_name 43 fn_name = frame.f_code.co_name
43 if fn_name.startswith("test"): 44 if fn_name.startswith("test"):
44 return fn_name 45 return fn_name
46 return None
45 47
46 48
47 class Collector(object): 49 class Collector(object):
48 """Collects trace data. 50 """Collects trace data.
49 51
63 # The stack of active Collectors. Collectors are added here when started, 65 # The stack of active Collectors. Collectors are added here when started,
64 # and popped when stopped. Collectors on the stack are paused when not 66 # and popped when stopped. Collectors on the stack are paused when not
65 # the top, and resumed when they become the top again. 67 # the top, and resumed when they become the top again.
66 _collectors = [] 68 _collectors = []
67 69
70 # The concurrency settings we support here.
71 SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"])
72
68 def __init__(self, should_trace, check_include, timid, branch, warn, concurrency): 73 def __init__(self, should_trace, check_include, timid, branch, warn, concurrency):
69 """Create a collector. 74 """Create a collector.
70 75
71 `should_trace` is a function, taking a file name, and returning a 76 `should_trace` is a function, taking a file name and a frame, and
72 `coverage.FileDisposition object`. 77 returning a `coverage.FileDisposition object`.
73 78
74 `check_include` is a function taking a file name and a frame. It returns 79 `check_include` is a function taking a file name and a frame. It returns
75 a boolean: True if the file should be traced, False if not. 80 a boolean: True if the file should be traced, False if not.
76 81
77 If `timid` is true, then a slower simpler trace function will be 82 If `timid` is true, then a slower simpler trace function will be
81 86
82 If `branch` is true, then branches will be measured. This involves 87 If `branch` is true, then branches will be measured. This involves
83 collecting data on which statements followed each other (arcs). Use 88 collecting data on which statements followed each other (arcs). Use
84 `get_arc_data` to get the arc data. 89 `get_arc_data` to get the arc data.
85 90
86 `warn` is a warning function, taking a single string message argument, 91 `warn` is a warning function, taking a single string message argument
87 to be used if a warning needs to be issued. 92 and an optional slug argument which will be a string or None, to be
88 93 used if a warning needs to be issued.
89 `concurrency` is a string indicating the concurrency library in use. 94
90 Valid values are "greenlet", "eventlet", "gevent", or "thread" (the 95 `concurrency` is a list of strings indicating the concurrency libraries
91 default). 96 in use. Valid values are "greenlet", "eventlet", "gevent", or "thread"
97 (the default). Of these four values, only one can be supplied. Other
98 values are ignored.
92 99
93 """ 100 """
94 self.should_trace = should_trace 101 self.should_trace = should_trace
95 self.check_include = check_include 102 self.check_include = check_include
96 self.warn = warn 103 self.warn = warn
97 self.branch = branch 104 self.branch = branch
98 self.threading = None 105 self.threading = None
99 self.concurrency = concurrency 106
107 self.origin = short_stack()
100 108
101 self.concur_id_func = None 109 self.concur_id_func = None
102 110
111 # We can handle a few concurrency options here, but only one at a time.
112 these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
113 if len(these_concurrencies) > 1:
114 raise CoverageException("Conflicting concurrency settings: %s" % concurrency)
115 self.concurrency = these_concurrencies.pop() if these_concurrencies else ''
116
103 try: 117 try:
104 if concurrency == "greenlet": 118 if self.concurrency == "greenlet":
105 import greenlet 119 import greenlet
106 self.concur_id_func = greenlet.getcurrent 120 self.concur_id_func = greenlet.getcurrent
107 elif concurrency == "eventlet": 121 elif self.concurrency == "eventlet":
108 import eventlet.greenthread # pylint: disable=import-error,useless-suppression 122 import eventlet.greenthread # pylint: disable=import-error,useless-suppression
109 self.concur_id_func = eventlet.greenthread.getcurrent 123 self.concur_id_func = eventlet.greenthread.getcurrent
110 elif concurrency == "gevent": 124 elif self.concurrency == "gevent":
111 import gevent # pylint: disable=import-error,useless-suppression 125 import gevent # pylint: disable=import-error,useless-suppression
112 self.concur_id_func = gevent.getcurrent 126 self.concur_id_func = gevent.getcurrent
113 elif concurrency == "thread" or not concurrency: 127 elif self.concurrency == "thread" or not self.concurrency:
114 # It's important to import threading only if we need it. If 128 # It's important to import threading only if we need it. If
115 # it's imported early, and the program being measured uses 129 # it's imported early, and the program being measured uses
116 # gevent, then gevent's monkey-patching won't work properly. 130 # gevent, then gevent's monkey-patching won't work properly.
117 import threading 131 import threading
118 self.threading = threading 132 self.threading = threading
119 else: 133 else:
120 raise CoverageException("Don't understand concurrency=%s" % concurrency) 134 raise CoverageException("Don't understand concurrency=%s" % concurrency)
121 except ImportError: 135 except ImportError:
122 raise CoverageException( 136 raise CoverageException(
123 "Couldn't trace with concurrency=%s, the module isn't installed." % concurrency 137 "Couldn't trace with concurrency=%s, the module isn't installed." % (
138 self.concurrency,
139 )
124 ) 140 )
125 141
126 # Who-Tests-What is just a hack at the moment, so turn it on with an 142 # Who-Tests-What is just a hack at the moment, so turn it on with an
127 # environment variable. 143 # environment variable.
128 self.wtw = int(os.getenv('COVERAGE_WTW', 0)) 144 self.wtw = int(os.getenv('COVERAGE_WTW', 0))
148 return "<Collector at 0x%x: %s>" % (id(self), self.tracer_name()) 164 return "<Collector at 0x%x: %s>" % (id(self), self.tracer_name())
149 165
150 def tracer_name(self): 166 def tracer_name(self):
151 """Return the class name of the tracer we're using.""" 167 """Return the class name of the tracer we're using."""
152 return self._trace_class.__name__ 168 return self._trace_class.__name__
169
170 def _clear_data(self):
171 """Clear out existing data, but stay ready for more collection."""
172 self.data.clear()
173
174 for tracer in self.tracers:
175 tracer.reset_activity()
153 176
154 def reset(self): 177 def reset(self):
155 """Clear collected data, and prepare to collect more.""" 178 """Clear collected data, and prepare to collect more."""
156 # A dictionary mapping file names to dicts with line number keys (if not 179 # A dictionary mapping file names to dicts with line number keys (if not
157 # branch coverage), or mapping file names to dicts with line number 180 # branch coverage), or mapping file names to dicts with line number
195 self.should_trace_cache = {} 218 self.should_trace_cache = {}
196 219
197 # Our active Tracers. 220 # Our active Tracers.
198 self.tracers = [] 221 self.tracers = []
199 222
223 self._clear_data()
224
200 def _start_tracer(self): 225 def _start_tracer(self):
201 """Start a new Tracer object, and store it in self.tracers.""" 226 """Start a new Tracer object, and store it in self.tracers."""
202 tracer = self._trace_class() 227 tracer = self._trace_class()
203 tracer.data = self.data 228 tracer.data = self.data
204 tracer.trace_arcs = self.branch 229 tracer.trace_arcs = self.branch
254 def start(self): 279 def start(self):
255 """Start collecting trace information.""" 280 """Start collecting trace information."""
256 if self._collectors: 281 if self._collectors:
257 self._collectors[-1].pause() 282 self._collectors[-1].pause()
258 283
284 self.tracers = []
285
259 # Check to see whether we had a fullcoverage tracer installed. If so, 286 # Check to see whether we had a fullcoverage tracer installed. If so,
260 # get the stack frames it stashed away for us. 287 # get the stack frames it stashed away for us.
261 traces0 = [] 288 traces0 = []
262 fn0 = sys.gettrace() 289 fn0 = sys.gettrace()
263 if fn0: 290 if fn0:
283 try: 310 try:
284 fn(frame, event, arg, lineno=lineno) 311 fn(frame, event, arg, lineno=lineno)
285 except TypeError: 312 except TypeError:
286 raise Exception("fullcoverage must be run with the C trace function.") 313 raise Exception("fullcoverage must be run with the C trace function.")
287 314
288 # Install our installation tracer in threading, to jump start other 315 # Install our installation tracer in threading, to jump-start other
289 # threads. 316 # threads.
290 if self.threading: 317 if self.threading:
291 self.threading.settrace(self._installation_trace) 318 self.threading.settrace(self._installation_trace)
292 319
293 def stop(self): 320 def stop(self):
294 """Stop collecting trace information.""" 321 """Stop collecting trace information."""
295 assert self._collectors 322 assert self._collectors
323 if self._collectors[-1] is not self:
324 print("self._collectors:")
325 for c in self._collectors:
326 print(" {!r}\n{}".format(c, c.origin))
296 assert self._collectors[-1] is self, ( 327 assert self._collectors[-1] is self, (
297 "Expected current collector to be %r, but it's %r" % (self, self._collectors[-1]) 328 "Expected current collector to be %r, but it's %r" % (self, self._collectors[-1])
298 ) 329 )
299 330
300 self.pause() 331 self.pause()
301 self.tracers = []
302 332
303 # Remove this Collector from the stack, and resume the one underneath 333 # Remove this Collector from the stack, and resume the one underneath
304 # (if any). 334 # (if any).
305 self._collectors.pop() 335 self._collectors.pop()
306 if self._collectors: 336 if self._collectors:
325 if self.threading: 355 if self.threading:
326 self.threading.settrace(self._installation_trace) 356 self.threading.settrace(self._installation_trace)
327 else: 357 else:
328 self._start_tracer() 358 self._start_tracer()
329 359
360 def _activity(self):
361 """Has any activity been traced?
362
363 Returns a boolean, True if any trace function was invoked.
364
365 """
366 return any(tracer.activity() for tracer in self.tracers)
367
330 def switch_context(self, new_context): 368 def switch_context(self, new_context):
331 """Who-Tests-What hack: switch to a new who-context.""" 369 """Who-Tests-What hack: switch to a new who-context."""
332 # Make a new data dict, or find the existing one, and switch all the 370 # Make a new data dict, or find the existing one, and switch all the
333 # tracers to use it. 371 # tracers to use it.
334 data = self.contexts.setdefault(new_context, {}) 372 data = self.contexts.setdefault(new_context, {})
336 tracer.data = data 374 tracer.data = data
337 375
338 def save_data(self, covdata): 376 def save_data(self, covdata):
339 """Save the collected data to a `CoverageData`. 377 """Save the collected data to a `CoverageData`.
340 378
341 Also resets the collector. 379 Returns True if there was data to save, False if not.
342
343 """ 380 """
381 if not self._activity():
382 return False
383
344 def abs_file_dict(d): 384 def abs_file_dict(d):
345 """Return a dict like d, but with keys modified by `abs_file`.""" 385 """Return a dict like d, but with keys modified by `abs_file`."""
346 return dict((abs_file(k), v) for k, v in iitems(d)) 386 # The call to litems() ensures that the GIL protects the dictionary
387 # iterator against concurrent modifications by tracers running
388 # in other threads. We try three times in case of concurrent
389 # access, hoping to get a clean copy.
390 runtime_err = None
391 for _ in range(3):
392 try:
393 items = litems(d)
394 except RuntimeError as ex:
395 runtime_err = ex
396 else:
397 break
398 else:
399 raise runtime_err # pylint: disable=raising-bad-type
400
401 return dict((abs_file(k), v) for k, v in items)
347 402
348 if self.branch: 403 if self.branch:
349 covdata.add_arcs(abs_file_dict(self.data)) 404 covdata.add_arcs(abs_file_dict(self.data))
350 else: 405 else:
351 covdata.add_lines(abs_file_dict(self.data)) 406 covdata.add_lines(abs_file_dict(self.data))
356 import pprint 411 import pprint
357 out_file = "coverage_wtw_{:06}.py".format(os.getpid()) 412 out_file = "coverage_wtw_{:06}.py".format(os.getpid())
358 with open(out_file, "w") as wtw_out: 413 with open(out_file, "w") as wtw_out:
359 pprint.pprint(self.contexts, wtw_out) 414 pprint.pprint(self.contexts, wtw_out)
360 415
361 self.reset() 416 self._clear_data()
417 return True

eric ide

mercurial