Home | History | Annotate | Download | only in coverage
      1 """Raw data collector for Coverage."""
      2 
      3 import sys, threading
      4 
      5 try:
      6     # Use the C extension code when we can, for speed.
      7     from coverage.tracer import CTracer
      8 except ImportError:
      9     # Couldn't import the C extension, maybe it isn't built.
     10     CTracer = None
     11 
     12 
     13 class PyTracer(object):
     14     """Python implementation of the raw data tracer."""
     15 
     16     # Because of poor implementations of trace-function-manipulating tools,
     17     # the Python trace function must be kept very simple.  In particular, there
     18     # must be only one function ever set as the trace function, both through
     19     # sys.settrace, and as the return value from the trace function.  Put
     20     # another way, the trace function must always return itself.  It cannot
     21     # swap in other functions, or return None to avoid tracing a particular
     22     # frame.
     23     #
     24     # The trace manipulator that introduced this restriction is DecoratorTools,
     25     # which sets a trace function, and then later restores the pre-existing one
     26     # by calling sys.settrace with a function it found in the current frame.
     27     #
     28     # Systems that use DecoratorTools (or similar trace manipulations) must use
     29     # PyTracer to get accurate results.  The command-line --timid argument is
     30     # used to force the use of this tracer.
     31 
     32     def __init__(self):
     33         self.data = None
     34         self.should_trace = None
     35         self.should_trace_cache = None
     36         self.warn = None
     37         self.cur_file_data = None
     38         self.last_line = 0
     39         self.data_stack = []
     40         self.last_exc_back = None
     41         self.last_exc_firstlineno = 0
     42         self.arcs = False
     43 
     44     def _trace(self, frame, event, arg_unused):
     45         """The trace function passed to sys.settrace."""
     46 
     47         #print("trace event: %s %r @%d" % (
     48         #           event, frame.f_code.co_filename, frame.f_lineno))
     49 
     50         if self.last_exc_back:
     51             if frame == self.last_exc_back:
     52                 # Someone forgot a return event.
     53                 if self.arcs and self.cur_file_data:
     54                     pair = (self.last_line, -self.last_exc_firstlineno)
     55                     self.cur_file_data[pair] = None
     56                 self.cur_file_data, self.last_line = self.data_stack.pop()
     57             self.last_exc_back = None
     58 
     59         if event == 'call':
     60             # Entering a new function context.  Decide if we should trace
     61             # in this file.
     62             self.data_stack.append((self.cur_file_data, self.last_line))
     63             filename = frame.f_code.co_filename
     64             tracename = self.should_trace_cache.get(filename)
     65             if tracename is None:
     66                 tracename = self.should_trace(filename, frame)
     67                 self.should_trace_cache[filename] = tracename
     68             #print("called, stack is %d deep, tracename is %r" % (
     69             #               len(self.data_stack), tracename))
     70             if tracename:
     71                 if tracename not in self.data:
     72                     self.data[tracename] = {}
     73                 self.cur_file_data = self.data[tracename]
     74             else:
     75                 self.cur_file_data = None
     76             # Set the last_line to -1 because the next arc will be entering a
     77             # code block, indicated by (-1, n).
     78             self.last_line = -1
     79         elif event == 'line':
     80             # Record an executed line.
     81             if self.cur_file_data is not None:
     82                 if self.arcs:
     83                     #print("lin", self.last_line, frame.f_lineno)
     84                     self.cur_file_data[(self.last_line, frame.f_lineno)] = None
     85                 else:
     86                     #print("lin", frame.f_lineno)
     87                     self.cur_file_data[frame.f_lineno] = None
     88             self.last_line = frame.f_lineno
     89         elif event == 'return':
     90             if self.arcs and self.cur_file_data:
     91                 first = frame.f_code.co_firstlineno
     92                 self.cur_file_data[(self.last_line, -first)] = None
     93             # Leaving this function, pop the filename stack.
     94             self.cur_file_data, self.last_line = self.data_stack.pop()
     95             #print("returned, stack is %d deep" % (len(self.data_stack)))
     96         elif event == 'exception':
     97             #print("exc", self.last_line, frame.f_lineno)
     98             self.last_exc_back = frame.f_back
     99             self.last_exc_firstlineno = frame.f_code.co_firstlineno
    100         return self._trace
    101 
    102     def start(self):
    103         """Start this Tracer.
    104 
    105         Return a Python function suitable for use with sys.settrace().
    106 
    107         """
    108         sys.settrace(self._trace)
    109         return self._trace
    110 
    111     def stop(self):
    112         """Stop this Tracer."""
    113         if hasattr(sys, "gettrace") and self.warn:
    114             if sys.gettrace() != self._trace:
    115                 msg = "Trace function changed, measurement is likely wrong: %r"
    116                 self.warn(msg % sys.gettrace())
    117         sys.settrace(None)
    118 
    119     def get_stats(self):
    120         """Return a dictionary of statistics, or None."""
    121         return None
    122 
    123 
    124 class Collector(object):
    125     """Collects trace data.
    126 
    127     Creates a Tracer object for each thread, since they track stack
    128     information.  Each Tracer points to the same shared data, contributing
    129     traced data points.
    130 
    131     When the Collector is started, it creates a Tracer for the current thread,
    132     and installs a function to create Tracers for each new thread started.
    133     When the Collector is stopped, all active Tracers are stopped.
    134 
    135     Threads started while the Collector is stopped will never have Tracers
    136     associated with them.
    137 
    138     """
    139 
    140     # The stack of active Collectors.  Collectors are added here when started,
    141     # and popped when stopped.  Collectors on the stack are paused when not
    142     # the top, and resumed when they become the top again.
    143     _collectors = []
    144 
    145     def __init__(self, should_trace, timid, branch, warn):
    146         """Create a collector.
    147 
    148         `should_trace` is a function, taking a filename, and returning a
    149         canonicalized filename, or False depending on whether the file should
    150         be traced or not.
    151 
    152         If `timid` is true, then a slower simpler trace function will be
    153         used.  This is important for some environments where manipulation of
    154         tracing functions make the faster more sophisticated trace function not
    155         operate properly.
    156 
    157         If `branch` is true, then branches will be measured.  This involves
    158         collecting data on which statements followed each other (arcs).  Use
    159         `get_arc_data` to get the arc data.
    160 
    161         `warn` is a warning function, taking a single string message argument,
    162         to be used if a warning needs to be issued.
    163 
    164         """
    165         self.should_trace = should_trace
    166         self.warn = warn
    167         self.branch = branch
    168         self.reset()
    169 
    170         if timid:
    171             # Being timid: use the simple Python trace function.
    172             self._trace_class = PyTracer
    173         else:
    174             # Being fast: use the C Tracer if it is available, else the Python
    175             # trace function.
    176             self._trace_class = CTracer or PyTracer
    177 
    178     def __repr__(self):
    179         return "<Collector at 0x%x>" % id(self)
    180 
    181     def tracer_name(self):
    182         """Return the class name of the tracer we're using."""
    183         return self._trace_class.__name__
    184 
    185     def reset(self):
    186         """Clear collected data, and prepare to collect more."""
    187         # A dictionary mapping filenames to dicts with linenumber keys,
    188         # or mapping filenames to dicts with linenumber pairs as keys.
    189         self.data = {}
    190 
    191         # A cache of the results from should_trace, the decision about whether
    192         # to trace execution in a file. A dict of filename to (filename or
    193         # False).
    194         self.should_trace_cache = {}
    195 
    196         # Our active Tracers.
    197         self.tracers = []
    198 
    199     def _start_tracer(self):
    200         """Start a new Tracer object, and store it in self.tracers."""
    201         tracer = self._trace_class()
    202         tracer.data = self.data
    203         tracer.arcs = self.branch
    204         tracer.should_trace = self.should_trace
    205         tracer.should_trace_cache = self.should_trace_cache
    206         tracer.warn = self.warn
    207         fn = tracer.start()
    208         self.tracers.append(tracer)
    209         return fn
    210 
    211     # The trace function has to be set individually on each thread before
    212     # execution begins.  Ironically, the only support the threading module has
    213     # for running code before the thread main is the tracing function.  So we
    214     # install this as a trace function, and the first time it's called, it does
    215     # the real trace installation.
    216 
    217     def _installation_trace(self, frame_unused, event_unused, arg_unused):
    218         """Called on new threads, installs the real tracer."""
    219         # Remove ourselves as the trace function
    220         sys.settrace(None)
    221         # Install the real tracer.
    222         fn = self._start_tracer()
    223         # Invoke the real trace function with the current event, to be sure
    224         # not to lose an event.
    225         if fn:
    226             fn = fn(frame_unused, event_unused, arg_unused)
    227         # Return the new trace function to continue tracing in this scope.
    228         return fn
    229 
    230     def start(self):
    231         """Start collecting trace information."""
    232         if self._collectors:
    233             self._collectors[-1].pause()
    234         self._collectors.append(self)
    235         #print >>sys.stderr, "Started: %r" % self._collectors
    236 
    237         # Check to see whether we had a fullcoverage tracer installed.
    238         traces0 = None
    239         if hasattr(sys, "gettrace"):
    240             fn0 = sys.gettrace()
    241             if fn0:
    242                 tracer0 = getattr(fn0, '__self__', None)
    243                 if tracer0:
    244                     traces0 = getattr(tracer0, 'traces', None)
    245 
    246         # Install the tracer on this thread.
    247         fn = self._start_tracer()
    248 
    249         if traces0:
    250             for args in traces0:
    251                 (frame, event, arg), lineno = args
    252                 fn(frame, event, arg, lineno=lineno)
    253 
    254         # Install our installation tracer in threading, to jump start other
    255         # threads.
    256         threading.settrace(self._installation_trace)
    257 
    258     def stop(self):
    259         """Stop collecting trace information."""
    260         #print >>sys.stderr, "Stopping: %r" % self._collectors
    261         assert self._collectors
    262         assert self._collectors[-1] is self
    263 
    264         self.pause()
    265         self.tracers = []
    266 
    267         # Remove this Collector from the stack, and resume the one underneath
    268         # (if any).
    269         self._collectors.pop()
    270         if self._collectors:
    271             self._collectors[-1].resume()
    272 
    273     def pause(self):
    274         """Pause tracing, but be prepared to `resume`."""
    275         for tracer in self.tracers:
    276             tracer.stop()
    277             stats = tracer.get_stats()
    278             if stats:
    279                 print("\nCoverage.py tracer stats:")
    280                 for k in sorted(stats.keys()):
    281                     print("%16s: %s" % (k, stats[k]))
    282         threading.settrace(None)
    283 
    284     def resume(self):
    285         """Resume tracing after a `pause`."""
    286         for tracer in self.tracers:
    287             tracer.start()
    288         threading.settrace(self._installation_trace)
    289 
    290     def get_line_data(self):
    291         """Return the line data collected.
    292 
    293         Data is { filename: { lineno: None, ...}, ...}
    294 
    295         """
    296         if self.branch:
    297             # If we were measuring branches, then we have to re-build the dict
    298             # to show line data.
    299             line_data = {}
    300             for f, arcs in self.data.items():
    301                 line_data[f] = ldf = {}
    302                 for l1, _ in list(arcs.keys()):
    303                     if l1:
    304                         ldf[l1] = None
    305             return line_data
    306         else:
    307             return self.data
    308 
    309     def get_arc_data(self):
    310         """Return the arc data collected.
    311 
    312         Data is { filename: { (l1, l2): None, ...}, ...}
    313 
    314         Note that no data is collected or returned if the Collector wasn't
    315         created with `branch` true.
    316 
    317         """
    318         if self.branch:
    319             return self.data
    320         else:
    321             return {}
    322