"Fossies" - the Fresh Open Source Software Archive

Member "pysize-0.2/tests/coverage.py" (11 Mar 2007, 35861 Bytes) of package /linux/privat/old/pysize-0.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #!/usr/bin/python
    2 #
    3 #             Perforce Defect Tracking Integration Project
    4 #              <http://www.ravenbrook.com/project/p4dti/>
    5 #
    6 #                   COVERAGE.PY -- COVERAGE TESTING
    7 #
    8 #             Gareth Rees, Ravenbrook Limited, 2001-12-04
    9 #                     Ned Batchelder, 2004-12-12
   10 #         http://nedbatchelder.com/code/modules/coverage.html
   11 #
   12 #
   13 # 1. INTRODUCTION
   14 #
   15 # This module provides coverage testing for Python code.
   16 #
   17 # The intended readership is all Python developers.
   18 #
   19 # This document is not confidential.
   20 #
   21 # See [GDR 2001-12-04a] for the command-line interface, programmatic
   22 # interface and limitations.  See [GDR 2001-12-04b] for requirements and
   23 # design.
   24 
   25 r"""Usage:
   26 
   27 coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
   28     Execute module, passing the given command-line arguments, collecting
   29     coverage data. With the -p option, write to a temporary file containing
   30     the machine name and process ID.
   31 
   32 coverage.py -e
   33     Erase collected coverage data.
   34 
   35 coverage.py -c
   36     Collect data from multiple coverage files (as created by -p option above)
   37     and store it into a single file representing the union of the coverage.
   38 
   39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
   40     Report on the statement coverage for the given files.  With the -m
   41     option, show line numbers of the statements that weren't executed.
   42 
   43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
   44     Make annotated copies of the given files, marking statements that
   45     are executed with > and statements that are missed with !.  With
   46     the -d option, make the copies in that directory.  Without the -d
   47     option, make each copy in the same directory as the original.
   48 
   49 -o dir,dir2,...
   50   Omit reporting or annotating files when their filename path starts with
   51   a directory listed in the omit list.
   52   e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
   53 
   54 Coverage data is saved in the file .coverage by default.  Set the
   55 COVERAGE_FILE environment variable to save it somewhere else."""
   56 
   57 __version__ = "2.6.20060823"    # see detailed history at the end of this file.
   58 
   59 import compiler
   60 import compiler.visitor
   61 import os
   62 import re
   63 import string
   64 import sys
   65 import threading
   66 import types
   67 from socket import gethostname
   68 
   69 # 2. IMPLEMENTATION
   70 #
   71 # This uses the "singleton" pattern.
   72 #
   73 # The word "morf" means a module object (from which the source file can
   74 # be deduced by suitable manipulation of the __file__ attribute) or a
   75 # filename.
   76 #
   77 # When we generate a coverage report we have to canonicalize every
   78 # filename in the coverage dictionary just in case it refers to the
   79 # module we are reporting on.  It seems a shame to throw away this
   80 # information so the data in the coverage dictionary is transferred to
   81 # the 'cexecuted' dictionary under the canonical filenames.
   82 #
   83 # The coverage dictionary is called "c" and the trace function "t".  The
   84 # reason for these short names is that Python looks up variables by name
   85 # at runtime and so execution time depends on the length of variables!
   86 # In the bottleneck of this application it's appropriate to abbreviate
   87 # names to increase speed.
   88 
   89 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
   90     def __init__(self, statements, excluded, suite_spots):
   91         compiler.visitor.ASTVisitor.__init__(self)
   92         self.statements = statements
   93         self.excluded = excluded
   94         self.suite_spots = suite_spots
   95         self.excluding_suite = 0
   96         
   97     def doRecursive(self, node):
   98         self.recordNodeLine(node)
   99         for n in node.getChildNodes():
  100             self.dispatch(n)
  101 
  102     visitStmt = visitModule = doRecursive
  103     
  104     def doCode(self, node):
  105         if hasattr(node, 'decorators') and node.decorators:
  106             self.dispatch(node.decorators)
  107             self.recordAndDispatch(node.code)
  108         else:
  109             self.doSuite(node, node.code)
  110             
  111     visitFunction = visitClass = doCode
  112 
  113     def getFirstLine(self, node):
  114         # Find the first line in the tree node.
  115         lineno = node.lineno
  116         for n in node.getChildNodes():
  117             f = self.getFirstLine(n)
  118             if lineno and f:
  119                 lineno = min(lineno, f)
  120             else:
  121                 lineno = lineno or f
  122         return lineno
  123 
  124     def getLastLine(self, node):
  125         # Find the first line in the tree node.
  126         lineno = node.lineno
  127         for n in node.getChildNodes():
  128             lineno = max(lineno, self.getLastLine(n))
  129         return lineno
  130     
  131     def doStatement(self, node):
  132         self.recordLine(self.getFirstLine(node))
  133 
  134     visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
  135         visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
  136         doStatement
  137     
  138     def recordNodeLine(self, node):
  139         return self.recordLine(node.lineno)
  140     
  141     def recordLine(self, lineno):
  142         # Returns a bool, whether the line is included or excluded.
  143         if lineno:
  144             # Multi-line tests introducing suites have to get charged to their
  145             # keyword.
  146             if lineno in self.suite_spots:
  147                 lineno = self.suite_spots[lineno][0]
  148             # If we're inside an exluded suite, record that this line was
  149             # excluded.
  150             if self.excluding_suite:
  151                 self.excluded[lineno] = 1
  152                 return 0
  153             # If this line is excluded, or suite_spots maps this line to
  154             # another line that is exlcuded, then we're excluded.
  155             elif self.excluded.has_key(lineno) or \
  156                  self.suite_spots.has_key(lineno) and \
  157                  self.excluded.has_key(self.suite_spots[lineno][1]):
  158                 return 0
  159             # Otherwise, this is an executable line.
  160             else:
  161                 self.statements[lineno] = 1
  162                 return 1
  163         return 0
  164     
  165     default = recordNodeLine
  166     
  167     def recordAndDispatch(self, node):
  168         self.recordNodeLine(node)
  169         self.dispatch(node)
  170 
  171     def doSuite(self, intro, body, exclude=0):
  172         exsuite = self.excluding_suite
  173         if exclude or (intro and not self.recordNodeLine(intro)):
  174             self.excluding_suite = 1
  175         self.recordAndDispatch(body)
  176         self.excluding_suite = exsuite
  177         
  178     def doPlainWordSuite(self, prevsuite, suite):
  179         # Finding the exclude lines for else's is tricky, because they aren't
  180         # present in the compiler parse tree.  Look at the previous suite,
  181         # and find its last line.  If any line between there and the else's
  182         # first line are excluded, then we exclude the else.
  183         lastprev = self.getLastLine(prevsuite)
  184         firstelse = self.getFirstLine(suite)
  185         for l in range(lastprev+1, firstelse):
  186             if self.suite_spots.has_key(l):
  187                 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
  188                 break
  189         else:
  190             self.doSuite(None, suite)
  191         
  192     def doElse(self, prevsuite, node):
  193         if node.else_:
  194             self.doPlainWordSuite(prevsuite, node.else_)
  195     
  196     def visitFor(self, node):
  197         self.doSuite(node, node.body)
  198         self.doElse(node.body, node)
  199 
  200     def visitIf(self, node):
  201         # The first test has to be handled separately from the rest.
  202         # The first test is credited to the line with the "if", but the others
  203         # are credited to the line with the test for the elif.
  204         self.doSuite(node, node.tests[0][1])
  205         for t, n in node.tests[1:]:
  206             self.doSuite(t, n)
  207         self.doElse(node.tests[-1][1], node)
  208 
  209     def visitWhile(self, node):
  210         self.doSuite(node, node.body)
  211         self.doElse(node.body, node)
  212 
  213     def visitTryExcept(self, node):
  214         self.doSuite(node, node.body)
  215         for i in range(len(node.handlers)):
  216             a, b, h = node.handlers[i]
  217             if not a:
  218                 # It's a plain "except:".  Find the previous suite.
  219                 if i > 0:
  220                     prev = node.handlers[i-1][2]
  221                 else:
  222                     prev = node.body
  223                 self.doPlainWordSuite(prev, h)
  224             else:
  225                 self.doSuite(a, h)
  226         self.doElse(node.handlers[-1][2], node)
  227     
  228     def visitTryFinally(self, node):
  229         self.doSuite(node, node.body)
  230         self.doPlainWordSuite(node.body, node.final)
  231         
  232     def visitGlobal(self, node):
  233         # "global" statements don't execute like others (they don't call the
  234         # trace function), so don't record their line numbers.
  235         pass
  236 
  237 the_coverage = None
  238 
  239 class CoverageException(Exception): pass
  240 
  241 class coverage:
  242     # Name of the cache file (unless environment variable is set).
  243     cache_default = ".coverage"
  244 
  245     # Environment variable naming the cache file.
  246     cache_env = "COVERAGE_FILE"
  247 
  248     # A dictionary with an entry for (Python source file name, line number
  249     # in that file) if that line has been executed.
  250     c = {}
  251     
  252     # A map from canonical Python source file name to a dictionary in
  253     # which there's an entry for each line number that has been
  254     # executed.
  255     cexecuted = {}
  256 
  257     # Cache of results of calling the analysis2() method, so that you can
  258     # specify both -r and -a without doing double work.
  259     analysis_cache = {}
  260 
  261     # Cache of results of calling the canonical_filename() method, to
  262     # avoid duplicating work.
  263     canonical_filename_cache = {}
  264 
  265     def __init__(self):
  266         global the_coverage
  267         if the_coverage:
  268             raise CoverageException, "Only one coverage object allowed."
  269         self.usecache = 1
  270         self.cache = None
  271         self.exclude_re = ''
  272         self.nesting = 0
  273         self.cstack = []
  274         self.xstack = []
  275         self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
  276 
  277     # t(f, x, y).  This method is passed to sys.settrace as a trace function.  
  278     # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and 
  279     # the arguments and return value of the trace function.
  280     # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
  281     # objects.
  282     
  283     def t(self, f, w, a):                                   #pragma: no cover
  284         if w == 'line':
  285             self.c[(f.f_code.co_filename, f.f_lineno)] = 1
  286             for c in self.cstack:
  287                 c[(f.f_code.co_filename, f.f_lineno)] = 1
  288         return self.t
  289     
  290     def help(self, error=None):
  291         if error:
  292             print error
  293             print
  294         print __doc__
  295         sys.exit(1)
  296 
  297     def command_line(self, argv, help=None):
  298         import getopt
  299         help = help or self.help
  300         settings = {}
  301         optmap = {
  302             '-a': 'annotate',
  303             '-c': 'collect',
  304             '-d:': 'directory=',
  305             '-e': 'erase',
  306             '-h': 'help',
  307             '-i': 'ignore-errors',
  308             '-m': 'show-missing',
  309             '-p': 'parallel-mode',
  310             '-r': 'report',
  311             '-x': 'execute',
  312             '-o:': 'omit=',
  313             }
  314         short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
  315         long_opts = optmap.values()
  316         options, args = getopt.getopt(argv, short_opts, long_opts)
  317         for o, a in options:
  318             if optmap.has_key(o):
  319                 settings[optmap[o]] = 1
  320             elif optmap.has_key(o + ':'):
  321                 settings[optmap[o + ':']] = a
  322             elif o[2:] in long_opts:
  323                 settings[o[2:]] = 1
  324             elif o[2:] + '=' in long_opts:
  325                 settings[o[2:]+'='] = a
  326             else:       #pragma: no cover
  327                 pass    # Can't get here, because getopt won't return anything unknown.
  328 
  329         if settings.get('help'):
  330             help()
  331 
  332         for i in ['erase', 'execute']:
  333             for j in ['annotate', 'report', 'collect']:
  334                 if settings.get(i) and settings.get(j):
  335                     help("You can't specify the '%s' and '%s' "
  336                               "options at the same time." % (i, j))
  337 
  338         args_needed = (settings.get('execute')
  339                        or settings.get('annotate')
  340                        or settings.get('report'))
  341         action = (settings.get('erase') 
  342                   or settings.get('collect')
  343                   or args_needed)
  344         if not action:
  345             help("You must specify at least one of -e, -x, -c, -r, or -a.")
  346         if not args_needed and args:
  347             help("Unexpected arguments: %s" % " ".join(args))
  348         
  349         self.get_ready(settings.get('parallel-mode'))
  350         self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
  351 
  352         if settings.get('erase'):
  353             self.erase()
  354         if settings.get('execute'):
  355             if not args:
  356                 help("Nothing to do.")
  357             sys.argv = args
  358             self.start()
  359             import __main__
  360             sys.path[0] = os.path.dirname(sys.argv[0])
  361             execfile(sys.argv[0], __main__.__dict__)
  362         if settings.get('collect'):
  363             self.collect()
  364         if not args:
  365             args = self.cexecuted.keys()
  366         
  367         ignore_errors = settings.get('ignore-errors')
  368         show_missing = settings.get('show-missing')
  369         directory = settings.get('directory=')
  370 
  371         omit = settings.get('omit=')
  372         if omit is not None:
  373             omit = omit.split(',')
  374         else:
  375             omit = []
  376 
  377         if settings.get('report'):
  378             self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
  379         if settings.get('annotate'):
  380             self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
  381 
  382     def use_cache(self, usecache, cache_file=None):
  383         self.usecache = usecache
  384         if cache_file and not self.cache:
  385             self.cache_default = cache_file
  386         
  387     def get_ready(self, parallel_mode=False):
  388         if self.usecache and not self.cache:
  389             self.cache = os.environ.get(self.cache_env, self.cache_default)
  390             if parallel_mode:
  391                 self.cache += "." + gethostname() + "." + str(os.getpid())
  392             self.restore()
  393         self.analysis_cache = {}
  394         
  395     def start(self, parallel_mode=False):
  396         self.get_ready(parallel_mode)
  397         if self.nesting == 0:                               #pragma: no cover
  398             sys.settrace(self.t)
  399             if hasattr(threading, 'settrace'):
  400                 threading.settrace(self.t)
  401         self.nesting += 1
  402         
  403     def stop(self):
  404         self.nesting -= 1
  405         if self.nesting == 0:                               #pragma: no cover
  406             sys.settrace(None)
  407             if hasattr(threading, 'settrace'):
  408                 threading.settrace(None)
  409 
  410     def erase(self):
  411         self.c = {}
  412         self.analysis_cache = {}
  413         self.cexecuted = {}
  414         if self.cache and os.path.exists(self.cache):
  415             os.remove(self.cache)
  416         self.exclude_re = ""
  417 
  418     def exclude(self, re):
  419         if self.exclude_re:
  420             self.exclude_re += "|"
  421         self.exclude_re += "(" + re + ")"
  422 
  423     def begin_recursive(self):
  424         self.cstack.append(self.c)
  425         self.xstack.append(self.exclude_re)
  426         
  427     def end_recursive(self):
  428         self.c = self.cstack.pop()
  429         self.exclude_re = self.xstack.pop()
  430 
  431     # save().  Save coverage data to the coverage cache.
  432 
  433     def save(self):
  434         if self.usecache and self.cache:
  435             self.canonicalize_filenames()
  436             cache = open(self.cache, 'wb')
  437             import marshal
  438             marshal.dump(self.cexecuted, cache)
  439             cache.close()
  440 
  441     # restore().  Restore coverage data from the coverage cache (if it exists).
  442 
  443     def restore(self):
  444         self.c = {}
  445         self.cexecuted = {}
  446         assert self.usecache
  447         if os.path.exists(self.cache):
  448             self.cexecuted = self.restore_file(self.cache)
  449 
  450     def restore_file(self, file_name):
  451         try:
  452             cache = open(file_name, 'rb')
  453             import marshal
  454             cexecuted = marshal.load(cache)
  455             cache.close()
  456             if isinstance(cexecuted, types.DictType):
  457                 return cexecuted
  458             else:
  459                 return {}
  460         except:
  461             return {}
  462 
  463     # collect(). Collect data in multiple files produced by parallel mode
  464 
  465     def collect(self):
  466         cache_dir, local = os.path.split(self.cache)
  467         for file in os.listdir(cache_dir):
  468             if not file.startswith(local):
  469                 continue
  470 
  471             full_path = os.path.join(cache_dir, file)
  472             cexecuted = self.restore_file(full_path)
  473             self.merge_data(cexecuted)
  474 
  475     def merge_data(self, new_data):
  476         for file_name, file_data in new_data.items():
  477             if self.cexecuted.has_key(file_name):
  478                 self.merge_file_data(self.cexecuted[file_name], file_data)
  479             else:
  480                 self.cexecuted[file_name] = file_data
  481 
  482     def merge_file_data(self, cache_data, new_data):
  483         for line_number in new_data.keys():
  484             if not cache_data.has_key(line_number):
  485                 cache_data[line_number] = new_data[line_number]
  486 
  487     # canonical_filename(filename).  Return a canonical filename for the
  488     # file (that is, an absolute path with no redundant components and
  489     # normalized case).  See [GDR 2001-12-04b, 3.3].
  490 
  491     def canonical_filename(self, filename):
  492         if not self.canonical_filename_cache.has_key(filename):
  493             f = filename
  494             if os.path.isabs(f) and not os.path.exists(f):
  495                 f = os.path.basename(f)
  496             if not os.path.isabs(f):
  497                 for path in [os.curdir] + sys.path:
  498                     g = os.path.join(path, f)
  499                     if os.path.exists(g):
  500                         f = g
  501                         break
  502             cf = os.path.normcase(os.path.abspath(f))
  503             self.canonical_filename_cache[filename] = cf
  504         return self.canonical_filename_cache[filename]
  505 
  506     # canonicalize_filenames().  Copy results from "c" to "cexecuted", 
  507     # canonicalizing filenames on the way.  Clear the "c" map.
  508 
  509     def canonicalize_filenames(self):
  510         for filename, lineno in self.c.keys():
  511             f = self.canonical_filename(filename)
  512             if not self.cexecuted.has_key(f):
  513                 self.cexecuted[f] = {}
  514             self.cexecuted[f][lineno] = 1
  515         self.c = {}
  516 
  517     # morf_filename(morf).  Return the filename for a module or file.
  518 
  519     def morf_filename(self, morf):
  520         if isinstance(morf, types.ModuleType):
  521             if not hasattr(morf, '__file__'):
  522                 raise CoverageException, "Module has no __file__ attribute."
  523             file = morf.__file__
  524         else:
  525             file = morf
  526         return self.canonical_filename(file)
  527 
  528     # analyze_morf(morf).  Analyze the module or filename passed as
  529     # the argument.  If the source code can't be found, raise an error.
  530     # Otherwise, return a tuple of (1) the canonical filename of the
  531     # source code for the module, (2) a list of lines of statements
  532     # in the source code, and (3) a list of lines of excluded statements.
  533 
  534     def analyze_morf(self, morf):
  535         if self.analysis_cache.has_key(morf):
  536             return self.analysis_cache[morf]
  537         filename = self.morf_filename(morf)
  538         ext = os.path.splitext(filename)[1]
  539         if ext == '.pyc':
  540             if not os.path.exists(filename[0:-1]):
  541                 raise CoverageException, ("No source for compiled code '%s'."
  542                                    % filename)
  543             filename = filename[0:-1]
  544         elif ext != '.py':
  545             raise CoverageException, "File '%s' not Python source." % filename
  546         source = open(filename, 'r')
  547         lines, excluded_lines = self.find_executable_statements(
  548             source.read(), exclude=self.exclude_re
  549             )
  550         source.close()
  551         result = filename, lines, excluded_lines
  552         self.analysis_cache[morf] = result
  553         return result
  554 
  555     def get_suite_spots(self, tree, spots):
  556         import symbol, token
  557         for i in range(1, len(tree)):
  558             if type(tree[i]) == type(()):
  559                 if tree[i][0] == symbol.suite:
  560                     # Found a suite, look back for the colon and keyword.
  561                     lineno_colon = lineno_word = None
  562                     for j in range(i-1, 0, -1):
  563                         if tree[j][0] == token.COLON:
  564                             lineno_colon = tree[j][2]
  565                         elif tree[j][0] == token.NAME:
  566                             if tree[j][1] == 'elif':
  567                                 # Find the line number of the first non-terminal
  568                                 # after the keyword.
  569                                 t = tree[j+1]
  570                                 while t and token.ISNONTERMINAL(t[0]):
  571                                     t = t[1]
  572                                 if t:
  573                                     lineno_word = t[2]
  574                             else:
  575                                 lineno_word = tree[j][2]
  576                             break
  577                         elif tree[j][0] == symbol.except_clause:
  578                             # "except" clauses look like:
  579                             # ('except_clause', ('NAME', 'except', lineno), ...)
  580                             if tree[j][1][0] == token.NAME:
  581                                 lineno_word = tree[j][1][2]
  582                                 break
  583                     if lineno_colon and lineno_word:
  584                         # Found colon and keyword, mark all the lines
  585                         # between the two with the two line numbers.
  586                         for l in range(lineno_word, lineno_colon+1):
  587                             spots[l] = (lineno_word, lineno_colon)
  588                 self.get_suite_spots(tree[i], spots)
  589 
  590     def find_executable_statements(self, text, exclude=None):
  591         # Find lines which match an exclusion pattern.
  592         excluded = {}
  593         suite_spots = {}
  594         if exclude:
  595             reExclude = re.compile(exclude)
  596             lines = text.split('\n')
  597             for i in range(len(lines)):
  598                 if reExclude.search(lines[i]):
  599                     excluded[i+1] = 1
  600 
  601         import parser
  602         tree = parser.suite(text+'\n\n').totuple(1)
  603         self.get_suite_spots(tree, suite_spots)
  604             
  605         # Use the compiler module to parse the text and find the executable
  606         # statements.  We add newlines to be impervious to final partial lines.
  607         statements = {}
  608         ast = compiler.parse(text+'\n\n')
  609         visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
  610         compiler.walk(ast, visitor, walker=visitor)
  611 
  612         lines = statements.keys()
  613         lines.sort()
  614         excluded_lines = excluded.keys()
  615         excluded_lines.sort()
  616         return lines, excluded_lines
  617 
  618     # format_lines(statements, lines).  Format a list of line numbers
  619     # for printing by coalescing groups of lines as long as the lines
  620     # represent consecutive statements.  This will coalesce even if
  621     # there are gaps between statements, so if statements =
  622     # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
  623     # format_lines will return "1-2, 5-11, 13-14".
  624 
  625     def format_lines(self, statements, lines):
  626         pairs = []
  627         i = 0
  628         j = 0
  629         start = None
  630         pairs = []
  631         while i < len(statements) and j < len(lines):
  632             if statements[i] == lines[j]:
  633                 if start == None:
  634                     start = lines[j]
  635                 end = lines[j]
  636                 j = j + 1
  637             elif start:
  638                 pairs.append((start, end))
  639                 start = None
  640             i = i + 1
  641         if start:
  642             pairs.append((start, end))
  643         def stringify(pair):
  644             start, end = pair
  645             if start == end:
  646                 return "%d" % start
  647             else:
  648                 return "%d-%d" % (start, end)
  649         return string.join(map(stringify, pairs), ", ")
  650 
  651     # Backward compatibility with version 1.
  652     def analysis(self, morf):
  653         f, s, _, m, mf = self.analysis2(morf)
  654         return f, s, m, mf
  655 
  656     def analysis2(self, morf):
  657         filename, statements, excluded = self.analyze_morf(morf)
  658         self.canonicalize_filenames()
  659         if not self.cexecuted.has_key(filename):
  660             self.cexecuted[filename] = {}
  661         missing = []
  662         for line in statements:
  663             if not self.cexecuted[filename].has_key(line):
  664                 missing.append(line)
  665         return (filename, statements, excluded, missing,
  666                 self.format_lines(statements, missing))
  667 
  668     def relative_filename(self, filename):
  669         """ Convert filename to relative filename from self.relative_dir.
  670         """
  671         return filename.replace(self.relative_dir, "")
  672 
  673     def morf_name(self, morf):
  674         """ Return the name of morf as used in report.
  675         """
  676         if isinstance(morf, types.ModuleType):
  677             return morf.__name__
  678         else:
  679             return self.relative_filename(os.path.splitext(morf)[0])
  680 
  681     def filter_by_prefix(self, morfs, omit_prefixes):
  682         """ Return list of morfs where the morf name does not begin
  683             with any one of the omit_prefixes.
  684         """
  685         filtered_morfs = []
  686         for morf in morfs:
  687             for prefix in omit_prefixes:
  688                 if self.morf_name(morf).startswith(prefix):
  689                     break
  690             else:
  691                 filtered_morfs.append(morf)
  692 
  693         return filtered_morfs
  694 
  695     def morf_name_compare(self, x, y):
  696         return cmp(self.morf_name(x), self.morf_name(y))
  697 
  698     def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
  699         if not isinstance(morfs, types.ListType):
  700             morfs = [morfs]
  701         morfs = self.filter_by_prefix(morfs, omit_prefixes)
  702         morfs.sort(self.morf_name_compare)
  703 
  704         max_name = max([5,] + map(len, map(self.morf_name, morfs)))
  705         fmt_name = "%%- %ds  " % max_name
  706         fmt_err = fmt_name + "%s: %s"
  707         header = fmt_name % "Name" + " Stmts   Exec  Cover"
  708         fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
  709         if show_missing:
  710             header = header + "   Missing"
  711             fmt_coverage = fmt_coverage + "   %s"
  712         if not file:
  713             file = sys.stdout
  714         print >>file, header
  715         print >>file, "-" * len(header)
  716         total_statements = 0
  717         total_executed = 0
  718         for morf in morfs:
  719             name = self.morf_name(morf)
  720             try:
  721                 _, statements, _, missing, readable  = self.analysis2(morf)
  722                 n = len(statements)
  723                 m = n - len(missing)
  724                 if n > 0:
  725                     pc = 100.0 * m / n
  726                 else:
  727                     pc = 100.0
  728                 args = (name, n, m, pc)
  729                 if show_missing:
  730                     args = args + (readable,)
  731                 print >>file, fmt_coverage % args
  732                 total_statements = total_statements + n
  733                 total_executed = total_executed + m
  734             except KeyboardInterrupt:                       #pragma: no cover
  735                 raise
  736             except:
  737                 if not ignore_errors:
  738                     type, msg = sys.exc_info()[0:2]
  739                     print >>file, fmt_err % (name, type, msg)
  740         if len(morfs) > 1:
  741             print >>file, "-" * len(header)
  742             if total_statements > 0:
  743                 pc = 100.0 * total_executed / total_statements
  744             else:
  745                 pc = 100.0
  746             args = ("TOTAL", total_statements, total_executed, pc)
  747             if show_missing:
  748                 args = args + ("",)
  749             print >>file, fmt_coverage % args
  750 
  751     # annotate(morfs, ignore_errors).
  752 
  753     blank_re = re.compile(r"\s*(#|$)")
  754     else_re = re.compile(r"\s*else\s*:\s*(#|$)")
  755 
  756     def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
  757         morfs = self.filter_by_prefix(morfs, omit_prefixes)
  758         for morf in morfs:
  759             try:
  760                 filename, statements, excluded, missing, _ = self.analysis2(morf)
  761                 self.annotate_file(filename, statements, excluded, missing, directory)
  762             except KeyboardInterrupt:
  763                 raise
  764             except:
  765                 if not ignore_errors:
  766                     raise
  767                 
  768     def annotate_file(self, filename, statements, excluded, missing, directory=None):
  769         source = open(filename, 'r')
  770         if directory:
  771             dest_file = os.path.join(directory,
  772                                      os.path.basename(filename)
  773                                      + ',cover')
  774         else:
  775             dest_file = filename + ',cover'
  776         dest = open(dest_file, 'w')
  777         lineno = 0
  778         i = 0
  779         j = 0
  780         covered = 1
  781         while 1:
  782             line = source.readline()
  783             if line == '':
  784                 break
  785             lineno = lineno + 1
  786             while i < len(statements) and statements[i] < lineno:
  787                 i = i + 1
  788             while j < len(missing) and missing[j] < lineno:
  789                 j = j + 1
  790             if i < len(statements) and statements[i] == lineno:
  791                 covered = j >= len(missing) or missing[j] > lineno
  792             if self.blank_re.match(line):
  793                 dest.write('  ')
  794             elif self.else_re.match(line):
  795                 # Special logic for lines containing only 'else:'.  
  796                 # See [GDR 2001-12-04b, 3.2].
  797                 if i >= len(statements) and j >= len(missing):
  798                     dest.write('! ')
  799                 elif i >= len(statements) or j >= len(missing):
  800                     dest.write('> ')
  801                 elif statements[i] == missing[j]:
  802                     dest.write('! ')
  803                 else:
  804                     dest.write('> ')
  805             elif lineno in excluded:
  806                 dest.write('- ')
  807             elif covered:
  808                 dest.write('> ')
  809             else:
  810                 dest.write('! ')
  811             dest.write(line)
  812         source.close()
  813         dest.close()
  814 
  815 # Singleton object.
  816 the_coverage = coverage()
  817 
  818 # Module functions call methods in the singleton object.
  819 def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
  820 def start(*args, **kw): return the_coverage.start(*args, **kw)
  821 def stop(*args, **kw): return the_coverage.stop(*args, **kw)
  822 def erase(*args, **kw): return the_coverage.erase(*args, **kw)
  823 def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw)
  824 def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw)
  825 def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
  826 def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
  827 def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
  828 def report(*args, **kw): return the_coverage.report(*args, **kw)
  829 def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
  830 def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
  831 
  832 # Commented for pysize by Guillaume Chazarain: we don't want to save the cache
  833 # # Save coverage data when Python exits.  (The atexit module wasn't
  834 # # introduced until Python 2.0, so use sys.exitfunc when it's not
  835 # # available.)
  836 # try:
  837 #     import atexit
  838 #     atexit.register(the_coverage.save)
  839 # except ImportError:
  840 #     sys.exitfunc = the_coverage.save
  841 
  842 # Command-line interface.
  843 if __name__ == '__main__':
  844     the_coverage.command_line(sys.argv[1:])
  845 
  846 
  847 # A. REFERENCES
  848 #
  849 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
  850 # Ravenbrook Limited; 2001-12-04;
  851 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
  852 #
  853 # [GDR 2001-12-04b] "Statement coverage for Python: design and
  854 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
  855 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
  856 #
  857 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
  858 # Guide van Rossum; 2001-07-20;
  859 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
  860 #
  861 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
  862 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
  863 #
  864 #
  865 # B. DOCUMENT HISTORY
  866 #
  867 # 2001-12-04 GDR Created.
  868 #
  869 # 2001-12-06 GDR Added command-line interface and source code
  870 # annotation.
  871 #
  872 # 2001-12-09 GDR Moved design and interface to separate documents.
  873 #
  874 # 2001-12-10 GDR Open cache file as binary on Windows.  Allow
  875 # simultaneous -e and -x, or -a and -r.
  876 #
  877 # 2001-12-12 GDR Added command-line help.  Cache analysis so that it
  878 # only needs to be done once when you specify -a and -r.
  879 #
  880 # 2001-12-13 GDR Improved speed while recording.  Portable between
  881 # Python 1.5.2 and 2.1.1.
  882 #
  883 # 2002-01-03 GDR Module-level functions work correctly.
  884 #
  885 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
  886 # so that it matches the value the program would get if it were run on
  887 # its own.
  888 #
  889 # 2004-12-12 NMB Significant code changes.
  890 # - Finding executable statements has been rewritten so that docstrings and
  891 #   other quirks of Python execution aren't mistakenly identified as missing
  892 #   lines.
  893 # - Lines can be excluded from consideration, even entire suites of lines.
  894 # - The filesystem cache of covered lines can be disabled programmatically.
  895 # - Modernized the code.
  896 #
  897 # 2004-12-14 NMB Minor tweaks.  Return 'analysis' to its original behavior
  898 # and add 'analysis2'.  Add a global for 'annotate', and factor it, adding
  899 # 'annotate_file'.
  900 #
  901 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
  902 # Thanks, Allen.
  903 #
  904 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
  905 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be 
  906 # captured to a different destination.
  907 #
  908 # 2005-12-03 NMB coverage.py can now measure itself.
  909 #
  910 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
  911 # and sorting and omitting files to report on.
  912 #
  913 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
  914 #
  915 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
  916 # handling.
  917 #
  918 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
  919 #
  920 # 2006-08-23 NMB Refactorings to improve testability.  Fixes to command-line
  921 # logic for parallel mode and collect.
  922 
  923 # C. COPYRIGHT AND LICENCE
  924 #
  925 # Copyright 2001 Gareth Rees.  All rights reserved.
  926 # Copyright 2004-2006 Ned Batchelder.  All rights reserved.
  927 #
  928 # Redistribution and use in source and binary forms, with or without
  929 # modification, are permitted provided that the following conditions are
  930 # met:
  931 #
  932 # 1. Redistributions of source code must retain the above copyright
  933 #    notice, this list of conditions and the following disclaimer.
  934 #
  935 # 2. Redistributions in binary form must reproduce the above copyright
  936 #    notice, this list of conditions and the following disclaimer in the
  937 #    documentation and/or other materials provided with the
  938 #    distribution.
  939 #
  940 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  941 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  942 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  943 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  944 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  945 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  946 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  947 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  948 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  949 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  950 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  951 # DAMAGE.
  952 #
  953 # $Id: coverage.py 47 2006-08-24 01:08:48Z Ned $