tests/coverage.py
changeset 10282 08a0f04b56bd
parent 7047 277c91fe8384
child 10319 86a6bc519592
equal deleted inserted replaced
10281:e7d3b509af8b 10282:08a0f04b56bd
   104         compiler.visitor.ASTVisitor.__init__(self)
   104         compiler.visitor.ASTVisitor.__init__(self)
   105         self.statements = statements
   105         self.statements = statements
   106         self.excluded = excluded
   106         self.excluded = excluded
   107         self.suite_spots = suite_spots
   107         self.suite_spots = suite_spots
   108         self.excluding_suite = 0
   108         self.excluding_suite = 0
   109         
   109 
   110     def doRecursive(self, node):
   110     def doRecursive(self, node):
   111         for n in node.getChildNodes():
   111         for n in node.getChildNodes():
   112             self.dispatch(n)
   112             self.dispatch(n)
   113 
   113 
   114     visitStmt = visitModule = doRecursive
   114     visitStmt = visitModule = doRecursive
   115     
   115 
   116     def doCode(self, node):
   116     def doCode(self, node):
   117         if hasattr(node, 'decorators') and node.decorators:
   117         if hasattr(node, 'decorators') and node.decorators:
   118             self.dispatch(node.decorators)
   118             self.dispatch(node.decorators)
   119             self.recordAndDispatch(node.code)
   119             self.recordAndDispatch(node.code)
   120         else:
   120         else:
   121             self.doSuite(node, node.code)
   121             self.doSuite(node, node.code)
   122             
   122 
   123     visitFunction = visitClass = doCode
   123     visitFunction = visitClass = doCode
   124 
   124 
   125     def getFirstLine(self, node):
   125     def getFirstLine(self, node):
   126         # Find the first line in the tree node.
   126         # Find the first line in the tree node.
   127         lineno = node.lineno
   127         lineno = node.lineno
   137         # Find the first line in the tree node.
   137         # Find the first line in the tree node.
   138         lineno = node.lineno
   138         lineno = node.lineno
   139         for n in node.getChildNodes():
   139         for n in node.getChildNodes():
   140             lineno = max(lineno, self.getLastLine(n))
   140             lineno = max(lineno, self.getLastLine(n))
   141         return lineno
   141         return lineno
   142     
   142 
   143     def doStatement(self, node):
   143     def doStatement(self, node):
   144         self.recordLine(self.getFirstLine(node))
   144         self.recordLine(self.getFirstLine(node))
   145 
   145 
   146     visitAssert = visitAssign = visitAssTuple = visitPrint = \
   146     visitAssert = visitAssign = visitAssTuple = visitPrint = \
   147         visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
   147         visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
   148         doStatement
   148         doStatement
   149     
   149 
   150     def visitPass(self, node):
   150     def visitPass(self, node):
   151         # Pass statements have weird interactions with docstrings.  If this
   151         # Pass statements have weird interactions with docstrings. If
   152         # pass statement is part of one of those pairs, claim that the statement
   152         # this pass statement is part of one of those pairs, claim
   153         # is on the later of the two lines.
   153         # that the statement is on the later of the two lines.
   154         l = node.lineno
   154         l = node.lineno
   155         if l:
   155         if l:
   156             lines = self.suite_spots.get(l, [l,l])
   156             lines = self.suite_spots.get(l, [l, l])
   157             self.statements[lines[1]] = 1
   157             self.statements[lines[1]] = 1
   158         
   158 
   159     def visitDiscard(self, node):
   159     def visitDiscard(self, node):
   160         # Discard nodes are statements that execute an expression, but then
   160         # Discard nodes are statements that execute an expression, but then
   161         # discard the results.  This includes function calls, so we can't 
   161         # discard the results.  This includes function calls, so we can't
   162         # ignore them all.  But if the expression is a constant, the statement
   162         # ignore them all.  But if the expression is a constant, the statement
   163         # won't be "executed", so don't count it now.
   163         # won't be "executed", so don't count it now.
   164         if node.expr.__class__.__name__ != 'Const':
   164         if node.expr.__class__.__name__ != 'Const':
   165             self.doStatement(node)
   165             self.doStatement(node)
   166 
   166 
   170         # like "global a").
   170         # like "global a").
   171         if node.__class__.__name__ != 'Stmt':
   171         if node.__class__.__name__ != 'Stmt':
   172             return self.recordLine(self.getFirstLine(node))
   172             return self.recordLine(self.getFirstLine(node))
   173         else:
   173         else:
   174             return 0
   174             return 0
   175     
   175 
   176     def recordLine(self, lineno):
   176     def recordLine(self, lineno):
   177         # Returns a bool, whether the line is included or excluded.
   177         # Returns a bool, whether the line is included or excluded.
   178         if lineno:
   178         if lineno:
   179             # Multi-line tests introducing suites have to get charged to their
   179             # Multi-line tests introducing suites have to get charged to their
   180             # keyword.
   180             # keyword.
   194             # Otherwise, this is an executable line.
   194             # Otherwise, this is an executable line.
   195             else:
   195             else:
   196                 self.statements[lineno] = 1
   196                 self.statements[lineno] = 1
   197                 return 1
   197                 return 1
   198         return 0
   198         return 0
   199     
   199 
   200     default = recordNodeLine
   200     default = recordNodeLine
   201     
   201 
   202     def recordAndDispatch(self, node):
   202     def recordAndDispatch(self, node):
   203         self.recordNodeLine(node)
   203         self.recordNodeLine(node)
   204         self.dispatch(node)
   204         self.dispatch(node)
   205 
   205 
   206     def doSuite(self, intro, body, exclude=0):
   206     def doSuite(self, intro, body, exclude=0):
   207         exsuite = self.excluding_suite
   207         exsuite = self.excluding_suite
   208         if exclude or (intro and not self.recordNodeLine(intro)):
   208         if exclude or (intro and not self.recordNodeLine(intro)):
   209             self.excluding_suite = 1
   209             self.excluding_suite = 1
   210         self.recordAndDispatch(body)
   210         self.recordAndDispatch(body)
   211         self.excluding_suite = exsuite
   211         self.excluding_suite = exsuite
   212         
   212 
   213     def doPlainWordSuite(self, prevsuite, suite):
   213     def doPlainWordSuite(self, prevsuite, suite):
   214         # Finding the exclude lines for else's is tricky, because they aren't
   214         # Finding the exclude lines for else's is tricky, because they aren't
   215         # present in the compiler parse tree.  Look at the previous suite,
   215         # present in the compiler parse tree.  Look at the previous suite,
   216         # and find its last line.  If any line between there and the else's
   216         # and find its last line.  If any line between there and the else's
   217         # first line are excluded, then we exclude the else.
   217         # first line are excluded, then we exclude the else.
   218         lastprev = self.getLastLine(prevsuite)
   218         lastprev = self.getLastLine(prevsuite)
   219         firstelse = self.getFirstLine(suite)
   219         firstelse = self.getFirstLine(suite)
   220         for l in range(lastprev+1, firstelse):
   220         for l in range(lastprev + 1, firstelse):
   221             if self.suite_spots.has_key(l):
   221             if self.suite_spots.has_key(l):
   222                 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
   222                 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
   223                 break
   223                 break
   224         else:
   224         else:
   225             self.doSuite(None, suite)
   225             self.doSuite(None, suite)
   226         
   226 
   227     def doElse(self, prevsuite, node):
   227     def doElse(self, prevsuite, node):
   228         if node.else_:
   228         if node.else_:
   229             self.doPlainWordSuite(prevsuite, node.else_)
   229             self.doPlainWordSuite(prevsuite, node.else_)
   230     
   230 
   231     def visitFor(self, node):
   231     def visitFor(self, node):
   232         self.doSuite(node, node.body)
   232         self.doSuite(node, node.body)
   233         self.doElse(node.body, node)
   233         self.doElse(node.body, node)
   234 
   234 
   235     visitWhile = visitFor
   235     visitWhile = visitFor
   248         for i in range(len(node.handlers)):
   248         for i in range(len(node.handlers)):
   249             a, b, h = node.handlers[i]
   249             a, b, h = node.handlers[i]
   250             if not a:
   250             if not a:
   251                 # It's a plain "except:".  Find the previous suite.
   251                 # It's a plain "except:".  Find the previous suite.
   252                 if i > 0:
   252                 if i > 0:
   253                     prev = node.handlers[i-1][2]
   253                     prev = node.handlers[i - 1][2]
   254                 else:
   254                 else:
   255                     prev = node.body
   255                     prev = node.body
   256                 self.doPlainWordSuite(prev, h)
   256                 self.doPlainWordSuite(prev, h)
   257             else:
   257             else:
   258                 self.doSuite(a, h)
   258                 self.doSuite(a, h)
   259         self.doElse(node.handlers[-1][2], node)
   259         self.doElse(node.handlers[-1][2], node)
   260     
   260 
   261     def visitTryFinally(self, node):
   261     def visitTryFinally(self, node):
   262         self.doSuite(node, node.body)
   262         self.doSuite(node, node.body)
   263         self.doPlainWordSuite(node.body, node.final)
   263         self.doPlainWordSuite(node.body, node.final)
   264         
   264 
   265     def visitWith(self, node):
   265     def visitWith(self, node):
   266         self.doSuite(node, node.body)
   266         self.doSuite(node, node.body)
   267         
   267 
   268     def visitGlobal(self, node):
   268     def visitGlobal(self, node):
   269         # "global" statements don't execute like others (they don't call the
   269         # "global" statements don't execute like others (they don't call the
   270         # trace function), so don't record their line numbers.
   270         # trace function), so don't record their line numbers.
   271         pass
   271         pass
   272 
   272 
   283     cache_env = "COVERAGE_FILE"
   283     cache_env = "COVERAGE_FILE"
   284 
   284 
   285     # A dictionary with an entry for (Python source file name, line number
   285     # A dictionary with an entry for (Python source file name, line number
   286     # in that file) if that line has been executed.
   286     # in that file) if that line has been executed.
   287     c = {}
   287     c = {}
   288     
   288 
   289     # A map from canonical Python source file name to a dictionary in
   289     # A map from canonical Python source file name to a dictionary in
   290     # which there's an entry for each line number that has been
   290     # which there's an entry for each line number that has been
   291     # executed.
   291     # executed.
   292     cexecuted = {}
   292     cexecuted = {}
   293 
   293 
   311         self.cstack = []
   311         self.cstack = []
   312         self.xstack = []
   312         self.xstack = []
   313         self.relative_dir = self.abs_file(os.curdir)+os.sep
   313         self.relative_dir = self.abs_file(os.curdir)+os.sep
   314         self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
   314         self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
   315 
   315 
   316     # t(f, x, y).  This method is passed to sys.settrace as a trace function.  
   316     # t(f, x, y).  This method is passed to sys.settrace as a trace function.
   317     # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and 
   317     # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
   318     # the arguments and return value of the trace function.
   318     # the arguments and return value of the trace function.
   319     # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
   319     # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
   320     # objects.
   320     # objects.
   321     
   321     def t(self, f, w, unused): #pragma: no cover
   322     def t(self, f, w, unused):                                 #pragma: no cover
       
   323         if w == 'line':
   322         if w == 'line':
   324             self.c[(f.f_code.co_filename, f.f_lineno)] = 1
   323             self.c[(f.f_code.co_filename, f.f_lineno)] = 1
   325             #-for c in self.cstack:
   324             #-for c in self.cstack:
   326             #-    c[(f.f_code.co_filename, f.f_lineno)] = 1
   325             #-    c[(f.f_code.co_filename, f.f_lineno)] = 1
   327         return self.t
   326         return self.t
   328     
   327 
   329     def help(self, error=None):     #pragma: no cover
   328     def help(self, error=None):     #pragma: no cover
   330         if error:
   329         if error:
   331             print error
   330             print error
   332             print
   331             print
   333         print __doc__
   332         print __doc__
   361             elif o[2:] in long_opts:
   360             elif o[2:] in long_opts:
   362                 settings[o[2:]] = 1
   361                 settings[o[2:]] = 1
   363             elif o[2:] + '=' in long_opts:
   362             elif o[2:] + '=' in long_opts:
   364                 settings[o[2:]+'='] = a
   363                 settings[o[2:]+'='] = a
   365             else:       #pragma: no cover
   364             else:       #pragma: no cover
   366                 pass    # Can't get here, because getopt won't return anything unknown.
   365                 # Can't get here, because getopt won't return anything unknown.
       
   366                 pass
   367 
   367 
   368         if settings.get('help'):
   368         if settings.get('help'):
   369             help_fn()
   369             help_fn()
   370 
   370 
   371         for i in ['erase', 'execute']:
   371         for i in ['erase', 'execute']:
   375                               "options at the same time." % (i, j))
   375                               "options at the same time." % (i, j))
   376 
   376 
   377         args_needed = (settings.get('execute')
   377         args_needed = (settings.get('execute')
   378                        or settings.get('annotate')
   378                        or settings.get('annotate')
   379                        or settings.get('report'))
   379                        or settings.get('report'))
   380         action = (settings.get('erase') 
   380         action = (settings.get('erase')
   381                   or settings.get('collect')
   381                   or settings.get('collect')
   382                   or args_needed)
   382                   or args_needed)
   383         if not action:
   383         if not action:
   384             help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
   384             help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
   385         if not args_needed and args:
   385         if not args_needed and args:
   386             help_fn("Unexpected arguments: %s" % " ".join(args))
   386             help_fn("Unexpected arguments: %s" % " ".join(args))
   387         
   387 
   388         self.parallel_mode = settings.get('parallel-mode')
   388         self.parallel_mode = settings.get('parallel-mode')
   389         self.get_ready()
   389         self.get_ready()
   390 
   390 
   391         if settings.get('erase'):
   391         if settings.get('erase'):
   392             self.erase()
   392             self.erase()
   400             execfile(sys.argv[0], __main__.__dict__)
   400             execfile(sys.argv[0], __main__.__dict__)
   401         if settings.get('collect'):
   401         if settings.get('collect'):
   402             self.collect()
   402             self.collect()
   403         if not args:
   403         if not args:
   404             args = self.cexecuted.keys()
   404             args = self.cexecuted.keys()
   405         
   405 
   406         ignore_errors = settings.get('ignore-errors')
   406         ignore_errors = settings.get('ignore-errors')
   407         show_missing = settings.get('show-missing')
   407         show_missing = settings.get('show-missing')
   408         directory = settings.get('directory=')
   408         directory = settings.get('directory=')
   409 
   409 
   410         omit = settings.get('omit=')
   410         omit = settings.get('omit=')
   411         if omit is not None:
   411         if omit is not None:
   412             omit = [self.abs_file(p) for p in omit.split(',')]
   412             omit = [self.abs_file(p) for p in omit.split(',')]
   413         else:
   413         else:
   414             omit = []
   414             omit = []
   415         
   415 
   416         if settings.get('report'):
   416         if settings.get('report'):
   417             self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
   417             self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
   418         if settings.get('annotate'):
   418         if settings.get('annotate'):
   419             self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
   419             self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
   420 
   420 
   421     def use_cache(self, usecache, cache_file=None):
   421     def use_cache(self, usecache, cache_file=None):
   422         self.usecache = usecache
   422         self.usecache = usecache
   423         if cache_file and not self.cache:
   423         if cache_file and not self.cache:
   424             self.cache_default = cache_file
   424             self.cache_default = cache_file
   425         
   425 
   426     def get_ready(self, parallel_mode=False):
   426     def get_ready(self, parallel_mode=False):
   427         if self.usecache and not self.cache:
   427         if self.usecache and not self.cache:
   428             self.cache = os.environ.get(self.cache_env, self.cache_default)
   428             self.cache = os.environ.get(self.cache_env, self.cache_default)
   429             if self.parallel_mode:
   429             if self.parallel_mode:
   430                 self.cache += "." + gethostname() + "." + str(os.getpid())
   430                 self.cache += "." + gethostname() + "." + str(os.getpid())
   431             self.restore()
   431             self.restore()
   432         self.analysis_cache = {}
   432         self.analysis_cache = {}
   433         
   433 
   434     def start(self, parallel_mode=False):
   434     def start(self, parallel_mode=False):
   435         self.get_ready()
   435         self.get_ready()
   436         if self.nesting == 0:                               #pragma: no cover
   436         if self.nesting == 0:                               #pragma: no cover
   437             sys.settrace(self.t)
   437             sys.settrace(self.t)
   438             if hasattr(threading, 'settrace'):
   438             if hasattr(threading, 'settrace'):
   439                 threading.settrace(self.t)
   439                 threading.settrace(self.t)
   440         self.nesting += 1
   440         self.nesting += 1
   441         
   441 
   442     def stop(self):
   442     def stop(self):
   443         self.nesting -= 1
   443         self.nesting -= 1
   444         if self.nesting == 0:                               #pragma: no cover
   444         if self.nesting == 0:                               #pragma: no cover
   445             sys.settrace(None)
   445             sys.settrace(None)
   446             if hasattr(threading, 'settrace'):
   446             if hasattr(threading, 'settrace'):
   460         self.exclude_re += "(" + re + ")"
   460         self.exclude_re += "(" + re + ")"
   461 
   461 
   462     def begin_recursive(self):
   462     def begin_recursive(self):
   463         self.cstack.append(self.c)
   463         self.cstack.append(self.c)
   464         self.xstack.append(self.exclude_re)
   464         self.xstack.append(self.exclude_re)
   465         
   465 
   466     def end_recursive(self):
   466     def end_recursive(self):
   467         self.c = self.cstack.pop()
   467         self.c = self.cstack.pop()
   468         self.exclude_re = self.xstack.pop()
   468         self.exclude_re = self.xstack.pop()
   469 
   469 
   470     # save().  Save coverage data to the coverage cache.
   470     # save().  Save coverage data to the coverage cache.
   566                         break
   566                         break
   567             cf = self.abs_file(f)
   567             cf = self.abs_file(f)
   568             self.canonical_filename_cache[filename] = cf
   568             self.canonical_filename_cache[filename] = cf
   569         return self.canonical_filename_cache[filename]
   569         return self.canonical_filename_cache[filename]
   570 
   570 
   571     # canonicalize_filenames().  Copy results from "c" to "cexecuted", 
   571     # canonicalize_filenames().  Copy results from "c" to "cexecuted",
   572     # canonicalizing filenames on the way.  Clear the "c" map.
   572     # canonicalizing filenames on the way.  Clear the "c" map.
   573 
   573 
   574     def canonicalize_filenames(self):
   574     def canonicalize_filenames(self):
   575         for filename, lineno in self.c.keys():
   575         for filename, lineno in self.c.keys():
   576             if filename == '<string>':
   576             if filename == '<string>':
   596     # Otherwise, return a tuple of (1) the canonical filename of the
   596     # Otherwise, return a tuple of (1) the canonical filename of the
   597     # source code for the module, (2) a list of lines of statements
   597     # source code for the module, (2) a list of lines of statements
   598     # in the source code, (3) a list of lines of excluded statements,
   598     # in the source code, (3) a list of lines of excluded statements,
   599     # and (4), a map of line numbers to multi-line line number ranges, for
   599     # and (4), a map of line numbers to multi-line line number ranges, for
   600     # statements that cross lines.
   600     # statements that cross lines.
   601     
       
   602     def analyze_morf(self, morf):
   601     def analyze_morf(self, morf):
   603         if self.analysis_cache.has_key(morf):
   602         if self.analysis_cache.has_key(morf):
   604             return self.analysis_cache[morf]
   603             return self.analysis_cache[morf]
   605         filename = self.morf_filename(morf)
   604         filename = self.morf_filename(morf)
   606         ext = os.path.splitext(filename)[1]
   605         ext = os.path.splitext(filename)[1]
   634     def first_line_of_tree(self, tree):
   633     def first_line_of_tree(self, tree):
   635         while True:
   634         while True:
   636             if len(tree) == 3 and type(tree[2]) == type(1):
   635             if len(tree) == 3 and type(tree[2]) == type(1):
   637                 return tree[2]
   636                 return tree[2]
   638             tree = tree[1]
   637             tree = tree[1]
   639     
   638 
   640     def last_line_of_tree(self, tree):
   639     def last_line_of_tree(self, tree):
   641         while True:
   640         while True:
   642             if len(tree) == 3 and type(tree[2]) == type(1):
   641             if len(tree) == 3 and type(tree[2]) == type(1):
   643                 return tree[2]
   642                 return tree[2]
   644             tree = tree[-1]
   643             tree = tree[-1]
   645     
   644 
   646     def find_docstring_pass_pair(self, tree, spots):
   645     def find_docstring_pass_pair(self, tree, spots):
   647         for i in range(1, len(tree)):
   646         for i in range(1, len(tree)):
   648             if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
   647             if (self.is_string_constant(tree[i]) and
       
   648                 self.is_pass_stmt(tree[i + 1]):
   649                 first_line = self.first_line_of_tree(tree[i])
   649                 first_line = self.first_line_of_tree(tree[i])
   650                 last_line = self.last_line_of_tree(tree[i+1])
   650                 last_line = self.last_line_of_tree(tree[i + 1])
   651                 self.record_multiline(spots, first_line, last_line)
   651                 self.record_multiline(spots, first_line, last_line)
   652         
   652 
   653     def is_string_constant(self, tree):
   653     def is_string_constant(self, tree):
   654         try:
   654         try:
   655             return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
   655             return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
   656         except:
   656         except:
   657             return False
   657             return False
   658         
   658 
   659     def is_pass_stmt(self, tree):
   659     def is_pass_stmt(self, tree):
   660         try:
   660         try:
   661             return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
   661             return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
   662         except:
   662         except:
   663             return False
   663             return False
   664 
   664 
   665     def record_multiline(self, spots, i, j):
   665     def record_multiline(self, spots, i, j):
   666         for l in range(i, j+1):
   666         for l in range(i, j + 1):
   667             spots[l] = (i, j)
   667             spots[l] = (i, j)
   668             
   668 
   669     def get_suite_spots(self, tree, spots):
   669     def get_suite_spots(self, tree, spots):
   670         """ Analyze a parse tree to find suite introducers which span a number
   670         """ Analyze a parse tree to find suite introducers which span a number
   671             of lines.
   671             of lines.
   672         """
   672         """
   673         for i in range(1, len(tree)):
   673         for i in range(1, len(tree)):
   674             if type(tree[i]) == type(()):
   674             if type(tree[i]) == type(()):
   675                 if tree[i][0] == symbol.suite:
   675                 if tree[i][0] == symbol.suite:
   676                     # Found a suite, look back for the colon and keyword.
   676                     # Found a suite, look back for the colon and keyword.
   677                     lineno_colon = lineno_word = None
   677                     lineno_colon = lineno_word = None
   678                     for j in range(i-1, 0, -1):
   678                     for j in range(i - 1, 0, -1):
   679                         if tree[j][0] == token.COLON:
   679                         if tree[j][0] == token.COLON:
   680                             # Colons are never executed themselves: we want the
   680                             # Colons are never executed themselves: we want the
   681                             # line number of the last token before the colon.
   681                             # line number of the last token before the colon.
   682                             lineno_colon = self.last_line_of_tree(tree[j-1])
   682                             lineno_colon = self.last_line_of_tree(tree[j - 1])
   683                         elif tree[j][0] == token.NAME:
   683                         elif tree[j][0] == token.NAME:
   684                             if tree[j][1] == 'elif':
   684                             if tree[j][1] == 'elif':
   685                                 # Find the line number of the first non-terminal
   685                                 # Find the line number of the first
   686                                 # after the keyword.
   686                                 # non-terminal after the keyword.
   687                                 t = tree[j+1]
   687                                 t = tree[j + 1]
   688                                 while t and token.ISNONTERMINAL(t[0]):
   688                                 while t and token.ISNONTERMINAL(t[0]):
   689                                     t = t[1]
   689                                     t = t[1]
   690                                 if t:
   690                                 if t:
   691                                     lineno_word = t[2]
   691                                     lineno_word = t[2]
   692                             else:
   692                             else:
   693                                 lineno_word = tree[j][2]
   693                                 lineno_word = tree[j][2]
   694                             break
   694                             break
   695                         elif tree[j][0] == symbol.except_clause:
   695                         elif tree[j][0] == symbol.except_clause:
   696                             # "except" clauses look like:
   696                             # "except" clauses look like:
   697                             # ('except_clause', ('NAME', 'except', lineno), ...)
   697                             # ('except_clause', ('NAME', 'except', lineno),...)
   698                             if tree[j][1][0] == token.NAME:
   698                             if tree[j][1][0] == token.NAME:
   699                                 lineno_word = tree[j][1][2]
   699                                 lineno_word = tree[j][1][2]
   700                                 break
   700                                 break
   701                     if lineno_colon and lineno_word:
   701                     if lineno_colon and lineno_word:
   702                         # Found colon and keyword, mark all the lines
   702                         # Found colon and keyword, mark all the lines
   703                         # between the two with the two line numbers.
   703                         # between the two with the two line numbers.
   704                         self.record_multiline(spots, lineno_word, lineno_colon)
   704                         self.record_multiline(spots, lineno_word, lineno_colon)
   705 
   705 
   706                     # "pass" statements are tricky: different versions of Python
   706                     # "pass" statements are tricky: different versions
   707                     # treat them differently, especially in the common case of a
   707                     # of Python treat them differently, especially in
   708                     # function with a doc string and a single pass statement.
   708                     # the common case of a function with a doc string
       
   709                     # and a single pass statement.
   709                     self.find_docstring_pass_pair(tree[i], spots)
   710                     self.find_docstring_pass_pair(tree[i], spots)
   710                     
       
   711                 elif tree[i][0] == symbol.simple_stmt:
   711                 elif tree[i][0] == symbol.simple_stmt:
   712                     first_line = self.first_line_of_tree(tree[i])
   712                     first_line = self.first_line_of_tree(tree[i])
   713                     last_line = self.last_line_of_tree(tree[i])
   713                     last_line = self.last_line_of_tree(tree[i])
   714                     if first_line != last_line:
   714                     if first_line != last_line:
   715                         self.record_multiline(spots, first_line, last_line)
   715                         self.record_multiline(spots, first_line, last_line)
   722         if exclude:
   722         if exclude:
   723             reExclude = re.compile(exclude)
   723             reExclude = re.compile(exclude)
   724             lines = text.split('\n')
   724             lines = text.split('\n')
   725             for i in range(len(lines)):
   725             for i in range(len(lines)):
   726                 if reExclude.search(lines[i]):
   726                 if reExclude.search(lines[i]):
   727                     excluded[i+1] = 1
   727                     excluded[i + 1] = 1
   728 
   728 
   729         # Parse the code and analyze the parse tree to find out which statements
   729         # Parse the code and analyze the parse tree to find out which statements
   730         # are multiline, and where suites begin and end.
   730         # are multiline, and where suites begin and end.
   731         import parser
   731         import parser
   732         tree = parser.suite(text+'\n\n').totuple(1)
   732         tree = parser.suite(text+'\n\n').totuple(1)
   733         self.get_suite_spots(tree, suite_spots)
   733         self.get_suite_spots(tree, suite_spots)
   734         #print "Suite spots:", suite_spots
   734         #print "Suite spots:", suite_spots
   735         
   735 
   736         # Use the compiler module to parse the text and find the executable
   736         # Use the compiler module to parse the text and find the executable
   737         # statements.  We add newlines to be impervious to final partial lines.
   737         # statements.  We add newlines to be impervious to final partial lines.
   738         statements = {}
   738         statements = {}
   739         ast = compiler.parse(text+'\n\n')
   739         ast = compiler.parse(text+'\n\n')
   740         visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
   740         visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
   829         return filtered_morfs
   829         return filtered_morfs
   830 
   830 
   831     def morf_name_compare(self, x, y):
   831     def morf_name_compare(self, x, y):
   832         return cmp(self.morf_name(x), self.morf_name(y))
   832         return cmp(self.morf_name(x), self.morf_name(y))
   833 
   833 
   834     def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
   834     def report(self, morfs, show_missing=1, ignore_errors=0, file=None,
       
   835                omit_prefixes=[]):
   835         if not isinstance(morfs, types.ListType):
   836         if not isinstance(morfs, types.ListType):
   836             morfs = [morfs]
   837             morfs = [morfs]
   837         # On windows, the shell doesn't expand wildcards.  Do it here.
   838         # On windows, the shell doesn't expand wildcards.  Do it here.
   838         globbed = []
   839         globbed = []
   839         for morf in morfs:
   840         for morf in morfs:
   840             if isinstance(morf, strclass):
   841             if isinstance(morf, strclass):
   841                 globbed.extend(glob.glob(morf))
   842                 globbed.extend(glob.glob(morf))
   842             else:
   843             else:
   843                 globbed.append(morf)
   844                 globbed.append(morf)
   844         morfs = globbed
   845         morfs = globbed
   845         
   846 
   846         morfs = self.filter_by_prefix(morfs, omit_prefixes)
   847         morfs = self.filter_by_prefix(morfs, omit_prefixes)
   847         morfs.sort(self.morf_name_compare)
   848         morfs.sort(self.morf_name_compare)
   848 
   849 
   849         max_name = max([5,] + map(len, map(self.morf_name, morfs)))
   850         max_name = max([5,] + map(len, map(self.morf_name, morfs)))
   850         fmt_name = "%%- %ds  " % max_name
   851         fmt_name = "%%- %ds  " % max_name
   854         if show_missing:
   855         if show_missing:
   855             header = header + "   Missing"
   856             header = header + "   Missing"
   856             fmt_coverage = fmt_coverage + "   %s"
   857             fmt_coverage = fmt_coverage + "   %s"
   857         if not file:
   858         if not file:
   858             file = sys.stdout
   859             file = sys.stdout
   859         print >>file, header
   860         print >> file, header
   860         print >>file, "-" * len(header)
   861         print >> file, "-" * len(header)
   861         total_statements = 0
   862         total_statements = 0
   862         total_executed = 0
   863         total_executed = 0
   863         for morf in morfs:
   864         for morf in morfs:
   864             name = self.morf_name(morf)
   865             name = self.morf_name(morf)
   865             try:
   866             try:
   901     def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
   902     def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
   902         morfs = self.filter_by_prefix(morfs, omit_prefixes)
   903         morfs = self.filter_by_prefix(morfs, omit_prefixes)
   903         for morf in morfs:
   904         for morf in morfs:
   904             try:
   905             try:
   905                 filename, statements, excluded, missing, _ = self.analysis2(morf)
   906                 filename, statements, excluded, missing, _ = self.analysis2(morf)
   906                 self.annotate_file(filename, statements, excluded, missing, directory)
   907                 self.annotate_file(filename, statements, excluded, missing,
       
   908                                    directory)
   907             except KeyboardInterrupt:
   909             except KeyboardInterrupt:
   908                 raise
   910                 raise
   909             except:
   911             except:
   910                 if not ignore_errors:
   912                 if not ignore_errors:
   911                     raise
   913                     raise
   912                 
   914 
   913     def annotate_file(self, filename, statements, excluded, missing, directory=None):
   915     def annotate_file(self, filename, statements, excluded, missing,
       
   916                       directory=None):
   914         source = open(filename, 'r')
   917         source = open(filename, 'r')
   915         if directory:
   918         if directory:
   916             dest_file = os.path.join(directory,
   919             dest_file = os.path.join(directory,
   917                                      os.path.basename(filename)
   920                                      os.path.basename(filename)
   918                                      + ',cover')
   921                                      + ',cover')
   935             if i < len(statements) and statements[i] == lineno:
   938             if i < len(statements) and statements[i] == lineno:
   936                 covered = j >= len(missing) or missing[j] > lineno
   939                 covered = j >= len(missing) or missing[j] > lineno
   937             if self.blank_re.match(line):
   940             if self.blank_re.match(line):
   938                 dest.write('  ')
   941                 dest.write('  ')
   939             elif self.else_re.match(line):
   942             elif self.else_re.match(line):
   940                 # Special logic for lines containing only 'else:'.  
   943                 # Special logic for lines containing only 'else:'.
   941                 # See [GDR 2001-12-04b, 3.2].
   944                 # See [GDR 2001-12-04b, 3.2].
   942                 if i >= len(statements) and j >= len(missing):
   945                 if i >= len(statements) and j >= len(missing):
   943                     dest.write('! ')
   946                     dest.write('! ')
   944                 elif i >= len(statements) or j >= len(missing):
   947                 elif i >= len(statements) or j >= len(missing):
   945                     dest.write('> ')
   948                     dest.write('> ')
   959 
   962 
   960 # Singleton object.
   963 # Singleton object.
   961 the_coverage = coverage()
   964 the_coverage = coverage()
   962 
   965 
   963 # Module functions call methods in the singleton object.
   966 # Module functions call methods in the singleton object.
   964 def use_cache(*args, **kw): 
   967 def use_cache(*args, **kw):
   965     return the_coverage.use_cache(*args, **kw)
   968     return the_coverage.use_cache(*args, **kw)
   966 
   969 
   967 def start(*args, **kw): 
   970 def start(*args, **kw):
   968     return the_coverage.start(*args, **kw)
   971     return the_coverage.start(*args, **kw)
   969 
   972 
   970 def stop(*args, **kw): 
   973 def stop(*args, **kw):
   971     return the_coverage.stop(*args, **kw)
   974     return the_coverage.stop(*args, **kw)
   972 
   975 
   973 def erase(*args, **kw): 
   976 def erase(*args, **kw):
   974     return the_coverage.erase(*args, **kw)
   977     return the_coverage.erase(*args, **kw)
   975 
   978 
   976 def begin_recursive(*args, **kw): 
   979 def begin_recursive(*args, **kw):
   977     return the_coverage.begin_recursive(*args, **kw)
   980     return the_coverage.begin_recursive(*args, **kw)
   978 
   981 
   979 def end_recursive(*args, **kw): 
   982 def end_recursive(*args, **kw):
   980     return the_coverage.end_recursive(*args, **kw)
   983     return the_coverage.end_recursive(*args, **kw)
   981 
   984 
   982 def exclude(*args, **kw): 
   985 def exclude(*args, **kw):
   983     return the_coverage.exclude(*args, **kw)
   986     return the_coverage.exclude(*args, **kw)
   984 
   987 
   985 def analysis(*args, **kw): 
   988 def analysis(*args, **kw):
   986     return the_coverage.analysis(*args, **kw)
   989     return the_coverage.analysis(*args, **kw)
   987 
   990 
   988 def analysis2(*args, **kw): 
   991 def analysis2(*args, **kw):
   989     return the_coverage.analysis2(*args, **kw)
   992     return the_coverage.analysis2(*args, **kw)
   990 
   993 
   991 def report(*args, **kw): 
   994 def report(*args, **kw):
   992     return the_coverage.report(*args, **kw)
   995     return the_coverage.report(*args, **kw)
   993 
   996 
   994 def annotate(*args, **kw): 
   997 def annotate(*args, **kw):
   995     return the_coverage.annotate(*args, **kw)
   998     return the_coverage.annotate(*args, **kw)
   996 
   999 
   997 def annotate_file(*args, **kw): 
  1000 def annotate_file(*args, **kw):
   998     return the_coverage.annotate_file(*args, **kw)
  1001     return the_coverage.annotate_file(*args, **kw)
   999 
  1002 
  1000 # Save coverage data when Python exits.  (The atexit module wasn't
  1003 # Save coverage data when Python exits.  (The atexit module wasn't
  1001 # introduced until Python 2.0, so use sys.exitfunc when it's not
  1004 # introduced until Python 2.0, so use sys.exitfunc when it's not
  1002 # available.)
  1005 # available.)
  1006 except ImportError:
  1009 except ImportError:
  1007     sys.exitfunc = the_coverage.save
  1010     sys.exitfunc = the_coverage.save
  1008 
  1011 
  1009 def main():
  1012 def main():
  1010     the_coverage.command_line(sys.argv[1:])
  1013     the_coverage.command_line(sys.argv[1:])
  1011     
  1014 
  1012 # Command-line interface.
  1015 # Command-line interface.
  1013 if __name__ == '__main__':
  1016 if __name__ == '__main__':
  1014     main()
  1017     main()
  1015 
  1018 
  1016 
  1019 
  1070 #
  1073 #
  1071 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
  1074 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
  1072 # Thanks, Allen.
  1075 # Thanks, Allen.
  1073 #
  1076 #
  1074 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
  1077 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
  1075 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be 
  1078 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
  1076 # captured to a different destination.
  1079 # captured to a different destination.
  1077 #
  1080 #
  1078 # 2005-12-03 NMB coverage.py can now measure itself.
  1081 # 2005-12-03 NMB coverage.py can now measure itself.
  1079 #
  1082 #
  1080 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
  1083 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,