summaryrefslogtreecommitdiff
path: root/coverage/collector.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/collector.py')
-rw-r--r--coverage/collector.py247
1 files changed, 78 insertions, 169 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index 94af5df5..97b45deb 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -1,6 +1,9 @@
"""Raw data collector for Coverage."""
-import collections, os, sys, threading
+import os, sys
+
+from coverage.misc import CoverageException
+from coverage.pytracer import PyTracer
try:
# Use the C extension code when we can, for speed.
@@ -21,147 +24,6 @@ except ImportError:
CTracer = None
-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.warn = None
- self.cur_file_data = None
- self.last_line = 0
- self.data_stack = []
- self.data_stacks = collections.defaultdict(list)
- self.last_exc_back = None
- self.last_exc_firstlineno = 0
- self.arcs = False
- self.thread = None
- self.stopped = False
- self.coroutine_id_func = None
- self.last_coroutine = None
-
- def _trace(self, frame, event, arg_unused):
- """The trace function passed to sys.settrace."""
-
- 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:
- pair = (self.last_line, -self.last_exc_firstlineno)
- self.cur_file_data[pair] = None
- if self.coroutine_id_func:
- self.data_stack = self.data_stacks[self.coroutine_id_func()]
- self.cur_file_data, self.last_line = self.data_stack.pop()
- self.last_exc_back = None
-
- if event == 'call':
- # Entering a new function context. Decide if we should trace
- # in this file.
- if self.coroutine_id_func:
- self.data_stack = self.data_stacks[self.coroutine_id_func()]
- self.last_coroutine = self.coroutine_id_func()
- self.data_stack.append((self.cur_file_data, self.last_line))
- filename = frame.f_code.co_filename
- 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.coroutine_id_func:
- # assert self.last_coroutine == self.coroutine_id_func()
- 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:
- #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:
- first = frame.f_code.co_firstlineno
- self.cur_file_data[(self.last_line, -first)] = None
- # Leaving this function, pop the filename stack.
- if self.coroutine_id_func:
- self.data_stack = self.data_stacks[self.coroutine_id_func()]
- self.last_coroutine = self.coroutine_id_func()
- 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)
- 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.
-
- 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):
- """Return a dictionary of statistics, or None."""
- return None
-
-
class Collector(object):
"""Collects trace data.
@@ -183,13 +45,17 @@ class Collector(object):
# the top, and resumed when they become the top again.
_collectors = []
- def __init__(self, should_trace, timid, branch, warn, coroutine):
+ def __init__(self,
+ should_trace, check_include, timid, branch, warn, coroutine,
+ ):
"""Create a collector.
`should_trace` is a function, taking a filename, and returning a
canonicalized filename, or None depending on whether the file should
be traced or not.
+ TODO: `check_include`
+
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
@@ -202,21 +68,44 @@ class Collector(object):
`warn` is a warning function, taking a single string message argument,
to be used if a warning needs to be issued.
+ TODO: `coroutine`
+
"""
self.should_trace = should_trace
+ self.check_include = check_include
self.warn = warn
self.branch = branch
- if coroutine == "greenlet":
- import greenlet
- self.coroutine_id_func = greenlet.getcurrent
- elif coroutine == "eventlet":
- import eventlet.greenthread
- self.coroutine_id_func = eventlet.greenthread.getcurrent
- elif coroutine == "gevent":
- import gevent
- self.coroutine_id_func = gevent.getcurrent
- else:
- self.coroutine_id_func = None
+ self.threading = None
+ self.coroutine = coroutine
+
+ self.coroutine_id_func = None
+
+ try:
+ if coroutine == "greenlet":
+ import greenlet
+ self.coroutine_id_func = greenlet.getcurrent
+ elif coroutine == "eventlet":
+ import eventlet.greenthread
+ self.coroutine_id_func = eventlet.greenthread.getcurrent
+ elif coroutine == "gevent":
+ import gevent
+ self.coroutine_id_func = gevent.getcurrent
+ elif coroutine == "thread" or not coroutine:
+ # It's important to import threading only if we need it. If
+ # it's imported early, and the program being measured uses
+ # gevent, then gevent's monkey-patching won't work properly.
+ import threading
+ self.threading = threading
+ else:
+ raise CoverageException(
+ "Don't understand coroutine=%s" % coroutine
+ )
+ except ImportError:
+ raise CoverageException(
+ "Couldn't trace with coroutine=%s, "
+ "the module isn't installed." % coroutine
+ )
+
self.reset()
if timid:
@@ -240,6 +129,8 @@ class Collector(object):
# or mapping filenames to dicts with linenumber pairs as keys.
self.data = {}
+ self.plugin_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
# None).
@@ -256,10 +147,25 @@ class Collector(object):
tracer.should_trace = self.should_trace
tracer.should_trace_cache = self.should_trace_cache
tracer.warn = self.warn
+
if hasattr(tracer, 'coroutine_id_func'):
tracer.coroutine_id_func = self.coroutine_id_func
+ elif self.coroutine_id_func:
+ raise CoverageException(
+ "Can't support coroutine=%s with %s, "
+ "only threads are supported" % (
+ self.coroutine, self.tracer_name(),
+ )
+ )
+
+ if hasattr(tracer, 'plugin_data'):
+ tracer.plugin_data = self.plugin_data
+ if hasattr(tracer, 'threading'):
+ tracer.threading = self.threading
+
fn = tracer.start()
self.tracers.append(tracer)
+
return fn
# The trace function has to be set individually on each thread before
@@ -286,20 +192,19 @@ class Collector(object):
if self._collectors:
self._collectors[-1].pause()
self._collectors.append(self)
- #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', [])
+ fn0 = sys.gettrace()
+ if fn0:
+ tracer0 = getattr(fn0, '__self__', None)
+ if tracer0:
+ traces0 = getattr(tracer0, 'traces', [])
# Install the tracer on this thread.
fn = self._start_tracer()
+ # Replay all the events from fullcoverage into the new trace function.
for args in traces0:
(frame, event, arg), lineno = args
try:
@@ -311,11 +216,11 @@ class Collector(object):
# Install our installation tracer in threading, to jump start other
# threads.
- threading.settrace(self._installation_trace)
+ if self.threading:
+ self.threading.settrace(self._installation_trace)
def stop(self):
"""Stop collecting trace information."""
- #print >>sys.stderr, "Stopping: %r" % self._collectors
assert self._collectors
assert self._collectors[-1] is self
@@ -337,13 +242,17 @@ class Collector(object):
print("\nCoverage.py tracer stats:")
for k in sorted(stats.keys()):
print("%16s: %s" % (k, stats[k]))
- threading.settrace(None)
+ if self.threading:
+ self.threading.settrace(None)
def resume(self):
"""Resume tracing after a `pause`."""
for tracer in self.tracers:
tracer.start()
- threading.settrace(self._installation_trace)
+ if self.threading:
+ self.threading.settrace(self._installation_trace)
+ else:
+ self._start_tracer()
def get_line_data(self):
"""Return the line data collected.
@@ -356,10 +265,7 @@ class Collector(object):
# to show line data.
line_data = {}
for f, arcs in self.data.items():
- line_data[f] = ldf = {}
- for l1, _ in list(arcs.keys()):
- if l1:
- ldf[l1] = None
+ line_data[f] = dict((l1, None) for l1, _ in arcs.keys() if l1)
return line_data
else:
return self.data
@@ -377,3 +283,6 @@ class Collector(object):
return self.data
else:
return {}
+
+ def get_plugin_data(self):
+ return self.plugin_data