Package nltk :: Package test :: Module coverage
[hide private]
[frames] | no frames]

Source Code for Module nltk.test.coverage

   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  __docformat__ = 'plaintext en' 
  58  __version__ = "2.77.20070729"    # see detailed history at the end of this file. 
  59   
  60  import compiler 
  61  import compiler.visitor 
  62  import glob 
  63  import os 
  64  import re 
  65  import string 
  66  import symbol 
  67  import sys 
  68  import threading 
  69  import token 
  70  import types 
  71  from socket import gethostname 
  72   
  73  # Python version compatibility 
  74  try: 
  75      strclass = basestring   # new to 2.3 
  76  except: 
  77      strclass = str 
  78   
  79  # 2. IMPLEMENTATION 
  80  # 
  81  # This uses the "singleton" pattern. 
  82  # 
  83  # The word "morf" means a module object (from which the source file can 
  84  # be deduced by suitable manipulation of the __file__ attribute) or a 
  85  # filename. 
  86  # 
  87  # When we generate a coverage report we have to canonicalize every 
  88  # filename in the coverage dictionary just in case it refers to the 
  89  # module we are reporting on.  It seems a shame to throw away this 
  90  # information so the data in the coverage dictionary is transferred to 
  91  # the 'cexecuted' dictionary under the canonical filenames. 
  92  # 
  93  # The coverage dictionary is called "c" and the trace function "t".  The 
  94  # reason for these short names is that Python looks up variables by name 
  95  # at runtime and so execution time depends on the length of variables! 
  96  # In the bottleneck of this application it's appropriate to abbreviate 
  97  # names to increase speed. 
  98   
