home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / lib / python2.6 / trace.py < prev    next >
Encoding:
Python Source  |  2010-12-26  |  29.6 KB  |  825 lines

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