summaryrefslogtreecommitdiff
path: root/Lib/trace.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/trace.py')
-rw-r--r--Lib/trace.py187
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()