summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt4
-rw-r--r--coverage/collector.py70
2 files changed, 56 insertions, 18 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index dbb9f4e8..24f2ab6f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -19,6 +19,10 @@ Version 3.next
- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser
invocations could be overlooked.
+- On Python 2.3, coverage.py could mis-measure code with exceptions being
+ raised. This is now fixed, but as a result, on Python 2.3, the Python trace
+ function is used, reversing speed gains acheived in coverage.py 3.0.
+
Version 3.0, 13 June 2009
-------------------------
diff --git a/coverage/collector.py b/coverage/collector.py
index f290b85b..d196062b 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -2,12 +2,28 @@
import sys, threading
-try:
- # Use the C extension code when we can, for speed.
- from coverage.tracer import Tracer
-except ImportError:
- # If we don't have the C tracer, use this Python one.
- class Tracer:
+# Before Python 2.4, the trace function could be called with "call", and then
+# leave the scope without calling "return", for example when an exception
+# caused the scope to end. We use the matching call and return traces to do
+# bookkeeping for speed, so we need to know up front if they match our
+# expectations.
+CALL_AND_RETURN_MATCH = (sys.hexversion >= 0x02040000)
+
+
+Tracer = None
+if CALL_AND_RETURN_MATCH:
+ # We have matching calls and returns, so we can use the C extension.
+ try:
+ # Use the C extension code when we can, for speed.
+ from coverage.tracer import Tracer
+ except ImportError:
+ # Couldn't import the C extension, maybe it isn't built.
+ pass
+
+
+if not Tracer:
+ # If for whatever reason we don't have a Tracer yet, use this Python one.
+ class Tracer: # pylint: disable-msg=E0102
"""Python implementation of the raw data tracer."""
def __init__(self):
self.data = None
@@ -37,25 +53,43 @@ except ImportError:
# 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 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()
- return self._local_trace
-
+
+ if CALL_AND_RETURN_MATCH:
+ def _local_trace(self, frame, event, arg_unused):
+ """The trace function used within a function."""
+ 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()
+ return self._local_trace
+ else:
+ def _local_trace(self, frame, event, arg_unused):
+ """The trace function used within a function."""
+ if event == 'line':
+ if not self.cur_filename:
+ # Kinda hacky: call _global_trace to do the work of
+ # setting the cur_filename.
+ if not self._global_trace(frame, 'call', None):
+ return None
+ # Record an executed line.
+ self.data[(self.cur_filename, frame.f_lineno)] = True
+ elif event == 'return' or event == 'exception':
+ # Leaving this function, discard the current filename so
+ # we'll re-compute it at the next line.
+ self.cur_filename = None
+ return self._local_trace
+
def start(self):
"""Start this Tracer."""
sys.settrace(self._global_trace)
-
+
def stop(self):
"""Stop this Tracer."""
sys.settrace(None)
+
class Collector:
"""Collects trace data.