##// END OF EJS Templates
fix bugs in coverage.py.
Vadim Gelfer -
r2067:3094becf default
parent child Browse files
Show More
@@ -1,891 +1,890
1 #!/usr/bin/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
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 """Usage:
25 """Usage:
26
26
27 coverage.py -x MODULE.py [ARG1 ARG2 ...]
27 coverage.py -x 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.
30
30
31 coverage.py -e
31 coverage.py -e
32 Erase collected coverage data.
32 Erase collected coverage data.
33
33
34 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
34 coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
35 Report on the statement coverage for the given files. With the -m
35 Report on the statement coverage for the given files. With the -m
36 option, show line numbers of the statements that weren't executed.
36 option, show line numbers of the statements that weren't executed.
37
37
38 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
38 coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
39 Make annotated copies of the given files, marking statements that
39 Make annotated copies of the given files, marking statements that
40 are executed with > and statements that are missed with !. With
40 are executed with > and statements that are missed with !. With
41 the -d option, make the copies in that directory. Without the -d
41 the -d option, make the copies in that directory. Without the -d
42 option, make each copy in the same directory as the original.
42 option, make each copy in the same directory as the original.
43
43
44 -o dir,dir2,...
44 -o dir,dir2,...
45 Omit reporting or annotating files when their filename path starts with
45 Omit reporting or annotating files when their filename path starts with
46 a directory listed in the omit list.
46 a directory listed in the omit list.
47 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
47 e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
48
48
49 Coverage data is saved in the file .coverage by default. Set the
49 Coverage data is saved in the file .coverage by default. Set the
50 COVERAGE_FILE environment variable to save it somewhere else."""
50 COVERAGE_FILE environment variable to save it somewhere else."""
51
51
52 __version__ = "2.5.20051204" # see detailed history at the end of this file.
52 __version__ = "2.5.20051204" # see detailed history at the end of this file.
53
53
54 import compiler
54 import compiler
55 import compiler.visitor
55 import compiler.visitor
56 import os
56 import os
57 import re
57 import re
58 import string
58 import string
59 import sys
59 import sys
60 import threading
60 import threading
61 import types
61 import types
62
62
63 # 2. IMPLEMENTATION
63 # 2. IMPLEMENTATION
64 #
64 #
65 # This uses the "singleton" pattern.
65 # This uses the "singleton" pattern.
66 #
66 #
67 # The word "morf" means a module object (from which the source file can
67 # The word "morf" means a module object (from which the source file can
68 # be deduced by suitable manipulation of the __file__ attribute) or a
68 # be deduced by suitable manipulation of the __file__ attribute) or a
69 # filename.
69 # filename.
70 #
70 #
71 # When we generate a coverage report we have to canonicalize every
71 # When we generate a coverage report we have to canonicalize every
72 # filename in the coverage dictionary just in case it refers to the
72 # filename in the coverage dictionary just in case it refers to the
73 # module we are reporting on. It seems a shame to throw away this
73 # module we are reporting on. It seems a shame to throw away this
74 # information so the data in the coverage dictionary is transferred to
74 # information so the data in the coverage dictionary is transferred to
75 # the 'cexecuted' dictionary under the canonical filenames.
75 # the 'cexecuted' dictionary under the canonical filenames.
76 #
76 #
77 # The coverage dictionary is called "c" and the trace function "t". The
77 # The coverage dictionary is called "c" and the trace function "t". The
78 # reason for these short names is that Python looks up variables by name
78 # reason for these short names is that Python looks up variables by name
79 # at runtime and so execution time depends on the length of variables!
79 # at runtime and so execution time depends on the length of variables!
80 # In the bottleneck of this application it's appropriate to abbreviate
80 # In the bottleneck of this application it's appropriate to abbreviate
81 # names to increase speed.
81 # names to increase speed.
82
82
83 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
83 class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
84 def __init__(self, statements, excluded, suite_spots):
84 def __init__(self, statements, excluded, suite_spots):
85 compiler.visitor.ASTVisitor.__init__(self)
85 compiler.visitor.ASTVisitor.__init__(self)
86 self.statements = statements
86 self.statements = statements
87 self.excluded = excluded
87 self.excluded = excluded
88 self.suite_spots = suite_spots
88 self.suite_spots = suite_spots
89 self.excluding_suite = 0
89 self.excluding_suite = 0
90
90
91 def doRecursive(self, node):
91 def doRecursive(self, node):
92 self.recordNodeLine(node)
92 self.recordNodeLine(node)
93 for n in node.getChildNodes():
93 for n in node.getChildNodes():
94 self.dispatch(n)
94 self.dispatch(n)
95
95
96 visitStmt = visitModule = doRecursive
96 visitStmt = visitModule = doRecursive
97
97
98 def doCode(self, node):
98 def doCode(self, node):
99 if hasattr(node, 'decorators') and node.decorators:
99 if hasattr(node, 'decorators') and node.decorators:
100 self.dispatch(node.decorators)
100 self.dispatch(node.decorators)
101 self.doSuite(node, node.code)
101 self.doSuite(node, node.code)
102
102
103 visitFunction = visitClass = doCode
103 visitFunction = visitClass = doCode
104
104
105 def getFirstLine(self, node):
105 def getFirstLine(self, node):
106 # Find the first line in the tree node.
106 # Find the first line in the tree node.
107 lineno = node.lineno
107 lineno = node.lineno
108 for n in node.getChildNodes():
108 for n in node.getChildNodes():
109 f = self.getFirstLine(n)
109 f = self.getFirstLine(n)
110 if lineno and f:
110 if lineno and f:
111 lineno = min(lineno, f)
111 lineno = min(lineno, f)
112 else:
112 else:
113 lineno = lineno or f
113 lineno = lineno or f
114 return lineno
114 return lineno
115
115
116 def getLastLine(self, node):
116 def getLastLine(self, node):
117 # Find the first line in the tree node.
117 # Find the first line in the tree node.
118 lineno = node.lineno
118 lineno = node.lineno
119 for n in node.getChildNodes():
119 for n in node.getChildNodes():
120 lineno = max(lineno, self.getLastLine(n))
120 lineno = max(lineno, self.getLastLine(n))
121 return lineno
121 return lineno
122
122
123 def doStatement(self, node):
123 def doStatement(self, node):
124 self.recordLine(self.getFirstLine(node))
124 self.recordLine(self.getFirstLine(node))
125
125
126 visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
126 visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
127 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
127 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
128 doStatement
128 doStatement
129
129
130 def recordNodeLine(self, node):
130 def recordNodeLine(self, node):
131 return self.recordLine(node.lineno)
131 return self.recordLine(node.lineno)
132
132
133 def recordLine(self, lineno):
133 def recordLine(self, lineno):
134 # Returns a bool, whether the line is included or excluded.
134 # Returns a bool, whether the line is included or excluded.
135 if lineno:
135 if lineno:
136 # Multi-line tests introducing suites have to get charged to their
136 # Multi-line tests introducing suites have to get charged to their
137 # keyword.
137 # keyword.
138 if lineno in self.suite_spots:
138 if lineno in self.suite_spots:
139 lineno = self.suite_spots[lineno][0]
139 lineno = self.suite_spots[lineno][0]
140 # If we're inside an exluded suite, record that this line was
140 # If we're inside an exluded suite, record that this line was
141 # excluded.
141 # excluded.
142 if self.excluding_suite:
142 if self.excluding_suite:
143 self.excluded[lineno] = 1
143 self.excluded[lineno] = 1
144 return 0
144 return 0
145 # If this line is excluded, or suite_spots maps this line to
145 # If this line is excluded, or suite_spots maps this line to
146 # another line that is exlcuded, then we're excluded.
146 # another line that is exlcuded, then we're excluded.
147 elif self.excluded.has_key(lineno) or \
147 elif self.excluded.has_key(lineno) or \
148 self.suite_spots.has_key(lineno) and \
148 self.suite_spots.has_key(lineno) and \
149 self.excluded.has_key(self.suite_spots[lineno][1]):
149 self.excluded.has_key(self.suite_spots[lineno][1]):
150 return 0
150 return 0
151 # Otherwise, this is an executable line.
151 # Otherwise, this is an executable line.
152 else:
152 else:
153 self.statements[lineno] = 1
153 self.statements[lineno] = 1
154 return 1
154 return 1
155 return 0
155 return 0
156
156
157 default = recordNodeLine
157 default = recordNodeLine
158
158
159 def recordAndDispatch(self, node):
159 def recordAndDispatch(self, node):
160 self.recordNodeLine(node)
160 self.recordNodeLine(node)
161 self.dispatch(node)
161 self.dispatch(node)
162
162
163 def doSuite(self, intro, body, exclude=0):
163 def doSuite(self, intro, body, exclude=0):
164 exsuite = self.excluding_suite
164 exsuite = self.excluding_suite
165 if exclude or (intro and not self.recordNodeLine(intro)):
165 if exclude or (intro and not self.recordNodeLine(intro)):
166 self.excluding_suite = 1
166 self.excluding_suite = 1
167 self.recordAndDispatch(body)
167 self.recordAndDispatch(body)
168 self.excluding_suite = exsuite
168 self.excluding_suite = exsuite
169
169
170 def doPlainWordSuite(self, prevsuite, suite):
170 def doPlainWordSuite(self, prevsuite, suite):
171 # Finding the exclude lines for else's is tricky, because they aren't
171 # 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,
172 # 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
173 # and find its last line. If any line between there and the else's
174 # first line are excluded, then we exclude the else.
174 # first line are excluded, then we exclude the else.
175 lastprev = self.getLastLine(prevsuite)
175 lastprev = self.getLastLine(prevsuite)
176 firstelse = self.getFirstLine(suite)
176 firstelse = self.getFirstLine(suite)
177 for l in range(lastprev+1, firstelse):
177 for l in range(lastprev+1, firstelse):
178 if self.suite_spots.has_key(l):
178 if self.suite_spots.has_key(l):
179 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
179 self.doSuite(None, suite, exclude=self.excluded.has_key(l))
180 break
180 break
181 else:
181 else:
182 self.doSuite(None, suite)
182 self.doSuite(None, suite)
183
183
184 def doElse(self, prevsuite, node):
184 def doElse(self, prevsuite, node):
185 if node.else_:
185 if node.else_:
186 self.doPlainWordSuite(prevsuite, node.else_)
186 self.doPlainWordSuite(prevsuite, node.else_)
187
187
188 def visitFor(self, node):
188 def visitFor(self, node):
189 self.doSuite(node, node.body)
189 self.doSuite(node, node.body)
190 self.doElse(node.body, node)
190 self.doElse(node.body, node)
191
191
192 def visitIf(self, node):
192 def visitIf(self, node):
193 # The first test has to be handled separately from the rest.
193 # 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
194 # 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.
195 # are credited to the line with the test for the elif.
196 self.doSuite(node, node.tests[0][1])
196 self.doSuite(node, node.tests[0][1])
197 for t, n in node.tests[1:]:
197 for t, n in node.tests[1:]:
198 self.doSuite(t, n)
198 self.doSuite(t, n)
199 self.doElse(node.tests[-1][1], node)
199 self.doElse(node.tests[-1][1], node)
200
200
201 def visitWhile(self, node):
201 def visitWhile(self, node):
202 self.doSuite(node, node.body)
202 self.doSuite(node, node.body)
203 self.doElse(node.body, node)
203 self.doElse(node.body, node)
204
204
205 def visitTryExcept(self, node):
205 def visitTryExcept(self, node):
206 self.doSuite(node, node.body)
206 self.doSuite(node, node.body)
207 for i in range(len(node.handlers)):
207 for i in range(len(node.handlers)):
208 a, b, h = node.handlers[i]
208 a, b, h = node.handlers[i]
209 if not a:
209 if not a:
210 # It's a plain "except:". Find the previous suite.
210 # It's a plain "except:". Find the previous suite.
211 if i > 0:
211 if i > 0:
212 prev = node.handlers[i-1][2]
212 prev = node.handlers[i-1][2]
213 else:
213 else:
214 prev = node.body
214 prev = node.body
215 self.doPlainWordSuite(prev, h)
215 self.doPlainWordSuite(prev, h)
216 else:
216 else:
217 self.doSuite(a, h)
217 self.doSuite(a, h)
218 self.doElse(node.handlers[-1][2], node)
218 self.doElse(node.handlers[-1][2], node)
219
219
220 def visitTryFinally(self, node):
220 def visitTryFinally(self, node):
221 self.doSuite(node, node.body)
221 self.doSuite(node, node.body)
222 self.doPlainWordSuite(node.body, node.final)
222 self.doPlainWordSuite(node.body, node.final)
223
223
224 def visitGlobal(self, node):
224 def visitGlobal(self, node):
225 # "global" statements don't execute like others (they don't call the
225 # "global" statements don't execute like others (they don't call the
226 # trace function), so don't record their line numbers.
226 # trace function), so don't record their line numbers.
227 pass
227 pass
228
228
229 the_coverage = None
229 the_coverage = None
230
230
231 class coverage:
231 class coverage:
232 error = "coverage error"
232 error = "coverage error"
233
233
234 # Name of the cache file (unless environment variable is set).
234 # Name of the cache file (unless environment variable is set).
235 cache_default = ".coverage"
235 cache_default = ".coverage"
236
236
237 # Environment variable naming the cache file.
237 # Environment variable naming the cache file.
238 cache_env = "COVERAGE_FILE"
238 cache_env = "COVERAGE_FILE"
239
239
240 # A dictionary with an entry for (Python source file name, line number
240 # A dictionary with an entry for (Python source file name, line number
241 # in that file) if that line has been executed.
241 # in that file) if that line has been executed.
242 c = {}
242 c = {}
243
243
244 # A map from canonical Python source file name to a dictionary in
244 # 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
245 # which there's an entry for each line number that has been
246 # executed.
246 # executed.
247 cexecuted = {}
247 cexecuted = {}
248
248
249 # Cache of results of calling the analysis2() method, so that you can
249 # Cache of results of calling the analysis2() method, so that you can
250 # specify both -r and -a without doing double work.
250 # specify both -r and -a without doing double work.
251 analysis_cache = {}
251 analysis_cache = {}
252
252
253 # Cache of results of calling the canonical_filename() method, to
253 # Cache of results of calling the canonical_filename() method, to
254 # avoid duplicating work.
254 # avoid duplicating work.
255 canonical_filename_cache = {}
255 canonical_filename_cache = {}
256
256
257 def __init__(self):
257 def __init__(self):
258 global the_coverage
258 global the_coverage
259 if the_coverage:
259 if the_coverage:
260 raise self.error, "Only one coverage object allowed."
260 raise self.error, "Only one coverage object allowed."
261 self.usecache = 1
261 self.usecache = 1
262 self.cache = None
262 self.cache = None
263 self.exclude_re = ''
263 self.exclude_re = ''
264 self.nesting = 0
264 self.nesting = 0
265 self.cstack = []
265 self.cstack = []
266 self.xstack = []
266 self.xstack = []
267 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
267 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
268
268
269 # t(f, x, y). This method is passed to sys.settrace as a trace function.
269 # t(f, x, y). This method is passed to sys.settrace as a trace function.
270 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
270 # 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.
271 # 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
272 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
273 # objects.
273 # objects.
274
274
275 def t(self, f, w, a): #pragma: no cover
275 def t(self, f, w, a): #pragma: no cover
276 #print w, f.f_code.co_filename, f.f_lineno
276 #print w, f.f_code.co_filename, f.f_lineno
277 if w == 'line':
277 if w == 'line':
278 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
278 self.c[(f.f_code.co_filename, f.f_lineno)] = 1
279 for c in self.cstack:
279 for c in self.cstack:
280 c[(f.f_code.co_filename, f.f_lineno)] = 1
280 c[(f.f_code.co_filename, f.f_lineno)] = 1
281 return self.t
281 return self.t
282
282
283 def help(self, error=None):
283 def help(self, error=None):
284 if error:
284 if error:
285 print error
285 print error
286 print
286 print
287 print __doc__
287 print __doc__
288 sys.exit(1)
288 sys.exit(1)
289
289
290 def command_line(self):
290 def command_line(self):
291 import getopt
291 import getopt
292 settings = {}
292 settings = {}
293 optmap = {
293 optmap = {
294 '-a': 'annotate',
294 '-a': 'annotate',
295 '-d:': 'directory=',
295 '-d:': 'directory=',
296 '-e': 'erase',
296 '-e': 'erase',
297 '-h': 'help',
297 '-h': 'help',
298 '-i': 'ignore-errors',
298 '-i': 'ignore-errors',
299 '-m': 'show-missing',
299 '-m': 'show-missing',
300 '-r': 'report',
300 '-r': 'report',
301 '-x': 'execute',
301 '-x': 'execute',
302 '-o': 'omit=',
302 '-o': 'omit=',
303 }
303 }
304 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
304 short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
305 long_opts = optmap.values()
305 long_opts = optmap.values()
306 options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
306 options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
307 for o, a in options:
307 for o, a in options:
308 if optmap.has_key(o):
308 if optmap.has_key(o):
309 settings[optmap[o]] = 1
309 settings[optmap[o]] = 1
310 elif optmap.has_key(o + ':'):
310 elif optmap.has_key(o + ':'):
311 settings[optmap[o + ':']] = a
311 settings[optmap[o + ':']] = a
312 elif o[2:] in long_opts:
312 elif o[2:] in long_opts:
313 settings[o[2:]] = 1
313 settings[o[2:]] = 1
314 elif o[2:] + '=' in long_opts:
314 elif o[2:] + '=' in long_opts:
315 settings[o[2:]] = a
315 settings[o[2:]] = a
316 else:
316 else:
317 self.help("Unknown option: '%s'." % o)
317 self.help("Unknown option: '%s'." % o)
318 if settings.get('help'):
318 if settings.get('help'):
319 self.help()
319 self.help()
320 for i in ['erase', 'execute']:
320 for i in ['erase', 'execute']:
321 for j in ['annotate', 'report']:
321 for j in ['annotate', 'report']:
322 if settings.get(i) and settings.get(j):
322 if settings.get(i) and settings.get(j):
323 self.help("You can't specify the '%s' and '%s' "
323 self.help("You can't specify the '%s' and '%s' "
324 "options at the same time." % (i, j))
324 "options at the same time." % (i, j))
325 args_needed = (settings.get('execute')
325 args_needed = (settings.get('execute')
326 or settings.get('annotate')
326 or settings.get('annotate')
327 or settings.get('report'))
327 or settings.get('report'))
328 action = settings.get('erase') or args_needed
328 action = settings.get('erase') or args_needed
329 if not action:
329 if not action:
330 self.help("You must specify at least one of -e, -x, -r, or -a.")
330 self.help("You must specify at least one of -e, -x, -r, or -a.")
331 if not args_needed and args:
331 if not args_needed and args:
332 self.help("Unexpected arguments %s." % args)
332 self.help("Unexpected arguments %s." % args)
333
333
334 self.get_ready()
334 self.get_ready()
335 self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
335 self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
336
336
337 if settings.get('erase'):
337 if settings.get('erase'):
338 self.erase()
338 self.erase()
339 if settings.get('execute'):
339 if settings.get('execute'):
340 if not args:
340 if not args:
341 self.help("Nothing to do.")
341 self.help("Nothing to do.")
342 sys.argv = args
342 sys.argv = args
343 self.start()
343 self.start()
344 import __main__
344 import __main__
345 sys.path[0] = os.path.dirname(sys.argv[0])
345 sys.path[0] = os.path.dirname(sys.argv[0])
346 execfile(sys.argv[0], __main__.__dict__)
346 execfile(sys.argv[0], __main__.__dict__)
347 if not args:
347 if not args:
348 args = self.cexecuted.keys()
348 args = self.cexecuted.keys()
349 ignore_errors = settings.get('ignore-errors')
349 ignore_errors = settings.get('ignore-errors')
350 show_missing = settings.get('show-missing')
350 show_missing = settings.get('show-missing')
351 directory = settings.get('directory=')
351 directory = settings.get('directory')
352 omit = settings.get('omit=')
352 omit = filter(None, settings.get('omit', '').split(','))
353 if omit is not None:
354 omit = omit.split(',')
355 else:
356 omit = []
357
353
358 if settings.get('report'):
354 if settings.get('report'):
359 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
355 self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
360 if settings.get('annotate'):
356 if settings.get('annotate'):
361 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
357 self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
362
358
363 def use_cache(self, usecache):
359 def use_cache(self, usecache):
364 self.usecache = usecache
360 self.usecache = usecache
365
361
366 def get_ready(self):
362 def get_ready(self):
367 if self.usecache and not self.cache:
363 if self.usecache and not self.cache:
368 self.cache = os.environ.get(self.cache_env, self.cache_default)
364 self.cache = os.path.abspath(os.environ.get(self.cache_env,
365 self.cache_default))
369 self.restore()
366 self.restore()
370 self.analysis_cache = {}
367 self.analysis_cache = {}
371
368
372 def start(self):
369 def start(self):
373 self.get_ready()
370 self.get_ready()
374 if self.nesting == 0: #pragma: no cover
371 if self.nesting == 0: #pragma: no cover
375 sys.settrace(self.t)
372 sys.settrace(self.t)
376 if hasattr(threading, 'settrace'):
373 if hasattr(threading, 'settrace'):
377 threading.settrace(self.t)
374 threading.settrace(self.t)
378 self.nesting += 1
375 self.nesting += 1
379
376
380 def stop(self):
377 def stop(self):
381 self.nesting -= 1
378 self.nesting -= 1
382 if self.nesting == 0: #pragma: no cover
379 if self.nesting == 0: #pragma: no cover
383 sys.settrace(None)
380 sys.settrace(None)
384 if hasattr(threading, 'settrace'):
381 if hasattr(threading, 'settrace'):
385 threading.settrace(None)
382 threading.settrace(None)
386
383
387 def erase(self):
384 def erase(self):
388 self.c = {}
385 self.c = {}
389 self.analysis_cache = {}
386 self.analysis_cache = {}
390 self.cexecuted = {}
387 self.cexecuted = {}
391 if self.cache and os.path.exists(self.cache):
388 if self.cache and os.path.exists(self.cache):
392 os.remove(self.cache)
389 os.remove(self.cache)
393 self.exclude_re = ""
390 self.exclude_re = ""
394
391
395 def exclude(self, re):
392 def exclude(self, re):
396 if self.exclude_re:
393 if self.exclude_re:
397 self.exclude_re += "|"
394 self.exclude_re += "|"
398 self.exclude_re += "(" + re + ")"
395 self.exclude_re += "(" + re + ")"
399
396
400 def begin_recursive(self):
397 def begin_recursive(self):
401 self.cstack.append(self.c)
398 self.cstack.append(self.c)
402 self.xstack.append(self.exclude_re)
399 self.xstack.append(self.exclude_re)
403
400
404 def end_recursive(self):
401 def end_recursive(self):
405 self.c = self.cstack.pop()
402 self.c = self.cstack.pop()
406 self.exclude_re = self.xstack.pop()
403 self.exclude_re = self.xstack.pop()
407
404
408 # save(). Save coverage data to the coverage cache.
405 # save(). Save coverage data to the coverage cache.
409
406
410 def save(self):
407 def save(self):
408 # move to directory that must exist.
409 os.chdir(os.sep)
411 if self.usecache and self.cache:
410 if self.usecache and self.cache:
412 self.canonicalize_filenames()
411 self.canonicalize_filenames()
413 cache = open(self.cache, 'wb')
412 cache = open(self.cache, 'wb')
414 import marshal
413 import marshal
415 marshal.dump(self.cexecuted, cache)
414 marshal.dump(self.cexecuted, cache)
416 cache.close()
415 cache.close()
417
416
418 # restore(). Restore coverage data from the coverage cache (if it exists).
417 # restore(). Restore coverage data from the coverage cache (if it exists).
419
418
420 def restore(self):
419 def restore(self):
421 self.c = {}
420 self.c = {}
422 self.cexecuted = {}
421 self.cexecuted = {}
423 assert self.usecache
422 assert self.usecache
424 if not os.path.exists(self.cache):
423 if not os.path.exists(self.cache):
425 return
424 return
426 try:
425 try:
427 cache = open(self.cache, 'rb')
426 cache = open(self.cache, 'rb')
428 import marshal
427 import marshal
429 cexecuted = marshal.load(cache)
428 cexecuted = marshal.load(cache)
430 cache.close()
429 cache.close()
431 if isinstance(cexecuted, types.DictType):
430 if isinstance(cexecuted, types.DictType):
432 self.cexecuted = cexecuted
431 self.cexecuted = cexecuted
433 except:
432 except:
434 pass
433 pass
435
434
436 # canonical_filename(filename). Return a canonical filename for the
435 # canonical_filename(filename). Return a canonical filename for the
437 # file (that is, an absolute path with no redundant components and
436 # file (that is, an absolute path with no redundant components and
438 # normalized case). See [GDR 2001-12-04b, 3.3].
437 # normalized case). See [GDR 2001-12-04b, 3.3].
439
438
440 def canonical_filename(self, filename):
439 def canonical_filename(self, filename):
441 if not self.canonical_filename_cache.has_key(filename):
440 if not self.canonical_filename_cache.has_key(filename):
442 f = filename
441 f = filename
443 if os.path.isabs(f) and not os.path.exists(f):
442 if os.path.isabs(f) and not os.path.exists(f):
444 f = os.path.basename(f)
443 f = os.path.basename(f)
445 if not os.path.isabs(f):
444 if not os.path.isabs(f):
446 for path in [os.curdir] + sys.path:
445 for path in [os.curdir] + sys.path:
447 g = os.path.join(path, f)
446 g = os.path.join(path, f)
448 if os.path.exists(g):
447 if os.path.exists(g):
449 f = g
448 f = g
450 break
449 break
451 cf = os.path.normcase(os.path.abspath(f))
450 cf = os.path.normcase(os.path.abspath(f))
452 self.canonical_filename_cache[filename] = cf
451 self.canonical_filename_cache[filename] = cf
453 return self.canonical_filename_cache[filename]
452 return self.canonical_filename_cache[filename]
454
453
455 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
454 # canonicalize_filenames(). Copy results from "c" to "cexecuted",
456 # canonicalizing filenames on the way. Clear the "c" map.
455 # canonicalizing filenames on the way. Clear the "c" map.
457
456
458 def canonicalize_filenames(self):
457 def canonicalize_filenames(self):
459 for filename, lineno in self.c.keys():
458 for filename, lineno in self.c.keys():
460 f = self.canonical_filename(filename)
459 f = self.canonical_filename(filename)
461 if not self.cexecuted.has_key(f):
460 if not self.cexecuted.has_key(f):
462 self.cexecuted[f] = {}
461 self.cexecuted[f] = {}
463 self.cexecuted[f][lineno] = 1
462 self.cexecuted[f][lineno] = 1
464 self.c = {}
463 self.c = {}
465
464
466 # morf_filename(morf). Return the filename for a module or file.
465 # morf_filename(morf). Return the filename for a module or file.
467
466
468 def morf_filename(self, morf):
467 def morf_filename(self, morf):
469 if isinstance(morf, types.ModuleType):
468 if isinstance(morf, types.ModuleType):
470 if not hasattr(morf, '__file__'):
469 if not hasattr(morf, '__file__'):
471 raise self.error, "Module has no __file__ attribute."
470 raise self.error, "Module has no __file__ attribute."
472 file = morf.__file__
471 file = morf.__file__
473 else:
472 else:
474 file = morf
473 file = morf
475 return self.canonical_filename(file)
474 return self.canonical_filename(file)
476
475
477 # analyze_morf(morf). Analyze the module or filename passed as
476 # analyze_morf(morf). Analyze the module or filename passed as
478 # the argument. If the source code can't be found, raise an error.
477 # 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
478 # Otherwise, return a tuple of (1) the canonical filename of the
480 # source code for the module, (2) a list of lines of statements
479 # 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.
480 # in the source code, and (3) a list of lines of excluded statements.
482
481
483 def analyze_morf(self, morf):
482 def analyze_morf(self, morf):
484 if self.analysis_cache.has_key(morf):
483 if self.analysis_cache.has_key(morf):
485 return self.analysis_cache[morf]
484 return self.analysis_cache[morf]
486 filename = self.morf_filename(morf)
485 filename = self.morf_filename(morf)
487 ext = os.path.splitext(filename)[1]
486 ext = os.path.splitext(filename)[1]
488 if ext == '.pyc':
487 if ext == '.pyc':
489 if not os.path.exists(filename[0:-1]):
488 if not os.path.exists(filename[0:-1]):
490 raise self.error, ("No source for compiled code '%s'."
489 raise self.error, ("No source for compiled code '%s'."
491 % filename)
490 % filename)
492 filename = filename[0:-1]
491 filename = filename[0:-1]
493 elif ext != '.py':
492 elif ext != '.py':
494 raise self.error, "File '%s' not Python source." % filename
493 raise self.error, "File '%s' not Python source." % filename
495 source = open(filename, 'r')
494 source = open(filename, 'r')
496 lines, excluded_lines = self.find_executable_statements(
495 lines, excluded_lines = self.find_executable_statements(
497 source.read(), exclude=self.exclude_re
496 source.read(), exclude=self.exclude_re
498 )
497 )
499 source.close()
498 source.close()
500 result = filename, lines, excluded_lines
499 result = filename, lines, excluded_lines
501 self.analysis_cache[morf] = result
500 self.analysis_cache[morf] = result
502 return result
501 return result
503
502
504 def get_suite_spots(self, tree, spots):
503 def get_suite_spots(self, tree, spots):
505 import symbol, token
504 import symbol, token
506 for i in range(1, len(tree)):
505 for i in range(1, len(tree)):
507 if type(tree[i]) == type(()):
506 if type(tree[i]) == type(()):
508 if tree[i][0] == symbol.suite:
507 if tree[i][0] == symbol.suite:
509 # Found a suite, look back for the colon and keyword.
508 # Found a suite, look back for the colon and keyword.
510 lineno_colon = lineno_word = None
509 lineno_colon = lineno_word = None
511 for j in range(i-1, 0, -1):
510 for j in range(i-1, 0, -1):
512 if tree[j][0] == token.COLON:
511 if tree[j][0] == token.COLON:
513 lineno_colon = tree[j][2]
512 lineno_colon = tree[j][2]
514 elif tree[j][0] == token.NAME:
513 elif tree[j][0] == token.NAME:
515 if tree[j][1] == 'elif':
514 if tree[j][1] == 'elif':
516 # Find the line number of the first non-terminal
515 # Find the line number of the first non-terminal
517 # after the keyword.
516 # after the keyword.
518 t = tree[j+1]
517 t = tree[j+1]
519 while t and token.ISNONTERMINAL(t[0]):
518 while t and token.ISNONTERMINAL(t[0]):
520 t = t[1]
519 t = t[1]
521 if t:
520 if t:
522 lineno_word = t[2]
521 lineno_word = t[2]
523 else:
522 else:
524 lineno_word = tree[j][2]
523 lineno_word = tree[j][2]
525 break
524 break
526 elif tree[j][0] == symbol.except_clause:
525 elif tree[j][0] == symbol.except_clause:
527 # "except" clauses look like:
526 # "except" clauses look like:
528 # ('except_clause', ('NAME', 'except', lineno), ...)
527 # ('except_clause', ('NAME', 'except', lineno), ...)
529 if tree[j][1][0] == token.NAME:
528 if tree[j][1][0] == token.NAME:
530 lineno_word = tree[j][1][2]
529 lineno_word = tree[j][1][2]
531 break
530 break
532 if lineno_colon and lineno_word:
531 if lineno_colon and lineno_word:
533 # Found colon and keyword, mark all the lines
532 # Found colon and keyword, mark all the lines
534 # between the two with the two line numbers.
533 # between the two with the two line numbers.
535 for l in range(lineno_word, lineno_colon+1):
534 for l in range(lineno_word, lineno_colon+1):
536 spots[l] = (lineno_word, lineno_colon)
535 spots[l] = (lineno_word, lineno_colon)
537 self.get_suite_spots(tree[i], spots)
536 self.get_suite_spots(tree[i], spots)
538
537
539 def find_executable_statements(self, text, exclude=None):
538 def find_executable_statements(self, text, exclude=None):
540 # Find lines which match an exclusion pattern.
539 # Find lines which match an exclusion pattern.
541 excluded = {}
540 excluded = {}
542 suite_spots = {}
541 suite_spots = {}
543 if exclude:
542 if exclude:
544 reExclude = re.compile(exclude)
543 reExclude = re.compile(exclude)
545 lines = text.split('\n')
544 lines = text.split('\n')
546 for i in range(len(lines)):
545 for i in range(len(lines)):
547 if reExclude.search(lines[i]):
546 if reExclude.search(lines[i]):
548 excluded[i+1] = 1
547 excluded[i+1] = 1
549
548
550 import parser
549 import parser
551 tree = parser.suite(text+'\n\n').totuple(1)
550 tree = parser.suite(text+'\n\n').totuple(1)
552 self.get_suite_spots(tree, suite_spots)
551 self.get_suite_spots(tree, suite_spots)
553
552
554 # Use the compiler module to parse the text and find the executable
553 # Use the compiler module to parse the text and find the executable
555 # statements. We add newlines to be impervious to final partial lines.
554 # statements. We add newlines to be impervious to final partial lines.
556 statements = {}
555 statements = {}
557 ast = compiler.parse(text+'\n\n')
556 ast = compiler.parse(text+'\n\n')
558 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
557 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
559 compiler.walk(ast, visitor, walker=visitor)
558 compiler.walk(ast, visitor, walker=visitor)
560
559
561 lines = statements.keys()
560 lines = statements.keys()
562 lines.sort()
561 lines.sort()
563 excluded_lines = excluded.keys()
562 excluded_lines = excluded.keys()
564 excluded_lines.sort()
563 excluded_lines.sort()
565 return lines, excluded_lines
564 return lines, excluded_lines
566
565
567 # format_lines(statements, lines). Format a list of line numbers
566 # format_lines(statements, lines). Format a list of line numbers
568 # for printing by coalescing groups of lines as long as the lines
567 # for printing by coalescing groups of lines as long as the lines
569 # represent consecutive statements. This will coalesce even if
568 # represent consecutive statements. This will coalesce even if
570 # there are gaps between statements, so if statements =
569 # there are gaps between statements, so if statements =
571 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
570 # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
572 # format_lines will return "1-2, 5-11, 13-14".
571 # format_lines will return "1-2, 5-11, 13-14".
573
572
574 def format_lines(self, statements, lines):
573 def format_lines(self, statements, lines):
575 pairs = []
574 pairs = []
576 i = 0
575 i = 0
577 j = 0
576 j = 0
578 start = None
577 start = None
579 pairs = []
578 pairs = []
580 while i < len(statements) and j < len(lines):
579 while i < len(statements) and j < len(lines):
581 if statements[i] == lines[j]:
580 if statements[i] == lines[j]:
582 if start == None:
581 if start == None:
583 start = lines[j]
582 start = lines[j]
584 end = lines[j]
583 end = lines[j]
585 j = j + 1
584 j = j + 1
586 elif start:
585 elif start:
587 pairs.append((start, end))
586 pairs.append((start, end))
588 start = None
587 start = None
589 i = i + 1
588 i = i + 1
590 if start:
589 if start:
591 pairs.append((start, end))
590 pairs.append((start, end))
592 def stringify(pair):
591 def stringify(pair):
593 start, end = pair
592 start, end = pair
594 if start == end:
593 if start == end:
595 return "%d" % start
594 return "%d" % start
596 else:
595 else:
597 return "%d-%d" % (start, end)
596 return "%d-%d" % (start, end)
598 return string.join(map(stringify, pairs), ", ")
597 return string.join(map(stringify, pairs), ", ")
599
598
600 # Backward compatibility with version 1.
599 # Backward compatibility with version 1.
601 def analysis(self, morf):
600 def analysis(self, morf):
602 f, s, _, m, mf = self.analysis2(morf)
601 f, s, _, m, mf = self.analysis2(morf)
603 return f, s, m, mf
602 return f, s, m, mf
604
603
605 def analysis2(self, morf):
604 def analysis2(self, morf):
606 filename, statements, excluded = self.analyze_morf(morf)
605 filename, statements, excluded = self.analyze_morf(morf)
607 self.canonicalize_filenames()
606 self.canonicalize_filenames()
608 if not self.cexecuted.has_key(filename):
607 if not self.cexecuted.has_key(filename):
609 self.cexecuted[filename] = {}
608 self.cexecuted[filename] = {}
610 missing = []
609 missing = []
611 for line in statements:
610 for line in statements:
612 if not self.cexecuted[filename].has_key(line):
611 if not self.cexecuted[filename].has_key(line):
613 missing.append(line)
612 missing.append(line)
614 return (filename, statements, excluded, missing,
613 return (filename, statements, excluded, missing,
615 self.format_lines(statements, missing))
614 self.format_lines(statements, missing))
616
615
617 def relative_filename(self, filename):
616 def relative_filename(self, filename):
618 """ Convert filename to relative filename from self.relative_dir.
617 """ Convert filename to relative filename from self.relative_dir.
619 """
618 """
620 return filename.replace(self.relative_dir, "")
619 return filename.replace(self.relative_dir, "")
621
620
622 def morf_name(self, morf):
621 def morf_name(self, morf):
623 """ Return the name of morf as used in report.
622 """ Return the name of morf as used in report.
624 """
623 """
625 if isinstance(morf, types.ModuleType):
624 if isinstance(morf, types.ModuleType):
626 return morf.__name__
625 return morf.__name__
627 else:
626 else:
628 return self.relative_filename(os.path.splitext(morf)[0])
627 return self.relative_filename(os.path.splitext(morf)[0])
629
628
630 def filter_by_prefix(self, morfs, omit_prefixes):
629 def filter_by_prefix(self, morfs, omit_prefixes):
631 """ Return list of morfs where the morf name does not begin
630 """ Return list of morfs where the morf name does not begin
632 with any one of the omit_prefixes.
631 with any one of the omit_prefixes.
633 """
632 """
634 filtered_morfs = []
633 filtered_morfs = []
635 for morf in morfs:
634 for morf in morfs:
636 for prefix in omit_prefixes:
635 for prefix in omit_prefixes:
637 if self.morf_name(morf).startswith(prefix):
636 if self.morf_name(morf).startswith(prefix):
638 break
637 break
639 else:
638 else:
640 filtered_morfs.append(morf)
639 filtered_morfs.append(morf)
641
640
642 return filtered_morfs
641 return filtered_morfs
643
642
644 def morf_name_compare(self, x, y):
643 def morf_name_compare(self, x, y):
645 return cmp(self.morf_name(x), self.morf_name(y))
644 return cmp(self.morf_name(x), self.morf_name(y))
646
645
647 def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
646 def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
648 if not isinstance(morfs, types.ListType):
647 if not isinstance(morfs, types.ListType):
649 morfs = [morfs]
648 morfs = [morfs]
650 morfs = self.filter_by_prefix(morfs, omit_prefixes)
649 morfs = self.filter_by_prefix(morfs, omit_prefixes)
651 morfs.sort(self.morf_name_compare)
650 morfs.sort(self.morf_name_compare)
652
651
653 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
652 max_name = max([5,] + map(len, map(self.morf_name, morfs)))
654 fmt_name = "%%- %ds " % max_name
653 fmt_name = "%%- %ds " % max_name
655 fmt_err = fmt_name + "%s: %s"
654 fmt_err = fmt_name + "%s: %s"
656 header = fmt_name % "Name" + " Stmts Exec Cover"
655 header = fmt_name % "Name" + " Stmts Exec Cover"
657 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
656 fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
658 if show_missing:
657 if show_missing:
659 header = header + " Missing"
658 header = header + " Missing"
660 fmt_coverage = fmt_coverage + " %s"
659 fmt_coverage = fmt_coverage + " %s"
661 if not file:
660 if not file:
662 file = sys.stdout
661 file = sys.stdout
663 print >>file, header
662 print >>file, header
664 print >>file, "-" * len(header)
663 print >>file, "-" * len(header)
665 total_statements = 0
664 total_statements = 0
666 total_executed = 0
665 total_executed = 0
667 for morf in morfs:
666 for morf in morfs:
668 name = self.morf_name(morf)
667 name = self.morf_name(morf)
669 try:
668 try:
670 _, statements, _, missing, readable = self.analysis2(morf)
669 _, statements, _, missing, readable = self.analysis2(morf)
671 n = len(statements)
670 n = len(statements)
672 m = n - len(missing)
671 m = n - len(missing)
673 if n > 0:
672 if n > 0:
674 pc = 100.0 * m / n
673 pc = 100.0 * m / n
675 else:
674 else:
676 pc = 100.0
675 pc = 100.0
677 args = (name, n, m, pc)
676 args = (name, n, m, pc)
678 if show_missing:
677 if show_missing:
679 args = args + (readable,)
678 args = args + (readable,)
680 print >>file, fmt_coverage % args
679 print >>file, fmt_coverage % args
681 total_statements = total_statements + n
680 total_statements = total_statements + n
682 total_executed = total_executed + m
681 total_executed = total_executed + m
683 except KeyboardInterrupt: #pragma: no cover
682 except KeyboardInterrupt: #pragma: no cover
684 raise
683 raise
685 except:
684 except:
686 if not ignore_errors:
685 if not ignore_errors:
687 type, msg = sys.exc_info()[0:2]
686 type, msg = sys.exc_info()[0:2]
688 print >>file, fmt_err % (name, type, msg)
687 print >>file, fmt_err % (name, type, msg)
689 if len(morfs) > 1:
688 if len(morfs) > 1:
690 print >>file, "-" * len(header)
689 print >>file, "-" * len(header)
691 if total_statements > 0:
690 if total_statements > 0:
692 pc = 100.0 * total_executed / total_statements
691 pc = 100.0 * total_executed / total_statements
693 else:
692 else:
694 pc = 100.0
693 pc = 100.0
695 args = ("TOTAL", total_statements, total_executed, pc)
694 args = ("TOTAL", total_statements, total_executed, pc)
696 if show_missing:
695 if show_missing:
697 args = args + ("",)
696 args = args + ("",)
698 print >>file, fmt_coverage % args
697 print >>file, fmt_coverage % args
699
698
700 # annotate(morfs, ignore_errors).
699 # annotate(morfs, ignore_errors).
701
700
702 blank_re = re.compile(r"\s*(#|$)")
701 blank_re = re.compile(r"\s*(#|$)")
703 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
702 else_re = re.compile(r"\s*else\s*:\s*(#|$)")
704
703
705 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
704 def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
706 morfs = self.filter_by_prefix(morfs, omit_prefixes)
705 morfs = self.filter_by_prefix(morfs, omit_prefixes)
707 for morf in morfs:
706 for morf in morfs:
708 try:
707 try:
709 filename, statements, excluded, missing, _ = self.analysis2(morf)
708 filename, statements, excluded, missing, _ = self.analysis2(morf)
710 self.annotate_file(filename, statements, excluded, missing, directory)
709 self.annotate_file(filename, statements, excluded, missing, directory)
711 except KeyboardInterrupt:
710 except KeyboardInterrupt:
712 raise
711 raise
713 except:
712 except:
714 if not ignore_errors:
713 if not ignore_errors:
715 raise
714 raise
716
715
717 def annotate_file(self, filename, statements, excluded, missing, directory=None):
716 def annotate_file(self, filename, statements, excluded, missing, directory=None):
718 source = open(filename, 'r')
717 source = open(filename, 'r')
719 if directory:
718 if directory:
720 dest_file = os.path.join(directory,
719 dest_file = os.path.join(directory,
721 os.path.basename(filename)
720 os.path.basename(filename)
722 + ',cover')
721 + ',cover')
723 else:
722 else:
724 dest_file = filename + ',cover'
723 dest_file = filename + ',cover'
725 dest = open(dest_file, 'w')
724 dest = open(dest_file, 'w')
726 lineno = 0
725 lineno = 0
727 i = 0
726 i = 0
728 j = 0
727 j = 0
729 covered = 1
728 covered = 1
730 while 1:
729 while 1:
731 line = source.readline()
730 line = source.readline()
732 if line == '':
731 if line == '':
733 break
732 break
734 lineno = lineno + 1
733 lineno = lineno + 1
735 while i < len(statements) and statements[i] < lineno:
734 while i < len(statements) and statements[i] < lineno:
736 i = i + 1
735 i = i + 1
737 while j < len(missing) and missing[j] < lineno:
736 while j < len(missing) and missing[j] < lineno:
738 j = j + 1
737 j = j + 1
739 if i < len(statements) and statements[i] == lineno:
738 if i < len(statements) and statements[i] == lineno:
740 covered = j >= len(missing) or missing[j] > lineno
739 covered = j >= len(missing) or missing[j] > lineno
741 if self.blank_re.match(line):
740 if self.blank_re.match(line):
742 dest.write(' ')
741 dest.write(' ')
743 elif self.else_re.match(line):
742 elif self.else_re.match(line):
744 # Special logic for lines containing only 'else:'.
743 # Special logic for lines containing only 'else:'.
745 # See [GDR 2001-12-04b, 3.2].
744 # See [GDR 2001-12-04b, 3.2].
746 if i >= len(statements) and j >= len(missing):
745 if i >= len(statements) and j >= len(missing):
747 dest.write('! ')
746 dest.write('! ')
748 elif i >= len(statements) or j >= len(missing):
747 elif i >= len(statements) or j >= len(missing):
749 dest.write('> ')
748 dest.write('> ')
750 elif statements[i] == missing[j]:
749 elif statements[i] == missing[j]:
751 dest.write('! ')
750 dest.write('! ')
752 else:
751 else:
753 dest.write('> ')
752 dest.write('> ')
754 elif lineno in excluded:
753 elif lineno in excluded:
755 dest.write('- ')
754 dest.write('- ')
756 elif covered:
755 elif covered:
757 dest.write('> ')
756 dest.write('> ')
758 else:
757 else:
759 dest.write('! ')
758 dest.write('! ')
760 dest.write(line)
759 dest.write(line)
761 source.close()
760 source.close()
762 dest.close()
761 dest.close()
763
762
764 # Singleton object.
763 # Singleton object.
765 the_coverage = coverage()
764 the_coverage = coverage()
766
765
767 # Module functions call methods in the singleton object.
766 # Module functions call methods in the singleton object.
768 def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
767 def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
769 def start(*args, **kw): return the_coverage.start(*args, **kw)
768 def start(*args, **kw): return the_coverage.start(*args, **kw)
770 def stop(*args, **kw): return the_coverage.stop(*args, **kw)
769 def stop(*args, **kw): return the_coverage.stop(*args, **kw)
771 def erase(*args, **kw): return the_coverage.erase(*args, **kw)
770 def erase(*args, **kw): return the_coverage.erase(*args, **kw)
772 def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw)
771 def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw)
773 def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw)
772 def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw)
774 def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
773 def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
775 def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
774 def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
776 def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
775 def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
777 def report(*args, **kw): return the_coverage.report(*args, **kw)
776 def report(*args, **kw): return the_coverage.report(*args, **kw)
778 def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
777 def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
779 def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
778 def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
780
779
781 # Save coverage data when Python exits. (The atexit module wasn't
780 # 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
781 # introduced until Python 2.0, so use sys.exitfunc when it's not
783 # available.)
782 # available.)
784 try:
783 try:
785 import atexit
784 import atexit
786 atexit.register(the_coverage.save)
785 atexit.register(the_coverage.save)
787 except ImportError:
786 except ImportError:
788 sys.exitfunc = the_coverage.save
787 sys.exitfunc = the_coverage.save
789
788
790 # Command-line interface.
789 # Command-line interface.
791 if __name__ == '__main__':
790 if __name__ == '__main__':
792 the_coverage.command_line()
791 the_coverage.command_line()
793
792
794
793
795 # A. REFERENCES
794 # A. REFERENCES
796 #
795 #
797 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
796 # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
798 # Ravenbrook Limited; 2001-12-04;
797 # Ravenbrook Limited; 2001-12-04;
799 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
798 # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
800 #
799 #
801 # [GDR 2001-12-04b] "Statement coverage for Python: design and
800 # [GDR 2001-12-04b] "Statement coverage for Python: design and
802 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
801 # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
803 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
802 # <http://www.nedbatchelder.com/code/modules/rees-design.html>.
804 #
803 #
805 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
804 # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
806 # Guide van Rossum; 2001-07-20;
805 # Guide van Rossum; 2001-07-20;
807 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
806 # <http://www.python.org/doc/2.1.1/ref/ref.html>.
808 #
807 #
809 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
808 # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
810 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
809 # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
811 #
810 #
812 #
811 #
813 # B. DOCUMENT HISTORY
812 # B. DOCUMENT HISTORY
814 #
813 #
815 # 2001-12-04 GDR Created.
814 # 2001-12-04 GDR Created.
816 #
815 #
817 # 2001-12-06 GDR Added command-line interface and source code
816 # 2001-12-06 GDR Added command-line interface and source code
818 # annotation.
817 # annotation.
819 #
818 #
820 # 2001-12-09 GDR Moved design and interface to separate documents.
819 # 2001-12-09 GDR Moved design and interface to separate documents.
821 #
820 #
822 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
821 # 2001-12-10 GDR Open cache file as binary on Windows. Allow
823 # simultaneous -e and -x, or -a and -r.
822 # simultaneous -e and -x, or -a and -r.
824 #
823 #
825 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
824 # 2001-12-12 GDR Added command-line help. Cache analysis so that it
826 # only needs to be done once when you specify -a and -r.
825 # only needs to be done once when you specify -a and -r.
827 #
826 #
828 # 2001-12-13 GDR Improved speed while recording. Portable between
827 # 2001-12-13 GDR Improved speed while recording. Portable between
829 # Python 1.5.2 and 2.1.1.
828 # Python 1.5.2 and 2.1.1.
830 #
829 #
831 # 2002-01-03 GDR Module-level functions work correctly.
830 # 2002-01-03 GDR Module-level functions work correctly.
832 #
831 #
833 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
832 # 2002-01-07 GDR Update sys.path when running a file with the -x option,
834 # so that it matches the value the program would get if it were run on
833 # so that it matches the value the program would get if it were run on
835 # its own.
834 # its own.
836 #
835 #
837 # 2004-12-12 NMB Significant code changes.
836 # 2004-12-12 NMB Significant code changes.
838 # - Finding executable statements has been rewritten so that docstrings and
837 # - Finding executable statements has been rewritten so that docstrings and
839 # other quirks of Python execution aren't mistakenly identified as missing
838 # other quirks of Python execution aren't mistakenly identified as missing
840 # lines.
839 # lines.
841 # - Lines can be excluded from consideration, even entire suites of lines.
840 # - Lines can be excluded from consideration, even entire suites of lines.
842 # - The filesystem cache of covered lines can be disabled programmatically.
841 # - The filesystem cache of covered lines can be disabled programmatically.
843 # - Modernized the code.
842 # - Modernized the code.
844 #
843 #
845 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
844 # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
846 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
845 # and add 'analysis2'. Add a global for 'annotate', and factor it, adding
847 # 'annotate_file'.
846 # 'annotate_file'.
848 #
847 #
849 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
848 # 2004-12-31 NMB Allow for keyword arguments in the module global functions.
850 # Thanks, Allen.
849 # Thanks, Allen.
851 #
850 #
852 # 2005-12-02 NMB Call threading.settrace so that all threads are measured.
851 # 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
852 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be
854 # captured to a different destination.
853 # captured to a different destination.
855 #
854 #
856 # 2005-12-03 NMB coverage.py can now measure itself.
855 # 2005-12-03 NMB coverage.py can now measure itself.
857 #
856 #
858 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
857 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
859 # and sorting and omitting files to report on.
858 # and sorting and omitting files to report on.
860 #
859 #
861 # C. COPYRIGHT AND LICENCE
860 # C. COPYRIGHT AND LICENCE
862 #
861 #
863 # Copyright 2001 Gareth Rees. All rights reserved.
862 # Copyright 2001 Gareth Rees. All rights reserved.
864 # Copyright 2004-2005 Ned Batchelder. All rights reserved.
863 # Copyright 2004-2005 Ned Batchelder. All rights reserved.
865 #
864 #
866 # Redistribution and use in source and binary forms, with or without
865 # Redistribution and use in source and binary forms, with or without
867 # modification, are permitted provided that the following conditions are
866 # modification, are permitted provided that the following conditions are
868 # met:
867 # met:
869 #
868 #
870 # 1. Redistributions of source code must retain the above copyright
869 # 1. Redistributions of source code must retain the above copyright
871 # notice, this list of conditions and the following disclaimer.
870 # notice, this list of conditions and the following disclaimer.
872 #
871 #
873 # 2. Redistributions in binary form must reproduce the above copyright
872 # 2. Redistributions in binary form must reproduce the above copyright
874 # notice, this list of conditions and the following disclaimer in the
873 # notice, this list of conditions and the following disclaimer in the
875 # documentation and/or other materials provided with the
874 # documentation and/or other materials provided with the
876 # distribution.
875 # distribution.
877 #
876 #
878 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
877 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
879 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
878 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
880 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
879 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
881 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
880 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
882 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
881 # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
883 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
882 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
884 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
883 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
885 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
884 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
886 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
885 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
887 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
886 # 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
887 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
889 # DAMAGE.
888 # DAMAGE.
890 #
889 #
891 # $Id: coverage.py 26 2005-12-04 18:42:44Z ned $
890 # $Id: coverage.py 26 2005-12-04 18:42:44Z ned $
General Comments 0
You need to be logged in to leave comments. Login now