85 compiler.visitor.ASTVisitor.__init__(self) |
85 compiler.visitor.ASTVisitor.__init__(self) |
86 self.statements = statements |
86 self.statements = statements |
87 self.excluded = excluded |
87 self.excluded = excluded |
88 self.suite_spots = suite_spots |
88 self.suite_spots = suite_spots |
89 self.excluding_suite = 0 |
89 self.excluding_suite = 0 |
90 |
90 |
91 def doRecursive(self, node): |
91 def doRecursive(self, node): |
92 self.recordNodeLine(node) |
92 self.recordNodeLine(node) |
93 for n in node.getChildNodes(): |
93 for n in node.getChildNodes(): |
94 self.dispatch(n) |
94 self.dispatch(n) |
95 |
95 |
96 visitStmt = visitModule = doRecursive |
96 visitStmt = visitModule = doRecursive |
97 |
97 |
98 def doCode(self, node): |
98 def doCode(self, node): |
99 if hasattr(node, 'decorators') and node.decorators: |
99 if hasattr(node, 'decorators') and node.decorators: |
100 self.dispatch(node.decorators) |
100 self.dispatch(node.decorators) |
101 self.doSuite(node, node.code) |
101 self.doSuite(node, node.code) |
102 |
102 |
103 visitFunction = visitClass = doCode |
103 visitFunction = visitClass = doCode |
104 |
104 |
105 def getFirstLine(self, node): |
105 def getFirstLine(self, node): |
106 # Find the first line in the tree node. |
106 # Find the first line in the tree node. |
107 lineno = node.lineno |
107 lineno = node.lineno |
117 # Find the first line in the tree node. |
117 # Find the first line in the tree node. |
118 lineno = node.lineno |
118 lineno = node.lineno |
119 for n in node.getChildNodes(): |
119 for n in node.getChildNodes(): |
120 lineno = max(lineno, self.getLastLine(n)) |
120 lineno = max(lineno, self.getLastLine(n)) |
121 return lineno |
121 return lineno |
122 |
122 |
123 def doStatement(self, node): |
123 def doStatement(self, node): |
124 self.recordLine(self.getFirstLine(node)) |
124 self.recordLine(self.getFirstLine(node)) |
125 |
125 |
126 visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \ |
126 visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \ |
127 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ |
127 visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ |
128 doStatement |
128 doStatement |
129 |
129 |
130 def recordNodeLine(self, node): |
130 def recordNodeLine(self, node): |
131 return self.recordLine(node.lineno) |
131 return self.recordLine(node.lineno) |
132 |
132 |
133 def recordLine(self, lineno): |
133 def recordLine(self, lineno): |
134 # Returns a bool, whether the line is included or excluded. |
134 # Returns a bool, whether the line is included or excluded. |
135 if lineno: |
135 if lineno: |
136 # Multi-line tests introducing suites have to get charged to their |
136 # Multi-line tests introducing suites have to get charged to their |
137 # keyword. |
137 # keyword. |
151 # Otherwise, this is an executable line. |
151 # Otherwise, this is an executable line. |
152 else: |
152 else: |
153 self.statements[lineno] = 1 |
153 self.statements[lineno] = 1 |
154 return 1 |
154 return 1 |
155 return 0 |
155 return 0 |
156 |
156 |
157 default = recordNodeLine |
157 default = recordNodeLine |
158 |
158 |
159 def recordAndDispatch(self, node): |
159 def recordAndDispatch(self, node): |
160 self.recordNodeLine(node) |
160 self.recordNodeLine(node) |
161 self.dispatch(node) |
161 self.dispatch(node) |
162 |
162 |
163 def doSuite(self, intro, body, exclude=0): |
163 def doSuite(self, intro, body, exclude=0): |
164 exsuite = self.excluding_suite |
164 exsuite = self.excluding_suite |
165 if exclude or (intro and not self.recordNodeLine(intro)): |
165 if exclude or (intro and not self.recordNodeLine(intro)): |
166 self.excluding_suite = 1 |
166 self.excluding_suite = 1 |
167 self.recordAndDispatch(body) |
167 self.recordAndDispatch(body) |
168 self.excluding_suite = exsuite |
168 self.excluding_suite = exsuite |
169 |
169 |
170 def doPlainWordSuite(self, prevsuite, suite): |
170 def doPlainWordSuite(self, prevsuite, suite): |
171 # Finding the exclude lines for else's is tricky, because they aren't |
171 # Finding the exclude lines for else's is tricky, because they aren't |
172 # present in the compiler parse tree. Look at the previous suite, |
172 # present in the compiler parse tree. Look at the previous suite, |
173 # and find its last line. If any line between there and the else's |
173 # and find its last line. If any line between there and the else's |
174 # first line are excluded, then we exclude the else. |
174 # first line are excluded, then we exclude the else. |
178 if self.suite_spots.has_key(l): |
178 if self.suite_spots.has_key(l): |
179 self.doSuite(None, suite, exclude=self.excluded.has_key(l)) |
179 self.doSuite(None, suite, exclude=self.excluded.has_key(l)) |
180 break |
180 break |
181 else: |
181 else: |
182 self.doSuite(None, suite) |
182 self.doSuite(None, suite) |
183 |
183 |
184 def doElse(self, prevsuite, node): |
184 def doElse(self, prevsuite, node): |
185 if node.else_: |
185 if node.else_: |
186 self.doPlainWordSuite(prevsuite, node.else_) |
186 self.doPlainWordSuite(prevsuite, node.else_) |
187 |
187 |
188 def visitFor(self, node): |
188 def visitFor(self, node): |
189 self.doSuite(node, node.body) |
189 self.doSuite(node, node.body) |
190 self.doElse(node.body, node) |
190 self.doElse(node.body, node) |
191 |
191 |
192 def visitIf(self, node): |
192 def visitIf(self, node): |
214 prev = node.body |
214 prev = node.body |
215 self.doPlainWordSuite(prev, h) |
215 self.doPlainWordSuite(prev, h) |
216 else: |
216 else: |
217 self.doSuite(a, h) |
217 self.doSuite(a, h) |
218 self.doElse(node.handlers[-1][2], node) |
218 self.doElse(node.handlers[-1][2], node) |
219 |
219 |
220 def visitTryFinally(self, node): |
220 def visitTryFinally(self, node): |
221 self.doSuite(node, node.body) |
221 self.doSuite(node, node.body) |
222 self.doPlainWordSuite(node.body, node.final) |
222 self.doPlainWordSuite(node.body, node.final) |
223 |
223 |
224 def visitGlobal(self, node): |
224 def visitGlobal(self, node): |
225 # "global" statements don't execute like others (they don't call the |
225 # "global" statements don't execute like others (they don't call the |
226 # trace function), so don't record their line numbers. |
226 # trace function), so don't record their line numbers. |
227 pass |
227 pass |
228 |
228 |
238 cache_env = "COVERAGE_FILE" |
238 cache_env = "COVERAGE_FILE" |
239 |
239 |
240 # A dictionary with an entry for (Python source file name, line number |
240 # A dictionary with an entry for (Python source file name, line number |
241 # in that file) if that line has been executed. |
241 # in that file) if that line has been executed. |
242 c = {} |
242 c = {} |
243 |
243 |
244 # A map from canonical Python source file name to a dictionary in |
244 # A map from canonical Python source file name to a dictionary in |
245 # which there's an entry for each line number that has been |
245 # which there's an entry for each line number that has been |
246 # executed. |
246 # executed. |
247 cexecuted = {} |
247 cexecuted = {} |
248 |
248 |
264 self.nesting = 0 |
264 self.nesting = 0 |
265 self.cstack = [] |
265 self.cstack = [] |
266 self.xstack = [] |
266 self.xstack = [] |
267 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep) |
267 self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep) |
268 |
268 |
269 # t(f, x, y). This method is passed to sys.settrace as a trace function. |
269 # t(f, x, y). This method is passed to sys.settrace as a trace function. |
270 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and |
270 # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and |
271 # the arguments and return value of the trace function. |
271 # the arguments and return value of the trace function. |
272 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code |
272 # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code |
273 # objects. |
273 # objects. |
274 |
274 |
275 def t(self, f, w, a): #pragma: no cover |
275 def t(self, f, w, a): #pragma: no cover |
276 #print w, f.f_code.co_filename, f.f_lineno |
276 #print w, f.f_code.co_filename, f.f_lineno |
277 if w == 'line': |
277 if w == 'line': |
278 self.c[(f.f_code.co_filename, f.f_lineno)] = 1 |
278 self.c[(f.f_code.co_filename, f.f_lineno)] = 1 |
279 for c in self.cstack: |
279 for c in self.cstack: |
280 c[(f.f_code.co_filename, f.f_lineno)] = 1 |
280 c[(f.f_code.co_filename, f.f_lineno)] = 1 |
281 return self.t |
281 return self.t |
282 |
282 |
283 def help(self, error=None): |
283 def help(self, error=None): |
284 if error: |
284 if error: |
285 print error |
285 print error |
286 print |
286 print |
287 print __doc__ |
287 print __doc__ |
328 action = settings.get('erase') or args_needed |
328 action = settings.get('erase') or args_needed |
329 if not action: |
329 if not action: |
330 self.help("You must specify at least one of -e, -x, -r, or -a.") |
330 self.help("You must specify at least one of -e, -x, -r, or -a.") |
331 if not args_needed and args: |
331 if not args_needed and args: |
332 self.help("Unexpected arguments %s." % args) |
332 self.help("Unexpected arguments %s." % args) |
333 |
333 |
334 self.get_ready() |
334 self.get_ready() |
335 self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') |
335 self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') |
336 |
336 |
337 if settings.get('erase'): |
337 if settings.get('erase'): |
338 self.erase() |
338 self.erase() |
357 if settings.get('annotate'): |
357 if settings.get('annotate'): |
358 self.annotate(args, directory, ignore_errors, omit_prefixes=omit) |
358 self.annotate(args, directory, ignore_errors, omit_prefixes=omit) |
359 |
359 |
360 def use_cache(self, usecache): |
360 def use_cache(self, usecache): |
361 self.usecache = usecache |
361 self.usecache = usecache |
362 |
362 |
363 def get_ready(self): |
363 def get_ready(self): |
364 if self.usecache and not self.cache: |
364 if self.usecache and not self.cache: |
365 self.cache = os.path.abspath(os.environ.get(self.cache_env, |
365 self.cache = os.path.abspath(os.environ.get(self.cache_env, |
366 self.cache_default)) |
366 self.cache_default)) |
367 self.restore() |
367 self.restore() |
368 self.analysis_cache = {} |
368 self.analysis_cache = {} |
369 |
369 |
370 def start(self): |
370 def start(self): |
371 self.get_ready() |
371 self.get_ready() |
372 if self.nesting == 0: #pragma: no cover |
372 if self.nesting == 0: #pragma: no cover |
373 sys.settrace(self.t) |
373 sys.settrace(self.t) |
374 if hasattr(threading, 'settrace'): |
374 if hasattr(threading, 'settrace'): |
375 threading.settrace(self.t) |
375 threading.settrace(self.t) |
376 self.nesting += 1 |
376 self.nesting += 1 |
377 |
377 |
378 def stop(self): |
378 def stop(self): |
379 self.nesting -= 1 |
379 self.nesting -= 1 |
380 if self.nesting == 0: #pragma: no cover |
380 if self.nesting == 0: #pragma: no cover |
381 sys.settrace(None) |
381 sys.settrace(None) |
382 if hasattr(threading, 'settrace'): |
382 if hasattr(threading, 'settrace'): |
396 self.exclude_re += "(" + re + ")" |
396 self.exclude_re += "(" + re + ")" |
397 |
397 |
398 def begin_recursive(self): |
398 def begin_recursive(self): |
399 self.cstack.append(self.c) |
399 self.cstack.append(self.c) |
400 self.xstack.append(self.exclude_re) |
400 self.xstack.append(self.exclude_re) |
401 |
401 |
402 def end_recursive(self): |
402 def end_recursive(self): |
403 self.c = self.cstack.pop() |
403 self.c = self.cstack.pop() |
404 self.exclude_re = self.xstack.pop() |
404 self.exclude_re = self.xstack.pop() |
405 |
405 |
406 # save(). Save coverage data to the coverage cache. |
406 # save(). Save coverage data to the coverage cache. |
450 break |
450 break |
451 cf = os.path.normcase(os.path.abspath(f)) |
451 cf = os.path.normcase(os.path.abspath(f)) |
452 self.canonical_filename_cache[filename] = cf |
452 self.canonical_filename_cache[filename] = cf |
453 return self.canonical_filename_cache[filename] |
453 return self.canonical_filename_cache[filename] |
454 |
454 |
455 # canonicalize_filenames(). Copy results from "c" to "cexecuted", |
455 # canonicalize_filenames(). Copy results from "c" to "cexecuted", |
456 # canonicalizing filenames on the way. Clear the "c" map. |
456 # canonicalizing filenames on the way. Clear the "c" map. |
457 |
457 |
458 def canonicalize_filenames(self): |
458 def canonicalize_filenames(self): |
459 for filename, lineno in self.c.keys(): |
459 for filename, lineno in self.c.keys(): |
460 f = self.canonical_filename(filename) |
460 f = self.canonical_filename(filename) |
548 excluded[i+1] = 1 |
548 excluded[i+1] = 1 |
549 |
549 |
550 import parser |
550 import parser |
551 tree = parser.suite(text+'\n\n').totuple(1) |
551 tree = parser.suite(text+'\n\n').totuple(1) |
552 self.get_suite_spots(tree, suite_spots) |
552 self.get_suite_spots(tree, suite_spots) |
553 |
553 |
554 # Use the compiler module to parse the text and find the executable |
554 # Use the compiler module to parse the text and find the executable |
555 # statements. We add newlines to be impervious to final partial lines. |
555 # statements. We add newlines to be impervious to final partial lines. |
556 statements = {} |
556 statements = {} |
557 ast = compiler.parse(text+'\n\n') |
557 ast = compiler.parse(text+'\n\n') |
558 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) |
558 visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) |
711 except KeyboardInterrupt: |
711 except KeyboardInterrupt: |
712 raise |
712 raise |
713 except: |
713 except: |
714 if not ignore_errors: |
714 if not ignore_errors: |
715 raise |
715 raise |
716 |
716 |
717 def annotate_file(self, filename, statements, excluded, missing, directory=None): |
717 def annotate_file(self, filename, statements, excluded, missing, directory=None): |
718 source = open(filename, 'r') |
718 source = open(filename, 'r') |
719 if directory: |
719 if directory: |
720 dest_file = os.path.join(directory, |
720 dest_file = os.path.join(directory, |
721 os.path.basename(filename) |
721 os.path.basename(filename) |
739 if i < len(statements) and statements[i] == lineno: |
739 if i < len(statements) and statements[i] == lineno: |
740 covered = j >= len(missing) or missing[j] > lineno |
740 covered = j >= len(missing) or missing[j] > lineno |
741 if self.blank_re.match(line): |
741 if self.blank_re.match(line): |
742 dest.write(' ') |
742 dest.write(' ') |
743 elif self.else_re.match(line): |
743 elif self.else_re.match(line): |
744 # Special logic for lines containing only 'else:'. |
744 # Special logic for lines containing only 'else:'. |
745 # See [GDR 2001-12-04b, 3.2]. |
745 # See [GDR 2001-12-04b, 3.2]. |
746 if i >= len(statements) and j >= len(missing): |
746 if i >= len(statements) and j >= len(missing): |
747 dest.write('! ') |
747 dest.write('! ') |
748 elif i >= len(statements) or j >= len(missing): |
748 elif i >= len(statements) or j >= len(missing): |
749 dest.write('> ') |
749 dest.write('> ') |
848 # |
848 # |
849 # 2004-12-31 NMB Allow for keyword arguments in the module global functions. |
849 # 2004-12-31 NMB Allow for keyword arguments in the module global functions. |
850 # Thanks, Allen. |
850 # Thanks, Allen. |
851 # |
851 # |
852 # 2005-12-02 NMB Call threading.settrace so that all threads are measured. |
852 # 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 |
853 # Thanks Martin Fuzzey. Add a file argument to report so that reports can be |
854 # captured to a different destination. |
854 # captured to a different destination. |
855 # |
855 # |
856 # 2005-12-03 NMB coverage.py can now measure itself. |
856 # 2005-12-03 NMB coverage.py can now measure itself. |
857 # |
857 # |
858 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, |
858 # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, |