2 |
2 |
3 import sys, threading |
3 import 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 coverage.tracer import Tracer |
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 |
10 Tracer = None |
11 class Tracer: |
11 |
12 """Python implementation of the raw data tracer.""" |
12 |
13 def __init__(self): |
13 class PyTracer(object): |
14 self.data = None |
14 """Python implementation of the raw data tracer.""" |
15 self.should_trace = None |
15 |
16 self.should_trace_cache = None |
16 # Because of poor implementations of trace-function-manipulating tools, |
17 self.cur_filename = None |
17 # the Python trace function must be kept very simple. In particular, there |
18 self.filename_stack = [] |
18 # must be only one function ever set as the trace function, both through |
|
19 # sys.settrace, and as the return value from the trace function. Put |
|
20 # another way, the trace function must always return itself. It cannot |
|
21 # swap in other functions, or return None to avoid tracing a particular |
|
22 # frame. |
|
23 # |
|
24 # The trace manipulator that introduced this restriction is DecoratorTools, |
|
25 # which sets a trace function, and then later restores the pre-existing one |
|
26 # by calling sys.settrace with a function it found in the current frame. |
|
27 # |
|
28 # Systems that use DecoratorTools (or similar trace manipulations) must use |
|
29 # PyTracer to get accurate results. The command-line --timid argument is |
|
30 # used to force the use of this tracer. |
|
31 |
|
32 def __init__(self): |
|
33 self.data = None |
|
34 self.should_trace = None |
|
35 self.should_trace_cache = None |
|
36 self.cur_file_data = None |
|
37 self.last_line = 0 |
|
38 self.data_stack = [] |
|
39 self.last_exc_back = None |
|
40 self.arcs = False |
|
41 |
|
42 def _trace(self, frame, event, arg_unused): |
|
43 """The trace function passed to sys.settrace.""" |
|
44 |
|
45 #print "trace event: %s %r @%d" % ( |
|
46 # event, frame.f_code.co_filename, frame.f_lineno) |
|
47 |
|
48 if self.last_exc_back: |
|
49 if frame == self.last_exc_back: |
|
50 # Someone forgot a return event. |
|
51 if self.arcs and self.cur_file_data: |
|
52 self.cur_file_data[(self.last_line, -1)] = None |
|
53 self.cur_file_data, self.last_line = self.data_stack.pop() |
19 self.last_exc_back = None |
54 self.last_exc_back = None |
20 |
55 |
21 def _global_trace(self, frame, event, arg_unused): |
56 if event == 'call': |
22 """The trace function passed to sys.settrace.""" |
57 # Entering a new function context. Decide if we should trace |
23 if event == 'call': |
58 # in this file. |
24 # Entering a new function context. Decide if we should trace |
59 self.data_stack.append((self.cur_file_data, self.last_line)) |
25 # in this file. |
60 filename = frame.f_code.co_filename |
26 filename = frame.f_code.co_filename |
61 tracename = self.should_trace(filename, frame) |
27 tracename = self.should_trace_cache.get(filename) |
62 if tracename: |
28 if tracename is None: |
63 if tracename not in self.data: |
29 tracename = self.should_trace(filename, frame) |
64 self.data[tracename] = {} |
30 self.should_trace_cache[filename] = tracename |
65 self.cur_file_data = self.data[tracename] |
31 if tracename: |
66 else: |
32 # We need to trace. Push the current filename on the stack |
67 self.cur_file_data = None |
33 # and record the new current filename. |
68 self.last_line = -1 |
34 self.filename_stack.append(self.cur_filename) |
69 elif event == 'line': |
35 self.cur_filename = tracename |
70 # Record an executed line. |
36 # Use _local_trace for tracing within this function. |
71 if self.cur_file_data is not None: |
37 return self._local_trace |
72 if self.arcs: |
|
73 #print "lin", self.last_line, frame.f_lineno |
|
74 self.cur_file_data[(self.last_line, frame.f_lineno)] = None |
38 else: |
75 else: |
39 # No tracing in this function. |
76 #print "lin", frame.f_lineno |
40 return None |
77 self.cur_file_data[frame.f_lineno] = None |
41 return self._global_trace |
78 self.last_line = frame.f_lineno |
42 |
79 elif event == 'return': |
43 def _local_trace(self, frame, event, arg_unused): |
80 if self.arcs and self.cur_file_data: |
44 """The trace function used within a function.""" |
81 self.cur_file_data[(self.last_line, -1)] = None |
45 if self.last_exc_back: |
82 # Leaving this function, pop the filename stack. |
46 if frame == self.last_exc_back: |
83 self.cur_file_data, self.last_line = self.data_stack.pop() |
47 # Someone forgot a return event. |
84 elif event == 'exception': |
48 self.cur_filename = self.filename_stack.pop() |
85 #print "exc", self.last_line, frame.f_lineno |
49 self.last_exc_back = None |
86 self.last_exc_back = frame.f_back |
50 |
87 return self._trace |
51 if event == 'line': |
88 |
52 # Record an executed line. |
89 def start(self): |
53 self.data[(self.cur_filename, frame.f_lineno)] = True |
90 """Start this Tracer.""" |
54 elif event == 'return': |
91 sys.settrace(self._trace) |
55 # Leaving this function, pop the filename stack. |
92 |
56 self.cur_filename = self.filename_stack.pop() |
93 def stop(self): |
57 elif event == 'exception': |
94 """Stop this Tracer.""" |
58 self.last_exc_back = frame.f_back |
95 sys.settrace(None) |
59 return self._local_trace |
96 |
60 |
97 def get_stats(self): |
61 def start(self): |
98 """Return a dictionary of statistics, or None.""" |
62 """Start this Tracer.""" |
99 return None |
63 sys.settrace(self._global_trace) |
100 |
64 |
101 |
65 def stop(self): |
102 class Collector(object): |
66 """Stop this Tracer.""" |
|
67 sys.settrace(None) |
|
68 |
|
69 |
|
70 class Collector: |
|
71 """Collects trace data. |
103 """Collects trace data. |
72 |
104 |
73 Creates a Tracer object for each thread, since they track stack information. |
105 Creates a Tracer object for each thread, since they track stack |
74 Each Tracer points to the same shared data, contributing traced data points. |
106 information. Each Tracer points to the same shared data, contributing |
75 |
107 traced data points. |
|
108 |
76 When the Collector is started, it creates a Tracer for the current thread, |
109 When the Collector is started, it creates a Tracer for the current thread, |
77 and installs a function to create Tracers for each new thread started. |
110 and installs a function to create Tracers for each new thread started. |
78 When the Collector is stopped, all active Tracers are stopped. |
111 When the Collector is stopped, all active Tracers are stopped. |
79 |
112 |
80 Threads started while the Collector is stopped will never have Tracers |
113 Threads started while the Collector is stopped will never have Tracers |
81 associated with them. |
114 associated with them. |
82 |
115 |
83 """ |
116 """ |
84 |
117 |
85 # The stack of active Collectors. Collectors are added here when started, |
118 # The stack of active Collectors. Collectors are added here when started, |
86 # and popped when stopped. Collectors on the stack are paused when not |
119 # and popped when stopped. Collectors on the stack are paused when not |
87 # the top, and resumed when they become the top again. |
120 # the top, and resumed when they become the top again. |
88 _collectors = [] |
121 _collectors = [] |
89 |
122 |
90 def __init__(self, should_trace): |
123 def __init__(self, should_trace, timid, branch): |
91 """Create a collector. |
124 """Create a collector. |
92 |
125 |
93 `should_trace` is a function, taking a filename, and returning a |
126 `should_trace` is a function, taking a filename, and returning a |
94 canonicalized filename, or False depending on whether the file should |
127 canonicalized filename, or False depending on whether the file should |
95 be traced or not. |
128 be traced or not. |
96 |
129 |
|
130 If `timid` is true, then a slower simpler trace function will be |
|
131 used. This is important for some environments where manipulation of |
|
132 tracing functions make the faster more sophisticated trace function not |
|
133 operate properly. |
|
134 |
|
135 If `branch` is true, then branches will be measured. This involves |
|
136 collecting data on which statements followed each other (arcs). Use |
|
137 `get_arc_data` to get the arc data. |
|
138 |
97 """ |
139 """ |
98 self.should_trace = should_trace |
140 self.should_trace = should_trace |
|
141 self.branch = branch |
99 self.reset() |
142 self.reset() |
|
143 |
|
144 if timid: |
|
145 # Being timid: use the simple Python trace function. |
|
146 self._trace_class = PyTracer |
|
147 else: |
|
148 # Being fast: use the C Tracer if it is available, else the Python |
|
149 # trace function. |
|
150 self._trace_class = Tracer or PyTracer |
|
151 |
|
152 def __repr__(self): |
|
153 return "<Collector at 0x%x>" % id(self) |
|
154 |
|
155 def tracer_name(self): |
|
156 """Return the class name of the tracer we're using.""" |
|
157 return self._trace_class.__name__ |
100 |
158 |
101 def reset(self): |
159 def reset(self): |
102 """Clear collected data, and prepare to collect more.""" |
160 """Clear collected data, and prepare to collect more.""" |
103 # A dictionary with an entry for (Python source file name, line number |
161 # A dictionary mapping filenames to dicts with linenumber keys, |
104 # in that file) if that line has been executed. |
162 # or mapping filenames to dicts with linenumber pairs as keys. |
105 self.data = {} |
163 self.data = {} |
106 |
164 |
107 # A cache of the results from should_trace, the decision about whether |
165 # A cache of the results from should_trace, the decision about whether |
108 # to trace execution in a file. A dict of filename to (filename or |
166 # to trace execution in a file. A dict of filename to (filename or |
109 # False). |
167 # False). |
110 self.should_trace_cache = {} |
168 self.should_trace_cache = {} |
111 |
169 |
112 # Our active Tracers. |
170 # Our active Tracers. |
113 self.tracers = [] |
171 self.tracers = [] |
114 |
172 |
115 def _start_tracer(self): |
173 def _start_tracer(self): |
116 """Start a new Tracer object, and store it in self.tracers.""" |
174 """Start a new Tracer object, and store it in self.tracers.""" |
117 tracer = Tracer() |
175 tracer = self._trace_class() |
118 tracer.data = self.data |
176 tracer.data = self.data |
|
177 tracer.arcs = self.branch |
119 tracer.should_trace = self.should_trace |
178 tracer.should_trace = self.should_trace |
120 tracer.should_trace_cache = self.should_trace_cache |
179 tracer.should_trace_cache = self.should_trace_cache |
121 tracer.start() |
180 tracer.start() |
122 self.tracers.append(tracer) |
181 self.tracers.append(tracer) |
123 |
182 |
139 def start(self): |
198 def start(self): |
140 """Start collecting trace information.""" |
199 """Start collecting trace information.""" |
141 if self._collectors: |
200 if self._collectors: |
142 self._collectors[-1].pause() |
201 self._collectors[-1].pause() |
143 self._collectors.append(self) |
202 self._collectors.append(self) |
|
203 #print >>sys.stderr, "Started: %r" % self._collectors |
144 # Install the tracer on this thread. |
204 # Install the tracer on this thread. |
145 self._start_tracer() |
205 self._start_tracer() |
146 # Install our installation tracer in threading, to jump start other |
206 # Install our installation tracer in threading, to jump start other |
147 # threads. |
207 # threads. |
148 threading.settrace(self._installation_trace) |
208 threading.settrace(self._installation_trace) |
149 |
209 |
150 def stop(self): |
210 def stop(self): |
151 """Stop collecting trace information.""" |
211 """Stop collecting trace information.""" |
|
212 #print >>sys.stderr, "Stopping: %r" % self._collectors |
152 assert self._collectors |
213 assert self._collectors |
153 assert self._collectors[-1] is self |
214 assert self._collectors[-1] is self |
154 |
215 |
155 for tracer in self.tracers: |
216 self.pause() |
156 tracer.stop() |
|
157 self.tracers = [] |
217 self.tracers = [] |
158 threading.settrace(None) |
218 |
159 |
|
160 # Remove this Collector from the stack, and resume the one underneath |
219 # Remove this Collector from the stack, and resume the one underneath |
161 # (if any). |
220 # (if any). |
162 self._collectors.pop() |
221 self._collectors.pop() |
163 if self._collectors: |
222 if self._collectors: |
164 self._collectors[-1].resume() |
223 self._collectors[-1].resume() |
165 |
224 |
166 def pause(self): |
225 def pause(self): |
167 """Pause tracing, but be prepared to `resume`.""" |
226 """Pause tracing, but be prepared to `resume`.""" |
168 for tracer in self.tracers: |
227 for tracer in self.tracers: |
169 tracer.stop() |
228 tracer.stop() |
|
229 stats = tracer.get_stats() |
|
230 if stats: |
|
231 print("\nCoverage.py tracer stats:") |
|
232 for k in sorted(stats.keys()): |
|
233 print("%16s: %s" % (k, stats[k])) |
170 threading.settrace(None) |
234 threading.settrace(None) |
171 |
235 |
172 def resume(self): |
236 def resume(self): |
173 """Resume tracing after a `pause`.""" |
237 """Resume tracing after a `pause`.""" |
174 for tracer in self.tracers: |
238 for tracer in self.tracers: |
175 tracer.start() |
239 tracer.start() |
176 threading.settrace(self._installation_trace) |
240 threading.settrace(self._installation_trace) |
177 |
241 |
178 def data_points(self): |
242 def get_line_data(self): |
179 """Return the (filename, lineno) pairs collected.""" |
243 """Return the line data collected. |
180 return self.data.keys() |
244 |
|
245 Data is { filename: { lineno: None, ...}, ...} |
|
246 |
|
247 """ |
|
248 if self.branch: |
|
249 # If we were measuring branches, then we have to re-build the dict |
|
250 # to show line data. |
|
251 line_data = {} |
|
252 for f, arcs in self.data.items(): |
|
253 line_data[f] = ldf = {} |
|
254 for l1, _ in arcs: |
|
255 if l1: |
|
256 ldf[l1] = None |
|
257 return line_data |
|
258 else: |
|
259 return self.data |
|
260 |
|
261 def get_arc_data(self): |
|
262 """Return the arc data collected. |
|
263 |
|
264 Data is { filename: { (l1, l2): None, ...}, ...} |
|
265 |
|
266 Note that no data is collected or returned if the Collector wasn't |
|
267 created with `branch` true. |
|
268 |
|
269 """ |
|
270 if self.branch: |
|
271 return self.data |
|
272 else: |
|
273 return {} |