DebugClients/Python3/coverage/collector.py

branch
Py2 comp.
changeset 3495
fac17a82b431
parent 29
391dc0bc4ae5
child 3499
f2d4b02c7e88
--- a/DebugClients/Python3/coverage/collector.py	Fri Apr 04 22:57:07 2014 +0200
+++ b/DebugClients/Python3/coverage/collector.py	Thu Apr 10 23:02:20 2014 +0200
@@ -1,13 +1,24 @@
 """Raw data collector for Coverage."""
 
-import sys, threading
+import os, sys, threading
 
 try:
     # Use the C extension code when we can, for speed.
-    from .tracer import Tracer
+    from .tracer import CTracer         # pylint: disable=F0401,E0611
 except ImportError:
     # Couldn't import the C extension, maybe it isn't built.
-    Tracer = None
+    if os.getenv('COVERAGE_TEST_TRACER') == 'c':
+        # During testing, we use the COVERAGE_TEST_TRACER env var to indicate
+        # that we've fiddled with the environment to test this fallback code.
+        # If we thought we had a C tracer, but couldn't import it, then exit
+        # quickly and clearly instead of dribbling confusing errors. I'm using
+        # sys.exit here instead of an exception because an exception here
+        # causes all sorts of other noise in unittest.
+        sys.stderr.write(
+            "*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n"
+            )
+        sys.exit(1)
+    CTracer = None
 
 
 class PyTracer(object):
@@ -33,23 +44,33 @@
         self.data = None
         self.should_trace = None
         self.should_trace_cache = None
+        self.warn = None
         self.cur_file_data = None
         self.last_line = 0
         self.data_stack = []
         self.last_exc_back = None
+        self.last_exc_firstlineno = 0
         self.arcs = False
