comparison tests/coverage.py @ 7047:277c91fe8384

Update coverage.py There is no technical reason to update it except it contains all the patches already done in mercurial plus other stuff. It will be easier to update and maintain in the future.
author Patrick Mezard <pmezard@gmail.com>
date Wed, 17 Sep 2008 22:15:36 +0200
parents 6aaf5b1d8f15
children 08a0f04b56bd
comparison
equal deleted inserted replaced
7040:f29b674cc221 7047:277c91fe8384
52 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits 52 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
53 53
54 Coverage data is saved in the file .coverage by default. Set the 54 Coverage data is saved in the file .coverage by default. Set the
55 COVERAGE_FILE environment variable to save it somewhere else.""" 55 COVERAGE_FILE environment variable to save it somewhere else."""
56 56
57 __version__ = "2.77.20070729" # see detailed history at the end of this file. 57 __version__ = "2.85.20080914" # see detailed history at the end of this file.
58 58
59 import compiler 59 import compiler
60 import compiler.visitor 60 import compiler.visitor
61 import glob 61 import glob
62 import os 62 import os
65 import symbol 65 import symbol
66 import sys 66 import sys
67 import threading 67 import threading
68 import token 68 import token
69 import types 69 import types
70 import zipimport
70 from socket import gethostname 71 from socket import gethostname
71 72
72 # Python version compatibility 73 # Python version compatibility
73 try: 74 try:
74 strclass = basestring # new to 2.3 75 strclass = basestring # new to 2.3
103 compiler.visitor.ASTVisitor.__init__(self) 104 compiler.visitor.ASTVisitor.__init__(self)
104 self.statements = statements 105 self.statements = statements
105 self.excluded = excluded 106 self.excluded = excluded
106 self.suite_spots = suite_spots 107 self.suite_spots = suite_spots
107 self.excluding_suite = 0 108 self.excluding_suite = 0
108 109
109 def doRecursive(self, node): 110 def doRecursive(self, node):
110 for n in node.getChildNodes(): 111 for n in node.getChildNodes():
111 self.dispatch(n) 112 self.dispatch(n)
112 113
113 visitStmt = visitModule = doRecursive 114 visitStmt = visitModule = doRecursive
114 115
115 def doCode(self, node): 116 def doCode(self, node):
116 if hasattr(node, 'decorators') and node.decorators: 117 if hasattr(node, 'decorators') and node.decorators:
117 self.dispatch(node.decorators) 118 self.dispatch(node.decorators)
118 self.recordAndDispatch(node.code) 119 self.recordAndDispatch(node.code)
119 else: 120 else:
120 self.doSuite(node, node.code) 121 self.doSuite(node, node.code)
121 122
122 visitFunction = visitClass = doCode 123 visitFunction = visitClass = doCode
123 124
124 def getFirstLine(self, node): 125 def getFirstLine(self, node):
125 # Find the first line in the tree node. 126 # Find the first line in the tree node.
126 lineno = node.lineno 127 lineno = node.lineno
136 # Find the first line in the tree node. 137 # Find the first line in the tree node.
137 lineno = node.lineno 138 lineno = node.lineno
138 for n in node.getChildNodes(): 139 for n in node.getChildNodes():
139 lineno = max(lineno, self.getLastLine(n)) 140 lineno = max(lineno, self.getLastLine(n))
140 return lineno 141 return lineno
141 142
142 def doStatement(self, node): 143 def doStatement(self, node):
143 self.recordLine(self.getFirstLine(node)) 144 self.recordLine(self.getFirstLine(node))
144 145
145 visitAssert = visitAssign = visitAssTuple = visitPrint = \ 146 visitAssert = visitAssign = visitAssTuple = visitPrint = \
146 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ 147 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
147 doStatement 148 doStatement
148 149
149 def visitPass(self, node): 150 def visitPass(self, node):
150 # Pass statements have weird interactions with docstrings. If this 151 # Pass statements have weird interactions with docstrings. If this
151 # pass statement is part of one of those pairs, claim that the statement 152 # pass statement is part of one of those pairs, claim that the statement
152 # is on the later of the two lines. 153 # is on the later of the two lines.
153 l = node.lineno 154 l = node.lineno
154 if l: 155 if l:
155 lines = self.suite_spots.get(l, [l,l]) 156 lines = self.suite_spots.get(l, [l,l])
156 self.statements[lines[1]] = 1 157 self.statements[lines[1]] = 1
157 158
158 def visitDiscard(self, node): 159 def visitDiscard(self, node):
159 # Discard nodes are statements that execute an expression, but then 160 # Discard nodes are statements that execute an expression, but then
160 # discard the results. This includes function calls, so we can't 161 # 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 # ignore them all. But if the expression is a constant, the statement
162 # won't be "executed", so don't count it now. 163 # won't be "executed", so don't count it now.
163 if node.expr.__class__.__name__ != 'Const': 164 if node.expr.__class__.__name__ != 'Const':
164 self.doStatement(node) 165 self.doStatement(node)
165 166
169 # like "global a"). 170 # like "global a").
170 if node.__class__.__name__ != 'Stmt': 171 if node.__class__.__name__ != 'Stmt':
171 return self.recordLine(self.getFirstLine(node)) 172 return self.recordLine(self.getFirstLine(node))
172 else: 173 else:
173 return 0 174 return 0
174 175
175 def recordLine(self, lineno): 176 def recordLine(self, lineno):
176 # Returns a bool, whether the line is included or excluded. 177 # Returns a bool, whether the line is included or excluded.
177 if lineno: 178 if lineno:
178 # Multi-line tests introducing suites have to get charged to their 179 # Multi-line tests introducing suites have to get charged to their
179 # keyword. 180 # keyword.
184 if self.excluding_suite: 185 if self.excluding_suite:
185 self.excluded[lineno] = 1 186 self.excluded[lineno] = 1
186 return 0 187 return 0
187 # If this line is excluded, or suite_spots maps this line to 188 # If this line is excluded, or suite_spots maps this line to
188 # another line that is exlcuded, then we're excluded. 189 # another line that is exlcuded, then we're excluded.
189 elif lineno in self.excluded or \ 190 elif self.excluded.has_key(lineno) or \
190 lineno in self.suite_spots and \ 191 self.suite_spots.has_key(lineno) and \
191 self.suite_spots[lineno][1] in self.excluded: 192 self.excluded.has_key(self.suite_spots[lineno][1]):
192 return 0 193 return 0
193 # Otherwise, this is an executable line. 194 # Otherwise, this is an executable line.
194 else: 195 else:
195 self.statements[lineno] = 1 196 self.statements[lineno] = 1
196 return 1 197 return 1
197 return 0 198 return 0
198 199
199 default = recordNodeLine 200 default = recordNodeLine
200 201
201 def recordAndDispatch(self, node): 202 def recordAndDispatch(self, node):
202 self.recordNodeLine(node) 203 self.recordNodeLine(node)
203 self.dispatch(node) 204 self.dispatch(node)
204 205
205 def doSuite(self, intro, body, exclude=0): 206 def doSuite(self, intro, body, exclude=0):
206 exsuite = self.excluding_suite 207 exsuite = self.excluding_suite
207 if exclude or (intro and not self.recordNodeLine(intro)): 208 if exclude or (intro and not self.recordNodeLine(intro)):
208 self.excluding_suite = 1 209 self.excluding_suite = 1
209 self.recordAndDispatch(body) 210 self.recordAndDispatch(body)
210 self.excluding_suite = exsuite 211 self.excluding_suite = exsuite
211 212
212 def doPlainWordSuite(self, prevsuite, suite): 213 def doPlainWordSuite(self, prevsuite, suite):
213 # Finding the exclude lines for else's is tricky, because they aren't 214 # Finding the exclude lines for else's is tricky, because they aren't
214 # present in the compiler parse tree. Look at the previous suite, 215 # present in the compiler parse tree. Look at the previous suite,
215 # and find its last line. If any line between there and the else's 216 # and find its last line. If any line between there and the else's
216 # first line are excluded, then we exclude the else. 217 # first line are excluded, then we exclude the else.
217 lastprev = self.getLastLine(prevsuite) 218 lastprev = self.getLastLine(prevsuite)
218 firstelse = self.getFirstLine(suite) 219 firstelse = self.getFirstLine(suite)
219 for l in range(lastprev+1, firstelse): 220 for l in range(lastprev+1, firstelse):
220 if l in self.suite_spots: 221 if self.suite_spots.has_key(l):
221 self.doSuite(None, suite, exclude=l in self.excluded) 222 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
222 break 223 break
223 else: 224 else:
224 self.doSuite(None, suite) 225 self.doSuite(None, suite)
225 226
226 def doElse(self, prevsuite, node): 227 def doElse(self, prevsuite, node):
227 if node.else_: 228 if node.else_:
228 self.doPlainWordSuite(prevsuite, node.else_) 229 self.doPlainWordSuite(prevsuite, node.else_)
229 230
230 def visitFor(self, node): 231 def visitFor(self, node):
231 self.doSuite(node, node.body) 232 self.doSuite(node, node.body)
232 self.doElse(node.body, node) 233 self.doElse(node.body, node)
233 234
234 visitWhile = visitFor 235 visitWhile = visitFor
254 prev = node.body 255 prev = node.body
255 self.doPlainWordSuite(prev, h) 256 self.doPlainWordSuite(prev, h)
256 else: 257 else:
257 self.doSuite(a, h) 258 self.doSuite(a, h)
258 self.doElse(node.handlers[-1][2], node) 259 self.doElse(node.handlers[-1][2], node)
259 260
260 def visitTryFinally(self, node): 261 def visitTryFinally(self, node):
261 self.doSuite(node, node.body) 262 self.doSuite(node, node.body)
262 self.doPlainWordSuite(node.body, node.final) 263 self.doPlainWordSuite(node.body, node.final)
263 264
264 def visitWith(self, node): 265 def visitWith(self, node):
265 self.doSuite(node, node.body) 266 self.doSuite(node, node.body)
266 267
267 def visitGlobal(self, node): 268 def visitGlobal(self, node):
268 # "global" statements don't execute like others (they don't call the 269 # "global" statements don't execute like others (they don't call the
269 # trace function), so don't record their line numbers. 270 # trace function), so don't record their line numbers.
270 pass 271 pass
271 272
272 the_coverage = None 273 the_coverage = None
273 274
274 class CoverageException(Exception): pass 275 class CoverageException(Exception):
276 pass
275 277
276 class coverage: 278 class coverage:
277 # Name of the cache file (unless environment variable is set). 279 # Name of the cache file (unless environment variable is set).
278 cache_default = ".coverage" 280 cache_default = ".coverage"
279 281
281 cache_env = "COVERAGE_FILE" 283 cache_env = "COVERAGE_FILE"
282 284
283 # A dictionary with an entry for (Python source file name, line number 285 # A dictionary with an entry for (Python source file name, line number
284 # in that file) if that line has been executed. 286 # in that file) if that line has been executed.
285 c = {} 287 c = {}
286 288
287 # A map from canonical Python source file name to a dictionary in 289 # A map from canonical Python source file name to a dictionary in
288 # which there's an entry for each line number that has been 290 # which there's an entry for each line number that has been
289 # executed. 291 # executed.
290 cexecuted = {} 292 cexecuted = {}
291 293
298 canonical_filename_cache = {} 300 canonical_filename_cache = {}
299 301
300 def __init__(self): 302 def __init__(self):
301 global the_coverage 303 global the_coverage
302 if the_coverage: 304 if the_coverage:
303 raise CoverageException, "Only one coverage object allowed." 305 raise CoverageException("Only one coverage object allowed.")
304 self.usecache = 1 306 self.usecache = 1
305 self.cache = None 307 self.cache = None
306 self.parallel_mode = False 308 self.parallel_mode = False
307 self.exclude_re = '' 309 self.exclude_re = ''
308 self.nesting = 0 310 self.nesting = 0
309 self.cstack = [] 311 self.cstack = []
310 self.xstack = [] 312 self.xstack = []
311 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep) 313 self.relative_dir = self.abs_file(os.curdir)+os.sep
312 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]') 314 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
313 315
314 # t(f, x, y). This method is passed to sys.settrace as a trace function. 316 # 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 317 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
316 # the arguments and return value of the trace function. 318 # the arguments and return value of the trace function.
317 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code 319 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
318 # objects. 320 # objects.
319 321
320 def t(self, f, w, unused): #pragma: no cover 322 def t(self, f, w, unused): #pragma: no cover
321 if w == 'line': 323 if w == 'line':
322 #print "Executing %s @ %d" % (f.f_code.co_filename, f.f_lineno)
323 self.c[(f.f_code.co_filename, f.f_lineno)] = 1 324 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
324 for c in self.cstack: 325 #-for c in self.cstack:
325 c[(f.f_code.co_filename, f.f_lineno)] = 1 326 #- c[(f.f_code.co_filename, f.f_lineno)] = 1
326 return self.t 327 return self.t
327 328
328 def help(self, error=None): #pragma: no cover 329 def help(self, error=None): #pragma: no cover
329 if error: 330 if error:
330 print error 331 print error
331 print 332 print
332 print __doc__ 333 print __doc__
351 } 352 }
352 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') 353 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
353 long_opts = optmap.values() 354 long_opts = optmap.values()
354 options, args = getopt.getopt(argv, short_opts, long_opts) 355 options, args = getopt.getopt(argv, short_opts, long_opts)
355 for o, a in options: 356 for o, a in options:
356 if o in optmap: 357 if optmap.has_key(o):
357 settings[optmap[o]] = 1 358 settings[optmap[o]] = 1
358 elif o + ':' in optmap: 359 elif optmap.has_key(o + ':'):
359 settings[optmap[o + ':']] = a 360 settings[optmap[o + ':']] = a
360 elif o[2:] in long_opts: 361 elif o[2:] in long_opts:
361 settings[o[2:]] = 1 362 settings[o[2:]] = 1
362 elif o[2:] + '=' in long_opts: 363 elif o[2:] + '=' in long_opts:
363 settings[o[2:]+'='] = a 364 settings[o[2:]+'='] = a
374 "options at the same time." % (i, j)) 375 "options at the same time." % (i, j))
375 376
376 args_needed = (settings.get('execute') 377 args_needed = (settings.get('execute')
377 or settings.get('annotate') 378 or settings.get('annotate')
378 or settings.get('report')) 379 or settings.get('report'))
379 action = (settings.get('erase') 380 action = (settings.get('erase')
380 or settings.get('collect') 381 or settings.get('collect')
381 or args_needed) 382 or args_needed)
382 if not action: 383 if not action:
383 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.") 384 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
384 if not args_needed and args: 385 if not args_needed and args:
385 help_fn("Unexpected arguments: %s" % " ".join(args)) 386 help_fn("Unexpected arguments: %s" % " ".join(args))
386 387
387 self.parallel_mode = settings.get('parallel-mode') 388 self.parallel_mode = settings.get('parallel-mode')
388 self.get_ready() 389 self.get_ready()
389 390
390 if settings.get('erase'): 391 if settings.get('erase'):
391 self.erase() 392 self.erase()
399 execfile(sys.argv[0], __main__.__dict__) 400 execfile(sys.argv[0], __main__.__dict__)
400 if settings.get('collect'): 401 if settings.get('collect'):
401 self.collect() 402 self.collect()
402 if not args: 403 if not args:
403 args = self.cexecuted.keys() 404 args = self.cexecuted.keys()
404 405
405 ignore_errors = settings.get('ignore-errors') 406 ignore_errors = settings.get('ignore-errors')
406 show_missing = settings.get('show-missing') 407 show_missing = settings.get('show-missing')
407 directory = settings.get('directory=') 408 directory = settings.get('directory=')
408 409
409 omit = settings.get('omit=') 410 omit = settings.get('omit=')
410 if omit is not None: 411 if omit is not None:
411 omit = omit.split(',') 412 omit = [self.abs_file(p) for p in omit.split(',')]
412 else: 413 else:
413 omit = [] 414 omit = []
414 415
415 omit = [os.path.normcase(os.path.abspath(os.path.realpath(p)))
416 for p in omit]
417
418 if settings.get('report'): 416 if settings.get('report'):
419 self.report(args, show_missing, ignore_errors, omit_prefixes=omit) 417 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
420 if settings.get('annotate'): 418 if settings.get('annotate'):
421 self.annotate(args, directory, ignore_errors, omit_prefixes=omit) 419 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
422 420
423 def use_cache(self, usecache, cache_file=None): 421 def use_cache(self, usecache, cache_file=None):
424 self.usecache = usecache 422 self.usecache = usecache
425 if cache_file and not self.cache: 423 if cache_file and not self.cache:
426 self.cache_default = cache_file 424 self.cache_default = cache_file
427 425
428 def get_ready(self, parallel_mode=False): 426 def get_ready(self, parallel_mode=False):
429 if self.usecache and not self.cache: 427 if self.usecache and not self.cache:
430 self.cache = os.environ.get(self.cache_env, self.cache_default) 428 self.cache = os.environ.get(self.cache_env, self.cache_default)
431 if self.parallel_mode: 429 if self.parallel_mode:
432 self.cache += "." + gethostname() + "." + str(os.getpid()) 430 self.cache += "." + gethostname() + "." + str(os.getpid())
433 self.restore() 431 self.restore()
434 self.analysis_cache = {} 432 self.analysis_cache = {}
435 433
436 def start(self, parallel_mode=False): 434 def start(self, parallel_mode=False):
437 self.get_ready() 435 self.get_ready()
438 if self.nesting == 0: #pragma: no cover 436 if self.nesting == 0: #pragma: no cover
439 sys.settrace(self.t) 437 sys.settrace(self.t)
440 if hasattr(threading, 'settrace'): 438 if hasattr(threading, 'settrace'):
441 threading.settrace(self.t) 439 threading.settrace(self.t)
442 self.nesting += 1 440 self.nesting += 1
443 441
444 def stop(self): 442 def stop(self):
445 self.nesting -= 1 443 self.nesting -= 1
446 if self.nesting == 0: #pragma: no cover 444 if self.nesting == 0: #pragma: no cover
447 sys.settrace(None) 445 sys.settrace(None)
448 if hasattr(threading, 'settrace'): 446 if hasattr(threading, 'settrace'):
462 self.exclude_re += "(" + re + ")" 460 self.exclude_re += "(" + re + ")"
463 461
464 def begin_recursive(self): 462 def begin_recursive(self):
465 self.cstack.append(self.c) 463 self.cstack.append(self.c)
466 self.xstack.append(self.exclude_re) 464 self.xstack.append(self.exclude_re)
467 465
468 def end_recursive(self): 466 def end_recursive(self):
469 self.c = self.cstack.pop() 467 self.c = self.cstack.pop()
470 self.exclude_re = self.xstack.pop() 468 self.exclude_re = self.xstack.pop()
471 469
472 # save(). Save coverage data to the coverage cache. 470 # save(). Save coverage data to the coverage cache.
513 cexecuted = self.restore_file(full_path) 511 cexecuted = self.restore_file(full_path)
514 self.merge_data(cexecuted) 512 self.merge_data(cexecuted)
515 513
516 def merge_data(self, new_data): 514 def merge_data(self, new_data):
517 for file_name, file_data in new_data.items(): 515 for file_name, file_data in new_data.items():
518 if file_name in self.cexecuted: 516 if self.cexecuted.has_key(file_name):
519 self.merge_file_data(self.cexecuted[file_name], file_data) 517 self.merge_file_data(self.cexecuted[file_name], file_data)
520 else: 518 else:
521 self.cexecuted[file_name] = file_data 519 self.cexecuted[file_name] = file_data
522 520
523 def merge_file_data(self, cache_data, new_data): 521 def merge_file_data(self, cache_data, new_data):
524 for line_number in new_data.keys(): 522 for line_number in new_data.keys():
525 if not line_number in cache_data: 523 if not cache_data.has_key(line_number):
526 cache_data[line_number] = new_data[line_number] 524 cache_data[line_number] = new_data[line_number]
525
526 def abs_file(self, filename):
527 """ Helper function to turn a filename into an absolute normalized
528 filename.
529 """
530 return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
531
532 def get_zip_data(self, filename):
533 """ Get data from `filename` if it is a zip file path, or return None
534 if it is not.
535 """
536 markers = ['.zip'+os.sep, '.egg'+os.sep]
537 for marker in markers:
538 if marker in filename:
539 parts = filename.split(marker)
540 try:
541 zi = zipimport.zipimporter(parts[0]+marker[:-1])
542 except zipimport.ZipImportError:
543 continue
544 try:
545 data = zi.get_data(parts[1])
546 except IOError:
547 continue
548 return data
549 return None
527 550
528 # canonical_filename(filename). Return a canonical filename for the 551 # canonical_filename(filename). Return a canonical filename for the
529 # file (that is, an absolute path with no redundant components and 552 # file (that is, an absolute path with no redundant components and
530 # normalized case). See [GDR 2001-12-04b, 3.3]. 553 # normalized case). See [GDR 2001-12-04b, 3.3].
531 554
532 def canonical_filename(self, filename): 555 def canonical_filename(self, filename):
533 if not filename in self.canonical_filename_cache: 556 if not self.canonical_filename_cache.has_key(filename):
534 f = filename 557 f = filename
535 if os.path.isabs(f) and not os.path.exists(f): 558 if os.path.isabs(f) and not os.path.exists(f):
536 f = os.path.basename(f) 559 if not self.get_zip_data(f):
560 f = os.path.basename(f)
537 if not os.path.isabs(f): 561 if not os.path.isabs(f):
538 for path in [os.curdir] + sys.path: 562 for path in [os.curdir] + sys.path:
539 g = os.path.join(path, f) 563 g = os.path.join(path, f)
540 if os.path.exists(g): 564 if os.path.exists(g):
541 f = g 565 f = g
542 break 566 break
543 cf = os.path.normcase(os.path.abspath(os.path.realpath(f))) 567 cf = self.abs_file(f)
544 self.canonical_filename_cache[filename] = cf 568 self.canonical_filename_cache[filename] = cf
545 return self.canonical_filename_cache[filename] 569 return self.canonical_filename_cache[filename]
546 570
547 # canonicalize_filenames(). Copy results from "c" to "cexecuted", 571 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
548 # canonicalizing filenames on the way. Clear the "c" map. 572 # canonicalizing filenames on the way. Clear the "c" map.
549 573
550 def canonicalize_filenames(self): 574 def canonicalize_filenames(self):
551 for filename, lineno in self.c.keys(): 575 for filename, lineno in self.c.keys():
552 if filename == '<string>': 576 if filename == '<string>':
553 # Can't do anything useful with exec'd strings, so skip them. 577 # Can't do anything useful with exec'd strings, so skip them.
554 continue 578 continue
555 f = self.canonical_filename(filename) 579 f = self.canonical_filename(filename)
556 if not f in self.cexecuted: 580 if not self.cexecuted.has_key(f):
557 self.cexecuted[f] = {} 581 self.cexecuted[f] = {}
558 self.cexecuted[f][lineno] = 1 582 self.cexecuted[f][lineno] = 1
559 self.c = {} 583 self.c = {}
560 584
561 # morf_filename(morf). Return the filename for a module or file. 585 # morf_filename(morf). Return the filename for a module or file.
562 586
563 def morf_filename(self, morf): 587 def morf_filename(self, morf):
564 if isinstance(morf, types.ModuleType): 588 if hasattr(morf, '__file__'):
565 if not hasattr(morf, '__file__'):
566 raise CoverageException, "Module has no __file__ attribute."
567 f = morf.__file__ 589 f = morf.__file__
568 else: 590 else:
569 f = morf 591 f = morf
570 return self.canonical_filename(f) 592 return self.canonical_filename(f)
571 593
574 # Otherwise, return a tuple of (1) the canonical filename of the 596 # Otherwise, return a tuple of (1) the canonical filename of the
575 # source code for the module, (2) a list of lines of statements 597 # source code for the module, (2) a list of lines of statements
576 # in the source code, (3) a list of lines of excluded statements, 598 # in the source code, (3) a list of lines of excluded statements,
577 # and (4), a map of line numbers to multi-line line number ranges, for 599 # and (4), a map of line numbers to multi-line line number ranges, for
578 # statements that cross lines. 600 # statements that cross lines.
579 601
580 def analyze_morf(self, morf): 602 def analyze_morf(self, morf):
581 if morf in self.analysis_cache: 603 if self.analysis_cache.has_key(morf):
582 return self.analysis_cache[morf] 604 return self.analysis_cache[morf]
583 filename = self.morf_filename(morf) 605 filename = self.morf_filename(morf)
584 ext = os.path.splitext(filename)[1] 606 ext = os.path.splitext(filename)[1]
607 source, sourcef = None, None
585 if ext == '.pyc': 608 if ext == '.pyc':
586 if not os.path.exists(filename[0:-1]): 609 if not os.path.exists(filename[:-1]):
587 raise CoverageException, ("No source for compiled code '%s'." 610 source = self.get_zip_data(filename[:-1])
588 % filename) 611 if not source:
589 filename = filename[0:-1] 612 raise CoverageException(
590 elif ext != '.py': 613 "No source for compiled code '%s'." % filename
591 raise CoverageException, "File '%s' not Python source." % filename 614 )
592 source = open(filename, 'r') 615 filename = filename[:-1]
593 lines, excluded_lines, line_map = self.find_executable_statements( 616 if not source:
594 source.read(), exclude=self.exclude_re 617 sourcef = open(filename, 'rU')
595 ) 618 source = sourcef.read()
596 source.close() 619 try:
620 lines, excluded_lines, line_map = self.find_executable_statements(
621 source, exclude=self.exclude_re
622 )
623 except SyntaxError, synerr:
624 raise CoverageException(
625 "Couldn't parse '%s' as Python source: '%s' at line %d" %
626 (filename, synerr.msg, synerr.lineno)
627 )
628 if sourcef:
629 sourcef.close()
597 result = filename, lines, excluded_lines, line_map 630 result = filename, lines, excluded_lines, line_map
598 self.analysis_cache[morf] = result 631 self.analysis_cache[morf] = result
599 return result 632 return result
600 633
601 def first_line_of_tree(self, tree): 634 def first_line_of_tree(self, tree):
602 while True: 635 while True:
603 if len(tree) == 3 and type(tree[2]) == type(1): 636 if len(tree) == 3 and type(tree[2]) == type(1):
604 return tree[2] 637 return tree[2]
605 tree = tree[1] 638 tree = tree[1]
606 639
607 def last_line_of_tree(self, tree): 640 def last_line_of_tree(self, tree):
608 while True: 641 while True:
609 if len(tree) == 3 and type(tree[2]) == type(1): 642 if len(tree) == 3 and type(tree[2]) == type(1):
610 return tree[2] 643 return tree[2]
611 tree = tree[-1] 644 tree = tree[-1]
612 645
613 def find_docstring_pass_pair(self, tree, spots): 646 def find_docstring_pass_pair(self, tree, spots):
614 for i in range(1, len(tree)): 647 for i in range(1, len(tree)):
615 if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]): 648 if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
616 first_line = self.first_line_of_tree(tree[i]) 649 first_line = self.first_line_of_tree(tree[i])
617 last_line = self.last_line_of_tree(tree[i+1]) 650 last_line = self.last_line_of_tree(tree[i+1])
618 self.record_multiline(spots, first_line, last_line) 651 self.record_multiline(spots, first_line, last_line)
619 652
620 def is_string_constant(self, tree): 653 def is_string_constant(self, tree):
621 try: 654 try:
622 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt 655 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
623 except: 656 except:
624 return False 657 return False
625 658
626 def is_pass_stmt(self, tree): 659 def is_pass_stmt(self, tree):
627 try: 660 try:
628 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt 661 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
629 except: 662 except:
630 return False 663 return False
631 664
632 def record_multiline(self, spots, i, j): 665 def record_multiline(self, spots, i, j):
633 for l in range(i, j+1): 666 for l in range(i, j+1):
634 spots[l] = (i, j) 667 spots[l] = (i, j)
635 668
636 def get_suite_spots(self, tree, spots): 669 def get_suite_spots(self, tree, spots):
637 """ Analyze a parse tree to find suite introducers which span a number 670 """ Analyze a parse tree to find suite introducers which span a number
638 of lines. 671 of lines.
639 """ 672 """
640 for i in range(1, len(tree)): 673 for i in range(1, len(tree)):
672 705
673 # "pass" statements are tricky: different versions of Python 706 # "pass" statements are tricky: different versions of Python
674 # treat them differently, especially in the common case of a 707 # treat them differently, especially in the common case of a
675 # function with a doc string and a single pass statement. 708 # function with a doc string and a single pass statement.
676 self.find_docstring_pass_pair(tree[i], spots) 709 self.find_docstring_pass_pair(tree[i], spots)
677 710
678 elif tree[i][0] == symbol.simple_stmt: 711 elif tree[i][0] == symbol.simple_stmt:
679 first_line = self.first_line_of_tree(tree[i]) 712 first_line = self.first_line_of_tree(tree[i])
680 last_line = self.last_line_of_tree(tree[i]) 713 last_line = self.last_line_of_tree(tree[i])
681 if first_line != last_line: 714 if first_line != last_line:
682 self.record_multiline(spots, first_line, last_line) 715 self.record_multiline(spots, first_line, last_line)
697 # are multiline, and where suites begin and end. 730 # are multiline, and where suites begin and end.
698 import parser 731 import parser
699 tree = parser.suite(text+'\n\n').totuple(1) 732 tree = parser.suite(text+'\n\n').totuple(1)
700 self.get_suite_spots(tree, suite_spots) 733 self.get_suite_spots(tree, suite_spots)
701 #print "Suite spots:", suite_spots 734 #print "Suite spots:", suite_spots
702 735
703 # Use the compiler module to parse the text and find the executable 736 # Use the compiler module to parse the text and find the executable
704 # statements. We add newlines to be impervious to final partial lines. 737 # statements. We add newlines to be impervious to final partial lines.
705 statements = {} 738 statements = {}
706 ast = compiler.parse(text+'\n\n') 739 ast = compiler.parse(text+'\n\n')
707 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) 740 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
753 return f, s, m, mf 786 return f, s, m, mf
754 787
755 def analysis2(self, morf): 788 def analysis2(self, morf):
756 filename, statements, excluded, line_map = self.analyze_morf(morf) 789 filename, statements, excluded, line_map = self.analyze_morf(morf)
757 self.canonicalize_filenames() 790 self.canonicalize_filenames()
758 if not filename in self.cexecuted: 791 if not self.cexecuted.has_key(filename):
759 self.cexecuted[filename] = {} 792 self.cexecuted[filename] = {}
760 missing = [] 793 missing = []
761 for line in statements: 794 for line in statements:
762 lines = line_map.get(line, [line, line]) 795 lines = line_map.get(line, [line, line])
763 for l in range(lines[0], lines[1]+1): 796 for l in range(lines[0], lines[1]+1):
764 if l in self.cexecuted[filename]: 797 if self.cexecuted[filename].has_key(l):
765 break 798 break
766 else: 799 else:
767 missing.append(line) 800 missing.append(line)
768 return (filename, statements, excluded, missing, 801 return (filename, statements, excluded, missing,
769 self.format_lines(statements, missing)) 802 self.format_lines(statements, missing))
774 return filename.replace(self.relative_dir, "") 807 return filename.replace(self.relative_dir, "")
775 808
776 def morf_name(self, morf): 809 def morf_name(self, morf):
777 """ Return the name of morf as used in report. 810 """ Return the name of morf as used in report.
778 """ 811 """
779 if isinstance(morf, types.ModuleType): 812 if hasattr(morf, '__name__'):
780 return morf.__name__ 813 return morf.__name__
781 else: 814 else:
782 return self.relative_filename(os.path.splitext(morf)[0]) 815 return self.relative_filename(os.path.splitext(morf)[0])
783 816
784 def filter_by_prefix(self, morfs, omit_prefixes): 817 def filter_by_prefix(self, morfs, omit_prefixes):
807 if isinstance(morf, strclass): 840 if isinstance(morf, strclass):
808 globbed.extend(glob.glob(morf)) 841 globbed.extend(glob.glob(morf))
809 else: 842 else:
810 globbed.append(morf) 843 globbed.append(morf)
811 morfs = globbed 844 morfs = globbed
812 845
813 morfs = self.filter_by_prefix(morfs, omit_prefixes) 846 morfs = self.filter_by_prefix(morfs, omit_prefixes)
814 morfs.sort(self.morf_name_compare) 847 morfs.sort(self.morf_name_compare)
815 848
816 max_name = max([5,] + map(len, map(self.morf_name, morfs))) 849 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
817 fmt_name = "%%- %ds " % max_name 850 fmt_name = "%%- %ds " % max_name
845 total_executed = total_executed + m 878 total_executed = total_executed + m
846 except KeyboardInterrupt: #pragma: no cover 879 except KeyboardInterrupt: #pragma: no cover
847 raise 880 raise
848 except: 881 except:
849 if not ignore_errors: 882 if not ignore_errors:
850 typ, msg = sys.exc_info()[0:2] 883 typ, msg = sys.exc_info()[:2]
851 print >>file, fmt_err % (name, typ, msg) 884 print >>file, fmt_err % (name, typ, msg)
852 if len(morfs) > 1: 885 if len(morfs) > 1:
853 print >>file, "-" * len(header) 886 print >>file, "-" * len(header)
854 if total_statements > 0: 887 if total_statements > 0:
855 pc = 100.0 * total_executed / total_statements 888 pc = 100.0 * total_executed / total_statements
874 except KeyboardInterrupt: 907 except KeyboardInterrupt:
875 raise 908 raise
876 except: 909 except:
877 if not ignore_errors: 910 if not ignore_errors:
878 raise 911 raise
879 912
880 def annotate_file(self, filename, statements, excluded, missing, directory=None): 913 def annotate_file(self, filename, statements, excluded, missing, directory=None):
881 source = open(filename, 'r') 914 source = open(filename, 'r')
882 if directory: 915 if directory:
883 dest_file = os.path.join(directory, 916 dest_file = os.path.join(directory,
884 os.path.basename(filename) 917 os.path.basename(filename)
902 if i < len(statements) and statements[i] == lineno: 935 if i < len(statements) and statements[i] == lineno:
903 covered = j >= len(missing) or missing[j] > lineno 936 covered = j >= len(missing) or missing[j] > lineno
904 if self.blank_re.match(line): 937 if self.blank_re.match(line):
905 dest.write(' ') 938 dest.write(' ')
906 elif self.else_re.match(line): 939 elif self.else_re.match(line):
907 # Special logic for lines containing only 'else:'. 940 # Special logic for lines containing only 'else:'.
908 # See [GDR 2001-12-04b, 3.2]. 941 # See [GDR 2001-12-04b, 3.2].
909 if i >= len(statements) and j >= len(missing): 942 if i >= len(statements) and j >= len(missing):
910 dest.write('! ') 943 dest.write('! ')
911 elif i >= len(statements) or j >= len(missing): 944 elif i >= len(statements) or j >= len(missing):
912 dest.write('> ') 945 dest.write('> ')
926 959
927 # Singleton object. 960 # Singleton object.
928 the_coverage = coverage() 961 the_coverage = coverage()
929 962
930 # Module functions call methods in the singleton object. 963 # Module functions call methods in the singleton object.
931 def use_cache(*args, **kw): 964 def use_cache(*args, **kw):
932 return the_coverage.use_cache(*args, **kw) 965 return the_coverage.use_cache(*args, **kw)
933 966
934 def start(*args, **kw): 967 def start(*args, **kw):
935 return the_coverage.start(*args, **kw) 968 return the_coverage.start(*args, **kw)
936 969
937 def stop(*args, **kw): 970 def stop(*args, **kw):
938 return the_coverage.stop(*args, **kw) 971 return the_coverage.stop(*args, **kw)
939 972
940 def erase(*args, **kw): 973 def erase(*args, **kw):
941 return the_coverage.erase(*args, **kw) 974 return the_coverage.erase(*args, **kw)
942 975
943 def begin_recursive(*args, **kw): 976 def begin_recursive(*args, **kw):
944 return the_coverage.begin_recursive(*args, **kw) 977 return the_coverage.begin_recursive(*args, **kw)
945 978
946 def end_recursive(*args, **kw): 979 def end_recursive(*args, **kw):
947 return the_coverage.end_recursive(*args, **kw) 980 return the_coverage.end_recursive(*args, **kw)
948 981
949 def exclude(*args, **kw): 982 def exclude(*args, **kw):
950 return the_coverage.exclude(*args, **kw) 983 return the_coverage.exclude(*args, **kw)
951 984
952 def analysis(*args, **kw): 985 def analysis(*args, **kw):
953 return the_coverage.analysis(*args, **kw) 986 return the_coverage.analysis(*args, **kw)
954 987
955 def analysis2(*args, **kw): 988 def analysis2(*args, **kw):
956 return the_coverage.analysis2(*args, **kw) 989 return the_coverage.analysis2(*args, **kw)
957 990
958 def report(*args, **kw): 991 def report(*args, **kw):
959 return the_coverage.report(*args, **kw) 992 return the_coverage.report(*args, **kw)
960 993
961 def annotate(*args, **kw): 994 def annotate(*args, **kw):
962 return the_coverage.annotate(*args, **kw) 995 return the_coverage.annotate(*args, **kw)
963 996
964 def annotate_file(*args, **kw): 997 def annotate_file(*args, **kw):
965 return the_coverage.annotate_file(*args, **kw) 998 return the_coverage.annotate_file(*args, **kw)
966 999
967 # Save coverage data when Python exits. (The atexit module wasn't 1000 # Save coverage data when Python exits. (The atexit module wasn't
968 # introduced until Python 2.0, so use sys.exitfunc when it's not 1001 # introduced until Python 2.0, so use sys.exitfunc when it's not
969 # available.) 1002 # available.)
971 import atexit 1004 import atexit
972 atexit.register(the_coverage.save) 1005 atexit.register(the_coverage.save)
973 except ImportError: 1006 except ImportError:
974 sys.exitfunc = the_coverage.save 1007 sys.exitfunc = the_coverage.save
975 1008
1009 def main():
1010 the_coverage.command_line(sys.argv[1:])
1011
976 # Command-line interface. 1012 # Command-line interface.
977 if __name__ == '__main__': 1013 if __name__ == '__main__':
978 the_coverage.command_line(sys.argv[1:]) 1014 main()
979 1015
980 1016
981 # A. REFERENCES 1017 # A. REFERENCES
982 # 1018 #
983 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; 1019 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
1034 # 1070 #
1035 # 2004-12-31 NMB Allow for keyword arguments in the module global functions. 1071 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1036 # Thanks, Allen. 1072 # Thanks, Allen.
1037 # 1073 #
1038 # 2005-12-02 NMB Call threading.settrace so that all threads are measured. 1074 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
1039 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be 1075 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1040 # captured to a different destination. 1076 # captured to a different destination.
1041 # 1077 #
1042 # 2005-12-03 NMB coverage.py can now measure itself. 1078 # 2005-12-03 NMB coverage.py can now measure itself.
1043 # 1079 #
1044 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, 1080 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1077 # 1113 #
1078 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the 1114 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1079 # new with statement is counted as executable. 1115 # new with statement is counted as executable.
1080 # 1116 #
1081 # 2007-07-29 NMB Better packaging. 1117 # 2007-07-29 NMB Better packaging.
1082 1118 #
1119 # 2007-09-30 NMB Don't try to predict whether a file is Python source based on
1120 # the extension. Extensionless files are often Pythons scripts. Instead, simply
1121 # parse the file and catch the syntax errors. Hat tip to Ben Finney.
1122 #
1123 # 2008-05-25 NMB Open files in rU mode to avoid line ending craziness.
1124 # Thanks, Edward Loper.
1125 #
1126 # 2008-09-14 NMB Add support for finding source files in eggs.
1127 # Don't check for morf's being instances of ModuleType, instead use duck typing
1128 # so that pseudo-modules can participate. Thanks, Imri Goldberg.
1129 # Use os.realpath as part of the fixing of filenames so that symlinks won't
1130 # confuse things. Thanks, Patrick Mezard.
1131 #
1132 #
1083 # C. COPYRIGHT AND LICENCE 1133 # C. COPYRIGHT AND LICENCE
1084 # 1134 #
1085 # Copyright 2001 Gareth Rees. All rights reserved. 1135 # Copyright 2001 Gareth Rees. All rights reserved.
1086 # Copyright 2004-2007 Ned Batchelder. All rights reserved. 1136 # Copyright 2004-2008 Ned Batchelder. All rights reserved.
1087 # 1137 #
1088 # Redistribution and use in source and binary forms, with or without 1138 # Redistribution and use in source and binary forms, with or without
1089 # modification, are permitted provided that the following conditions are 1139 # modification, are permitted provided that the following conditions are
1090 # met: 1140 # met:
1091 # 1141 #
1108 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 1158 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1109 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 1159 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1110 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 1160 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1111 # DAMAGE. 1161 # DAMAGE.
1112 # 1162 #
1113 # $Id: coverage.py 74 2007-07-29 22:28:35Z nedbat $ 1163 # $Id: coverage.py 96 2008-09-14 18:34:13Z nedbat $