--- a/DebugClients/Python3/coverage/collector.py Thu Jan 07 12:31:11 2010 +0000 +++ b/DebugClients/Python3/coverage/collector.py Thu Jan 07 13:13:31 2010 +0000 @@ -4,106 +4,164 @@ try: # Use the C extension code when we can, for speed. - from tracer import Tracer + from .tracer import Tracer except ImportError: # Couldn't import the C extension, maybe it isn't built. + Tracer = None - class Tracer: - """Python implementation of the raw data tracer.""" - def __init__(self): - self.data = None - self.should_trace = None - self.should_trace_cache = None - self.cur_filename = None - self.filename_stack = [] + +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 + # 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.data = None + self.should_trace = None + self.should_trace_cache = None + self.cur_file_data = None + self.last_line = 0 + self.data_stack = [] + self.last_exc_back = None + 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 + self.cur_file_data, self.last_line = self.data_stack.pop() self.last_exc_back = None - def _global_trace(self, frame, event, arg_unused): - """The trace function passed to sys.settrace.""" - if event == 'call': - # Entering a new function context. Decide if we should trace - # in this file. - filename = frame.f_code.co_filename - tracename = self.should_trace_cache.get(filename) - if tracename is None: - tracename = self.should_trace(filename, frame) - self.should_trace_cache[filename] = tracename - if tracename: - # We need to trace. Push the current filename on the stack - # and record the new current filename. - self.filename_stack.append(self.cur_filename) - self.cur_filename = tracename - # Use _local_trace for tracing within this function. - return self._local_trace + if event == 'call': + # Entering a new function context. Decide if we should trace + # in this file. + self.data_stack.append((self.cur_file_data, self.last_line)) + filename = frame.f_code.co_filename + tracename = self.should_trace(filename, frame) + if tracename: + if tracename not in self.data: + self.data[tracename] = {} + self.cur_file_data = self.data[tracename] + else: + self.cur_file_data = None + self.last_line = -1 + elif event == 'line': + # Record an executed line. + if self.cur_file_data is not None: + if self.arcs: + #print "lin", self.last_line, frame.f_lineno + self.cur_file_data[(self.last_line, frame.f_lineno)] = None else: - # No tracing in this function. - return None - return self._global_trace - - def _local_trace(self, frame, event, arg_unused): - """The trace function used within a function.""" - 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 == 'line': - # Record an executed line. - self.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._local_trace - - def start(self): - """Start this Tracer.""" - sys.settrace(self._global_trace) - - def stop(self): - """Stop this Tracer.""" - sys.settrace(None) + #print "lin", frame.f_lineno + self.cur_file_data[frame.f_lineno] = None + 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 + # 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 + return self._trace + + def start(self): + """Start this Tracer.""" + sys.settrace(self._trace) + + def stop(self): + """Stop this Tracer.""" + sys.settrace(None) + + def get_stats(self): + """Return a dictionary of statistics, or None.""" + return None -class Collector: +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. _collectors = [] - def __init__(self, should_trace): + 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 self.reset() + if timid: + # Being timid: use the simple Python trace function. + self._trace_class = PyTracer + else: + # Being fast: use the C Tracer if it is available, else the Python + # 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__ + 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. + # 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). @@ -114,8 +172,9 @@ def _start_tracer(self): """Start a new Tracer object, and store it in self.tracers.""" - tracer = Tracer() + tracer = self._trace_class() tracer.data = self.data + tracer.arcs = self.branch tracer.should_trace = self.should_trace tracer.should_trace_cache = self.should_trace_cache tracer.start() @@ -141,6 +200,7 @@ 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 @@ -149,14 +209,13 @@ def stop(self): """Stop collecting trace information.""" + #print >>sys.stderr, "Stopping: %r" % self._collectors assert self._collectors assert self._collectors[-1] is self - - for tracer in self.tracers: - tracer.stop() + + self.pause() self.tracers = [] - threading.settrace(None) - + # Remove this Collector from the stack, and resume the one underneath # (if any). self._collectors.pop() @@ -167,14 +226,48 @@ """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: tracer.start() threading.settrace(self._installation_trace) - def data_points(self): - """Return the (filename, lineno) pairs collected.""" - return list(self.data.keys()) \ No newline at end of file + 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 + # to show line data. + line_data = {} + for f, arcs in self.data.items(): + line_data[f] = ldf = {} + for l1, _ in arcs: + if l1: + ldf[l1] = None + return line_data + else: + return self.data + + 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 + created with `branch` true. + + """ + if self.branch: + return self.data + else: + return {} \ No newline at end of file