tests/coverage.py
changeset 3223 53e843840349
parent 2144 d3bddedfdbd0
child 4122 306055f5b65c
equal deleted inserted replaced
3222:a5603ad915c5 3223:53e843840349
    85         compiler.visitor.ASTVisitor.__init__(self)
    85         compiler.visitor.ASTVisitor.__init__(self)
    86         self.statements = statements
    86         self.statements = statements
    87         self.excluded = excluded
    87         self.excluded = excluded
    88         self.suite_spots = suite_spots
    88         self.suite_spots = suite_spots
    89         self.excluding_suite = 0
    89         self.excluding_suite = 0
    90         
    90 
    91     def doRecursive(self, node):
    91     def doRecursive(self, node):
    92         self.recordNodeLine(node)
    92         self.recordNodeLine(node)
    93         for n in node.getChildNodes():
    93         for n in node.getChildNodes():
    94             self.dispatch(n)
    94             self.dispatch(n)
    95 
    95 
    96     visitStmt = visitModule = doRecursive
    96     visitStmt = visitModule = doRecursive
    97     
    97 
    98     def doCode(self, node):
    98     def doCode(self, node):
    99         if hasattr(node, 'decorators') and node.decorators:
    99         if hasattr(node, 'decorators') and node.decorators:
   100             self.dispatch(node.decorators)
   100             self.dispatch(node.decorators)
   101         self.doSuite(node, node.code)
   101         self.doSuite(node, node.code)
   102     
   102 
   103     visitFunction = visitClass = doCode
   103     visitFunction = visitClass = doCode
   104 
   104 
   105     def getFirstLine(self, node):
   105     def getFirstLine(self, node):
   106         # Find the first line in the tree node.
   106         # Find the first line in the tree node.
   107         lineno = node.lineno
   107         lineno = node.lineno
   117         # Find the first line in the tree node.
   117         # Find the first line in the tree node.
   118         lineno = node.lineno
   118         lineno = node.lineno
   119         for n in node.getChildNodes():
   119         for n in node.getChildNodes():
   120             lineno = max(lineno, self.getLastLine(n))
   120             lineno = max(lineno, self.getLastLine(n))
   121         return lineno
   121         return lineno
   122     
   122 
   123     def doStatement(self, node):
   123     def doStatement(self, node):
   124         self.recordLine(self.getFirstLine(node))
   124         self.recordLine(self.getFirstLine(node))
   125 
   125 
   126     visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
   126     visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
   127         visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
   127         visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
   128         doStatement
   128         doStatement
   129     
   129 
   130     def recordNodeLine(self, node):
   130     def recordNodeLine(self, node):
   131         return self.recordLine(node.lineno)
   131         return self.recordLine(node.lineno)
   132     
   132 
   133     def recordLine(self, lineno):
   133     def recordLine(self, lineno):
   134         # Returns a bool, whether the line is included or excluded.
   134         # Returns a bool, whether the line is included or excluded.
   135         if lineno:
   135         if lineno:
   136             # Multi-line tests introducing suites have to get charged to their
   136             # Multi-line tests introducing suites have to get charged to their
   137             # keyword.
   137             # keyword.
   151             # Otherwise, this is an executable line.
   151             # Otherwise, this is an executable line.
   152             else:
   152             else:
   153                 self.statements[lineno] = 1
   153                 self.statements[lineno] = 1
   154                 return 1
   154                 return 1
   155         return 0
   155         return 0
   156     
   156 
   157     default = recordNodeLine
   157     default = recordNodeLine
   158     
   158 
   159     def recordAndDispatch(self, node):
   159     def recordAndDispatch(self, node):
   160         self.recordNodeLine(node)
   160         self.recordNodeLine(node)
   161         self.dispatch(node)
   161         self.dispatch(node)
   162 
   162 
   163     def doSuite(self, intro, body, exclude=0):
   163     def doSuite(self, intro, body, exclude=0):
   164         exsuite = self.excluding_suite
   164         exsuite = self.excluding_suite
   165         if exclude or (intro and not self.recordNodeLine(intro)):
   165         if exclude or (intro and not self.recordNodeLine(intro)):
   166             self.excluding_suite = 1
   166             self.excluding_suite = 1
   167         self.recordAndDispatch(body)
   167         self.recordAndDispatch(body)
   168         self.excluding_suite = exsuite
   168         self.excluding_suite = exsuite
   169         
   169 
   170     def doPlainWordSuite(self, prevsuite, suite):
   170     def doPlainWordSuite(self, prevsuite, suite):
   171         # Finding the exclude lines for else's is tricky, because they aren't
   171         # Finding the exclude lines for else's is tricky, because they aren't
   172         # present in the compiler parse tree.  Look at the previous suite,
   172         # present in the compiler parse tree.  Look at the previous suite,
   173         # and find its last line.  If any line between there and the else's
   173         # and find its last line.  If any line between there and the else's
   174         # first line are excluded, then we exclude the else.
   174         # first line are excluded, then we exclude the else.
   178             if self.suite_spots.has_key(l):
   178             if self.suite_spots.has_key(l):
   179                 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
   179                 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
   180                 break
   180                 break
   181         else:
   181         else:
   182             self.doSuite(None, suite)
   182             self.doSuite(None, suite)
   183         
   183 
   184     def doElse(self, prevsuite, node):
   184     def doElse(self, prevsuite, node):
   185         if node.else_:
   185         if node.else_:
   186             self.doPlainWordSuite(prevsuite, node.else_)
   186             self.doPlainWordSuite(prevsuite, node.else_)
   187     
   187 
   188     def visitFor(self, node):
   188     def visitFor(self, node):
   189         self.doSuite(node, node.body)
   189         self.doSuite(node, node.body)
   190         self.doElse(node.body, node)
   190         self.doElse(node.body, node)
   191 
   191 
   192     def visitIf(self, node):
   192     def visitIf(self, node):
   214                     prev = node.body
   214                     prev = node.body
   215                 self.doPlainWordSuite(prev, h)
   215                 self.doPlainWordSuite(prev, h)
   216             else:
   216             else:
   217                 self.doSuite(a, h)
   217                 self.doSuite(a, h)
   218         self.doElse(node.handlers[-1][2], node)
   218         self.doElse(node.handlers[-1][2], node)
   219     
   219 
   220     def visitTryFinally(self, node):
   220     def visitTryFinally(self, node):
   221         self.doSuite(node, node.body)
   221         self.doSuite(node, node.body)
   222         self.doPlainWordSuite(node.body, node.final)
   222         self.doPlainWordSuite(node.body, node.final)
   223         
   223 
   224     def visitGlobal(self, node):
   224     def visitGlobal(self, node):
   225         # "global" statements don't execute like others (they don't call the
   225         # "global" statements don't execute like others (they don't call the
   226         # trace function), so don't record their line numbers.
   226         # trace function), so don't record their line numbers.
   227         pass
   227         pass
   228 
   228 
   238     cache_env = "COVERAGE_FILE"
   238     cache_env = "COVERAGE_FILE"
   239 
   239 
   240     # A dictionary with an entry for (Python source file name, line number
   240     # A dictionary with an entry for (Python source file name, line number
   241     # in that file) if that line has been executed.
   241     # in that file) if that line has been executed.
   242     c = {}
   242     c = {}
   243     
   243 
   244     # A map from canonical Python source file name to a dictionary in
   244     # A map from canonical Python source file name to a dictionary in
   245     # which there's an entry for each line number that has been
   245     # which there's an entry for each line number that has been
   246     # executed.
   246     # executed.
   247     cexecuted = {}
   247     cexecuted = {}
   248 
   248 
   264         self.nesting = 0
   264         self.nesting = 0
   265         self.cstack = []
   265         self.cstack = []
   266         self.xstack = []
   266         self.xstack = []
   267         self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
   267         self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
   268 
   268 
   269     # t(f, x, y).  This method is passed to sys.settrace as a trace function.  
   269     # t(f, x, y).  This method is passed to sys.settrace as a trace function.
   270     # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and 
   270     # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
   271     # the arguments and return value of the trace function.
   271     # the arguments and return value of the trace function.
   272     # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
   272     # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
   273     # objects.
   273     # objects.
   274     
   274 
   275     def t(self, f, w, a):                                   #pragma: no cover
   275     def t(self, f, w, a):                                   #pragma: no cover
   276         #print w, f.f_code.co_filename, f.f_lineno
   276         #print w, f.f_code.co_filename, f.f_lineno
   277         if w == 'line':
   277         if w == 'line':
   278             self.c[(f.f_code.co_filename, f.f_lineno)] = 1
   278             self.c[(f.f_code.co_filename, f.f_lineno)] = 1
   279             for c in self.cstack:
   279             for c in self.cstack:
   280                 c[(f.f_code.co_filename, f.f_lineno)] = 1
   280                 c[(f.f_code.co_filename, f.f_lineno)] = 1
   281         return self.t
   281         return self.t
   282     
   282 
   283     def help(self, error=None):
   283     def help(self, error=None):
   284         if error:
   284         if error:
   285             print error
   285             print error
   286             print
   286             print
   287         print __doc__
   287         print __doc__
   328         action = settings.get('erase') or args_needed
   328         action = settings.get('erase') or args_needed
   329         if not action:
   329         if not action:
   330             self.help("You must specify at least one of -e, -x, -r, or -a.")
   330             self.help("You must specify at least one of -e, -x, -r, or -a.")
   331         if not args_needed and args:
   331         if not args_needed and args:
   332             self.help("Unexpected arguments %s." % args)
   332             self.help("Unexpected arguments %s." % args)
   333         
   333 
   334         self.get_ready()
   334         self.get_ready()
   335         self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
   335         self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
   336 
   336 
   337         if settings.get('erase'):
   337         if settings.get('erase'):
   338             self.erase()
   338             self.erase()
   357         if settings.get('annotate'):
   357         if settings.get('annotate'):
   358             self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
   358             self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
   359 
   359 
   360     def use_cache(self, usecache):
   360     def use_cache(self, usecache):
   361         self.usecache = usecache
   361         self.usecache = usecache
   362         
   362 
   363     def get_ready(self):
   363     def get_ready(self):
   364         if self.usecache and not self.cache:
   364         if self.usecache and not self.cache:
   365             self.cache = os.path.abspath(os.environ.get(self.cache_env,
   365             self.cache = os.path.abspath(os.environ.get(self.cache_env,
   366                                                         self.cache_default))
   366                                                         self.cache_default))
   367             self.restore()
   367             self.restore()
   368         self.analysis_cache = {}
   368         self.analysis_cache = {}
   369         
   369 
   370     def start(self):
   370     def start(self):
   371         self.get_ready()
   371         self.get_ready()
   372         if self.nesting == 0:                               #pragma: no cover
   372         if self.nesting == 0:                               #pragma: no cover
   373             sys.settrace(self.t)
   373             sys.settrace(self.t)
   374             if hasattr(threading, 'settrace'):
   374             if hasattr(threading, 'settrace'):
   375                 threading.settrace(self.t)
   375                 threading.settrace(self.t)
   376         self.nesting += 1
   376         self.nesting += 1
   377         
   377 
   378     def stop(self):
   378     def stop(self):
   379         self.nesting -= 1
   379         self.nesting -= 1
   380         if self.nesting == 0:                               #pragma: no cover
   380         if self.nesting == 0:                               #pragma: no cover
   381             sys.settrace(None)
   381             sys.settrace(None)
   382             if hasattr(threading, 'settrace'):
   382             if hasattr(threading, 'settrace'):
   396         self.exclude_re += "(" + re + ")"
   396         self.exclude_re += "(" + re + ")"
   397 
   397 
   398     def begin_recursive(self):
   398     def begin_recursive(self):
   399         self.cstack.append(self.c)
   399         self.cstack.append(self.c)
   400         self.xstack.append(self.exclude_re)
   400         self.xstack.append(self.exclude_re)
   401         
   401 
   402     def end_recursive(self):
   402     def end_recursive(self):
   403         self.c = self.cstack.pop()
   403         self.c = self.cstack.pop()
   404         self.exclude_re = self.xstack.pop()
   404         self.exclude_re = self.xstack.pop()
   405 
   405 
   406     # save().  Save coverage data to the coverage cache.
   406     # save().  Save coverage data to the coverage cache.
   450                         break
   450                         break
   451             cf = os.path.normcase(os.path.abspath(f))
   451             cf = os.path.normcase(os.path.abspath(f))
   452             self.canonical_filename_cache[filename] = cf
   452             self.canonical_filename_cache[filename] = cf
   453         return self.canonical_filename_cache[filename]
   453         return self.canonical_filename_cache[filename]
   454 
   454 
   455     # canonicalize_filenames().  Copy results from "c" to "cexecuted", 
   455     # canonicalize_filenames().  Copy results from "c" to "cexecuted",
   456     # canonicalizing filenames on the way.  Clear the "c" map.
   456     # canonicalizing filenames on the way.  Clear the "c" map.
   457 
   457 
   458     def canonicalize_filenames(self):
   458     def canonicalize_filenames(self):
   459         for filename, lineno in self.c.keys():
   459         for filename, lineno in self.c.keys():
   460             f = self.canonical_filename(filename)
   460             f = self.canonical_filename(filename)
   548                     excluded[i+1] = 1
   548                     excluded[i+1] = 1
   549 
   549 
   550         import parser
   550         import parser
   551         tree = parser.suite(text+'\n\n').totuple(1)
   551         tree = parser.suite(text+'\n\n').totuple(1)
   552         self.get_suite_spots(tree, suite_spots)
   552         self.get_suite_spots(tree, suite_spots)
   553             
   553 
   554         # Use the compiler module to parse the text and find the executable
   554         # Use the compiler module to parse the text and find the executable
   555         # statements.  We add newlines to be impervious to final partial lines.
   555         # statements.  We add newlines to be impervious to final partial lines.
   556         statements = {}
   556         statements = {}
   557         ast = compiler.parse(text+'\n\n')
   557         ast = compiler.parse(text+'\n\n')
   558         visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
   558         visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
   711             except KeyboardInterrupt:
   711             except KeyboardInterrupt:
   712                 raise
   712                 raise
   713             except:
   713             except:
   714                 if not ignore_errors:
   714                 if not ignore_errors:
   715                     raise
   715                     raise
   716                 
   716 
   717     def annotate_file(self, filename, statements, excluded, missing, directory=None):
   717     def annotate_file(self, filename, statements, excluded, missing, directory=None):
   718         source = open(filename, 'r')
   718         source = open(filename, 'r')
   719         if directory:
   719         if directory:
   720             dest_file = os.path.join(directory,
   720             dest_file = os.path.join(directory,
   721                                      os.path.basename(filename)
   721                                      os.path.basename(filename)
   739             if i < len(statements) and statements[i] == lineno:
   739             if i < len(statements) and statements[i] == lineno:
   740                 covered = j >= len(missing) or missing[j] > lineno
   740                 covered = j >= len(missing) or missing[j] > lineno
   741             if self.blank_re.match(line):
   741             if self.blank_re.match(line):
   742                 dest.write('  ')
   742                 dest.write('  ')
   743             elif self.else_re.match(line):
   743             elif self.else_re.match(line):
   744                 # Special logic for lines containing only 'else:'.  
   744                 # Special logic for lines containing only 'else:'.
   745                 # See [GDR 2001-12-04b, 3.2].
   745                 # See [GDR 2001-12-04b, 3.2].
   746                 if i >= len(statements) and j >= len(missing):
   746                 if i >= len(statements) and j >= len(missing):
   747                     dest.write('! ')
   747                     dest.write('! ')
   748                 elif i >= len(statements) or j >= len(missing):
   748                 elif i >= len(statements) or j >= len(missing):
   749                     dest.write('> ')
   749                     dest.write('> ')
   848 #
   848 #
   849 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
   849 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
   850 # Thanks, Allen.
   850 # Thanks, Allen.
   851 #
   851 #
   852 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
   852 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
   853 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be 
   853 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
   854 # captured to a different destination.
   854 # captured to a different destination.
   855 #
   855 #
   856 # 2005-12-03 NMB coverage.py can now measure itself.
   856 # 2005-12-03 NMB coverage.py can now measure itself.
   857 #
   857 #
   858 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
   858 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,