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