home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 April / PCWorld_2001-04_cd.bin / Software / TemaCD / webclean / !!!python!!! / BeOpen-Python-2.0.exe / TRACE.PY < prev    next >
Encoding:
Python Source  |  2000-09-28  |  24.2 KB  |  661 lines

  1. #!/usr/bin/env python
  2.  
  3. # Copyright 2000, Mojam Media, Inc., all rights reserved.
  4. # Author: Skip Montanaro
  5. #
  6. # Copyright 1999, Bioreason, Inc., all rights reserved.
  7. # Author: Andrew Dalke
  8. #
  9. # Copyright 1995-1997, Automatrix, Inc., all rights reserved.
  10. # Author: Skip Montanaro
  11. #
  12. # Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
  13. #
  14. #
  15. # Permission to use, copy, modify, and distribute this Python software and
  16. # its associated documentation for any purpose without fee is hereby
  17. # granted, provided that the above copyright notice appears in all copies,
  18. # and that both that copyright notice and this permission notice appear in
  19. # supporting documentation, and that the name of neither Automatrix,
  20. # Bioreason or Mojam Media be used in advertising or publicity pertaining to
  21. # distribution of the software without specific, written prior permission.
  22. #
  23. #
  24. # Summary of recent changes:
  25. #   Support for files with the same basename (submodules in packages)
  26. #   Expanded the idea of how to ignore files or modules
  27. #   Split tracing and counting into different classes
  28. #   Extracted count information and reporting from the count class
  29. #   Added some ability to detect which missing lines could be executed
  30. #   Added pseudo-pragma to prohibit complaining about unexecuted lines
  31. #   Rewrote the main program
  32.  
  33. # Summary of older changes:
  34. #   Added run-time display of statements being executed
  35. #   Incorporated portability and performance fixes from Greg Stein
  36. #   Incorporated main program from Michael Scharf
  37.  
  38. """
  39. program/module to trace Python program or function execution
  40.  
  41. Sample use, command line:
  42.   trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
  43.   trace.py -t --ignore-dir '$prefix' spam.py eggs
  44.  
  45. Sample use, programmatically (still more complicated than it should be)
  46.    # create an Ignore option, telling it what you want to ignore
  47.    ignore = trace.Ignore(dirs = [sys.prefix, sys.exec_prefix])
  48.    # create a Coverage object, telling it what to ignore
  49.    coverage = trace.Coverage(ignore)
  50.    # run the new command using the given trace
  51.    trace.run(coverage.trace, 'main()')
  52.  
  53.    # make a report, telling it where you want output
  54.    t = trace.create_results_log(coverage.results(),
  55.                                 '/usr/local/Automatrix/concerts/coverage')
  56.                                 show_missing = 1)
  57.  
  58.    The Trace class can be instantited instead of the Coverage class if
  59.    runtime display of executable lines is desired instead of statement
  60.    converage measurement.
  61. """
  62.  
  63. import sys, os, string, marshal, tempfile, copy, operator
  64.  
  65. def usage(outfile):
  66.     outfile.write("""Usage: %s [OPTIONS] <file> [ARGS]
  67.  
  68. Execution:
  69.       --help           Display this help then exit.
  70.       --version        Output version information then exit.
  71.    -t,--trace          Print the line to be executed to sys.stdout.
  72.    -c,--count          Count the number of times a line is executed.
  73.                          Results are written in the results file, if given.
  74.    -r,--report         Generate a report from a results file; do not
  75.                          execute any code.
  76.         (One of `-t', `-c' or `-r' must be specified)
  77.  
  78. I/O:
  79.    -f,--file=          File name for accumulating results over several runs.
  80.                          (No file name means do not archive results)
  81.    -d,--logdir=        Directory to use when writing annotated log files.
  82.                          Log files are the module __name__ with `.` replaced
  83.                          by os.sep and with '.pyl' added.
  84.    -m,--missing        Annotate all executable lines which were not executed
  85.                          with a '>>>>>> '.
  86.    -R,--no-report      Do not generate the annotated reports.  Useful if
  87.                          you want to accumulate several over tests.
  88.  
  89. Selection:                 Do not trace or log lines from ...
  90.   --ignore-module=[string]   modules with the given __name__, and submodules
  91.                               of that module
  92.   --ignore-dir=[string]      files in the stated directory (multiple
  93.                               directories can be joined by os.pathsep)
  94.  
  95.   The selection options can be listed multiple times to ignore different
  96. modules.
  97. """ % sys.argv[0])
  98.  
  99.  
  100. class Ignore:
  101.     def __init__(self, modules = None, dirs = None):
  102.         self._mods = modules or []
  103.         self._dirs = dirs or []
  104.  
  105.         self._ignore = { '<string>': 1 }
  106.  
  107.  
  108.     def names(self, filename, modulename):
  109.         if self._ignore.has_key(modulename):
  110.             return self._ignore[modulename]
  111.  
  112.         # haven't seen this one before, so see if the module name is
  113.         # on the ignore list.  Need to take some care since ignoring
  114.         # "cmp" musn't mean ignoring "cmpcache" but ignoring
  115.         # "Spam" must also mean ignoring "Spam.Eggs".
  116.         for mod in self._mods:
  117.             if mod == modulename:  # Identical names, so ignore
  118.                 self._ignore[modulename] = 1
  119.                 return 1
  120.             # check if the module is a proper submodule of something on
  121.             # the ignore list
  122.             n = len(mod)
  123.             # (will not overflow since if the first n characters are the
  124.             # same and the name has not already occured, then the size
  125.             # of "name" is greater than that of "mod")
  126.             if mod == modulename[:n] and modulename[n] == '.':
  127.                 self._ignore[modulename] = 1
  128.                 return 1
  129.  
  130.         # Now check that __file__ isn't in one of the directories
  131.         if filename is None:
  132.             # must be a built-in, so we must ignore
  133.             self._ignore[modulename] = 1
  134.             return 1
  135.  
  136.         # Ignore a file when it contains one of the ignorable paths
  137.         for d in self._dirs:
  138.             # The '+ os.sep' is to ensure that d is a parent directory,
  139.             # as compared to cases like:
  140.             #  d = "/usr/local"
  141.             #  filename = "/usr/local.py"
  142.             # or
  143.             #  d = "/usr/local.py"
  144.             #  filename = "/usr/local.py"
  145.             if string.find(filename, d + os.sep) == 0:
  146.                 self._ignore[modulename] = 1
  147.                 return 1
  148.  
  149.         # Tried the different ways, so we don't ignore this module
  150.         self._ignore[modulename] = 0
  151.         return 0
  152.  
  153. def run(trace, cmd):
  154.     import __main__
  155.     dict = __main__.__dict__
  156.     sys.settrace(trace)
  157.     try:
  158.         exec cmd in dict, dict
  159.     finally:
  160.         sys.settrace(None)
  161.  
  162. def runctx(trace, cmd, globals=None, locals=None):
  163.     if globals is None: globals = {}
  164.     if locals is None: locals = {}
  165.     sys.settrace(trace)
  166.     try:
  167.         exec cmd in dict, dict
  168.     finally:
  169.         sys.settrace(None)
  170.  
  171. def runfunc(trace, func, *args, **kw):
  172.     result = None
  173.     sys.settrace(trace)
  174.     try:
  175.         result = apply(func, args, kw)
  176.     finally:
  177.         sys.settrace(None)
  178.     return result
  179.  
  180.  
  181. class CoverageResults:
  182.     def __init__(self, counts = {}, modules = {}):
  183.         self.counts = counts.copy()    # map (filename, lineno) to count
  184.         self.modules = modules.copy()  # map filenames to modules
  185.  
  186.     def update(self, other):
  187.         """Merge in the data from another CoverageResults"""
  188.         counts = self.counts
  189.         other_counts = other.counts
  190.         modules = self.modules
  191.         other_modules = other.modules
  192.  
  193.         for key in other_counts.keys():
  194.             counts[key] = counts.get(key, 0) + other_counts[key]
  195.  
  196.         for key in other_modules.keys():
  197.             if modules.has_key(key):
  198.                 # make sure they point to the same file
  199.                 assert modules[key] == other_modules[key], \
  200.                       "Strange! filename %s has two different module names" % \
  201.                       (key, modules[key], other_module[key])
  202.             else:
  203.                 modules[key] = other_modules[key]
  204.  
  205. # Given a code string, return the SET_LINENO information
  206. def _find_LINENO_from_string(co_code):
  207.     """return all of the SET_LINENO information from a code string"""
  208.     import dis
  209.     linenos = {}
  210.  
  211.     # This code was filched from the `dis' module then modified
  212.     n = len(co_code)
  213.     i = 0
  214.     prev_op = None
  215.     prev_lineno = 0
  216.     while i < n:
  217.         c = co_code[i]
  218.         op = ord(c)
  219.         if op == dis.SET_LINENO:
  220.             if prev_op == op:
  221.                 # two SET_LINENO in a row, so the previous didn't
  222.                 # indicate anything.  This occurs with triple
  223.                 # quoted strings (?).  Remove the old one.
  224.                 del linenos[prev_lineno]
  225.             prev_lineno = ord(co_code[i+1]) + ord(co_code[i+2])*256
  226.             linenos[prev_lineno] = 1
  227.         if op >= dis.HAVE_ARGUMENT:
  228.             i = i + 3
  229.         else:
  230.             i = i + 1
  231.         prev_op = op
  232.     return linenos
  233.  
  234. def _find_LINENO(code):
  235.     """return all of the SET_LINENO information from a code object"""
  236.     import types
  237.  
  238.     # get all of the lineno information from the code of this scope level
  239.     linenos = _find_LINENO_from_string(code.co_code)
  240.  
  241.     # and check the constants for references to other code objects
  242.     for c in code.co_consts:
  243.         if type(c) == types.CodeType:
  244.             # find another code object, so recurse into it
  245.             linenos.update(_find_LINENO(c))
  246.     return linenos
  247.  
  248. def find_executable_linenos(filename):
  249.     """return a dict of the line numbers from executable statements in a file
  250.  
  251.     Works by finding all of the code-like objects in the module then searching
  252.     the byte code for 'SET_LINENO' terms (so this won't work one -O files).
  253.  
  254.     """
  255.     import parser
  256.  
  257.     prog = open(filename).read()
  258.     ast = parser.suite(prog)
  259.     code = parser.compileast(ast, filename)
  260.  
  261.     # The only way I know to find line numbers is to look for the
  262.     # SET_LINENO instructions.  Isn't there some way to get it from
  263.     # the AST?
  264.     
  265.     return _find_LINENO(code)
  266.  
  267. ### XXX because os.path.commonprefix seems broken by my way of thinking...
  268. def commonprefix(dirs):
  269.     "Given a list of pathnames, returns the longest common leading component"
  270.     if not dirs: return ''
  271.     n = copy.copy(dirs)
  272.     for i in range(len(n)):
  273.         n[i] = n[i].split(os.sep)
  274.     prefix = n[0]
  275.     for item in n:
  276.         for i in range(len(prefix)):
  277.             if prefix[:i+1] <> item[:i+1]:
  278.                 prefix = prefix[:i]
  279.                 if i == 0: return ''
  280.                 break
  281.     return os.sep.join(prefix)
  282.     
  283. def create_results_log(results, dirname = ".", show_missing = 1,
  284.                        save_counts = 0):
  285.     import re
  286.     # turn the counts data ("(filename, lineno) = count") into something
  287.     # accessible on a per-file basis
  288.     per_file = {}
  289.     for filename, lineno in results.counts.keys():
  290.         lines_hit = per_file[filename] = per_file.get(filename, {})
  291.         lines_hit[lineno] = results.counts[(filename, lineno)]
  292.  
  293.     # try and merge existing counts and modules file from dirname
  294.     try:
  295.         counts = marshal.load(open(os.path.join(dirname, "counts")))
  296.         modules = marshal.load(open(os.path.join(dirname, "modules")))
  297.         results.update(results.__class__(counts, modules))
  298.     except IOError:
  299.         pass
  300.     
  301.     # there are many places where this is insufficient, like a blank
  302.     # line embedded in a multiline string.
  303.     blank = re.compile(r'^\s*(#.*)?$')
  304.  
  305.     # generate file paths for the coverage files we are going to write...
  306.     fnlist = []
  307.     tfdir = tempfile.gettempdir()
  308.     for key in per_file.keys():
  309.         filename = key
  310.         
  311.         # skip some "files" we don't care about...
  312.         if filename == "<string>":
  313.             continue
  314.         # are these caused by code compiled using exec or something?
  315.         if filename.startswith(tfdir):
  316.             continue
  317.  
  318.         # XXX this is almost certainly not portable!!!
  319.         fndir = os.path.dirname(filename)
  320.         if filename[:1] == os.sep:
  321.             coverpath = os.path.join(dirname, "."+fndir)
  322.         else:
  323.             coverpath = os.path.join(dirname, fndir)
  324.  
  325.         if filename.endswith(".pyc") or filename.endswith(".pyo"):
  326.             filename = filename[:-1]
  327.  
  328.         # Get the original lines from the .py file
  329.         try:
  330.             lines = open(filename, 'r').readlines()
  331.         except IOError, err:
  332.             sys.stderr.write("%s: Could not open %s for reading " \
  333.                              "because: %s - skipping\n" % \
  334.                              ("trace", `filename`, err.strerror))
  335.             continue
  336.  
  337.         modulename = os.path.split(results.modules[key])[1]
  338.  
  339.         # build list file name by appending a ".cover" to the module name
  340.         # and sticking it into the specified directory
  341.         listfilename = os.path.join(coverpath, modulename + ".cover")
  342.         #sys.stderr.write("modulename: %(modulename)s\n"
  343.         #                 "filename: %(filename)s\n"
  344.         #                 "coverpath: %(coverpath)s\n"
  345.         #                 "listfilename: %(listfilename)s\n"
  346.         #                 "dirname: %(dirname)s\n"
  347.         #                 % locals())
  348.         try:
  349.             outfile = open(listfilename, 'w')
  350.         except IOError, err:
  351.             sys.stderr.write(
  352.                 '%s: Could not open %s for writing because: %s" \
  353.                 "- skipping\n' % ("trace", `listfilename`, err.strerror))
  354.             continue
  355.  
  356.         # If desired, get a list of the line numbers which represent
  357.         # executable content (returned as a dict for better lookup speed)
  358.         if show_missing:
  359.             executable_linenos = find_executable_linenos(filename)
  360.         else:
  361.             executable_linenos = {}
  362.  
  363.         lines_hit = per_file[key]
  364.         for i in range(len(lines)):
  365.             line = lines[i]
  366.  
  367.             # do the blank/comment match to try to mark more lines
  368.             # (help the reader find stuff that hasn't been covered)
  369.             if lines_hit.has_key(i+1):
  370.                 # count precedes the lines that we captured
  371.                 outfile.write('%5d: ' % lines_hit[i+1])
  372.             elif blank.match(line):
  373.                 # blank lines and comments are preceded by dots
  374.                 outfile.write('    . ')
  375.             else:
  376.                 # lines preceded by no marks weren't hit
  377.                 # Highlight them if so indicated, unless the line contains
  378.                 # '#pragma: NO COVER' (it is possible to embed this into
  379.                 # the text as a non-comment; no easy fix)
  380.                 if executable_linenos.has_key(i+1) and \
  381.                    string.find(lines[i],
  382.                                string.join(['#pragma', 'NO COVER'])) == -1:
  383.                     outfile.write('>>>>>> ')
  384.                 else:
  385.                     outfile.write(' '*7)
  386.             outfile.write(string.expandtabs(lines[i], 8))
  387.  
  388.         outfile.close()
  389.  
  390.         if save_counts:
  391.             # try and store counts and module info into dirname
  392.             try:
  393.                 marshal.dump(results.counts,
  394.                              open(os.path.join(dirname, "counts"), "w"))
  395.                 marshal.dump(results.modules,
  396.                              open(os.path.join(dirname, "modules"), "w"))
  397.             except IOError, err:
  398.                 sys.stderr.write("cannot save counts/modules " \
  399.                                  "files because %s" % err.strerror)
  400.  
  401. # There is a lot of code shared between these two classes even though
  402. # it is straightforward to make a super class to share code.  However,
  403. # for performance reasons (remember, this is called at every step) I
  404. # wanted to keep everything to a single function call.  Also, by
  405. # staying within a single scope, I don't have to temporarily nullify
  406. # sys.settrace, which would slow things down even more.
  407.  
  408. class Coverage:
  409.     def __init__(self, ignore = Ignore()):
  410.         self.ignore = ignore
  411.         self.ignore_names = ignore._ignore # access ignore's cache (speed hack)
  412.  
  413.         self.counts = {}   # keys are (filename, linenumber)
  414.         self.modules = {}  # maps filename -> module name
  415.  
  416.     def trace(self, frame, why, arg):
  417.         if why == 'line':
  418.             # something is fishy about getting the file name
  419.             filename = frame.f_globals.get("__file__", None)
  420.             if filename is None:
  421.                 filename = frame.f_code.co_filename
  422.             modulename = frame.f_globals["__name__"]
  423.  
  424.             # We do this next block to keep from having to make methods
  425.             # calls, which also requires resetting the trace
  426.             ignore_it = self.ignore_names.get(modulename, -1)
  427.             if ignore_it == -1:  # unknown filename
  428.                 sys.settrace(None)
  429.                 ignore_it = self.ignore.names(filename, modulename)
  430.                 sys.settrace(self.trace)
  431.  
  432.                 # record the module name for every file
  433.                 self.modules[filename] = modulename
  434.  
  435.             if not ignore_it:
  436.                 lineno = frame.f_lineno
  437.  
  438.                 # record the file name and line number of every trace
  439.                 key = (filename, lineno)
  440.                 self.counts[key] = self.counts.get(key, 0) + 1
  441.  
  442.         return self.trace
  443.  
  444.     def results(self):
  445.         return CoverageResults(self.counts, self.modules)
  446.  
  447. class Trace:
  448.     def __init__(self, ignore = Ignore()):
  449.         self.ignore = ignore
  450.         self.ignore_names = ignore._ignore # access ignore's cache (speed hack)
  451.  
  452.         self.files = {'<string>': None}  # stores lines from the .py file, or None
  453.  
  454.     def trace(self, frame, why, arg):
  455.         if why == 'line':
  456.             filename = frame.f_code.co_filename
  457.             modulename = frame.f_globals["__name__"]
  458.  
  459.             # We do this next block to keep from having to make methods
  460.             # calls, which also requires resetting the trace
  461.             ignore_it = self.ignore_names.get(modulename, -1)
  462.             if ignore_it == -1:  # unknown filename
  463.                 sys.settrace(None)
  464.                 ignore_it = self.ignore.names(filename, modulename)
  465.                 sys.settrace(self.trace)
  466.  
  467.             if not ignore_it:
  468.                 lineno = frame.f_lineno
  469.                 files = self.files
  470.  
  471.                 if filename != '<string>' and not files.has_key(filename):
  472.                     files[filename] = map(string.rstrip,
  473.                                           open(filename).readlines())
  474.  
  475.                 # If you want to see filenames (the original behaviour), try:
  476.                 #   modulename = filename
  477.                 # or, prettier but confusing when several files have the same name
  478.                 #   modulename = os.path.basename(filename)
  479.  
  480.                 if files[filename] != None:
  481.                     print '%s(%d): %s' % (os.path.basename(filename), lineno,
  482.                                           files[filename][lineno-1])
  483.                 else:
  484.                     print '%s(%d): ??' % (modulename, lineno)
  485.  
  486.         return self.trace
  487.     
  488.  
  489. def _err_exit(msg):
  490.     sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
  491.     sys.exit(1)
  492.  
  493. def main(argv = None):
  494.     import getopt
  495.  
  496.     if argv is None:
  497.         argv = sys.argv
  498.     try:
  499.         opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:m",
  500.                                         ["help", "version", "trace", "count",
  501.                                          "report", "no-report",
  502.                                          "file=", "logdir=", "missing",
  503.                                          "ignore-module=", "ignore-dir="])
  504.  
  505.     except getopt.error, msg:
  506.         sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
  507.         sys.stderr.write("Try `%s --help' for more information\n" % sys.argv[0])
  508.         sys.exit(1)
  509.  
  510.     trace = 0
  511.     count = 0
  512.     report = 0
  513.     no_report = 0
  514.     counts_file = None
  515.     logdir = "."
  516.     missing = 0
  517.     ignore_modules = []
  518.     ignore_dirs = []
  519.  
  520.     for opt, val in opts:
  521.         if opt == "--help":
  522.             usage(sys.stdout)
  523.             sys.exit(0)
  524.  
  525.         if opt == "--version":
  526.             sys.stdout.write("trace 2.0\n")
  527.             sys.exit(0)
  528.  
  529.         if opt == "-t" or opt == "--trace":
  530.             trace = 1
  531.             continue
  532.  
  533.         if opt == "-c" or opt == "--count":
  534.             count = 1
  535.             continue
  536.  
  537.         if opt == "-r" or opt == "--report":
  538.             report = 1
  539.             continue
  540.  
  541.         if opt == "-R" or opt == "--no-report":
  542.             no_report = 1
  543.             continue
  544.  
  545.         if opt == "-f" or opt == "--file":
  546.             counts_file = val
  547.             continue
  548.  
  549.         if opt == "-d" or opt == "--logdir":
  550.             logdir = val
  551.             continue
  552.  
  553.         if opt == "-m" or opt == "--missing":
  554.             missing = 1
  555.             continue
  556.  
  557.         if opt == "--ignore-module":
  558.             ignore_modules.append(val)
  559.             continue
  560.  
  561.         if opt == "--ignore-dir":
  562.             for s in string.split(val, os.pathsep):
  563.                 s = os.path.expandvars(s)
  564.                 # should I also call expanduser? (after all, could use $HOME)
  565.  
  566.                 s = string.replace(s, "$prefix",
  567.                                    os.path.join(sys.prefix, "lib",
  568.                                                 "python" + sys.version[:3]))
  569.                 s = string.replace(s, "$exec_prefix",
  570.                                    os.path.join(sys.exec_prefix, "lib",
  571.                                                 "python" + sys.version[:3]))
  572.                 s = os.path.normpath(s)
  573.                 ignore_dirs.append(s)
  574.             continue
  575.  
  576.         assert 0, "Should never get here"
  577.  
  578.     if len(prog_argv) == 0:
  579.         _err_exit("missing name of file to run")
  580.  
  581.     if count + trace + report > 1:
  582.         _err_exit("can only specify one of --trace, --count or --report")
  583.  
  584.     if count + trace + report == 0:
  585.         _err_exit("must specify one of --trace, --count or --report")
  586.  
  587.     if report and counts_file is None:
  588.         _err_exit("--report requires a --file")
  589.  
  590.     if report and no_report:
  591.         _err_exit("cannot specify both --report and --no-report")
  592.  
  593.     if logdir is not None:
  594.         # warn if the directory doesn't exist, but keep on going
  595.         # (is this the correct behaviour?)
  596.         if not os.path.isdir(logdir):
  597.             sys.stderr.write(
  598.                 "trace: WARNING, --logdir directory %s is not available\n" %
  599.                        `logdir`)
  600.  
  601.     sys.argv = prog_argv
  602.     progname = prog_argv[0]
  603.     if eval(sys.version[:3])>1.3:
  604.         sys.path[0] = os.path.split(progname)[0] # ???
  605.  
  606.     # everything is ready
  607.     ignore = Ignore(ignore_modules, ignore_dirs)
  608.     if trace:
  609.         t = Trace(ignore)
  610.         try:
  611.             run(t.trace, 'execfile(' + `progname` + ')')
  612.         except IOError, err:
  613.             _err_exit("Cannot run file %s because: %s" % \
  614.                       (`sys.argv[0]`, err.strerror))
  615.  
  616.     elif count:
  617.         t = Coverage(ignore)
  618.         try:
  619.             run(t.trace, 'execfile(' + `progname` + ')')
  620.         except IOError, err:
  621.             _err_exit("Cannot run file %s because: %s" % \
  622.                       (`sys.argv[0]`, err.strerror))
  623.         except SystemExit:
  624.             pass
  625.  
  626.         results = t.results()
  627.         # Add another lookup from the program's file name to its import name
  628.         # This give the right results, but I'm not sure why ...
  629.         results.modules[progname] = os.path.splitext(progname)[0]
  630.  
  631.         if counts_file:
  632.             # add in archived data, if available
  633.             try:
  634.                 old_counts, old_modules = marshal.load(open(counts_file, 'rb'))
  635.             except IOError:
  636.                 pass
  637.             else:
  638.                 results.update(CoverageResults(old_counts, old_modules))
  639.  
  640.         if not no_report:
  641.             create_results_log(results, logdir, missing)
  642.  
  643.         if counts_file:
  644.             try:
  645.                 marshal.dump( (results.counts, results.modules),
  646.                               open(counts_file, 'wb'))
  647.             except IOError, err:
  648.                 _err_exit("Cannot save counts file %s because: %s" % \
  649.                           (`counts_file`, err.strerror))
  650.  
  651.     elif report:
  652.         old_counts, old_modules = marshal.load(open(counts_file, 'rb'))
  653.         results = CoverageResults(old_counts, old_modules)
  654.         create_results_log(results, logdir, missing)
  655.  
  656.     else:
  657.         assert 0, "Should never get here"
  658.  
  659. if __name__=='__main__':
  660.     main()
  661.