summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2001-11-28 19:41:45 (GMT)
committerGuido van Rossum <guido@python.org>2001-11-28 19:41:45 (GMT)
commita30eacf45815c14b480972c5097677fac83ed4c0 (patch)
tree30c5de713f3f30a00eb4929303f18aca450dc8a4
parenta437d4594bd38a93f5116e345c8f0b06a6593d38 (diff)
downloadcpython-a30eacf45815c14b480972c5097677fac83ed4c0.zip
cpython-a30eacf45815c14b480972c5097677fac83ed4c0.tar.gz
cpython-a30eacf45815c14b480972c5097677fac83ed4c0.tar.bz2
Checking in Zooko's version per SF patch #476866, plus my changes to
the usage docs: (a) align properly, (b) explain properly.
-rw-r--r--Tools/scripts/trace.py824
1 files changed, 429 insertions, 395 deletions
diff --git a/Tools/scripts/trace.py b/Tools/scripts/trace.py
index 14ccffd..e75551c 100644
--- a/Tools/scripts/trace.py
+++ b/Tools/scripts/trace.py
@@ -1,5 +1,12 @@
#!/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
#
@@ -21,6 +28,30 @@
# distribution of the software without specific, written prior permission.
#
#
+# Cleaned up the usage message --GvR 11/28/01
+#
+# Summary of even more recent changes, --Zooko 2001-10-14
+# Used new `inspect' module for better (?) determination of file<->module
+# mappings, line numbers, and source code.
+# Used new local trace function for faster (and better?) operation.
+# Removed "speed hack", which, as far as I can tell, meant that it would
+# ignore all files ??? (When I tried it, it would ignore only *most* of my
+# files. In any case with the speed hack removed in favor of actually
+# calling `Ignore.names()', it ignores only those files that I told it to
+# ignore, so I am happy.)
+# Rolled the `Coverage' class into `Trace', which now does either tracing or
+# counting or both according to constructor flags.
+# Moved the construction of the `Ignore' object inside the constructor of
+# `Trace', simplifying usage.
+# Changed function `create_results_log()' into method
+# `CoverageResults.write_results()'.
+# Add new mode "countfuncs" which is faster and which just reports which
+# functions were invoked.
+
+# Made `write_results' create `coverdir' if it doesn't already exist.
+# Moved the `run' funcs into `Trace' for simpler usage.
+# Use pickle instead of marshal for persistence.
+#
# Summary of recent changes:
# Support for files with the same basename (submodules in packages)
# Expanded the idea of how to ignore files or modules
@@ -42,72 +73,73 @@ 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 (still more complicated than it should be)
- # create an Ignore option, telling it what you want to ignore
- ignore = trace.Ignore(dirs = [sys.prefix, sys.exec_prefix])
- # create a Coverage object, telling it what to ignore
- coverage = trace.Coverage(ignore)
+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.trace, 'main()')
-
+ trace.run(coverage.globaltrace, 'main()')
# make a report, telling it where you want output
- t = trace.create_results_log(coverage.results(),
- '/usr/local/Automatrix/concerts/coverage')
- show_missing = 1)
-
- The Trace class can be instantited instead of the Coverage class if
- runtime display of executable lines is desired instead of statement
- converage measurement.
+ trace.print_results(show_missing=1)
"""
-import sys, os, string, marshal, tempfile, copy, operator
+import sys, os, string, tempfile, types, copy, operator, inspect, exceptions, marshal
+try:
+ import cPickle
+ pickle = cPickle
+except ImportError:
+ import pickle
+
+true = 1
+false = None
+
+# 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]
-Execution:
- --help Display this help then exit.
- --version Output version information then exit.
- -t,--trace Print the line to be executed to sys.stdout.
- -c,--count Count the number of times a line is executed.
- Results are written in the results file, if given.
- -r,--report Generate a report from a results file; do not
- execute any code.
- (One of `-t', `-c' or `-r' must be specified)
- -s,--summary Generate a brief summary for each file. (Can only
- be used with -c or -r.)
-
-I/O:
- -f,--file= File name for accumulating results over several runs.
- (No file name means do not archive results)
- -d,--logdir= Directory to use when writing annotated log files.
- Log files are the module __name__ with `.` replaced
- by os.sep and with '.pyl' added.
- -m,--missing Annotate all executable lines which were not executed
- with a '>>>>>> '.
- -R,--no-report Do not generate the annotated reports. Useful if
- you want to accumulate several over tests.
- -C,--coverdir= Generate .cover files in this directory
-
-Selection: Do not trace or log lines from ...
- --ignore-module=[string] modules with the given __name__, and submodules
- of that module
- --ignore-dir=[string] files in the stated directory (multiple
- directories can be joined by os.pathsep)
-
- The selection options can be listed multiple times to ignore different
-modules.
+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]
@@ -153,58 +185,190 @@ class Ignore:
self._ignore[modulename] = 0
return 0
-def run(trace, cmd):
- import __main__
- dict = __main__.__dict__
- sys.settrace(trace)
- try:
- exec cmd in dict, dict
- finally:
- sys.settrace(None)
-
-def runctx(trace, cmd, globals=None, locals=None):
- if globals is None: globals = {}
- if locals is None: locals = {}
- sys.settrace(trace)
- try:
- exec cmd in dict, dict
- finally:
- sys.settrace(None)
-
-def runfunc(trace, func, *args, **kw):
- result = None
- sys.settrace(trace)
- try:
- result = apply(func, args, kw)
- finally:
- sys.settrace(None)
- return result
-
-
class CoverageResults:
- def __init__(self, counts = {}, modules = {}):
- self.counts = counts.copy() # map (filename, lineno) to count
- self.modules = modules.copy() # map filenames to modules
+ 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
- modules = self.modules
- other_modules = other.modules
+ other_calledfuncs = other.calledfuncs
for key in other_counts.keys():
- counts[key] = counts.get(key, 0) + other_counts[key]
-
- for key in other_modules.keys():
- if modules.has_key(key):
- # make sure they point to the same file
- assert modules[key] == other_modules[key], \
- "Strange! filename %s has two different module " \
- "names: %s and %s" % \
- (key, modules[key], other_modules[key])
+ 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:
- modules[key] = other_modules[key]
+ 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 \
+ string.find(lines[i],
+ string.join(['#pragma', 'NO COVER'])) == -1:
+ outfile.write('>>>>>> ')
+ else:
+ outfile.write(' '*7)
+ n_lines = n_lines + 1
+ outfile.write(string.expandtabs(lines[i], 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)
# Given a code string, return the SET_LINENO information
def _find_LINENO_from_string(co_code):
@@ -286,267 +450,182 @@ def commonprefix(dirs):
break
return os.sep.join(prefix)
-def create_results_log(results, dirname = ".", show_missing = 1,
- save_counts = 0, summary = 0, coverdir = None):
- import re
- # turn the counts data ("(filename, lineno) = count") into something
- # accessible on a per-file basis
- per_file = {}
- for filename, lineno in results.counts.keys():
- lines_hit = per_file[filename] = per_file.get(filename, {})
- lines_hit[lineno] = results.counts[(filename, lineno)]
-
- # try and merge existing counts and modules file from dirname
- try:
- counts = marshal.load(open(os.path.join(dirname, "counts")))
- modules = marshal.load(open(os.path.join(dirname, "modules")))
- results.update(results.__class__(counts, modules))
- except IOError:
- pass
-
- # 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 = os.path.split(results.modules[key])[1]
-
- if filename.endswith(".pyc") or filename.endswith(".pyo"):
- filename = filename[:-1]
-
- if coverdir:
- listfilename = os.path.join(coverdir, modulename + ".cover")
+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:
- # XXX this is almost certainly not portable!!!
- fndir = os.path.dirname(filename)
- if os.path.isabs(filename):
- coverpath = fndir
- else:
- coverpath = os.path.join(dirname, fndir)
-
- # 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(coverpath, finalname + ".cover")
- else:
- listfilename = os.path.join(coverpath, modulename + ".cover")
-
- # Get the original lines from the .py file
+ # 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:
- lines = open(filename, 'r').readlines()
- except IOError, err:
- print >> sys.stderr, "trace: Could not open %s for reading " \
- "because: %s - skipping" % (`filename`, err)
- continue
+ 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(gself.lobaltrace)
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
+ exec cmd in dict, dict
+ finally:
+ if not self.donothing:
+ sys.settrace(None)
- # 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(' . ')
+ 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:
- # 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 \
- string.find(lines[i],
- string.join(['#pragma', 'NO COVER'])) == -1:
- outfile.write('>>>>>> ')
- else:
- outfile.write(' '*7)
- n_lines = n_lines + 1
- outfile.write(string.expandtabs(lines[i], 8))
-
- outfile.close()
-
- if summary and n_lines:
- percent = int(100 * n_hits / n_lines)
- sums[modulename] = n_lines, percent, modulename, filename
-
- if save_counts:
- # try and store counts and module info into dirname
- try:
- marshal.dump(results.counts,
- open(os.path.join(dirname, "counts"), "w"))
- marshal.dump(results.modules,
- open(os.path.join(dirname, "modules"), "w"))
- except IOError, err:
- sys.stderr.write("cannot save counts/modules " \
- "files because %s" % err)
-
- 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]
-
-# There is a lot of code shared between these two classes even though
-# it is straightforward to make a super class to share code. However,
-# for performance reasons (remember, this is called at every step) I
-# wanted to keep everything to a single function call. Also, by
-# staying within a single scope, I don't have to temporarily nullify
-# sys.settrace, which would slow things down even more.
-
-class Coverage:
- def __init__(self, ignore = Ignore()):
- self.ignore = ignore
- self.ignore_names = ignore._ignore # access ignore's cache (speed hack)
-
- self.counts = {} # keys are (filename, linenumber)
- self.modules = {} # maps filename -> module name
-
- def trace(self, frame, why, arg):
- if why == 'line':
- # something is fishy about getting the file name
- filename = frame.f_globals.get("__file__", None)
- if filename is None:
- filename = frame.f_code.co_filename
- try:
- modulename = frame.f_globals["__name__"]
- except KeyError:
- # PyRun_String() for example
- # XXX what to do?
modulename = None
-
- # We do this next block to keep from having to make methods
- # calls, which also requires resetting the trace
- ignore_it = self.ignore_names.get(modulename, -1)
- if ignore_it == -1: # unknown filename
- sys.settrace(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 DEBUG_MODE and not filename:
+ # print "%s.globaltrace(frame: %s, why: %s, arg: %s): filename: %s, lineno: %s, funcname: %s, context: %s, lineindex: %s\n" % (self, frame, why, arg, filename, lineno, funcname, context, lineindex,)
+ if filename:
+ modulename = inspect.getmodulename(filename)
ignore_it = self.ignore.names(filename, modulename)
- sys.settrace(self.trace)
-
- # record the module name for every file
- self.modules[filename] = modulename
-
- if not ignore_it:
- lineno = frame.f_lineno
-
- # record the file name and line number of every trace
- key = (filename, lineno)
- self.counts[key] = self.counts.get(key, 0) + 1
-
- return self.trace
-
- def results(self):
- return CoverageResults(self.counts, self.modules)
-
-class Trace:
- def __init__(self, ignore = Ignore()):
- self.ignore = ignore
- self.ignore_names = ignore._ignore # access ignore's cache (speed hack)
-
- self.files = {'<string>': None} # stores lines from the .py file,
- # or None
+ # if DEBUG_MODE and not self.blabbed.has_key((filename, modulename,)):
+ # self.blabbed[(filename, modulename,)] = None
+ # print "%s.globaltrace(frame: %s, why: %s, arg: %s, filename: %s, modulename: %s, ignore_it: %s\n" % (self, frame, why, arg, filename, modulename, ignore_it,)
+ if not ignore_it:
+ if self.trace:
+ print " --- modulename: %s, funcname: %s" % (modulename, funcname,)
+ # if DEBUG_MODE:
+ # print "%s.globaltrace(frame: %s, why: %s, arg: %s, filename: %s, modulename: %s, ignore_it: %s -- about to localtrace\n" % (self, frame, why, arg, filename, modulename, ignore_it,)
+ return self.localtrace
+ else:
+ # XXX why no filename?
+ return None
- def trace(self, frame, why, arg):
+ def localtrace_trace_and_count(self, frame, why, arg):
if why == 'line':
- filename = frame.f_code.co_filename
+ # 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:
- modulename = frame.f_globals["__name__"]
- except KeyError:
- # PyRun_String() for example
- # XXX what to do?
- modulename = None
-
- # We do this next block to keep from having to make methods
- # calls, which also requires resetting the trace
- ignore_it = self.ignore_names.get(modulename, -1)
- if ignore_it == -1: # unknown filename
- sys.settrace(None)
- ignore_it = self.ignore.names(filename, modulename)
- sys.settrace(self.trace)
-
- if not ignore_it:
- lineno = frame.f_lineno
- files = self.files
-
- if filename != '<string>' and not files.has_key(filename):
- files[filename] = map(string.rstrip,
- open(filename).readlines())
-
- # If you want to see filenames (the original behaviour), try:
- # modulename = filename
- # or, prettier but confusing when several files have the
- # same name
- # modulename = os.path.basename(filename)
+ 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
- if files[filename] != None:
- print '%s(%d): %s' % (os.path.basename(filename), lineno,
- files[filename][lineno-1])
- else:
- print '%s(%d): ??' % (modulename, lineno)
+ 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)
+ # if DEBUG_MODE:
+ # print "%s.localtrace_trace(frame: %s, why: %s, arg: %s); filename: %s, lineno: %s, funcname: %s, context: %s, lineindex: %s\n" % (self, frame, why, arg, filename, lineno, funcname, context, lineindex,)
+ # 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
- return self.trace
+ def localtrace_count(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)
+ 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):
- print >> sys.stderr, "%s: %s" % (sys.argv[0], msg)
+ sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
sys.exit(1)
-def main(argv = None):
+def main(argv=None):
import getopt
if argv is None:
argv = sys.argv
try:
- opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:",
+ opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:l",
["help", "version", "trace", "count",
"report", "no-report",
- "file=", "logdir=", "missing",
+ "file=", "missing",
"ignore-module=", "ignore-dir=",
- "coverdir="])
+ "coverdir=", "listfuncs",])
except getopt.error, msg:
- print >> sys.stderr, "%s: %s" % (sys.argv[0], msg)
- print >> sys.stderr, "Try `%s --help' for more information" \
- % sys.argv[0]
+ 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
@@ -554,12 +633,12 @@ def main(argv = None):
report = 0
no_report = 0
counts_file = None
- logdir = "."
missing = 0
ignore_modules = []
ignore_dirs = []
coverdir = None
summary = 0
+ listfuncs = false
for opt, val in opts:
if opt == "--help":
@@ -570,6 +649,10 @@ def main(argv = None):
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
@@ -590,10 +673,6 @@ def main(argv = None):
counts_file = val
continue
- if opt == "-d" or opt == "--logdir":
- logdir = val
- continue
-
if opt == "-m" or opt == "--missing":
missing = 1
continue
@@ -627,88 +706,43 @@ def main(argv = None):
assert 0, "Should never get here"
- if len(prog_argv) == 0:
- _err_exit("missing name of file to run")
-
- if count + trace + report > 1:
- _err_exit("can only specify one of --trace, --count or --report")
-
- if count + trace + report == 0:
- _err_exit("must specify one of --trace, --count or --report")
+ if listfuncs and (count or trace):
+ _err_exit("cannot specify both --listfuncs and (--trace or --count)")
- if report and counts_file is None:
- _err_exit("--report requires a --file")
+ 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 logdir is not None:
- # warn if the directory doesn't exist, but keep on going
- # (is this the correct behaviour?)
- if not os.path.isdir(logdir):
- sys.stderr.write(
- "trace: WARNING, --logdir directory %s is not available\n" %
- `logdir`)
+ if report and not counts_file:
+ _err_exit("--report requires a --file")
- sys.argv = prog_argv
- progname = prog_argv[0]
- if eval(sys.version[:3])>1.3:
- sys.path[0] = os.path.split(progname)[0] # ???
+ if no_report and len(prog_argv) == 0:
+ _err_exit("missing name of file to run")
# everything is ready
- ignore = Ignore(ignore_modules, ignore_dirs)
- if trace:
- t = Trace(ignore)
- try:
- run(t.trace, 'execfile(' + `progname` + ')')
- except IOError, err:
- _err_exit("Cannot run file %s because: %s" % \
- (`sys.argv[0]`, err))
+ 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]
+ if eval(sys.version[:3])>1.3:
+ sys.path[0] = os.path.split(progname)[0] # ???
- elif count:
- t = Coverage(ignore)
+ t = Trace(count, trace, countfuncs=listfuncs, ignoremods=ignore_modules, ignoredirs=ignore_dirs, infile=counts_file, outfile=counts_file)
try:
- run(t.trace, 'execfile(' + `progname` + ')')
+ t.run('execfile(' + `progname` + ')')
except IOError, err:
- _err_exit("Cannot run file %s because: %s" % \
- (`sys.argv[0]`, err))
+ _err_exit("Cannot run file %s because: %s" % (`sys.argv[0]`, err))
except SystemExit:
pass
results = t.results()
- # Add another lookup from the program's file name to its import name
- # This give the right results, but I'm not sure why ...
- results.modules[progname] = os.path.splitext(progname)[0]
-
- if counts_file:
- # add in archived data, if available
- try:
- old_counts, old_modules = marshal.load(open(counts_file, 'rb'))
- except IOError:
- pass
- else:
- results.update(CoverageResults(old_counts, old_modules))
if not no_report:
- create_results_log(results, logdir, missing,
- summary=summary, coverdir=coverdir)
-
- if counts_file:
- try:
- marshal.dump( (results.counts, results.modules),
- open(counts_file, 'wb'))
- except IOError, err:
- _err_exit("Cannot save counts file %s because: %s" % \
- (`counts_file`, err))
-
- elif report:
- old_counts, old_modules = marshal.load(open(counts_file, 'rb'))
- results = CoverageResults(old_counts, old_modules)
- create_results_log(results, logdir, missing,
- summary=summary, coverdir=coverdir)
-
- else:
- assert 0, "Should never get here"
+ results.write_results(missing, summary=summary, coverdir=coverdir)
if __name__=='__main__':
main()