Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(106)

Unified Diff: third_party/pycoverage/coverage/collector.py

Issue 727003004: Add python coverage module to third_party (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/pycoverage/coverage/codeunit.py ('k') | third_party/pycoverage/coverage/config.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/pycoverage/coverage/collector.py
diff --git a/third_party/pycoverage/coverage/collector.py b/third_party/pycoverage/coverage/collector.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ba7d87cd4e055b2e11efccd487bc7493f58d19d
--- /dev/null
+++ b/third_party/pycoverage/coverage/collector.py
@@ -0,0 +1,353 @@
+"""Raw data collector for Coverage."""
+
+import os, sys, threading
+
+try:
+ # Use the C extension code when we can, for speed.
+ from coverage.tracer import CTracer # pylint: disable=F0401,E0611
+except ImportError:
+ # Couldn't import the C extension, maybe it isn't built.
+ 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):
+ """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.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."""
+
+ 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
+ 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.
+ 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.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.
+ 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.
+
+ 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, timid, branch, warn):
+ """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.
+
+ 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.
+
+ `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()
+
+ 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 = CTracer 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 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
+ # None).
+ self.should_trace_cache = {}
+
+ # Our active Tracers.
+ self.tracers = []
+
+ def _start_tracer(self):
+ """Start a new Tracer object, and store it in self.tracers."""
+ 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.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
+ # for running code before the thread main is the tracing function. So we
+ # install this as a trace function, and the first time it's called, it does
+ # the real trace installation.
+
+ def _installation_trace(self, frame_unused, event_unused, arg_unused):
+ """Called on new threads, installs the real tracer."""
+ # Remove ourselves as the trace function
+ sys.settrace(None)
+ # Install the real tracer.
+ 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("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.
+ 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)
+
+ def stop(self):
+ """Stop collecting trace information."""
+ #print >>sys.stderr, "Stopping: %r" % self._collectors
+ assert self._collectors
+ assert self._collectors[-1] is self
+
+ self.pause()
+ self.tracers = []
+
+ # Remove this Collector from the stack, and resume the one underneath
+ # (if any).
+ self._collectors.pop()
+ if self._collectors:
+ self._collectors[-1].resume()
+
+ def pause(self):
+ """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 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 list(arcs.keys()):
+ 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 previous file with comments | « third_party/pycoverage/coverage/codeunit.py ('k') | third_party/pycoverage/coverage/config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698