Make the release script independent of the package name
[pysize.git] / tests / coverage.py
blobdc676cda5b7828d250d9759cace061f4e0295d91
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"""Usage:
27 coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
28 Execute module, passing the given command-line arguments, collecting
29 coverage data. With the -p option, write to a temporary file containing
30 the machine name and process ID.
32 coverage.py -e
33 Erase collected coverage data.
35 coverage.py -c
36 Collect data from multiple coverage files (as created by -p option above)
37 and store it into a single file representing the union of the coverage.
39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
40 Report on the statement coverage for the given files. With the -m
41 option, show line numbers of the statements that weren't executed.
43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
44 Make annotated copies of the given files, marking statements that
45 are executed with > and statements that are missed with !. With
46 the -d option, make the copies in that directory. Without the -d
47 option, make each copy in the same directory as the original.
49 -o dir,dir2,...
50 Omit reporting or annotating files when their filename path starts with
51 a directory listed in the omit list.
52 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
54 Coverage data is saved in the file .coverage by default. Set the
55 COVERAGE_FILE environment variable to save it somewhere else."""
57 __version__ = "2.6.20060823" # see detailed history at the end of this file.
59 import compiler
60 import compiler.visitor
61 import os
62 import re
63 import string
64 import sys
65 import threading
66 import types
67 from socket import gethostname
69 # 2. IMPLEMENTATION
71 # This uses the "singleton" pattern.
73 # The word "morf" means a module object (from which the source file can
74 # be deduced by suitable manipulation of the __file__ attribute) or a
75 # filename.
77 # When we generate a coverage report we have to canonicalize every
78 # filename in the coverage dictionary just in case it refers to the
79 # module we are reporting on. It seems a shame to throw away this
80 # information so the data in the coverage dictionary is transferred to
81 # the 'cexecuted' dictionary under the canonical filenames.
83 # The coverage dictionary is called "c" and the trace function "t". The
84 # reason for these short names is that Python looks up variables by name
85 # at runtime and so execution time depends on the length of variables!
86 # In the bottleneck of this application it's appropriate to abbreviate
87 # names to increase speed.
89 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
90 def __init__(self, statements, excluded, suite_spots):
91 compiler.visitor.ASTVisitor.__init__(self)
92 self.statements = statements
93 self.excluded = excluded
94 self.suite_spots = suite_spots
95 self.excluding_suite = 0
97 def doRecursive(self, node):
98 self.recordNodeLine(node)
99 for n in node.getChildNodes():
100 self.dispatch(n)
102 visitStmt = visitModule = doRecursive
104 def doCode(self, node):
105 if hasattr(node, 'decorators') and node.decorators:
106 self.dispatch(node.decorators)
107 self.recordAndDispatch(node.code)
108 else:
109 self.doSuite(node, node.code)
111 visitFunction = visitClass = doCode
113 def getFirstLine(self, node):
114 # Find the first line in the tree node.
115 lineno = node.lineno
116 for n in node.getChildNodes():
117 f = self.getFirstLine(n)
118 if lineno and f:
119 lineno = min(lineno, f)
120 else:
121 lineno = lineno or f
122 return lineno
124 def getLastLine(self, node):
125 # Find the first line in the tree node.
126 lineno = node.lineno
127 for n in node.getChildNodes():
128 lineno = max(lineno, self.getLastLine(n))
129 return lineno
131 def doStatement(self, node):
132 self.recordLine(self.getFirstLine(node))
134 visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
135 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
136 doStatement
138 def recordNodeLine(self, node):
139 return self.recordLine(node.lineno)
141 def recordLine(self, lineno):
142 # Returns a bool, whether the line is included or excluded.
143 if lineno:
144 # Multi-line tests introducing suites have to get charged to their
145 # keyword.
146 if lineno in self.suite_spots:
147 lineno = self.suite_spots[lineno][0]
148 # If we're inside an exluded suite, record that this line was
149 # excluded.
150 if self.excluding_suite:
151 self.excluded[lineno] = 1
152 return 0
153 # If this line is excluded, or suite_spots maps this line to
154 # another line that is exlcuded, then we're excluded.
155 elif self.excluded.has_key(lineno) or \
156 self.suite_spots.has_key(lineno) and \
157 self.excluded.has_key(self.suite_spots[lineno][1]):
158 return 0
159 # Otherwise, this is an executable line.
160 else:
161 self.statements[lineno] = 1
162 return 1
163 return 0
165 default = recordNodeLine
167 def recordAndDispatch(self, node):
168 self.recordNodeLine(node)
169 self.dispatch(node)
171 def doSuite(self, intro, body, exclude=0):
172 exsuite = self.excluding_suite
173 if exclude or (intro and not self.recordNodeLine(intro)):
174 self.excluding_suite = 1
175 self.recordAndDispatch(body)
176 self.excluding_suite = exsuite
178 def doPlainWordSuite(self, prevsuite, suite):
179 # Finding the exclude lines for else's is tricky, because they aren't
180 # present in the compiler parse tree. Look at the previous suite,
181 # and find its last line. If any line between there and the else's
182 # first line are excluded, then we exclude the else.
183 lastprev = self.getLastLine(prevsuite)
184 firstelse = self.getFirstLine(suite)
185 for l in range(lastprev+1, firstelse):
186 if self.suite_spots.has_key(l):
187 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
188 break
189 else:
190 self.doSuite(None, suite)
192 def doElse(self, prevsuite, node):
193 if node.else_:
194 self.doPlainWordSuite(prevsuite, node.else_)
196 def visitFor(self, node):
197 self.doSuite(node, node.body)
198 self.doElse(node.body, node)
200 def visitIf(self, node):
201 # The first test has to be handled separately from the rest.
202 # The first test is credited to the line with the "if", but the others
203 # are credited to the line with the test for the elif.
204 self.doSuite(node, node.tests[0][1])
205 for t, n in node.tests[1:]:
206 self.doSuite(t, n)
207 self.doElse(node.tests[-1][1], node)
209 def visitWhile(self, node):
210 self.doSuite(node, node.body)
211 self.doElse(node.body, node)
213 def visitTryExcept(self, node):
214 self.doSuite(node, node.body)
215 for i in range(len(node.handlers)):
216 a, b, h = node.handlers[i]
217 if not a:
218 # It's a plain "except:". Find the previous suite.
219 if i > 0:
220 prev = node.handlers[i-1][2]
221 else:
222 prev = node.body
223 self.doPlainWordSuite(prev, h)
224 else:
225 self.doSuite(a, h)
226 self.doElse(node.handlers[-1][2], node)
228 def visitTryFinally(self, node):
229 self.doSuite(node, node.body)
230 self.doPlainWordSuite(node.body, node.final)
232 def visitGlobal(self, node):
233 # "global" statements don't execute like others (they don't call the
234 # trace function), so don't record their line numbers.
235 pass
237 the_coverage = None
239 class CoverageException(Exception): pass
241 class coverage:
242 # Name of the cache file (unless environment variable is set).
243 cache_default = ".coverage"
245 # Environment variable naming the cache file.
246 cache_env = "COVERAGE_FILE"
248 # A dictionary with an entry for (Python source file name, line number
249 # in that file) if that line has been executed.
250 c = {}
252 # A map from canonical Python source file name to a dictionary in
253 # which there's an entry for each line number that has been
254 # executed.
255 cexecuted = {}
257 # Cache of results of calling the analysis2() method, so that you can
258 # specify both -r and -a without doing double work.
259 analysis_cache = {}
261 # Cache of results of calling the canonical_filename() method, to
262 # avoid duplicating work.
263 canonical_filename_cache = {}
265 def __init__(self):
266 global the_coverage
267 if the_coverage:
268 raise CoverageException, "Only one coverage object allowed."
269 self.usecache = 1
270 self.cache = None
271 self.exclude_re = ''
272 self.nesting = 0
273 self.cstack = []
274 self.xstack = []
275 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
277 # t(f, x, y). This method is passed to sys.settrace as a trace function.
278 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
279 # the arguments and return value of the trace function.
280 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
281 # objects.
283 def t(self, f, w, a): #pragma: no cover
284 if w == 'line':
285 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
286 for c in self.cstack:
287 c[(f.f_code.co_filename, f.f_lineno)] = 1
288 return self.t
290 def help(self, error=None):
291 if error:
292 print error
293 print
294 print __doc__
295 sys.exit(1)
297 def command_line(self, argv, help=None):
298 import getopt
299 help = help or self.help
300 settings = {}
301 optmap = {
302 '-a': 'annotate',
303 '-c': 'collect',
304 '-d:': 'directory=',
305 '-e': 'erase',
306 '-h': 'help',
307 '-i': 'ignore-errors',
308 '-m': 'show-missing',
309 '-p': 'parallel-mode',
310 '-r': 'report',
311 '-x': 'execute',
312 '-o:': 'omit=',
314 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
315 long_opts = optmap.values()
316 options, args = getopt.getopt(argv, short_opts, long_opts)
317 for o, a in options:
318 if optmap.has_key(o):
319 settings[optmap[o]] = 1
320 elif optmap.has_key(o + ':'):
321 settings[optmap[o + ':']] = a
322 elif o[2:] in long_opts:
323 settings[o[2:]] = 1
324 elif o[2:] + '=' in long_opts:
325 settings[o[2:]+'='] = a
326 else: #pragma: no cover
327 pass # Can't get here, because getopt won't return anything unknown.
329 if settings.get('help'):
330 help()
332 for i in ['erase', 'execute']:
333 for j in ['annotate', 'report', 'collect']:
334 if settings.get(i) and settings.get(j):
335 help("You can't specify the '%s' and '%s' "
336 "options at the same time." % (i, j))
338 args_needed = (settings.get('execute')
339 or settings.get('annotate')
340 or settings.get('report'))
341 action = (settings.get('erase')
342 or settings.get('collect')
343 or args_needed)
344 if not action:
345 help("You must specify at least one of -e, -x, -c, -r, or -a.")
346 if not args_needed and args:
347 help("Unexpected arguments: %s" % " ".join(args))
349 self.get_ready(settings.get('parallel-mode'))
350 self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
352 if settings.get('erase'):
353 self.erase()
354 if settings.get('execute'):
355 if not args:
356 help("Nothing to do.")
357 sys.argv = args
358 self.start()
359 import __main__
360 sys.path[0] = os.path.dirname(sys.argv[0])
361 execfile(sys.argv[0], __main__.__dict__)
362 if settings.get('collect'):
363 self.collect()
364 if not args:
365 args = self.cexecuted.keys()
367 ignore_errors = settings.get('ignore-errors')
368 show_missing = settings.get('show-missing')
369 directory = settings.get('directory=')
371 omit = settings.get('omit=')
372 if omit is not None:
373 omit = omit.split(',')
374 else:
375 omit = []
377 if settings.get('report'):
378 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
379 if settings.get('annotate'):
380 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
382 def use_cache(self, usecache, cache_file=None):
383 self.usecache = usecache
384 if cache_file and not self.cache:
385 self.cache_default = cache_file
387 def get_ready(self, parallel_mode=False):
388 if self.usecache and not self.cache:
389 self.cache = os.environ.get(self.cache_env, self.cache_default)
390 if parallel_mode:
391 self.cache += "." + gethostname() + "." + str(os.getpid())
392 self.restore()
393 self.analysis_cache = {}
395 def start(self, parallel_mode=False):
396 self.get_ready(parallel_mode)
397 if self.nesting == 0: #pragma: no cover
398 sys.settrace(self.t)
399 if hasattr(threading, 'settrace'):
400 threading.settrace(self.t)
401 self.nesting += 1
403 def stop(self):
404 self.nesting -= 1
405 if self.nesting == 0: #pragma: no cover
406 sys.settrace(None)
407 if hasattr(threading, 'settrace'):
408 threading.settrace(None)
410 def erase(self):
411 self.c = {}
412 self.analysis_cache = {}
413 self.cexecuted = {}
414 if self.cache and os.path.exists(self.cache):
415 os.remove(self.cache)
416 self.exclude_re = ""
418 def exclude(self, re):
419 if self.exclude_re:
420 self.exclude_re += "|"
421 self.exclude_re += "(" + re + ")"
423 def begin_recursive(self):
424 self.cstack.append(self.c)
425 self.xstack.append(self.exclude_re)
427 def end_recursive(self):
428 self.c = self.cstack.pop()
429 self.exclude_re = self.xstack.pop()
431 # save(). Save coverage data to the coverage cache.
433 def save(self):
434 if self.usecache and self.cache:
435 self.canonicalize_filenames()
436 cache = open(self.cache, 'wb')
437 import marshal
438 marshal.dump(self.cexecuted, cache)
439 cache.close()
441 # restore(). Restore coverage data from the coverage cache (if it exists).
443 def restore(self):
444 self.c = {}
445 self.cexecuted = {}
446 assert self.usecache
447 if os.path.exists(self.cache):
448 self.cexecuted = self.restore_file(self.cache)
450 def restore_file(self, file_name):
451 try:
452 cache = open(file_name, 'rb')
453 import marshal
454 cexecuted = marshal.load(cache)
455 cache.close()
456 if isinstance(cexecuted, types.DictType):
457 return cexecuted
458 else:
459 return {}
460 except:
461 return {}
463 # collect(). Collect data in multiple files produced by parallel mode
465 def collect(self):
466 cache_dir, local = os.path.split(self.cache)
467 for file in os.listdir(cache_dir):
468 if not file.startswith(local):
469 continue
471 full_path = os.path.join(cache_dir, file)
472 cexecuted = self.restore_file(full_path)
473 self.merge_data(cexecuted)
475 def merge_data(self, new_data):
476 for file_name, file_data in new_data.items():
477 if self.cexecuted.has_key(file_name):
478 self.merge_file_data(self.cexecuted[file_name], file_data)
479 else:
480 self.cexecuted[file_name] = file_data
482 def merge_file_data(self, cache_data, new_data):
483 for line_number in new_data.keys():
484 if not cache_data.has_key(line_number):
485 cache_data[line_number] = new_data[line_number]
487 # canonical_filename(filename). Return a canonical filename for the
488 # file (that is, an absolute path with no redundant components and
489 # normalized case). See [GDR 2001-12-04b, 3.3].
491 def canonical_filename(self, filename):
492 if not self.canonical_filename_cache.has_key(filename):
493 f = filename
494 if os.path.isabs(f) and not os.path.exists(f):
495 f = os.path.basename(f)
496 if not os.path.isabs(f):
497 for path in [os.curdir] + sys.path:
498 g = os.path.join(path, f)
499 if os.path.exists(g):
500 f = g
501 break
502 cf = os.path.normcase(os.path.abspath(f))
503 self.canonical_filename_cache[filename] = cf
504 return self.canonical_filename_cache[filename]
506 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
507 # canonicalizing filenames on the way. Clear the "c" map.
509 def canonicalize_filenames(self):
510 for filename, lineno in self.c.keys():
511 f = self.canonical_filename(filename)
512 if not self.cexecuted.has_key(f):
513 self.cexecuted[f] = {}
514 self.cexecuted[f][lineno] = 1
515 self.c = {}
517 # morf_filename(morf). Return the filename for a module or file.
519 def morf_filename(self, morf):
520 if isinstance(morf, types.ModuleType):
521 if not hasattr(morf, '__file__'):
522 raise CoverageException, "Module has no __file__ attribute."
523 file = morf.__file__
524 else:
525 file = morf
526 return self.canonical_filename(file)
528 # analyze_morf(morf). Analyze the module or filename passed as
529 # the argument. If the source code can't be found, raise an error.
530 # Otherwise, return a tuple of (1) the canonical filename of the
531 # source code for the module, (2) a list of lines of statements
532 # in the source code, and (3) a list of lines of excluded statements.
534 def analyze_morf(self, morf):
535 if self.analysis_cache.has_key(morf):
536 return self.analysis_cache[morf]
537 filename = self.morf_filename(morf)
538 ext = os.path.splitext(filename)[1]
539 if ext == '.pyc':
540 if not os.path.exists(filename[0:-1]):
541 raise CoverageException, ("No source for compiled code '%s'."
542 % filename)
543 filename = filename[0:-1]
544 elif ext != '.py':
545 raise CoverageException, "File '%s' not Python source." % filename
546 source = open(filename, 'r')
547 lines, excluded_lines = self.find_executable_statements(
548 source.read(), exclude=self.exclude_re
550 source.close()
551 result = filename, lines, excluded_lines
552 self.analysis_cache[morf] = result
553 return result
555 def get_suite_spots(self, tree, spots):
556 import symbol, token
557 for i in range(1, len(tree)):
558 if type(tree[i]) == type(()):
559 if tree[i][0] == symbol.suite:
560 # Found a suite, look back for the colon and keyword.
561 lineno_colon = lineno_word = None
562 for j in range(i-1, 0, -1):
563 if tree[j][0] == token.COLON:
564 lineno_colon = tree[j][2]
565 elif tree[j][0] == token.NAME:
566 if tree[j][1] == 'elif':
567 # Find the line number of the first non-terminal
568 # after the keyword.
569 t = tree[j+1]
570 while t and token.ISNONTERMINAL(t[0]):
571 t = t[1]
572 if t:
573 lineno_word = t[2]
574 else:
575 lineno_word = tree[j][2]
576 break
577 elif tree[j][0] == symbol.except_clause:
578 # "except" clauses look like:
579 # ('except_clause', ('NAME', 'except', lineno), ...)
580 if tree[j][1][0] == token.NAME:
581 lineno_word = tree[j][1][2]
582 break
583 if lineno_colon and lineno_word:
584 # Found colon and keyword, mark all the lines
585 # between the two with the two line numbers.
586 for l in range(lineno_word, lineno_colon+1):
587 spots[l] = (lineno_word, lineno_colon)
588 self.get_suite_spots(tree[i], spots)
590 def find_executable_statements(self, text, exclude=None):
591 # Find lines which match an exclusion pattern.
592 excluded = {}
593 suite_spots = {}
594 if exclude:
595 reExclude = re.compile(exclude)
596 lines = text.split('\n')
597 for i in range(len(lines)):
598 if reExclude.search(lines[i]):
599 excluded[i+1] = 1
601 import parser
602 tree = parser.suite(text+'\n\n').totuple(1)
603 self.get_suite_spots(tree, suite_spots)
605 # Use the compiler module to parse the text and find the executable
606 # statements. We add newlines to be impervious to final partial lines.
607 statements = {}
608 ast = compiler.parse(text+'\n\n')
609 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
610 compiler.walk(ast, visitor, walker=visitor)
612 lines = statements.keys()
613 lines.sort()
614 excluded_lines = excluded.keys()
615 excluded_lines.sort()
616 return lines, excluded_lines
618 # format_lines(statements, lines). Format a list of line numbers
619 # for printing by coalescing groups of lines as long as the lines
620 # represent consecutive statements. This will coalesce even if
621 # there are gaps between statements, so if statements =
622 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
623 # format_lines will return "1-2, 5-11, 13-14".
625 def format_lines(self, statements, lines):
626 pairs = []
627 i = 0
628 j = 0
629 start = None
630 pairs = []
631 while i < len(statements) and j < len(lines):
632 if statements[i] == lines[j]:
633 if start == None:
634 start = lines[j]
635 end = lines[j]
636 j = j + 1
637 elif start:
638 pairs.append((start, end))
639 start = None
640 i = i + 1
641 if start:
642 pairs.append((start, end))
643 def stringify(pair):
644 start, end = pair
645 if start == end:
646 return "%d" % start
647 else:
648 return "%d-%d" % (start, end)
649 return string.join(map(stringify, pairs), ", ")
651 # Backward compatibility with version 1.
652 def analysis(self, morf):
653 f, s, _, m, mf = self.analysis2(morf)
654 return f, s, m, mf
656 def analysis2(self, morf):
657 filename, statements, excluded = self.analyze_morf(morf)
658 self.canonicalize_filenames()
659 if not self.cexecuted.has_key(filename):
660 self.cexecuted[filename] = {}
661 missing = []
662 for line in statements:
663 if not self.cexecuted[filename].has_key(line):
664 missing.append(line)
665 return (filename, statements, excluded, missing,
666 self.format_lines(statements, missing))
668 def relative_filename(self, filename):
669 """ Convert filename to relative filename from self.relative_dir.
671 return filename.replace(self.relative_dir, "")
673 def morf_name(self, morf):
674 """ Return the name of morf as used in report.
676 if isinstance(morf, types.ModuleType):
677 return morf.__name__
678 else:
679 return self.relative_filename(os.path.splitext(morf)[0])
681 def filter_by_prefix(self, morfs, omit_prefixes):
682 """ Return list of morfs where the morf name does not begin
683 with any one of the omit_prefixes.
685 filtered_morfs = []
686 for morf in morfs:
687 for prefix in omit_prefixes:
688 if self.morf_name(morf).startswith(prefix):
689 break
690 else:
691 filtered_morfs.append(morf)
693 return filtered_morfs
695 def morf_name_compare(self, x, y):
696 return cmp(self.morf_name(x), self.morf_name(y))
698 def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
699 if not isinstance(morfs, types.ListType):
700 morfs = [morfs]
701 morfs = self.filter_by_prefix(morfs, omit_prefixes)
702 morfs.sort(self.morf_name_compare)
704 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
705 fmt_name = "%%- %ds " % max_name
706 fmt_err = fmt_name + "%s: %s"
707 header = fmt_name % "Name" + " Stmts Exec Cover"
708 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
709 if show_missing:
710 header = header + " Missing"
711 fmt_coverage = fmt_coverage + " %s"
712 if not file:
713 file = sys.stdout
714 print >>file, header
715 print >>file, "-" * len(header)
716 total_statements = 0
717 total_executed = 0
718 for morf in morfs:
719 name = self.morf_name(morf)
720 try:
721 _, statements, _, missing, readable = self.analysis2(morf)
722 n = len(statements)
723 m = n - len(missing)
724 if n > 0:
725 pc = 100.0 * m / n
726 else:
727 pc = 100.0
728 args = (name, n, m, pc)
729 if show_missing:
730 args = args + (readable,)
731 print >>file, fmt_coverage % args
732 total_statements = total_statements + n
733 total_executed = total_executed + m
734 except KeyboardInterrupt: #pragma: no cover
735 raise
736 except:
737 if not ignore_errors:
738 type, msg = sys.exc_info()[0:2]
739 print >>file, fmt_err % (name, type, msg)
740 if len(morfs) > 1:
741 print >>file, "-" * len(header)
742 if total_statements > 0:
743 pc = 100.0 * total_executed / total_statements
744 else:
745 pc = 100.0
746 args = ("TOTAL", total_statements, total_executed, pc)
747 if show_missing:
748 args = args + ("",)
749 print >>file, fmt_coverage % args
751 # annotate(morfs, ignore_errors).
753 blank_re = re.compile(r"\s*(#|$)")
754 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
756 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
757 morfs = self.filter_by_prefix(morfs, omit_prefixes)
758 for morf in morfs:
759 try:
760 filename, statements, excluded, missing, _ = self.analysis2(morf)
761 self.annotate_file(filename, statements, excluded, missing, directory)
762 except KeyboardInterrupt:
763 raise
764 except:
765 if not ignore_errors:
766 raise
768 def annotate_file(self, filename, statements, excluded, missing, directory=None):
769 source = open(filename, 'r')
770 if directory:
771 dest_file = os.path.join(directory,
772 os.path.basename(filename)
773 + ',cover')
774 else:
775 dest_file = filename + ',cover'
776 dest = open(dest_file, 'w')
777 lineno = 0
778 i = 0
779 j = 0
780 covered = 1
781 while 1:
782 line = source.readline()
783 if line == '':
784 break
785 lineno = lineno + 1
786 while i < len(statements) and statements[i] < lineno:
787 i = i + 1
788 while j < len(missing) and missing[j] < lineno:
789 j = j + 1
790 if i < len(statements) and statements[i] == lineno:
791 covered = j >= len(missing) or missing[j] > lineno
792 if self.blank_re.match(line):
793 dest.write(' ')
794 elif self.else_re.match(line):
795 # Special logic for lines containing only 'else:'.
796 # See [GDR 2001-12-04b, 3.2].
797 if i >= len(statements) and j >= len(missing):
798 dest.write('! ')
799 elif i >= len(statements) or j >= len(missing):
800 dest.write('> ')
801 elif statements[i] == missing[j]:
802 dest.write('! ')
803 else:
804 dest.write('> ')
805 elif lineno in excluded:
806 dest.write('- ')
807 elif covered:
808 dest.write('> ')
809 else:
810 dest.write('! ')
811 dest.write(line)
812 source.close()
813 dest.close()
815 # Singleton object.
816 the_coverage = coverage()
818 # Module functions call methods in the singleton object.
819 def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
820 def start(*args, **kw): return the_coverage.start(*args, **kw)
821 def stop(*args, **kw): return the_coverage.stop(*args, **kw)
822 def erase(*args, **kw): return the_coverage.erase(*args, **kw)
823 def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw)
824 def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw)
825 def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
826 def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
827 def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
828 def report(*args, **kw): return the_coverage.report(*args, **kw)
829 def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
830 def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
832 # Commented for pysize by Guillaume Chazarain: we don't want to save the cache
833 # # Save coverage data when Python exits. (The atexit module wasn't
834 # # introduced until Python 2.0, so use sys.exitfunc when it's not
835 # # available.)
836 # try:
837 # import atexit
838 # atexit.register(the_coverage.save)
839 # except ImportError:
840 # sys.exitfunc = the_coverage.save
842 # Command-line interface.
843 if __name__ == '__main__':
844 the_coverage.command_line(sys.argv[1:])
847 # A. REFERENCES
849 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
850 # Ravenbrook Limited; 2001-12-04;
851 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
853 # [GDR 2001-12-04b] "Statement coverage for Python: design and
854 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
855 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
857 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
858 # Guide van Rossum; 2001-07-20;
859 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
861 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
862 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
865 # B. DOCUMENT HISTORY
867 # 2001-12-04 GDR Created.
869 # 2001-12-06 GDR Added command-line interface and source code
870 # annotation.
872 # 2001-12-09 GDR Moved design and interface to separate documents.
874 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
875 # simultaneous -e and -x, or -a and -r.
877 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
878 # only needs to be done once when you specify -a and -r.
880 # 2001-12-13 GDR Improved speed while recording. Portable between
881 # Python 1.5.2 and 2.1.1.
883 # 2002-01-03 GDR Module-level functions work correctly.
885 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
886 # so that it matches the value the program would get if it were run on
887 # its own.
889 # 2004-12-12 NMB Significant code changes.
890 # - Finding executable statements has been rewritten so that docstrings and
891 # other quirks of Python execution aren't mistakenly identified as missing
892 # lines.
893 # - Lines can be excluded from consideration, even entire suites of lines.
894 # - The filesystem cache of covered lines can be disabled programmatically.
895 # - Modernized the code.
897 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
898 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
899 # 'annotate_file'.
901 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
902 # Thanks, Allen.
904 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
905 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
906 # captured to a different destination.
908 # 2005-12-03 NMB coverage.py can now measure itself.
910 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
911 # and sorting and omitting files to report on.
913 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
915 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
916 # handling.
918 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
920 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
921 # logic for parallel mode and collect.
923 # C. COPYRIGHT AND LICENCE
925 # Copyright 2001 Gareth Rees. All rights reserved.
926 # Copyright 2004-2006 Ned Batchelder. All rights reserved.
928 # Redistribution and use in source and binary forms, with or without
929 # modification, are permitted provided that the following conditions are
930 # met:
932 # 1. Redistributions of source code must retain the above copyright
933 # notice, this list of conditions and the following disclaimer.
935 # 2. Redistributions in binary form must reproduce the above copyright
936 # notice, this list of conditions and the following disclaimer in the
937 # documentation and/or other materials provided with the
938 # distribution.
940 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
941 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
942 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
943 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
944 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
945 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
946 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
947 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
948 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
949 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
950 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
951 # DAMAGE.
953 # $Id: coverage.py 47 2006-08-24 01:08:48Z Ned $