##// END OF EJS Templates
import latest coverage.py version
Dirkjan Ochtman -
r5592:7a4d846b default
parent child Browse files
Show More
@@ -1,4 +1,4 b''
1 #!/usr/bin/env python
1 #!/usr/bin/python
2 2 #
3 3 # Perforce Defect Tracking Integration Project
4 4 # <http://www.ravenbrook.com/project/p4dti/>
@@ -22,15 +22,20 b''
22 22 # interface and limitations. See [GDR 2001-12-04b] for requirements and
23 23 # design.
24 24
25 """Usage:
25 r"""Usage:
26 26
27 coverage.py -x MODULE.py [ARG1 ARG2 ...]
27 coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
28 28 Execute module, passing the given command-line arguments, collecting
29 coverage data.
29 coverage data. With the -p option, write to a temporary file containing
30 the machine name and process ID.
30 31
31 32 coverage.py -e
32 33 Erase collected coverage data.
33 34
35 coverage.py -c
36 Collect data from multiple coverage files (as created by -p option above)
37 and store it into a single file representing the union of the coverage.
38
34 39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
35 40 Report on the statement coverage for the given files. With the -m
36 41 option, show line numbers of the statements that weren't executed.
@@ -49,16 +54,26 b' coverage.py -a [-d dir] [-o dir1,dir2,..'
49 54 Coverage data is saved in the file .coverage by default. Set the
50 55 COVERAGE_FILE environment variable to save it somewhere else."""
51 56
52 __version__ = "2.5.20051204" # see detailed history at the end of this file.
57 __version__ = "2.77.20070729" # see detailed history at the end of this file.
53 58
54 59 import compiler
55 60 import compiler.visitor
61 import glob
56 62 import os
57 63 import re
58 64 import string
65 import symbol
59 66 import sys
60 67 import threading
68 import token
61 69 import types
70 from socket import gethostname
71
72 # Python version compatibility
73 try:
74 strclass = basestring # new to 2.3
75 except:
76 strclass = str
62 77
63 78 # 2. IMPLEMENTATION
64 79 #
@@ -81,25 +96,29 b' import types'
81 96 # names to increase speed.
82 97
83 98 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
99 """ A visitor for a parsed Abstract Syntax Tree which finds executable
100 statements.
101 """
84 102 def __init__(self, statements, excluded, suite_spots):
85 103 compiler.visitor.ASTVisitor.__init__(self)
86 104 self.statements = statements
87 105 self.excluded = excluded
88 106 self.suite_spots = suite_spots
89 107 self.excluding_suite = 0
90
108
91 109 def doRecursive(self, node):
92 self.recordNodeLine(node)
93 110 for n in node.getChildNodes():
94 111 self.dispatch(n)
95 112
96 113 visitStmt = visitModule = doRecursive
97
114
98 115 def doCode(self, node):
99 116 if hasattr(node, 'decorators') and node.decorators:
100 117 self.dispatch(node.decorators)
101 self.doSuite(node, node.code)
102
118 self.recordAndDispatch(node.code)
119 else:
120 self.doSuite(node, node.code)
121
103 122 visitFunction = visitClass = doCode
104 123
105 124 def getFirstLine(self, node):
@@ -119,17 +138,40 b' class StatementFindingAstVisitor(compile'
119 138 for n in node.getChildNodes():
120 139 lineno = max(lineno, self.getLastLine(n))
121 140 return lineno
122
141
123 142 def doStatement(self, node):
124 143 self.recordLine(self.getFirstLine(node))
125 144
126 visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
145 visitAssert = visitAssign = visitAssTuple = visitPrint = \
127 146 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
128 147 doStatement
148
149 def visitPass(self, node):
150 # Pass statements have weird interactions with docstrings. If this
151 # pass statement is part of one of those pairs, claim that the statement
152 # is on the later of the two lines.
153 l = node.lineno
154 if l:
155 lines = self.suite_spots.get(l, [l,l])
156 self.statements[lines[1]] = 1
157
158 def visitDiscard(self, node):
159 # Discard nodes are statements that execute an expression, but then
160 # discard the results. This includes function calls, so we can't
161 # ignore them all. But if the expression is a constant, the statement
162 # won't be "executed", so don't count it now.
163 if node.expr.__class__.__name__ != 'Const':
164 self.doStatement(node)
129 165
130 166 def recordNodeLine(self, node):
131 return self.recordLine(node.lineno)
132
167 # Stmt nodes often have None, but shouldn't claim the first line of
168 # their children (because the first child might be an ignorable line
169 # like "global a").
170 if node.__class__.__name__ != 'Stmt':
171 return self.recordLine(self.getFirstLine(node))
172 else:
173 return 0
174
133 175 def recordLine(self, lineno):
134 176 # Returns a bool, whether the line is included or excluded.
135 177 if lineno:
@@ -137,7 +179,7 b' class StatementFindingAstVisitor(compile'
137 179 # keyword.
138 180 if lineno in self.suite_spots:
139 181 lineno = self.suite_spots[lineno][0]
140 # If we're inside an exluded suite, record that this line was
182 # If we're inside an excluded suite, record that this line was
141 183 # excluded.
142 184 if self.excluding_suite:
143 185 self.excluded[lineno] = 1
@@ -153,9 +195,9 b' class StatementFindingAstVisitor(compile'
153 195 self.statements[lineno] = 1
154 196 return 1
155 197 return 0
156
198
157 199 default = recordNodeLine
158
200
159 201 def recordAndDispatch(self, node):
160 202 self.recordNodeLine(node)
161 203 self.dispatch(node)
@@ -166,7 +208,7 b' class StatementFindingAstVisitor(compile'
166 208 self.excluding_suite = 1
167 209 self.recordAndDispatch(body)
168 210 self.excluding_suite = exsuite
169
211
170 212 def doPlainWordSuite(self, prevsuite, suite):
171 213 # Finding the exclude lines for else's is tricky, because they aren't
172 214 # present in the compiler parse tree. Look at the previous suite,
@@ -180,15 +222,17 b' class StatementFindingAstVisitor(compile'
180 222 break
181 223 else:
182 224 self.doSuite(None, suite)
183
225
184 226 def doElse(self, prevsuite, node):
185 227 if node.else_:
186 228 self.doPlainWordSuite(prevsuite, node.else_)
187
229
188 230 def visitFor(self, node):
189 231 self.doSuite(node, node.body)
190 232 self.doElse(node.body, node)
191 233
234 visitWhile = visitFor
235
192 236 def visitIf(self, node):
193 237 # The first test has to be handled separately from the rest.
194 238 # The first test is credited to the line with the "if", but the others
@@ -198,10 +242,6 b' class StatementFindingAstVisitor(compile'
198 242 self.doSuite(t, n)
199 243 self.doElse(node.tests[-1][1], node)
200 244
201 def visitWhile(self, node):
202 self.doSuite(node, node.body)
203 self.doElse(node.body, node)
204
205 245 def visitTryExcept(self, node):
206 246 self.doSuite(node, node.body)
207 247 for i in range(len(node.handlers)):
@@ -216,11 +256,14 b' class StatementFindingAstVisitor(compile'
216 256 else:
217 257 self.doSuite(a, h)
218 258 self.doElse(node.handlers[-1][2], node)
219
259
220 260 def visitTryFinally(self, node):
221 261 self.doSuite(node, node.body)
222 262 self.doPlainWordSuite(node.body, node.final)
223
263
264 def visitWith(self, node):
265 self.doSuite(node, node.body)
266
224 267 def visitGlobal(self, node):
225 268 # "global" statements don't execute like others (they don't call the
226 269 # trace function), so don't record their line numbers.
@@ -228,9 +271,9 b' class StatementFindingAstVisitor(compile'
228 271
229 272 the_coverage = None
230 273
274 class CoverageException(Exception): pass
275
231 276 class coverage:
232 error = "coverage error"
233
234 277 # Name of the cache file (unless environment variable is set).
235 278 cache_default = ".coverage"
236 279
@@ -240,7 +283,7 b' class coverage:'
240 283 # A dictionary with an entry for (Python source file name, line number
241 284 # in that file) if that line has been executed.
242 285 c = {}
243
286
244 287 # A map from canonical Python source file name to a dictionary in
245 288 # which there's an entry for each line number that has been
246 289 # executed.
@@ -257,53 +300,58 b' class coverage:'
257 300 def __init__(self):
258 301 global the_coverage
259 302 if the_coverage:
260 raise self.error, "Only one coverage object allowed."
303 raise CoverageException, "Only one coverage object allowed."
261 304 self.usecache = 1
262 305 self.cache = None
306 self.parallel_mode = False
263 307 self.exclude_re = ''
264 308 self.nesting = 0
265 309 self.cstack = []
266 310 self.xstack = []
267 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
311 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep)
312 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
268 313
269 # t(f, x, y). This method is passed to sys.settrace as a trace function.
270 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
314 # t(f, x, y). This method is passed to sys.settrace as a trace function.
315 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
271 316 # the arguments and return value of the trace function.
272 317 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
273 318 # objects.
274
275 def t(self, f, w, a): #pragma: no cover
276 #print w, f.f_code.co_filename, f.f_lineno
319
320 def t(self, f, w, unused): #pragma: no cover
277 321 if w == 'line':
322 #print "Executing %s @ %d" % (f.f_code.co_filename, f.f_lineno)
278 323 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
279 324 for c in self.cstack:
280 325 c[(f.f_code.co_filename, f.f_lineno)] = 1
281 326 return self.t
282
283 def help(self, error=None):
327
328 def help(self, error=None): #pragma: no cover
284 329 if error:
285 330 print error
286 331 print
287 332 print __doc__
288 333 sys.exit(1)
289 334
290 def command_line(self):
335 def command_line(self, argv, help_fn=None):
291 336 import getopt
337 help_fn = help_fn or self.help
292 338 settings = {}
293 339 optmap = {
294 340 '-a': 'annotate',
341 '-c': 'collect',
295 342 '-d:': 'directory=',
296 343 '-e': 'erase',
297 344 '-h': 'help',
298 345 '-i': 'ignore-errors',
299 346 '-m': 'show-missing',
347 '-p': 'parallel-mode',
300 348 '-r': 'report',
301 349 '-x': 'execute',
302 '-o': 'omit=',
350 '-o:': 'omit=',
303 351 }
304 352 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
305 353 long_opts = optmap.values()
306 options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
354 options, args = getopt.getopt(argv, short_opts, long_opts)
307 355 for o, a in options:
308 356 if optmap.has_key(o):
309 357 settings[optmap[o]] = 1
@@ -312,69 +360,84 b' class coverage:'
312 360 elif o[2:] in long_opts:
313 361 settings[o[2:]] = 1
314 362 elif o[2:] + '=' in long_opts:
315 settings[o[2:]] = a
316 else:
317 self.help("Unknown option: '%s'." % o)
363 settings[o[2:]+'='] = a
364 else: #pragma: no cover
365 pass # Can't get here, because getopt won't return anything unknown.
366
318 367 if settings.get('help'):
319 self.help()
368 help_fn()
369
320 370 for i in ['erase', 'execute']:
321 for j in ['annotate', 'report']:
371 for j in ['annotate', 'report', 'collect']:
322 372 if settings.get(i) and settings.get(j):
323 self.help("You can't specify the '%s' and '%s' "
373 help_fn("You can't specify the '%s' and '%s' "
324 374 "options at the same time." % (i, j))
375
325 376 args_needed = (settings.get('execute')
326 377 or settings.get('annotate')
327 378 or settings.get('report'))
328 action = settings.get('erase') or args_needed
379 action = (settings.get('erase')
380 or settings.get('collect')
381 or args_needed)
329 382 if not action:
330 self.help("You must specify at least one of -e, -x, -r, or -a.")
383 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
331 384 if not args_needed and args:
332 self.help("Unexpected arguments %s." % args)
333
385 help_fn("Unexpected arguments: %s" % " ".join(args))
386
387 self.parallel_mode = settings.get('parallel-mode')
334 388 self.get_ready()
335 self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
336 389
337 390 if settings.get('erase'):
338 391 self.erase()
339 392 if settings.get('execute'):
340 393 if not args:
341 self.help("Nothing to do.")
394 help_fn("Nothing to do.")
342 395 sys.argv = args
343 396 self.start()
344 397 import __main__
345 398 sys.path[0] = os.path.dirname(sys.argv[0])
346 399 execfile(sys.argv[0], __main__.__dict__)
400 if settings.get('collect'):
401 self.collect()
347 402 if not args:
348 403 args = self.cexecuted.keys()
404
349 405 ignore_errors = settings.get('ignore-errors')
350 406 show_missing = settings.get('show-missing')
351 directory = settings.get('directory')
352 omit = filter(None, settings.get('omit', '').split(','))
353 omit += ['/<'] # Always skip /<string> etc.
407 directory = settings.get('directory=')
408
409 omit = settings.get('omit=')
410 if omit is not None:
411 omit = omit.split(',')
412 else:
413 omit = []
354 414
355 415 if settings.get('report'):
356 416 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
357 417 if settings.get('annotate'):
358 418 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
359 419
360 def use_cache(self, usecache):
420 def use_cache(self, usecache, cache_file=None):
361 421 self.usecache = usecache
362
363 def get_ready(self):
422 if cache_file and not self.cache:
423 self.cache_default = cache_file
424
425 def get_ready(self, parallel_mode=False):
364 426 if self.usecache and not self.cache:
365 self.cache = os.path.abspath(os.environ.get(self.cache_env,
366 self.cache_default))
427 self.cache = os.environ.get(self.cache_env, self.cache_default)
428 if self.parallel_mode:
429 self.cache += "." + gethostname() + "." + str(os.getpid())
367 430 self.restore()
368 431 self.analysis_cache = {}
369
370 def start(self):
432
433 def start(self, parallel_mode=False):
371 434 self.get_ready()
372 435 if self.nesting == 0: #pragma: no cover
373 436 sys.settrace(self.t)
374 437 if hasattr(threading, 'settrace'):
375 438 threading.settrace(self.t)
376 439 self.nesting += 1
377
440
378 441 def stop(self):
379 442 self.nesting -= 1
380 443 if self.nesting == 0: #pragma: no cover
@@ -383,12 +446,12 b' class coverage:'
383 446 threading.settrace(None)
384 447
385 448 def erase(self):
449 self.get_ready()
386 450 self.c = {}
387 451 self.analysis_cache = {}
388 452 self.cexecuted = {}
389 453 if self.cache and os.path.exists(self.cache):
390 454 os.remove(self.cache)
391 self.exclude_re = ""
392 455
393 456 def exclude(self, re):
394 457 if self.exclude_re:
@@ -398,7 +461,7 b' class coverage:'
398 461 def begin_recursive(self):
399 462 self.cstack.append(self.c)
400 463 self.xstack.append(self.exclude_re)
401
464
402 465 def end_recursive(self):
403 466 self.c = self.cstack.pop()
404 467 self.exclude_re = self.xstack.pop()
@@ -406,8 +469,6 b' class coverage:'
406 469 # save(). Save coverage data to the coverage cache.
407 470
408 471 def save(self):
409 # move to directory that must exist.
410 os.chdir(os.sep)
411 472 if self.usecache and self.cache:
412 473 self.canonicalize_filenames()
413 474 cache = open(self.cache, 'wb')
@@ -421,17 +482,45 b' class coverage:'
421 482 self.c = {}
422 483 self.cexecuted = {}
423 484 assert self.usecache
424 if not os.path.exists(self.cache):
425 return
485 if os.path.exists(self.cache):
486 self.cexecuted = self.restore_file(self.cache)
487
488 def restore_file(self, file_name):
426 489 try:
427 cache = open(self.cache, 'rb')
490 cache = open(file_name, 'rb')
428 491 import marshal
429 492 cexecuted = marshal.load(cache)
430 493 cache.close()
431 494 if isinstance(cexecuted, types.DictType):
432 self.cexecuted = cexecuted
495 return cexecuted
496 else:
497 return {}
433 498 except:
434 pass
499 return {}
500
501 # collect(). Collect data in multiple files produced by parallel mode
502
503 def collect(self):
504 cache_dir, local = os.path.split(self.cache)
505 for f in os.listdir(cache_dir or '.'):
506 if not f.startswith(local):
507 continue
508
509 full_path = os.path.join(cache_dir, f)
510 cexecuted = self.restore_file(full_path)
511 self.merge_data(cexecuted)
512
513 def merge_data(self, new_data):
514 for file_name, file_data in new_data.items():
515 if self.cexecuted.has_key(file_name):
516 self.merge_file_data(self.cexecuted[file_name], file_data)
517 else:
518 self.cexecuted[file_name] = file_data
519
520 def merge_file_data(self, cache_data, new_data):
521 for line_number in new_data.keys():
522 if not cache_data.has_key(line_number):
523 cache_data[line_number] = new_data[line_number]
435 524
436 525 # canonical_filename(filename). Return a canonical filename for the
437 526 # file (that is, an absolute path with no redundant components and
@@ -452,11 +541,14 b' class coverage:'
452 541 self.canonical_filename_cache[filename] = cf
453 542 return self.canonical_filename_cache[filename]
454 543
455 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
544 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
456 545 # canonicalizing filenames on the way. Clear the "c" map.
457 546
458 547 def canonicalize_filenames(self):
459 548 for filename, lineno in self.c.keys():
549 if filename == '<string>':
550 # Can't do anything useful with exec'd strings, so skip them.
551 continue
460 552 f = self.canonical_filename(filename)
461 553 if not self.cexecuted.has_key(f):
462 554 self.cexecuted[f] = {}
@@ -468,18 +560,20 b' class coverage:'
468 560 def morf_filename(self, morf):
469 561 if isinstance(morf, types.ModuleType):
470 562 if not hasattr(morf, '__file__'):
471 raise self.error, "Module has no __file__ attribute."
472 file = morf.__file__
563 raise CoverageException, "Module has no __file__ attribute."
564 f = morf.__file__
473 565 else:
474 file = morf
475 return self.canonical_filename(file)
566 f = morf
567 return self.canonical_filename(f)
476 568
477 569 # analyze_morf(morf). Analyze the module or filename passed as
478 570 # the argument. If the source code can't be found, raise an error.
479 571 # Otherwise, return a tuple of (1) the canonical filename of the
480 572 # source code for the module, (2) a list of lines of statements
481 # in the source code, and (3) a list of lines of excluded statements.
482
573 # in the source code, (3) a list of lines of excluded statements,
574 # and (4), a map of line numbers to multi-line line number ranges, for
575 # statements that cross lines.
576
483 577 def analyze_morf(self, morf):
484 578 if self.analysis_cache.has_key(morf):
485 579 return self.analysis_cache[morf]
@@ -487,30 +581,69 b' class coverage:'
487 581 ext = os.path.splitext(filename)[1]
488 582 if ext == '.pyc':
489 583 if not os.path.exists(filename[0:-1]):
490 raise self.error, ("No source for compiled code '%s'."
584 raise CoverageException, ("No source for compiled code '%s'."
491 585 % filename)
492 586 filename = filename[0:-1]
493 587 elif ext != '.py':
494 raise self.error, "File '%s' not Python source." % filename
588 raise CoverageException, "File '%s' not Python source." % filename
495 589 source = open(filename, 'r')
496 lines, excluded_lines = self.find_executable_statements(
590 lines, excluded_lines, line_map = self.find_executable_statements(
497 591 source.read(), exclude=self.exclude_re
498 592 )
499 593 source.close()
500 result = filename, lines, excluded_lines
594 result = filename, lines, excluded_lines, line_map
501 595 self.analysis_cache[morf] = result
502 596 return result
503 597
598 def first_line_of_tree(self, tree):
599 while True:
600 if len(tree) == 3 and type(tree[2]) == type(1):
601 return tree[2]
602 tree = tree[1]
603
604 def last_line_of_tree(self, tree):
605 while True:
606 if len(tree) == 3 and type(tree[2]) == type(1):
607 return tree[2]
608 tree = tree[-1]
609
610 def find_docstring_pass_pair(self, tree, spots):
611 for i in range(1, len(tree)):
612 if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
613 first_line = self.first_line_of_tree(tree[i])
614 last_line = self.last_line_of_tree(tree[i+1])
615 self.record_multiline(spots, first_line, last_line)
616
617 def is_string_constant(self, tree):
618 try:
619 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
620 except:
621 return False
622
623 def is_pass_stmt(self, tree):
624 try:
625 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
626 except:
627 return False
628
629 def record_multiline(self, spots, i, j):
630 for l in range(i, j+1):
631 spots[l] = (i, j)
632
504 633 def get_suite_spots(self, tree, spots):
505 import symbol, token
634 """ Analyze a parse tree to find suite introducers which span a number
635 of lines.
636 """
506 637 for i in range(1, len(tree)):
507 if isinstance(tree[i], tuple):
638 if type(tree[i]) == type(()):
508 639 if tree[i][0] == symbol.suite:
509 640 # Found a suite, look back for the colon and keyword.
510 641 lineno_colon = lineno_word = None
511 642 for j in range(i-1, 0, -1):
512 643 if tree[j][0] == token.COLON:
513 lineno_colon = tree[j][2]
644 # Colons are never executed themselves: we want the
645 # line number of the last token before the colon.
646 lineno_colon = self.last_line_of_tree(tree[j-1])
514 647 elif tree[j][0] == token.NAME:
515 648 if tree[j][1] == 'elif':
516 649 # Find the line number of the first non-terminal
@@ -532,8 +665,18 b' class coverage:'
532 665 if lineno_colon and lineno_word:
533 666 # Found colon and keyword, mark all the lines
534 667 # between the two with the two line numbers.
535 for l in range(lineno_word, lineno_colon+1):
536 spots[l] = (lineno_word, lineno_colon)
668 self.record_multiline(spots, lineno_word, lineno_colon)
669
670 # "pass" statements are tricky: different versions of Python
671 # treat them differently, especially in the common case of a
672 # function with a doc string and a single pass statement.
673 self.find_docstring_pass_pair(tree[i], spots)
674
675 elif tree[i][0] == symbol.simple_stmt:
676 first_line = self.first_line_of_tree(tree[i])
677 last_line = self.last_line_of_tree(tree[i])
678 if first_line != last_line:
679 self.record_multiline(spots, first_line, last_line)
537 680 self.get_suite_spots(tree[i], spots)
538 681
539 682 def find_executable_statements(self, text, exclude=None):
@@ -547,10 +690,13 b' class coverage:'
547 690 if reExclude.search(lines[i]):
548 691 excluded[i+1] = 1
549 692
693 # Parse the code and analyze the parse tree to find out which statements
694 # are multiline, and where suites begin and end.
550 695 import parser
551 696 tree = parser.suite(text+'\n\n').totuple(1)
552 697 self.get_suite_spots(tree, suite_spots)
553
698 #print "Suite spots:", suite_spots
699
554 700 # Use the compiler module to parse the text and find the executable
555 701 # statements. We add newlines to be impervious to final partial lines.
556 702 statements = {}
@@ -562,7 +708,7 b' class coverage:'
562 708 lines.sort()
563 709 excluded_lines = excluded.keys()
564 710 excluded_lines.sort()
565 return lines, excluded_lines
711 return lines, excluded_lines, suite_spots
566 712
567 713 # format_lines(statements, lines). Format a list of line numbers
568 714 # for printing by coalescing groups of lines as long as the lines
@@ -595,7 +741,8 b' class coverage:'
595 741 return "%d" % start
596 742 else:
597 743 return "%d-%d" % (start, end)
598 return string.join(map(stringify, pairs), ", ")
744 ret = string.join(map(stringify, pairs), ", ")
745 return ret
599 746
600 747 # Backward compatibility with version 1.
601 748 def analysis(self, morf):
@@ -603,13 +750,17 b' class coverage:'
603 750 return f, s, m, mf
604 751
605 752 def analysis2(self, morf):
606 filename, statements, excluded = self.analyze_morf(morf)
753 filename, statements, excluded, line_map = self.analyze_morf(morf)
607 754 self.canonicalize_filenames()
608 755 if not self.cexecuted.has_key(filename):
609 756 self.cexecuted[filename] = {}
610 757 missing = []
611 758 for line in statements:
612 if not self.cexecuted[filename].has_key(line):
759 lines = line_map.get(line, [line, line])
760 for l in range(lines[0], lines[1]+1):
761 if self.cexecuted[filename].has_key(l):
762 break
763 else:
613 764 missing.append(line)
614 765 return (filename, statements, excluded, missing,
615 766 self.format_lines(statements, missing))
@@ -647,6 +798,15 b' class coverage:'
647 798 def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
648 799 if not isinstance(morfs, types.ListType):
649 800 morfs = [morfs]
801 # On windows, the shell doesn't expand wildcards. Do it here.
802 globbed = []
803 for morf in morfs:
804 if isinstance(morf, strclass):
805 globbed.extend(glob.glob(morf))
806 else:
807 globbed.append(morf)
808 morfs = globbed
809
650 810 morfs = self.filter_by_prefix(morfs, omit_prefixes)
651 811 morfs.sort(self.morf_name_compare)
652 812
@@ -684,8 +844,8 b' class coverage:'
684 844 raise
685 845 except:
686 846 if not ignore_errors:
687 type, msg = sys.exc_info()[0:2]
688 print >>file, fmt_err % (name, type, msg)
847 typ, msg = sys.exc_info()[0:2]
848 print >>file, fmt_err % (name, typ, msg)
689 849 if len(morfs) > 1:
690 850 print >>file, "-" * len(header)
691 851 if total_statements > 0:
@@ -713,7 +873,7 b' class coverage:'
713 873 except:
714 874 if not ignore_errors:
715 875 raise
716
876
717 877 def annotate_file(self, filename, statements, excluded, missing, directory=None):
718 878 source = open(filename, 'r')
719 879 if directory:
@@ -741,7 +901,7 b' class coverage:'
741 901 if self.blank_re.match(line):
742 902 dest.write(' ')
743 903 elif self.else_re.match(line):
744 # Special logic for lines containing only 'else:'.
904 # Special logic for lines containing only 'else:'.
745 905 # See [GDR 2001-12-04b, 3.2].
746 906 if i >= len(statements) and j >= len(missing):
747 907 dest.write('! ')
@@ -765,18 +925,41 b' class coverage:'
765 925 the_coverage = coverage()
766 926
767 927 # Module functions call methods in the singleton object.
768 def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
769 def start(*args, **kw): return the_coverage.start(*args, **kw)
770 def stop(*args, **kw): return the_coverage.stop(*args, **kw)
771 def erase(*args, **kw): return the_coverage.erase(*args, **kw)
772 def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw)
773 def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw)
774 def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
775 def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
776 def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
777 def report(*args, **kw): return the_coverage.report(*args, **kw)
778 def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
779 def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
928 def use_cache(*args, **kw):
929 return the_coverage.use_cache(*args, **kw)
930
931 def start(*args, **kw):
932 return the_coverage.start(*args, **kw)
933
934 def stop(*args, **kw):
935 return the_coverage.stop(*args, **kw)
936
937 def erase(*args, **kw):
938 return the_coverage.erase(*args, **kw)
939
940 def begin_recursive(*args, **kw):
941 return the_coverage.begin_recursive(*args, **kw)
942
943 def end_recursive(*args, **kw):
944 return the_coverage.end_recursive(*args, **kw)
945
946 def exclude(*args, **kw):
947 return the_coverage.exclude(*args, **kw)
948
949 def analysis(*args, **kw):
950 return the_coverage.analysis(*args, **kw)
951
952 def analysis2(*args, **kw):
953 return the_coverage.analysis2(*args, **kw)
954
955 def report(*args, **kw):
956 return the_coverage.report(*args, **kw)
957
958 def annotate(*args, **kw):
959 return the_coverage.annotate(*args, **kw)
960
961 def annotate_file(*args, **kw):
962 return the_coverage.annotate_file(*args, **kw)
780 963
781 964 # Save coverage data when Python exits. (The atexit module wasn't
782 965 # introduced until Python 2.0, so use sys.exitfunc when it's not
@@ -789,7 +972,7 b' except ImportError:'
789 972
790 973 # Command-line interface.
791 974 if __name__ == '__main__':
792 the_coverage.command_line()
975 the_coverage.command_line(sys.argv[1:])
793 976
794 977
795 978 # A. REFERENCES
@@ -850,7 +1033,7 b" if __name__ == '__main__':"
850 1033 # Thanks, Allen.
851 1034 #
852 1035 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
853 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1036 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
854 1037 # captured to a different destination.
855 1038 #
856 1039 # 2005-12-03 NMB coverage.py can now measure itself.
@@ -858,10 +1041,46 b" if __name__ == '__main__':"
858 1041 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
859 1042 # and sorting and omitting files to report on.
860 1043 #
1044 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1045 #
1046 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1047 # handling.
1048 #
1049 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1050 #
1051 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
1052 # logic for parallel mode and collect.
1053 #
1054 # 2006-08-25 NMB "#pragma: nocover" is excluded by default.
1055 #
1056 # 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
1057 # appear in the middle of a function, a problem reported by Tim Leslie.
1058 # Minor changes to avoid lint warnings.
1059 #
1060 # 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
1061 # Change how parallel mode is invoked, and fix erase() so that it erases the
1062 # cache when called programmatically.
1063 #
1064 # 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
1065 # do anything useful with it anyway.
1066 # Better file handling on Linux, thanks Guillaume Chazarain.
1067 # Better shell support on Windows, thanks Noel O'Boyle.
1068 # Python 2.2 support maintained, thanks Catherine Proulx.
1069 #
1070 # 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
1071 # multi-line statements is now less sensitive to the exact line that Python
1072 # reports during execution. Pass statements are handled specially so that their
1073 # disappearance during execution won't throw off the measurement.
1074 #
1075 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1076 # new with statement is counted as executable.
1077 #
1078 # 2007-07-29 NMB Better packaging.
1079
861 1080 # C. COPYRIGHT AND LICENCE
862 1081 #
863 1082 # Copyright 2001 Gareth Rees. All rights reserved.
864 # Copyright 2004-2005 Ned Batchelder. All rights reserved.
1083 # Copyright 2004-2007 Ned Batchelder. All rights reserved.
865 1084 #
866 1085 # Redistribution and use in source and binary forms, with or without
867 1086 # modification, are permitted provided that the following conditions are
@@ -888,4 +1107,4 b" if __name__ == '__main__':"
888 1107 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
889 1108 # DAMAGE.
890 1109 #
891 # $Id: coverage.py 26 2005-12-04 18:42:44Z ned $
1110 # $Id: coverage.py 74 2007-07-29 22:28:35Z nedbat $
General Comments 0
You need to be logged in to leave comments. Login now