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