1 """Raw data collector for Coverage."""
3 import os
, sys
, threading
6 # Use the C extension code when we can, for speed.
7 from coverage
.tracer
import CTracer
# pylint: disable=F0401,E0611
9 # Couldn't import the C extension, maybe it isn't built.
10 if os
.getenv('COVERAGE_TEST_TRACER') == 'c':
11 # During testing, we use the COVERAGE_TEST_TRACER env var to indicate
12 # that we've fiddled with the environment to test this fallback code.
13 # If we thought we had a C tracer, but couldn't import it, then exit
14 # quickly and clearly instead of dribbling confusing errors. I'm using
15 # sys.exit here instead of an exception because an exception here
16 # causes all sorts of other noise in unittest.
18 "*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n"
24 class PyTracer(object):
25 """Python implementation of the raw data tracer."""
27 # Because of poor implementations of trace-function-manipulating tools,
28 # the Python trace function must be kept very simple. In particular, there
29 # must be only one function ever set as the trace function, both through
30 # sys.settrace, and as the return value from the trace function. Put
31 # another way, the trace function must always return itself. It cannot
32 # swap in other functions, or return None to avoid tracing a particular
35 # The trace manipulator that introduced this restriction is DecoratorTools,
36 # which sets a trace function, and then later restores the pre-existing one
37 # by calling sys.settrace with a function it found in the current frame.
39 # Systems that use DecoratorTools (or similar trace manipulations) must use
40 # PyTracer to get accurate results. The command-line --timid argument is
41 # used to force the use of this tracer.
45 self
.should_trace
= None
46 self
.should_trace_cache
= None
48 self
.cur_file_data
= None
51 self
.last_exc_back
= None
52 self
.last_exc_firstlineno
= 0
57 def _trace(self
, frame
, event
, arg_unused
):
58 """The trace function passed to sys.settrace."""
64 sys
.stderr
.write("trace event: %s %r @%d\n" % (
65 event
, frame
.f_code
.co_filename
, frame
.f_lineno
68 if self
.last_exc_back
:
69 if frame
== self
.last_exc_back
:
70 # Someone forgot a return event.
71 if self
.arcs
and self
.cur_file_data
:
72 pair
= (self
.last_line
, -self
.last_exc_firstlineno
)
73 self
.cur_file_data
[pair
] = None
74 self
.cur_file_data
, self
.last_line
= self
.data_stack
.pop()
75 self
.last_exc_back
= None
78 # Entering a new function context. Decide if we should trace
80 self
.data_stack
.append((self
.cur_file_data
, self
.last_line
))
81 filename
= frame
.f_code
.co_filename
82 if filename
not in self
.should_trace_cache
:
83 tracename
= self
.should_trace(filename
, frame
)
84 self
.should_trace_cache
[filename
] = tracename
86 tracename
= self
.should_trace_cache
[filename
]
87 #print("called, stack is %d deep, tracename is %r" % (
88 # len(self.data_stack), tracename))
90 if tracename
not in self
.data
:
91 self
.data
[tracename
] = {}
92 self
.cur_file_data
= self
.data
[tracename
]
94 self
.cur_file_data
= None
95 # Set the last_line to -1 because the next arc will be entering a
96 # code block, indicated by (-1, n).
99 # Record an executed line.
100 if self
.cur_file_data
is not None:
102 #print("lin", self.last_line, frame.f_lineno)
103 self
.cur_file_data
[(self
.last_line
, frame
.f_lineno
)] = None
105 #print("lin", frame.f_lineno)
106 self
.cur_file_data
[frame
.f_lineno
] = None
107 self
.last_line
= frame
.f_lineno
108 elif event
== 'return':
109 if self
.arcs
and self
.cur_file_data
:
110 first
= frame
.f_code
.co_firstlineno
111 self
.cur_file_data
[(self
.last_line
, -first
)] = None
112 # Leaving this function, pop the filename stack.
113 self
.cur_file_data
, self
.last_line
= self
.data_stack
.pop()
114 #print("returned, stack is %d deep" % (len(self.data_stack)))
115 elif event
== 'exception':
116 #print("exc", self.last_line, frame.f_lineno)
117 self
.last_exc_back
= frame
.f_back
118 self
.last_exc_firstlineno
= frame
.f_code
.co_firstlineno
122 """Start this Tracer.
124 Return a Python function suitable for use with sys.settrace().
127 self
.thread
= threading
.currentThread()
128 sys
.settrace(self
._trace
)
132 """Stop this Tracer."""
134 if self
.thread
!= threading
.currentThread():
135 # Called on a different thread than started us: we can't unhook
136 # ourseves, but we've set the flag that we should stop, so we won't
137 # do any more tracing.
140 if hasattr(sys
, "gettrace") and self
.warn
:
141 if sys
.gettrace() != self
._trace
:
142 msg
= "Trace function changed, measurement is likely wrong: %r"
143 self
.warn(msg
% (sys
.gettrace(),))
144 #print("Stopping tracer on %s" % threading.current_thread().ident)
148 """Return a dictionary of statistics, or None."""
152 class Collector(object):
153 """Collects trace data.
155 Creates a Tracer object for each thread, since they track stack
156 information. Each Tracer points to the same shared data, contributing
159 When the Collector is started, it creates a Tracer for the current thread,
160 and installs a function to create Tracers for each new thread started.
161 When the Collector is stopped, all active Tracers are stopped.
163 Threads started while the Collector is stopped will never have Tracers
164 associated with them.
168 # The stack of active Collectors. Collectors are added here when started,
169 # and popped when stopped. Collectors on the stack are paused when not
170 # the top, and resumed when they become the top again.
173 def __init__(self
, should_trace
, timid
, branch
, warn
):
174 """Create a collector.
176 `should_trace` is a function, taking a filename, and returning a
177 canonicalized filename, or None depending on whether the file should
180 If `timid` is true, then a slower simpler trace function will be
181 used. This is important for some environments where manipulation of
182 tracing functions make the faster more sophisticated trace function not
185 If `branch` is true, then branches will be measured. This involves
186 collecting data on which statements followed each other (arcs). Use
187 `get_arc_data` to get the arc data.
189 `warn` is a warning function, taking a single string message argument,
190 to be used if a warning needs to be issued.
193 self
.should_trace
= should_trace
199 # Being timid: use the simple Python trace function.
200 self
._trace
_class
= PyTracer
202 # Being fast: use the C Tracer if it is available, else the Python
204 self
._trace
_class
= CTracer
or PyTracer
207 return "<Collector at 0x%x>" % id(self
)
209 def tracer_name(self
):
210 """Return the class name of the tracer we're using."""
211 return self
._trace
_class
.__name
__
214 """Clear collected data, and prepare to collect more."""
215 # A dictionary mapping filenames to dicts with linenumber keys,
216 # or mapping filenames to dicts with linenumber pairs as keys.
219 # A cache of the results from should_trace, the decision about whether
220 # to trace execution in a file. A dict of filename to (filename or
222 self
.should_trace_cache
= {}
224 # Our active Tracers.
227 def _start_tracer(self
):
228 """Start a new Tracer object, and store it in self.tracers."""
229 tracer
= self
._trace
_class
()
230 tracer
.data
= self
.data
231 tracer
.arcs
= self
.branch
232 tracer
.should_trace
= self
.should_trace
233 tracer
.should_trace_cache
= self
.should_trace_cache
234 tracer
.warn
= self
.warn
236 self
.tracers
.append(tracer
)
239 # The trace function has to be set individually on each thread before
240 # execution begins. Ironically, the only support the threading module has
241 # for running code before the thread main is the tracing function. So we
242 # install this as a trace function, and the first time it's called, it does
243 # the real trace installation.
245 def _installation_trace(self
, frame_unused
, event_unused
, arg_unused
):
246 """Called on new threads, installs the real tracer."""
247 # Remove ourselves as the trace function
249 # Install the real tracer.
250 fn
= self
._start
_tracer
()
251 # Invoke the real trace function with the current event, to be sure
252 # not to lose an event.
254 fn
= fn(frame_unused
, event_unused
, arg_unused
)
255 # Return the new trace function to continue tracing in this scope.
259 """Start collecting trace information."""
261 self
._collectors
[-1].pause()
262 self
._collectors
.append(self
)
263 #print("Started: %r" % self._collectors, file=sys.stderr)
265 # Check to see whether we had a fullcoverage tracer installed.
267 if hasattr(sys
, "gettrace"):
270 tracer0
= getattr(fn0
, '__self__', None)
272 traces0
= getattr(tracer0
, 'traces', [])
274 # Install the tracer on this thread.
275 fn
= self
._start
_tracer
()
278 (frame
, event
, arg
), lineno
= args
280 fn(frame
, event
, arg
, lineno
=lineno
)
283 "fullcoverage must be run with the C trace function."
286 # Install our installation tracer in threading, to jump start other
288 threading
.settrace(self
._installation
_trace
)
291 """Stop collecting trace information."""
292 #print >>sys.stderr, "Stopping: %r" % self._collectors
293 assert self
._collectors
294 assert self
._collectors
[-1] is self
299 # Remove this Collector from the stack, and resume the one underneath
301 self
._collectors
.pop()
303 self
._collectors
[-1].resume()
306 """Pause tracing, but be prepared to `resume`."""
307 for tracer
in self
.tracers
:
309 stats
= tracer
.get_stats()
311 print("\nCoverage.py tracer stats:")
312 for k
in sorted(stats
.keys()):
313 print("%16s: %s" % (k
, stats
[k
]))
314 threading
.settrace(None)
317 """Resume tracing after a `pause`."""
318 for tracer
in self
.tracers
:
320 threading
.settrace(self
._installation
_trace
)
322 def get_line_data(self
):
323 """Return the line data collected.
325 Data is { filename: { lineno: None, ...}, ...}
329 # If we were measuring branches, then we have to re-build the dict
332 for f
, arcs
in self
.data
.items():
333 line_data
[f
] = ldf
= {}
334 for l1
, _
in list(arcs
.keys()):
341 def get_arc_data(self
):
342 """Return the arc data collected.
344 Data is { filename: { (l1, l2): None, ...}, ...}
346 Note that no data is collected or returned if the Collector wasn't
347 created with `branch` true.