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