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