99 -class DefInfo:
100 FUNC = 'func' 101 CLASS = 'class'
102 - def __init__(self, node, defstart, codestart, end, coverage=None):
103 self.typ = self.type_for(node) 104 self.name = node.name 105 self.defstart = defstart 106 self.codestart = codestart 107 self.end = end 108 self.coverage = coverage
109
110 - def __repr__(self):
111 return ('DefInfo(%s, %s, %s, %s, %s, %s)' % 112 (self.typ, self.name, self.defstart, self.codestart, 113 self.end, self.coverage))
114 115 @staticmethod
116 - def type_for(node):
117 if isinstance(node, compiler.ast.Function): 118 return DefInfo.FUNC 119 else: 120 return DefInfo.CLASS
121
122 -class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
123 """ A visitor for a parsed Abstract Syntax Tree which finds executable 124 statements. 125 """
126 - def __init__(self, statements, excluded, suite_spots, definfo):
127 compiler.visitor.ASTVisitor.__init__(self) 128 self.statements = statements 129 self.excluded = excluded 130 self.suite_spots = suite_spots 131 self.excluding_suite = 0 132 self.definfo = definfo
133
134 - def doRecursive(self, node):
135 for n in node.getChildNodes(): 136 self.dispatch(n)
137 138 visitStmt = visitModule = doRecursive 139
140 - def doCode(self, node):
141 if hasattr(node, 'decorators') and node.decorators: 142 self.dispatch(node.decorators) 143 self.recordAndDispatch(node.code) 144 else: 145 self.doSuite(node, node.code) 146 147 defstart = self.getFirstLine(node) 148 codestart = self.getFirstLine(node.code) 149 end = self.getLastLine(node.code) 150 self.definfo.append(DefInfo(node, defstart, codestart, end)) 151 # When we exit a block, build up dotted names. 152 for i in range(len(self.definfo)-2, -1, -1): 153 if self.definfo[i].defstart < defstart: break 154 self.definfo[i].name = '%s.%s' % (node.name, self.definfo[i].name) 155 # When we exit a func block, hide any contained objects. 156 if len(self.definfo)>1 and self.definfo[-1].typ == DefInfo.FUNC: 157 del self.definfo[i+1:-1]
158 159 visitFunction = visitClass = doCode 160
161 - def getFirstLine(self, node):
162 # Find the first line in the tree node. 163 lineno = node.lineno 164 for n in node.getChildNodes(): 165 f = self.getFirstLine(n) 166 if lineno and f: 167 lineno = min(lineno, f) 168 else: 169 lineno = lineno or f 170 return lineno
171
172 - def getLastLine(self, node):
173 # Find the first line in the tree node. 174 lineno = node.lineno 175 for n in node.getChildNodes(): 176 lineno = max(lineno, self.getLastLine(n)) 177 return lineno
178
179 - def doStatement(self, node):
180 self.recordLine(self.getFirstLine(node))
181 182 visitAssert = visitAssign = visitAssTuple = visitPrint = \ 183 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ 184 doStatement 185
186 - def visitPass(self, node):
187 # Pass statements have weird interactions with docstrings. If this 188 # pass statement is part of one of those pairs, claim that the statement 189 # is on the later of the two lines. 190 l = node.lineno 191 if l: 192 lines = self.suite_spots.get(l, [l,l]) 193 self.statements[lines[1]] = 1
194
195 - def visitDiscard(self, node):
196 # Discard nodes are statements that execute an expression, but then 197 # discard the results. This includes function calls, so we can't 198 # ignore them all. But if the expression is a constant, the statement 199 # won't be "executed", so don't count it now. 200 if node.expr.__class__.__name__ != 'Const': 201 self.doStatement(node)
202
203 - def recordNodeLine(self, node):
204 # Stmt nodes often have None, but shouldn't claim the first line of 205 # their children (because the first child might be an ignorable line 206 # like "global a"). 207 if node.__class__.__name__ != 'Stmt': 208 return self.recordLine(self.getFirstLine(node)) 209 else: 210 return 0
211
212 - def recordLine(self, lineno):
213 # Returns a bool, whether the line is included or excluded. 214 if lineno: 215 # Multi-line tests introducing suites have to get charged to their 216 # keyword. 217 if lineno in self.suite_spots: 218 lineno = self.suite_spots[lineno][0] 219 # If we're inside an excluded suite, record that this line was 220 # excluded. 221 if self.excluding_suite: 222 self.excluded[lineno] = 1 223 return 0 224 # If this line is excluded, or suite_spots maps this line to 225 # another line that is exlcuded, then we're excluded. 226 elif self.excluded.has_key(lineno) or \ 227 self.suite_spots.has_key(lineno) and \ 228 self.excluded.has_key(self.suite_spots[lineno][1]): 229 return 0 230 # Otherwise, this is an executable line. 231 else: 232 self.statements[lineno] = 1 233 return 1 234 return 0
235 236 default = recordNodeLine 237
238 - def recordAndDispatch(self, node):
239 self.recordNodeLine(node) 240 self.dispatch(node)
241
242 - def doSuite(self, intro, body, exclude=0):
243 exsuite = self.excluding_suite 244 if exclude or (intro and not self.recordNodeLine(intro)): 245 self.excluding_suite = 1 246 self.recordAndDispatch(body) 247 self.excluding_suite = exsuite
248
249 - def doPlainWordSuite(self, prevsuite, suite):
250 # Finding the exclude lines for else's is tricky, because they aren't 251 # present in the compiler parse tree. Look at the previous suite, 252 # and find its last line. If any line between there and the else's 253 # first line are excluded, then we exclude the else. 254 lastprev = self.getLastLine(prevsuite) 255 firstelse = self.getFirstLine(suite) 256 for l in range(lastprev+1, firstelse): 257 if self.suite_spots.has_key(l): 258 self.doSuite(None, suite, exclude=self.excluded.has_key(l)) 259 break 260 else: 261 self.doSuite(None, suite)
262
263 - def doElse(self, prevsuite, node):
264 if node.else_: 265 self.doPlainWordSuite(prevsuite, node.else_)
266
267 - def visitFor(self, node):
268 self.doSuite(node, node.body) 269 self.doElse(node.body, node)
270 271 visitWhile = visitFor 272
273 - def visitIf(self, node):
274 # The first test has to be handled separately from the rest. 275 # The first test is credited to the line with the "if", but the others 276 # are credited to the line with the test for the elif. 277 self.doSuite(node, node.tests[0][1]) 278 for t, n in node.tests[1:]: 279 self.doSuite(t, n) 280 self.doElse(node.tests[-1][1], node)
281
282 - def visitTryExcept(self, node):
283 self.doSuite(node, node.body) 284 for i in range(len(node.handlers)): 285 a, b, h = node.handlers[i] 286 if not a: 287 # It's a plain "except:". Find the previous suite. 288 if i > 0: 289 prev = node.handlers[i-1][2] 290 else: 291 prev = node.body 292 self.doPlainWordSuite(prev, h) 293 else: 294 self.doSuite(a, h) 295 self.doElse(node.handlers[-1][2], node)
296
297 - def visitTryFinally(self, node):
298 self.doSuite(node, node.body) 299 self.doPlainWordSuite(node.body, node.final)
300
301 - def visitWith(self, node):
302 self.doSuite(node, node.body)
303
304 - def visitGlobal(self, node):
305 # "global" statements don't execute like others (they don't call the 306 # trace function), so don't record their line numbers. 307 pass
308 309 the_coverage = None 310
311 -class CoverageException(Exception): pass
312
313 -class coverage:
314 # Name of the cache file (unless environment variable is set). 315 cache_default = ".coverage" 316 317 # Environment variable naming the cache file. 318 cache_env = "COVERAGE_FILE" 319 320 # A dictionary with an entry for (Python source file name, line number 321 # in that file) if that line has been executed. 322 c = {} 323 324 # A map from canonical Python source file name to a dictionary in 325 # which there's an entry for each line number that has been 326 # executed. 327 cexecuted = {} 328 329 # Cache of results of calling the analysis2() method, so that you can 330 # specify both -r and -a without doing double work. 331 analysis_cache = {} 332 333 # Cache of results of calling the canonical_filename() method, to 334 # avoid duplicating work. 335 canonical_filename_cache = {} 336
337 - def __init__(self):
338 global the_coverage 339 if the_coverage: 340 raise CoverageException, "Only one coverage object allowed." 341 self.usecache = 1 342 self.cache = None 343 self.parallel_mode = False 344 self.exclude_re = '' 345 self.nesting = 0 346 self.cstack = [] 347 self.xstack = [] 348 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep) 349 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
350 351 # t(f, x, y). This method is passed to sys.settrace as a trace function. 352 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and 353 # the arguments and return value of the trace function. 354 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code 355 # objects. 356
357 - def t(self, f, w, unused): #pragma: no cover
358 if w == 'line': 359 #print "Executing %s @ %d" % (f.f_code.co_filename, f.f_lineno) 360 self.c[(f.f_code.co_filename, f.f_lineno)] = 1 361 for c in self.cstack: 362 c[(f.f_code.co_filename, f.f_lineno)] = 1 363 return self.t
364
365 - def help(self, error=None): #pragma: no cover
366 if error: 367 print error 368 print 369 print __doc__ 370 sys.exit(1) 371
372 - def command_line(self, argv, help_fn=None):
373 import getopt 374 help_fn = help_fn or self.help 375 settings = {} 376 optmap = { 377 '-a': 'annotate', 378 '-c': 'collect', 379 '-d:': 'directory=', 380 '-e': 'erase', 381 '-h': 'help', 382 '-i': 'ignore-errors', 383 '-m': 'show-missing', 384 '-p': 'parallel-mode', 385 '-r': 'report', 386 '-x': 'execute', 387 '-o:': 'omit=', 388 } 389 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') 390 long_opts = optmap.values() 391 options, args = getopt.getopt(argv, short_opts, long_opts) 392 for o, a in options: 393 if optmap.has_key(o): 394 settings[optmap[o]] = 1 395 elif optmap.has_key(o + ':'): 396 settings[optmap[o + ':']] = a 397 elif o[2:] in long_opts: 398 settings[o[2:]] = 1 399 elif o[2:] + '=' in long_opts: 400 settings[o[2:]+'='] = a 401 else: #pragma: no cover 402 pass # Can't get here, because getopt won't return anything unknown. 403 404 if settings.get('help'): 405 help_fn() 406 407 for i in ['erase', 'execute']: 408 for j in ['annotate', 'report', 'collect']: 409 if settings.get(i) and settings.get(j): 410 help_fn("You can't specify the '%s' and '%s' " 411 "options at the same time." % (i, j)) 412 413 args_needed = (settings.get('execute') 414 or settings.get('annotate') 415 or settings.get('report')) 416 action = (settings.get('erase') 417 or settings.get('collect') 418 or args_needed) 419 if not action: 420 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.") 421 if not args_needed and args: 422 help_fn("Unexpected arguments: %s" % " ".join(args)) 423 424 self.parallel_mode = settings.get('parallel-mode') 425 self.get_ready() 426 427 if settings.get('erase'): 428 self.erase() 429 if settings.get('execute'): 430 if not args: 431 help_fn("Nothing to do.") 432 sys.argv = args 433 self.start() 434 import __main__ 435 sys.path[0] = os.path.dirname(sys.argv[0]) 436 execfile(sys.argv[0], __main__.__dict__) 437 if settings.get('collect'): 438 self.collect() 439 if not args: 440 args = self.cexecuted.keys() 441 442 ignore_errors = settings.get('ignore-errors') 443 show_missing = settings.get('show-missing') 444 directory = settings.get('directory=') 445 446 omit = settings.get('omit=') 447 if omit is not None: 448 omit = omit.split(',') 449 else: 450 omit = [] 451 452 if settings.get('report'): 453 self.report(args, show_missing, ignore_errors, omit_prefixes=omit) 454 if settings.get('annotate'): 455 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
456
457 - def use_cache(self, usecache, cache_file=None):
458 self.usecache = usecache 459 if cache_file and not self.cache: 460 self.cache_default = cache_file
461
462 - def get_ready(self, parallel_mode=False):
463 if self.usecache and not self.cache: 464 self.cache = os.environ.get(self.cache_env, self.cache_default) 465 if self.parallel_mode: 466 self.cache += "." + gethostname() + "." + str(os.getpid()) 467 self.restore() 468 self.analysis_cache = {}
469
470 - def start(self, parallel_mode=False):
471 self.get_ready() 472 if self.nesting == 0: #pragma: no cover 473 sys.settrace(self.t) 474 if hasattr(threading, 'settrace'): 475 threading.settrace(self.t) 476 self.nesting += 1
477
478 - def stop(self):
479 self.nesting -= 1 480 if self.nesting == 0: #pragma: no cover 481 sys.settrace(None) 482 if hasattr(threading, 'settrace'): 483 threading.settrace(None)
484
485 - def erase(self):
486 self.get_ready() 487 self.c = {} 488 self.analysis_cache = {} 489 self.cexecuted = {} 490 if self.cache and os.path.exists(self.cache): 491 os.remove(self.cache)
492
493 - def exclude(self, re):
494 if self.exclude_re: 495 self.exclude_re += "|" 496 self.exclude_re += "(" + re + ")"
497
498 - def begin_recursive(self):
499 self.cstack.append(self.c) 500 self.xstack.append(self.exclude_re)
501
502 - def end_recursive(self):
503 self.c = self.cstack.pop() 504 self.exclude_re = self.xstack.pop()
505 506 # save(). Save coverage data to the coverage cache. 507
508 - def save(self):
509 if self.usecache and self.cache: 510 self.canonicalize_filenames() 511 cache = open(self.cache, 'wb') 512 import marshal 513 marshal.dump(self.cexecuted, cache) 514 cache.close()
515 516 # restore(). Restore coverage data from the coverage cache (if it exists). 517
518 - def restore(self):
519 self.c = {} 520 self.cexecuted = {} 521 assert self.usecache 522 if os.path.exists(self.cache): 523 self.cexecuted = self.restore_file(self.cache)
524
525 - def restore_file(self, file_name):
526 try: 527 cache = open(file_name, 'rb') 528 import marshal 529 cexecuted = marshal.load(cache) 530 cache.close() 531 if isinstance(cexecuted, types.DictType): 532 return cexecuted 533 else: 534 return {} 535 except: 536 return {}
537 538 # collect(). Collect data in multiple files produced by parallel mode 539
540 - def collect(self):
541 cache_dir, local = os.path.split(self.cache) 542 for f in os.listdir(cache_dir or '.'): 543 if not f.startswith(local): 544 continue 545 546 full_path = os.path.join(cache_dir, f) 547 cexecuted = self.restore_file(full_path) 548 self.merge_data(cexecuted)
549
550 - def merge_data(self, new_data):
551 for file_name, file_data in new_data.items(): 552 if self.cexecuted.has_key(file_name): 553 self.merge_file_data(self.cexecuted[file_name], file_data) 554 else: 555 self.cexecuted[file_name] = file_data
556
557 - def merge_file_data(self, cache_data, new_data):
558 for line_number in new_data.keys(): 559 if not cache_data.has_key(line_number): 560 cache_data[line_number] = new_data[line_number]
561 562 # canonical_filename(filename). Return a canonical filename for the 563 # file (that is, an absolute path with no redundant components and 564 # normalized case). See [GDR 2001-12-04b, 3.3]. 565
566 - def canonical_filename(self, filename):
567 if not self.canonical_filename_cache.has_key(filename): 568 f = filename 569 if os.path.isabs(f) and not os.path.exists(f): 570 f = os.path.basename(f) 571 if not os.path.isabs(f): 572 for path in [os.curdir] + sys.path: 573 g = os.path.join(path, f) 574 if os.path.exists(g): 575 f = g 576 break 577 cf = os.path.normcase(os.path.abspath(f)) 578 self.canonical_filename_cache[filename] = cf 579 return self.canonical_filename_cache[filename]
580 581 # canonicalize_filenames(). Copy results from "c" to "cexecuted", 582 # canonicalizing filenames on the way. Clear the "c" map. 583
584 - def canonicalize_filenames(self):
585 for filename, lineno in self.c.keys(): 586 if filename == '<string>': 587 # Can't do anything useful with exec'd strings, so skip them. 588 continue 589 f = self.canonical_filename(filename) 590 if not self.cexecuted.has_key(f): 591 self.cexecuted[f] = {} 592 self.cexecuted[f][lineno] = 1 593 self.c = {}
594 595 # morf_filename(morf). Return the filename for a module or file. 596
597 - def morf_filename(self, morf):
598 if isinstance(morf, types.ModuleType): 599 if not hasattr(morf, '__file__'): 600 raise CoverageException, "Module has no __file__ attribute." 601 f = morf.__file__ 602 else: 603 f = morf 604 return self.canonical_filename(f)
605 606 # analyze_morf(morf). Analyze the module or filename passed as 607 # the argument. If the source code can't be found, raise an error. 608 # Otherwise, return a tuple of (1) the canonical filename of the 609 # source code for the module, (2) a list of lines of statements 610 # in the source code, (3) a list of lines of excluded statements, 611 # and (4), a map of line numbers to multi-line line number ranges, for 612 # statements that cross lines. 613
614 - def analyze_morf(self, morf):
615 return self.analyze_morf(morf)[:-1]
616
617 - def analyze_morf2(self, morf):
618 if self.analysis_cache.has_key(morf): 619 return self.analysis_cache[morf] 620 filename = self.morf_filename(morf) 621 ext = os.path.splitext(filename)[1] 622 if ext == '.pyc': 623 if not os.path.exists(filename[0:-1]): 624 raise CoverageException, ("No source for compiled code '%s'." 625 % filename) 626 filename = filename[0:-1] 627 elif ext != '.py': 628 raise CoverageException, "File '%s' not Python source." % filename 629 source = open(filename, 'rU') 630 lines, excluded_lines, line_map, definfo = \ 631 self.find_executable_statements2(source.read(), 632 exclude=self.exclude_re) 633 source.close() 634 result = filename, lines, excluded_lines, line_map, definfo 635 self.analysis_cache[morf] = result 636 return result
637
638 - def first_line_of_tree(self, tree):
639 while True: 640 if len(tree) == 3 and type(tree[2]) == type(1): 641 return tree[2] 642 tree = tree[1]
643
644 - def last_line_of_tree(self, tree):
645 while True: 646 if len(tree) == 3 and type(tree[2]) == type(1): 647 return tree[2] 648 tree = tree[-1]
649
650 - def find_docstring_pass_pair(self, tree, spots):
651 for i in range(1, len(tree)): 652 if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]): 653 first_line = self.first_line_of_tree(tree[i]) 654 last_line = self.last_line_of_tree(tree[i+1]) 655 self.record_multiline(spots, first_line, last_line)
656
657 - def is_string_constant(self, tree):
658 try: 659 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt 660 except: 661 return False
662
663 - def is_pass_stmt(self, tree):
664 try: 665 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt 666 except: 667 return False
668
669 - def record_multiline(self, spots, i, j):
670 for l in range(i, j+1): 671 spots[l] = (i, j)
672
673 - def get_suite_spots(self, tree, spots):
674 """ Analyze a parse tree to find suite introducers which span a number 675 of lines. 676 """ 677 for i in range(1, len(tree)): 678 if type(tree[i]) == type(()): 679 if tree[i][0] == symbol.suite: 680 # Found a suite, look back for the colon and keyword. 681 lineno_colon = lineno_word = None 682 for j in range(i-1, 0, -1): 683 if tree[j][0] == token.COLON: 684 # Colons are never executed themselves: we want the 685 # line number of the last token before the colon. 686 lineno_colon = self.last_line_of_tree(tree[j-1]) 687 elif tree[j][0] == token.NAME: 688 if tree[j][1] == 'elif': 689 # Find the line number of the first non-terminal 690 # after the keyword. 691 t = tree[j+1] 692 while t and token.ISNONTERMINAL(t[0]): 693 t = t[1] 694 if t: 695 lineno_word = t[2] 696 else: 697 lineno_word = tree[j][2] 698 break 699 elif tree[j][0] == symbol.except_clause: 700 # "except" clauses look like: 701 # ('except_clause', ('NAME', 'except', lineno), ...) 702 if tree[j][1][0] == token.NAME: 703 lineno_word = tree[j][1][2] 704 break 705 if lineno_colon and lineno_word: 706 # Found colon and keyword, mark all the lines 707 # between the two with the two line numbers. 708 self.record_multiline(spots, lineno_word, lineno_colon) 709 710 # "pass" statements are tricky: different versions of Python 711 # treat them differently, especially in the common case of a 712 # function with a doc string and a single pass statement. 713 self.find_docstring_pass_pair(tree[i], spots) 714 715 elif tree[i][0] == symbol.simple_stmt: 716 first_line = self.first_line_of_tree(tree[i]) 717 last_line = self.last_line_of_tree(tree[i]) 718 if first_line != last_line: 719 self.record_multiline(spots, first_line, last_line) 720 self.get_suite_spots(tree[i], spots)
721
722 - def find_executable_statements(self, text, exclude=None):
723 return self.find_executable_statements2(text, exclude)[:-1]
724
725 - def find_executable_statements2(self, text, exclude=None):
726 # Find lines which match an exclusion pattern. 727 excluded = {} 728 suite_spots = {} 729 if exclude: 730 reExclude = re.compile(exclude) 731 lines = text.split('\n') 732 for i in range(len(lines)): 733 if reExclude.search(lines[i]): 734 excluded[i+1] = 1 735 736 # Parse the code and analyze the parse tree to find out which statements 737 # are multiline, and where suites begin and end. 738 import parser 739 tree = parser.suite(text+'\n\n').totuple(1) 740 self.get_suite_spots(tree, suite_spots) 741 #print "Suite spots:", suite_spots 742 743 # Mapping from name of func/class -> (start, end). 744 definfo = [] 745 746 # Use the compiler module to parse the text and find the executable 747 # statements. We add newlines to be impervious to final partial lines. 748 statements = {} 749 ast = compiler.parse(text+'\n\n') 750 visitor = StatementFindingAstVisitor(statements, excluded, 751 suite_spots, definfo) 752 compiler.walk(ast, visitor, walker=visitor) 753 754 lines = statements.keys() 755 lines.sort() 756 excluded_lines = excluded.keys() 757 excluded_lines.sort() 758 return lines, excluded_lines, suite_spots, definfo
759 760 # format_lines(statements, lines). Format a list of line numbers 761 # for printing by coalescing groups of lines as long as the lines 762 # represent consecutive statements. This will coalesce even if 763 # there are gaps between statements, so if statements = 764 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then 765 # format_lines will return "1-2, 5-11, 13-14". 766
767 - def format_lines(self, statements, lines):
768 pairs = [] 769 i = 0 770 j = 0 771 start = None 772 pairs = [] 773 while i < len(statements) and j < len(lines): 774 if statements[i] == lines[j]: 775 if start == None: 776 start = lines[j] 777 end = lines[j] 778 j = j + 1 779 elif start: 780 pairs.append((start, end)) 781 start = None 782 i = i + 1 783 if start: 784 pairs.append((start, end)) 785 def stringify(pair): 786 start, end = pair 787 if start == end: 788 return "%d" % start 789 else: 790 return "%d-%d" % (start, end)
791 ret = string.join(map(stringify, pairs), ", ") 792 return ret 793 794 # Backward compatibility with version 1.
795 - def analysis(self, morf):
796 f, s, _, m, mf = self.analysis2(morf) 797 return f, s, m, mf
798
799 - def analysis2(self, morf):
800 f, s, e, m, mf, _ = self.analysis3(morf) 801 return f, s, e, m, mf
802
803 - def analysis3(self, morf):
804 filename, statements, excluded, line_map, definfo = \ 805 self.analyze_morf2(morf) 806 self.canonicalize_filenames() 807 if not self.cexecuted.has_key(filename): 808 self.cexecuted[filename] = {} 809 missing = [] 810 for line in statements: 811 lines = line_map.get(line, [line, line]) 812 for l in range(lines[0], lines[1]+1): 813 if self.cexecuted[filename].has_key(l): 814 break 815 else: 816 missing.append(line) 817 self.find_def_coverage(morf, statements, missing, definfo) 818 return (filename, statements, excluded, missing, 819 self.format_lines(statements, missing), definfo)
820
821 - def find_def_coverage(self, morf, statements, missing, definfo):
822 """Return mapping from function name to coverage. 823 """ 824 def_coverage = {} 825 root = self.morf_name(morf) 826 statements = set(statements) 827 missing = set(missing) 828 for info in definfo: 829 if info.codestart is None: 830 info.coverage = 1 831 else: 832 lines = set(range(info.codestart, info.end+1)) 833 stmt = len(lines.intersection(statements)) 834 miss = len(lines.intersection(missing)) 835 if miss == 0: info.cover = 1 836 else: info.coverage = (1.0 - float(miss)/float(stmt))
837
838 - def relative_filename(self, filename):
839 """ Convert filename to relative filename from self.relative_dir. 840 """ 841 return filename.replace(self.relative_dir, "")
842
843 - def morf_name(self, morf):
844 """ Return the name of morf as used in report. 845 """ 846 if isinstance(morf, types.ModuleType): 847 return morf.__name__ 848 else: 849 return self.relative_filename(os.path.splitext(morf)[0])
850
851 - def filter_by_prefix(self, morfs, omit_prefixes):
852 """ Return list of morfs where the morf name does not begin 853 with any one of the omit_prefixes. 854 """ 855 filtered_morfs = [] 856 for morf in morfs: 857 for prefix in omit_prefixes: 858 if self.morf_name(morf).startswith(prefix): 859 break 860 else: 861 filtered_morfs.append(morf) 862 863 return filtered_morfs
864
865 - def morf_name_compare(self, x, y):
866 return cmp(self.morf_name(x), self.morf_name(y))
867
868 - def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
869 if not isinstance(morfs, types.ListType): 870 morfs = [morfs] 871 # On windows, the shell doesn't expand wildcards. Do it here. 872 globbed = [] 873 for morf in morfs: 874 if isinstance(morf, strclass): 875 globbed.extend(glob.glob(morf)) 876 else: 877 globbed.append(morf) 878 morfs = globbed 879 880 morfs = self.filter_by_prefix(morfs, omit_prefixes) 881 morfs.sort(self.morf_name_compare) 882 883 max_name = max([5,] + map(len, map(self.morf_name, morfs))) 884 fmt_name = "%%- %ds " % max_name 885 fmt_err = fmt_name + "%s: %s" 886 header = fmt_name % "Name" + " Stmts Exec Cover" 887 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" 888 if show_missing: 889 header = header + " Missing" 890 fmt_coverage = fmt_coverage + " %s" 891 if not file: 892 file = sys.stdout 893 print >>file, header 894 print >>file, "-" * len(header) 895 total_statements = 0 896 total_executed = 0 897 for morf in morfs: 898 name = self.morf_name(morf) 899 try: 900 _, statements, _, missing, readable = self.analysis2(morf) 901 n = len(statements) 902 m = n - len(missing) 903 if n > 0: 904 pc = 100.0 * m / n 905 else: 906 pc = 100.0 907 args = (name, n, m, pc) 908 if show_missing: 909 args = args + (readable,) 910 print >>file, fmt_coverage % args 911 total_statements = total_statements + n 912 total_executed = total_executed + m 913 except KeyboardInterrupt: #pragma: no cover 914 raise 915 except: 916 if not ignore_errors: 917 typ, msg = sys.exc_info()[0:2] 918 print >>file, fmt_err % (name, typ, msg) 919 if len(morfs) > 1: 920 print >>file, "-" * len(header) 921 if total_statements > 0: 922 pc = 100.0 * total_executed / total_statements 923 else: 924 pc = 100.0 925 args = ("TOTAL", total_statements, total_executed, pc) 926 if show_missing: 927 args = args + ("",) 928 print >>file, fmt_coverage % args
929 930 # annotate(morfs, ignore_errors). 931 932 blank_re = re.compile(r"\s*(#|$)") 933 else_re = re.compile(r"\s*else\s*:\s*(#|$)") 934
935 - def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
936 morfs = self.filter_by_prefix(morfs, omit_prefixes) 937 for morf in morfs: 938 try: 939 filename, statements, excluded, missing, _ = self.analysis2(morf) 940 self.annotate_file(filename, statements, excluded, missing, directory) 941 except KeyboardInterrupt: 942 raise 943 except: 944 if not ignore_errors: 945 raise
946
947 - def annotate_file(self, filename, statements, excluded, missing, directory=None):
948 source = open(filename, 'rU') 949 if directory: 950 dest_file = os.path.join(directory, 951 os.path.basename(filename) 952 + ',cover') 953 else: 954 dest_file = filename + ',cover' 955 dest = open(dest_file, 'w') 956 lineno = 0 957 i = 0 958 j = 0 959 covered = 1 960 while 1: 961 line = source.readline() 962 if line == '': 963 break 964 lineno = lineno + 1 965 while i < len(statements) and statements[i] < lineno: 966 i = i + 1 967 while j < len(missing) and missing[j] < lineno: 968 j = j + 1 969 if i < len(statements) and statements[i] == lineno: 970 covered = j >= len(missing) or missing[j] > lineno 971 if self.blank_re.match(line): 972 dest.write(' ') 973 elif self.else_re.match(line): 974 # Special logic for lines containing only 'else:'. 975 # See [GDR 2001-12-04b, 3.2]. 976 if i >= len(statements) and j >= len(missing): 977 dest.write('! ') 978 elif i >= len(statements) or j >= len(missing): 979 dest.write('> ') 980 elif statements[i] == missing[j]: 981 dest.write('! ') 982 else: 983 dest.write('> ') 984 elif lineno in excluded: 985 dest.write('- ') 986 elif covered: 987 dest.write('> ') 988 else: 989 dest.write('! ') 990 dest.write(line) 991 source.close() 992 dest.close()
993 994 # Singleton object. 995 the_coverage = coverage() 996 997 # Module functions call methods in the singleton object.
998 -def use_cache(*args, **kw):
999 return the_coverage.use_cache(*args, **kw) 1000
1001 -def start(*args, **kw):
1002 return the_coverage.start(*args, **kw) 1003
1004 -def stop(*args, **kw):
1005 return the_coverage.stop(*args, **kw) 1006
1007 -def erase(*args, **kw):
1008 return the_coverage.erase(*args, **kw) 1009
1010 -def begin_recursive(*args, **kw):
1011 return the_coverage.begin_recursive(*args, **kw) 1012
1013 -def end_recursive(*args, **kw):
1014 return the_coverage.end_recursive(*args, **kw) 1015
1016 -def exclude(*args, **kw):
1017 return the_coverage.exclude(*args, **kw) 1018
1019 -def analysis(*args, **kw):
1020 return the_coverage.analysis(*args, **kw) 1021
1022 -def analysis2(*args, **kw):
1023 return the_coverage.analysis2(*args, **kw) 1024
1025 -def analysis3(*args, **kw):
1026 return the_coverage.analysis3(*args, **kw) 1027
1028 -def report(*args, **kw):
1029 return the_coverage.report(*args, **kw) 1030
1031 -def annotate(*args, **kw):
1032 return the_coverage.annotate(*args, **kw) 1033
1034 -def annotate_file(*args, **kw):
1035 return the_coverage.annotate_file(*args, **kw) 1036 1037 # Save coverage data when Python exits. (The atexit module wasn't 1038 # introduced until Python 2.0, so use sys.exitfunc when it's not 1039 # available.) 1040 try: 1041 import atexit 1042 atexit.register(the_coverage.save) 1043 except ImportError: 1044 sys.exitfunc = the_coverage.save 1045 1046 # Command-line interface. 1047 if __name__ == '__main__': 1048 the_coverage.command_line(sys.argv[1:]) 1049 1050 1051 # A. REFERENCES 1052 # 1053 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; 1054 # Ravenbrook Limited; 2001-12-04; 1055 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>. 1056 # 1057 # [GDR 2001-12-04b] "Statement coverage for Python: design and 1058 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; 1059 # <http://www.nedbatchelder.com/code/modules/rees-design.html>. 1060 # 1061 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; 1062 # Guide van Rossum; 2001-07-20; 1063 # <http://www.python.org/doc/2.1.1/ref/ref.html>. 1064 # 1065 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; 1066 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>. 1067 # 1068 # 1069 # B. DOCUMENT HISTORY 1070 # 1071 # 2001-12-04 GDR Created. 1072 # 1073 # 2001-12-06 GDR Added command-line interface and source code 1074 # annotation. 1075 # 1076 # 2001-12-09 GDR Moved design and interface to separate documents. 1077 # 1078 # 2001-12-10 GDR Open cache file as binary on Windows. Allow 1079 # simultaneous -e and -x, or -a and -r. 1080 # 1081 # 2001-12-12 GDR Added command-line help. Cache analysis so that it 1082 # only needs to be done once when you specify -a and -r. 1083 # 1084 # 2001-12-13 GDR Improved speed while recording. Portable between 1085 # Python 1.5.2 and 2.1.1. 1086 # 1087 # 2002-01-03 GDR Module-level functions work correctly. 1088 # 1089 # 2002-01-07 GDR Update sys.path when running a file with the -x option, 1090 # so that it matches the value the program would get if it were run on 1091 # its own. 1092 # 1093 # 2004-12-12 NMB Significant code changes. 1094 # - Finding executable statements has been rewritten so that docstrings and 1095 # other quirks of Python execution aren't mistakenly identified as missing 1096 # lines. 1097 # - Lines can be excluded from consideration, even entire suites of lines. 1098 # - The filesystem cache of covered lines can be disabled programmatically. 1099 # - Modernized the code. 1100 # 1101 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior 1102 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding 1103 # 'annotate_file'. 1104 # 1105 # 2004-12-31 NMB Allow for keyword arguments in the module global functions. 1106 # Thanks, Allen. 1107 # 1108 # 2005-12-02 NMB Call threading.settrace so that all threads are measured. 1109 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be 1110 # captured to a different destination. 1111 # 1112 # 2005-12-03 NMB coverage.py can now measure itself. 1113 # 1114 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, 1115 # and sorting and omitting files to report on. 1116 # 1117 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators. 1118 # 1119 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument 1120 # handling. 1121 # 1122 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch. 1123 # 1124 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line 1125 # logic for parallel mode and collect. 1126 # 1127 # 2006-08-25 NMB "#pragma: nocover" is excluded by default. 1128 # 1129 # 2006-09-10 NMB Properly ignore docstrings and other constant expressions that 1130 # appear in the middle of a function, a problem reported by Tim Leslie. 1131 # Minor changes to avoid lint warnings. 1132 # 1133 # 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex. 1134 # Change how parallel mode is invoked, and fix erase() so that it erases the 1135 # cache when called programmatically. 1136 # 1137 # 2007-07-21 NMB In reports, ignore code executed from strings, since we can't 1138 # do anything useful with it anyway. 1139 # Better file handling on Linux, thanks Guillaume Chazarain. 1140 # Better shell support on Windows, thanks Noel O'Boyle. 1141 # Python 2.2 support maintained, thanks Catherine Proulx. 1142 # 1143 # 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with 1144 # multi-line statements is now less sensitive to the exact line that Python 1145 # reports during execution. Pass statements are handled specially so that their 1146 # disappearance during execution won't throw off the measurement. 1147 # 1148 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the 1149 # new with statement is counted as executable. 1150 # 1151 # 2007-07-29 NMB Better packaging. 1152 # 1153 # 2007-09-13 EDL Open Python files with 'rU' mode. 1154 # 1155 # 2007-09-20 EDL Added DefInfo, which gives fine-grained info about 1156 # coverage (and location) of functions & classes. 1157 1158 # C. COPYRIGHT AND LICENCE 1159 # 1160 # Copyright 2001 Gareth Rees. All rights reserved. 1161 # Copyright 2004-2007 Ned Batchelder. All rights reserved. 1162 # 1163 # Redistribution and use in source and binary forms, with or without 1164 # modification, are permitted provided that the following conditions are 1165 # met: 1166 # 1167 # 1. Redistributions of source code must retain the above copyright 1168 # notice, this list of conditions and the following disclaimer. 1169 # 1170 # 2. Redistributions in binary form must reproduce the above copyright 1171 # notice, this list of conditions and the following disclaimer in the 1172 # documentation and/or other materials provided with the 1173 # distribution. 1174 # 1175 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1176 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1177 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1178 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1179 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 1180 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 1181 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 1182 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 1183 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 1184 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 1185 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 1186 # DAMAGE. 1187 # 1188 # $Id: coverage.py 74 2007-07-29 22:28:35Z nedbat $ 1189