##// END OF EJS Templates
coverage: deal with symlinked input paths (MacOSX issue)...
Patrick Mezard -
r6349:6aaf5b1d default
parent child Browse files
Show More
@@ -1,1110 +1,1113 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env 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
7 #
7 #
8 # Gareth Rees, Ravenbrook Limited, 2001-12-04
8 # Gareth Rees, Ravenbrook Limited, 2001-12-04
9 # Ned Batchelder, 2004-12-12
9 # Ned Batchelder, 2004-12-12
10 # http://nedbatchelder.com/code/modules/coverage.html
10 # http://nedbatchelder.com/code/modules/coverage.html
11 #
11 #
12 #
12 #
13 # 1. INTRODUCTION
13 # 1. INTRODUCTION
14 #
14 #
15 # This module provides coverage testing for Python code.
15 # This module provides coverage testing for Python code.
16 #
16 #
17 # The intended readership is all Python developers.
17 # The intended readership is all Python developers.
18 #
18 #
19 # This document is not confidential.
19 # This document is not confidential.
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 r"""Usage:
25 r"""Usage:
26
26
27 coverage.py -x [-p] 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. With the -p option, write to a temporary file containing
29 coverage data. With the -p option, write to a temporary file containing
30 the machine name and process ID.
30 the machine name and process ID.
31
31
32 coverage.py -e
32 coverage.py -e
33 Erase collected coverage data.
33 Erase collected coverage data.
34
34
35 coverage.py -c
35 coverage.py -c
36 Collect data from multiple coverage files (as created by -p option above)
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.
37 and store it into a single file representing the union of the coverage.
38
38
39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
39 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
40 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
41 option, show line numbers of the statements that weren't executed.
41 option, show line numbers of the statements that weren't executed.
42
42
43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
43 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
44 Make annotated copies of the given files, marking statements that
44 Make annotated copies of the given files, marking statements that
45 are executed with > and statements that are missed with !. With
45 are executed with > and statements that are missed with !. With
46 the -d option, make the copies in that directory. Without the -d
46 the -d option, make the copies in that directory. Without the -d
47 option, make each copy in the same directory as the original.
47 option, make each copy in the same directory as the original.
48
48
49 -o dir,dir2,...
49 -o dir,dir2,...
50 Omit reporting or annotating files when their filename path starts with
50 Omit reporting or annotating files when their filename path starts with
51 a directory listed in the omit list.
51 a directory listed in the omit list.
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.77.20070729" # 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
63 import re
63 import re
64 import string
64 import string
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 from socket import gethostname
70 from socket import gethostname
71
71
72 # Python version compatibility
72 # Python version compatibility
73 try:
73 try:
74 strclass = basestring # new to 2.3
74 strclass = basestring # new to 2.3
75 except:
75 except:
76 strclass = str
76 strclass = str
77
77
78 # 2. IMPLEMENTATION
78 # 2. IMPLEMENTATION
79 #
79 #
80 # This uses the "singleton" pattern.
80 # This uses the "singleton" pattern.
81 #
81 #
82 # The word "morf" means a module object (from which the source file can
82 # The word "morf" means a module object (from which the source file can
83 # be deduced by suitable manipulation of the __file__ attribute) or a
83 # be deduced by suitable manipulation of the __file__ attribute) or a
84 # filename.
84 # filename.
85 #
85 #
86 # When we generate a coverage report we have to canonicalize every
86 # When we generate a coverage report we have to canonicalize every
87 # filename in the coverage dictionary just in case it refers to the
87 # filename in the coverage dictionary just in case it refers to the
88 # module we are reporting on. It seems a shame to throw away this
88 # module we are reporting on. It seems a shame to throw away this
89 # information so the data in the coverage dictionary is transferred to
89 # information so the data in the coverage dictionary is transferred to
90 # the 'cexecuted' dictionary under the canonical filenames.
90 # the 'cexecuted' dictionary under the canonical filenames.
91 #
91 #
92 # The coverage dictionary is called "c" and the trace function "t". The
92 # The coverage dictionary is called "c" and the trace function "t". The
93 # reason for these short names is that Python looks up variables by name
93 # reason for these short names is that Python looks up variables by name
94 # 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!
95 # In the bottleneck of this application it's appropriate to abbreviate
95 # In the bottleneck of this application it's appropriate to abbreviate
96 # names to increase speed.
96 # names to increase speed.
97
97
98 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
98 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
99 """ A visitor for a parsed Abstract Syntax Tree which finds executable
99 """ A visitor for a parsed Abstract Syntax Tree which finds executable
100 statements.
100 statements.
101 """
101 """
102 def __init__(self, statements, excluded, suite_spots):
102 def __init__(self, statements, excluded, suite_spots):
103 compiler.visitor.ASTVisitor.__init__(self)
103 compiler.visitor.ASTVisitor.__init__(self)
104 self.statements = statements
104 self.statements = statements
105 self.excluded = excluded
105 self.excluded = excluded
106 self.suite_spots = suite_spots
106 self.suite_spots = suite_spots
107 self.excluding_suite = 0
107 self.excluding_suite = 0
108
108
109 def doRecursive(self, node):
109 def doRecursive(self, node):
110 for n in node.getChildNodes():
110 for n in node.getChildNodes():
111 self.dispatch(n)
111 self.dispatch(n)
112
112
113 visitStmt = visitModule = doRecursive
113 visitStmt = visitModule = doRecursive
114
114
115 def doCode(self, node):
115 def doCode(self, node):
116 if hasattr(node, 'decorators') and node.decorators:
116 if hasattr(node, 'decorators') and node.decorators:
117 self.dispatch(node.decorators)
117 self.dispatch(node.decorators)
118 self.recordAndDispatch(node.code)
118 self.recordAndDispatch(node.code)
119 else:
119 else:
120 self.doSuite(node, node.code)
120 self.doSuite(node, node.code)
121
121
122 visitFunction = visitClass = doCode
122 visitFunction = visitClass = doCode
123
123
124 def getFirstLine(self, node):
124 def getFirstLine(self, node):
125 # Find the first line in the tree node.
125 # Find the first line in the tree node.
126 lineno = node.lineno
126 lineno = node.lineno
127 for n in node.getChildNodes():
127 for n in node.getChildNodes():
128 f = self.getFirstLine(n)
128 f = self.getFirstLine(n)
129 if lineno and f:
129 if lineno and f:
130 lineno = min(lineno, f)
130 lineno = min(lineno, f)
131 else:
131 else:
132 lineno = lineno or f
132 lineno = lineno or f
133 return lineno
133 return lineno
134
134
135 def getLastLine(self, node):
135 def getLastLine(self, node):
136 # Find the first line in the tree node.
136 # Find the first line in the tree node.
137 lineno = node.lineno
137 lineno = node.lineno
138 for n in node.getChildNodes():
138 for n in node.getChildNodes():
139 lineno = max(lineno, self.getLastLine(n))
139 lineno = max(lineno, self.getLastLine(n))
140 return lineno
140 return lineno
141
141
142 def doStatement(self, node):
142 def doStatement(self, node):
143 self.recordLine(self.getFirstLine(node))
143 self.recordLine(self.getFirstLine(node))
144
144
145 visitAssert = visitAssign = visitAssTuple = visitPrint = \
145 visitAssert = visitAssign = visitAssTuple = visitPrint = \
146 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
146 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
147 doStatement
147 doStatement
148
148
149 def visitPass(self, node):
149 def visitPass(self, node):
150 # Pass statements have weird interactions with docstrings. If this
150 # Pass statements have weird interactions with docstrings. If this
151 # pass statement is part of one of those pairs, claim that the statement
151 # pass statement is part of one of those pairs, claim that the statement
152 # is on the later of the two lines.
152 # is on the later of the two lines.
153 l = node.lineno
153 l = node.lineno
154 if l:
154 if l:
155 lines = self.suite_spots.get(l, [l,l])
155 lines = self.suite_spots.get(l, [l,l])
156 self.statements[lines[1]] = 1
156 self.statements[lines[1]] = 1
157
157
158 def visitDiscard(self, node):
158 def visitDiscard(self, node):
159 # Discard nodes are statements that execute an expression, but then
159 # Discard nodes are statements that execute an expression, but then
160 # discard the results. This includes function calls, so we can't
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
161 # ignore them all. But if the expression is a constant, the statement
162 # won't be "executed", so don't count it now.
162 # won't be "executed", so don't count it now.
163 if node.expr.__class__.__name__ != 'Const':
163 if node.expr.__class__.__name__ != 'Const':
164 self.doStatement(node)
164 self.doStatement(node)
165
165
166 def recordNodeLine(self, node):
166 def recordNodeLine(self, node):
167 # Stmt nodes often have None, but shouldn't claim the first line of
167 # Stmt nodes often have None, but shouldn't claim the first line of
168 # their children (because the first child might be an ignorable line
168 # their children (because the first child might be an ignorable line
169 # like "global a").
169 # like "global a").
170 if node.__class__.__name__ != 'Stmt':
170 if node.__class__.__name__ != 'Stmt':
171 return self.recordLine(self.getFirstLine(node))
171 return self.recordLine(self.getFirstLine(node))
172 else:
172 else:
173 return 0
173 return 0
174
174
175 def recordLine(self, lineno):
175 def recordLine(self, lineno):
176 # Returns a bool, whether the line is included or excluded.
176 # Returns a bool, whether the line is included or excluded.
177 if lineno:
177 if lineno:
178 # Multi-line tests introducing suites have to get charged to their
178 # Multi-line tests introducing suites have to get charged to their
179 # keyword.
179 # keyword.
180 if lineno in self.suite_spots:
180 if lineno in self.suite_spots:
181 lineno = self.suite_spots[lineno][0]
181 lineno = self.suite_spots[lineno][0]
182 # If we're inside an excluded suite, record that this line was
182 # If we're inside an excluded suite, record that this line was
183 # excluded.
183 # excluded.
184 if self.excluding_suite:
184 if self.excluding_suite:
185 self.excluded[lineno] = 1
185 self.excluded[lineno] = 1
186 return 0
186 return 0
187 # 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
188 # another line that is exlcuded, then we're excluded.
188 # another line that is exlcuded, then we're excluded.
189 elif lineno in self.excluded or \
189 elif lineno in self.excluded or \
190 lineno in self.suite_spots and \
190 lineno in self.suite_spots and \
191 self.suite_spots[lineno][1] in self.excluded:
191 self.suite_spots[lineno][1] in self.excluded:
192 return 0
192 return 0
193 # Otherwise, this is an executable line.
193 # Otherwise, this is an executable line.
194 else:
194 else:
195 self.statements[lineno] = 1
195 self.statements[lineno] = 1
196 return 1
196 return 1
197 return 0
197 return 0
198
198
199 default = recordNodeLine
199 default = recordNodeLine
200
200
201 def recordAndDispatch(self, node):
201 def recordAndDispatch(self, node):
202 self.recordNodeLine(node)
202 self.recordNodeLine(node)
203 self.dispatch(node)
203 self.dispatch(node)
204
204
205 def doSuite(self, intro, body, exclude=0):
205 def doSuite(self, intro, body, exclude=0):
206 exsuite = self.excluding_suite
206 exsuite = self.excluding_suite
207 if exclude or (intro and not self.recordNodeLine(intro)):
207 if exclude or (intro and not self.recordNodeLine(intro)):
208 self.excluding_suite = 1
208 self.excluding_suite = 1
209 self.recordAndDispatch(body)
209 self.recordAndDispatch(body)
210 self.excluding_suite = exsuite
210 self.excluding_suite = exsuite
211
211
212 def doPlainWordSuite(self, prevsuite, suite):
212 def doPlainWordSuite(self, prevsuite, suite):
213 # 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
214 # present in the compiler parse tree. Look at the previous suite,
214 # 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
215 # and find its last line. If any line between there and the else's
216 # first line are excluded, then we exclude the else.
216 # first line are excluded, then we exclude the else.
217 lastprev = self.getLastLine(prevsuite)
217 lastprev = self.getLastLine(prevsuite)
218 firstelse = self.getFirstLine(suite)
218 firstelse = self.getFirstLine(suite)
219 for l in range(lastprev+1, firstelse):
219 for l in range(lastprev+1, firstelse):
220 if l in self.suite_spots:
220 if l in self.suite_spots:
221 self.doSuite(None, suite, exclude=l in self.excluded)
221 self.doSuite(None, suite, exclude=l in self.excluded)
222 break
222 break
223 else:
223 else:
224 self.doSuite(None, suite)
224 self.doSuite(None, suite)
225
225
226 def doElse(self, prevsuite, node):
226 def doElse(self, prevsuite, node):
227 if node.else_:
227 if node.else_:
228 self.doPlainWordSuite(prevsuite, node.else_)
228 self.doPlainWordSuite(prevsuite, node.else_)
229
229
230 def visitFor(self, node):
230 def visitFor(self, node):
231 self.doSuite(node, node.body)
231 self.doSuite(node, node.body)
232 self.doElse(node.body, node)
232 self.doElse(node.body, node)
233
233
234 visitWhile = visitFor
234 visitWhile = visitFor
235
235
236 def visitIf(self, node):
236 def visitIf(self, node):
237 # The first test has to be handled separately from the rest.
237 # The first test has to be handled separately from the rest.
238 # 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
239 # are credited to the line with the test for the elif.
239 # are credited to the line with the test for the elif.
240 self.doSuite(node, node.tests[0][1])
240 self.doSuite(node, node.tests[0][1])
241 for t, n in node.tests[1:]:
241 for t, n in node.tests[1:]:
242 self.doSuite(t, n)
242 self.doSuite(t, n)
243 self.doElse(node.tests[-1][1], node)
243 self.doElse(node.tests[-1][1], node)
244
244
245 def visitTryExcept(self, node):
245 def visitTryExcept(self, node):
246 self.doSuite(node, node.body)
246 self.doSuite(node, node.body)
247 for i in range(len(node.handlers)):
247 for i in range(len(node.handlers)):
248 a, b, h = node.handlers[i]
248 a, b, h = node.handlers[i]
249 if not a:
249 if not a:
250 # It's a plain "except:". Find the previous suite.
250 # It's a plain "except:". Find the previous suite.
251 if i > 0:
251 if i > 0:
252 prev = node.handlers[i-1][2]
252 prev = node.handlers[i-1][2]
253 else:
253 else:
254 prev = node.body
254 prev = node.body
255 self.doPlainWordSuite(prev, h)
255 self.doPlainWordSuite(prev, h)
256 else:
256 else:
257 self.doSuite(a, h)
257 self.doSuite(a, h)
258 self.doElse(node.handlers[-1][2], node)
258 self.doElse(node.handlers[-1][2], node)
259
259
260 def visitTryFinally(self, node):
260 def visitTryFinally(self, node):
261 self.doSuite(node, node.body)
261 self.doSuite(node, node.body)
262 self.doPlainWordSuite(node.body, node.final)
262 self.doPlainWordSuite(node.body, node.final)
263
263
264 def visitWith(self, node):
264 def visitWith(self, node):
265 self.doSuite(node, node.body)
265 self.doSuite(node, node.body)
266
266
267 def visitGlobal(self, node):
267 def visitGlobal(self, node):
268 # "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
269 # trace function), so don't record their line numbers.
269 # trace function), so don't record their line numbers.
270 pass
270 pass
271
271
272 the_coverage = None
272 the_coverage = None
273
273
274 class CoverageException(Exception): pass
274 class CoverageException(Exception): pass
275
275
276 class coverage:
276 class coverage:
277 # Name of the cache file (unless environment variable is set).
277 # Name of the cache file (unless environment variable is set).
278 cache_default = ".coverage"
278 cache_default = ".coverage"
279
279
280 # Environment variable naming the cache file.
280 # Environment variable naming the cache file.
281 cache_env = "COVERAGE_FILE"
281 cache_env = "COVERAGE_FILE"
282
282
283 # 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
284 # in that file) if that line has been executed.
284 # in that file) if that line has been executed.
285 c = {}
285 c = {}
286
286
287 # 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
288 # 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
289 # executed.
289 # executed.
290 cexecuted = {}
290 cexecuted = {}
291
291
292 # Cache of results of calling the analysis2() method, so that you can
292 # Cache of results of calling the analysis2() method, so that you can
293 # specify both -r and -a without doing double work.
293 # specify both -r and -a without doing double work.
294 analysis_cache = {}
294 analysis_cache = {}
295
295
296 # Cache of results of calling the canonical_filename() method, to
296 # Cache of results of calling the canonical_filename() method, to
297 # avoid duplicating work.
297 # avoid duplicating work.
298 canonical_filename_cache = {}
298 canonical_filename_cache = {}
299
299
300 def __init__(self):
300 def __init__(self):
301 global the_coverage
301 global the_coverage
302 if the_coverage:
302 if the_coverage:
303 raise CoverageException, "Only one coverage object allowed."
303 raise CoverageException, "Only one coverage object allowed."
304 self.usecache = 1
304 self.usecache = 1
305 self.cache = None
305 self.cache = None
306 self.parallel_mode = False
306 self.parallel_mode = False
307 self.exclude_re = ''
307 self.exclude_re = ''
308 self.nesting = 0
308 self.nesting = 0
309 self.cstack = []
309 self.cstack = []
310 self.xstack = []
310 self.xstack = []
311 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.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]')
312 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
313
313
314 # 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.
315 # 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
316 # the arguments and return value of the trace function.
316 # 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
317 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
318 # objects.
318 # objects.
319
319
320 def t(self, f, w, unused): #pragma: no cover
320 def t(self, f, w, unused): #pragma: no cover
321 if w == 'line':
321 if w == 'line':
322 #print "Executing %s @ %d" % (f.f_code.co_filename, f.f_lineno)
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
323 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
324 for c in self.cstack:
324 for c in self.cstack:
325 c[(f.f_code.co_filename, f.f_lineno)] = 1
325 c[(f.f_code.co_filename, f.f_lineno)] = 1
326 return self.t
326 return self.t
327
327
328 def help(self, error=None): #pragma: no cover
328 def help(self, error=None): #pragma: no cover
329 if error:
329 if error:
330 print error
330 print error
331 print
331 print
332 print __doc__
332 print __doc__
333 sys.exit(1)
333 sys.exit(1)
334
334
335 def command_line(self, argv, help_fn=None):
335 def command_line(self, argv, help_fn=None):
336 import getopt
336 import getopt
337 help_fn = help_fn or self.help
337 help_fn = help_fn or self.help
338 settings = {}
338 settings = {}
339 optmap = {
339 optmap = {
340 '-a': 'annotate',
340 '-a': 'annotate',
341 '-c': 'collect',
341 '-c': 'collect',
342 '-d:': 'directory=',
342 '-d:': 'directory=',
343 '-e': 'erase',
343 '-e': 'erase',
344 '-h': 'help',
344 '-h': 'help',
345 '-i': 'ignore-errors',
345 '-i': 'ignore-errors',
346 '-m': 'show-missing',
346 '-m': 'show-missing',
347 '-p': 'parallel-mode',
347 '-p': 'parallel-mode',
348 '-r': 'report',
348 '-r': 'report',
349 '-x': 'execute',
349 '-x': 'execute',
350 '-o:': 'omit=',
350 '-o:': 'omit=',
351 }
351 }
352 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
352 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
353 long_opts = optmap.values()
353 long_opts = optmap.values()
354 options, args = getopt.getopt(argv, short_opts, long_opts)
354 options, args = getopt.getopt(argv, short_opts, long_opts)
355 for o, a in options:
355 for o, a in options:
356 if o in optmap:
356 if o in optmap:
357 settings[optmap[o]] = 1
357 settings[optmap[o]] = 1
358 elif o + ':' in optmap:
358 elif o + ':' in optmap:
359 settings[optmap[o + ':']] = a
359 settings[optmap[o + ':']] = a
360 elif o[2:] in long_opts:
360 elif o[2:] in long_opts:
361 settings[o[2:]] = 1
361 settings[o[2:]] = 1
362 elif o[2:] + '=' in long_opts:
362 elif o[2:] + '=' in long_opts:
363 settings[o[2:]+'='] = a
363 settings[o[2:]+'='] = a
364 else: #pragma: no cover
364 else: #pragma: no cover
365 pass # Can't get here, because getopt won't return anything unknown.
365 pass # Can't get here, because getopt won't return anything unknown.
366
366
367 if settings.get('help'):
367 if settings.get('help'):
368 help_fn()
368 help_fn()
369
369
370 for i in ['erase', 'execute']:
370 for i in ['erase', 'execute']:
371 for j in ['annotate', 'report', 'collect']:
371 for j in ['annotate', 'report', 'collect']:
372 if settings.get(i) and settings.get(j):
372 if settings.get(i) and settings.get(j):
373 help_fn("You can't specify the '%s' and '%s' "
373 help_fn("You can't specify the '%s' and '%s' "
374 "options at the same time." % (i, j))
374 "options at the same time." % (i, j))
375
375
376 args_needed = (settings.get('execute')
376 args_needed = (settings.get('execute')
377 or settings.get('annotate')
377 or settings.get('annotate')
378 or settings.get('report'))
378 or settings.get('report'))
379 action = (settings.get('erase')
379 action = (settings.get('erase')
380 or settings.get('collect')
380 or settings.get('collect')
381 or args_needed)
381 or args_needed)
382 if not action:
382 if not action:
383 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
383 help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
384 if not args_needed and args:
384 if not args_needed and args:
385 help_fn("Unexpected arguments: %s" % " ".join(args))
385 help_fn("Unexpected arguments: %s" % " ".join(args))
386
386
387 self.parallel_mode = settings.get('parallel-mode')
387 self.parallel_mode = settings.get('parallel-mode')
388 self.get_ready()
388 self.get_ready()
389
389
390 if settings.get('erase'):
390 if settings.get('erase'):
391 self.erase()
391 self.erase()
392 if settings.get('execute'):
392 if settings.get('execute'):
393 if not args:
393 if not args:
394 help_fn("Nothing to do.")
394 help_fn("Nothing to do.")
395 sys.argv = args
395 sys.argv = args
396 self.start()
396 self.start()
397 import __main__
397 import __main__
398 sys.path[0] = os.path.dirname(sys.argv[0])
398 sys.path[0] = os.path.dirname(sys.argv[0])
399 execfile(sys.argv[0], __main__.__dict__)
399 execfile(sys.argv[0], __main__.__dict__)
400 if settings.get('collect'):
400 if settings.get('collect'):
401 self.collect()
401 self.collect()
402 if not args:
402 if not args:
403 args = self.cexecuted.keys()
403 args = self.cexecuted.keys()
404
404
405 ignore_errors = settings.get('ignore-errors')
405 ignore_errors = settings.get('ignore-errors')
406 show_missing = settings.get('show-missing')
406 show_missing = settings.get('show-missing')
407 directory = settings.get('directory=')
407 directory = settings.get('directory=')
408
408
409 omit = settings.get('omit=')
409 omit = settings.get('omit=')
410 if omit is not None:
410 if omit is not None:
411 omit = omit.split(',')
411 omit = omit.split(',')
412 else:
412 else:
413 omit = []
413 omit = []
414
414
415 omit = [os.path.normcase(os.path.abspath(os.path.realpath(p)))
416 for p in omit]
417
415 if settings.get('report'):
418 if settings.get('report'):
416 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
419 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
417 if settings.get('annotate'):
420 if settings.get('annotate'):
418 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
421 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
419
422
420 def use_cache(self, usecache, cache_file=None):
423 def use_cache(self, usecache, cache_file=None):
421 self.usecache = usecache
424 self.usecache = usecache
422 if cache_file and not self.cache:
425 if cache_file and not self.cache:
423 self.cache_default = cache_file
426 self.cache_default = cache_file
424
427
425 def get_ready(self, parallel_mode=False):
428 def get_ready(self, parallel_mode=False):
426 if self.usecache and not self.cache:
429 if self.usecache and not self.cache:
427 self.cache = os.environ.get(self.cache_env, self.cache_default)
430 self.cache = os.environ.get(self.cache_env, self.cache_default)
428 if self.parallel_mode:
431 if self.parallel_mode:
429 self.cache += "." + gethostname() + "." + str(os.getpid())
432 self.cache += "." + gethostname() + "." + str(os.getpid())
430 self.restore()
433 self.restore()
431 self.analysis_cache = {}
434 self.analysis_cache = {}
432
435
433 def start(self, parallel_mode=False):
436 def start(self, parallel_mode=False):
434 self.get_ready()
437 self.get_ready()
435 if self.nesting == 0: #pragma: no cover
438 if self.nesting == 0: #pragma: no cover
436 sys.settrace(self.t)
439 sys.settrace(self.t)
437 if hasattr(threading, 'settrace'):
440 if hasattr(threading, 'settrace'):
438 threading.settrace(self.t)
441 threading.settrace(self.t)
439 self.nesting += 1
442 self.nesting += 1
440
443
441 def stop(self):
444 def stop(self):
442 self.nesting -= 1
445 self.nesting -= 1
443 if self.nesting == 0: #pragma: no cover
446 if self.nesting == 0: #pragma: no cover
444 sys.settrace(None)
447 sys.settrace(None)
445 if hasattr(threading, 'settrace'):
448 if hasattr(threading, 'settrace'):
446 threading.settrace(None)
449 threading.settrace(None)
447
450
448 def erase(self):
451 def erase(self):
449 self.get_ready()
452 self.get_ready()
450 self.c = {}
453 self.c = {}
451 self.analysis_cache = {}
454 self.analysis_cache = {}
452 self.cexecuted = {}
455 self.cexecuted = {}
453 if self.cache and os.path.exists(self.cache):
456 if self.cache and os.path.exists(self.cache):
454 os.remove(self.cache)
457 os.remove(self.cache)
455
458
456 def exclude(self, re):
459 def exclude(self, re):
457 if self.exclude_re:
460 if self.exclude_re:
458 self.exclude_re += "|"
461 self.exclude_re += "|"
459 self.exclude_re += "(" + re + ")"
462 self.exclude_re += "(" + re + ")"
460
463
461 def begin_recursive(self):
464 def begin_recursive(self):
462 self.cstack.append(self.c)
465 self.cstack.append(self.c)
463 self.xstack.append(self.exclude_re)
466 self.xstack.append(self.exclude_re)
464
467
465 def end_recursive(self):
468 def end_recursive(self):
466 self.c = self.cstack.pop()
469 self.c = self.cstack.pop()
467 self.exclude_re = self.xstack.pop()
470 self.exclude_re = self.xstack.pop()
468
471
469 # save(). Save coverage data to the coverage cache.
472 # save(). Save coverage data to the coverage cache.
470
473
471 def save(self):
474 def save(self):
472 if self.usecache and self.cache:
475 if self.usecache and self.cache:
473 self.canonicalize_filenames()
476 self.canonicalize_filenames()
474 cache = open(self.cache, 'wb')
477 cache = open(self.cache, 'wb')
475 import marshal
478 import marshal
476 marshal.dump(self.cexecuted, cache)
479 marshal.dump(self.cexecuted, cache)
477 cache.close()
480 cache.close()
478
481
479 # restore(). Restore coverage data from the coverage cache (if it exists).
482 # restore(). Restore coverage data from the coverage cache (if it exists).
480
483
481 def restore(self):
484 def restore(self):
482 self.c = {}
485 self.c = {}
483 self.cexecuted = {}
486 self.cexecuted = {}
484 assert self.usecache
487 assert self.usecache
485 if os.path.exists(self.cache):
488 if os.path.exists(self.cache):
486 self.cexecuted = self.restore_file(self.cache)
489 self.cexecuted = self.restore_file(self.cache)
487
490
488 def restore_file(self, file_name):
491 def restore_file(self, file_name):
489 try:
492 try:
490 cache = open(file_name, 'rb')
493 cache = open(file_name, 'rb')
491 import marshal
494 import marshal
492 cexecuted = marshal.load(cache)
495 cexecuted = marshal.load(cache)
493 cache.close()
496 cache.close()
494 if isinstance(cexecuted, types.DictType):
497 if isinstance(cexecuted, types.DictType):
495 return cexecuted
498 return cexecuted
496 else:
499 else:
497 return {}
500 return {}
498 except:
501 except:
499 return {}
502 return {}
500
503
501 # collect(). Collect data in multiple files produced by parallel mode
504 # collect(). Collect data in multiple files produced by parallel mode
502
505
503 def collect(self):
506 def collect(self):
504 cache_dir, local = os.path.split(self.cache)
507 cache_dir, local = os.path.split(self.cache)
505 for f in os.listdir(cache_dir or '.'):
508 for f in os.listdir(cache_dir or '.'):
506 if not f.startswith(local):
509 if not f.startswith(local):
507 continue
510 continue
508
511
509 full_path = os.path.join(cache_dir, f)
512 full_path = os.path.join(cache_dir, f)
510 cexecuted = self.restore_file(full_path)
513 cexecuted = self.restore_file(full_path)
511 self.merge_data(cexecuted)
514 self.merge_data(cexecuted)
512
515
513 def merge_data(self, new_data):
516 def merge_data(self, new_data):
514 for file_name, file_data in new_data.items():
517 for file_name, file_data in new_data.items():
515 if file_name in self.cexecuted:
518 if file_name in self.cexecuted:
516 self.merge_file_data(self.cexecuted[file_name], file_data)
519 self.merge_file_data(self.cexecuted[file_name], file_data)
517 else:
520 else:
518 self.cexecuted[file_name] = file_data
521 self.cexecuted[file_name] = file_data
519
522
520 def merge_file_data(self, cache_data, new_data):
523 def merge_file_data(self, cache_data, new_data):
521 for line_number in new_data.keys():
524 for line_number in new_data.keys():
522 if not line_number in cache_data:
525 if not line_number in cache_data:
523 cache_data[line_number] = new_data[line_number]
526 cache_data[line_number] = new_data[line_number]
524
527
525 # canonical_filename(filename). Return a canonical filename for the
528 # canonical_filename(filename). Return a canonical filename for the
526 # file (that is, an absolute path with no redundant components and
529 # file (that is, an absolute path with no redundant components and
527 # normalized case). See [GDR 2001-12-04b, 3.3].
530 # normalized case). See [GDR 2001-12-04b, 3.3].
528
531
529 def canonical_filename(self, filename):
532 def canonical_filename(self, filename):
530 if not filename in self.canonical_filename_cache:
533 if not filename in self.canonical_filename_cache:
531 f = filename
534 f = filename
532 if os.path.isabs(f) and not os.path.exists(f):
535 if os.path.isabs(f) and not os.path.exists(f):
533 f = os.path.basename(f)
536 f = os.path.basename(f)
534 if not os.path.isabs(f):
537 if not os.path.isabs(f):
535 for path in [os.curdir] + sys.path:
538 for path in [os.curdir] + sys.path:
536 g = os.path.join(path, f)
539 g = os.path.join(path, f)
537 if os.path.exists(g):
540 if os.path.exists(g):
538 f = g
541 f = g
539 break
542 break
540 cf = os.path.normcase(os.path.abspath(f))
543 cf = os.path.normcase(os.path.abspath(os.path.realpath(f)))
541 self.canonical_filename_cache[filename] = cf
544 self.canonical_filename_cache[filename] = cf
542 return self.canonical_filename_cache[filename]
545 return self.canonical_filename_cache[filename]
543
546
544 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
547 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
545 # canonicalizing filenames on the way. Clear the "c" map.
548 # canonicalizing filenames on the way. Clear the "c" map.
546
549
547 def canonicalize_filenames(self):
550 def canonicalize_filenames(self):
548 for filename, lineno in self.c.keys():
551 for filename, lineno in self.c.keys():
549 if filename == '<string>':
552 if filename == '<string>':
550 # Can't do anything useful with exec'd strings, so skip them.
553 # Can't do anything useful with exec'd strings, so skip them.
551 continue
554 continue
552 f = self.canonical_filename(filename)
555 f = self.canonical_filename(filename)
553 if not f in self.cexecuted:
556 if not f in self.cexecuted:
554 self.cexecuted[f] = {}
557 self.cexecuted[f] = {}
555 self.cexecuted[f][lineno] = 1
558 self.cexecuted[f][lineno] = 1
556 self.c = {}
559 self.c = {}
557
560
558 # morf_filename(morf). Return the filename for a module or file.
561 # morf_filename(morf). Return the filename for a module or file.
559
562
560 def morf_filename(self, morf):
563 def morf_filename(self, morf):
561 if isinstance(morf, types.ModuleType):
564 if isinstance(morf, types.ModuleType):
562 if not hasattr(morf, '__file__'):
565 if not hasattr(morf, '__file__'):
563 raise CoverageException, "Module has no __file__ attribute."
566 raise CoverageException, "Module has no __file__ attribute."
564 f = morf.__file__
567 f = morf.__file__
565 else:
568 else:
566 f = morf
569 f = morf
567 return self.canonical_filename(f)
570 return self.canonical_filename(f)
568
571
569 # analyze_morf(morf). Analyze the module or filename passed as
572 # analyze_morf(morf). Analyze the module or filename passed as
570 # the argument. If the source code can't be found, raise an error.
573 # the argument. If the source code can't be found, raise an error.
571 # Otherwise, return a tuple of (1) the canonical filename of the
574 # Otherwise, return a tuple of (1) the canonical filename of the
572 # source code for the module, (2) a list of lines of statements
575 # source code for the module, (2) a list of lines of statements
573 # in the source code, (3) a list of lines of excluded statements,
576 # in the source code, (3) a list of lines of excluded statements,
574 # and (4), a map of line numbers to multi-line line number ranges, for
577 # and (4), a map of line numbers to multi-line line number ranges, for
575 # statements that cross lines.
578 # statements that cross lines.
576
579
577 def analyze_morf(self, morf):
580 def analyze_morf(self, morf):
578 if morf in self.analysis_cache:
581 if morf in self.analysis_cache:
579 return self.analysis_cache[morf]
582 return self.analysis_cache[morf]
580 filename = self.morf_filename(morf)
583 filename = self.morf_filename(morf)
581 ext = os.path.splitext(filename)[1]
584 ext = os.path.splitext(filename)[1]
582 if ext == '.pyc':
585 if ext == '.pyc':
583 if not os.path.exists(filename[0:-1]):
586 if not os.path.exists(filename[0:-1]):
584 raise CoverageException, ("No source for compiled code '%s'."
587 raise CoverageException, ("No source for compiled code '%s'."
585 % filename)
588 % filename)
586 filename = filename[0:-1]
589 filename = filename[0:-1]
587 elif ext != '.py':
590 elif ext != '.py':
588 raise CoverageException, "File '%s' not Python source." % filename
591 raise CoverageException, "File '%s' not Python source." % filename
589 source = open(filename, 'r')
592 source = open(filename, 'r')
590 lines, excluded_lines, line_map = self.find_executable_statements(
593 lines, excluded_lines, line_map = self.find_executable_statements(
591 source.read(), exclude=self.exclude_re
594 source.read(), exclude=self.exclude_re
592 )
595 )
593 source.close()
596 source.close()
594 result = filename, lines, excluded_lines, line_map
597 result = filename, lines, excluded_lines, line_map
595 self.analysis_cache[morf] = result
598 self.analysis_cache[morf] = result
596 return result
599 return result
597
600
598 def first_line_of_tree(self, tree):
601 def first_line_of_tree(self, tree):
599 while True:
602 while True:
600 if len(tree) == 3 and type(tree[2]) == type(1):
603 if len(tree) == 3 and type(tree[2]) == type(1):
601 return tree[2]
604 return tree[2]
602 tree = tree[1]
605 tree = tree[1]
603
606
604 def last_line_of_tree(self, tree):
607 def last_line_of_tree(self, tree):
605 while True:
608 while True:
606 if len(tree) == 3 and type(tree[2]) == type(1):
609 if len(tree) == 3 and type(tree[2]) == type(1):
607 return tree[2]
610 return tree[2]
608 tree = tree[-1]
611 tree = tree[-1]
609
612
610 def find_docstring_pass_pair(self, tree, spots):
613 def find_docstring_pass_pair(self, tree, spots):
611 for i in range(1, len(tree)):
614 for i in range(1, len(tree)):
612 if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
615 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])
616 first_line = self.first_line_of_tree(tree[i])
614 last_line = self.last_line_of_tree(tree[i+1])
617 last_line = self.last_line_of_tree(tree[i+1])
615 self.record_multiline(spots, first_line, last_line)
618 self.record_multiline(spots, first_line, last_line)
616
619
617 def is_string_constant(self, tree):
620 def is_string_constant(self, tree):
618 try:
621 try:
619 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
622 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
620 except:
623 except:
621 return False
624 return False
622
625
623 def is_pass_stmt(self, tree):
626 def is_pass_stmt(self, tree):
624 try:
627 try:
625 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
628 return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
626 except:
629 except:
627 return False
630 return False
628
631
629 def record_multiline(self, spots, i, j):
632 def record_multiline(self, spots, i, j):
630 for l in range(i, j+1):
633 for l in range(i, j+1):
631 spots[l] = (i, j)
634 spots[l] = (i, j)
632
635
633 def get_suite_spots(self, tree, spots):
636 def get_suite_spots(self, tree, spots):
634 """ Analyze a parse tree to find suite introducers which span a number
637 """ Analyze a parse tree to find suite introducers which span a number
635 of lines.
638 of lines.
636 """
639 """
637 for i in range(1, len(tree)):
640 for i in range(1, len(tree)):
638 if type(tree[i]) == type(()):
641 if type(tree[i]) == type(()):
639 if tree[i][0] == symbol.suite:
642 if tree[i][0] == symbol.suite:
640 # Found a suite, look back for the colon and keyword.
643 # Found a suite, look back for the colon and keyword.
641 lineno_colon = lineno_word = None
644 lineno_colon = lineno_word = None
642 for j in range(i-1, 0, -1):
645 for j in range(i-1, 0, -1):
643 if tree[j][0] == token.COLON:
646 if tree[j][0] == token.COLON:
644 # Colons are never executed themselves: we want the
647 # Colons are never executed themselves: we want the
645 # line number of the last token before the colon.
648 # line number of the last token before the colon.
646 lineno_colon = self.last_line_of_tree(tree[j-1])
649 lineno_colon = self.last_line_of_tree(tree[j-1])
647 elif tree[j][0] == token.NAME:
650 elif tree[j][0] == token.NAME:
648 if tree[j][1] == 'elif':
651 if tree[j][1] == 'elif':
649 # Find the line number of the first non-terminal
652 # Find the line number of the first non-terminal
650 # after the keyword.
653 # after the keyword.
651 t = tree[j+1]
654 t = tree[j+1]
652 while t and token.ISNONTERMINAL(t[0]):
655 while t and token.ISNONTERMINAL(t[0]):
653 t = t[1]
656 t = t[1]
654 if t:
657 if t:
655 lineno_word = t[2]
658 lineno_word = t[2]
656 else:
659 else:
657 lineno_word = tree[j][2]
660 lineno_word = tree[j][2]
658 break
661 break
659 elif tree[j][0] == symbol.except_clause:
662 elif tree[j][0] == symbol.except_clause:
660 # "except" clauses look like:
663 # "except" clauses look like:
661 # ('except_clause', ('NAME', 'except', lineno), ...)
664 # ('except_clause', ('NAME', 'except', lineno), ...)
662 if tree[j][1][0] == token.NAME:
665 if tree[j][1][0] == token.NAME:
663 lineno_word = tree[j][1][2]
666 lineno_word = tree[j][1][2]
664 break
667 break
665 if lineno_colon and lineno_word:
668 if lineno_colon and lineno_word:
666 # Found colon and keyword, mark all the lines
669 # Found colon and keyword, mark all the lines
667 # between the two with the two line numbers.
670 # between the two with the two line numbers.
668 self.record_multiline(spots, lineno_word, lineno_colon)
671 self.record_multiline(spots, lineno_word, lineno_colon)
669
672
670 # "pass" statements are tricky: different versions of Python
673 # "pass" statements are tricky: different versions of Python
671 # treat them differently, especially in the common case of a
674 # treat them differently, especially in the common case of a
672 # function with a doc string and a single pass statement.
675 # function with a doc string and a single pass statement.
673 self.find_docstring_pass_pair(tree[i], spots)
676 self.find_docstring_pass_pair(tree[i], spots)
674
677
675 elif tree[i][0] == symbol.simple_stmt:
678 elif tree[i][0] == symbol.simple_stmt:
676 first_line = self.first_line_of_tree(tree[i])
679 first_line = self.first_line_of_tree(tree[i])
677 last_line = self.last_line_of_tree(tree[i])
680 last_line = self.last_line_of_tree(tree[i])
678 if first_line != last_line:
681 if first_line != last_line:
679 self.record_multiline(spots, first_line, last_line)
682 self.record_multiline(spots, first_line, last_line)
680 self.get_suite_spots(tree[i], spots)
683 self.get_suite_spots(tree[i], spots)
681
684
682 def find_executable_statements(self, text, exclude=None):
685 def find_executable_statements(self, text, exclude=None):
683 # Find lines which match an exclusion pattern.
686 # Find lines which match an exclusion pattern.
684 excluded = {}
687 excluded = {}
685 suite_spots = {}
688 suite_spots = {}
686 if exclude:
689 if exclude:
687 reExclude = re.compile(exclude)
690 reExclude = re.compile(exclude)
688 lines = text.split('\n')
691 lines = text.split('\n')
689 for i in range(len(lines)):
692 for i in range(len(lines)):
690 if reExclude.search(lines[i]):
693 if reExclude.search(lines[i]):
691 excluded[i+1] = 1
694 excluded[i+1] = 1
692
695
693 # Parse the code and analyze the parse tree to find out which statements
696 # Parse the code and analyze the parse tree to find out which statements
694 # are multiline, and where suites begin and end.
697 # are multiline, and where suites begin and end.
695 import parser
698 import parser
696 tree = parser.suite(text+'\n\n').totuple(1)
699 tree = parser.suite(text+'\n\n').totuple(1)
697 self.get_suite_spots(tree, suite_spots)
700 self.get_suite_spots(tree, suite_spots)
698 #print "Suite spots:", suite_spots
701 #print "Suite spots:", suite_spots
699
702
700 # Use the compiler module to parse the text and find the executable
703 # Use the compiler module to parse the text and find the executable
701 # statements. We add newlines to be impervious to final partial lines.
704 # statements. We add newlines to be impervious to final partial lines.
702 statements = {}
705 statements = {}
703 ast = compiler.parse(text+'\n\n')
706 ast = compiler.parse(text+'\n\n')
704 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
707 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
705 compiler.walk(ast, visitor, walker=visitor)
708 compiler.walk(ast, visitor, walker=visitor)
706
709
707 lines = statements.keys()
710 lines = statements.keys()
708 lines.sort()
711 lines.sort()
709 excluded_lines = excluded.keys()
712 excluded_lines = excluded.keys()
710 excluded_lines.sort()
713 excluded_lines.sort()
711 return lines, excluded_lines, suite_spots
714 return lines, excluded_lines, suite_spots
712
715
713 # format_lines(statements, lines). Format a list of line numbers
716 # format_lines(statements, lines). Format a list of line numbers
714 # for printing by coalescing groups of lines as long as the lines
717 # for printing by coalescing groups of lines as long as the lines
715 # represent consecutive statements. This will coalesce even if
718 # represent consecutive statements. This will coalesce even if
716 # there are gaps between statements, so if statements =
719 # there are gaps between statements, so if statements =
717 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
720 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
718 # format_lines will return "1-2, 5-11, 13-14".
721 # format_lines will return "1-2, 5-11, 13-14".
719
722
720 def format_lines(self, statements, lines):
723 def format_lines(self, statements, lines):
721 pairs = []
724 pairs = []
722 i = 0
725 i = 0
723 j = 0
726 j = 0
724 start = None
727 start = None
725 pairs = []
728 pairs = []
726 while i < len(statements) and j < len(lines):
729 while i < len(statements) and j < len(lines):
727 if statements[i] == lines[j]:
730 if statements[i] == lines[j]:
728 if start == None:
731 if start == None:
729 start = lines[j]
732 start = lines[j]
730 end = lines[j]
733 end = lines[j]
731 j = j + 1
734 j = j + 1
732 elif start:
735 elif start:
733 pairs.append((start, end))
736 pairs.append((start, end))
734 start = None
737 start = None
735 i = i + 1
738 i = i + 1
736 if start:
739 if start:
737 pairs.append((start, end))
740 pairs.append((start, end))
738 def stringify(pair):
741 def stringify(pair):
739 start, end = pair
742 start, end = pair
740 if start == end:
743 if start == end:
741 return "%d" % start
744 return "%d" % start
742 else:
745 else:
743 return "%d-%d" % (start, end)
746 return "%d-%d" % (start, end)
744 ret = string.join(map(stringify, pairs), ", ")
747 ret = string.join(map(stringify, pairs), ", ")
745 return ret
748 return ret
746
749
747 # Backward compatibility with version 1.
750 # Backward compatibility with version 1.
748 def analysis(self, morf):
751 def analysis(self, morf):
749 f, s, _, m, mf = self.analysis2(morf)
752 f, s, _, m, mf = self.analysis2(morf)
750 return f, s, m, mf
753 return f, s, m, mf
751
754
752 def analysis2(self, morf):
755 def analysis2(self, morf):
753 filename, statements, excluded, line_map = self.analyze_morf(morf)
756 filename, statements, excluded, line_map = self.analyze_morf(morf)
754 self.canonicalize_filenames()
757 self.canonicalize_filenames()
755 if not filename in self.cexecuted:
758 if not filename in self.cexecuted:
756 self.cexecuted[filename] = {}
759 self.cexecuted[filename] = {}
757 missing = []
760 missing = []
758 for line in statements:
761 for line in statements:
759 lines = line_map.get(line, [line, line])
762 lines = line_map.get(line, [line, line])
760 for l in range(lines[0], lines[1]+1):
763 for l in range(lines[0], lines[1]+1):
761 if l in self.cexecuted[filename]:
764 if l in self.cexecuted[filename]:
762 break
765 break
763 else:
766 else:
764 missing.append(line)
767 missing.append(line)
765 return (filename, statements, excluded, missing,
768 return (filename, statements, excluded, missing,
766 self.format_lines(statements, missing))
769 self.format_lines(statements, missing))
767
770
768 def relative_filename(self, filename):
771 def relative_filename(self, filename):
769 """ Convert filename to relative filename from self.relative_dir.
772 """ Convert filename to relative filename from self.relative_dir.
770 """
773 """
771 return filename.replace(self.relative_dir, "")
774 return filename.replace(self.relative_dir, "")
772
775
773 def morf_name(self, morf):
776 def morf_name(self, morf):
774 """ Return the name of morf as used in report.
777 """ Return the name of morf as used in report.
775 """
778 """
776 if isinstance(morf, types.ModuleType):
779 if isinstance(morf, types.ModuleType):
777 return morf.__name__
780 return morf.__name__
778 else:
781 else:
779 return self.relative_filename(os.path.splitext(morf)[0])
782 return self.relative_filename(os.path.splitext(morf)[0])
780
783
781 def filter_by_prefix(self, morfs, omit_prefixes):
784 def filter_by_prefix(self, morfs, omit_prefixes):
782 """ Return list of morfs where the morf name does not begin
785 """ Return list of morfs where the morf name does not begin
783 with any one of the omit_prefixes.
786 with any one of the omit_prefixes.
784 """
787 """
785 filtered_morfs = []
788 filtered_morfs = []
786 for morf in morfs:
789 for morf in morfs:
787 for prefix in omit_prefixes:
790 for prefix in omit_prefixes:
788 if self.morf_name(morf).startswith(prefix):
791 if self.morf_name(morf).startswith(prefix):
789 break
792 break
790 else:
793 else:
791 filtered_morfs.append(morf)
794 filtered_morfs.append(morf)
792
795
793 return filtered_morfs
796 return filtered_morfs
794
797
795 def morf_name_compare(self, x, y):
798 def morf_name_compare(self, x, y):
796 return cmp(self.morf_name(x), self.morf_name(y))
799 return cmp(self.morf_name(x), self.morf_name(y))
797
800
798 def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
801 def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
799 if not isinstance(morfs, types.ListType):
802 if not isinstance(morfs, types.ListType):
800 morfs = [morfs]
803 morfs = [morfs]
801 # On windows, the shell doesn't expand wildcards. Do it here.
804 # On windows, the shell doesn't expand wildcards. Do it here.
802 globbed = []
805 globbed = []
803 for morf in morfs:
806 for morf in morfs:
804 if isinstance(morf, strclass):
807 if isinstance(morf, strclass):
805 globbed.extend(glob.glob(morf))
808 globbed.extend(glob.glob(morf))
806 else:
809 else:
807 globbed.append(morf)
810 globbed.append(morf)
808 morfs = globbed
811 morfs = globbed
809
812
810 morfs = self.filter_by_prefix(morfs, omit_prefixes)
813 morfs = self.filter_by_prefix(morfs, omit_prefixes)
811 morfs.sort(self.morf_name_compare)
814 morfs.sort(self.morf_name_compare)
812
815
813 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
816 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
814 fmt_name = "%%- %ds " % max_name
817 fmt_name = "%%- %ds " % max_name
815 fmt_err = fmt_name + "%s: %s"
818 fmt_err = fmt_name + "%s: %s"
816 header = fmt_name % "Name" + " Stmts Exec Cover"
819 header = fmt_name % "Name" + " Stmts Exec Cover"
817 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
820 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
818 if show_missing:
821 if show_missing:
819 header = header + " Missing"
822 header = header + " Missing"
820 fmt_coverage = fmt_coverage + " %s"
823 fmt_coverage = fmt_coverage + " %s"
821 if not file:
824 if not file:
822 file = sys.stdout
825 file = sys.stdout
823 print >>file, header
826 print >>file, header
824 print >>file, "-" * len(header)
827 print >>file, "-" * len(header)
825 total_statements = 0
828 total_statements = 0
826 total_executed = 0
829 total_executed = 0
827 for morf in morfs:
830 for morf in morfs:
828 name = self.morf_name(morf)
831 name = self.morf_name(morf)
829 try:
832 try:
830 _, statements, _, missing, readable = self.analysis2(morf)
833 _, statements, _, missing, readable = self.analysis2(morf)
831 n = len(statements)
834 n = len(statements)
832 m = n - len(missing)
835 m = n - len(missing)
833 if n > 0:
836 if n > 0:
834 pc = 100.0 * m / n
837 pc = 100.0 * m / n
835 else:
838 else:
836 pc = 100.0
839 pc = 100.0
837 args = (name, n, m, pc)
840 args = (name, n, m, pc)
838 if show_missing:
841 if show_missing:
839 args = args + (readable,)
842 args = args + (readable,)
840 print >>file, fmt_coverage % args
843 print >>file, fmt_coverage % args
841 total_statements = total_statements + n
844 total_statements = total_statements + n
842 total_executed = total_executed + m
845 total_executed = total_executed + m
843 except KeyboardInterrupt: #pragma: no cover
846 except KeyboardInterrupt: #pragma: no cover
844 raise
847 raise
845 except:
848 except:
846 if not ignore_errors:
849 if not ignore_errors:
847 typ, msg = sys.exc_info()[0:2]
850 typ, msg = sys.exc_info()[0:2]
848 print >>file, fmt_err % (name, typ, msg)
851 print >>file, fmt_err % (name, typ, msg)
849 if len(morfs) > 1:
852 if len(morfs) > 1:
850 print >>file, "-" * len(header)
853 print >>file, "-" * len(header)
851 if total_statements > 0:
854 if total_statements > 0:
852 pc = 100.0 * total_executed / total_statements
855 pc = 100.0 * total_executed / total_statements
853 else:
856 else:
854 pc = 100.0
857 pc = 100.0
855 args = ("TOTAL", total_statements, total_executed, pc)
858 args = ("TOTAL", total_statements, total_executed, pc)
856 if show_missing:
859 if show_missing:
857 args = args + ("",)
860 args = args + ("",)
858 print >>file, fmt_coverage % args
861 print >>file, fmt_coverage % args
859
862
860 # annotate(morfs, ignore_errors).
863 # annotate(morfs, ignore_errors).
861
864
862 blank_re = re.compile(r"\s*(#|$)")
865 blank_re = re.compile(r"\s*(#|$)")
863 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
866 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
864
867
865 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
868 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
866 morfs = self.filter_by_prefix(morfs, omit_prefixes)
869 morfs = self.filter_by_prefix(morfs, omit_prefixes)
867 for morf in morfs:
870 for morf in morfs:
868 try:
871 try:
869 filename, statements, excluded, missing, _ = self.analysis2(morf)
872 filename, statements, excluded, missing, _ = self.analysis2(morf)
870 self.annotate_file(filename, statements, excluded, missing, directory)
873 self.annotate_file(filename, statements, excluded, missing, directory)
871 except KeyboardInterrupt:
874 except KeyboardInterrupt:
872 raise
875 raise
873 except:
876 except:
874 if not ignore_errors:
877 if not ignore_errors:
875 raise
878 raise
876
879
877 def annotate_file(self, filename, statements, excluded, missing, directory=None):
880 def annotate_file(self, filename, statements, excluded, missing, directory=None):
878 source = open(filename, 'r')
881 source = open(filename, 'r')
879 if directory:
882 if directory:
880 dest_file = os.path.join(directory,
883 dest_file = os.path.join(directory,
881 os.path.basename(filename)
884 os.path.basename(filename)
882 + ',cover')
885 + ',cover')
883 else:
886 else:
884 dest_file = filename + ',cover'
887 dest_file = filename + ',cover'
885 dest = open(dest_file, 'w')
888 dest = open(dest_file, 'w')
886 lineno = 0
889 lineno = 0
887 i = 0
890 i = 0
888 j = 0
891 j = 0
889 covered = 1
892 covered = 1
890 while 1:
893 while 1:
891 line = source.readline()
894 line = source.readline()
892 if line == '':
895 if line == '':
893 break
896 break
894 lineno = lineno + 1
897 lineno = lineno + 1
895 while i < len(statements) and statements[i] < lineno:
898 while i < len(statements) and statements[i] < lineno:
896 i = i + 1
899 i = i + 1
897 while j < len(missing) and missing[j] < lineno:
900 while j < len(missing) and missing[j] < lineno:
898 j = j + 1
901 j = j + 1
899 if i < len(statements) and statements[i] == lineno:
902 if i < len(statements) and statements[i] == lineno:
900 covered = j >= len(missing) or missing[j] > lineno
903 covered = j >= len(missing) or missing[j] > lineno
901 if self.blank_re.match(line):
904 if self.blank_re.match(line):
902 dest.write(' ')
905 dest.write(' ')
903 elif self.else_re.match(line):
906 elif self.else_re.match(line):
904 # Special logic for lines containing only 'else:'.
907 # Special logic for lines containing only 'else:'.
905 # See [GDR 2001-12-04b, 3.2].
908 # See [GDR 2001-12-04b, 3.2].
906 if i >= len(statements) and j >= len(missing):
909 if i >= len(statements) and j >= len(missing):
907 dest.write('! ')
910 dest.write('! ')
908 elif i >= len(statements) or j >= len(missing):
911 elif i >= len(statements) or j >= len(missing):
909 dest.write('> ')
912 dest.write('> ')
910 elif statements[i] == missing[j]:
913 elif statements[i] == missing[j]:
911 dest.write('! ')
914 dest.write('! ')
912 else:
915 else:
913 dest.write('> ')
916 dest.write('> ')
914 elif lineno in excluded:
917 elif lineno in excluded:
915 dest.write('- ')
918 dest.write('- ')
916 elif covered:
919 elif covered:
917 dest.write('> ')
920 dest.write('> ')
918 else:
921 else:
919 dest.write('! ')
922 dest.write('! ')
920 dest.write(line)
923 dest.write(line)
921 source.close()
924 source.close()
922 dest.close()
925 dest.close()
923
926
924 # Singleton object.
927 # Singleton object.
925 the_coverage = coverage()
928 the_coverage = coverage()
926
929
927 # Module functions call methods in the singleton object.
930 # Module functions call methods in the singleton object.
928 def use_cache(*args, **kw):
931 def use_cache(*args, **kw):
929 return the_coverage.use_cache(*args, **kw)
932 return the_coverage.use_cache(*args, **kw)
930
933
931 def start(*args, **kw):
934 def start(*args, **kw):
932 return the_coverage.start(*args, **kw)
935 return the_coverage.start(*args, **kw)
933
936
934 def stop(*args, **kw):
937 def stop(*args, **kw):
935 return the_coverage.stop(*args, **kw)
938 return the_coverage.stop(*args, **kw)
936
939
937 def erase(*args, **kw):
940 def erase(*args, **kw):
938 return the_coverage.erase(*args, **kw)
941 return the_coverage.erase(*args, **kw)
939
942
940 def begin_recursive(*args, **kw):
943 def begin_recursive(*args, **kw):
941 return the_coverage.begin_recursive(*args, **kw)
944 return the_coverage.begin_recursive(*args, **kw)
942
945
943 def end_recursive(*args, **kw):
946 def end_recursive(*args, **kw):
944 return the_coverage.end_recursive(*args, **kw)
947 return the_coverage.end_recursive(*args, **kw)
945
948
946 def exclude(*args, **kw):
949 def exclude(*args, **kw):
947 return the_coverage.exclude(*args, **kw)
950 return the_coverage.exclude(*args, **kw)
948
951
949 def analysis(*args, **kw):
952 def analysis(*args, **kw):
950 return the_coverage.analysis(*args, **kw)
953 return the_coverage.analysis(*args, **kw)
951
954
952 def analysis2(*args, **kw):
955 def analysis2(*args, **kw):
953 return the_coverage.analysis2(*args, **kw)
956 return the_coverage.analysis2(*args, **kw)
954
957
955 def report(*args, **kw):
958 def report(*args, **kw):
956 return the_coverage.report(*args, **kw)
959 return the_coverage.report(*args, **kw)
957
960
958 def annotate(*args, **kw):
961 def annotate(*args, **kw):
959 return the_coverage.annotate(*args, **kw)
962 return the_coverage.annotate(*args, **kw)
960
963
961 def annotate_file(*args, **kw):
964 def annotate_file(*args, **kw):
962 return the_coverage.annotate_file(*args, **kw)
965 return the_coverage.annotate_file(*args, **kw)
963
966
964 # Save coverage data when Python exits. (The atexit module wasn't
967 # Save coverage data when Python exits. (The atexit module wasn't
965 # introduced until Python 2.0, so use sys.exitfunc when it's not
968 # introduced until Python 2.0, so use sys.exitfunc when it's not
966 # available.)
969 # available.)
967 try:
970 try:
968 import atexit
971 import atexit
969 atexit.register(the_coverage.save)
972 atexit.register(the_coverage.save)
970 except ImportError:
973 except ImportError:
971 sys.exitfunc = the_coverage.save
974 sys.exitfunc = the_coverage.save
972
975
973 # Command-line interface.
976 # Command-line interface.
974 if __name__ == '__main__':
977 if __name__ == '__main__':
975 the_coverage.command_line(sys.argv[1:])
978 the_coverage.command_line(sys.argv[1:])
976
979
977
980
978 # A. REFERENCES
981 # A. REFERENCES
979 #
982 #
980 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
983 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
981 # Ravenbrook Limited; 2001-12-04;
984 # Ravenbrook Limited; 2001-12-04;
982 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
985 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
983 #
986 #
984 # [GDR 2001-12-04b] "Statement coverage for Python: design and
987 # [GDR 2001-12-04b] "Statement coverage for Python: design and
985 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
988 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
986 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
989 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
987 #
990 #
988 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
991 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
989 # Guide van Rossum; 2001-07-20;
992 # Guide van Rossum; 2001-07-20;
990 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
993 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
991 #
994 #
992 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
995 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
993 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
996 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
994 #
997 #
995 #
998 #
996 # B. DOCUMENT HISTORY
999 # B. DOCUMENT HISTORY
997 #
1000 #
998 # 2001-12-04 GDR Created.
1001 # 2001-12-04 GDR Created.
999 #
1002 #
1000 # 2001-12-06 GDR Added command-line interface and source code
1003 # 2001-12-06 GDR Added command-line interface and source code
1001 # annotation.
1004 # annotation.
1002 #
1005 #
1003 # 2001-12-09 GDR Moved design and interface to separate documents.
1006 # 2001-12-09 GDR Moved design and interface to separate documents.
1004 #
1007 #
1005 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
1008 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
1006 # simultaneous -e and -x, or -a and -r.
1009 # simultaneous -e and -x, or -a and -r.
1007 #
1010 #
1008 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
1011 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
1009 # only needs to be done once when you specify -a and -r.
1012 # only needs to be done once when you specify -a and -r.
1010 #
1013 #
1011 # 2001-12-13 GDR Improved speed while recording. Portable between
1014 # 2001-12-13 GDR Improved speed while recording. Portable between
1012 # Python 1.5.2 and 2.1.1.
1015 # Python 1.5.2 and 2.1.1.
1013 #
1016 #
1014 # 2002-01-03 GDR Module-level functions work correctly.
1017 # 2002-01-03 GDR Module-level functions work correctly.
1015 #
1018 #
1016 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
1019 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
1017 # so that it matches the value the program would get if it were run on
1020 # so that it matches the value the program would get if it were run on
1018 # its own.
1021 # its own.
1019 #
1022 #
1020 # 2004-12-12 NMB Significant code changes.
1023 # 2004-12-12 NMB Significant code changes.
1021 # - Finding executable statements has been rewritten so that docstrings and
1024 # - Finding executable statements has been rewritten so that docstrings and
1022 # other quirks of Python execution aren't mistakenly identified as missing
1025 # other quirks of Python execution aren't mistakenly identified as missing
1023 # lines.
1026 # lines.
1024 # - Lines can be excluded from consideration, even entire suites of lines.
1027 # - Lines can be excluded from consideration, even entire suites of lines.
1025 # - The filesystem cache of covered lines can be disabled programmatically.
1028 # - The filesystem cache of covered lines can be disabled programmatically.
1026 # - Modernized the code.
1029 # - Modernized the code.
1027 #
1030 #
1028 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
1031 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
1029 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
1032 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
1030 # 'annotate_file'.
1033 # 'annotate_file'.
1031 #
1034 #
1032 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1035 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1033 # Thanks, Allen.
1036 # Thanks, Allen.
1034 #
1037 #
1035 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
1038 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
1036 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1039 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1037 # captured to a different destination.
1040 # captured to a different destination.
1038 #
1041 #
1039 # 2005-12-03 NMB coverage.py can now measure itself.
1042 # 2005-12-03 NMB coverage.py can now measure itself.
1040 #
1043 #
1041 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1044 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1042 # and sorting and omitting files to report on.
1045 # and sorting and omitting files to report on.
1043 #
1046 #
1044 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1047 # 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1045 #
1048 #
1046 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1049 # 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1047 # handling.
1050 # handling.
1048 #
1051 #
1049 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1052 # 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1050 #
1053 #
1051 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
1054 # 2006-08-23 NMB Refactorings to improve testability. Fixes to command-line
1052 # logic for parallel mode and collect.
1055 # logic for parallel mode and collect.
1053 #
1056 #
1054 # 2006-08-25 NMB "#pragma: nocover" is excluded by default.
1057 # 2006-08-25 NMB "#pragma: nocover" is excluded by default.
1055 #
1058 #
1056 # 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
1059 # 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.
1060 # appear in the middle of a function, a problem reported by Tim Leslie.
1058 # Minor changes to avoid lint warnings.
1061 # Minor changes to avoid lint warnings.
1059 #
1062 #
1060 # 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
1063 # 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
1064 # Change how parallel mode is invoked, and fix erase() so that it erases the
1062 # cache when called programmatically.
1065 # cache when called programmatically.
1063 #
1066 #
1064 # 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
1067 # 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
1065 # do anything useful with it anyway.
1068 # do anything useful with it anyway.
1066 # Better file handling on Linux, thanks Guillaume Chazarain.
1069 # Better file handling on Linux, thanks Guillaume Chazarain.
1067 # Better shell support on Windows, thanks Noel O'Boyle.
1070 # Better shell support on Windows, thanks Noel O'Boyle.
1068 # Python 2.2 support maintained, thanks Catherine Proulx.
1071 # Python 2.2 support maintained, thanks Catherine Proulx.
1069 #
1072 #
1070 # 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
1073 # 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
1074 # 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
1075 # reports during execution. Pass statements are handled specially so that their
1073 # disappearance during execution won't throw off the measurement.
1076 # disappearance during execution won't throw off the measurement.
1074 #
1077 #
1075 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1078 # 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1076 # new with statement is counted as executable.
1079 # new with statement is counted as executable.
1077 #
1080 #
1078 # 2007-07-29 NMB Better packaging.
1081 # 2007-07-29 NMB Better packaging.
1079
1082
1080 # C. COPYRIGHT AND LICENCE
1083 # C. COPYRIGHT AND LICENCE
1081 #
1084 #
1082 # Copyright 2001 Gareth Rees. All rights reserved.
1085 # Copyright 2001 Gareth Rees. All rights reserved.
1083 # Copyright 2004-2007 Ned Batchelder. All rights reserved.
1086 # Copyright 2004-2007 Ned Batchelder. All rights reserved.
1084 #
1087 #
1085 # Redistribution and use in source and binary forms, with or without
1088 # Redistribution and use in source and binary forms, with or without
1086 # modification, are permitted provided that the following conditions are
1089 # modification, are permitted provided that the following conditions are
1087 # met:
1090 # met:
1088 #
1091 #
1089 # 1. Redistributions of source code must retain the above copyright
1092 # 1. Redistributions of source code must retain the above copyright
1090 # notice, this list of conditions and the following disclaimer.
1093 # notice, this list of conditions and the following disclaimer.
1091 #
1094 #
1092 # 2. Redistributions in binary form must reproduce the above copyright
1095 # 2. Redistributions in binary form must reproduce the above copyright
1093 # notice, this list of conditions and the following disclaimer in the
1096 # notice, this list of conditions and the following disclaimer in the
1094 # documentation and/or other materials provided with the
1097 # documentation and/or other materials provided with the
1095 # distribution.
1098 # distribution.
1096 #
1099 #
1097 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1100 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1098 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1101 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1099 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1102 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1100 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1103 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1101 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1104 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1102 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1105 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1103 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1106 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1104 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1107 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1105 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1108 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1106 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1109 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1107 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1110 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1108 # DAMAGE.
1111 # DAMAGE.
1109 #
1112 #
1110 # $Id: coverage.py 74 2007-07-29 22:28:35Z nedbat $
1113 # $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