Virt: re-write and refactor save_build() utility function
[autotest-zwu.git] / contrib / coverage.py
blob9b2bf67ddfa7a7e10b9575fdb5a27a99de9aeec2
1 #!/usr/bin/python
3 # Perforce Defect Tracking Integration Project
4 # <http://www.ravenbrook.com/project/p4dti/>
6 # COVERAGE.PY -- COVERAGE TESTING
8 # Gareth Rees, Ravenbrook Limited, 2001-12-04
9 # Ned Batchelder, 2004-12-12
10 # http://nedbatchelder.com/code/modules/coverage.html
13 # 1. INTRODUCTION
15 # This module provides coverage testing for Python code.
17 # The intended readership is all Python developers.
19 # This document is not confidential.
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.
25 import pdb
27 r"""Usage:
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.
34 coverage.py -e
35 Erase collected coverage data.
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.
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.
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.
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
56 Coverage data is saved in the file .coverage by default. Set the
57 COVERAGE_FILE environment variable to save it somewhere else."""
59 __version__ = "2.78.20070930" # see detailed history at the end of this file.
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
74 # Python version compatibility
75 try:
76 strclass = basestring # new to 2.3
77 except:
78 strclass = str
80 # 2. IMPLEMENTATION
82 # This uses the "singleton" pattern.
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.
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.
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.
100 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
101 """ A visitor for a parsed Abstract Syntax Tree which finds executable
102 statements.
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
111 def doRecursive(self, node):
112 for n in node.getChildNodes():
113 self.dispatch(n)
115 visitStmt = visitModule = doRecursive
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)
124 visitFunction = visitClass = doCode
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
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
144 def doStatement(self, node):
145 self.recordLine(self.getFirstLine(node))
147 visitAssert = visitAssign = visitAssTuple = visitPrint = \
148 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
149 doStatement
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
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)
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
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
201 default = recordNodeLine
203 def recordAndDispatch(self, node):
204 self.recordNodeLine(node)
205 self.dispatch(node)
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
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)
228 def doElse(self, prevsuite, node):
229 if node.else_:
230 self.doPlainWordSuite(prevsuite, node.else_)
232 def visitFor(self, node):
233 self.doSuite(node, node.body)
234 self.doElse(node.body, node)
236 visitWhile = visitFor
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)
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)
262 def visitTryFinally(self, node):
263 self.doSuite(node, node.body)
264 self.doPlainWordSuite(node.body, node.final)
266 def visitWith(self, node):
267 self.doSuite(node, node.body)
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
274 the_coverage = None
276 class CoverageException(Exception): pass
278 class coverage:
279 # Name of the cache file (unless environment variable is set).
280 cache_default = ".coverage"
282 # Environment variable naming the cache file.
283 cache_env = "COVERAGE_FILE"
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 = {}
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 = {}
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 = {}
298 # Cache of results of calling the canonical_filename() method, to
299 # avoid duplicating work.
300 canonical_filename_cache = {}
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]')
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.
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
330 def help(self, error=None): #pragma: no cover
331 if error:
332 print error
333 print
334 print __doc__
335 sys.exit(1)
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=',
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)
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.
370 if settings.get('help'):
371 help_fn()
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))
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))
390 self.parallel_mode = settings.get('parallel-mode')
391 self.get_ready()
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()
410 ignore_errors = settings.get('ignore-errors')
411 show_missing = settings.get('show-missing')
412 directory = settings.get('directory=')
414 omit = settings.get('omit=')
415 if omit is not None:
416 omit = omit.split(',')
417 else:
418 omit = []
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)
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
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 = {}
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
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)
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)
461 def exclude(self, re):
462 if self.exclude_re:
463 self.exclude_re += "|"
464 self.exclude_re += "(" + re + ")"
466 def begin_recursive(self):
467 self.cstack.append(self.c)
468 self.xstack.append(self.exclude_re)
470 def end_recursive(self):
471 self.c = self.cstack.pop()
472 self.exclude_re = self.xstack.pop()
474 # save(). Save coverage data to the coverage cache.
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()
484 # restore(). Restore coverage data from the coverage cache (if it exists).
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)
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 {}
506 # collect(). Collect data in multiple files produced by parallel mode
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
514 full_path = os.path.join(cache_dir, f)
515 cexecuted = self.restore_file(full_path)
516 self.merge_data(cexecuted)
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
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]
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].
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]
549 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
550 # canonicalizing filenames on the way. Clear the "c" map.
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 = {}
563 # morf_filename(morf). Return the filename for a module or file.
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)
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.
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
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
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)
603 source.close()
604 result = filename, lines, excluded_lines, line_map
605 self.analysis_cache[morf] = result
606 return result
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]
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]
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)
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
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
639 def record_multiline(self, spots, i, j):
640 for l in range(i, j+1):
641 spots[l] = (i, j)
643 def get_suite_spots(self, tree, spots):
644 """ Analyze a parse tree to find suite introducers which span a number
645 of lines.
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)
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)
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)
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
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
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)
717 lines = statements.keys()
718 lines.sort()
719 excluded_lines = excluded.keys()
720 excluded_lines.sort()
721 return lines, excluded_lines, suite_spots
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".
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
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
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))
778 def relative_filename(self, filename):
779 """ Convert filename to relative filename from self.relative_dir.
781 return filename.replace(self.relative_dir, "")
783 def morf_name(self, morf):
784 """ Return the name of morf as used in report.
786 if isinstance(morf, types.ModuleType):
787 return morf.__name__
788 else:
789 return self.relative_filename(os.path.splitext(morf)[0])
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.
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)
803 return filtered_morfs
805 def morf_name_compare(self, x, y):
806 return cmp(self.morf_name(x), self.morf_name(y))
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
820 morfs = self.filter_by_prefix(morfs, omit_prefixes)
821 morfs.sort(self.morf_name_compare)
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
870 # annotate(morfs, ignore_errors).
872 blank_re = re.compile(r"\s*(#|$)")
873 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
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
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()
934 # Singleton object.
935 the_coverage = coverage()
937 # Module functions call methods in the singleton object.
938 def use_cache(*args, **kw):
939 return the_coverage.use_cache(*args, **kw)
941 def start(*args, **kw):
942 return the_coverage.start(*args, **kw)
944 def stop(*args, **kw):
945 return the_coverage.stop(*args, **kw)
947 def erase(*args, **kw):
948 return the_coverage.erase(*args, **kw)
950 def begin_recursive(*args, **kw):
951 return the_coverage.begin_recursive(*args, **kw)
953 def end_recursive(*args, **kw):
954 return the_coverage.end_recursive(*args, **kw)
956 def exclude(*args, **kw):
957 return the_coverage.exclude(*args, **kw)
959 def analysis(*args, **kw):
960 return the_coverage.analysis(*args, **kw)
962 def analysis2(*args, **kw):
963 return the_coverage.analysis2(*args, **kw)
965 def report(*args, **kw):
966 return the_coverage.report(*args, **kw)
968 def annotate(*args, **kw):
969 return the_coverage.annotate(*args, **kw)
971 def annotate_file(*args, **kw):
972 return the_coverage.annotate_file(*args, **kw)
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
983 # Command-line interface.
984 if __name__ == '__main__':
985 the_coverage.command_line(sys.argv[1:])
988 # A. REFERENCES
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>.
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>.
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>.
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>.
1006 # B. DOCUMENT HISTORY
1008 # 2001-12-04 GDR Created.
1010 # 2001-12-06 GDR Added command-line interface and source code
1011 # annotation.
1013 # 2001-12-09 GDR Moved design and interface to separate documents.
1015 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
1016 # simultaneous -e and -x, or -a and -r.
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.
1021 # 2001-12-13 GDR Improved speed while recording. Portable between
1022 # Python 1.5.2 and 2.1.1.
1024 # 2002-01-03 GDR Module-level functions work correctly.
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.
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.
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'.
1042 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1043 # Thanks, Allen.
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.
1049 # 2005-12-03 NMB coverage.py can now measure itself.
1051 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1052 # and sorting and omitting files to report on.
1054 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1056 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1057 # handling.
1059 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1061 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
1062 # logic for parallel mode and collect.
1064 # 2006-08-25 NMB "#pragma: nocover" is excluded by default.
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.
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.
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.
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.
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.
1088 # 2007-07-29 NMB Better packaging.
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.
1094 # C. COPYRIGHT AND LICENCE
1096 # Copyright 2001 Gareth Rees. All rights reserved.
1097 # Copyright 2004-2007 Ned Batchelder. All rights reserved.
1099 # Redistribution and use in source and binary forms, with or without
1100 # modification, are permitted provided that the following conditions are
1101 # met:
1103 # 1. Redistributions of source code must retain the above copyright
1104 # notice, this list of conditions and the following disclaimer.
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.
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.
1124 # $Id: coverage.py 79 2007-10-01 01:01:52Z nedbat $