diff options
Diffstat (limited to 'coverage/collector.py')
-rw-r--r-- | coverage/collector.py | 71 |
1 files changed, 46 insertions, 25 deletions
diff --git a/coverage/collector.py b/coverage/collector.py index 1a831c19..06ccda7e 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -12,7 +12,7 @@ except ImportError: class PyTracer(object): """Python implementation of the raw data tracer.""" - + # Because of poor implementations of trace-function-manipulating tools, # the Python trace function must be kept very simple. In particular, there # must be only one function ever set as the trace function, both through @@ -37,22 +37,24 @@ class PyTracer(object): self.last_line = 0 self.data_stack = [] self.last_exc_back = None + self.last_exc_firstlineno = 0 self.arcs = False def _trace(self, frame, event, arg_unused): """The trace function passed to sys.settrace.""" - + #print "trace event: %s %r @%d" % ( # event, frame.f_code.co_filename, frame.f_lineno) - + if self.last_exc_back: if frame == self.last_exc_back: # Someone forgot a return event. if self.arcs and self.cur_file_data: - self.cur_file_data[(self.last_line, -1)] = None + pair = (self.last_line, -self.last_exc_firstlineno) + self.cur_file_data[pair] = None self.cur_file_data, self.last_line = self.data_stack.pop() self.last_exc_back = None - + if event == 'call': # Entering a new function context. Decide if we should trace # in this file. @@ -65,6 +67,8 @@ class PyTracer(object): self.cur_file_data = self.data[tracename] else: self.cur_file_data = None + # Set the last_line to -1 because the next arc will be entering a + # code block, indicated by (-1, n). self.last_line = -1 elif event == 'line': # Record an executed line. @@ -78,14 +82,16 @@ class PyTracer(object): self.last_line = frame.f_lineno elif event == 'return': if self.arcs and self.cur_file_data: - self.cur_file_data[(self.last_line, -1)] = None + first = frame.f_code.co_firstlineno + self.cur_file_data[(self.last_line, -first)] = None # Leaving this function, pop the filename stack. self.cur_file_data, self.last_line = self.data_stack.pop() elif event == 'exception': #print "exc", self.last_line, frame.f_lineno self.last_exc_back = frame.f_back + self.last_exc_firstlineno = frame.f_code.co_firstlineno return self._trace - + def start(self): """Start this Tracer.""" sys.settrace(self._trace) @@ -94,22 +100,27 @@ class PyTracer(object): """Stop this Tracer.""" sys.settrace(None) + def get_stats(self): + """Return a dictionary of statistics, or None.""" + return None + class Collector(object): """Collects trace data. - Creates a Tracer object for each thread, since they track stack information. - Each Tracer points to the same shared data, contributing traced data points. - + Creates a Tracer object for each thread, since they track stack + information. Each Tracer points to the same shared data, contributing + traced data points. + When the Collector is started, it creates a Tracer for the current thread, and installs a function to create Tracers for each new thread started. When the Collector is stopped, all active Tracers are stopped. - + Threads started while the Collector is stopped will never have Tracers associated with them. - + """ - + # The stack of active Collectors. Collectors are added here when started, # and popped when stopped. Collectors on the stack are paused when not # the top, and resumed when they become the top again. @@ -117,20 +128,20 @@ class Collector(object): def __init__(self, should_trace, timid, branch): """Create a collector. - + `should_trace` is a function, taking a filename, and returning a canonicalized filename, or False depending on whether the file should be traced or not. - + If `timid` is true, then a slower simpler trace function will be used. This is important for some environments where manipulation of tracing functions make the faster more sophisticated trace function not operate properly. - + If `branch` is true, then branches will be measured. This involves collecting data on which statements followed each other (arcs). Use `get_arc_data` to get the arc data. - + """ self.should_trace = should_trace self.branch = branch @@ -144,6 +155,9 @@ class Collector(object): # trace function. self._trace_class = Tracer or PyTracer + def __repr__(self): + return "<Collector at 0x%x>" % id(self) + def tracer_name(self): """Return the class name of the tracer we're using.""" return self._trace_class.__name__ @@ -153,7 +167,7 @@ class Collector(object): # A dictionary mapping filenames to dicts with linenumber keys, # or mapping filenames to dicts with linenumber pairs as keys. self.data = {} - + # A cache of the results from should_trace, the decision about whether # to trace execution in a file. A dict of filename to (filename or # False). @@ -192,6 +206,7 @@ class Collector(object): if self._collectors: self._collectors[-1].pause() self._collectors.append(self) + #print >>sys.stderr, "Started: %r" % self._collectors # Install the tracer on this thread. self._start_tracer() # Install our installation tracer in threading, to jump start other @@ -200,12 +215,13 @@ class Collector(object): def stop(self): """Stop collecting trace information.""" + #print >>sys.stderr, "Stopping: %r" % self._collectors assert self._collectors assert self._collectors[-1] is self - self.pause() + self.pause() self.tracers = [] - + # Remove this Collector from the stack, and resume the one underneath # (if any). self._collectors.pop() @@ -216,8 +232,13 @@ class Collector(object): """Pause tracing, but be prepared to `resume`.""" for tracer in self.tracers: tracer.stop() + stats = tracer.get_stats() + if stats: + print("\nCoverage.py tracer stats:") + for k in sorted(stats.keys()): + print("%16s: %s" % (k, stats[k])) threading.settrace(None) - + def resume(self): """Resume tracing after a `pause`.""" for tracer in self.tracers: @@ -226,9 +247,9 @@ class Collector(object): def get_line_data(self): """Return the line data collected. - + Data is { filename: { lineno: None, ...}, ...} - + """ if self.branch: # If we were measuring branches, then we have to re-build the dict @@ -236,7 +257,7 @@ class Collector(object): line_data = {} for f, arcs in self.data.items(): line_data[f] = ldf = {} - for l1, _ in arcs: + for l1, _ in list(arcs.keys()): if l1: ldf[l1] = None return line_data @@ -245,7 +266,7 @@ class Collector(object): def get_arc_data(self): """Return the arc data collected. - + Data is { filename: { (l1, l2): None, ...}, ...} Note that no data is collected or returned if the Collector wasn't |