summaryrefslogtreecommitdiff
path: root/coverage/collector.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/collector.py')
-rw-r--r--coverage/collector.py71
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