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