diff options
author | Jeremy Hylton <jeremy@alum.mit.edu> | 2003-07-07 16:09:24 (GMT) |
---|---|---|
committer | Jeremy Hylton <jeremy@alum.mit.edu> | 2003-07-07 16:09:24 (GMT) |
commit | 5ec79681d2848c8914a6a67b47ab020538f6cecb (patch) | |
tree | ec133a8191d6c45c04fba8ff4cc91776997d698e /Tools/scripts | |
parent | d7ce86dcab78d3fef32bb8fff23e84cf9df628db (diff) | |
download | cpython-5ec79681d2848c8914a6a67b47ab020538f6cecb.zip cpython-5ec79681d2848c8914a6a67b47ab020538f6cecb.tar.gz cpython-5ec79681d2848c8914a6a67b47ab020538f6cecb.tar.bz2 |
This file was moved to Lib/.
Diffstat (limited to 'Tools/scripts')
-rw-r--r-- | Tools/scripts/trace.py | 729 |
1 files changed, 0 insertions, 729 deletions
diff --git a/Tools/scripts/trace.py b/Tools/scripts/trace.py deleted file mode 100644 index a4d324a..0000000 --- a/Tools/scripts/trace.py +++ /dev/null @@ -1,729 +0,0 @@ -#!/usr/bin/env python - -# portions copyright 2001, Autonomous Zones Industries, Inc., all rights... -# err... reserved and offered to the public under the terms of the -# Python 2.2 license. -# Author: Zooko O'Whielacronx -# http://zooko.com/ -# mailto:zooko@zooko.com -# -# Copyright 2000, Mojam Media, Inc., all rights reserved. -# Author: Skip Montanaro -# -# Copyright 1999, Bioreason, Inc., all rights reserved. -# Author: Andrew Dalke -# -# Copyright 1995-1997, Automatrix, Inc., all rights reserved. -# Author: Skip Montanaro -# -# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved. -# -# -# Permission to use, copy, modify, and distribute this Python software and -# its associated documentation for any purpose without fee is hereby -# granted, provided that the above copyright notice appears in all copies, -# and that both that copyright notice and this permission notice appear in -# supporting documentation, and that the name of neither Automatrix, -# Bioreason or Mojam Media be used in advertising or publicity pertaining to -# distribution of the software without specific, written prior permission. -# -"""program/module to trace Python program or function execution - -Sample use, command line: - trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs - trace.py -t --ignore-dir '$prefix' spam.py eggs - -Sample use, programmatically - # create a Trace object, telling it what to ignore, and whether to - # do tracing or line-counting or both. - trace = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0, - count=1) - # run the new command using the given trace - trace.run(coverage.globaltrace, 'main()') - # make a report, telling it where you want output - r = trace.results() - r.write_results(show_missing=1) -""" - -import sys, os, tempfile, types, copy, operator, inspect, exceptions, marshal -try: - import cPickle - pickle = cPickle -except ImportError: - import pickle - -# DEBUG_MODE=1 # make this true to get printouts which help you understand what's going on - -def usage(outfile): - outfile.write("""Usage: %s [OPTIONS] <file> [ARGS] - -Meta-options: ---help Display this help then exit. ---version Output version information then exit. - -Otherwise, exactly one of the following three options must be given: --t, --trace Print each line to sys.stdout before it is executed. --c, --count Count the number of times each line is executed - and write the counts to <module>.cover for each - module executed, in the module's directory. - See also `--coverdir', `--file', `--no-report' below. --r, --report Generate a report from a counts file; do not execute - any code. `--file' must specify the results file to - read, which must have been created in a previous run - with `--count --file=FILE'. - -Modifiers: --f, --file=<file> File to accumulate counts over several runs. --R, --no-report Do not generate the coverage report files. - Useful if you want to accumulate over several runs. --C, --coverdir=<dir> Directory where the report files. The coverage - report for <package>.<module> is written to file - <dir>/<package>/<module>.cover. --m, --missing Annotate executable lines that were not executed - with '>>>>>> '. --s, --summary Write a brief summary on stdout for each file. - (Can only be used with --count or --report.) - -Filters, may be repeated multiple times: ---ignore-module=<mod> Ignore the given module and its submodules - (if it is a package). ---ignore-dir=<dir> Ignore files in the given directory (multiple - directories can be joined by os.pathsep). -""" % sys.argv[0]) - -class Ignore: - def __init__(self, modules = None, dirs = None): - self._mods = modules or [] - self._dirs = dirs or [] - - self._dirs = map(os.path.normpath, self._dirs) - self._ignore = { '<string>': 1 } - - def names(self, filename, modulename): - if self._ignore.has_key(modulename): - 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". - 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 occured, then the size - # of "name" is greater than that of "mod") - if mod == modulename[:n] and modulename[n] == '.': - self._ignore[modulename] = 1 - return 1 - - # Now check that __file__ isn't in one of the directories - if filename is None: - # must be a built-in, so we must ignore - self._ignore[modulename] = 1 - return 1 - - # Ignore a file when it contains one of the ignorable paths - for d in self._dirs: - # The '+ os.sep' is to ensure that d is a parent directory, - # as compared to cases like: - # d = "/usr/local" - # filename = "/usr/local.py" - # or - # d = "/usr/local.py" - # filename = "/usr/local.py" - if filename.startswith(d + os.sep): - self._ignore[modulename] = 1 - return 1 - - # Tried the different ways, so we don't ignore this module - self._ignore[modulename] = 0 - return 0 - -class CoverageResults: - def __init__(self, counts=None, calledfuncs=None, infile=None, - outfile=None): - self.counts = counts - if self.counts is None: - self.counts = {} - self.counter = self.counts.copy() # map (filename, lineno) to count - self.calledfuncs = calledfuncs - if self.calledfuncs is None: - self.calledfuncs = {} - self.calledfuncs = self.calledfuncs.copy() - self.infile = infile - self.outfile = outfile - if self.infile: - # try and merge existing counts file - try: - thingie = pickle.load(open(self.infile, 'r')) - if type(thingie) is types.DictType: - # backwards compatibility for old trace.py after - # Zooko touched it but before calledfuncs --Zooko - # 2001-10-24 - self.update(self.__class__(thingie)) - elif type(thingie) is types.TupleType and len(thingie) == 2: - counts, calledfuncs = thingie - self.update(self.__class__(counts, calledfuncs)) - except (IOError, EOFError): - pass - except pickle.UnpicklingError: - # backwards compatibility for old trace.py before - # Zooko touched it --Zooko 2001-10-24 - self.update(self.__class__(marshal.load(open(self.infile)))) - - def update(self, other): - """Merge in the data from another CoverageResults""" - counts = self.counts - calledfuncs = self.calledfuncs - other_counts = other.counts - other_calledfuncs = other.calledfuncs - - for key in other_counts.keys(): - if key != 'calledfuncs': - # backwards compatibility for abortive attempt to - # stuff calledfuncs into self.counts, by Zooko - # --Zooko 2001-10-24 - counts[key] = counts.get(key, 0) + other_counts[key] - - for key in other_calledfuncs.keys(): - calledfuncs[key] = 1 - - def write_results(self, show_missing = 1, summary = 0, coverdir = None): - """ - @param coverdir - """ - for filename, modulename, funcname in self.calledfuncs.keys(): - print ("filename: %s, modulename: %s, funcname: %s" - % (filename, modulename, funcname)) - - import re - # turn the counts data ("(filename, lineno) = count") into something - # accessible on a per-file basis - per_file = {} - for thingie in self.counts.keys(): - if thingie != "calledfuncs": - # backwards compatibility for abortive attempt to - # stuff calledfuncs into self.counts, by Zooko --Zooko - # 2001-10-24 - filename, lineno = thingie - lines_hit = per_file[filename] = per_file.get(filename, {}) - lines_hit[lineno] = self.counts[(filename, lineno)] - - # there are many places where this is insufficient, like a blank - # line embedded in a multiline string. - blank = re.compile(r'^\s*(#.*)?$') - - # accumulate summary info, if needed - sums = {} - - # generate file paths for the coverage files we are going to write... - fnlist = [] - tfdir = tempfile.gettempdir() - for key in per_file.keys(): - filename = key - - # skip some "files" we don't care about... - if filename == "<string>": - continue - # are these caused by code compiled using exec or something? - if filename.startswith(tfdir): - continue - - modulename = inspect.getmodulename(filename) - - if filename.endswith(".pyc") or filename.endswith(".pyo"): - filename = filename[:-1] - - if coverdir: - thiscoverdir = coverdir - else: - thiscoverdir = os.path.dirname(os.path.abspath(filename)) - - # the code from here to "<<<" is the contents of the `fileutil.make_dirs()' function in the Mojo Nation project. --Zooko 2001-10-14 - # http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/mojonation/evil/common/fileutil.py?rev=HEAD&content-type=text/vnd.viewcvs-markup - tx = None - try: - os.makedirs(thiscoverdir) - except OSError, x: - tx = x - - if not os.path.isdir(thiscoverdir): - if tx: - raise tx - raise exceptions.IOError, "unknown error prevented creation of directory: %s" % thiscoverdir # careful not to construct an IOError with a 2-tuple, as that has a special meaning... - # <<< - - # build list file name by appending a ".cover" to the module name - # and sticking it into the specified directory - if "." in modulename: - # A module in a package - finalname = modulename.split(".")[-1] - listfilename = os.path.join(thiscoverdir, finalname + ".cover") - else: - listfilename = os.path.join(thiscoverdir, modulename + ".cover") - - # Get the original lines from the .py file - try: - lines = open(filename, 'r').readlines() - except IOError, err: - sys.stderr.write("trace: Could not open %s for reading because: %s - skipping\n" % (`filename`, err)) - continue - - try: - outfile = open(listfilename, 'w') - except IOError, err: - sys.stderr.write( - '%s: Could not open %s for writing because: %s" \ - "- skipping\n' % ("trace", `listfilename`, err)) - continue - - # If desired, get a list of the line numbers which represent - # executable content (returned as a dict for better lookup speed) - if show_missing: - executable_linenos = find_executable_linenos(filename) - else: - executable_linenos = {} - - n_lines = 0 - n_hits = 0 - lines_hit = per_file[key] - for i in range(len(lines)): - line = lines[i] - - # do the blank/comment match to try to mark more lines - # (help the reader find stuff that hasn't been covered) - if lines_hit.has_key(i+1): - # count precedes the lines that we captured - outfile.write('%5d: ' % lines_hit[i+1]) - n_hits = n_hits + 1 - n_lines = n_lines + 1 - elif blank.match(line): - # blank lines and comments are preceded by dots - outfile.write(' . ') - else: - # lines preceded by no marks weren't hit - # Highlight them if so indicated, unless the line contains - # '#pragma: NO COVER' (it is possible to embed this into - # the text as a non-comment; no easy fix) - if executable_linenos.has_key(i+1) and \ - lines[i].find(' '.join(['#pragma', 'NO COVER'])) == -1: - outfile.write('>>>>>> ') - else: - outfile.write(' '*7) - n_lines = n_lines + 1 - outfile.write(lines[i].expandtabs(8)) - - outfile.close() - - if summary and n_lines: - percent = int(100 * n_hits / n_lines) - sums[modulename] = n_lines, percent, modulename, filename - - if summary and sums: - mods = sums.keys() - mods.sort() - print "lines cov% module (path)" - for m in mods: - n_lines, percent, modulename, filename = sums[m] - print "%5d %3d%% %s (%s)" % sums[m] - - if self.outfile: - # try and store counts and module info into self.outfile - try: - pickle.dump((self.counts, self.calledfuncs), - open(self.outfile, 'w'), 1) - except IOError, err: - sys.stderr.write("cannot save counts files because %s" % err) - -def _find_LINENO_from_code(code): - """return the numbers of the lines containing the source code that - was compiled into code""" - linenos = {} - - line_increments = [ord(c) for c in code.co_lnotab[1::2]] - table_length = len(line_increments) - - lineno = code.co_firstlineno - - for li in line_increments: - linenos[lineno] = 1 - lineno += li - linenos[lineno] = 1 - - return linenos - -def _find_LINENO(code): - """return all of the lineno information from a code object""" - import types - - # get all of the lineno information from the code of this scope level - linenos = _find_LINENO_from_code(code) - - # and check the constants for references to other code objects - for c in code.co_consts: - if type(c) == types.CodeType: - # find another code object, so recurse into it - linenos.update(_find_LINENO(c)) - return linenos - -def find_executable_linenos(filename): - """return a dict of the line numbers from executable statements in a file - - """ - import parser - - assert filename.endswith('.py') - - prog = open(filename).read() - ast = parser.suite(prog) - code = parser.compileast(ast, filename) - - return _find_LINENO(code) - -### XXX because os.path.commonprefix seems broken by my way of thinking... -def commonprefix(dirs): - "Given a list of pathnames, returns the longest common leading component" - if not dirs: return '' - n = copy.copy(dirs) - for i in range(len(n)): - n[i] = n[i].split(os.sep) - prefix = n[0] - for item in n: - for i in range(len(prefix)): - if prefix[:i+1] <> item[:i+1]: - prefix = prefix[:i] - if i == 0: return '' - break - return os.sep.join(prefix) - -class Trace: - def __init__(self, count=1, trace=1, countfuncs=0, ignoremods=(), - ignoredirs=(), infile=None, outfile=None): - """ - @param count true iff it should count number of times each - line is executed - @param trace true iff it should print out each line that is - being counted - @param countfuncs true iff it should just output a list of - (filename, modulename, funcname,) for functions - that were called at least once; This overrides - `count' and `trace' - @param ignoremods a list of the names of modules to ignore - @param ignoredirs a list of the names of directories to ignore - all of the (recursive) contents of - @param infile file from which to read stored counts to be - added into the results - @param outfile file in which to write the results - """ - self.infile = infile - self.outfile = outfile - 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 - self._calledfuncs = {} - if countfuncs: - self.globaltrace = self.globaltrace_countfuncs - elif trace and count: - self.globaltrace = self.globaltrace_lt - self.localtrace = self.localtrace_trace_and_count - elif trace: - self.globaltrace = self.globaltrace_lt - self.localtrace = self.localtrace_trace - elif count: - self.globaltrace = self.globaltrace_lt - self.localtrace = self.localtrace_count - else: - # Ahem -- do nothing? Okay. - self.donothing = 1 - - def run(self, cmd): - import __main__ - dict = __main__.__dict__ - if not self.donothing: - sys.settrace(self.globaltrace) - try: - exec cmd in dict, dict - finally: - if not self.donothing: - sys.settrace(None) - - def runctx(self, cmd, globals=None, locals=None): - if globals is None: globals = {} - if locals is None: locals = {} - if not self.donothing: - sys.settrace(self.globaltrace) - try: - exec cmd in globals, locals - finally: - if not self.donothing: - sys.settrace(None) - - def runfunc(self, func, *args, **kw): - result = None - if not self.donothing: - sys.settrace(self.globaltrace) - try: - result = apply(func, args, kw) - finally: - if not self.donothing: - sys.settrace(None) - return result - - def globaltrace_countfuncs(self, frame, why, arg): - """ - Handles `call' events (why == 'call') and adds the (filename, modulename, funcname,) to the self._calledfuncs dict. - """ - if why == 'call': - filename, lineno, funcname, context, lineindex = \ - inspect.getframeinfo(frame, 0) - if filename: - modulename = inspect.getmodulename(filename) - else: - modulename = None - self._calledfuncs[(filename, modulename, funcname,)] = 1 - - def globaltrace_lt(self, frame, why, arg): - """ - Handles `call' events (why == 'call') and if the code block being entered is to be ignored then it returns `None', else it returns `self.localtrace'. - """ - if why == 'call': - filename, lineno, funcname, context, lineindex = \ - inspect.getframeinfo(frame, 0) - if filename: - modulename = inspect.getmodulename(filename) - if modulename is not None: - ignore_it = self.ignore.names(filename, modulename) - if not ignore_it: - if self.trace: - print (" --- modulename: %s, funcname: %s" - % (modulename, funcname)) - return self.localtrace - else: - # XXX why no filename? - return None - - def localtrace_trace_and_count(self, frame, why, arg): - if why == 'line': - # record the file name and line number of every trace - # XXX I wish inspect offered me an optimized - # `getfilename(frame)' to use in place of the presumably - # heavier `getframeinfo()'. --Zooko 2001-10-14 - - filename, lineno, funcname, context, lineindex = \ - inspect.getframeinfo(frame, 1) - key = filename, lineno - self.counts[key] = self.counts.get(key, 0) + 1 - - # XXX not convinced that this memoizing is a performance - # win -- I don't know enough about Python guts to tell. - # --Zooko 2001-10-14 - - bname = self.pathtobasename.get(filename) - if bname is None: - - # Using setdefault faster than two separate lines? - # --Zooko 2001-10-14 - bname = self.pathtobasename.setdefault(filename, - os.path.basename(filename)) - try: - print "%s(%d): %s" % (bname, lineno, context[lineindex]), - except IndexError: - # Uh.. sometimes getframeinfo gives me a context of - # length 1 and a lineindex of -2. Oh well. - pass - return self.localtrace - - def localtrace_trace(self, frame, why, arg): - if why == 'line': - # XXX shouldn't do the count increment when arg is - # exception? But be careful to return self.localtrace - # when arg is exception! ? --Zooko 2001-10-14 - - # record the file name and line number of every trace XXX - # I wish inspect offered me an optimized - # `getfilename(frame)' to use in place of the presumably - # heavier `getframeinfo()'. --Zooko 2001-10-14 - filename, lineno, funcname, context, lineindex = \ - inspect.getframeinfo(frame) - - # XXX not convinced that this memoizing is a performance - # win -- I don't know enough about Python guts to tell. - # --Zooko 2001-10-14 - bname = self.pathtobasename.get(filename) - if bname is None: - # Using setdefault faster than two separate lines? - # --Zooko 2001-10-14 - bname = self.pathtobasename.setdefault(filename, os.path.basename(filename)) - if context is not None: - try: - print "%s(%d): %s" % (bname, lineno, context[lineindex]), - except IndexError: - # Uh.. sometimes getframeinfo gives me a context of length 1 and a lineindex of -2. Oh well. - pass - else: - print "%s(???): ???" % bname - return self.localtrace - - def localtrace_count(self, frame, why, arg): - if why == 'line': - filename = frame.f_code.co_filename - lineno = frame.f_lineno - key = filename, lineno - self.counts[key] = self.counts.get(key, 0) + 1 - return self.localtrace - - def results(self): - return CoverageResults(self.counts, infile=self.infile, - outfile=self.outfile, - calledfuncs=self._calledfuncs) - -def _err_exit(msg): - sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) - sys.exit(1) - -def main(argv=None): - import getopt - - if argv is None: - argv = sys.argv - try: - opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:l", - ["help", "version", "trace", "count", - "report", "no-report", "summary", - "file=", "missing", - "ignore-module=", "ignore-dir=", - "coverdir=", "listfuncs",]) - - except getopt.error, msg: - sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) - sys.stderr.write("Try `%s --help' for more information\n" - % sys.argv[0]) - sys.exit(1) - - trace = 0 - count = 0 - report = 0 - no_report = 0 - counts_file = None - missing = 0 - ignore_modules = [] - ignore_dirs = [] - coverdir = None - summary = 0 - listfuncs = False - - for opt, val in opts: - if opt == "--help": - usage(sys.stdout) - sys.exit(0) - - if opt == "--version": - sys.stdout.write("trace 2.0\n") - sys.exit(0) - - if opt == "-l" or opt == "--listfuncs": - listfuncs = True - continue - - if opt == "-t" or opt == "--trace": - trace = 1 - continue - - if opt == "-c" or opt == "--count": - count = 1 - continue - - if opt == "-r" or opt == "--report": - report = 1 - continue - - if opt == "-R" or opt == "--no-report": - no_report = 1 - continue - - if opt == "-f" or opt == "--file": - counts_file = val - continue - - if opt == "-m" or opt == "--missing": - missing = 1 - continue - - if opt == "-C" or opt == "--coverdir": - coverdir = val - continue - - if opt == "-s" or opt == "--summary": - summary = 1 - continue - - if opt == "--ignore-module": - ignore_modules.append(val) - continue - - if opt == "--ignore-dir": - for s in val.split(os.pathsep): - s = os.path.expandvars(s) - # should I also call expanduser? (after all, could use $HOME) - - s = s.replace("$prefix", - os.path.join(sys.prefix, "lib", - "python" + sys.version[:3])) - s = s.replace("$exec_prefix", - os.path.join(sys.exec_prefix, "lib", - "python" + sys.version[:3])) - s = os.path.normpath(s) - ignore_dirs.append(s) - continue - - assert 0, "Should never get here" - - if listfuncs and (count or trace): - _err_exit("cannot specify both --listfuncs and (--trace or --count)") - - if not count and not trace and not report and not listfuncs: - _err_exit("must specify one of --trace, --count, --report or --listfuncs") - - if report and no_report: - _err_exit("cannot specify both --report and --no-report") - - if report and not counts_file: - _err_exit("--report requires a --file") - - if no_report and len(prog_argv) == 0: - _err_exit("missing name of file to run") - - # everything is ready - if report: - results = CoverageResults(infile=counts_file, outfile=counts_file) - results.write_results(missing, summary=summary, coverdir=coverdir) - else: - sys.argv = prog_argv - progname = prog_argv[0] - sys.path[0] = os.path.split(progname)[0] - - t = Trace(count, trace, countfuncs=listfuncs, - ignoremods=ignore_modules, ignoredirs=ignore_dirs, - infile=counts_file, outfile=counts_file) - try: - t.run('execfile(' + `progname` + ')') - except IOError, err: - _err_exit("Cannot run file %s because: %s" % (`sys.argv[0]`, err)) - except SystemExit: - pass - - results = t.results() - - if not no_report: - results.write_results(missing, summary=summary, coverdir=coverdir) - -if __name__=='__main__': - main() |