diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2009-08-09 18:12:23 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2009-08-09 18:12:23 -0400 |
commit | 5ab5521c24f923a82dc1600fedbeaa867d9bcda2 (patch) | |
tree | 08ed8407ca1c411dd1565492947edf5c5a960d47 /coverage/collector.py | |
parent | f0b4b0d49b5375955848fc54464caac07f37c195 (diff) | |
download | python-coveragepy-5ab5521c24f923a82dc1600fedbeaa867d9bcda2.tar.gz |
Fix a problem with DecoratorTools fiddling with the trace function and screwing us up. Now the Python trace function is simpler, with no variability of registered trace function. Fixes bugs #12 and #13.
Diffstat (limited to 'coverage/collector.py')
-rw-r--r-- | coverage/collector.py | 146 |
1 files changed, 81 insertions, 65 deletions
diff --git a/coverage/collector.py b/coverage/collector.py index 940f7c7..a121193 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -7,66 +7,72 @@ try: from coverage.tracer import Tracer except ImportError: # Couldn't import the C extension, maybe it isn't built. - - 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 = [] - self.last_exc_back = None - - def _global_trace(self, frame, event, arg_unused): - """The trace function passed to sys.settrace.""" - #print "global event: %s %r" % (event, frame.f_code.co_filename) - 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 - 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.""" - #print "local event: %s %r" % (event, frame.f_code.co_filename) - 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 + Tracer = None - def start(self): - """Start this Tracer.""" - sys.settrace(self._global_trace) +class PyTracer: + """Python implementation of the raw data tracer.""" - def stop(self): - """Stop this Tracer.""" - sys.settrace(None) + # 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_filename = None + self.filename_stack = [] + self.last_exc_back = None + + 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.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.""" + sys.settrace(self._trace) + + def stop(self): + """Stop this Tracer.""" + sys.settrace(None) class Collector: @@ -89,16 +95,28 @@ class Collector: # the top, and resumed when they become the top again. _collectors = [] - def __init__(self, should_trace): + def __init__(self, should_trace, timid=False): """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. + """ self.should_trace = should_trace 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 reset(self): """Clear collected data, and prepare to collect more.""" @@ -116,7 +134,7 @@ class Collector: 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.should_trace = self.should_trace tracer.should_trace_cache = self.should_trace_cache @@ -153,12 +171,10 @@ class Collector: """Stop collecting trace information.""" 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() |