DebugClients/Python3/coverage/collector.py

changeset 29
391dc0bc4ae5
parent 0
de9c2efb9d02
child 3495
fac17a82b431
--- 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

eric ide

mercurial