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