| #!/usr/bin/env python | 
 |  | 
 | # Copyright 2000, Mojam Media, Inc., all rights reserved. | 
 | # Author: Skip Montanaro | 
 | # | 
 | # Copyright 1999, Bioreason, Inc., all rights reserved. | 
 | # Author: Andrew Dalke | 
 | # | 
 | # Copyright 1995-1997, Automatrix, Inc., all rights reserved. | 
 | # Author: Skip Montanaro | 
 | # | 
 | # Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved. | 
 | # | 
 | # | 
 | # Permission to use, copy, modify, and distribute this Python software and | 
 | # its associated documentation for any purpose without fee is hereby | 
 | # granted, provided that the above copyright notice appears in all copies, | 
 | # and that both that copyright notice and this permission notice appear in | 
 | # supporting documentation, and that the name of neither Automatrix, | 
 | # Bioreason or Mojam Media be used in advertising or publicity pertaining to | 
 | # distribution of the software without specific, written prior permission. | 
 | # | 
 | # | 
 | # Summary of recent changes: | 
 | #   Support for files with the same basename (submodules in packages) | 
 | #   Expanded the idea of how to ignore files or modules | 
 | #   Split tracing and counting into different classes | 
 | #   Extracted count information and reporting from the count class | 
 | #   Added some ability to detect which missing lines could be executed | 
 | #   Added pseudo-pragma to prohibit complaining about unexecuted lines | 
 | #   Rewrote the main program | 
 |  | 
 | # Summary of older changes: | 
 | #   Added run-time display of statements being executed | 
 | #   Incorporated portability and performance fixes from Greg Stein | 
 | #   Incorporated main program from Michael Scharf | 
 |  | 
 | """ | 
 | program/module to trace Python program or function execution | 
 |  | 
 | Sample use, command line: | 
 |   trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs | 
 |   trace.py -t --ignore-dir '$prefix' spam.py eggs | 
 |  | 
 | Sample use, programmatically (still more complicated than it should be) | 
 |    # create an Ignore option, telling it what you want to ignore | 
 |    ignore = trace.Ignore(dirs = [sys.prefix, sys.exec_prefix]) | 
 |    # create a Coverage object, telling it what to ignore | 
 |    coverage = trace.Coverage(ignore) | 
 |    # run the new command using the given trace | 
 |    trace.run(coverage.trace, 'main()') | 
 |  | 
 |    # make a report, telling it where you want output | 
 |    t = trace.create_results_log(coverage.results(), | 
 |                                 '/usr/local/Automatrix/concerts/coverage') | 
 |                                 show_missing = 1) | 
 |  | 
 |    The Trace class can be instantited instead of the Coverage class if | 
 |    runtime display of executable lines is desired instead of statement | 
 |    converage measurement. | 
 | """ | 
 |  | 
 | import sys, os, string, marshal, tempfile, copy, operator | 
 |  | 
 | def usage(outfile): | 
 |     outfile.write("""Usage: %s [OPTIONS] <file> [ARGS] | 
 |  | 
 | Execution: | 
 |       --help           Display this help then exit. | 
 |       --version        Output version information then exit. | 
 |    -t,--trace          Print the line to be executed to sys.stdout. | 
 |    -c,--count          Count the number of times a line is executed. | 
 |                          Results are written in the results file, if given. | 
 |    -r,--report         Generate a report from a results file; do not | 
 |                          execute any code. | 
 |         (One of `-t', `-c' or `-r' must be specified) | 
 |  | 
 | I/O: | 
 |    -f,--file=          File name for accumulating results over several runs. | 
 |                          (No file name means do not archive results) | 
 |    -d,--logdir=        Directory to use when writing annotated log files. | 
 |                          Log files are the module __name__ with `.` replaced | 
 |                          by os.sep and with '.pyl' added. | 
 |    -m,--missing        Annotate all executable lines which were not executed | 
 |                          with a '>>>>>> '. | 
 |    -R,--no-report      Do not generate the annotated reports.  Useful if | 
 |                          you want to accumulate several over tests. | 
 |  | 
 | Selection:                 Do not trace or log lines from ... | 
 |   --ignore-module=[string]   modules with the given __name__, and submodules | 
 |                               of that module | 
 |   --ignore-dir=[string]      files in the stated directory (multiple | 
 |                               directories can be joined by os.pathsep) | 
 |  | 
 |   The selection options can be listed multiple times to ignore different | 
 | modules. | 
 | """ % sys.argv[0]) | 
 |  | 
 |  | 
 | class Ignore: | 
 |     def __init__(self, modules = None, dirs = None): | 
 |         self._mods = modules or [] | 
 |         self._dirs = dirs or [] | 
 |  | 
 |         self._ignore = { '<string>': 1 } | 
 |  | 
 |  | 
 |     def names(self, filename, modulename): | 
 |         if self._ignore.has_key(modulename): | 
 |             return self._ignore[modulename] | 
 |  | 
 |         # haven't seen this one before, so see if the module name is | 
 |         # on the ignore list.  Need to take some care since ignoring | 
 |         # "cmp" musn't mean ignoring "cmpcache" but ignoring | 
 |         # "Spam" must also mean ignoring "Spam.Eggs". | 
 |         for mod in self._mods: | 
 |             if mod == modulename:  # Identical names, so ignore | 
 |                 self._ignore[modulename] = 1 | 
 |                 return 1 | 
 |             # check if the module is a proper submodule of something on | 
 |             # the ignore list | 
 |             n = len(mod) | 
 |             # (will not overflow since if the first n characters are the | 
 |             # same and the name has not already occured, then the size | 
 |             # of "name" is greater than that of "mod") | 
 |             if mod == modulename[:n] and modulename[n] == '.': | 
 |                 self._ignore[modulename] = 1 | 
 |                 return 1 | 
 |  | 
 |         # Now check that __file__ isn't in one of the directories | 
 |         if filename is None: | 
 |             # must be a built-in, so we must ignore | 
 |             self._ignore[modulename] = 1 | 
 |             return 1 | 
 |  | 
 |         # Ignore a file when it contains one of the ignorable paths | 
 |         for d in self._dirs: | 
 |             # The '+ os.sep' is to ensure that d is a parent directory, | 
 |             # as compared to cases like: | 
 |             #  d = "/usr/local" | 
 |             #  filename = "/usr/local.py" | 
 |             # or | 
 |             #  d = "/usr/local.py" | 
 |             #  filename = "/usr/local.py" | 
 |             if string.find(filename, d + os.sep) == 0: | 
 |                 self._ignore[modulename] = 1 | 
 |                 return 1 | 
 |  | 
 |         # Tried the different ways, so we don't ignore this module | 
 |         self._ignore[modulename] = 0 | 
 |         return 0 | 
 |  | 
 | def run(trace, cmd): | 
 |     import __main__ | 
 |     dict = __main__.__dict__ | 
 |     sys.settrace(trace) | 
 |     try: | 
 |         exec cmd in dict, dict | 
 |     finally: | 
 |         sys.settrace(None) | 
 |  | 
 | def runctx(trace, cmd, globals=None, locals=None): | 
 |     if globals is None: globals = {} | 
 |     if locals is None: locals = {} | 
 |     sys.settrace(trace) | 
 |     try: | 
 |         exec cmd in dict, dict | 
 |     finally: | 
 |         sys.settrace(None) | 
 |  | 
 | def runfunc(trace, func, *args, **kw): | 
 |     result = None | 
 |     sys.settrace(trace) | 
 |     try: | 
 |         result = apply(func, args, kw) | 
 |     finally: | 
 |         sys.settrace(None) | 
 |     return result | 
 |  | 
 |  | 
 | class CoverageResults: | 
 |     def __init__(self, counts = {}, modules = {}): | 
 |         self.counts = counts.copy()    # map (filename, lineno) to count | 
 |         self.modules = modules.copy()  # map filenames to modules | 
 |  | 
 |     def update(self, other): | 
 |         """Merge in the data from another CoverageResults""" | 
 |         counts = self.counts | 
 |         other_counts = other.counts | 
 |         modules = self.modules | 
 |         other_modules = other.modules | 
 |  | 
 |         for key in other_counts.keys(): | 
 |             counts[key] = counts.get(key, 0) + other_counts[key] | 
 |  | 
 |         for key in other_modules.keys(): | 
 |             if modules.has_key(key): | 
 |                 # make sure they point to the same file | 
 |                 assert modules[key] == other_modules[key], \ | 
 |                       "Strange! filename %s has two different module names" % \ | 
 |                       (key, modules[key], other_module[key]) | 
 |             else: | 
 |                 modules[key] = other_modules[key] | 
 |  | 
 | # Given a code string, return the SET_LINENO information | 
 | def _find_LINENO_from_string(co_code): | 
 |     """return all of the SET_LINENO information from a code string""" | 
 |     import dis | 
 |     linenos = {} | 
 |  | 
 |     # This code was filched from the `dis' module then modified | 
 |     n = len(co_code) | 
 |     i = 0 | 
 |     prev_op = None | 
 |     prev_lineno = 0 | 
 |     while i < n: | 
 |         c = co_code[i] | 
 |         op = ord(c) | 
 |         if op == dis.SET_LINENO: | 
 |             if prev_op == op: | 
 |                 # two SET_LINENO in a row, so the previous didn't | 
 |                 # indicate anything.  This occurs with triple | 
 |                 # quoted strings (?).  Remove the old one. | 
 |                 del linenos[prev_lineno] | 
 |             prev_lineno = ord(co_code[i+1]) + ord(co_code[i+2])*256 | 
 |             linenos[prev_lineno] = 1 | 
 |         if op >= dis.HAVE_ARGUMENT: | 
 |             i = i + 3 | 
 |         else: | 
 |             i = i + 1 | 
 |         prev_op = op | 
 |     return linenos | 
 |  | 
 | def _find_LINENO(code): | 
 |     """return all of the SET_LINENO information from a code object""" | 
 |     import types | 
 |  | 
 |     # get all of the lineno information from the code of this scope level | 
 |     linenos = _find_LINENO_from_string(code.co_code) | 
 |  | 
 |     # and check the constants for references to other code objects | 
 |     for c in code.co_consts: | 
 |         if type(c) == types.CodeType: | 
 |             # find another code object, so recurse into it | 
 |             linenos.update(_find_LINENO(c)) | 
 |     return linenos | 
 |  | 
 | def find_executable_linenos(filename): | 
 |     """return a dict of the line numbers from executable statements in a file | 
 |  | 
 |     Works by finding all of the code-like objects in the module then searching | 
 |     the byte code for 'SET_LINENO' terms (so this won't work one -O files). | 
 |  | 
 |     """ | 
 |     import parser | 
 |  | 
 |     prog = open(filename).read() | 
 |     ast = parser.suite(prog) | 
 |     code = parser.compileast(ast, filename) | 
 |  | 
 |     # The only way I know to find line numbers is to look for the | 
 |     # SET_LINENO instructions.  Isn't there some way to get it from | 
 |     # the AST? | 
 |  | 
 |     return _find_LINENO(code) | 
 |  | 
 | ### XXX because os.path.commonprefix seems broken by my way of thinking... | 
 | def commonprefix(dirs): | 
 |     "Given a list of pathnames, returns the longest common leading component" | 
 |     if not dirs: return '' | 
 |     n = copy.copy(dirs) | 
 |     for i in range(len(n)): | 
 |         n[i] = n[i].split(os.sep) | 
 |     prefix = n[0] | 
 |     for item in n: | 
 |         for i in range(len(prefix)): | 
 |             if prefix[:i+1] <> item[:i+1]: | 
 |                 prefix = prefix[:i] | 
 |                 if i == 0: return '' | 
 |                 break | 
 |     return os.sep.join(prefix) | 
 |  | 
 | def create_results_log(results, dirname = ".", show_missing = 1, | 
 |                        save_counts = 0): | 
 |     import re | 
 |     # turn the counts data ("(filename, lineno) = count") into something | 
 |     # accessible on a per-file basis | 
 |     per_file = {} | 
 |     for filename, lineno in results.counts.keys(): | 
 |         lines_hit = per_file[filename] = per_file.get(filename, {}) | 
 |         lines_hit[lineno] = results.counts[(filename, lineno)] | 
 |  | 
 |     # try and merge existing counts and modules file from dirname | 
 |     try: | 
 |         counts = marshal.load(open(os.path.join(dirname, "counts"))) | 
 |         modules = marshal.load(open(os.path.join(dirname, "modules"))) | 
 |         results.update(results.__class__(counts, modules)) | 
 |     except IOError: | 
 |         pass | 
 |  | 
 |     # there are many places where this is insufficient, like a blank | 
 |     # line embedded in a multiline string. | 
 |     blank = re.compile(r'^\s*(#.*)?$') | 
 |  | 
 |     # generate file paths for the coverage files we are going to write... | 
 |     fnlist = [] | 
 |     tfdir = tempfile.gettempdir() | 
 |     for key in per_file.keys(): | 
 |         filename = key | 
 |  | 
 |         # skip some "files" we don't care about... | 
 |         if filename == "<string>": | 
 |             continue | 
 |         # are these caused by code compiled using exec or something? | 
 |         if filename.startswith(tfdir): | 
 |             continue | 
 |  | 
 |         # XXX this is almost certainly not portable!!! | 
 |         fndir = os.path.dirname(filename) | 
 |         if filename[:1] == os.sep: | 
 |             coverpath = os.path.join(dirname, "."+fndir) | 
 |         else: | 
 |             coverpath = os.path.join(dirname, fndir) | 
 |  | 
 |         if filename.endswith(".pyc") or filename.endswith(".pyo"): | 
 |             filename = filename[:-1] | 
 |  | 
 |         # Get the original lines from the .py file | 
 |         try: | 
 |             lines = open(filename, 'r').readlines() | 
 |         except IOError, err: | 
 |             sys.stderr.write("%s: Could not open %s for reading " \ | 
 |                              "because: %s - skipping\n" % \ | 
 |                              ("trace", `filename`, err.strerror)) | 
 |             continue | 
 |  | 
 |         modulename = os.path.split(results.modules[key])[1] | 
 |  | 
 |         # build list file name by appending a ".cover" to the module name | 
 |         # and sticking it into the specified directory | 
 |         listfilename = os.path.join(coverpath, modulename + ".cover") | 
 |         #sys.stderr.write("modulename: %(modulename)s\n" | 
 |         #                 "filename: %(filename)s\n" | 
 |         #                 "coverpath: %(coverpath)s\n" | 
 |         #                 "listfilename: %(listfilename)s\n" | 
 |         #                 "dirname: %(dirname)s\n" | 
 |         #                 % locals()) | 
 |         try: | 
 |             outfile = open(listfilename, 'w') | 
 |         except IOError, err: | 
 |             sys.stderr.write( | 
 |                 '%s: Could not open %s for writing because: %s" \ | 
 |                 "- skipping\n' % ("trace", `listfilename`, err.strerror)) | 
 |             continue | 
 |  | 
 |         # If desired, get a list of the line numbers which represent | 
 |         # executable content (returned as a dict for better lookup speed) | 
 |         if show_missing: | 
 |             executable_linenos = find_executable_linenos(filename) | 
 |         else: | 
 |             executable_linenos = {} | 
 |  | 
 |         lines_hit = per_file[key] | 
 |         for i in range(len(lines)): | 
 |             line = lines[i] | 
 |  | 
 |             # do the blank/comment match to try to mark more lines | 
 |             # (help the reader find stuff that hasn't been covered) | 
 |             if lines_hit.has_key(i+1): | 
 |                 # count precedes the lines that we captured | 
 |                 outfile.write('%5d: ' % lines_hit[i+1]) | 
 |             elif blank.match(line): | 
 |                 # blank lines and comments are preceded by dots | 
 |                 outfile.write('    . ') | 
 |             else: | 
 |                 # lines preceded by no marks weren't hit | 
 |                 # Highlight them if so indicated, unless the line contains | 
 |                 # '#pragma: NO COVER' (it is possible to embed this into | 
 |                 # the text as a non-comment; no easy fix) | 
 |                 if executable_linenos.has_key(i+1) and \ | 
 |                    string.find(lines[i], | 
 |                                string.join(['#pragma', 'NO COVER'])) == -1: | 
 |                     outfile.write('>>>>>> ') | 
 |                 else: | 
 |                     outfile.write(' '*7) | 
 |             outfile.write(string.expandtabs(lines[i], 8)) | 
 |  | 
 |         outfile.close() | 
 |  | 
 |         if save_counts: | 
 |             # try and store counts and module info into dirname | 
 |             try: | 
 |                 marshal.dump(results.counts, | 
 |                              open(os.path.join(dirname, "counts"), "w")) | 
 |                 marshal.dump(results.modules, | 
 |                              open(os.path.join(dirname, "modules"), "w")) | 
 |             except IOError, err: | 
 |                 sys.stderr.write("cannot save counts/modules " \ | 
 |                                  "files because %s" % err.strerror) | 
 |  | 
 | # There is a lot of code shared between these two classes even though | 
 | # it is straightforward to make a super class to share code.  However, | 
 | # for performance reasons (remember, this is called at every step) I | 
 | # wanted to keep everything to a single function call.  Also, by | 
 | # staying within a single scope, I don't have to temporarily nullify | 
 | # sys.settrace, which would slow things down even more. | 
 |  | 
 | class Coverage: | 
 |     def __init__(self, ignore = Ignore()): | 
 |         self.ignore = ignore | 
 |         self.ignore_names = ignore._ignore # access ignore's cache (speed hack) | 
 |  | 
 |         self.counts = {}   # keys are (filename, linenumber) | 
 |         self.modules = {}  # maps filename -> module name | 
 |  | 
 |     def trace(self, frame, why, arg): | 
 |         if why == 'line': | 
 |             # something is fishy about getting the file name | 
 |             filename = frame.f_globals.get("__file__", None) | 
 |             if filename is None: | 
 |                 filename = frame.f_code.co_filename | 
 |             modulename = frame.f_globals["__name__"] | 
 |  | 
 |             # We do this next block to keep from having to make methods | 
 |             # calls, which also requires resetting the trace | 
 |             ignore_it = self.ignore_names.get(modulename, -1) | 
 |             if ignore_it == -1:  # unknown filename | 
 |                 sys.settrace(None) | 
 |                 ignore_it = self.ignore.names(filename, modulename) | 
 |                 sys.settrace(self.trace) | 
 |  | 
 |                 # record the module name for every file | 
 |                 self.modules[filename] = modulename | 
 |  | 
 |             if not ignore_it: | 
 |                 lineno = frame.f_lineno | 
 |  | 
 |                 # record the file name and line number of every trace | 
 |                 key = (filename, lineno) | 
 |                 self.counts[key] = self.counts.get(key, 0) + 1 | 
 |  | 
 |         return self.trace | 
 |  | 
 |     def results(self): | 
 |         return CoverageResults(self.counts, self.modules) | 
 |  | 
 | class Trace: | 
 |     def __init__(self, ignore = Ignore()): | 
 |         self.ignore = ignore | 
 |         self.ignore_names = ignore._ignore # access ignore's cache (speed hack) | 
 |  | 
 |         self.files = {'<string>': None}  # stores lines from the .py file, or None | 
 |  | 
 |     def trace(self, frame, why, arg): | 
 |         if why == 'line': | 
 |             filename = frame.f_code.co_filename | 
 |             modulename = frame.f_globals["__name__"] | 
 |  | 
 |             # We do this next block to keep from having to make methods | 
 |             # calls, which also requires resetting the trace | 
 |             ignore_it = self.ignore_names.get(modulename, -1) | 
 |             if ignore_it == -1:  # unknown filename | 
 |                 sys.settrace(None) | 
 |                 ignore_it = self.ignore.names(filename, modulename) | 
 |                 sys.settrace(self.trace) | 
 |  | 
 |             if not ignore_it: | 
 |                 lineno = frame.f_lineno | 
 |                 files = self.files | 
 |  | 
 |                 if filename != '<string>' and not files.has_key(filename): | 
 |                     files[filename] = map(string.rstrip, | 
 |                                           open(filename).readlines()) | 
 |  | 
 |                 # If you want to see filenames (the original behaviour), try: | 
 |                 #   modulename = filename | 
 |                 # or, prettier but confusing when several files have the same name | 
 |                 #   modulename = os.path.basename(filename) | 
 |  | 
 |                 if files[filename] != None: | 
 |                     print '%s(%d): %s' % (os.path.basename(filename), lineno, | 
 |                                           files[filename][lineno-1]) | 
 |                 else: | 
 |                     print '%s(%d): ??' % (modulename, lineno) | 
 |  | 
 |         return self.trace | 
 |  | 
 |  | 
 | def _err_exit(msg): | 
 |     sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) | 
 |     sys.exit(1) | 
 |  | 
 | def main(argv = None): | 
 |     import getopt | 
 |  | 
 |     if argv is None: | 
 |         argv = sys.argv | 
 |     try: | 
 |         opts, prog_argv = getopt.getopt(argv[1:], "tcrRf:d:m", | 
 |                                         ["help", "version", "trace", "count", | 
 |                                          "report", "no-report", | 
 |                                          "file=", "logdir=", "missing", | 
 |                                          "ignore-module=", "ignore-dir="]) | 
 |  | 
 |     except getopt.error, msg: | 
 |         sys.stderr.write("%s: %s\n" % (sys.argv[0], msg)) | 
 |         sys.stderr.write("Try `%s --help' for more information\n" % sys.argv[0]) | 
 |         sys.exit(1) | 
 |  | 
 |     trace = 0 | 
 |     count = 0 | 
 |     report = 0 | 
 |     no_report = 0 | 
 |     counts_file = None | 
 |     logdir = "." | 
 |     missing = 0 | 
 |     ignore_modules = [] | 
 |     ignore_dirs = [] | 
 |  | 
 |     for opt, val in opts: | 
 |         if opt == "--help": | 
 |             usage(sys.stdout) | 
 |             sys.exit(0) | 
 |  | 
 |         if opt == "--version": | 
 |             sys.stdout.write("trace 2.0\n") | 
 |             sys.exit(0) | 
 |  | 
 |         if opt == "-t" or opt == "--trace": | 
 |             trace = 1 | 
 |             continue | 
 |  | 
 |         if opt == "-c" or opt == "--count": | 
 |             count = 1 | 
 |             continue | 
 |  | 
 |         if opt == "-r" or opt == "--report": | 
 |             report = 1 | 
 |             continue | 
 |  | 
 |         if opt == "-R" or opt == "--no-report": | 
 |             no_report = 1 | 
 |             continue | 
 |  | 
 |         if opt == "-f" or opt == "--file": | 
 |             counts_file = val | 
 |             continue | 
 |  | 
 |         if opt == "-d" or opt == "--logdir": | 
 |             logdir = val | 
 |             continue | 
 |  | 
 |         if opt == "-m" or opt == "--missing": | 
 |             missing = 1 | 
 |             continue | 
 |  | 
 |         if opt == "--ignore-module": | 
 |             ignore_modules.append(val) | 
 |             continue | 
 |  | 
 |         if opt == "--ignore-dir": | 
 |             for s in string.split(val, os.pathsep): | 
 |                 s = os.path.expandvars(s) | 
 |                 # should I also call expanduser? (after all, could use $HOME) | 
 |  | 
 |                 s = string.replace(s, "$prefix", | 
 |                                    os.path.join(sys.prefix, "lib", | 
 |                                                 "python" + sys.version[:3])) | 
 |                 s = string.replace(s, "$exec_prefix", | 
 |                                    os.path.join(sys.exec_prefix, "lib", | 
 |                                                 "python" + sys.version[:3])) | 
 |                 s = os.path.normpath(s) | 
 |                 ignore_dirs.append(s) | 
 |             continue | 
 |  | 
 |         assert 0, "Should never get here" | 
 |  | 
 |     if len(prog_argv) == 0: | 
 |         _err_exit("missing name of file to run") | 
 |  | 
 |     if count + trace + report > 1: | 
 |         _err_exit("can only specify one of --trace, --count or --report") | 
 |  | 
 |     if count + trace + report == 0: | 
 |         _err_exit("must specify one of --trace, --count or --report") | 
 |  | 
 |     if report and counts_file is None: | 
 |         _err_exit("--report requires a --file") | 
 |  | 
 |     if report and no_report: | 
 |         _err_exit("cannot specify both --report and --no-report") | 
 |  | 
 |     if logdir is not None: | 
 |         # warn if the directory doesn't exist, but keep on going | 
 |         # (is this the correct behaviour?) | 
 |         if not os.path.isdir(logdir): | 
 |             sys.stderr.write( | 
 |                 "trace: WARNING, --logdir directory %s is not available\n" % | 
 |                        `logdir`) | 
 |  | 
 |     sys.argv = prog_argv | 
 |     progname = prog_argv[0] | 
 |     if eval(sys.version[:3])>1.3: | 
 |         sys.path[0] = os.path.split(progname)[0] # ??? | 
 |  | 
 |     # everything is ready | 
 |     ignore = Ignore(ignore_modules, ignore_dirs) | 
 |     if trace: | 
 |         t = Trace(ignore) | 
 |         try: | 
 |             run(t.trace, 'execfile(' + `progname` + ')') | 
 |         except IOError, err: | 
 |             _err_exit("Cannot run file %s because: %s" % \ | 
 |                       (`sys.argv[0]`, err.strerror)) | 
 |  | 
 |     elif count: | 
 |         t = Coverage(ignore) | 
 |         try: | 
 |             run(t.trace, 'execfile(' + `progname` + ')') | 
 |         except IOError, err: | 
 |             _err_exit("Cannot run file %s because: %s" % \ | 
 |                       (`sys.argv[0]`, err.strerror)) | 
 |         except SystemExit: | 
 |             pass | 
 |  | 
 |         results = t.results() | 
 |         # Add another lookup from the program's file name to its import name | 
 |         # This give the right results, but I'm not sure why ... | 
 |         results.modules[progname] = os.path.splitext(progname)[0] | 
 |  | 
 |         if counts_file: | 
 |             # add in archived data, if available | 
 |             try: | 
 |                 old_counts, old_modules = marshal.load(open(counts_file, 'rb')) | 
 |             except IOError: | 
 |                 pass | 
 |             else: | 
 |                 results.update(CoverageResults(old_counts, old_modules)) | 
 |  | 
 |         if not no_report: | 
 |             create_results_log(results, logdir, missing) | 
 |  | 
 |         if counts_file: | 
 |             try: | 
 |                 marshal.dump( (results.counts, results.modules), | 
 |                               open(counts_file, 'wb')) | 
 |             except IOError, err: | 
 |                 _err_exit("Cannot save counts file %s because: %s" % \ | 
 |                           (`counts_file`, err.strerror)) | 
 |  | 
 |     elif report: | 
 |         old_counts, old_modules = marshal.load(open(counts_file, 'rb')) | 
 |         results = CoverageResults(old_counts, old_modules) | 
 |         create_results_log(results, logdir, missing) | 
 |  | 
 |     else: | 
 |         assert 0, "Should never get here" | 
 |  | 
 | if __name__=='__main__': | 
 |     main() |