DebugClients/Python/coverage/collector.py

changeset 0
de9c2efb9d02
child 31
744cd0b4b8cd
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 """Raw data collector for Coverage."""
2
3 import sys, threading
4
5 try:
6 # Use the C extension code when we can, for speed.
7 from tracer import Tracer
8 except ImportError:
9 # Couldn't import the C extension, maybe it isn't built.
10
11 class Tracer:
12 """Python implementation of the raw data tracer."""
13 def __init__(self):
14 self.data = None
15 self.should_trace = None
16 self.should_trace_cache = None
17 self.cur_filename = None
18 self.filename_stack = []
19 self.last_exc_back = None
20
21 def _global_trace(self, frame, event, arg_unused):
22 """The trace function passed to sys.settrace."""
23 if event == 'call':
24 # Entering a new function context. Decide if we should trace
25 # in this file.
26 filename = frame.f_code.co_filename
27 tracename = self.should_trace_cache.get(filename)
28 if tracename is None:
29 tracename = self.should_trace(filename, frame)
30 self.should_trace_cache[filename] = tracename
31 if tracename:
32 # We need to trace. Push the current filename on the stack
33 # and record the new current filename.
34 self.filename_stack.append(self.cur_filename)
35 self.cur_filename = tracename
36 # Use _local_trace for tracing within this function.
37 return self._local_trace
38 else:
39 # No tracing in this function.
40 return None
41 return self._global_trace
42
43 def _local_trace(self, frame, event, arg_unused):
44 """The trace function used within a function."""
45 if self.last_exc_back:
46 if frame == self.last_exc_back:
47 # Someone forgot a return event.
48 self.cur_filename = self.filename_stack.pop()
49 self.last_exc_back = None
50
51 if event == 'line':
52 # Record an executed line.
53 self.data[(self.cur_filename, frame.f_lineno)] = True
54 elif event == 'return':
55 # Leaving this function, pop the filename stack.
56 self.cur_filename = self.filename_stack.pop()
57 elif event == 'exception':
58 self.last_exc_back = frame.f_back
59 return self._local_trace
60
61 def start(self):
62 """Start this Tracer."""
63 sys.settrace(self._global_trace)
64
65 def stop(self):
66 """Stop this Tracer."""
67 sys.settrace(None)
68
69
70 class Collector:
71 """Collects trace data.
72
73 Creates a Tracer object for each thread, since they track stack information.
74 Each Tracer points to the same shared data, contributing traced data points.
75
76 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.
78 When the Collector is stopped, all active Tracers are stopped.
79
80 Threads started while the Collector is stopped will never have Tracers
81 associated with them.
82
83 """
84
85 # The stack of active Collectors. Collectors are added here when started,
86 # and popped when stopped. Collectors on the stack are paused when not
87 # the top, and resumed when they become the top again.
88 _collectors = []
89
90 def __init__(self, should_trace):
91 """Create a collector.
92
93 `should_trace` is a function, taking a filename, and returning a
94 canonicalized filename, or False depending on whether the file should
95 be traced or not.
96
97 """
98 self.should_trace = should_trace
99 self.reset()
100
101 def reset(self):
102 """Clear collected data, and prepare to collect more."""
103 # A dictionary with an entry for (Python source file name, line number
104 # in that file) if that line has been executed.
105 self.data = {}
106
107 # 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
109 # False).
110 self.should_trace_cache = {}
111
112 # Our active Tracers.
113 self.tracers = []
114
115 def _start_tracer(self):
116 """Start a new Tracer object, and store it in self.tracers."""
117 tracer = Tracer()
118 tracer.data = self.data
119 tracer.should_trace = self.should_trace
120 tracer.should_trace_cache = self.should_trace_cache
121 tracer.start()
122 self.tracers.append(tracer)
123
124 # The trace function has to be set individually on each thread before
125 # execution begins. Ironically, the only support the threading module has
126 # for running code before the thread main is the tracing function. So we
127 # install this as a trace function, and the first time it's called, it does
128 # the real trace installation.
129
130 def _installation_trace(self, frame_unused, event_unused, arg_unused):
131 """Called on new threads, installs the real tracer."""
132 # Remove ourselves as the trace function
133 sys.settrace(None)
134 # Install the real tracer.
135 self._start_tracer()
136 # Return None to reiterate that we shouldn't be used for tracing.
137 return None
138
139 def start(self):
140 """Start collecting trace information."""
141 if self._collectors:
142 self._collectors[-1].pause()
143 self._collectors.append(self)
144 # Install the tracer on this thread.
145 self._start_tracer()
146 # Install our installation tracer in threading, to jump start other
147 # threads.
148 threading.settrace(self._installation_trace)
149
150 def stop(self):
151 """Stop collecting trace information."""
152 assert self._collectors
153 assert self._collectors[-1] is self
154
155 for tracer in self.tracers:
156 tracer.stop()
157 self.tracers = []
158 threading.settrace(None)
159
160 # Remove this Collector from the stack, and resume the one underneath
161 # (if any).
162 self._collectors.pop()
163 if self._collectors:
164 self._collectors[-1].resume()
165
166 def pause(self):
167 """Pause tracing, but be prepared to `resume`."""
168 for tracer in self.tracers:
169 tracer.stop()
170 threading.settrace(None)
171
172 def resume(self):
173 """Resume tracing after a `pause`."""
174 for tracer in self.tracers:
175 tracer.start()
176 threading.settrace(self._installation_trace)
177
178 def data_points(self):
179 """Return the (filename, lineno) pairs collected."""
180 return self.data.keys()

eric ide

mercurial