+        self.thread = None
+        self.stopped = 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.stopped:
+            return
+
+        if 0:
+            sys.stderr.write("trace event: %s %r @%d\n" % (
+                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
+                    pair = (self.last_line, -self.last_exc_firstlineno)
+                    self.cur_file_data[pair] = None
                 self.cur_file_data, self.last_line = self.data_stack.pop()
             self.last_exc_back = None
 
@@ -58,40 +79,69 @@
             # 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 filename not in self.should_trace_cache:
+                tracename = self.should_trace(filename, frame)
+                self.should_trace_cache[filename] = tracename
+            else:
+                tracename = self.should_trace_cache[filename]
+            #print("called, stack is %d deep, tracename is %r" % (
+            #               len(self.data_stack), tracename))
             if tracename:
                 if tracename not in self.data:
                     self.data[tracename] = {}
                 self.cur_file_data = self.data[tracename]
             else:
                 self.cur_file_data = None
+            # Set the last_line to -1 because the next arc will be entering a
+            # code block, indicated by (-1, n).
             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
+                    #print("lin", self.last_line, frame.f_lineno)
                     self.cur_file_data[(self.last_line, frame.f_lineno)] = None
                 else:
-                    #print "lin", frame.f_lineno
+                    #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
+                first = frame.f_code.co_firstlineno
+                self.cur_file_data[(self.last_line, -first)] = None
             # Leaving this function, pop the filename stack.
             self.cur_file_data, self.last_line = self.data_stack.pop()
+            #print("returned, stack is %d deep" % (len(self.data_stack)))
         elif event == 'exception':
-            #print "exc", self.last_line, frame.f_lineno
+            #print("exc", self.last_line, frame.f_lineno)
             self.last_exc_back = frame.f_back
+            self.last_exc_firstlineno = frame.f_code.co_firstlineno
         return self._trace
 
     def start(self):
-        """Start this Tracer."""
+        """Start this Tracer.
+
+        Return a Python function suitable for use with sys.settrace().
+
+        """
+        self.thread = threading.currentThread()
         sys.settrace(self._trace)
+        return self._trace
 
     def stop(self):
         """Stop this Tracer."""
+        self.stopped = True
+        if self.thread != threading.currentThread():
+            # Called on a different thread than started us: we can't unhook
+            # ourseves, but we've set the flag that we should stop, so we won't
+            # do any more tracing.
+            return
+
+        if hasattr(sys, "gettrace") and self.warn:
+            if sys.gettrace() != self._trace:
+                msg = "Trace function changed, measurement is likely wrong: %r"
+                self.warn(msg % (sys.gettrace(),))
+        #print("Stopping tracer on %s" % threading.current_thread().ident)
         sys.settrace(None)
 
     def get_stats(self):
@@ -120,11 +170,11 @@
     # the top, and resumed when they become the top again.
     _collectors = []
 
-    def __init__(self, should_trace, timid, branch):
+    def __init__(self, should_trace, timid, branch, warn):
         """Create a collector.
 
         `should_trace` is a function, taking a filename, and returning a
-        canonicalized filename, or False depending on whether the file should
+        canonicalized filename, or None depending on whether the file should
         be traced or not.
 
         If `timid` is true, then a slower simpler trace function will be
@@ -136,8 +186,12 @@
         collecting data on which statements followed each other (arcs).  Use
         `get_arc_data` to get the arc data.
 
+        `warn` is a warning function, taking a single string message argument,
+        to be used if a warning needs to be issued.
+
         """
         self.should_trace = should_trace
+        self.warn = warn
         self.branch = branch
         self.reset()
 
@@ -147,7 +201,7 @@
         else:
             # Being fast: use the C Tracer if it is available, else the Python
             # trace function.
-            self._trace_class = Tracer or PyTracer
+            self._trace_class = CTracer or PyTracer
 
     def __repr__(self):
         return "<Collector at 0x%x>" % id(self)
@@ -164,7 +218,7 @@
 
         # 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).
+        # None).
         self.should_trace_cache = {}
 
         # Our active Tracers.
@@ -177,8 +231,10 @@
         tracer.arcs = self.branch
         tracer.should_trace = self.should_trace
         tracer.should_trace_cache = self.should_trace_cache
-        tracer.start()
+        tracer.warn = self.warn
+        fn = tracer.start()
         self.tracers.append(tracer)
+        return fn
 
     # The trace function has to be set individually on each thread before
     # execution begins.  Ironically, the only support the threading module has
@@ -191,18 +247,42 @@
         # Remove ourselves as the trace function
         sys.settrace(None)
         # Install the real tracer.
-        self._start_tracer()
-        # Return None to reiterate that we shouldn't be used for tracing.
-        return None
+        fn = self._start_tracer()
+        # Invoke the real trace function with the current event, to be sure
+        # not to lose an event.
+        if fn:
+            fn = fn(frame_unused, event_unused, arg_unused)
+        # Return the new trace function to continue tracing in this scope.
+        return fn
 
     def start(self):
         """Start collecting trace information."""
         if self._collectors:
             self._collectors[-1].pause()
         self._collectors.append(self)
-        #print >>sys.stderr, "Started: %r" % self._collectors
+        #print("Started: %r" % self._collectors, file=sys.stderr)
+
+        # Check to see whether we had a fullcoverage tracer installed.
+        traces0 = []
+        if hasattr(sys, "gettrace"):
+            fn0 = sys.gettrace()
+            if fn0:
+                tracer0 = getattr(fn0, '__self__', None)
+                if tracer0:
+                    traces0 = getattr(tracer0, 'traces', [])
+
         # Install the tracer on this thread.
-        self._start_tracer()
+        fn = self._start_tracer()
+
+        for args in traces0:
+            (frame, event, arg), lineno = args
+            try:
+                fn(frame, event, arg, lineno=lineno)
+            except TypeError:
+                raise Exception(
+                    "fullcoverage must be run with the C trace function."
+                )
+
         # Install our installation tracer in threading, to jump start other
         # threads.
         threading.settrace(self._installation_trace)
@@ -251,7 +331,7 @@
             line_data = {}
             for f, arcs in self.data.items():
                 line_data[f] = ldf = {}
-                for l1, _ in arcs:
+                for l1, _ in list(arcs.keys()):
                     if l1:
                         ldf[l1] = None
             return line_data
@@ -270,4 +350,4 @@
         if self.branch:
             return self.data
         else:
-            return {}
\ No newline at end of file
+            return {}

eric ide

mercurial