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