diff options
-rw-r--r-- | coverage/collector.py | 125 | ||||
-rw-r--r-- | coverage/control.py | 3 | ||||
-rw-r--r-- | coverage/data.py | 35 | ||||
-rw-r--r-- | coverage/tracer.c | 22 |
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 }
};
|