diff options
Diffstat (limited to 'Lib/trace.py')
-rw-r--r-- | Lib/trace.py | 187 |
1 files changed, 109 insertions, 78 deletions
diff --git a/Lib/trace.py b/Lib/trace.py index c01935f246..53c6150fd4 100644 --- a/Lib/trace.py +++ b/Lib/trace.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # portions copyright 2001, Autonomous Zones Industries, Inc., all rights... # err... reserved and offered to the public under the terms of the @@ -47,7 +47,7 @@ Sample use, programmatically r = tracer.results() r.write_results(show_missing=True, coverdir="/tmp") """ - +__all__ = ['Trace', 'CoverageResults'] import io import linecache import os @@ -60,6 +60,7 @@ import inspect import gc import dis import pickle +from warnings import warn as _warn try: import threading @@ -77,7 +78,7 @@ else: sys.settrace(None) threading.settrace(None) -def usage(outfile): +def _usage(outfile): outfile.write("""Usage: %s [OPTIONS] <file> [ARGS] Meta-options: @@ -127,12 +128,11 @@ PRAGMA_NOCOVER = "#pragma NO COVER" # Simple rx to find lines with no code. rx_blank = re.compile(r'^\s*(#.*)?$') -class Ignore: - def __init__(self, modules = None, dirs = None): - self._mods = modules or [] - self._dirs = dirs or [] - - self._dirs = list(map(os.path.normpath, self._dirs)) +class _Ignore: + def __init__(self, modules=None, dirs=None): + self._mods = set() if not modules else set(modules) + self._dirs = [] if not dirs else [os.path.normpath(d) + for d in dirs] self._ignore = { '<string>': 1 } def names(self, filename, modulename): @@ -140,24 +140,22 @@ class Ignore: return self._ignore[modulename] # haven't seen this one before, so see if the module name is - # on the ignore list. Need to take some care since ignoring - # "cmp" musn't mean ignoring "cmpcache" but ignoring - # "Spam" must also mean ignoring "Spam.Eggs". + # on the ignore list. + if modulename in self._mods: # Identical names, so ignore + self._ignore[modulename] = 1 + return 1 + + # check if the module is a proper submodule of something on + # the ignore list for mod in self._mods: - if mod == modulename: # Identical names, so ignore - self._ignore[modulename] = 1 - return 1 - # check if the module is a proper submodule of something on - # the ignore list - n = len(mod) - # (will not overflow since if the first n characters are the - # same and the name has not already occurred, then the size - # of "name" is greater than that of "mod") - if mod == modulename[:n] and modulename[n] == '.': + # Need to take some care since ignoring + # "cmp" mustn't mean ignoring "cmpcache" but ignoring + # "Spam" must also mean ignoring "Spam.Eggs". + if modulename.startswith(mod + '.'): self._ignore[modulename] = 1 return 1 - # Now check that __file__ isn't in one of the directories + # Now check that filename isn't in one of the directories if filename is None: # must be a built-in, so we must ignore self._ignore[modulename] = 1 @@ -180,14 +178,14 @@ class Ignore: self._ignore[modulename] = 0 return 0 -def modname(path): +def _modname(path): """Return a plausible module name for the patch.""" base = os.path.basename(path) filename, ext = os.path.splitext(base) return filename -def fullmodname(path): +def _fullmodname(path): """Return a plausible module name for the path.""" # If the file 'path' is part of a package, then the filename isn't @@ -258,13 +256,13 @@ class CoverageResults: other_calledfuncs = other.calledfuncs other_callers = other.callers - for key in other_counts.keys(): + for key in other_counts: counts[key] = counts.get(key, 0) + other_counts[key] - for key in other_calledfuncs.keys(): + for key in other_calledfuncs: calledfuncs[key] = 1 - for key in other_callers.keys(): + for key in other_callers: callers[key] = 1 def write_results(self, show_missing=True, summary=False, coverdir=None): @@ -274,7 +272,7 @@ class CoverageResults: if self.calledfuncs: print() print("functions called:") - calls = self.calledfuncs.keys() + calls = self.calledfuncs for filename, modulename, funcname in sorted(calls): print(("filename: %s, modulename: %s, funcname: %s" % (filename, modulename, funcname))) @@ -284,7 +282,7 @@ class CoverageResults: print("calling relationships:") lastfile = lastcfile = "" for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) \ - in sorted(self.callers.keys()): + in sorted(self.callers): if pfile != lastfile: print() print("***", pfile, "***") @@ -298,7 +296,7 @@ class CoverageResults: # turn the counts data ("(filename, lineno) = count") into something # accessible on a per-file basis per_file = {} - for filename, lineno in self.counts.keys(): + for filename, lineno in self.counts: lines_hit = per_file[filename] = per_file.get(filename, {}) lines_hit[lineno] = self.counts[(filename, lineno)] @@ -314,32 +312,33 @@ class CoverageResults: if coverdir is None: dir = os.path.dirname(os.path.abspath(filename)) - modulename = modname(filename) + modulename = _modname(filename) else: dir = coverdir if not os.path.exists(dir): os.makedirs(dir) - modulename = fullmodname(filename) + modulename = _fullmodname(filename) # If desired, get a list of the line numbers which represent # executable content (returned as a dict for better lookup speed) if show_missing: - lnotab = find_executable_linenos(filename) + lnotab = _find_executable_linenos(filename) else: lnotab = {} source = linecache.getlines(filename) coverpath = os.path.join(dir, modulename + ".cover") + with open(filename, 'rb') as fp: + encoding, _ = tokenize.detect_encoding(fp.readline) n_hits, n_lines = self.write_results_file(coverpath, source, - lnotab, count) - + lnotab, count, encoding) if summary and n_lines: percent = int(100 * n_hits / n_lines) sums[modulename] = n_lines, percent, modulename, filename if summary and sums: print("lines cov% module (path)") - for m in sorted(sums.keys()): + for m in sorted(sums): n_lines, percent, modulename, filename = sums[m] print("%5d %3d%% %s (%s)" % sums[m]) @@ -351,11 +350,11 @@ class CoverageResults: except IOError as err: print("Can't save counts files because %s" % err, file=sys.stderr) - def write_results_file(self, path, lines, lnotab, lines_hit): + def write_results_file(self, path, lines, lnotab, lines_hit, encoding=None): """Return a coverage results file in path.""" try: - outfile = open(path, "w") + outfile = open(path, "w", encoding=encoding) except IOError as err: print(("trace: Could not open %r for writing: %s" "- skipping" % (path, err)), file=sys.stderr) @@ -363,8 +362,7 @@ class CoverageResults: n_lines = 0 n_hits = 0 - for i, line in enumerate(lines): - lineno = i + 1 + for lineno, line in enumerate(lines, 1): # do the blank/comment match to try to mark more lines # (help the reader find stuff that hasn't been covered) if lineno in lines_hit: @@ -377,17 +375,17 @@ class CoverageResults: # lines preceded by no marks weren't hit # Highlight them if so indicated, unless the line contains # #pragma: NO COVER - if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]: + if lineno in lnotab and not PRAGMA_NOCOVER in line: outfile.write(">>>>>> ") n_lines += 1 else: outfile.write(" ") - outfile.write(lines[i].expandtabs(8)) + outfile.write(line.expandtabs(8)) outfile.close() return n_hits, n_lines -def find_lines_from_code(code, strs): +def _find_lines_from_code(code, strs): """Return dict where keys are lines in the line number table.""" linenos = {} @@ -397,19 +395,19 @@ def find_lines_from_code(code, strs): return linenos -def find_lines(code, strs): +def _find_lines(code, strs): """Return lineno dict for all code objects reachable from code.""" # get all of the lineno information from the code of this scope level - linenos = find_lines_from_code(code, strs) + linenos = _find_lines_from_code(code, strs) # and check the constants for references to other code objects for c in code.co_consts: if inspect.iscode(c): # find another code object, so recurse into it - linenos.update(find_lines(c, strs)) + linenos.update(_find_lines(c, strs)) return linenos -def find_strings(filename, encoding=None): +def _find_strings(filename, encoding=None): """Return a dict of possible docstring positions. The dict maps line numbers to strings. There is an entry for @@ -420,31 +418,31 @@ def find_strings(filename, encoding=None): # If the first token is a string, then it's the module docstring. # Add this special case so that the test in the loop passes. prev_ttype = token.INDENT - f = open(filename, encoding=encoding) - for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline): - if ttype == token.STRING: - if prev_ttype == token.INDENT: - sline, scol = start - eline, ecol = end - for i in range(sline, eline + 1): - d[i] = 1 - prev_ttype = ttype - f.close() + with open(filename, encoding=encoding) as f: + tok = tokenize.generate_tokens(f.readline) + for ttype, tstr, start, end, line in tok: + if ttype == token.STRING: + if prev_ttype == token.INDENT: + sline, scol = start + eline, ecol = end + for i in range(sline, eline + 1): + d[i] = 1 + prev_ttype = ttype return d -def find_executable_linenos(filename): +def _find_executable_linenos(filename): """Return dict where keys are line numbers in the line number table.""" try: - with io.FileIO(filename, 'r') as file: - encoding, lines = tokenize.detect_encoding(file.readline) - prog = open(filename, "r", encoding=encoding).read() + with tokenize.open(filename) as f: + prog = f.read() + encoding = f.encoding except IOError as err: print(("Not printing coverage data for %r: %s" % (filename, err)), file=sys.stderr) return {} code = compile(prog, filename, "exec") - strs = find_strings(filename, encoding) - return find_lines(code, strs) + strs = _find_strings(filename, encoding) + return _find_lines(code, strs) class Trace: def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0, @@ -469,9 +467,8 @@ class Trace: """ self.infile = infile self.outfile = outfile - self.ignore = Ignore(ignoremods, ignoredirs) + self.ignore = _Ignore(ignoremods, ignoredirs) self.counts = {} # keys are (filename, linenumber) - self.blabbed = {} # for debugging self.pathtobasename = {} # for memoizing os.path.basename self.donothing = 0 self.trace = trace @@ -501,15 +498,7 @@ class Trace: def run(self, cmd): import __main__ dict = __main__.__dict__ - if not self.donothing: - threading.settrace(self.globaltrace) - sys.settrace(self.globaltrace) - try: - exec(cmd, dict, dict) - finally: - if not self.donothing: - sys.settrace(None) - threading.settrace(None) + self.runctx(cmd, dict, dict) def runctx(self, cmd, globals=None, locals=None): if globals is None: globals = {} @@ -537,7 +526,7 @@ class Trace: code = frame.f_code filename = code.co_filename if filename: - modulename = modname(filename) + modulename = _modname(filename) else: modulename = None @@ -604,9 +593,9 @@ class Trace: code = frame.f_code filename = frame.f_globals.get('__file__', None) if filename: - # XXX modname() doesn't work right for packages, so + # XXX _modname() doesn't work right for packages, so # the ignore support won't work right for packages - modulename = modname(filename) + modulename = _modname(filename) if modulename is not None: ignore_it = self.ignore.names(filename, modulename) if not ignore_it: @@ -822,5 +811,47 @@ def main(argv=None): if not no_report: results.write_results(missing, summary=summary, coverdir=coverdir) +# Deprecated API +def usage(outfile): + _warn("The trace.usage() function is deprecated", + DeprecationWarning, 2) + _usage(outfile) + +class Ignore(_Ignore): + def __init__(self, modules=None, dirs=None): + _warn("The class trace.Ignore is deprecated", + DeprecationWarning, 2) + _Ignore.__init__(self, modules, dirs) + +def modname(path): + _warn("The trace.modname() function is deprecated", + DeprecationWarning, 2) + return _modname(path) + +def fullmodname(path): + _warn("The trace.fullmodname() function is deprecated", + DeprecationWarning, 2) + return _fullmodname(path) + +def find_lines_from_code(code, strs): + _warn("The trace.find_lines_from_code() function is deprecated", + DeprecationWarning, 2) + return _find_lines_from_code(code, strs) + +def find_lines(code, strs): + _warn("The trace.find_lines() function is deprecated", + DeprecationWarning, 2) + return _find_lines(code, strs) + +def find_strings(filename, encoding=None): + _warn("The trace.find_strings() function is deprecated", + DeprecationWarning, 2) + return _find_strings(filename, encoding=None) + +def find_executable_linenos(filename): + _warn("The trace.find_executable_linenos() function is deprecated", + DeprecationWarning, 2) + return _find_executable_linenos(filename) + if __name__=='__main__': main() |