63 tracing functions breaks the faster trace function. |
79 tracing functions breaks the faster trace function. |
64 |
80 |
65 If `branch` is true, then branch coverage will be measured in addition |
81 If `branch` is true, then branch coverage will be measured in addition |
66 to the usual statement coverage. |
82 to the usual statement coverage. |
67 |
83 |
68 `config_file` determines what config file to read. If it is a string, |
84 `config_file` determines what configuration file to read: |
69 it is the name of the config file to read. If it is True, then a |
85 |
70 standard file is read (".coveragerc"). If it is False, then no file is |
86 * If it is ".coveragerc", it is interpreted as if it were True, |
71 read. |
87 for backward compatibility. |
|
88 |
|
89 * If it is a string, it is the name of the file to read. If the |
|
90 file can't be read, it is an error. |
|
91 |
|
92 * If it is True, then a few standard files names are tried |
|
93 (".coveragerc", "setup.cfg"). It is not an error for these files |
|
94 to not be found. |
|
95 |
|
96 * If it is False, then no configuration file is read. |
72 |
97 |
73 `source` is a list of file paths or package names. Only code located |
98 `source` is a list of file paths or package names. Only code located |
74 in the trees indicated by the file paths or package names will be |
99 in the trees indicated by the file paths or package names will be |
75 measured. |
100 measured. |
76 |
101 |
77 `include` and `omit` are lists of filename patterns. Files that match |
102 `include` and `omit` are lists of file name patterns. Files that match |
78 `include` will be measured, files that match `omit` will not. Each |
103 `include` will be measured, files that match `omit` will not. Each |
79 will also accept a single string argument. |
104 will also accept a single string argument. |
80 |
105 |
81 `debug` is a list of strings indicating what debugging information is |
106 `debug` is a list of strings indicating what debugging information is |
82 desired. `debug_file` is the file to write debug messages to, |
107 desired. |
83 defaulting to stderr. |
108 |
84 |
109 `concurrency` is a string indicating the concurrency library being used |
85 """ |
110 in the measured code. Without this, coverage.py will get incorrect |
86 from . import __version__ |
111 results. Valid strings are "greenlet", "eventlet", "gevent", or |
87 |
112 "thread" (the default). |
88 # A record of all the warnings that have been issued. |
113 |
89 self._warnings = [] |
114 .. versionadded:: 4.0 |
90 |
115 The `concurrency` parameter. |
|
116 |
|
117 """ |
91 # Build our configuration from a number of sources: |
118 # Build our configuration from a number of sources: |
92 # 1: defaults: |
119 # 1: defaults: |
93 self.config = CoverageConfig() |
120 self.config = CoverageConfig() |
94 |
121 |
95 # 2: from the coveragerc file: |
122 # 2: from the rcfile, .coveragerc or setup.cfg file: |
96 if config_file: |
123 if config_file: |
97 if config_file is True: |
124 did_read_rc = False |
|
125 # Some API users were specifying ".coveragerc" to mean the same as |
|
126 # True, so make it so. |
|
127 if config_file == ".coveragerc": |
|
128 config_file = True |
|
129 specified_file = (config_file is not True) |
|
130 if not specified_file: |
98 config_file = ".coveragerc" |
131 config_file = ".coveragerc" |
99 try: |
132 |
100 self.config.from_file(config_file) |
133 did_read_rc = self.config.from_file(config_file) |
101 except ValueError: |
134 |
102 _, err, _ = sys.exc_info() |
135 if not did_read_rc: |
103 raise CoverageException( |
136 if specified_file: |
104 "Couldn't read config file %s: %s" % (config_file, err) |
137 raise CoverageException( |
105 ) |
138 "Couldn't read '%s' as a config file" % config_file |
|
139 ) |
|
140 self.config.from_file("setup.cfg", section_prefix="coverage:") |
106 |
141 |
107 # 3: from environment variables: |
142 # 3: from environment variables: |
108 self.config.from_environment('COVERAGE_OPTIONS') |
|
109 env_data_file = os.environ.get('COVERAGE_FILE') |
143 env_data_file = os.environ.get('COVERAGE_FILE') |
110 if env_data_file: |
144 if env_data_file: |
111 self.config.data_file = env_data_file |
145 self.config.data_file = env_data_file |
|
146 debugs = os.environ.get('COVERAGE_DEBUG') |
|
147 if debugs: |
|
148 self.config.debug.extend(debugs.split(",")) |
112 |
149 |
113 # 4: from constructor arguments: |
150 # 4: from constructor arguments: |
114 self.config.from_args( |
151 self.config.from_args( |
115 data_file=data_file, cover_pylib=cover_pylib, timid=timid, |
152 data_file=data_file, cover_pylib=cover_pylib, timid=timid, |
116 branch=branch, parallel=bool_or_none(data_suffix), |
153 branch=branch, parallel=bool_or_none(data_suffix), |
117 source=source, omit=omit, include=include, debug=debug, |
154 source=source, omit=omit, include=include, debug=debug, |
|
155 concurrency=concurrency, |
118 ) |
156 ) |
119 |
157 |
120 # Create and configure the debugging controller. |
158 self._debug_file = None |
121 self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) |
159 self._auto_data = auto_data |
122 |
160 self._data_suffix = data_suffix |
123 self.auto_data = auto_data |
161 |
124 |
162 # The matchers for _should_trace. |
125 # _exclude_re is a dict mapping exclusion list names to compiled |
163 self.source_match = None |
|
164 self.source_pkgs_match = None |
|
165 self.pylib_match = self.cover_match = None |
|
166 self.include_match = self.omit_match = None |
|
167 |
|
168 # Is it ok for no data to be collected? |
|
169 self._warn_no_data = True |
|
170 self._warn_unimported_source = True |
|
171 |
|
172 # A record of all the warnings that have been issued. |
|
173 self._warnings = [] |
|
174 |
|
175 # Other instance attributes, set later. |
|
176 self.omit = self.include = self.source = None |
|
177 self.source_pkgs = None |
|
178 self.data = self.data_files = self.collector = None |
|
179 self.plugins = None |
|
180 self.pylib_dirs = self.cover_dirs = None |
|
181 self.data_suffix = self.run_suffix = None |
|
182 self._exclude_re = None |
|
183 self.debug = None |
|
184 |
|
185 # State machine variables: |
|
186 # Have we initialized everything? |
|
187 self._inited = False |
|
188 # Have we started collecting and not stopped it? |
|
189 self._started = False |
|
190 # Have we measured some data and not harvested it? |
|
191 self._measured = False |
|
192 |
|
193 def _init(self): |
|
194 """Set all the initial state. |
|
195 |
|
196 This is called by the public methods to initialize state. This lets us |
|
197 construct a :class:`Coverage` object, then tweak its state before this |
|
198 function is called. |
|
199 |
|
200 """ |
|
201 if self._inited: |
|
202 return |
|
203 |
|
204 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE |
|
205 # is an environment variable, the name of a file to append debug logs |
|
206 # to. |
|
207 if self._debug_file is None: |
|
208 debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE") |
|
209 if debug_file_name: |
|
210 self._debug_file = open(debug_file_name, "a") |
|
211 else: |
|
212 self._debug_file = sys.stderr |
|
213 self.debug = DebugControl(self.config.debug, self._debug_file) |
|
214 |
|
215 # Load plugins |
|
216 self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug) |
|
217 |
|
218 # _exclude_re is a dict that maps exclusion list names to compiled |
126 # regexes. |
219 # regexes. |
127 self._exclude_re = {} |
220 self._exclude_re = {} |
128 self._exclude_regex_stale() |
221 self._exclude_regex_stale() |
129 |
222 |
130 self.file_locator = FileLocator() |
223 files.set_relative_directory() |
131 |
224 |
132 # The source argument can be directories or package names. |
225 # The source argument can be directories or package names. |
133 self.source = [] |
226 self.source = [] |
134 self.source_pkgs = [] |
227 self.source_pkgs = [] |
135 for src in self.config.source or []: |
228 for src in self.config.source or []: |
136 if os.path.exists(src): |
229 if os.path.exists(src): |
137 self.source.append(self.file_locator.canonical_filename(src)) |
230 self.source.append(files.canonical_filename(src)) |
138 else: |
231 else: |
139 self.source_pkgs.append(src) |
232 self.source_pkgs.append(src) |
140 |
233 |
141 self.omit = prep_patterns(self.config.omit) |
234 self.omit = prep_patterns(self.config.omit) |
142 self.include = prep_patterns(self.config.include) |
235 self.include = prep_patterns(self.config.include) |
143 |
236 |
|
237 concurrency = self.config.concurrency |
|
238 if concurrency == "multiprocessing": |
|
239 patch_multiprocessing() |
|
240 concurrency = None |
|
241 |
144 self.collector = Collector( |
242 self.collector = Collector( |
145 self._should_trace, timid=self.config.timid, |
243 should_trace=self._should_trace, |
146 branch=self.config.branch, warn=self._warn |
244 check_include=self._check_include_omit_etc, |
|
245 timid=self.config.timid, |
|
246 branch=self.config.branch, |
|
247 warn=self._warn, |
|
248 concurrency=concurrency, |
147 ) |
249 ) |
|
250 |
|
251 # Early warning if we aren't going to be able to support plugins. |
|
252 if self.plugins.file_tracers and not self.collector.supports_plugins: |
|
253 self._warn( |
|
254 "Plugin file tracers (%s) aren't supported with %s" % ( |
|
255 ", ".join( |
|
256 plugin._coverage_plugin_name |
|
257 for plugin in self.plugins.file_tracers |
|
258 ), |
|
259 self.collector.tracer_name(), |
|
260 ) |
|
261 ) |
|
262 for plugin in self.plugins.file_tracers: |
|
263 plugin._coverage_enabled = False |
148 |
264 |
149 # Suffixes are a bit tricky. We want to use the data suffix only when |
265 # Suffixes are a bit tricky. We want to use the data suffix only when |
150 # collecting data, not when combining data. So we save it as |
266 # collecting data, not when combining data. So we save it as |
151 # `self.run_suffix` now, and promote it to `self.data_suffix` if we |
267 # `self.run_suffix` now, and promote it to `self.data_suffix` if we |
152 # find that we are collecting data later. |
268 # find that we are collecting data later. |
153 if data_suffix or self.config.parallel: |
269 if self._data_suffix or self.config.parallel: |
154 if not isinstance(data_suffix, string_class): |
270 if not isinstance(self._data_suffix, string_class): |
155 # if data_suffix=True, use .machinename.pid.random |
271 # if data_suffix=True, use .machinename.pid.random |
156 data_suffix = True |
272 self._data_suffix = True |
157 else: |
273 else: |
158 data_suffix = None |
274 self._data_suffix = None |
159 self.data_suffix = None |
275 self.data_suffix = None |
160 self.run_suffix = data_suffix |
276 self.run_suffix = self._data_suffix |
161 |
277 |
162 # Create the data file. We do this at construction time so that the |
278 # Create the data file. We do this at construction time so that the |
163 # data file will be written into the directory where the process |
279 # data file will be written into the directory where the process |
164 # started rather than wherever the process eventually chdir'd to. |
280 # started rather than wherever the process eventually chdir'd to. |
165 self.data = CoverageData( |
281 self.data = CoverageData(debug=self.debug) |
166 basename=self.config.data_file, |
282 self.data_files = CoverageDataFiles(basename=self.config.data_file) |
167 collector="coverage v%s" % __version__, |
283 |
168 debug=self.debug, |
284 # The directories for files considered "installed with the interpreter". |
169 ) |
285 self.pylib_dirs = set() |
170 |
|
171 # The dirs for files considered "installed with the interpreter". |
|
172 self.pylib_dirs = [] |
|
173 if not self.config.cover_pylib: |
286 if not self.config.cover_pylib: |
174 # Look at where some standard modules are located. That's the |
287 # Look at where some standard modules are located. That's the |
175 # indication for "installed with the interpreter". In some |
288 # indication for "installed with the interpreter". In some |
176 # environments (virtualenv, for example), these modules may be |
289 # environments (virtualenv, for example), these modules may be |
177 # spread across a few locations. Look at all the candidate modules |
290 # spread across a few locations. Look at all the candidate modules |
178 # we've imported, and take all the different ones. |
291 # we've imported, and take all the different ones. |
179 for m in (atexit, os, random, socket, _structseq): |
292 for m in (atexit, inspect, os, platform, _structseq, traceback): |
180 if m is not None and hasattr(m, "__file__"): |
293 if m is not None and hasattr(m, "__file__"): |
181 m_dir = self._canonical_dir(m) |
294 self.pylib_dirs.add(self._canonical_dir(m)) |
182 if m_dir not in self.pylib_dirs: |
295 if _structseq and not hasattr(_structseq, '__file__'): |
183 self.pylib_dirs.append(m_dir) |
296 # PyPy 2.4 has no __file__ in the builtin modules, but the code |
184 |
297 # objects still have the file names. So dig into one to find |
185 # To avoid tracing the coverage code itself, we skip anything located |
298 # the path to exclude. |
186 # where we are. |
299 structseq_new = _structseq.structseq_new |
187 self.cover_dir = self._canonical_dir(__file__) |
300 try: |
188 |
301 structseq_file = structseq_new.func_code.co_filename |
189 # The matchers for _should_trace. |
302 except AttributeError: |
190 self.source_match = None |
303 structseq_file = structseq_new.__code__.co_filename |
191 self.pylib_match = self.cover_match = None |
304 self.pylib_dirs.add(self._canonical_dir(structseq_file)) |
192 self.include_match = self.omit_match = None |
305 |
|
306 # To avoid tracing the coverage.py code itself, we skip anything |
|
307 # located where we are. |
|
308 self.cover_dirs = [self._canonical_dir(__file__)] |
|
309 if env.TESTING: |
|
310 # When testing, we use PyContracts, which should be considered |
|
311 # part of coverage.py, and it uses six. Exclude those directories |
|
312 # just as we exclude ourselves. |
|
313 import contracts, six |
|
314 for mod in [contracts, six]: |
|
315 self.cover_dirs.append(self._canonical_dir(mod)) |
193 |
316 |
194 # Set the reporting precision. |
317 # Set the reporting precision. |
195 Numbers.set_precision(self.config.precision) |
318 Numbers.set_precision(self.config.precision) |
196 |
319 |
197 # Is it ok for no data to be collected? |
|
198 self._warn_no_data = True |
|
199 self._warn_unimported_source = True |
|
200 |
|
201 # State machine variables: |
|
202 # Have we started collecting and not stopped it? |
|
203 self._started = False |
|
204 # Have we measured some data and not harvested it? |
|
205 self._measured = False |
|
206 |
|
207 atexit.register(self._atexit) |
320 atexit.register(self._atexit) |
|
321 |
|
322 self._inited = True |
|
323 |
|
324 # Create the matchers we need for _should_trace |
|
325 if self.source or self.source_pkgs: |
|
326 self.source_match = TreeMatcher(self.source) |
|
327 self.source_pkgs_match = ModuleMatcher(self.source_pkgs) |
|
328 else: |
|
329 if self.cover_dirs: |
|
330 self.cover_match = TreeMatcher(self.cover_dirs) |
|
331 if self.pylib_dirs: |
|
332 self.pylib_match = TreeMatcher(self.pylib_dirs) |
|
333 if self.include: |
|
334 self.include_match = FnmatchMatcher(self.include) |
|
335 if self.omit: |
|
336 self.omit_match = FnmatchMatcher(self.omit) |
|
337 |
|
338 # The user may want to debug things, show info if desired. |
|
339 wrote_any = False |
|
340 if self.debug.should('config'): |
|
341 config_info = sorted(self.config.__dict__.items()) |
|
342 self.debug.write_formatted_info("config", config_info) |
|
343 wrote_any = True |
|
344 |
|
345 if self.debug.should('sys'): |
|
346 self.debug.write_formatted_info("sys", self.sys_info()) |
|
347 for plugin in self.plugins: |
|
348 header = "sys: " + plugin._coverage_plugin_name |
|
349 info = plugin.sys_info() |
|
350 self.debug.write_formatted_info(header, info) |
|
351 wrote_any = True |
|
352 |
|
353 if wrote_any: |
|
354 self.debug.write_formatted_info("end", ()) |
208 |
355 |
209 def _canonical_dir(self, morf): |
356 def _canonical_dir(self, morf): |
210 """Return the canonical directory of the module or file `morf`.""" |
357 """Return the canonical directory of the module or file `morf`.""" |
211 return os.path.split(CodeUnit(morf, self.file_locator).filename)[0] |
358 morf_filename = PythonFileReporter(morf, self).filename |
|
359 return os.path.split(morf_filename)[0] |
212 |
360 |
213 def _source_for_file(self, filename): |
361 def _source_for_file(self, filename): |
214 """Return the source file for `filename`.""" |
362 """Return the source file for `filename`. |
215 if not filename.endswith(".py"): |
363 |
216 if filename.endswith((".pyc", ".pyo")): |
364 Given a file name being traced, return the best guess as to the source |
217 filename = filename[:-1] |
365 file to attribute it to. |
218 elif filename.endswith("$py.class"): # jython |
366 |
219 filename = filename[:-9] + ".py" |
367 """ |
|
368 if filename.endswith(".py"): |
|
369 # .py files are themselves source files. |
|
370 return filename |
|
371 |
|
372 elif filename.endswith((".pyc", ".pyo")): |
|
373 # Bytecode files probably have source files near them. |
|
374 py_filename = filename[:-1] |
|
375 if os.path.exists(py_filename): |
|
376 # Found a .py file, use that. |
|
377 return py_filename |
|
378 if env.WINDOWS: |
|
379 # On Windows, it could be a .pyw file. |
|
380 pyw_filename = py_filename + "w" |
|
381 if os.path.exists(pyw_filename): |
|
382 return pyw_filename |
|
383 # Didn't find source, but it's probably the .py file we want. |
|
384 return py_filename |
|
385 |
|
386 elif filename.endswith("$py.class"): |
|
387 # Jython is easy to guess. |
|
388 return filename[:-9] + ".py" |
|
389 |
|
390 # No idea, just use the file name as-is. |
220 return filename |
391 return filename |
221 |
392 |
222 def _should_trace_with_reason(self, filename, frame): |
393 def _name_for_module(self, module_globals, filename): |
|
394 """Get the name of the module for a set of globals and file name. |
|
395 |
|
396 For configurability's sake, we allow __main__ modules to be matched by |
|
397 their importable name. |
|
398 |
|
399 If loaded via runpy (aka -m), we can usually recover the "original" |
|
400 full dotted module name, otherwise, we resort to interpreting the |
|
401 file name to get the module's name. In the case that the module name |
|
402 can't be determined, None is returned. |
|
403 |
|
404 """ |
|
405 dunder_name = module_globals.get('__name__', None) |
|
406 |
|
407 if isinstance(dunder_name, str) and dunder_name != '__main__': |
|
408 # This is the usual case: an imported module. |
|
409 return dunder_name |
|
410 |
|
411 loader = module_globals.get('__loader__', None) |
|
412 for attrname in ('fullname', 'name'): # attribute renamed in py3.2 |
|
413 if hasattr(loader, attrname): |
|
414 fullname = getattr(loader, attrname) |
|
415 else: |
|
416 continue |
|
417 |
|
418 if isinstance(fullname, str) and fullname != '__main__': |
|
419 # Module loaded via: runpy -m |
|
420 return fullname |
|
421 |
|
422 # Script as first argument to Python command line. |
|
423 inspectedname = inspect.getmodulename(filename) |
|
424 if inspectedname is not None: |
|
425 return inspectedname |
|
426 else: |
|
427 return dunder_name |
|
428 |
|
429 def _should_trace_internal(self, filename, frame): |
223 """Decide whether to trace execution in `filename`, with a reason. |
430 """Decide whether to trace execution in `filename`, with a reason. |
224 |
431 |
225 This function is called from the trace function. As each new file name |
432 This function is called from the trace function. As each new file name |
226 is encountered, this function determines whether it is traced or not. |
433 is encountered, this function determines whether it is traced or not. |
227 |
434 |
228 Returns a pair of values: the first indicates whether the file should |
435 Returns a FileDisposition object. |
229 be traced: it's a canonicalized filename if it should be traced, None |
436 |
230 if it should not. The second value is a string, the resason for the |
437 """ |
231 decision. |
438 original_filename = filename |
232 |
439 disp = _disposition_init(self.collector.file_disposition_class, filename) |
233 """ |
440 |
234 if not filename: |
441 def nope(disp, reason): |
235 # Empty string is pretty useless |
442 """Simple helper to make it easy to return NO.""" |
236 return None, "empty string isn't a filename" |
443 disp.trace = False |
237 |
444 disp.reason = reason |
238 if filename.startswith('<'): |
445 return disp |
239 # Lots of non-file execution is represented with artificial |
446 |
240 # filenames like "<string>", "<doctest readme.txt[0]>", or |
447 # Compiled Python files have two file names: frame.f_code.co_filename is |
241 # "<exec_function>". Don't ever trace these executions, since we |
448 # the file name at the time the .pyc was compiled. The second name is |
242 # can't do anything with the data later anyway. |
|
243 return None, "not a real filename" |
|
244 |
|
245 self._check_for_packages() |
|
246 |
|
247 # Compiled Python files have two filenames: frame.f_code.co_filename is |
|
248 # the filename at the time the .pyc was compiled. The second name is |
|
249 # __file__, which is where the .pyc was actually loaded from. Since |
449 # __file__, which is where the .pyc was actually loaded from. Since |
250 # .pyc files can be moved after compilation (for example, by being |
450 # .pyc files can be moved after compilation (for example, by being |
251 # installed), we look for __file__ in the frame and prefer it to the |
451 # installed), we look for __file__ in the frame and prefer it to the |
252 # co_filename value. |
452 # co_filename value. |
253 dunder_file = frame.f_globals.get('__file__') |
453 dunder_file = frame.f_globals.get('__file__') |
254 if dunder_file: |
454 if dunder_file: |
255 filename = self._source_for_file(dunder_file) |
455 filename = self._source_for_file(dunder_file) |
|
456 if original_filename and not original_filename.startswith('<'): |
|
457 orig = os.path.basename(original_filename) |
|
458 if orig != os.path.basename(filename): |
|
459 # Files shouldn't be renamed when moved. This happens when |
|
460 # exec'ing code. If it seems like something is wrong with |
|
461 # the frame's file name, then just use the original. |
|
462 filename = original_filename |
|
463 |
|
464 if not filename: |
|
465 # Empty string is pretty useless. |
|
466 return nope(disp, "empty string isn't a file name") |
|
467 |
|
468 if filename.startswith('memory:'): |
|
469 return nope(disp, "memory isn't traceable") |
|
470 |
|
471 if filename.startswith('<'): |
|
472 # Lots of non-file execution is represented with artificial |
|
473 # file names like "<string>", "<doctest readme.txt[0]>", or |
|
474 # "<exec_function>". Don't ever trace these executions, since we |
|
475 # can't do anything with the data later anyway. |
|
476 return nope(disp, "not a real file name") |
256 |
477 |
257 # Jython reports the .class file to the tracer, use the source file. |
478 # Jython reports the .class file to the tracer, use the source file. |
258 if filename.endswith("$py.class"): |
479 if filename.endswith("$py.class"): |
259 filename = filename[:-9] + ".py" |
480 filename = filename[:-9] + ".py" |
260 |
481 |
261 canonical = self.file_locator.canonical_filename(filename) |
482 canonical = files.canonical_filename(filename) |
|
483 disp.canonical_filename = canonical |
|
484 |
|
485 # Try the plugins, see if they have an opinion about the file. |
|
486 plugin = None |
|
487 for plugin in self.plugins.file_tracers: |
|
488 if not plugin._coverage_enabled: |
|
489 continue |
|
490 |
|
491 try: |
|
492 file_tracer = plugin.file_tracer(canonical) |
|
493 if file_tracer is not None: |
|
494 file_tracer._coverage_plugin = plugin |
|
495 disp.trace = True |
|
496 disp.file_tracer = file_tracer |
|
497 if file_tracer.has_dynamic_source_filename(): |
|
498 disp.has_dynamic_filename = True |
|
499 else: |
|
500 disp.source_filename = files.canonical_filename( |
|
501 file_tracer.source_filename() |
|
502 ) |
|
503 break |
|
504 except Exception: |
|
505 self._warn( |
|
506 "Disabling plugin %r due to an exception:" % ( |
|
507 plugin._coverage_plugin_name |
|
508 ) |
|
509 ) |
|
510 traceback.print_exc() |
|
511 plugin._coverage_enabled = False |
|
512 continue |
|
513 else: |
|
514 # No plugin wanted it: it's Python. |
|
515 disp.trace = True |
|
516 disp.source_filename = canonical |
|
517 |
|
518 if not disp.has_dynamic_filename: |
|
519 if not disp.source_filename: |
|
520 raise CoverageException( |
|
521 "Plugin %r didn't set source_filename for %r" % |
|
522 (plugin, disp.original_filename) |
|
523 ) |
|
524 reason = self._check_include_omit_etc_internal( |
|
525 disp.source_filename, frame, |
|
526 ) |
|
527 if reason: |
|
528 nope(disp, reason) |
|
529 |
|
530 return disp |
|
531 |
|
532 def _check_include_omit_etc_internal(self, filename, frame): |
|
533 """Check a file name against the include, omit, etc, rules. |
|
534 |
|
535 Returns a string or None. String means, don't trace, and is the reason |
|
536 why. None means no reason found to not trace. |
|
537 |
|
538 """ |
|
539 modulename = self._name_for_module(frame.f_globals, filename) |
262 |
540 |
263 # If the user specified source or include, then that's authoritative |
541 # If the user specified source or include, then that's authoritative |
264 # about the outer bound of what to measure and we don't have to apply |
542 # about the outer bound of what to measure and we don't have to apply |
265 # any canned exclusions. If they didn't, then we have to exclude the |
543 # any canned exclusions. If they didn't, then we have to exclude the |
266 # stdlib and coverage.py directories. |
544 # stdlib and coverage.py directories. |
267 if self.source_match: |
545 if self.source_match: |
268 if not self.source_match.match(canonical): |
546 if self.source_pkgs_match.match(modulename): |
269 return None, "falls outside the --source trees" |
547 if modulename in self.source_pkgs: |
|
548 self.source_pkgs.remove(modulename) |
|
549 return None # There's no reason to skip this file. |
|
550 |
|
551 if not self.source_match.match(filename): |
|
552 return "falls outside the --source trees" |
270 elif self.include_match: |
553 elif self.include_match: |
271 if not self.include_match.match(canonical): |
554 if not self.include_match.match(filename): |
272 return None, "falls outside the --include trees" |
555 return "falls outside the --include trees" |
273 else: |
556 else: |
274 # If we aren't supposed to trace installed code, then check if this |
557 # If we aren't supposed to trace installed code, then check if this |
275 # is near the Python standard library and skip it if so. |
558 # is near the Python standard library and skip it if so. |
276 if self.pylib_match and self.pylib_match.match(canonical): |
559 if self.pylib_match and self.pylib_match.match(filename): |
277 return None, "is in the stdlib" |
560 return "is in the stdlib" |
278 |
561 |
279 # We exclude the coverage code itself, since a little of it will be |
562 # We exclude the coverage.py code itself, since a little of it |
280 # measured otherwise. |
563 # will be measured otherwise. |
281 if self.cover_match and self.cover_match.match(canonical): |
564 if self.cover_match and self.cover_match.match(filename): |
282 return None, "is part of coverage.py" |
565 return "is part of coverage.py" |
283 |
566 |
284 # Check the file against the omit pattern. |
567 # Check the file against the omit pattern. |
285 if self.omit_match and self.omit_match.match(canonical): |
568 if self.omit_match and self.omit_match.match(filename): |
286 return None, "is inside an --omit pattern" |
569 return "is inside an --omit pattern" |
287 |
570 |
288 return canonical, "because we love you" |
571 # No reason found to skip this file. |
|
572 return None |
289 |
573 |
290 def _should_trace(self, filename, frame): |
574 def _should_trace(self, filename, frame): |
291 """Decide whether to trace execution in `filename`. |
575 """Decide whether to trace execution in `filename`. |
292 |
576 |
293 Calls `_should_trace_with_reason`, and returns just the decision. |
577 Calls `_should_trace_internal`, and returns the FileDisposition. |
294 |
578 |
295 """ |
579 """ |
296 canonical, reason = self._should_trace_with_reason(filename, frame) |
580 disp = self._should_trace_internal(filename, frame) |
297 if self.debug.should('trace'): |
581 if self.debug.should('trace'): |
298 if not canonical: |
582 self.debug.write(_disposition_debug_msg(disp)) |
299 msg = "Not tracing %r: %s" % (filename, reason) |
583 return disp |
|
584 |
|
585 def _check_include_omit_etc(self, filename, frame): |
|
586 """Check a file name against the include/omit/etc, rules, verbosely. |
|
587 |
|
588 Returns a boolean: True if the file should be traced, False if not. |
|
589 |
|
590 """ |
|
591 reason = self._check_include_omit_etc_internal(filename, frame) |
|
592 if self.debug.should('trace'): |
|
593 if not reason: |
|
594 msg = "Including %r" % (filename,) |
300 else: |
595 else: |
301 msg = "Tracing %r" % (filename,) |
596 msg = "Not including %r: %s" % (filename, reason) |
302 self.debug.write(msg) |
597 self.debug.write(msg) |
303 return canonical |
598 |
|
599 return not reason |
304 |
600 |
305 def _warn(self, msg): |
601 def _warn(self, msg): |
306 """Use `msg` as a warning.""" |
602 """Use `msg` as a warning.""" |
307 self._warnings.append(msg) |
603 self._warnings.append(msg) |
|
604 if self.debug.should('pid'): |
|
605 msg = "[%d] %s" % (os.getpid(), msg) |
308 sys.stderr.write("Coverage.py warning: %s\n" % msg) |
606 sys.stderr.write("Coverage.py warning: %s\n" % msg) |
309 |
607 |
310 def _check_for_packages(self): |
608 def get_option(self, option_name): |
311 """Update the source_match matcher with latest imported packages.""" |
609 """Get an option from the configuration. |
312 # Our self.source_pkgs attribute is a list of package names we want to |
610 |
313 # measure. Each time through here, we see if we've imported any of |
611 `option_name` is a colon-separated string indicating the section and |
314 # them yet. If so, we add its file to source_match, and we don't have |
612 option name. For example, the ``branch`` option in the ``[run]`` |
315 # to look for that package any more. |
613 section of the config file would be indicated with `"run:branch"`. |
316 if self.source_pkgs: |
614 |
317 found = [] |
615 Returns the value of the option. |
318 for pkg in self.source_pkgs: |
616 |
319 try: |
617 .. versionadded:: 4.0 |
320 mod = sys.modules[pkg] |
618 |
321 except KeyError: |
619 """ |
322 continue |
620 return self.config.get_option(option_name) |
323 |
621 |
324 found.append(pkg) |
622 def set_option(self, option_name, value): |
325 |
623 """Set an option in the configuration. |
326 try: |
624 |
327 pkg_file = mod.__file__ |
625 `option_name` is a colon-separated string indicating the section and |
328 except AttributeError: |
626 option name. For example, the ``branch`` option in the ``[run]`` |
329 pkg_file = None |
627 section of the config file would be indicated with ``"run:branch"``. |
330 else: |
628 |
331 d, f = os.path.split(pkg_file) |
629 `value` is the new value for the option. This should be a Python |
332 if f.startswith('__init__'): |
630 value where appropriate. For example, use True for booleans, not the |
333 # This is actually a package, return the directory. |
631 string ``"True"``. |
334 pkg_file = d |
632 |
335 else: |
633 As an example, calling:: |
336 pkg_file = self._source_for_file(pkg_file) |
634 |
337 pkg_file = self.file_locator.canonical_filename(pkg_file) |
635 cov.set_option("run:branch", True) |
338 if not os.path.exists(pkg_file): |
636 |
339 pkg_file = None |
637 has the same effect as this configuration file:: |
340 |
638 |
341 if pkg_file: |
639 [run] |
342 self.source.append(pkg_file) |
640 branch = True |
343 self.source_match.add(pkg_file) |
641 |
344 else: |
642 .. versionadded:: 4.0 |
345 self._warn("Module %s has no Python source." % pkg) |
643 |
346 |
644 """ |
347 for pkg in found: |
645 self.config.set_option(option_name, value) |
348 self.source_pkgs.remove(pkg) |
|
349 |
646 |
350 def use_cache(self, usecache): |
647 def use_cache(self, usecache): |
351 """Control the use of a data file (incorrectly called a cache). |
648 """Obsolete method.""" |
352 |
649 self._init() |
353 `usecache` is true or false, whether to read and write data on disk. |
650 if not usecache: |
354 |
651 self._warn("use_cache(False) is no longer supported.") |
355 """ |
|
356 self.data.usefile(usecache) |
|
357 |
652 |
358 def load(self): |
653 def load(self): |
359 """Load previously-collected coverage data from the data file.""" |
654 """Load previously-collected coverage data from the data file.""" |
|
655 self._init() |
360 self.collector.reset() |
656 self.collector.reset() |
361 self.data.read() |
657 self.data_files.read(self.data) |
362 |
658 |
363 def start(self): |
659 def start(self): |
364 """Start measuring code coverage. |
660 """Start measuring code coverage. |
365 |
661 |
366 Coverage measurement actually occurs in functions called after `start` |
662 Coverage measurement actually occurs in functions called after |
367 is invoked. Statements in the same scope as `start` won't be measured. |
663 :meth:`start` is invoked. Statements in the same scope as |
368 |
664 :meth:`start` won't be measured. |
369 Once you invoke `start`, you must also call `stop` eventually, or your |
665 |
370 process might not shut down cleanly. |
666 Once you invoke :meth:`start`, you must also call :meth:`stop` |
371 |
667 eventually, or your process might not shut down cleanly. |
372 """ |
668 |
|
669 """ |
|
670 self._init() |
373 if self.run_suffix: |
671 if self.run_suffix: |
374 # Calling start() means we're running code, so use the run_suffix |
672 # Calling start() means we're running code, so use the run_suffix |
375 # as the data_suffix when we eventually save the data. |
673 # as the data_suffix when we eventually save the data. |
376 self.data_suffix = self.run_suffix |
674 self.data_suffix = self.run_suffix |
377 if self.auto_data: |
675 if self._auto_data: |
378 self.load() |
676 self.load() |
379 |
|
380 # Create the matchers we need for _should_trace |
|
381 if self.source or self.source_pkgs: |
|
382 self.source_match = TreeMatcher(self.source) |
|
383 else: |
|
384 if self.cover_dir: |
|
385 self.cover_match = TreeMatcher([self.cover_dir]) |
|
386 if self.pylib_dirs: |
|
387 self.pylib_match = TreeMatcher(self.pylib_dirs) |
|
388 if self.include: |
|
389 self.include_match = FnmatchMatcher(self.include) |
|
390 if self.omit: |
|
391 self.omit_match = FnmatchMatcher(self.omit) |
|
392 |
|
393 # The user may want to debug things, show info if desired. |
|
394 if self.debug.should('config'): |
|
395 self.debug.write("Configuration values:") |
|
396 config_info = sorted(self.config.__dict__.items()) |
|
397 self.debug.write_formatted_info(config_info) |
|
398 |
|
399 if self.debug.should('sys'): |
|
400 self.debug.write("Debugging info:") |
|
401 self.debug.write_formatted_info(self.sysinfo()) |
|
402 |
677 |
403 self.collector.start() |
678 self.collector.start() |
404 self._started = True |
679 self._started = True |
405 self._measured = True |
680 self._measured = True |
406 |
681 |
407 def stop(self): |
682 def stop(self): |
408 """Stop measuring code coverage.""" |
683 """Stop measuring code coverage.""" |
|
684 if self._started: |
|
685 self.collector.stop() |
409 self._started = False |
686 self._started = False |
410 self.collector.stop() |
|
411 |
687 |
412 def _atexit(self): |
688 def _atexit(self): |
413 """Clean up on process shutdown.""" |
689 """Clean up on process shutdown.""" |
414 if self._started: |
690 if self._started: |
415 self.stop() |
691 self.stop() |
416 if self.auto_data: |
692 if self._auto_data: |
417 self.save() |
693 self.save() |
418 |
694 |
419 def erase(self): |
695 def erase(self): |
420 """Erase previously-collected coverage data. |
696 """Erase previously-collected coverage data. |
421 |
697 |
422 This removes the in-memory data collected in this session as well as |
698 This removes the in-memory data collected in this session as well as |
423 discarding the data file. |
699 discarding the data file. |
424 |
700 |
425 """ |
701 """ |
|
702 self._init() |
426 self.collector.reset() |
703 self.collector.reset() |
427 self.data.erase() |
704 self.data.erase() |
|
705 self.data_files.erase(parallel=self.config.parallel) |
428 |
706 |
429 def clear_exclude(self, which='exclude'): |
707 def clear_exclude(self, which='exclude'): |
430 """Clear the exclude list.""" |
708 """Clear the exclude list.""" |
|
709 self._init() |
431 setattr(self.config, which + "_list", []) |
710 setattr(self.config, which + "_list", []) |
432 self._exclude_regex_stale() |
711 self._exclude_regex_stale() |
433 |
712 |
434 def exclude(self, regex, which='exclude'): |
713 def exclude(self, regex, which='exclude'): |
435 """Exclude source lines from execution consideration. |
714 """Exclude source lines from execution consideration. |
462 return self._exclude_re[which] |
742 return self._exclude_re[which] |
463 |
743 |
464 def get_exclude_list(self, which='exclude'): |
744 def get_exclude_list(self, which='exclude'): |
465 """Return a list of excluded regex patterns. |
745 """Return a list of excluded regex patterns. |
466 |
746 |
467 `which` indicates which list is desired. See `exclude` for the lists |
747 `which` indicates which list is desired. See :meth:`exclude` for the |
468 that are available, and their meaning. |
748 lists that are available, and their meaning. |
469 |
749 |
470 """ |
750 """ |
|
751 self._init() |
471 return getattr(self.config, which + "_list") |
752 return getattr(self.config, which + "_list") |
472 |
753 |
473 def save(self): |
754 def save(self): |
474 """Save the collected coverage data to the data file.""" |
755 """Save the collected coverage data to the data file.""" |
475 data_suffix = self.data_suffix |
756 self._init() |
476 if data_suffix is True: |
757 self.get_data() |
477 # If data_suffix was a simple true value, then make a suffix with |
758 self.data_files.write(self.data, suffix=self.data_suffix) |
478 # plenty of distinguishing information. We do this here in |
759 |
479 # `save()` at the last minute so that the pid will be correct even |
760 def combine(self, data_paths=None): |
480 # if the process forks. |
|
481 extra = "" |
|
482 if _TEST_NAME_FILE: |
|
483 f = open(_TEST_NAME_FILE) |
|
484 test_name = f.read() |
|
485 f.close() |
|
486 extra = "." + test_name |
|
487 data_suffix = "%s%s.%s.%06d" % ( |
|
488 socket.gethostname(), extra, os.getpid(), |
|
489 random.randint(0, 999999) |
|
490 ) |
|
491 |
|
492 self._harvest_data() |
|
493 self.data.write(suffix=data_suffix) |
|
494 |
|
495 def combine(self): |
|
496 """Combine together a number of similarly-named coverage data files. |
761 """Combine together a number of similarly-named coverage data files. |
497 |
762 |
498 All coverage data files whose name starts with `data_file` (from the |
763 All coverage data files whose name starts with `data_file` (from the |
499 coverage() constructor) will be read, and combined together into the |
764 coverage() constructor) will be read, and combined together into the |
500 current measurements. |
765 current measurements. |
501 |
766 |
502 """ |
767 `data_paths` is a list of files or directories from which data should |
|
768 be combined. If no list is passed, then the data files from the |
|
769 directory indicated by the current data file (probably the current |
|
770 directory) will be combined. |
|
771 |
|
772 .. versionadded:: 4.0 |
|
773 The `data_paths` parameter. |
|
774 |
|
775 """ |
|
776 self._init() |
|
777 self.get_data() |
|
778 |
503 aliases = None |
779 aliases = None |
504 if self.config.paths: |
780 if self.config.paths: |
505 aliases = PathAliases(self.file_locator) |
781 aliases = PathAliases() |
506 for paths in self.config.paths.values(): |
782 for paths in self.config.paths.values(): |
507 result = paths[0] |
783 result = paths[0] |
508 for pattern in paths[1:]: |
784 for pattern in paths[1:]: |
509 aliases.add(pattern, result) |
785 aliases.add(pattern, result) |
510 self.data.combine_parallel_data(aliases=aliases) |
786 |
511 |
787 self.data_files.combine_parallel_data(self.data, aliases=aliases, data_paths=data_paths) |
512 def _harvest_data(self): |
788 |
|
789 def get_data(self): |
513 """Get the collected data and reset the collector. |
790 """Get the collected data and reset the collector. |
514 |
791 |
515 Also warn about various problems collecting data. |
792 Also warn about various problems collecting data. |
516 |
793 |
517 """ |
794 Returns a :class:`coverage.CoverageData`, the collected coverage data. |
|
795 |
|
796 .. versionadded:: 4.0 |
|
797 |
|
798 """ |
|
799 self._init() |
518 if not self._measured: |
800 if not self._measured: |
519 return |
801 return |
520 |
802 |
521 self.data.add_line_data(self.collector.get_line_data()) |
803 self.collector.save_data(self.data) |
522 self.data.add_arc_data(self.collector.get_arc_data()) |
|
523 self.collector.reset() |
|
524 |
804 |
525 # If there are still entries in the source_pkgs list, then we never |
805 # If there are still entries in the source_pkgs list, then we never |
526 # encountered those packages. |
806 # encountered those packages. |
527 if self._warn_unimported_source: |
807 if self._warn_unimported_source: |
528 for pkg in self.source_pkgs: |
808 for pkg in self.source_pkgs: |
529 self._warn("Module %s was never imported." % pkg) |
809 if pkg not in sys.modules: |
|
810 self._warn("Module %s was never imported." % pkg) |
|
811 elif not ( |
|
812 hasattr(sys.modules[pkg], '__file__') and |
|
813 os.path.exists(sys.modules[pkg].__file__) |
|
814 ): |
|
815 self._warn("Module %s has no Python source." % pkg) |
|
816 else: |
|
817 self._warn("Module %s was previously imported, but not measured." % pkg) |
530 |
818 |
531 # Find out if we got any data. |
819 # Find out if we got any data. |
532 summary = self.data.summary() |
820 if not self.data and self._warn_no_data: |
533 if not summary and self._warn_no_data: |
|
534 self._warn("No data was collected.") |
821 self._warn("No data was collected.") |
535 |
822 |
536 # Find files that were never executed at all. |
823 # Find files that were never executed at all. |
537 for src in self.source: |
824 for src in self.source: |
538 for py_file in find_python_files(src): |
825 for py_file in find_python_files(src): |
539 py_file = self.file_locator.canonical_filename(py_file) |
826 py_file = files.canonical_filename(py_file) |
540 |
827 |
541 if self.omit_match and self.omit_match.match(py_file): |
828 if self.omit_match and self.omit_match.match(py_file): |
542 # Turns out this file was omitted, so don't pull it back |
829 # Turns out this file was omitted, so don't pull it back |
543 # in as unexecuted. |
830 # in as unexecuted. |
544 continue |
831 continue |
545 |
832 |
546 self.data.touch_file(py_file) |
833 self.data.touch_file(py_file) |
547 |
834 |
|
835 # Add run information. |
|
836 self.data.add_run_info( |
|
837 brief_sys=" ".join([ |
|
838 platform.python_implementation(), |
|
839 platform.python_version(), |
|
840 platform.system(), |
|
841 ]) |
|
842 ) |
|
843 |
|
844 if self.config.note: |
|
845 self.data.add_run_info(note=self.config.note) |
|
846 |
548 self._measured = False |
847 self._measured = False |
|
848 return self.data |
549 |
849 |
550 # Backward compatibility with version 1. |
850 # Backward compatibility with version 1. |
551 def analysis(self, morf): |
851 def analysis(self, morf): |
552 """Like `analysis2` but doesn't return excluded line numbers.""" |
852 """Like `analysis2` but doesn't return excluded line numbers.""" |
553 f, s, _, m, mf = self.analysis2(morf) |
853 f, s, _, m, mf = self.analysis2(morf) |
554 return f, s, m, mf |
854 return f, s, m, mf |
555 |
855 |
556 def analysis2(self, morf): |
856 def analysis2(self, morf): |
557 """Analyze a module. |
857 """Analyze a module. |
558 |
858 |
559 `morf` is a module or a filename. It will be analyzed to determine |
859 `morf` is a module or a file name. It will be analyzed to determine |
560 its coverage statistics. The return value is a 5-tuple: |
860 its coverage statistics. The return value is a 5-tuple: |
561 |
861 |
562 * The filename for the module. |
862 * The file name for the module. |
563 * A list of line numbers of executable statements. |
863 * A list of line numbers of executable statements. |
564 * A list of line numbers of excluded statements. |
864 * A list of line numbers of excluded statements. |
565 * A list of line numbers of statements not run (missing from |
865 * A list of line numbers of statements not run (missing from |
566 execution). |
866 execution). |
567 * A readable formatted string of the missing line numbers. |
867 * A readable formatted string of the missing line numbers. |
568 |
868 |
569 The analysis uses the source file itself and the current measured |
869 The analysis uses the source file itself and the current measured |
570 coverage data. |
870 coverage data. |
571 |
871 |
572 """ |
872 """ |
|
873 self._init() |
573 analysis = self._analyze(morf) |
874 analysis = self._analyze(morf) |
574 return ( |
875 return ( |
575 analysis.filename, |
876 analysis.filename, |
576 sorted(analysis.statements), |
877 sorted(analysis.statements), |
577 sorted(analysis.excluded), |
878 sorted(analysis.excluded), |
646 It will be copied into the HTML directory. |
1000 It will be copied into the HTML directory. |
647 |
1001 |
648 `title` is a text string (not HTML) to use as the title of the HTML |
1002 `title` is a text string (not HTML) to use as the title of the HTML |
649 report. |
1003 report. |
650 |
1004 |
651 See `coverage.report()` for other arguments. |
1005 See :meth:`report` for other arguments. |
652 |
1006 |
653 Returns a float, the total percentage covered. |
1007 Returns a float, the total percentage covered. |
654 |
1008 |
655 """ |
1009 """ |
656 self._harvest_data() |
1010 self.get_data() |
657 self.config.from_args( |
1011 self.config.from_args( |
658 ignore_errors=ignore_errors, omit=omit, include=include, |
1012 ignore_errors=ignore_errors, omit=omit, include=include, |
659 html_dir=directory, extra_css=extra_css, html_title=title, |
1013 html_dir=directory, extra_css=extra_css, html_title=title, |
660 ) |
1014 ) |
661 reporter = HtmlReporter(self, self.config) |
1015 reporter = HtmlReporter(self, self.config) |
662 return reporter.report(morfs) |
1016 return reporter.report(morfs) |
663 |
1017 |
664 def xml_report(self, morfs=None, outfile=None, ignore_errors=None, |
1018 def xml_report( |
665 omit=None, include=None): |
1019 self, morfs=None, outfile=None, ignore_errors=None, |
|
1020 omit=None, include=None, |
|
1021 ): |
666 """Generate an XML report of coverage results. |
1022 """Generate an XML report of coverage results. |
667 |
1023 |
668 The report is compatible with Cobertura reports. |
1024 The report is compatible with Cobertura reports. |
669 |
1025 |
670 Each module in `morfs` is included in the report. `outfile` is the |
1026 Each module in `morfs` is included in the report. `outfile` is the |
671 path to write the file to, "-" will write to stdout. |
1027 path to write the file to, "-" will write to stdout. |
672 |
1028 |
673 See `coverage.report()` for other arguments. |
1029 See :meth:`report` for other arguments. |
674 |
1030 |
675 Returns a float, the total percentage covered. |
1031 Returns a float, the total percentage covered. |
676 |
1032 |
677 """ |
1033 """ |
678 self._harvest_data() |
1034 self.get_data() |
679 self.config.from_args( |
1035 self.config.from_args( |
680 ignore_errors=ignore_errors, omit=omit, include=include, |
1036 ignore_errors=ignore_errors, omit=omit, include=include, |
681 xml_output=outfile, |
1037 xml_output=outfile, |
682 ) |
1038 ) |
683 file_to_close = None |
1039 file_to_close = None |
684 delete_file = False |
1040 delete_file = False |
685 if self.config.xml_output: |
1041 if self.config.xml_output: |
686 if self.config.xml_output == '-': |
1042 if self.config.xml_output == '-': |
687 outfile = sys.stdout |
1043 outfile = sys.stdout |
688 else: |
1044 else: |
|
1045 # Ensure that the output directory is created; done here |
|
1046 # because this report pre-opens the output file. |
|
1047 # HTMLReport does this using the Report plumbing because |
|
1048 # its task is more complex, being multiple files. |
|
1049 output_dir = os.path.dirname(self.config.xml_output) |
|
1050 if output_dir and not os.path.isdir(output_dir): |
|
1051 os.makedirs(output_dir) |
689 outfile = open(self.config.xml_output, "w") |
1052 outfile = open(self.config.xml_output, "w") |
690 file_to_close = outfile |
1053 file_to_close = outfile |
691 try: |
1054 try: |
692 try: |
1055 reporter = XmlReporter(self, self.config) |
693 reporter = XmlReporter(self, self.config) |
1056 return reporter.report(morfs, outfile=outfile) |
694 return reporter.report(morfs, outfile=outfile) |
1057 except CoverageException: |
695 except CoverageException: |
1058 delete_file = True |
696 delete_file = True |
1059 raise |
697 raise |
|
698 finally: |
1060 finally: |
699 if file_to_close: |
1061 if file_to_close: |
700 file_to_close.close() |
1062 file_to_close.close() |
701 if delete_file: |
1063 if delete_file: |
702 file_be_gone(self.config.xml_output) |
1064 file_be_gone(self.config.xml_output) |
703 |
1065 |
704 def sysinfo(self): |
1066 def sys_info(self): |
705 """Return a list of (key, value) pairs showing internal information.""" |
1067 """Return a list of (key, value) pairs showing internal information.""" |
706 |
1068 |
707 import coverage as covmod |
1069 import coverage as covmod |
708 import platform, re |
1070 |
709 |
1071 self._init() |
710 try: |
1072 |
711 implementation = platform.python_implementation() |
1073 ft_plugins = [] |
712 except AttributeError: |
1074 for ft in self.plugins.file_tracers: |
713 implementation = "unknown" |
1075 ft_name = ft._coverage_plugin_name |
|
1076 if not ft._coverage_enabled: |
|
1077 ft_name += " (disabled)" |
|
1078 ft_plugins.append(ft_name) |
714 |
1079 |
715 info = [ |
1080 info = [ |
716 ('version', covmod.__version__), |
1081 ('version', covmod.__version__), |
717 ('coverage', covmod.__file__), |
1082 ('coverage', covmod.__file__), |
718 ('cover_dir', self.cover_dir), |
1083 ('cover_dirs', self.cover_dirs), |
719 ('pylib_dirs', self.pylib_dirs), |
1084 ('pylib_dirs', self.pylib_dirs), |
720 ('tracer', self.collector.tracer_name()), |
1085 ('tracer', self.collector.tracer_name()), |
|
1086 ('plugins.file_tracers', ft_plugins), |
721 ('config_files', self.config.attempted_config_files), |
1087 ('config_files', self.config.attempted_config_files), |
722 ('configs_read', self.config.config_files), |
1088 ('configs_read', self.config.config_files), |
723 ('data_path', self.data.filename), |
1089 ('data_path', self.data_files.filename), |
724 ('python', sys.version.replace('\n', '')), |
1090 ('python', sys.version.replace('\n', '')), |
725 ('platform', platform.platform()), |
1091 ('platform', platform.platform()), |
726 ('implementation', implementation), |
1092 ('implementation', platform.python_implementation()), |
727 ('executable', sys.executable), |
1093 ('executable', sys.executable), |
728 ('cwd', os.getcwd()), |
1094 ('cwd', os.getcwd()), |
729 ('path', sys.path), |
1095 ('path', sys.path), |
730 ('environment', sorted([ |
1096 ('environment', sorted( |
731 ("%s = %s" % (k, v)) for k, v in iitems(os.environ) |
1097 ("%s = %s" % (k, v)) |
732 if re.search(r"^COV|^PY", k) |
1098 for k, v in iitems(os.environ) |
733 ])), |
1099 if k.startswith(("COV", "PY")) |
|
1100 )), |
734 ('command_line', " ".join(getattr(sys, 'argv', ['???']))), |
1101 ('command_line', " ".join(getattr(sys, 'argv', ['???']))), |
735 ] |
1102 ] |
736 if self.source_match: |
1103 |
737 info.append(('source_match', self.source_match.info())) |
1104 matcher_names = [ |
738 if self.include_match: |
1105 'source_match', 'source_pkgs_match', |
739 info.append(('include_match', self.include_match.info())) |
1106 'include_match', 'omit_match', |
740 if self.omit_match: |
1107 'cover_match', 'pylib_match', |
741 info.append(('omit_match', self.omit_match.info())) |
1108 ] |
742 if self.cover_match: |
1109 |
743 info.append(('cover_match', self.cover_match.info())) |
1110 for matcher_name in matcher_names: |
744 if self.pylib_match: |
1111 matcher = getattr(self, matcher_name) |
745 info.append(('pylib_match', self.pylib_match.info())) |
1112 if matcher: |
|
1113 matcher_info = matcher.info() |
|
1114 else: |
|
1115 matcher_info = '-none-' |
|
1116 info.append((matcher_name, matcher_info)) |
746 |
1117 |
747 return info |
1118 return info |
748 |
1119 |
749 |
1120 |
|
1121 # FileDisposition "methods": FileDisposition is a pure value object, so it can |
|
1122 # be implemented in either C or Python. Acting on them is done with these |
|
1123 # functions. |
|
1124 |
|
1125 def _disposition_init(cls, original_filename): |
|
1126 """Construct and initialize a new FileDisposition object.""" |
|
1127 disp = cls() |
|
1128 disp.original_filename = original_filename |
|
1129 disp.canonical_filename = original_filename |
|
1130 disp.source_filename = None |
|
1131 disp.trace = False |
|
1132 disp.reason = "" |
|
1133 disp.file_tracer = None |
|
1134 disp.has_dynamic_filename = False |
|
1135 return disp |
|
1136 |
|
1137 |
|
1138 def _disposition_debug_msg(disp): |
|
1139 """Make a nice debug message of what the FileDisposition is doing.""" |
|
1140 if disp.trace: |
|
1141 msg = "Tracing %r" % (disp.original_filename,) |
|
1142 if disp.file_tracer: |
|
1143 msg += ": will be traced by %r" % disp.file_tracer |
|
1144 else: |
|
1145 msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason) |
|
1146 return msg |
|
1147 |
|
1148 |
750 def process_startup(): |
1149 def process_startup(): |
751 """Call this at Python startup to perhaps measure coverage. |
1150 """Call this at Python start-up to perhaps measure coverage. |
752 |
1151 |
753 If the environment variable COVERAGE_PROCESS_START is defined, coverage |
1152 If the environment variable COVERAGE_PROCESS_START is defined, coverage |
754 measurement is started. The value of the variable is the config file |
1153 measurement is started. The value of the variable is the config file |
755 to use. |
1154 to use. |
756 |
1155 |