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