diff -r 7af576e3cb0c Tools/fullcoverage/encodings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Tools/fullcoverage/encodings.py Mon Aug 01 15:27:00 2011 -0400 @@ -0,0 +1,181 @@ +"""Imposter encodings module that installs a coverage-style tracer. + +This is NOT the encodings module; it is an imposter that sets up tracing +instrumentation and then replaces itself with the real encodings module. + +If the directory that holds this file is placed first in the PYTHONPATH +when using "coverage" to run Python's tests, then this file will become +the very first module imported by the internals of Python 3. It does +two things: + +* Installs a coverage-compatible trace function that can watch + Standard Library modules execute from the very earliest stages of + Python's own boot process. This fixes a problem with coverage - + that it starts too late to trace the coverage of many of the most + fundamental modules in the Standard Library. + +* When the process exits, it opens the .coverage file in the current + directory, if one exists, and merges in the line numbers that were + recorded by its own primordial tracer. + +""" + +import atexit +import sys + +# ---------------- Section cut-and-pasted from coverage ---------------- + +class PyTracer: + """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. + + 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 + + def _trace(self, frame, event, arg_unused): + """The trace function passed to sys.settrace.""" + + 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 + tracename = self.should_trace_cache.get(filename) + if tracename is None: + tracename = self.should_trace(filename, frame) + self.should_trace_cache[filename] = tracename + #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(). + + """ + sys.settrace(self._trace) + return self._trace + + def stop(self): + """Stop this Tracer.""" + 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()) + sys.settrace(None) + + def get_stats(self): + """Return a dictionary of statistics, or None.""" + return None + +# ---------------- End cut-and-paste from coverage ---------------- + + def atexit(self): + """Supplement the .coverage file with lines we ourselves observed.""" + + import pickle # cannot import pickle when this module is 1st loaded + with open('.coverage', 'rb') as f: + dotcoverage = pickle.load(f) + + linedb = dotcoverage['lines'] # { filename: [lineno+]} + arcdb = dotcoverage.get('arcs', None) # { filename: [(last, lineno)+]} + for filename, arcdict in self.data.items(): + + lines = set(lineno for last_line, lineno in arcdict) + if filename in linedb: + lines.update(linedb[filename]) + linedb[filename] = sorted(lines) + + if arcdb is None: + continue + + arcs = set(arcdict) + if filename in arcdb: + arcs.update(arcdb[filename]) + arcdb[filename] = sorted(arcs) + + with open('.coverage', 'wb') as f: + pickle.dump(dotcoverage, f) + +# Create our tracer and register its "atexit()" method. + +def always_trace(filename, frame): + return filename + +def fake_warning_function(filename): + pass + +tracer = PyTracer() +atexit.register(tracer.atexit) +tracer.data = {} +tracer.should_trace = always_trace +tracer.should_trace_cache = {} +tracer.warn = fake_warning_function +tracer.arcs = True +tracer.start() + +# Finally, remove our own directory from sys.path; remove ourselves from +# sys.modules; and re-import "encodings", which will be the real package +# this time. Note that the delete from sys.modules dictionary has to +# happen last, since all of the symbols in this module will become None +# at that exact moment, including "sys". + +del sys.path[0] +del sys.modules['encodings'] +import encodings