Mercurial > public > mercurial-scm > hg-stable
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 $ |