diff options
Diffstat (limited to 'coverage/collector.py')
-rw-r--r-- | coverage/collector.py | 156 |
1 files changed, 80 insertions, 76 deletions
diff --git a/coverage/collector.py b/coverage/collector.py index 546525d2..4caf6363 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -1,6 +1,6 @@ """Raw data collector for Coverage.""" -import collections, os, sys, threading +import collections, os, sys try: # Use the C extension code when we can, for speed. @@ -47,12 +47,14 @@ class PyTracer(object): self.should_trace = None self.should_trace_cache = None self.warn = None - self.extensions = None + self.plugin_data = None + # The threading module to use, if any. + self.threading = None + + self.plugin = [] + self.cur_file_dict = [] + self.last_line = [0] - self.extension = None - self.cur_tracename = None # TODO: This is only maintained for the if0 debugging output. Get rid of it eventually. - self.cur_file_data = None - self.last_line = 0 self.data_stack = [] self.data_stacks = collections.defaultdict(list) self.last_exc_back = None @@ -62,48 +64,28 @@ class PyTracer(object): self.coroutine_id_func = None self.last_coroutine = None + def __repr__(self): + return "<PyTracer at 0x{0:0x}: {1} lines in {2} files>".format( + id(self), + sum(len(v) for v in self.data.values()), + len(self.data), + ) + def _trace(self, frame, event, arg_unused): """The trace function passed to sys.settrace.""" if self.stopped: return - if 0: - # A lot of debugging to try to understand why gevent isn't right. - import os.path, pprint - def short_ident(ident): - return "{}:{:06X}".format(ident.__class__.__name__, id(ident) & 0xFFFFFF) - - ident = None - if self.coroutine_id_func: - ident = short_ident(self.coroutine_id_func()) - sys.stdout.write("trace event: %s %s %r @%d\n" % ( - event, ident, frame.f_code.co_filename, frame.f_lineno - )) - pprint.pprint( - dict( - ( - short_ident(ident), - [ - (os.path.basename(tn or ""), sorted((cfd or {}).keys()), ll) - for ex, tn, cfd, ll in data_stacks - ] - ) - for ident, data_stacks in self.data_stacks.items() - ) - , width=250) - pprint.pprint(sorted((self.cur_file_data or {}).keys()), width=250) - print("TRYING: {}".format(sorted(next((v for k,v in self.data.items() if k.endswith("try_it.py")), {}).keys()))) - - if self.last_exc_back: + if self.last_exc_back: # TODO: bring this up to speed if frame == self.last_exc_back: # Someone forgot a return event. - if self.arcs and self.cur_file_data: + if self.arcs and self.cur_file_dict: pair = (self.last_line, -self.last_exc_firstlineno) - self.cur_file_data[pair] = None + self.cur_file_dict[pair] = None if self.coroutine_id_func: self.data_stack = self.data_stacks[self.coroutine_id_func()] - self.handler, _, self.cur_file_data, self.last_line = self.data_stack.pop() + self.plugin, self.cur_file_dict, self.last_line = self.data_stack.pop() self.last_exc_back = None if event == 'call': @@ -112,27 +94,33 @@ class PyTracer(object): 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.extension, self.cur_tracename, self.cur_file_data, self.last_line)) + self.data_stack.append((self.plugin, self.cur_file_dict, self.last_line)) filename = frame.f_code.co_filename disp = self.should_trace_cache.get(filename) if disp is None: disp = self.should_trace(filename, frame) self.should_trace_cache[filename] = disp - #print("called, stack is %d deep, tracename is %r" % ( - # len(self.data_stack), tracename)) - tracename = disp.filename - if tracename and disp.extension: - tracename = disp.extension.file_name(frame) + + self.plugin = None + self.cur_file_dict = None + if disp.trace: + tracename = disp.source_filename + if disp.plugin: + dyn_func = disp.plugin.dynamic_source_file_name() + if dyn_func: + tracename = dyn_func(tracename, frame) + if tracename: + if not self.check_include(tracename): + tracename = None + else: + tracename = None if tracename: if tracename not in self.data: self.data[tracename] = {} - if disp.extension: - self.extensions[tracename] = disp.extension.__name__ - self.cur_tracename = tracename - self.cur_file_data = self.data[tracename] - self.extension = disp.extension - else: - self.cur_file_data = None + if disp.plugin: + self.plugin_data[tracename] = disp.plugin.__name__ + self.cur_file_dict = self.data[tracename] + self.plugin = disp.plugin # Set the last_line to -1 because the next arc will be entering a # code block, indicated by (-1, n). self.last_line = -1 @@ -142,32 +130,29 @@ class PyTracer(object): this_coroutine = self.coroutine_id_func() if self.last_coroutine != this_coroutine: print("mismatch: {0} != {1}".format(self.last_coroutine, this_coroutine)) - if self.extension: - lineno_from, lineno_to = self.extension.line_number_range(frame) + + if self.plugin: + lineno_from, lineno_to = self.plugin.line_number_range(frame) else: lineno_from, lineno_to = frame.f_lineno, frame.f_lineno if lineno_from != -1: - if self.cur_file_data is not None: + if self.cur_file_dict is not None: if self.arcs: - #print("lin", self.last_line, frame.f_lineno) - self.cur_file_data[(self.last_line, lineno_from)] = None + self.cur_file_dict[(self.last_line, lineno_from)] = None else: - #print("lin", frame.f_lineno) for lineno in range(lineno_from, lineno_to+1): - self.cur_file_data[lineno] = None + self.cur_file_dict[lineno] = None self.last_line = lineno_to elif event == 'return': - if self.arcs and self.cur_file_data: + if self.arcs and self.cur_file_dict: first = frame.f_code.co_firstlineno - self.cur_file_data[(self.last_line, -first)] = None + self.cur_file_dict[(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.extension, _, self.cur_file_data, self.last_line = self.data_stack.pop() - #print("returned, stack is %d deep" % (len(self.data_stack))) + self.plugin, self.cur_file_dict, 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 self.last_exc_firstlineno = frame.f_code.co_firstlineno return self._trace @@ -178,14 +163,15 @@ class PyTracer(object): Return a Python function suitable for use with sys.settrace(). """ - self.thread = threading.currentThread() + if self.threading: + self.thread = self.threading.currentThread() sys.settrace(self._trace) return self._trace def stop(self): """Stop this Tracer.""" self.stopped = True - if self.thread != threading.currentThread(): + if self.threading and self.thread != self.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. @@ -195,7 +181,7 @@ class PyTracer(object): 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): @@ -224,13 +210,15 @@ 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 @@ -243,10 +231,15 @@ 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 + self.threading = None + if coroutine == "greenlet": import greenlet self.coroutine_id_func = greenlet.getcurrent @@ -257,7 +250,13 @@ class Collector(object): import gevent self.coroutine_id_func = gevent.getcurrent else: + # 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.coroutine_id_func = None + self.threading = threading + self.reset() if timid: @@ -281,7 +280,7 @@ class Collector(object): # or mapping filenames to dicts with linenumber pairs as keys. self.data = {} - self.extensions = {} + 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 @@ -301,8 +300,10 @@ class Collector(object): tracer.warn = self.warn if hasattr(tracer, 'coroutine_id_func'): tracer.coroutine_id_func = self.coroutine_id_func - if hasattr(tracer, 'extensions'): - tracer.extensions = self.extensions + 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 @@ -331,7 +332,6 @@ 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 = [] @@ -356,11 +356,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 @@ -382,13 +382,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. @@ -420,5 +424,5 @@ class Collector(object): else: return {} - def get_extension_data(self): - return self.extensions + def get_plugin_data(self): + return self.plugin_data |