1 """Raw data collector for Coverage.""" |
1 """Raw data collector for Coverage.""" |
2 |
2 |
3 import sys, threading |
3 import os, sys, threading |
4 |
4 |
5 try: |
5 try: |
6 # Use the C extension code when we can, for speed. |
6 # Use the C extension code when we can, for speed. |
7 from .tracer import Tracer |
7 from .tracer import CTracer # pylint: disable=F0401,E0611 |
8 except ImportError: |
8 except ImportError: |
9 # Couldn't import the C extension, maybe it isn't built. |
9 # Couldn't import the C extension, maybe it isn't built. |
10 Tracer = None |
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. |
|
17 sys.stderr.write( |
|
18 "*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n" |
|
19 ) |
|
20 sys.exit(1) |
|
21 CTracer = None |
11 |
22 |
12 |
23 |
13 class PyTracer(object): |
24 class PyTracer(object): |
14 """Python implementation of the raw data tracer.""" |
25 """Python implementation of the raw data tracer.""" |
15 |
26 |
31 |
42 |
32 def __init__(self): |
43 def __init__(self): |
33 self.data = None |
44 self.data = None |
34 self.should_trace = None |
45 self.should_trace = None |
35 self.should_trace_cache = None |
46 self.should_trace_cache = None |
|
47 self.warn = None |
36 self.cur_file_data = None |
48 self.cur_file_data = None |
37 self.last_line = 0 |
49 self.last_line = 0 |
38 self.data_stack = [] |
50 self.data_stack = [] |
39 self.last_exc_back = None |
51 self.last_exc_back = None |
|
52 self.last_exc_firstlineno = 0 |
40 self.arcs = False |
53 self.arcs = False |
|
54 self.thread = None |
|
55 self.stopped = False |
41 |
56 |
42 def _trace(self, frame, event, arg_unused): |
57 def _trace(self, frame, event, arg_unused): |
43 """The trace function passed to sys.settrace.""" |
58 """The trace function passed to sys.settrace.""" |
44 |
59 |
45 #print "trace event: %s %r @%d" % ( |
60 if self.stopped: |
46 # event, frame.f_code.co_filename, frame.f_lineno) |
61 return |
|
62 |
|
63 if 0: |
|
64 sys.stderr.write("trace event: %s %r @%d\n" % ( |
|
65 event, frame.f_code.co_filename, frame.f_lineno |
|
66 )) |
47 |
67 |
48 if self.last_exc_back: |
68 if self.last_exc_back: |
49 if frame == self.last_exc_back: |
69 if frame == self.last_exc_back: |
50 # Someone forgot a return event. |
70 # Someone forgot a return event. |
51 if self.arcs and self.cur_file_data: |
71 if self.arcs and self.cur_file_data: |
52 self.cur_file_data[(self.last_line, -1)] = None |
72 pair = (self.last_line, -self.last_exc_firstlineno) |
|
73 self.cur_file_data[pair] = None |
53 self.cur_file_data, self.last_line = self.data_stack.pop() |
74 self.cur_file_data, self.last_line = self.data_stack.pop() |
54 self.last_exc_back = None |
75 self.last_exc_back = None |
55 |
76 |
56 if event == 'call': |
77 if event == 'call': |
57 # Entering a new function context. Decide if we should trace |
78 # Entering a new function context. Decide if we should trace |
58 # in this file. |
79 # in this file. |
59 self.data_stack.append((self.cur_file_data, self.last_line)) |
80 self.data_stack.append((self.cur_file_data, self.last_line)) |
60 filename = frame.f_code.co_filename |
81 filename = frame.f_code.co_filename |
61 tracename = self.should_trace(filename, frame) |
82 if filename not in self.should_trace_cache: |
|
83 tracename = self.should_trace(filename, frame) |
|
84 self.should_trace_cache[filename] = tracename |
|
85 else: |
|
86 tracename = self.should_trace_cache[filename] |
|
87 #print("called, stack is %d deep, tracename is %r" % ( |
|
88 # len(self.data_stack), tracename)) |
62 if tracename: |
89 if tracename: |
63 if tracename not in self.data: |
90 if tracename not in self.data: |
64 self.data[tracename] = {} |
91 self.data[tracename] = {} |
65 self.cur_file_data = self.data[tracename] |
92 self.cur_file_data = self.data[tracename] |
66 else: |
93 else: |
67 self.cur_file_data = None |
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). |
68 self.last_line = -1 |
97 self.last_line = -1 |
69 elif event == 'line': |
98 elif event == 'line': |
70 # Record an executed line. |
99 # Record an executed line. |
71 if self.cur_file_data is not None: |
100 if self.cur_file_data is not None: |
72 if self.arcs: |
101 if self.arcs: |
73 #print "lin", self.last_line, frame.f_lineno |
102 #print("lin", self.last_line, frame.f_lineno) |
74 self.cur_file_data[(self.last_line, frame.f_lineno)] = None |
103 self.cur_file_data[(self.last_line, frame.f_lineno)] = None |
75 else: |
104 else: |
76 #print "lin", frame.f_lineno |
105 #print("lin", frame.f_lineno) |
77 self.cur_file_data[frame.f_lineno] = None |
106 self.cur_file_data[frame.f_lineno] = None |
78 self.last_line = frame.f_lineno |
107 self.last_line = frame.f_lineno |
79 elif event == 'return': |
108 elif event == 'return': |
80 if self.arcs and self.cur_file_data: |
109 if self.arcs and self.cur_file_data: |
81 self.cur_file_data[(self.last_line, -1)] = None |
110 first = frame.f_code.co_firstlineno |
|
111 self.cur_file_data[(self.last_line, -first)] = None |
82 # Leaving this function, pop the filename stack. |
112 # Leaving this function, pop the filename stack. |
83 self.cur_file_data, self.last_line = self.data_stack.pop() |
113 self.cur_file_data, self.last_line = self.data_stack.pop() |
|
114 #print("returned, stack is %d deep" % (len(self.data_stack))) |
84 elif event == 'exception': |
115 elif event == 'exception': |
85 #print "exc", self.last_line, frame.f_lineno |
116 #print("exc", self.last_line, frame.f_lineno) |
86 self.last_exc_back = frame.f_back |
117 self.last_exc_back = frame.f_back |
|
118 self.last_exc_firstlineno = frame.f_code.co_firstlineno |
87 return self._trace |
119 return self._trace |
88 |
120 |
89 def start(self): |
121 def start(self): |
90 """Start this Tracer.""" |
122 """Start this Tracer. |
|
123 |
|
124 Return a Python function suitable for use with sys.settrace(). |
|
125 |
|
126 """ |
|
127 self.thread = threading.currentThread() |
91 sys.settrace(self._trace) |
128 sys.settrace(self._trace) |
|
129 return self._trace |
92 |
130 |
93 def stop(self): |
131 def stop(self): |
94 """Stop this Tracer.""" |
132 """Stop this Tracer.""" |
|
133 self.stopped = True |
|
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. |
|
138 return |
|
139 |
|
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) |
95 sys.settrace(None) |
145 sys.settrace(None) |
96 |
146 |
97 def get_stats(self): |
147 def get_stats(self): |
98 """Return a dictionary of statistics, or None.""" |
148 """Return a dictionary of statistics, or None.""" |
99 return None |
149 return None |
118 # The stack of active Collectors. Collectors are added here when started, |
168 # The stack of active Collectors. Collectors are added here when started, |
119 # and popped when stopped. Collectors on the stack are paused when not |
169 # and popped when stopped. Collectors on the stack are paused when not |
120 # the top, and resumed when they become the top again. |
170 # the top, and resumed when they become the top again. |
121 _collectors = [] |
171 _collectors = [] |
122 |
172 |
123 def __init__(self, should_trace, timid, branch): |
173 def __init__(self, should_trace, timid, branch, warn): |
124 """Create a collector. |
174 """Create a collector. |
125 |
175 |
126 `should_trace` is a function, taking a filename, and returning a |
176 `should_trace` is a function, taking a filename, and returning a |
127 canonicalized filename, or False depending on whether the file should |
177 canonicalized filename, or None depending on whether the file should |
128 be traced or not. |
178 be traced or not. |
129 |
179 |
130 If `timid` is true, then a slower simpler trace function will be |
180 If `timid` is true, then a slower simpler trace function will be |
131 used. This is important for some environments where manipulation of |
181 used. This is important for some environments where manipulation of |
132 tracing functions make the faster more sophisticated trace function not |
182 tracing functions make the faster more sophisticated trace function not |
189 def _installation_trace(self, frame_unused, event_unused, arg_unused): |
245 def _installation_trace(self, frame_unused, event_unused, arg_unused): |
190 """Called on new threads, installs the real tracer.""" |
246 """Called on new threads, installs the real tracer.""" |
191 # Remove ourselves as the trace function |
247 # Remove ourselves as the trace function |
192 sys.settrace(None) |
248 sys.settrace(None) |
193 # Install the real tracer. |
249 # Install the real tracer. |
194 self._start_tracer() |
250 fn = self._start_tracer() |
195 # Return None to reiterate that we shouldn't be used for tracing. |
251 # Invoke the real trace function with the current event, to be sure |
196 return None |
252 # not to lose an event. |
|
253 if fn: |
|
254 fn = fn(frame_unused, event_unused, arg_unused) |
|
255 # Return the new trace function to continue tracing in this scope. |
|
256 return fn |
197 |
257 |
198 def start(self): |
258 def start(self): |
199 """Start collecting trace information.""" |
259 """Start collecting trace information.""" |
200 if self._collectors: |
260 if self._collectors: |
201 self._collectors[-1].pause() |
261 self._collectors[-1].pause() |
202 self._collectors.append(self) |
262 self._collectors.append(self) |
203 #print >>sys.stderr, "Started: %r" % self._collectors |
263 #print("Started: %r" % self._collectors, file=sys.stderr) |
|
264 |
|
265 # Check to see whether we had a fullcoverage tracer installed. |
|
266 traces0 = [] |
|
267 if hasattr(sys, "gettrace"): |
|
268 fn0 = sys.gettrace() |
|
269 if fn0: |
|
270 tracer0 = getattr(fn0, '__self__', None) |
|
271 if tracer0: |
|
272 traces0 = getattr(tracer0, 'traces', []) |
|
273 |
204 # Install the tracer on this thread. |
274 # Install the tracer on this thread. |
205 self._start_tracer() |
275 fn = self._start_tracer() |
|
276 |
|
277 for args in traces0: |
|
278 (frame, event, arg), lineno = args |
|
279 try: |
|
280 fn(frame, event, arg, lineno=lineno) |
|
281 except TypeError: |
|
282 raise Exception( |
|
283 "fullcoverage must be run with the C trace function." |
|
284 ) |
|
285 |
206 # Install our installation tracer in threading, to jump start other |
286 # Install our installation tracer in threading, to jump start other |
207 # threads. |
287 # threads. |
208 threading.settrace(self._installation_trace) |
288 threading.settrace(self._installation_trace) |
209 |
289 |
210 def stop(self): |
290 def stop(self): |