python-2.5.2/win32/Lib/trace.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     1 #!/usr/bin/env python
       
     2 
       
     3 # portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
       
     4 # err...  reserved and offered to the public under the terms of the
       
     5 # Python 2.2 license.
       
     6 # Author: Zooko O'Whielacronx
       
     7 # http://zooko.com/
       
     8 # mailto:zooko@zooko.com
       
     9 #
       
    10 # Copyright 2000, Mojam Media, Inc., all rights reserved.
       
    11 # Author: Skip Montanaro
       
    12 #
       
    13 # Copyright 1999, Bioreason, Inc., all rights reserved.
       
    14 # Author: Andrew Dalke
       
    15 #
       
    16 # Copyright 1995-1997, Automatrix, Inc., all rights reserved.
       
    17 # Author: Skip Montanaro
       
    18 #
       
    19 # Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
       
    20 #
       
    21 #
       
    22 # Permission to use, copy, modify, and distribute this Python software and
       
    23 # its associated documentation for any purpose without fee is hereby
       
    24 # granted, provided that the above copyright notice appears in all copies,
       
    25 # and that both that copyright notice and this permission notice appear in
       
    26 # supporting documentation, and that the name of neither Automatrix,
       
    27 # Bioreason or Mojam Media be used in advertising or publicity pertaining to
       
    28 # distribution of the software without specific, written prior permission.
       
    29 #
       
    30 """program/module to trace Python program or function execution
       
    31 
       
    32 Sample use, command line:
       
    33   trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
       
    34   trace.py -t --ignore-dir '$prefix' spam.py eggs
       
    35   trace.py --trackcalls spam.py eggs
       
    36 
       
    37 Sample use, programmatically
       
    38   import sys
       
    39 
       
    40   # create a Trace object, telling it what to ignore, and whether to
       
    41   # do tracing or line-counting or both.
       
    42   tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0,
       
    43                     count=1)
       
    44   # run the new command using the given tracer
       
    45   tracer.run('main()')
       
    46   # make a report, placing output in /tmp
       
    47   r = tracer.results()
       
    48   r.write_results(show_missing=True, coverdir="/tmp")
       
    49 """
       
    50 
       
    51 import linecache
       
    52 import os
       
    53 import re
       
    54 import sys
       
    55 import threading
       
    56 import token
       
    57 import tokenize
       
    58 import types
       
    59 import gc
       
    60 
       
    61 try:
       
    62     import cPickle
       
    63     pickle = cPickle
       
    64 except ImportError:
       
    65     import pickle
       
    66 
       
    67 def usage(outfile):
       
    68     outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
       
    69 
       
    70 Meta-options:
       
    71 --help                Display this help then exit.
       
    72 --version             Output version information then exit.
       
    73 
       
    74 Otherwise, exactly one of the following three options must be given:
       
    75 -t, --trace           Print each line to sys.stdout before it is executed.
       
    76 -c, --count           Count the number of times each line is executed
       
    77                       and write the counts to <module>.cover for each
       
    78                       module executed, in the module's directory.
       
    79                       See also `--coverdir', `--file', `--no-report' below.
       
    80 -l, --listfuncs       Keep track of which functions are executed at least
       
    81                       once and write the results to sys.stdout after the
       
    82                       program exits.
       
    83 -T, --trackcalls      Keep track of caller/called pairs and write the
       
    84                       results to sys.stdout after the program exits.
       
    85 -r, --report          Generate a report from a counts file; do not execute
       
    86                       any code.  `--file' must specify the results file to
       
    87                       read, which must have been created in a previous run
       
    88                       with `--count --file=FILE'.
       
    89 
       
    90 Modifiers:
       
    91 -f, --file=<file>     File to accumulate counts over several runs.
       
    92 -R, --no-report       Do not generate the coverage report files.
       
    93                       Useful if you want to accumulate over several runs.
       
    94 -C, --coverdir=<dir>  Directory where the report files.  The coverage
       
    95                       report for <package>.<module> is written to file
       
    96                       <dir>/<package>/<module>.cover.
       
    97 -m, --missing         Annotate executable lines that were not executed
       
    98                       with '>>>>>> '.
       
    99 -s, --summary         Write a brief summary on stdout for each file.
       
   100                       (Can only be used with --count or --report.)
       
   101 
       
   102 Filters, may be repeated multiple times:
       
   103 --ignore-module=<mod> Ignore the given module and its submodules
       
   104                       (if it is a package).
       
   105 --ignore-dir=<dir>    Ignore files in the given directory (multiple
       
   106                       directories can be joined by os.pathsep).
       
   107 """ % sys.argv[0])
       
   108 
       
   109 PRAGMA_NOCOVER = "#pragma NO COVER"
       
   110 
       
   111 # Simple rx to find lines with no code.
       
   112 rx_blank = re.compile(r'^\s*(#.*)?$')
       
   113 
       
   114 class Ignore:
       
   115     def __init__(self, modules = None, dirs = None):
       
   116         self._mods = modules or []
       
   117         self._dirs = dirs or []
       
   118 
       
   119         self._dirs = map(os.path.normpath, self._dirs)
       
   120         self._ignore = { '<string>': 1 }
       
   121 
       
   122     def names(self, filename, modulename):
       
   123         if self._ignore.has_key(modulename):
       
   124             return self._ignore[modulename]
       
   125 
       
   126         # haven't seen this one before, so see if the module name is
       
   127         # on the ignore list.  Need to take some care since ignoring
       
   128         # "cmp" musn't mean ignoring "cmpcache" but ignoring
       
   129         # "Spam" must also mean ignoring "Spam.Eggs".
       
   130         for mod in self._mods:
       
   131             if mod == modulename:  # Identical names, so ignore
       
   132                 self._ignore[modulename] = 1
       
   133                 return 1
       
   134             # check if the module is a proper submodule of something on
       
   135             # the ignore list
       
   136             n = len(mod)
       
   137             # (will not overflow since if the first n characters are the
       
   138             # same and the name has not already occurred, then the size
       
   139             # of "name" is greater than that of "mod")
       
   140             if mod == modulename[:n] and modulename[n] == '.':
       
   141                 self._ignore[modulename] = 1
       
   142                 return 1
       
   143 
       
   144         # Now check that __file__ isn't in one of the directories
       
   145         if filename is None:
       
   146             # must be a built-in, so we must ignore
       
   147             self._ignore[modulename] = 1
       
   148             return 1
       
   149 
       
   150         # Ignore a file when it contains one of the ignorable paths
       
   151         for d in self._dirs:
       
   152             # The '+ os.sep' is to ensure that d is a parent directory,
       
   153             # as compared to cases like:
       
   154             #  d = "/usr/local"
       
   155             #  filename = "/usr/local.py"
       
   156             # or
       
   157             #  d = "/usr/local.py"
       
   158             #  filename = "/usr/local.py"
       
   159             if filename.startswith(d + os.sep):
       
   160                 self._ignore[modulename] = 1
       
   161                 return 1
       
   162 
       
   163         # Tried the different ways, so we don't ignore this module
       
   164         self._ignore[modulename] = 0
       
   165         return 0
       
   166 
       
   167 def modname(path):
       
   168     """Return a plausible module name for the patch."""
       
   169 
       
   170     base = os.path.basename(path)
       
   171     filename, ext = os.path.splitext(base)
       
   172     return filename
       
   173 
       
   174 def fullmodname(path):
       
   175     """Return a plausible module name for the path."""
       
   176 
       
   177     # If the file 'path' is part of a package, then the filename isn't
       
   178     # enough to uniquely identify it.  Try to do the right thing by
       
   179     # looking in sys.path for the longest matching prefix.  We'll
       
   180     # assume that the rest is the package name.
       
   181 
       
   182     comparepath = os.path.normcase(path)
       
   183     longest = ""
       
   184     for dir in sys.path:
       
   185         dir = os.path.normcase(dir)
       
   186         if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep:
       
   187             if len(dir) > len(longest):
       
   188                 longest = dir
       
   189 
       
   190     if longest:
       
   191         base = path[len(longest) + 1:]
       
   192     else:
       
   193         base = path
       
   194     base = base.replace(os.sep, ".")
       
   195     if os.altsep:
       
   196         base = base.replace(os.altsep, ".")
       
   197     filename, ext = os.path.splitext(base)
       
   198     return filename
       
   199 
       
   200 class CoverageResults:
       
   201     def __init__(self, counts=None, calledfuncs=None, infile=None,
       
   202                  callers=None, outfile=None):
       
   203         self.counts = counts
       
   204         if self.counts is None:
       
   205             self.counts = {}
       
   206         self.counter = self.counts.copy() # map (filename, lineno) to count
       
   207         self.calledfuncs = calledfuncs
       
   208         if self.calledfuncs is None:
       
   209             self.calledfuncs = {}
       
   210         self.calledfuncs = self.calledfuncs.copy()
       
   211         self.callers = callers
       
   212         if self.callers is None:
       
   213             self.callers = {}
       
   214         self.callers = self.callers.copy()
       
   215         self.infile = infile
       
   216         self.outfile = outfile
       
   217         if self.infile:
       
   218             # Try to merge existing counts file.
       
   219             try:
       
   220                 counts, calledfuncs, callers = \
       
   221                         pickle.load(open(self.infile, 'rb'))
       
   222                 self.update(self.__class__(counts, calledfuncs, callers))
       
   223             except (IOError, EOFError, ValueError), err:
       
   224                 print >> sys.stderr, ("Skipping counts file %r: %s"
       
   225                                       % (self.infile, err))
       
   226 
       
   227     def update(self, other):
       
   228         """Merge in the data from another CoverageResults"""
       
   229         counts = self.counts
       
   230         calledfuncs = self.calledfuncs
       
   231         callers = self.callers
       
   232         other_counts = other.counts
       
   233         other_calledfuncs = other.calledfuncs
       
   234         other_callers = other.callers
       
   235 
       
   236         for key in other_counts.keys():
       
   237             counts[key] = counts.get(key, 0) + other_counts[key]
       
   238 
       
   239         for key in other_calledfuncs.keys():
       
   240             calledfuncs[key] = 1
       
   241 
       
   242         for key in other_callers.keys():
       
   243             callers[key] = 1
       
   244 
       
   245     def write_results(self, show_missing=True, summary=False, coverdir=None):
       
   246         """
       
   247         @param coverdir
       
   248         """
       
   249         if self.calledfuncs:
       
   250             print
       
   251             print "functions called:"
       
   252             calls = self.calledfuncs.keys()
       
   253             calls.sort()
       
   254             for filename, modulename, funcname in calls:
       
   255                 print ("filename: %s, modulename: %s, funcname: %s"
       
   256                        % (filename, modulename, funcname))
       
   257 
       
   258         if self.callers:
       
   259             print
       
   260             print "calling relationships:"
       
   261             calls = self.callers.keys()
       
   262             calls.sort()
       
   263             lastfile = lastcfile = ""
       
   264             for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) in calls:
       
   265                 if pfile != lastfile:
       
   266                     print
       
   267                     print "***", pfile, "***"
       
   268                     lastfile = pfile
       
   269                     lastcfile = ""
       
   270                 if cfile != pfile and lastcfile != cfile:
       
   271                     print "  -->", cfile
       
   272                     lastcfile = cfile
       
   273                 print "    %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc)
       
   274 
       
   275         # turn the counts data ("(filename, lineno) = count") into something
       
   276         # accessible on a per-file basis
       
   277         per_file = {}
       
   278         for filename, lineno in self.counts.keys():
       
   279             lines_hit = per_file[filename] = per_file.get(filename, {})
       
   280             lines_hit[lineno] = self.counts[(filename, lineno)]
       
   281 
       
   282         # accumulate summary info, if needed
       
   283         sums = {}
       
   284 
       
   285         for filename, count in per_file.iteritems():
       
   286             # skip some "files" we don't care about...
       
   287             if filename == "<string>":
       
   288                 continue
       
   289             if filename.startswith("<doctest "):
       
   290                 continue
       
   291 
       
   292             if filename.endswith((".pyc", ".pyo")):
       
   293                 filename = filename[:-1]
       
   294 
       
   295             if coverdir is None:
       
   296                 dir = os.path.dirname(os.path.abspath(filename))
       
   297                 modulename = modname(filename)
       
   298             else:
       
   299                 dir = coverdir
       
   300                 if not os.path.exists(dir):
       
   301                     os.makedirs(dir)
       
   302                 modulename = fullmodname(filename)
       
   303 
       
   304             # If desired, get a list of the line numbers which represent
       
   305             # executable content (returned as a dict for better lookup speed)
       
   306             if show_missing:
       
   307                 lnotab = find_executable_linenos(filename)
       
   308             else:
       
   309                 lnotab = {}
       
   310 
       
   311             source = linecache.getlines(filename)
       
   312             coverpath = os.path.join(dir, modulename + ".cover")
       
   313             n_hits, n_lines = self.write_results_file(coverpath, source,
       
   314                                                       lnotab, count)
       
   315 
       
   316             if summary and n_lines:
       
   317                 percent = int(100 * n_hits / n_lines)
       
   318                 sums[modulename] = n_lines, percent, modulename, filename
       
   319 
       
   320         if summary and sums:
       
   321             mods = sums.keys()
       
   322             mods.sort()
       
   323             print "lines   cov%   module   (path)"
       
   324             for m in mods:
       
   325                 n_lines, percent, modulename, filename = sums[m]
       
   326                 print "%5d   %3d%%   %s   (%s)" % sums[m]
       
   327 
       
   328         if self.outfile:
       
   329             # try and store counts and module info into self.outfile
       
   330             try:
       
   331                 pickle.dump((self.counts, self.calledfuncs, self.callers),
       
   332                             open(self.outfile, 'wb'), 1)
       
   333             except IOError, err:
       
   334                 print >> sys.stderr, "Can't save counts files because %s" % err
       
   335 
       
   336     def write_results_file(self, path, lines, lnotab, lines_hit):
       
   337         """Return a coverage results file in path."""
       
   338 
       
   339         try:
       
   340             outfile = open(path, "w")
       
   341         except IOError, err:
       
   342             print >> sys.stderr, ("trace: Could not open %r for writing: %s"
       
   343                                   "- skipping" % (path, err))
       
   344             return 0, 0
       
   345 
       
   346         n_lines = 0
       
   347         n_hits = 0
       
   348         for i, line in enumerate(lines):
       
   349             lineno = i + 1
       
   350             # do the blank/comment match to try to mark more lines
       
   351             # (help the reader find stuff that hasn't been covered)
       
   352             if lineno in lines_hit:
       
   353                 outfile.write("%5d: " % lines_hit[lineno])
       
   354                 n_hits += 1
       
   355                 n_lines += 1
       
   356             elif rx_blank.match(line):
       
   357                 outfile.write("       ")
       
   358             else:
       
   359                 # lines preceded by no marks weren't hit
       
   360                 # Highlight them if so indicated, unless the line contains
       
   361                 # #pragma: NO COVER
       
   362                 if lineno in lnotab and not PRAGMA_NOCOVER in lines[i]:
       
   363                     outfile.write(">>>>>> ")
       
   364                     n_lines += 1
       
   365                 else:
       
   366                     outfile.write("       ")
       
   367             outfile.write(lines[i].expandtabs(8))
       
   368         outfile.close()
       
   369 
       
   370         return n_hits, n_lines
       
   371 
       
   372 def find_lines_from_code(code, strs):
       
   373     """Return dict where keys are lines in the line number table."""
       
   374     linenos = {}
       
   375 
       
   376     line_increments = [ord(c) for c in code.co_lnotab[1::2]]
       
   377     table_length = len(line_increments)
       
   378     docstring = False
       
   379 
       
   380     lineno = code.co_firstlineno
       
   381     for li in line_increments:
       
   382         lineno += li
       
   383         if lineno not in strs:
       
   384             linenos[lineno] = 1
       
   385 
       
   386     return linenos
       
   387 
       
   388 def find_lines(code, strs):
       
   389     """Return lineno dict for all code objects reachable from code."""
       
   390     # get all of the lineno information from the code of this scope level
       
   391     linenos = find_lines_from_code(code, strs)
       
   392 
       
   393     # and check the constants for references to other code objects
       
   394     for c in code.co_consts:
       
   395         if isinstance(c, types.CodeType):
       
   396             # find another code object, so recurse into it
       
   397             linenos.update(find_lines(c, strs))
       
   398     return linenos
       
   399 
       
   400 def find_strings(filename):
       
   401     """Return a dict of possible docstring positions.
       
   402 
       
   403     The dict maps line numbers to strings.  There is an entry for
       
   404     line that contains only a string or a part of a triple-quoted
       
   405     string.
       
   406     """
       
   407     d = {}
       
   408     # If the first token is a string, then it's the module docstring.
       
   409     # Add this special case so that the test in the loop passes.
       
   410     prev_ttype = token.INDENT
       
   411     f = open(filename)
       
   412     for ttype, tstr, start, end, line in tokenize.generate_tokens(f.readline):
       
   413         if ttype == token.STRING:
       
   414             if prev_ttype == token.INDENT:
       
   415                 sline, scol = start
       
   416                 eline, ecol = end
       
   417                 for i in range(sline, eline + 1):
       
   418                     d[i] = 1
       
   419         prev_ttype = ttype
       
   420     f.close()
       
   421     return d
       
   422 
       
   423 def find_executable_linenos(filename):
       
   424     """Return dict where keys are line numbers in the line number table."""
       
   425     try:
       
   426         prog = open(filename, "rU").read()
       
   427     except IOError, err:
       
   428         print >> sys.stderr, ("Not printing coverage data for %r: %s"
       
   429                               % (filename, err))
       
   430         return {}
       
   431     code = compile(prog, filename, "exec")
       
   432     strs = find_strings(filename)
       
   433     return find_lines(code, strs)
       
   434 
       
   435 class Trace:
       
   436     def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
       
   437                  ignoremods=(), ignoredirs=(), infile=None, outfile=None):
       
   438         """
       
   439         @param count true iff it should count number of times each
       
   440                      line is executed
       
   441         @param trace true iff it should print out each line that is
       
   442                      being counted
       
   443         @param countfuncs true iff it should just output a list of
       
   444                      (filename, modulename, funcname,) for functions
       
   445                      that were called at least once;  This overrides
       
   446                      `count' and `trace'
       
   447         @param ignoremods a list of the names of modules to ignore
       
   448         @param ignoredirs a list of the names of directories to ignore
       
   449                      all of the (recursive) contents of
       
   450         @param infile file from which to read stored counts to be
       
   451                      added into the results
       
   452         @param outfile file in which to write the results
       
   453         """
       
   454         self.infile = infile
       
   455         self.outfile = outfile
       
   456         self.ignore = Ignore(ignoremods, ignoredirs)
       
   457         self.counts = {}   # keys are (filename, linenumber)
       
   458         self.blabbed = {} # for debugging
       
   459         self.pathtobasename = {} # for memoizing os.path.basename
       
   460         self.donothing = 0
       
   461         self.trace = trace
       
   462         self._calledfuncs = {}
       
   463         self._callers = {}
       
   464         self._caller_cache = {}
       
   465         if countcallers:
       
   466             self.globaltrace = self.globaltrace_trackcallers
       
   467         elif countfuncs:
       
   468             self.globaltrace = self.globaltrace_countfuncs
       
   469         elif trace and count:
       
   470             self.globaltrace = self.globaltrace_lt
       
   471             self.localtrace = self.localtrace_trace_and_count
       
   472         elif trace:
       
   473             self.globaltrace = self.globaltrace_lt
       
   474             self.localtrace = self.localtrace_trace
       
   475         elif count:
       
   476             self.globaltrace = self.globaltrace_lt
       
   477             self.localtrace = self.localtrace_count
       
   478         else:
       
   479             # Ahem -- do nothing?  Okay.
       
   480             self.donothing = 1
       
   481 
       
   482     def run(self, cmd):
       
   483         import __main__
       
   484         dict = __main__.__dict__
       
   485         if not self.donothing:
       
   486             sys.settrace(self.globaltrace)
       
   487             threading.settrace(self.globaltrace)
       
   488         try:
       
   489             exec cmd in dict, dict
       
   490         finally:
       
   491             if not self.donothing:
       
   492                 sys.settrace(None)
       
   493                 threading.settrace(None)
       
   494 
       
   495     def runctx(self, cmd, globals=None, locals=None):
       
   496         if globals is None: globals = {}
       
   497         if locals is None: locals = {}
       
   498         if not self.donothing:
       
   499             sys.settrace(self.globaltrace)
       
   500             threading.settrace(self.globaltrace)
       
   501         try:
       
   502             exec cmd in globals, locals
       
   503         finally:
       
   504             if not self.donothing:
       
   505                 sys.settrace(None)
       
   506                 threading.settrace(None)
       
   507 
       
   508     def runfunc(self, func, *args, **kw):
       
   509         result = None
       
   510         if not self.donothing:
       
   511             sys.settrace(self.globaltrace)
       
   512         try:
       
   513             result = func(*args, **kw)
       
   514         finally:
       
   515             if not self.donothing:
       
   516                 sys.settrace(None)
       
   517         return result
       
   518 
       
   519     def file_module_function_of(self, frame):
       
   520         code = frame.f_code
       
   521         filename = code.co_filename
       
   522         if filename:
       
   523             modulename = modname(filename)
       
   524         else:
       
   525             modulename = None
       
   526 
       
   527         funcname = code.co_name
       
   528         clsname = None
       
   529         if code in self._caller_cache:
       
   530             if self._caller_cache[code] is not None:
       
   531                 clsname = self._caller_cache[code]
       
   532         else:
       
   533             self._caller_cache[code] = None
       
   534             ## use of gc.get_referrers() was suggested by Michael Hudson
       
   535             # all functions which refer to this code object
       
   536             funcs = [f for f in gc.get_referrers(code)
       
   537                          if hasattr(f, "func_doc")]
       
   538             # require len(func) == 1 to avoid ambiguity caused by calls to
       
   539             # new.function(): "In the face of ambiguity, refuse the
       
   540             # temptation to guess."
       
   541             if len(funcs) == 1:
       
   542                 dicts = [d for d in gc.get_referrers(funcs[0])
       
   543                              if isinstance(d, dict)]
       
   544                 if len(dicts) == 1:
       
   545                     classes = [c for c in gc.get_referrers(dicts[0])
       
   546                                    if hasattr(c, "__bases__")]
       
   547                     if len(classes) == 1:
       
   548                         # ditto for new.classobj()
       
   549                         clsname = str(classes[0])
       
   550                         # cache the result - assumption is that new.* is
       
   551                         # not called later to disturb this relationship
       
   552                         # _caller_cache could be flushed if functions in
       
   553                         # the new module get called.
       
   554                         self._caller_cache[code] = clsname
       
   555         if clsname is not None:
       
   556             # final hack - module name shows up in str(cls), but we've already
       
   557             # computed module name, so remove it
       
   558             clsname = clsname.split(".")[1:]
       
   559             clsname = ".".join(clsname)
       
   560             funcname = "%s.%s" % (clsname, funcname)
       
   561 
       
   562         return filename, modulename, funcname
       
   563 
       
   564     def globaltrace_trackcallers(self, frame, why, arg):
       
   565         """Handler for call events.
       
   566 
       
   567         Adds information about who called who to the self._callers dict.
       
   568         """
       
   569         if why == 'call':
       
   570             # XXX Should do a better job of identifying methods
       
   571             this_func = self.file_module_function_of(frame)
       
   572             parent_func = self.file_module_function_of(frame.f_back)
       
   573             self._callers[(parent_func, this_func)] = 1
       
   574 
       
   575     def globaltrace_countfuncs(self, frame, why, arg):
       
   576         """Handler for call events.
       
   577 
       
   578         Adds (filename, modulename, funcname) to the self._calledfuncs dict.
       
   579         """
       
   580         if why == 'call':
       
   581             this_func = self.file_module_function_of(frame)
       
   582             self._calledfuncs[this_func] = 1
       
   583 
       
   584     def globaltrace_lt(self, frame, why, arg):
       
   585         """Handler for call events.
       
   586 
       
   587         If the code block being entered is to be ignored, returns `None',
       
   588         else returns self.localtrace.
       
   589         """
       
   590         if why == 'call':
       
   591             code = frame.f_code
       
   592             filename = frame.f_globals.get('__file__', None)
       
   593             if filename:
       
   594                 # XXX modname() doesn't work right for packages, so
       
   595                 # the ignore support won't work right for packages
       
   596                 modulename = modname(filename)
       
   597                 if modulename is not None:
       
   598                     ignore_it = self.ignore.names(filename, modulename)
       
   599                     if not ignore_it:
       
   600                         if self.trace:
       
   601                             print (" --- modulename: %s, funcname: %s"
       
   602                                    % (modulename, code.co_name))
       
   603                         return self.localtrace
       
   604             else:
       
   605                 return None
       
   606 
       
   607     def localtrace_trace_and_count(self, frame, why, arg):
       
   608         if why == "line":
       
   609             # record the file name and line number of every trace
       
   610             filename = frame.f_code.co_filename
       
   611             lineno = frame.f_lineno
       
   612             key = filename, lineno
       
   613             self.counts[key] = self.counts.get(key, 0) + 1
       
   614 
       
   615             bname = os.path.basename(filename)
       
   616             print "%s(%d): %s" % (bname, lineno,
       
   617                                   linecache.getline(filename, lineno)),
       
   618         return self.localtrace
       
   619 
       
   620     def localtrace_trace(self, frame, why, arg):
       
   621         if why == "line":
       
   622             # record the file name and line number of every trace
       
   623             filename = frame.f_code.co_filename
       
   624             lineno = frame.f_lineno
       
   625 
       
   626             bname = os.path.basename(filename)
       
   627             print "%s(%d): %s" % (bname, lineno,
       
   628                                   linecache.getline(filename, lineno)),
       
   629         return self.localtrace
       
   630 
       
   631     def localtrace_count(self, frame, why, arg):
       
   632         if why == "line":
       
   633             filename = frame.f_code.co_filename
       
   634             lineno = frame.f_lineno
       
   635             key = filename, lineno
       
   636             self.counts[key] = self.counts.get(key, 0) + 1
       
   637         return self.localtrace
       
   638 
       
   639     def results(self):
       
   640         return CoverageResults(self.counts, infile=self.infile,
       
   641                                outfile=self.outfile,
       
   642                                calledfuncs=self._calledfuncs,
       
   643                                callers=self._callers)
       
   644 
       
   645 def _err_exit(msg):
       
   646     sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
       
   647     sys.exit(1)
       
   648 
       
   649 def main(argv=None):
       
   650     import getopt
       
   651 
       
   652     if argv is None:
       
   653         argv = sys.argv
       
   654     try:
       
   655         opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:msC:lT",
       
   656                                         ["help", "version", "trace", "count",
       
   657                                          "report", "no-report", "summary",
       
   658                                          "file=", "missing",
       
   659                                          "ignore-module=", "ignore-dir=",
       
   660                                          "coverdir=", "listfuncs",
       
   661                                          "trackcalls"])
       
   662 
       
   663     except getopt.error, msg:
       
   664         sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
       
   665         sys.stderr.write("Try `%s --help' for more information\n"
       
   666                          % sys.argv[0])
       
   667         sys.exit(1)
       
   668 
       
   669     trace = 0
       
   670     count = 0
       
   671     report = 0
       
   672     no_report = 0
       
   673     counts_file = None
       
   674     missing = 0
       
   675     ignore_modules = []
       
   676     ignore_dirs = []
       
   677     coverdir = None
       
   678     summary = 0
       
   679     listfuncs = False
       
   680     countcallers = False
       
   681 
       
   682     for opt, val in opts:
       
   683         if opt == "--help":
       
   684             usage(sys.stdout)
       
   685             sys.exit(0)
       
   686 
       
   687         if opt == "--version":
       
   688             sys.stdout.write("trace 2.0\n")
       
   689             sys.exit(0)
       
   690 
       
   691         if opt == "-T" or opt == "--trackcalls":
       
   692             countcallers = True
       
   693             continue
       
   694 
       
   695         if opt == "-l" or opt == "--listfuncs":
       
   696             listfuncs = True
       
   697             continue
       
   698 
       
   699         if opt == "-t" or opt == "--trace":
       
   700             trace = 1
       
   701             continue
       
   702 
       
   703         if opt == "-c" or opt == "--count":
       
   704             count = 1
       
   705             continue
       
   706 
       
   707         if opt == "-r" or opt == "--report":
       
   708             report = 1
       
   709             continue
       
   710 
       
   711         if opt == "-R" or opt == "--no-report":
       
   712             no_report = 1
       
   713             continue
       
   714 
       
   715         if opt == "-f" or opt == "--file":
       
   716             counts_file = val
       
   717             continue
       
   718 
       
   719         if opt == "-m" or opt == "--missing":
       
   720             missing = 1
       
   721             continue
       
   722 
       
   723         if opt == "-C" or opt == "--coverdir":
       
   724             coverdir = val
       
   725             continue
       
   726 
       
   727         if opt == "-s" or opt == "--summary":
       
   728             summary = 1
       
   729             continue
       
   730 
       
   731         if opt == "--ignore-module":
       
   732             ignore_modules.append(val)
       
   733             continue
       
   734 
       
   735         if opt == "--ignore-dir":
       
   736             for s in val.split(os.pathsep):
       
   737                 s = os.path.expandvars(s)
       
   738                 # should I also call expanduser? (after all, could use $HOME)
       
   739 
       
   740                 s = s.replace("$prefix",
       
   741                               os.path.join(sys.prefix, "lib",
       
   742                                            "python" + sys.version[:3]))
       
   743                 s = s.replace("$exec_prefix",
       
   744                               os.path.join(sys.exec_prefix, "lib",
       
   745                                            "python" + sys.version[:3]))
       
   746                 s = os.path.normpath(s)
       
   747                 ignore_dirs.append(s)
       
   748             continue
       
   749 
       
   750         assert 0, "Should never get here"
       
   751 
       
   752     if listfuncs and (count or trace):
       
   753         _err_exit("cannot specify both --listfuncs and (--trace or --count)")
       
   754 
       
   755     if not (count or trace or report or listfuncs or countcallers):
       
   756         _err_exit("must specify one of --trace, --count, --report, "
       
   757                   "--listfuncs, or --trackcalls")
       
   758 
       
   759     if report and no_report:
       
   760         _err_exit("cannot specify both --report and --no-report")
       
   761 
       
   762     if report and not counts_file:
       
   763         _err_exit("--report requires a --file")
       
   764 
       
   765     if no_report and len(prog_argv) == 0:
       
   766         _err_exit("missing name of file to run")
       
   767 
       
   768     # everything is ready
       
   769     if report:
       
   770         results = CoverageResults(infile=counts_file, outfile=counts_file)
       
   771         results.write_results(missing, summary=summary, coverdir=coverdir)
       
   772     else:
       
   773         sys.argv = prog_argv
       
   774         progname = prog_argv[0]
       
   775         sys.path[0] = os.path.split(progname)[0]
       
   776 
       
   777         t = Trace(count, trace, countfuncs=listfuncs,
       
   778                   countcallers=countcallers, ignoremods=ignore_modules,
       
   779                   ignoredirs=ignore_dirs, infile=counts_file,
       
   780                   outfile=counts_file)
       
   781         try:
       
   782             t.run('execfile(%r)' % (progname,))
       
   783         except IOError, err:
       
   784             _err_exit("Cannot run file %r because: %s" % (sys.argv[0], err))
       
   785         except SystemExit:
       
   786             pass
       
   787 
       
   788         results = t.results()
       
   789 
       
   790         if not no_report:
       
   791             results.write_results(missing, summary=summary, coverdir=coverdir)
       
   792 
       
   793 if __name__=='__main__':
       
   794     main()