DebugClients/Python/coverage/collector.py

branch
Py2 comp.
changeset 3495
fac17a82b431
parent 790
2c0ea0163ef4
child 3499
f2d4b02c7e88
equal deleted inserted replaced
3485:f1cbc18f88b2 3495:fac17a82b431
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
134 184
135 If `branch` is true, then branches will be measured. This involves 185 If `branch` is true, then branches will be measured. This involves
136 collecting data on which statements followed each other (arcs). Use 186 collecting data on which statements followed each other (arcs). Use
137 `get_arc_data` to get the arc data. 187 `get_arc_data` to get the arc data.
138 188
189 `warn` is a warning function, taking a single string message argument,
190 to be used if a warning needs to be issued.
191
139 """ 192 """
140 self.should_trace = should_trace 193 self.should_trace = should_trace
194 self.warn = warn
141 self.branch = branch 195 self.branch = branch
142 self.reset() 196 self.reset()
143 197
144 if timid: 198 if timid:
145 # Being timid: use the simple Python trace function. 199 # Being timid: use the simple Python trace function.
146 self._trace_class = PyTracer 200 self._trace_class = PyTracer
147 else: 201 else:
148 # Being fast: use the C Tracer if it is available, else the Python 202 # Being fast: use the C Tracer if it is available, else the Python
149 # trace function. 203 # trace function.
150 self._trace_class = Tracer or PyTracer 204 self._trace_class = CTracer or PyTracer
151 205
152 def __repr__(self): 206 def __repr__(self):
153 return "<Collector at 0x%x>" % id(self) 207 return "<Collector at 0x%x>" % id(self)
154 208
155 def tracer_name(self): 209 def tracer_name(self):
162 # or mapping filenames to dicts with linenumber pairs as keys. 216 # or mapping filenames to dicts with linenumber pairs as keys.
163 self.data = {} 217 self.data = {}
164 218
165 # A cache of the results from should_trace, the decision about whether 219 # A cache of the results from should_trace, the decision about whether
166 # to trace execution in a file. A dict of filename to (filename or 220 # to trace execution in a file. A dict of filename to (filename or
167 # False). 221 # None).
168 self.should_trace_cache = {} 222 self.should_trace_cache = {}
169 223
170 # Our active Tracers. 224 # Our active Tracers.
171 self.tracers = [] 225 self.tracers = []
172 226
175 tracer = self._trace_class() 229 tracer = self._trace_class()
176 tracer.data = self.data 230 tracer.data = self.data
177 tracer.arcs = self.branch 231 tracer.arcs = self.branch
178 tracer.should_trace = self.should_trace 232 tracer.should_trace = self.should_trace
179 tracer.should_trace_cache = self.should_trace_cache 233 tracer.should_trace_cache = self.should_trace_cache
180 tracer.start() 234 tracer.warn = self.warn
235 fn = tracer.start()
181 self.tracers.append(tracer) 236 self.tracers.append(tracer)
237 return fn
182 238
183 # The trace function has to be set individually on each thread before 239 # The trace function has to be set individually on each thread before
184 # execution begins. Ironically, the only support the threading module has 240 # execution begins. Ironically, the only support the threading module has
185 # for running code before the thread main is the tracing function. So we 241 # for running code before the thread main is the tracing function. So we
186 # install this as a trace function, and the first time it's called, it does 242 # install this as a trace function, and the first time it's called, it does
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):
249 # If we were measuring branches, then we have to re-build the dict 329 # If we were measuring branches, then we have to re-build the dict
250 # to show line data. 330 # to show line data.
251 line_data = {} 331 line_data = {}
252 for f, arcs in self.data.items(): 332 for f, arcs in self.data.items():
253 line_data[f] = ldf = {} 333 line_data[f] = ldf = {}
254 for l1, _ in arcs: 334 for l1, _ in list(arcs.keys()):
255 if l1: 335 if l1:
256 ldf[l1] = None 336 ldf[l1] = None
257 return line_data 337 return line_data
258 else: 338 else:
259 return self.data 339 return self.data
269 """ 349 """
270 if self.branch: 350 if self.branch:
271 return self.data 351 return self.data
272 else: 352 else:
273 return {} 353 return {}
274
275 #
276 # eflag: FileType = Python2

eric ide

mercurial