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 |
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 |
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)) |