Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(343)

Side by Side Diff: third_party/coverage-3.6/coverage/collector.py

Issue 14988009: First cut of testing infrastructure for recipes. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: nitfixen Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 """Raw data collector for Coverage."""
2
3 import os, sys, threading
4
5 try:
6 # Use the C extension code when we can, for speed.
7 from coverage.tracer import CTracer # pylint: disable=F0401,E0611
8 except ImportError:
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.
17 sys.stderr.write(
18 "*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n"
19 )
20 sys.exit(1)
21 CTracer = None
22
23
24 class PyTracer(object):
25 """Python implementation of the raw data tracer."""
26
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
33 # frame.
34 #
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.
38 #
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.
42
43 def __init__(self):
44 self.data = None
45 self.should_trace = None
46 self.should_trace_cache = None
47 self.warn = None
48 self.cur_file_data = None
49 self.last_line = 0
50 self.data_stack = []
51 self.last_exc_back = None
52 self.last_exc_firstlineno = 0
53 self.arcs = False
54
55 def _trace(self, frame, event, arg_unused):
56 """The trace function passed to sys.settrace."""
57
58 #print("trace event: %s %r @%d" % (
59 # event, frame.f_code.co_filename, frame.f_lineno),
60 # file=sys.stderr)
61
62 if self.last_exc_back:
63 if frame == self.last_exc_back:
64 # Someone forgot a return event.
65 if self.arcs and self.cur_file_data:
66 pair = (self.last_line, -self.last_exc_firstlineno)
67 self.cur_file_data[pair] = None
68 self.cur_file_data, self.last_line = self.data_stack.pop()
69 self.last_exc_back = None
70
71 if event == 'call':
72 # Entering a new function context. Decide if we should trace
73 # in this file.
74 self.data_stack.append((self.cur_file_data, self.last_line))
75 filename = frame.f_code.co_filename
76 tracename = self.should_trace_cache.get(filename)
77 if tracename is None:
78 tracename = self.should_trace(filename, frame)
79 self.should_trace_cache[filename] = tracename
80 #print("called, stack is %d deep, tracename is %r" % (
81 # len(self.data_stack), tracename))
82 if tracename:
83 if tracename not in self.data:
84 self.data[tracename] = {}
85 self.cur_file_data = self.data[tracename]
86 else:
87 self.cur_file_data = None
88 # Set the last_line to -1 because the next arc will be entering a
89 # code block, indicated by (-1, n).
90 self.last_line = -1
91 elif event == 'line':
92 # Record an executed line.
93 if self.cur_file_data is not None:
94 if self.arcs:
95 #print("lin", self.last_line, frame.f_lineno)
96 self.cur_file_data[(self.last_line, frame.f_lineno)] = None
97 else:
98 #print("lin", frame.f_lineno)
99 self.cur_file_data[frame.f_lineno] = None
100 self.last_line = frame.f_lineno
101 elif event == 'return':
102 if self.arcs and self.cur_file_data:
103 first = frame.f_code.co_firstlineno
104 self.cur_file_data[(self.last_line, -first)] = None
105 # Leaving this function, pop the filename stack.
106 self.cur_file_data, self.last_line = self.data_stack.pop()
107 #print("returned, stack is %d deep" % (len(self.data_stack)))
108 elif event == 'exception':
109 #print("exc", self.last_line, frame.f_lineno)
110 self.last_exc_back = frame.f_back
111 self.last_exc_firstlineno = frame.f_code.co_firstlineno
112 return self._trace
113
114 def start(self):
115 """Start this Tracer.
116
117 Return a Python function suitable for use with sys.settrace().
118
119 """
120 sys.settrace(self._trace)
121 return self._trace
122
123 def stop(self):
124 """Stop this Tracer."""
125 if hasattr(sys, "gettrace") and self.warn:
126 if sys.gettrace() != self._trace:
127 msg = "Trace function changed, measurement is likely wrong: %r"
128 self.warn(msg % (sys.gettrace(),))
129 #--debug
130 #from coverage.misc import short_stack
131 #self.warn(msg % (sys.gettrace()))#, short_stack()))
132 sys.settrace(None)
133
134 def get_stats(self):
135 """Return a dictionary of statistics, or None."""
136 return None
137
138
139 class Collector(object):
140 """Collects trace data.
141
142 Creates a Tracer object for each thread, since they track stack
143 information. Each Tracer points to the same shared data, contributing
144 traced data points.
145
146 When the Collector is started, it creates a Tracer for the current thread,
147 and installs a function to create Tracers for each new thread started.
148 When the Collector is stopped, all active Tracers are stopped.
149
150 Threads started while the Collector is stopped will never have Tracers
151 associated with them.
152
153 """
154
155 # The stack of active Collectors. Collectors are added here when started,
156 # and popped when stopped. Collectors on the stack are paused when not
157 # the top, and resumed when they become the top again.
158 _collectors = []
159
160 def __init__(self, should_trace, timid, branch, warn):
161 """Create a collector.
162
163 `should_trace` is a function, taking a filename, and returning a
164 canonicalized filename, or False depending on whether the file should
165 be traced or not.
166
167 If `timid` is true, then a slower simpler trace function will be
168 used. This is important for some environments where manipulation of
169 tracing functions make the faster more sophisticated trace function not
170 operate properly.
171
172 If `branch` is true, then branches will be measured. This involves
173 collecting data on which statements followed each other (arcs). Use
174 `get_arc_data` to get the arc data.
175
176 `warn` is a warning function, taking a single string message argument,
177 to be used if a warning needs to be issued.
178
179 """
180 self.should_trace = should_trace
181 self.warn = warn
182 self.branch = branch
183 self.reset()
184
185 if timid:
186 # Being timid: use the simple Python trace function.
187 self._trace_class = PyTracer
188 else:
189 # Being fast: use the C Tracer if it is available, else the Python
190 # trace function.
191 self._trace_class = CTracer or PyTracer
192
193 def __repr__(self):
194 return "<Collector at 0x%x>" % id(self)
195
196 def tracer_name(self):
197 """Return the class name of the tracer we're using."""
198 return self._trace_class.__name__
199
200 def reset(self):
201 """Clear collected data, and prepare to collect more."""
202 # A dictionary mapping filenames to dicts with linenumber keys,
203 # or mapping filenames to dicts with linenumber pairs as keys.
204 self.data = {}
205
206 # A cache of the results from should_trace, the decision about whether
207 # to trace execution in a file. A dict of filename to (filename or
208 # False).
209 self.should_trace_cache = {}
210
211 # Our active Tracers.
212 self.tracers = []
213
214 def _start_tracer(self):
215 """Start a new Tracer object, and store it in self.tracers."""
216 tracer = self._trace_class()
217 tracer.data = self.data
218 tracer.arcs = self.branch
219 tracer.should_trace = self.should_trace
220 tracer.should_trace_cache = self.should_trace_cache
221 tracer.warn = self.warn
222 fn = tracer.start()
223 self.tracers.append(tracer)
224 return fn
225
226 # The trace function has to be set individually on each thread before
227 # execution begins. Ironically, the only support the threading module has
228 # for running code before the thread main is the tracing function. So we
229 # install this as a trace function, and the first time it's called, it does
230 # the real trace installation.
231
232 def _installation_trace(self, frame_unused, event_unused, arg_unused):
233 """Called on new threads, installs the real tracer."""
234 # Remove ourselves as the trace function
235 sys.settrace(None)
236 # Install the real tracer.
237 fn = self._start_tracer()
238 # Invoke the real trace function with the current event, to be sure
239 # not to lose an event.
240 if fn:
241 fn = fn(frame_unused, event_unused, arg_unused)
242 # Return the new trace function to continue tracing in this scope.
243 return fn
244
245 def start(self):
246 """Start collecting trace information."""
247 if self._collectors:
248 self._collectors[-1].pause()
249 self._collectors.append(self)
250 #print("Started: %r" % self._collectors, file=sys.stderr)
251
252 # Check to see whether we had a fullcoverage tracer installed.
253 traces0 = []
254 if hasattr(sys, "gettrace"):
255 fn0 = sys.gettrace()
256 if fn0:
257 tracer0 = getattr(fn0, '__self__', None)
258 if tracer0:
259 traces0 = getattr(tracer0, 'traces', [])
260
261 # Install the tracer on this thread.
262 fn = self._start_tracer()
263
264 for args in traces0:
265 (frame, event, arg), lineno = args
266 try:
267 fn(frame, event, arg, lineno=lineno)
268 except TypeError:
269 raise Exception(
270 "fullcoverage must be run with the C trace function."
271 )
272
273 # Install our installation tracer in threading, to jump start other
274 # threads.
275 threading.settrace(self._installation_trace)
276
277 def stop(self):
278 """Stop collecting trace information."""
279 #print >>sys.stderr, "Stopping: %r" % self._collectors
280 assert self._collectors
281 assert self._collectors[-1] is self
282
283 self.pause()
284 self.tracers = []
285
286 # Remove this Collector from the stack, and resume the one underneath
287 # (if any).
288 self._collectors.pop()
289 if self._collectors:
290 self._collectors[-1].resume()
291
292 def pause(self):
293 """Pause tracing, but be prepared to `resume`."""
294 for tracer in self.tracers:
295 tracer.stop()
296 stats = tracer.get_stats()
297 if stats:
298 print("\nCoverage.py tracer stats:")
299 for k in sorted(stats.keys()):
300 print("%16s: %s" % (k, stats[k]))
301 threading.settrace(None)
302
303 def resume(self):
304 """Resume tracing after a `pause`."""
305 for tracer in self.tracers:
306 tracer.start()
307 threading.settrace(self._installation_trace)
308
309 def get_line_data(self):
310 """Return the line data collected.
311
312 Data is { filename: { lineno: None, ...}, ...}
313
314 """
315 if self.branch:
316 # If we were measuring branches, then we have to re-build the dict
317 # to show line data.
318 line_data = {}
319 for f, arcs in self.data.items():
320 line_data[f] = ldf = {}
321 for l1, _ in list(arcs.keys()):
322 if l1:
323 ldf[l1] = None
324 return line_data
325 else:
326 return self.data
327
328 def get_arc_data(self):
329 """Return the arc data collected.
330
331 Data is { filename: { (l1, l2): None, ...}, ...}
332
333 Note that no data is collected or returned if the Collector wasn't
334 created with `branch` true.
335
336 """
337 if self.branch:
338 return self.data
339 else:
340 return {}
OLDNEW
« no previous file with comments | « third_party/coverage-3.6/coverage/codeunit.py ('k') | third_party/coverage-3.6/coverage/config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698