📄 coverage.py
字号:
#!/usr/bin/python## Perforce Defect Tracking Integration Project# <http://www.ravenbrook.com/project/p4dti/>## COVERAGE.PY -- COVERAGE TESTING## Gareth Rees, Ravenbrook Limited, 2001-12-04# Ned Batchelder, 2004-12-12# http://nedbatchelder.com/code/modules/coverage.html### 1. INTRODUCTION## This module provides coverage testing for Python code.## The intended readership is all Python developers.## This document is not confidential.## See [GDR 2001-12-04a] for the command-line interface, programmatic# interface and limitations. See [GDR 2001-12-04b] for requirements and# design.r"""\Usage:coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...] Execute module, passing the given command-line arguments, collecting coverage data. With the -p option, write to a temporary file containing the machine name and process ID.coverage.py -e Erase collected coverage data.coverage.py -c Collect data from multiple coverage files (as created by -p option above) and store it into a single file representing the union of the coverage.coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ... Report on the statement coverage for the given files. With the -m option, show line numbers of the statements that weren't executed.coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ... Make annotated copies of the given files, marking statements that are executed with > and statements that are missed with !. With the -d option, make the copies in that directory. Without the -d option, make each copy in the same directory as the original.-o dir,dir2,... Omit reporting or annotating files when their filename path starts with a directory listed in the omit list. e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traitsCoverage data is saved in the file .coverage by default. Set theCOVERAGE_FILE environment variable to save it somewhere else."""__version__ = "2.75.20070722" # see detailed history at the end of this file.import compilerimport compiler.visitorimport globimport osimport reimport stringimport symbolimport sysimport threadingimport tokenimport typesfrom socket import gethostname# Python version compatibilitytry: strclass = basestring # new to 2.3except: strclass = str# 2. IMPLEMENTATION## This uses the "singleton" pattern.## The word "morf" means a module object (from which the source file can# be deduced by suitable manipulation of the __file__ attribute) or a# filename.## When we generate a coverage report we have to canonicalize every# filename in the coverage dictionary just in case it refers to the# module we are reporting on. It seems a shame to throw away this# information so the data in the coverage dictionary is transferred to# the 'cexecuted' dictionary under the canonical filenames.## The coverage dictionary is called "c" and the trace function "t". The# reason for these short names is that Python looks up variables by name# at runtime and so execution time depends on the length of variables!# In the bottleneck of this application it's appropriate to abbreviate# names to increase speed.class StatementFindingAstVisitor(compiler.visitor.ASTVisitor): """ A visitor for a parsed Abstract Syntax Tree which finds executable statements. """ def __init__(self, statements, excluded, suite_spots): compiler.visitor.ASTVisitor.__init__(self) self.statements = statements self.excluded = excluded self.suite_spots = suite_spots self.excluding_suite = 0 def doRecursive(self, node): for n in node.getChildNodes(): self.dispatch(n) visitStmt = visitModule = doRecursive def doCode(self, node): if hasattr(node, 'decorators') and node.decorators: self.dispatch(node.decorators) self.recordAndDispatch(node.code) else: self.doSuite(node, node.code) visitFunction = visitClass = doCode def getFirstLine(self, node): # Find the first line in the tree node. lineno = node.lineno for n in node.getChildNodes(): f = self.getFirstLine(n) if lineno and f: lineno = min(lineno, f) else: lineno = lineno or f return lineno def getLastLine(self, node): # Find the first line in the tree node. lineno = node.lineno for n in node.getChildNodes(): lineno = max(lineno, self.getLastLine(n)) return lineno def doStatement(self, node): self.recordLine(self.getFirstLine(node)) visitAssert = visitAssign = visitAssTuple = visitPrint = \ visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ doStatement def visitPass(self, node): # Pass statements have weird interactions with docstrings. If this # pass statement is part of one of those pairs, claim that the statement # is on the later of the two lines. l = node.lineno if l: lines = self.suite_spots.get(l, [l,l]) self.statements[lines[1]] = 1 def visitDiscard(self, node): # Discard nodes are statements that execute an expression, but then # discard the results. This includes function calls, so we can't # ignore them all. But if the expression is a constant, the statement # won't be "executed", so don't count it now. if node.expr.__class__.__name__ != 'Const': self.doStatement(node) def recordNodeLine(self, node): # Stmt nodes often have None, but shouldn't claim the first line of # their children (because the first child might be an ignorable line # like "global a"). if node.__class__.__name__ != 'Stmt': return self.recordLine(self.getFirstLine(node)) else: return 0 def recordLine(self, lineno): # Returns a bool, whether the line is included or excluded. if lineno: # Multi-line tests introducing suites have to get charged to their # keyword. if lineno in self.suite_spots: lineno = self.suite_spots[lineno][0] # If we're inside an excluded suite, record that this line was # excluded. if self.excluding_suite: self.excluded[lineno] = 1 return 0 # If this line is excluded, or suite_spots maps this line to # another line that is exlcuded, then we're excluded. elif self.excluded.has_key(lineno) or \ self.suite_spots.has_key(lineno) and \ self.excluded.has_key(self.suite_spots[lineno][1]): return 0 # Otherwise, this is an executable line. else: self.statements[lineno] = 1 return 1 return 0 default = recordNodeLine def recordAndDispatch(self, node): self.recordNodeLine(node) self.dispatch(node) def doSuite(self, intro, body, exclude=0): exsuite = self.excluding_suite if exclude or (intro and not self.recordNodeLine(intro)): self.excluding_suite = 1 self.recordAndDispatch(body) self.excluding_suite = exsuite def doPlainWordSuite(self, prevsuite, suite): # Finding the exclude lines for else's is tricky, because they aren't # present in the compiler parse tree. Look at the previous suite, # and find its last line. If any line between there and the else's # first line are excluded, then we exclude the else. lastprev = self.getLastLine(prevsuite) firstelse = self.getFirstLine(suite) for l in range(lastprev+1, firstelse): if self.suite_spots.has_key(l): self.doSuite(None, suite, exclude=self.excluded.has_key(l)) break else: self.doSuite(None, suite) def doElse(self, prevsuite, node): if node.else_: self.doPlainWordSuite(prevsuite, node.else_) def visitFor(self, node): self.doSuite(node, node.body) self.doElse(node.body, node) visitWhile = visitFor def visitIf(self, node): # The first test has to be handled separately from the rest. # The first test is credited to the line with the "if", but the others # are credited to the line with the test for the elif. self.doSuite(node, node.tests[0][1]) for t, n in node.tests[1:]: self.doSuite(t, n) self.doElse(node.tests[-1][1], node) def visitTryExcept(self, node): self.doSuite(node, node.body) for i in range(len(node.handlers)): a, b, h = node.handlers[i] if not a: # It's a plain "except:". Find the previous suite. if i > 0: prev = node.handlers[i-1][2] else: prev = node.body self.doPlainWordSuite(prev, h) else: self.doSuite(a, h) self.doElse(node.handlers[-1][2], node) def visitTryFinally(self, node): self.doSuite(node, node.body) self.doPlainWordSuite(node.body, node.final) def visitGlobal(self, node): # "global" statements don't execute like others (they don't call the # trace function), so don't record their line numbers. passthe_coverage = Noneclass CoverageException(Exception): passclass coverage: # Name of the cache file (unless environment variable is set). cache_default = ".coverage" # Environment variable naming the cache file. cache_env = "COVERAGE_FILE" # A dictionary with an entry for (Python source file name, line number # in that file) if that line has been executed. c = {} # A map from canonical Python source file name to a dictionary in # which there's an entry for each line number that has been # executed. cexecuted = {} # Cache of results of calling the analysis2() method, so that you can # specify both -r and -a without doing double work. analysis_cache = {} # Cache of results of calling the canonical_filename() method, to # avoid duplicating work. canonical_filename_cache = {} def __init__(self): global the_coverage if the_coverage: raise CoverageException, "Only one coverage object allowed." self.usecache = 1 self.cache = None self.parallel_mode = False self.exclude_re = '' self.nesting = 0 self.cstack = [] self.xstack = [] self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep) self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]') # t(f, x, y). This method is passed to sys.settrace as a trace function. # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and # the arguments and return value of the trace function. # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code # objects. def t(self, f, w, unused): #pragma: no cover if w == 'line': #print "Executing %s @ %d" % (f.f_code.co_filename, f.f_lineno) self.c[(f.f_code.co_filename, f.f_lineno)] = 1 for c in self.cstack: c[(f.f_code.co_filename, f.f_lineno)] = 1 return self.t def help(self, error=None): #pragma: no cover if error: print error print print __doc__ sys.exit(1) def command_line(self, argv, help_fn=None): import getopt help_fn = help_fn or self.help settings = {} optmap = { '-a': 'annotate', '-c': 'collect', '-d:': 'directory=', '-e': 'erase', '-h': 'help', '-i': 'ignore-errors', '-m': 'show-missing', '-p': 'parallel-mode', '-r': 'report', '-x': 'execute', '-o:': 'omit=', } short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') long_opts = optmap.values() options, args = getopt.getopt(argv, short_opts, long_opts) for o, a in options: if optmap.has_key(o): settings[optmap[o]] = 1 elif optmap.has_key(o + ':'): settings[optmap[o + ':']] = a elif o[2:] in long_opts: settings[o[2:]] = 1 elif o[2:] + '=' in long_opts: settings[o[2:]+'='] = a else: #pragma: no cover pass # Can't get here, because getopt won't return anything unknown. if settings.get('help'): help_fn() for i in ['erase', 'execute']:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -