comparison tests/coverage.py @ 5592:7a4d846b178f

import latest coverage.py version
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Sun, 02 Dec 2007 23:26:40 +0100
parents d9e385a7a806
children 8623debad845
comparison
equal deleted inserted replaced
5591:08887121a652 5592:7a4d846b178f
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/>
5 # 5 #
6 # COVERAGE.PY -- COVERAGE TESTING 6 # COVERAGE.PY -- COVERAGE TESTING
20 # 20 #
21 # See [GDR 2001-12-04a] for the command-line interface, programmatic 21 # See [GDR 2001-12-04a] for the command-line interface, programmatic
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.
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.
33 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.
37 42
47 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
48 53
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 #
65 # This uses the "singleton" pattern. 80 # This uses the "singleton" pattern.
66 # 81 #
79 # at runtime and so execution time depends on the length of variables! 94 # at runtime and so execution time depends on the length of variables!
80 # In the bottleneck of this application it's appropriate to abbreviate 95 # In the bottleneck of this application it's appropriate to abbreviate
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):
106 # Find the first line in the tree node. 125 # Find the first line in the tree node.
107 lineno = node.lineno 126 lineno = node.lineno
117 # Find the first line in the tree node. 136 # Find the first line in the tree node.
118 lineno = node.lineno 137 lineno = node.lineno
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:
136 # Multi-line tests introducing suites have to get charged to their 178 # Multi-line tests introducing suites have to get charged to their
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
144 return 0 186 return 0
145 # If this line is excluded, or suite_spots maps this line to 187 # If this line is excluded, or suite_spots maps this line to
151 # Otherwise, this is an executable line. 193 # Otherwise, this is an executable line.
152 else: 194 else:
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)
162 204
163 def doSuite(self, intro, body, exclude=0): 205 def doSuite(self, intro, body, exclude=0):
164 exsuite = self.excluding_suite 206 exsuite = self.excluding_suite
165 if exclude or (intro and not self.recordNodeLine(intro)): 207 if exclude or (intro and not self.recordNodeLine(intro)):
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,
173 # and find its last line. If any line between there and the else's 215 # and find its last line. If any line between there and the else's
174 # first line are excluded, then we exclude the else. 216 # first line are excluded, then we exclude the else.
178 if self.suite_spots.has_key(l): 220 if self.suite_spots.has_key(l):
179 self.doSuite(None, suite, exclude=self.excluded.has_key(l)) 221 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
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)
233
234 visitWhile = visitFor
191 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
195 # are credited to the line with the test for the elif. 239 # are credited to the line with the test for the elif.
196 self.doSuite(node, node.tests[0][1]) 240 self.doSuite(node, node.tests[0][1])
197 for t, n in node.tests[1:]: 241 for t, n in node.tests[1:]:
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
201 def visitWhile(self, node):
202 self.doSuite(node, node.body)
203 self.doElse(node.body, node)
204 244
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)):
208 a, b, h = node.handlers[i] 248 a, b, h = node.handlers[i]
214 prev = node.body 254 prev = node.body
215 self.doPlainWordSuite(prev, h) 255 self.doPlainWordSuite(prev, h)
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.
227 pass 270 pass
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
237 # Environment variable naming the cache file. 280 # Environment variable naming the cache file.
238 cache_env = "COVERAGE_FILE" 281 cache_env = "COVERAGE_FILE"
239 282
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.
247 cexecuted = {} 290 cexecuted = {}
248 291
255 canonical_filename_cache = {} 298 canonical_filename_cache = {}
256 299
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)
268 312 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
269 # t(f, x, y). This method is passed to sys.settrace as a trace function. 313
270 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and 314 # t(f, x, y). This method is passed to sys.settrace as a trace function.
315 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
271 # 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
310 elif optmap.has_key(o + ':'): 358 elif optmap.has_key(o + ':'):
311 settings[optmap[o + ':']] = a 359 settings[optmap[o + ':']] = a
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
381 sys.settrace(None) 444 sys.settrace(None)
382 if hasattr(threading, 'settrace'): 445 if hasattr(threading, 'settrace'):
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:
395 self.exclude_re += "|" 458 self.exclude_re += "|"
396 self.exclude_re += "(" + re + ")" 459 self.exclude_re += "(" + re + ")"
397 460
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()
405 468
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')
414 import marshal 475 import marshal
415 marshal.dump(self.cexecuted, cache) 476 marshal.dump(self.cexecuted, cache)
419 480
420 def restore(self): 481 def restore(self):
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
438 # normalized case). See [GDR 2001-12-04b, 3.3]. 527 # normalized case). See [GDR 2001-12-04b, 3.3].
439 528
450 break 539 break
451 cf = os.path.normcase(os.path.abspath(f)) 540 cf = os.path.normcase(os.path.abspath(f))
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] = {}
463 self.cexecuted[f][lineno] = 1 555 self.cexecuted[f][lineno] = 1
464 self.c = {} 556 self.c = {}
466 # morf_filename(morf). Return the filename for a module or file. 558 # morf_filename(morf). Return the filename for a module or file.
467 559
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]
486 filename = self.morf_filename(morf) 580 filename = self.morf_filename(morf)
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
517 # after the keyword. 650 # after the keyword.
518 t = tree[j+1] 651 t = tree[j+1]
530 lineno_word = tree[j][1][2] 663 lineno_word = tree[j][1][2]
531 break 664 break
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):
540 # Find lines which match an exclusion pattern. 683 # Find lines which match an exclusion pattern.
541 excluded = {} 684 excluded = {}
545 lines = text.split('\n') 688 lines = text.split('\n')
546 for i in range(len(lines)): 689 for i in range(len(lines)):
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 = {}
557 ast = compiler.parse(text+'\n\n') 703 ast = compiler.parse(text+'\n\n')
558 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) 704 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
560 706
561 lines = statements.keys() 707 lines = statements.keys()
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
569 # represent consecutive statements. This will coalesce even if 715 # represent consecutive statements. This will coalesce even if
570 # there are gaps between statements, so if statements = 716 # there are gaps between statements, so if statements =
593 start, end = pair 739 start, end = pair
594 if start == end: 740 if start == end:
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):
602 f, s, _, m, mf = self.analysis2(morf) 749 f, s, _, m, mf = self.analysis2(morf)
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))
616 767
617 def relative_filename(self, filename): 768 def relative_filename(self, filename):
645 return cmp(self.morf_name(x), self.morf_name(y)) 796 return cmp(self.morf_name(x), self.morf_name(y))
646 797
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
653 max_name = max([5,] + map(len, map(self.morf_name, morfs))) 813 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
654 fmt_name = "%%- %ds " % max_name 814 fmt_name = "%%- %ds " % max_name
682 total_executed = total_executed + m 842 total_executed = total_executed + m
683 except KeyboardInterrupt: #pragma: no cover 843 except KeyboardInterrupt: #pragma: no cover
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:
692 pc = 100.0 * total_executed / total_statements 852 pc = 100.0 * total_executed / total_statements
693 else: 853 else:
711 except KeyboardInterrupt: 871 except KeyboardInterrupt:
712 raise 872 raise
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:
720 dest_file = os.path.join(directory, 880 dest_file = os.path.join(directory,
721 os.path.basename(filename) 881 os.path.basename(filename)
739 if i < len(statements) and statements[i] == lineno: 899 if i < len(statements) and statements[i] == lineno:
740 covered = j >= len(missing) or missing[j] > lineno 900 covered = j >= len(missing) or missing[j] > lineno
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('! ')
748 elif i >= len(statements) or j >= len(missing): 908 elif i >= len(statements) or j >= len(missing):
749 dest.write('> ') 909 dest.write('> ')
763 923
764 # Singleton object. 924 # Singleton object.
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
783 # available.) 966 # available.)
784 try: 967 try:
787 except ImportError: 970 except ImportError:
788 sys.exitfunc = the_coverage.save 971 sys.exitfunc = the_coverage.save
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
796 # 979 #
797 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; 980 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
848 # 1031 #
849 # 2004-12-31 NMB Allow for keyword arguments in the module global functions. 1032 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
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.
857 # 1040 #
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
868 # met: 1087 # met:
869 # 1088 #
886 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 1105 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
887 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 1106 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
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 $