summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/collector.py125
-rw-r--r--coverage/control.py3
-rw-r--r--coverage/data.py35
-rw-r--r--coverage/tracer.c22
4 files changed, 62 insertions, 123 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index 7c6c27c8..8cb72dcd 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -8,76 +8,9 @@ try:
except ImportError:
# Couldn't import the C extension, maybe it isn't built.
Tracer = None
-
-class PyTracer:
- """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
- # sys.settrace, and as the return value from the trace function. Put
- # another way, the trace function must always return itself. It cannot
- # swap in other functions, or return None to avoid tracing a particular
- # frame.
- #
- # The trace manipulator that introduced this restriction is DecoratorTools,
- # which sets a trace function, and then later restores the pre-existing one
- # by calling sys.settrace with a function it found in the current frame.
- #
- # Systems that use DecoratorTools (or similar trace manipulations) must use
- # PyTracer to get accurate results. The command-line --timid argument is
- # used to force the use of this tracer.
- def __init__(self):
- self.line_data = None
- self.should_trace = None
- self.should_trace_cache = None
- self.cur_filename = None
- self.filename_stack = []
- self.last_exc_back = None
- self.branch = 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.
- self.cur_filename = self.filename_stack.pop()
- self.last_exc_back = None
-
- if event == 'call':
- # Entering a new function context. Decide if we should trace
- # in this file.
- self.filename_stack.append(self.cur_filename)
- filename = frame.f_code.co_filename
- tracename = self.should_trace(filename, frame)
- self.cur_filename = tracename
- elif event == 'line':
- # Record an executed line.
- if self.cur_filename:
- self.line_data[(self.cur_filename, frame.f_lineno)] = True
- elif event == 'return':
- # Leaving this function, pop the filename stack.
- self.cur_filename = self.filename_stack.pop()
- elif event == 'exception':
- self.last_exc_back = frame.f_back
- return self._trace
-
- def start(self):
- """Start this Tracer."""
- assert not self.branch
- sys.settrace(self._trace)
-
- def stop(self):
- """Stop this Tracer."""
- sys.settrace(None)
-
-
-class PyBranchTracer:
+class PyTracer:
"""Python implementation of the raw data tracer."""
# Because of poor implementations of trace-function-manipulating tools,
@@ -97,15 +30,14 @@ class PyBranchTracer:
# used to force the use of this tracer.
def __init__(self):
- self.line_data = None
- self.arc_data = None
+ self.data = None
self.should_trace = None
self.should_trace_cache = None
self.cur_filename = None
self.last_line = 0
self.filename_stack = []
self.last_exc_back = None
- self.branch = False
+ self.arcs = False
def _trace(self, frame, event, arg_unused):
"""The trace function passed to sys.settrace."""
@@ -116,8 +48,8 @@ class PyBranchTracer:
if self.last_exc_back:
if frame == self.last_exc_back:
# Someone forgot a return event.
- if self.cur_filename:
- self.arc_data[(self.cur_filename, self.last_line, 0)] = True
+ if self.arcs and self.cur_filename:
+ self.data[(self.cur_filename, self.last_line, 0)] = True
self.cur_filename, self.last_line = self.filename_stack.pop()
self.last_exc_back = None
@@ -132,12 +64,14 @@ class PyBranchTracer:
elif event == 'line':
# Record an executed line.
if self.cur_filename:
- self.line_data[(self.cur_filename, frame.f_lineno)] = True
- self.arc_data[(self.cur_filename, self.last_line, frame.f_lineno)] = True
+ if self.arcs:
+ self.data[(self.cur_filename, self.last_line, frame.f_lineno)] = True
+ else:
+ self.data[(self.cur_filename, frame.f_lineno)] = True
self.last_line = frame.f_lineno
elif event == 'return':
- if self.cur_filename:
- self.arc_data[(self.cur_filename, self.last_line, 0)] = True
+ if self.arcs and self.cur_filename:
+ self.data[(self.cur_filename, self.last_line, 0)] = True
# Leaving this function, pop the filename stack.
self.cur_filename, self.last_line = self.filename_stack.pop()
elif event == 'exception':
@@ -146,7 +80,6 @@ class PyBranchTracer:
def start(self):
"""Start this Tracer."""
- assert self.branch
sys.settrace(self._trace)
def stop(self):
@@ -174,7 +107,7 @@ class Collector:
# the top, and resumed when they become the top again.
_collectors = []
- def __init__(self, should_trace, timid=False, branch=False):
+ def __init__(self, should_trace, timid, branch):
"""Create a collector.
`should_trace` is a function, taking a filename, and returning a
@@ -192,8 +125,8 @@ class Collector:
self.should_trace = should_trace
self.branch = branch
self.reset()
- if branch:
- self._trace_class = PyBranchTracer
+ if branch: # For now use PyTracer for branch, so we can wait to update the C code.
+ self._trace_class = PyTracer
elif timid:
# Being timid: use the simple Python trace function.
self._trace_class = PyTracer
@@ -209,11 +142,8 @@ class Collector:
def reset(self):
"""Clear collected data, and prepare to collect more."""
# A dictionary with an entry for (Python source file name, line number
- # in that file) if that line has been executed.
- self.line_data = {}
-
- # TODO
- self.arc_data = {}
+ # in that file) if that line has been executed. TODO
+ 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
@@ -226,11 +156,10 @@ class Collector:
def _start_tracer(self):
"""Start a new Tracer object, and store it in self.tracers."""
tracer = self._trace_class()
- tracer.line_data = self.line_data
- tracer.arc_data = self.arc_data
+ tracer.data = self.data
tracer.should_trace = self.should_trace
tracer.should_trace_cache = self.should_trace_cache
- tracer.branch = self.branch
+ tracer.arcs = self.branch
tracer.start()
self.tracers.append(tracer)
@@ -286,10 +215,16 @@ class Collector:
tracer.start()
threading.settrace(self._installation_trace)
- def get_data(self, kind):
+ def get_line_data(self):
"""Return the (filename, lineno) pairs collected."""
- if self.arc_data:
- import pprint
- pprint.pprint(self.arc_data)
- if kind == 'line':
- return self.line_data.keys()
+ if self.branch:
+ return [(f,l) for f,l,_ in self.data.keys() if l]
+ else:
+ return self.data.keys()
+
+ def get_arc_data(self):
+ """Return the (filename, (from_line, to_line)) arc data collected."""
+ if self.branch:
+ return self.data.keys()
+ else:
+ return []
diff --git a/coverage/control.py b/coverage/control.py
index aa1727fe..9b62c57d 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -215,7 +215,8 @@ class coverage:
def _harvest_data(self):
"""Get the collected data by filename and reset the collector."""
- self.data.add_line_data(self.collector.get_data('line'))
+ self.data.add_line_data(self.collector.get_line_data())
+ self.data.add_arc_data(self.collector.get_arc_data())
self.collector.reset()
# Backward compatibility with version 1.
diff --git a/coverage/data.py b/coverage/data.py
index 54979658..28925f54 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -16,6 +16,8 @@ class CoverageData:
executed:
{ 'file1': [17,23,45], 'file2': [1,2,3], ... }
+ * arcs: TODO
+
"""
# Name of the data file (unless environment variable is set).
@@ -59,16 +61,18 @@ class CoverageData:
#
self.lines = {}
+ self.arcs = {} # TODO
+
def usefile(self, use_file=True):
"""Set whether or not to use a disk file for data."""
self.use_file = use_file
def read(self):
"""Read coverage data from the coverage data file (if it exists)."""
- data = {}
if self.use_file:
- data = self._read_file(self.filename)
- self.lines = data
+ self.lines, self.arcs = self._read_file(self.filename)
+ else:
+ self.lines, self.arcs = {}, {}
def write(self):
"""Write the collected coverage data to a file."""
@@ -81,11 +85,12 @@ class CoverageData:
if self.filename and os.path.exists(self.filename):
os.remove(self.filename)
self.lines = {}
+ self.arcs = {}
def line_data(self):
"""Return the map from filenames to lists of line numbers executed."""
return dict(
- [(f, sorted(linemap.keys())) for f, linemap in self.lines.items()]
+ [(f, sorted(lmap.keys())) for f, lmap in self.lines.items()]
)
def write_file(self, filename):
@@ -108,10 +113,12 @@ class CoverageData:
def read_file(self, filename):
"""Read the coverage data from `filename`."""
- self.lines = self._read_file(filename)
+ self.lines, self.arcs = self._read_file(filename)
def _read_file(self, filename):
"""Return the stored coverage data from the given file."""
+ lines = {}
+ arcs = {}
try:
fdata = open(filename, 'rb')
try:
@@ -121,14 +128,12 @@ class CoverageData:
if isinstance(data, dict):
# Unpack the 'lines' item.
lines = dict([
- (f, dict([(l, True) for l in linenos]))
- for f,linenos in data['lines'].items()
+ (f, dict.fromkeys(linenos, True))
+ for f, linenos in data['lines'].items()
])
- return lines
- else:
- return {}
except Exception:
- return {}
+ pass
+ return lines, arcs
def combine_parallel_data(self):
""" Treat self.filename as a file prefix, and combine the data from all
@@ -138,8 +143,8 @@ class CoverageData:
for f in os.listdir(data_dir or '.'):
if f.startswith(local):
full_path = os.path.join(data_dir, f)
- new_data = self._read_file(full_path)
- for filename, file_data in new_data.items():
+ new_lines, new_arcs = self._read_file(full_path)
+ for filename, file_data in new_lines.items():
self.lines.setdefault(filename, {}).update(file_data)
def add_line_data(self, data_points):
@@ -151,6 +156,10 @@ class CoverageData:
for filename, lineno in data_points:
self.lines.setdefault(filename, {})[lineno] = True
+ def add_arc_data(self, arc_data):
+ for filename, arc in arc_data:
+ self.arcs.setdefault(filename, {})[arc_data] = True
+
def executed_files(self):
"""A list of all files that had been measured as executed."""
return list(self.lines.keys())
diff --git a/coverage/tracer.c b/coverage/tracer.c
index 98f10d1b..f52d66db 100644
--- a/coverage/tracer.c
+++ b/coverage/tracer.c
@@ -39,10 +39,9 @@
typedef struct {
PyObject_HEAD
PyObject * should_trace;
- PyObject * line_data;
- PyObject * arc_data;
+ PyObject * data;
PyObject * should_trace_cache;
- PyObject * branch;
+ PyObject * arcs;
int started;
/* The index of the last-used entry in tracenames. */
int depth;
@@ -60,8 +59,7 @@ static int
Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
{
self->should_trace = NULL;
- self->line_data = NULL;
- self->arc_data = NULL;
+ self->data = NULL;
self->should_trace_cache = NULL;
self->started = 0;
self->depth = -1;
@@ -82,8 +80,7 @@ Tracer_dealloc(Tracer *self)
}
Py_XDECREF(self->should_trace);
- Py_XDECREF(self->line_data);
- Py_XDECREF(self->arc_data);
+ Py_XDECREF(self->data);
Py_XDECREF(self->should_trace_cache);
while (self->depth >= 0) {
@@ -248,7 +245,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
Py_INCREF(tracename);
PyTuple_SET_ITEM(t, 0, tracename);
PyTuple_SET_ITEM(t, 1, MyInt_FromLong(frame->f_lineno));
- PyDict_SetItem(self->line_data, t, Py_None);
+ PyDict_SetItem(self->data, t, Py_None);
Py_DECREF(t);
}
}
@@ -299,17 +296,14 @@ Tracer_members[] = {
{ "should_trace", T_OBJECT, offsetof(Tracer, should_trace), 0,
PyDoc_STR("Function indicating whether to trace a file.") },
- { "line_data", T_OBJECT, offsetof(Tracer, line_data), 0,
- PyDoc_STR("The raw dictionary of trace data.") },
-
- { "arc_data", T_OBJECT, offsetof(Tracer, arc_data), 0,
+ { "data", T_OBJECT, offsetof(Tracer, data), 0,
PyDoc_STR("The raw dictionary of trace data.") },
{ "should_trace_cache", T_OBJECT, offsetof(Tracer, should_trace_cache), 0,
PyDoc_STR("Dictionary caching should_trace results.") },
- { "branch", T_OBJECT, offsetof(Tracer, branch), 0,
- PyDoc_STR("Should we trace branches?") },
+ { "arcs", T_OBJECT, offsetof(Tracer, arcs), 0,
+ PyDoc_STR("Should we trace arcs, or just lines?") },
{ NULL }
};