130 in the measured code. Without this, coverage.py will get incorrect |
157 in the measured code. Without this, coverage.py will get incorrect |
131 results if these libraries are in use. Valid strings are "greenlet", |
158 results if these libraries are in use. Valid strings are "greenlet", |
132 "eventlet", "gevent", "multiprocessing", or "thread" (the default). |
159 "eventlet", "gevent", "multiprocessing", or "thread" (the default). |
133 This can also be a list of these strings. |
160 This can also be a list of these strings. |
134 |
161 |
|
162 If `check_preimported` is true, then when coverage is started, the |
|
163 already-imported files will be checked to see if they should be |
|
164 measured by coverage. Importing measured files before coverage is |
|
165 started can mean that code is missed. |
|
166 |
|
167 `context` is a string to use as the :ref:`static context |
|
168 <static_contexts>` label for collected data. |
|
169 |
135 .. versionadded:: 4.0 |
170 .. versionadded:: 4.0 |
136 The `concurrency` parameter. |
171 The `concurrency` parameter. |
137 |
172 |
138 .. versionadded:: 4.2 |
173 .. versionadded:: 4.2 |
139 The `concurrency` parameter can now be a list of strings. |
174 The `concurrency` parameter can now be a list of strings. |
140 |
175 |
141 """ |
176 .. versionadded:: 5.0 |
|
177 The `check_preimported` and `context` parameters. |
|
178 |
|
179 """ |
|
180 # data_file=None means no disk file at all. data_file missing means |
|
181 # use the value from the config file. |
|
182 self._no_disk = data_file is None |
|
183 if data_file is _DEFAULT_DATAFILE: |
|
184 data_file = None |
|
185 |
142 # Build our configuration from a number of sources. |
186 # Build our configuration from a number of sources. |
143 self.config_file, self.config = read_coverage_config( |
187 self.config = read_coverage_config( |
144 config_file=config_file, |
188 config_file=config_file, |
145 data_file=data_file, cover_pylib=cover_pylib, timid=timid, |
189 data_file=data_file, cover_pylib=cover_pylib, timid=timid, |
146 branch=branch, parallel=bool_or_none(data_suffix), |
190 branch=branch, parallel=bool_or_none(data_suffix), |
147 source=source, run_omit=omit, run_include=include, debug=debug, |
191 source=source, run_omit=omit, run_include=include, debug=debug, |
148 report_omit=omit, report_include=include, |
192 report_omit=omit, report_include=include, |
149 concurrency=concurrency, |
193 concurrency=concurrency, context=context, |
150 ) |
194 ) |
151 |
195 |
152 # This is injectable by tests. |
196 # This is injectable by tests. |
153 self._debug_file = None |
197 self._debug_file = None |
154 |
198 |
155 self._auto_load = self._auto_save = auto_data |
199 self._auto_load = self._auto_save = auto_data |
156 self._data_suffix = data_suffix |
200 self._data_suffix_specified = data_suffix |
157 |
|
158 # The matchers for _should_trace. |
|
159 self.source_match = None |
|
160 self.source_pkgs_match = None |
|
161 self.pylib_match = self.cover_match = None |
|
162 self.include_match = self.omit_match = None |
|
163 |
201 |
164 # Is it ok for no data to be collected? |
202 # Is it ok for no data to be collected? |
165 self._warn_no_data = True |
203 self._warn_no_data = True |
166 self._warn_unimported_source = True |
204 self._warn_unimported_source = True |
|
205 self._warn_preimported_source = check_preimported |
|
206 self._no_warn_slugs = None |
167 |
207 |
168 # A record of all the warnings that have been issued. |
208 # A record of all the warnings that have been issued. |
169 self._warnings = [] |
209 self._warnings = [] |
170 |
210 |
171 # Other instance attributes, set later. |
211 # Other instance attributes, set later. |
172 self.omit = self.include = self.source = None |
212 self._data = self._collector = None |
173 self.source_pkgs_unmatched = None |
213 self._plugins = None |
174 self.source_pkgs = None |
214 self._inorout = None |
175 self.data = self.data_files = self.collector = None |
215 self._inorout_class = InOrOut |
176 self.plugins = None |
216 self._data_suffix = self._run_suffix = None |
177 self.pylib_paths = self.cover_paths = None |
|
178 self.data_suffix = self.run_suffix = None |
|
179 self._exclude_re = None |
217 self._exclude_re = None |
180 self.debug = None |
218 self._debug = None |
|
219 self._file_mapper = None |
181 |
220 |
182 # State machine variables: |
221 # State machine variables: |
183 # Have we initialized everything? |
222 # Have we initialized everything? |
184 self._inited = False |
223 self._inited = False |
|
224 self._inited_for_start = False |
185 # Have we started collecting and not stopped it? |
225 # Have we started collecting and not stopped it? |
186 self._started = False |
226 self._started = False |
|
227 # Should we write the debug output? |
|
228 self._should_write_debug = True |
187 |
229 |
188 # If we have sub-process measurement happening automatically, then we |
230 # If we have sub-process measurement happening automatically, then we |
189 # want any explicit creation of a Coverage object to mean, this process |
231 # want any explicit creation of a Coverage object to mean, this process |
190 # is already coverage-aware, so don't auto-measure it. By now, the |
232 # is already coverage-aware, so don't auto-measure it. By now, the |
191 # auto-creation of a Coverage object has already happened. But we can |
233 # auto-creation of a Coverage object has already happened. But we can |
207 self._inited = True |
249 self._inited = True |
208 |
250 |
209 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE |
251 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE |
210 # is an environment variable, the name of a file to append debug logs |
252 # is an environment variable, the name of a file to append debug logs |
211 # to. |
253 # to. |
212 if self._debug_file is None: |
254 self._debug = DebugControl(self.config.debug, self._debug_file) |
213 debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE") |
255 |
214 if debug_file_name: |
256 if "multiprocessing" in (self.config.concurrency or ()): |
215 self._debug_file = open(debug_file_name, "a") |
257 # Multi-processing uses parallel for the subprocesses, so also use |
216 else: |
258 # it for the main process. |
217 self._debug_file = sys.stderr |
259 self.config.parallel = True |
218 self.debug = DebugControl(self.config.debug, self._debug_file) |
|
219 |
260 |
220 # _exclude_re is a dict that maps exclusion list names to compiled regexes. |
261 # _exclude_re is a dict that maps exclusion list names to compiled regexes. |
221 self._exclude_re = {} |
262 self._exclude_re = {} |
222 |
263 |
223 set_relative_directory() |
264 set_relative_directory() |
|
265 self._file_mapper = relative_filename if self.config.relative_files else abs_file |
224 |
266 |
225 # Load plugins |
267 # Load plugins |
226 self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug) |
268 self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug) |
227 |
269 |
228 # Run configuring plugins. |
270 # Run configuring plugins. |
229 for plugin in self.plugins.configurers: |
271 for plugin in self._plugins.configurers: |
230 # We need an object with set_option and get_option. Either self or |
272 # We need an object with set_option and get_option. Either self or |
231 # self.config will do. Choosing randomly stops people from doing |
273 # self.config will do. Choosing randomly stops people from doing |
232 # other things with those objects, against the public API. Yes, |
274 # other things with those objects, against the public API. Yes, |
233 # this is a bit childish. :) |
275 # this is a bit childish. :) |
234 plugin.configure([self, self.config][int(time.time()) % 2]) |
276 plugin.configure([self, self.config][int(time.time()) % 2]) |
235 |
277 |
236 # The source argument can be directories or package names. |
278 def _post_init(self): |
237 self.source = [] |
279 """Stuff to do after everything is initialized.""" |
238 self.source_pkgs = [] |
280 if self._should_write_debug: |
239 for src in self.config.source or []: |
281 self._should_write_debug = False |
240 if os.path.isdir(src): |
282 self._write_startup_debug() |
241 self.source.append(canonical_filename(src)) |
283 |
|
284 # '[run] _crash' will raise an exception if the value is close by in |
|
285 # the call stack, for testing error handling. |
|
286 if self.config._crash and self.config._crash in short_stack(limit=4): |
|
287 raise Exception("Crashing because called by {}".format(self.config._crash)) |
|
288 |
|
289 def _write_startup_debug(self): |
|
290 """Write out debug info at startup if needed.""" |
|
291 wrote_any = False |
|
292 with self._debug.without_callers(): |
|
293 if self._debug.should('config'): |
|
294 config_info = sorted(self.config.__dict__.items()) |
|
295 config_info = [(k, v) for k, v in config_info if not k.startswith('_')] |
|
296 write_formatted_info(self._debug, "config", config_info) |
|
297 wrote_any = True |
|
298 |
|
299 if self._debug.should('sys'): |
|
300 write_formatted_info(self._debug, "sys", self.sys_info()) |
|
301 for plugin in self._plugins: |
|
302 header = "sys: " + plugin._coverage_plugin_name |
|
303 info = plugin.sys_info() |
|
304 write_formatted_info(self._debug, header, info) |
|
305 wrote_any = True |
|
306 |
|
307 if wrote_any: |
|
308 write_formatted_info(self._debug, "end", ()) |
|
309 |
|
310 def _should_trace(self, filename, frame): |
|
311 """Decide whether to trace execution in `filename`. |
|
312 |
|
313 Calls `_should_trace_internal`, and returns the FileDisposition. |
|
314 |
|
315 """ |
|
316 disp = self._inorout.should_trace(filename, frame) |
|
317 if self._debug.should('trace'): |
|
318 self._debug.write(disposition_debug_msg(disp)) |
|
319 return disp |
|
320 |
|
321 def _check_include_omit_etc(self, filename, frame): |
|
322 """Check a file name against the include/omit/etc, rules, verbosely. |
|
323 |
|
324 Returns a boolean: True if the file should be traced, False if not. |
|
325 |
|
326 """ |
|
327 reason = self._inorout.check_include_omit_etc(filename, frame) |
|
328 if self._debug.should('trace'): |
|
329 if not reason: |
|
330 msg = "Including %r" % (filename,) |
242 else: |
331 else: |
243 self.source_pkgs.append(src) |
332 msg = "Not including %r: %s" % (filename, reason) |
244 self.source_pkgs_unmatched = self.source_pkgs[:] |
333 self._debug.write(msg) |
245 |
334 |
246 self.omit = prep_patterns(self.config.run_omit) |
335 return not reason |
247 self.include = prep_patterns(self.config.run_include) |
336 |
248 |
337 def _warn(self, msg, slug=None, once=False): |
249 concurrency = self.config.concurrency or [] |
338 """Use `msg` as a warning. |
|
339 |
|
340 For warning suppression, use `slug` as the shorthand. |
|
341 |
|
342 If `once` is true, only show this warning once (determined by the |
|
343 slug.) |
|
344 |
|
345 """ |
|
346 if self._no_warn_slugs is None: |
|
347 self._no_warn_slugs = list(self.config.disable_warnings) |
|
348 |
|
349 if slug in self._no_warn_slugs: |
|
350 # Don't issue the warning |
|
351 return |
|
352 |
|
353 self._warnings.append(msg) |
|
354 if slug: |
|
355 msg = "%s (%s)" % (msg, slug) |
|
356 if self._debug.should('pid'): |
|
357 msg = "[%d] %s" % (os.getpid(), msg) |
|
358 sys.stderr.write("Coverage.py warning: %s\n" % msg) |
|
359 |
|
360 if once: |
|
361 self._no_warn_slugs.append(slug) |
|
362 |
|
363 def get_option(self, option_name): |
|
364 """Get an option from the configuration. |
|
365 |
|
366 `option_name` is a colon-separated string indicating the section and |
|
367 option name. For example, the ``branch`` option in the ``[run]`` |
|
368 section of the config file would be indicated with `"run:branch"`. |
|
369 |
|
370 Returns the value of the option. |
|
371 |
|
372 .. versionadded:: 4.0 |
|
373 |
|
374 """ |
|
375 return self.config.get_option(option_name) |
|
376 |
|
377 def set_option(self, option_name, value): |
|
378 """Set an option in the configuration. |
|
379 |
|
380 `option_name` is a colon-separated string indicating the section and |
|
381 option name. For example, the ``branch`` option in the ``[run]`` |
|
382 section of the config file would be indicated with ``"run:branch"``. |
|
383 |
|
384 `value` is the new value for the option. This should be an |
|
385 appropriate Python value. For example, use True for booleans, not the |
|
386 string ``"True"``. |
|
387 |
|
388 As an example, calling:: |
|
389 |
|
390 cov.set_option("run:branch", True) |
|
391 |
|
392 has the same effect as this configuration file:: |
|
393 |
|
394 [run] |
|
395 branch = True |
|
396 |
|
397 .. versionadded:: 4.0 |
|
398 |
|
399 """ |
|
400 self.config.set_option(option_name, value) |
|
401 |
|
402 def load(self): |
|
403 """Load previously-collected coverage data from the data file.""" |
|
404 self._init() |
|
405 if self._collector: |
|
406 self._collector.reset() |
|
407 should_skip = self.config.parallel and not os.path.exists(self.config.data_file) |
|
408 if not should_skip: |
|
409 self._init_data(suffix=None) |
|
410 self._post_init() |
|
411 if not should_skip: |
|
412 self._data.read() |
|
413 |
|
414 def _init_for_start(self): |
|
415 """Initialization for start()""" |
|
416 # Construct the collector. |
|
417 concurrency = self.config.concurrency or () |
250 if "multiprocessing" in concurrency: |
418 if "multiprocessing" in concurrency: |
251 if not patch_multiprocessing: |
419 if not patch_multiprocessing: |
252 raise CoverageException( # pragma: only jython |
420 raise CoverageException( # pragma: only jython |
253 "multiprocessing is not supported on this Python" |
421 "multiprocessing is not supported on this Python" |
254 ) |
422 ) |
255 patch_multiprocessing(rcfile=self.config_file) |
423 patch_multiprocessing(rcfile=self.config.config_file) |
256 # Multi-processing uses parallel for the subprocesses, so also use |
424 |
257 # it for the main process. |
425 dycon = self.config.dynamic_context |
258 self.config.parallel = True |
426 if not dycon or dycon == "none": |
259 |
427 context_switchers = [] |
260 self.collector = Collector( |
428 elif dycon == "test_function": |
|
429 context_switchers = [should_start_context_test_function] |
|
430 else: |
|
431 raise CoverageException( |
|
432 "Don't understand dynamic_context setting: {!r}".format(dycon) |
|
433 ) |
|
434 |
|
435 context_switchers.extend( |
|
436 plugin.dynamic_context for plugin in self._plugins.context_switchers |
|
437 ) |
|
438 |
|
439 should_start_context = combine_context_switchers(context_switchers) |
|
440 |
|
441 self._collector = Collector( |
261 should_trace=self._should_trace, |
442 should_trace=self._should_trace, |
262 check_include=self._check_include_omit_etc, |
443 check_include=self._check_include_omit_etc, |
|
444 should_start_context=should_start_context, |
|
445 file_mapper=self._file_mapper, |
263 timid=self.config.timid, |
446 timid=self.config.timid, |
264 branch=self.config.branch, |
447 branch=self.config.branch, |
265 warn=self._warn, |
448 warn=self._warn, |
266 concurrency=concurrency, |
449 concurrency=concurrency, |
267 ) |
450 ) |
268 |
451 |
|
452 suffix = self._data_suffix_specified |
|
453 if suffix or self.config.parallel: |
|
454 if not isinstance(suffix, string_class): |
|
455 # if data_suffix=True, use .machinename.pid.random |
|
456 suffix = True |
|
457 else: |
|
458 suffix = None |
|
459 |
|
460 self._init_data(suffix) |
|
461 |
|
462 self._collector.use_data(self._data, self.config.context) |
|
463 |
269 # Early warning if we aren't going to be able to support plugins. |
464 # Early warning if we aren't going to be able to support plugins. |
270 if self.plugins.file_tracers and not self.collector.supports_plugins: |
465 if self._plugins.file_tracers and not self._collector.supports_plugins: |
271 self._warn( |
466 self._warn( |
272 "Plugin file tracers (%s) aren't supported with %s" % ( |
467 "Plugin file tracers (%s) aren't supported with %s" % ( |
273 ", ".join( |
468 ", ".join( |
274 plugin._coverage_plugin_name |
469 plugin._coverage_plugin_name |
275 for plugin in self.plugins.file_tracers |
470 for plugin in self._plugins.file_tracers |
276 ), |
471 ), |
277 self.collector.tracer_name(), |
472 self._collector.tracer_name(), |
278 ) |
473 ) |
279 ) |
474 ) |
280 for plugin in self.plugins.file_tracers: |
475 for plugin in self._plugins.file_tracers: |
281 plugin._coverage_enabled = False |
476 plugin._coverage_enabled = False |
282 |
477 |
283 # Suffixes are a bit tricky. We want to use the data suffix only when |
478 # Create the file classifying substructure. |
284 # collecting data, not when combining data. So we save it as |
479 self._inorout = self._inorout_class(warn=self._warn) |
285 # `self.run_suffix` now, and promote it to `self.data_suffix` if we |
480 self._inorout.configure(self.config) |
286 # find that we are collecting data later. |
481 self._inorout.plugins = self._plugins |
287 if self._data_suffix or self.config.parallel: |
482 self._inorout.disp_class = self._collector.file_disposition_class |
288 if not isinstance(self._data_suffix, string_class): |
483 |
289 # if data_suffix=True, use .machinename.pid.random |
484 # It's useful to write debug info after initing for start. |
290 self._data_suffix = True |
485 self._should_write_debug = True |
291 else: |
|
292 self._data_suffix = None |
|
293 self.data_suffix = None |
|
294 self.run_suffix = self._data_suffix |
|
295 |
|
296 # Create the data file. We do this at construction time so that the |
|
297 # data file will be written into the directory where the process |
|
298 # started rather than wherever the process eventually chdir'd to. |
|
299 self.data = CoverageData(debug=self.debug) |
|
300 self.data_files = CoverageDataFiles( |
|
301 basename=self.config.data_file, warn=self._warn, debug=self.debug, |
|
302 ) |
|
303 |
|
304 # The directories for files considered "installed with the interpreter". |
|
305 self.pylib_paths = set() |
|
306 if not self.config.cover_pylib: |
|
307 # Look at where some standard modules are located. That's the |
|
308 # indication for "installed with the interpreter". In some |
|
309 # environments (virtualenv, for example), these modules may be |
|
310 # spread across a few locations. Look at all the candidate modules |
|
311 # we've imported, and take all the different ones. |
|
312 for m in (atexit, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback): |
|
313 if m is not None and hasattr(m, "__file__"): |
|
314 self.pylib_paths.add(self._canonical_path(m, directory=True)) |
|
315 |
|
316 if _structseq and not hasattr(_structseq, '__file__'): |
|
317 # PyPy 2.4 has no __file__ in the builtin modules, but the code |
|
318 # objects still have the file names. So dig into one to find |
|
319 # the path to exclude. |
|
320 structseq_new = _structseq.structseq_new |
|
321 try: |
|
322 structseq_file = structseq_new.func_code.co_filename |
|
323 except AttributeError: |
|
324 structseq_file = structseq_new.__code__.co_filename |
|
325 self.pylib_paths.add(self._canonical_path(structseq_file)) |
|
326 |
|
327 # To avoid tracing the coverage.py code itself, we skip anything |
|
328 # located where we are. |
|
329 self.cover_paths = [self._canonical_path(__file__, directory=True)] |
|
330 if env.TESTING: |
|
331 # Don't include our own test code. |
|
332 self.cover_paths.append(os.path.join(self.cover_paths[0], "tests")) |
|
333 |
|
334 # When testing, we use PyContracts, which should be considered |
|
335 # part of coverage.py, and it uses six. Exclude those directories |
|
336 # just as we exclude ourselves. |
|
337 import contracts |
|
338 import six |
|
339 for mod in [contracts, six]: |
|
340 self.cover_paths.append(self._canonical_path(mod)) |
|
341 |
|
342 # Set the reporting precision. |
|
343 Numbers.set_precision(self.config.precision) |
|
344 |
486 |
345 atexit.register(self._atexit) |
487 atexit.register(self._atexit) |
346 |
488 |
347 # Create the matchers we need for _should_trace |
489 def _init_data(self, suffix): |
348 if self.source or self.source_pkgs: |
490 """Create a data file if we don't have one yet.""" |
349 self.source_match = TreeMatcher(self.source) |
491 if self._data is None: |
350 self.source_pkgs_match = ModuleMatcher(self.source_pkgs) |
492 # Create the data file. We do this at construction time so that the |
351 else: |
493 # data file will be written into the directory where the process |
352 if self.cover_paths: |
494 # started rather than wherever the process eventually chdir'd to. |
353 self.cover_match = TreeMatcher(self.cover_paths) |
495 ensure_dir_for_file(self.config.data_file) |
354 if self.pylib_paths: |
496 self._data = CoverageData( |
355 self.pylib_match = TreeMatcher(self.pylib_paths) |
497 basename=self.config.data_file, |
356 if self.include: |
498 suffix=suffix, |
357 self.include_match = FnmatchMatcher(self.include) |
499 warn=self._warn, |
358 if self.omit: |
500 debug=self._debug, |
359 self.omit_match = FnmatchMatcher(self.omit) |
501 no_disk=self._no_disk, |
360 |
|
361 # The user may want to debug things, show info if desired. |
|
362 self._write_startup_debug() |
|
363 |
|
364 def _write_startup_debug(self): |
|
365 """Write out debug info at startup if needed.""" |
|
366 wrote_any = False |
|
367 with self.debug.without_callers(): |
|
368 if self.debug.should('config'): |
|
369 config_info = sorted(self.config.__dict__.items()) |
|
370 write_formatted_info(self.debug, "config", config_info) |
|
371 wrote_any = True |
|
372 |
|
373 if self.debug.should('sys'): |
|
374 write_formatted_info(self.debug, "sys", self.sys_info()) |
|
375 for plugin in self.plugins: |
|
376 header = "sys: " + plugin._coverage_plugin_name |
|
377 info = plugin.sys_info() |
|
378 write_formatted_info(self.debug, header, info) |
|
379 wrote_any = True |
|
380 |
|
381 if wrote_any: |
|
382 write_formatted_info(self.debug, "end", ()) |
|
383 |
|
384 def _canonical_path(self, morf, directory=False): |
|
385 """Return the canonical path of the module or file `morf`. |
|
386 |
|
387 If the module is a package, then return its directory. If it is a |
|
388 module, then return its file, unless `directory` is True, in which |
|
389 case return its enclosing directory. |
|
390 |
|
391 """ |
|
392 morf_path = PythonFileReporter(morf, self).filename |
|
393 if morf_path.endswith("__init__.py") or directory: |
|
394 morf_path = os.path.split(morf_path)[0] |
|
395 return morf_path |
|
396 |
|
397 def _name_for_module(self, module_globals, filename): |
|
398 """Get the name of the module for a set of globals and file name. |
|
399 |
|
400 For configurability's sake, we allow __main__ modules to be matched by |
|
401 their importable name. |
|
402 |
|
403 If loaded via runpy (aka -m), we can usually recover the "original" |
|
404 full dotted module name, otherwise, we resort to interpreting the |
|
405 file name to get the module's name. In the case that the module name |
|
406 can't be determined, None is returned. |
|
407 |
|
408 """ |
|
409 if module_globals is None: # pragma: only ironpython |
|
410 # IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296 |
|
411 module_globals = {} |
|
412 |
|
413 dunder_name = module_globals.get('__name__', None) |
|
414 |
|
415 if isinstance(dunder_name, str) and dunder_name != '__main__': |
|
416 # This is the usual case: an imported module. |
|
417 return dunder_name |
|
418 |
|
419 loader = module_globals.get('__loader__', None) |
|
420 for attrname in ('fullname', 'name'): # attribute renamed in py3.2 |
|
421 if hasattr(loader, attrname): |
|
422 fullname = getattr(loader, attrname) |
|
423 else: |
|
424 continue |
|
425 |
|
426 if isinstance(fullname, str) and fullname != '__main__': |
|
427 # Module loaded via: runpy -m |
|
428 return fullname |
|
429 |
|
430 # Script as first argument to Python command line. |
|
431 inspectedname = inspect.getmodulename(filename) |
|
432 if inspectedname is not None: |
|
433 return inspectedname |
|
434 else: |
|
435 return dunder_name |
|
436 |
|
437 def _should_trace_internal(self, filename, frame): |
|
438 """Decide whether to trace execution in `filename`, with a reason. |
|
439 |
|
440 This function is called from the trace function. As each new file name |
|
441 is encountered, this function determines whether it is traced or not. |
|
442 |
|
443 Returns a FileDisposition object. |
|
444 |
|
445 """ |
|
446 original_filename = filename |
|
447 disp = _disposition_init(self.collector.file_disposition_class, filename) |
|
448 |
|
449 def nope(disp, reason): |
|
450 """Simple helper to make it easy to return NO.""" |
|
451 disp.trace = False |
|
452 disp.reason = reason |
|
453 return disp |
|
454 |
|
455 # Compiled Python files have two file names: frame.f_code.co_filename is |
|
456 # the file name at the time the .pyc was compiled. The second name is |
|
457 # __file__, which is where the .pyc was actually loaded from. Since |
|
458 # .pyc files can be moved after compilation (for example, by being |
|
459 # installed), we look for __file__ in the frame and prefer it to the |
|
460 # co_filename value. |
|
461 dunder_file = frame.f_globals and frame.f_globals.get('__file__') |
|
462 if dunder_file: |
|
463 filename = source_for_file(dunder_file) |
|
464 if original_filename and not original_filename.startswith('<'): |
|
465 orig = os.path.basename(original_filename) |
|
466 if orig != os.path.basename(filename): |
|
467 # Files shouldn't be renamed when moved. This happens when |
|
468 # exec'ing code. If it seems like something is wrong with |
|
469 # the frame's file name, then just use the original. |
|
470 filename = original_filename |
|
471 |
|
472 if not filename: |
|
473 # Empty string is pretty useless. |
|
474 return nope(disp, "empty string isn't a file name") |
|
475 |
|
476 if filename.startswith('memory:'): |
|
477 return nope(disp, "memory isn't traceable") |
|
478 |
|
479 if filename.startswith('<'): |
|
480 # Lots of non-file execution is represented with artificial |
|
481 # file names like "<string>", "<doctest readme.txt[0]>", or |
|
482 # "<exec_function>". Don't ever trace these executions, since we |
|
483 # can't do anything with the data later anyway. |
|
484 return nope(disp, "not a real file name") |
|
485 |
|
486 # pyexpat does a dumb thing, calling the trace function explicitly from |
|
487 # C code with a C file name. |
|
488 if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename): |
|
489 return nope(disp, "pyexpat lies about itself") |
|
490 |
|
491 # Jython reports the .class file to the tracer, use the source file. |
|
492 if filename.endswith("$py.class"): |
|
493 filename = filename[:-9] + ".py" |
|
494 |
|
495 canonical = canonical_filename(filename) |
|
496 disp.canonical_filename = canonical |
|
497 |
|
498 # Try the plugins, see if they have an opinion about the file. |
|
499 plugin = None |
|
500 for plugin in self.plugins.file_tracers: |
|
501 if not plugin._coverage_enabled: |
|
502 continue |
|
503 |
|
504 try: |
|
505 file_tracer = plugin.file_tracer(canonical) |
|
506 if file_tracer is not None: |
|
507 file_tracer._coverage_plugin = plugin |
|
508 disp.trace = True |
|
509 disp.file_tracer = file_tracer |
|
510 if file_tracer.has_dynamic_source_filename(): |
|
511 disp.has_dynamic_filename = True |
|
512 else: |
|
513 disp.source_filename = canonical_filename( |
|
514 file_tracer.source_filename() |
|
515 ) |
|
516 break |
|
517 except Exception: |
|
518 self._warn( |
|
519 "Disabling plug-in %r due to an exception:" % ( |
|
520 plugin._coverage_plugin_name |
|
521 ) |
|
522 ) |
|
523 traceback.print_exc() |
|
524 plugin._coverage_enabled = False |
|
525 continue |
|
526 else: |
|
527 # No plugin wanted it: it's Python. |
|
528 disp.trace = True |
|
529 disp.source_filename = canonical |
|
530 |
|
531 if not disp.has_dynamic_filename: |
|
532 if not disp.source_filename: |
|
533 raise CoverageException( |
|
534 "Plugin %r didn't set source_filename for %r" % |
|
535 (plugin, disp.original_filename) |
|
536 ) |
|
537 reason = self._check_include_omit_etc_internal( |
|
538 disp.source_filename, frame, |
|
539 ) |
502 ) |
540 if reason: |
|
541 nope(disp, reason) |
|
542 |
|
543 return disp |
|
544 |
|
545 def _check_include_omit_etc_internal(self, filename, frame): |
|
546 """Check a file name against the include, omit, etc, rules. |
|
547 |
|
548 Returns a string or None. String means, don't trace, and is the reason |
|
549 why. None means no reason found to not trace. |
|
550 |
|
551 """ |
|
552 modulename = self._name_for_module(frame.f_globals, filename) |
|
553 |
|
554 # If the user specified source or include, then that's authoritative |
|
555 # about the outer bound of what to measure and we don't have to apply |
|
556 # any canned exclusions. If they didn't, then we have to exclude the |
|
557 # stdlib and coverage.py directories. |
|
558 if self.source_match: |
|
559 if self.source_pkgs_match.match(modulename): |
|
560 if modulename in self.source_pkgs_unmatched: |
|
561 self.source_pkgs_unmatched.remove(modulename) |
|
562 elif not self.source_match.match(filename): |
|
563 return "falls outside the --source trees" |
|
564 elif self.include_match: |
|
565 if not self.include_match.match(filename): |
|
566 return "falls outside the --include trees" |
|
567 else: |
|
568 # If we aren't supposed to trace installed code, then check if this |
|
569 # is near the Python standard library and skip it if so. |
|
570 if self.pylib_match and self.pylib_match.match(filename): |
|
571 return "is in the stdlib" |
|
572 |
|
573 # We exclude the coverage.py code itself, since a little of it |
|
574 # will be measured otherwise. |
|
575 if self.cover_match and self.cover_match.match(filename): |
|
576 return "is part of coverage.py" |
|
577 |
|
578 # Check the file against the omit pattern. |
|
579 if self.omit_match and self.omit_match.match(filename): |
|
580 return "is inside an --omit pattern" |
|
581 |
|
582 # No reason found to skip this file. |
|
583 return None |
|
584 |
|
585 def _should_trace(self, filename, frame): |
|
586 """Decide whether to trace execution in `filename`. |
|
587 |
|
588 Calls `_should_trace_internal`, and returns the FileDisposition. |
|
589 |
|
590 """ |
|
591 disp = self._should_trace_internal(filename, frame) |
|
592 if self.debug.should('trace'): |
|
593 self.debug.write(_disposition_debug_msg(disp)) |
|
594 return disp |
|
595 |
|
596 def _check_include_omit_etc(self, filename, frame): |
|
597 """Check a file name against the include/omit/etc, rules, verbosely. |
|
598 |
|
599 Returns a boolean: True if the file should be traced, False if not. |
|
600 |
|
601 """ |
|
602 reason = self._check_include_omit_etc_internal(filename, frame) |
|
603 if self.debug.should('trace'): |
|
604 if not reason: |
|
605 msg = "Including %r" % (filename,) |
|
606 else: |
|
607 msg = "Not including %r: %s" % (filename, reason) |
|
608 self.debug.write(msg) |
|
609 |
|
610 return not reason |
|
611 |
|
612 def _warn(self, msg, slug=None): |
|
613 """Use `msg` as a warning. |
|
614 |
|
615 For warning suppression, use `slug` as the shorthand. |
|
616 """ |
|
617 if slug in self.config.disable_warnings: |
|
618 # Don't issue the warning |
|
619 return |
|
620 |
|
621 self._warnings.append(msg) |
|
622 if slug: |
|
623 msg = "%s (%s)" % (msg, slug) |
|
624 if self.debug.should('pid'): |
|
625 msg = "[%d] %s" % (os.getpid(), msg) |
|
626 sys.stderr.write("Coverage.py warning: %s\n" % msg) |
|
627 |
|
628 def get_option(self, option_name): |
|
629 """Get an option from the configuration. |
|
630 |
|
631 `option_name` is a colon-separated string indicating the section and |
|
632 option name. For example, the ``branch`` option in the ``[run]`` |
|
633 section of the config file would be indicated with `"run:branch"`. |
|
634 |
|
635 Returns the value of the option. |
|
636 |
|
637 .. versionadded:: 4.0 |
|
638 |
|
639 """ |
|
640 return self.config.get_option(option_name) |
|
641 |
|
642 def set_option(self, option_name, value): |
|
643 """Set an option in the configuration. |
|
644 |
|
645 `option_name` is a colon-separated string indicating the section and |
|
646 option name. For example, the ``branch`` option in the ``[run]`` |
|
647 section of the config file would be indicated with ``"run:branch"``. |
|
648 |
|
649 `value` is the new value for the option. This should be an |
|
650 appropriate Python value. For example, use True for booleans, not the |
|
651 string ``"True"``. |
|
652 |
|
653 As an example, calling:: |
|
654 |
|
655 cov.set_option("run:branch", True) |
|
656 |
|
657 has the same effect as this configuration file:: |
|
658 |
|
659 [run] |
|
660 branch = True |
|
661 |
|
662 .. versionadded:: 4.0 |
|
663 |
|
664 """ |
|
665 self.config.set_option(option_name, value) |
|
666 |
|
667 def use_cache(self, usecache): |
|
668 """Obsolete method.""" |
|
669 self._init() |
|
670 if not usecache: |
|
671 self._warn("use_cache(False) is no longer supported.") |
|
672 |
|
673 def load(self): |
|
674 """Load previously-collected coverage data from the data file.""" |
|
675 self._init() |
|
676 self.collector.reset() |
|
677 self.data_files.read(self.data) |
|
678 |
503 |
679 def start(self): |
504 def start(self): |
680 """Start measuring code coverage. |
505 """Start measuring code coverage. |
681 |
506 |
682 Coverage measurement only occurs in functions called after |
507 Coverage measurement only occurs in functions called after |
1159 return entries |
993 return entries |
1160 |
994 |
1161 info = [ |
995 info = [ |
1162 ('version', covmod.__version__), |
996 ('version', covmod.__version__), |
1163 ('coverage', covmod.__file__), |
997 ('coverage', covmod.__file__), |
1164 ('cover_paths', self.cover_paths), |
998 ('tracer', self._collector.tracer_name() if self._collector else "-none-"), |
1165 ('pylib_paths', self.pylib_paths), |
999 ('CTracer', 'available' if CTracer else "unavailable"), |
1166 ('tracer', self.collector.tracer_name()), |
1000 ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)), |
1167 ('plugins.file_tracers', plugin_info(self.plugins.file_tracers)), |
1001 ('plugins.configurers', plugin_info(self._plugins.configurers)), |
1168 ('plugins.configurers', plugin_info(self.plugins.configurers)), |
1002 ('plugins.context_switchers', plugin_info(self._plugins.context_switchers)), |
1169 ('config_files', self.config.attempted_config_files), |
1003 ('configs_attempted', self.config.attempted_config_files), |
1170 ('configs_read', self.config.config_files), |
1004 ('configs_read', self.config.config_files_read), |
1171 ('data_path', self.data_files.filename), |
1005 ('config_file', self.config.config_file), |
|
1006 ('config_contents', |
|
1007 repr(self.config._config_contents) |
|
1008 if self.config._config_contents |
|
1009 else '-none-' |
|
1010 ), |
|
1011 ('data_file', self._data.data_filename() if self._data is not None else "-none-"), |
1172 ('python', sys.version.replace('\n', '')), |
1012 ('python', sys.version.replace('\n', '')), |
1173 ('platform', platform.platform()), |
1013 ('platform', platform.platform()), |
1174 ('implementation', platform.python_implementation()), |
1014 ('implementation', platform.python_implementation()), |
1175 ('executable', sys.executable), |
1015 ('executable', sys.executable), |
|
1016 ('def_encoding', sys.getdefaultencoding()), |
|
1017 ('fs_encoding', sys.getfilesystemencoding()), |
|
1018 ('pid', os.getpid()), |
1176 ('cwd', os.getcwd()), |
1019 ('cwd', os.getcwd()), |
1177 ('path', sys.path), |
1020 ('path', sys.path), |
1178 ('environment', sorted( |
1021 ('environment', sorted( |
1179 ("%s = %s" % (k, v)) |
1022 ("%s = %s" % (k, v)) |
1180 for k, v in iitems(os.environ) |
1023 for k, v in iitems(os.environ) |
1181 if k.startswith(("COV", "PY")) |
1024 if any(slug in k for slug in ("COV", "PY")) |
1182 )), |
1025 )), |
1183 ('command_line', " ".join(getattr(sys, 'argv', ['???']))), |
1026 ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))), |
1184 ] |
1027 ] |
1185 |
1028 |
1186 matcher_names = [ |
1029 if self._inorout: |
1187 'source_match', 'source_pkgs_match', |
1030 info.extend(self._inorout.sys_info()) |
1188 'include_match', 'omit_match', |
1031 |
1189 'cover_match', 'pylib_match', |
1032 info.extend(CoverageData.sys_info()) |
1190 ] |
|
1191 |
|
1192 for matcher_name in matcher_names: |
|
1193 matcher = getattr(self, matcher_name) |
|
1194 if matcher: |
|
1195 matcher_info = matcher.info() |
|
1196 else: |
|
1197 matcher_info = '-none-' |
|
1198 info.append((matcher_name, matcher_info)) |
|
1199 |
1033 |
1200 return info |
1034 return info |
1201 |
1035 |
1202 |
1036 |
1203 def module_is_namespace(mod): |
1037 # Mega debugging... |
1204 """Is the module object `mod` a PEP420 namespace module?""" |
1038 # $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage. |
1205 return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None |
1039 if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging |
1206 |
1040 from coverage.debug import decorate_methods, show_calls |
1207 |
1041 |
1208 def module_has_file(mod): |
1042 Coverage = decorate_methods(show_calls(show_args=True), butnot=['get_data'])(Coverage) |
1209 """Does the module object `mod` have an existing __file__ ?""" |
|
1210 mod__file__ = getattr(mod, '__file__', None) |
|
1211 if mod__file__ is None: |
|
1212 return False |
|
1213 return os.path.exists(mod__file__) |
|
1214 |
|
1215 |
|
1216 # FileDisposition "methods": FileDisposition is a pure value object, so it can |
|
1217 # be implemented in either C or Python. Acting on them is done with these |
|
1218 # functions. |
|
1219 |
|
1220 def _disposition_init(cls, original_filename): |
|
1221 """Construct and initialize a new FileDisposition object.""" |
|
1222 disp = cls() |
|
1223 disp.original_filename = original_filename |
|
1224 disp.canonical_filename = original_filename |
|
1225 disp.source_filename = None |
|
1226 disp.trace = False |
|
1227 disp.reason = "" |
|
1228 disp.file_tracer = None |
|
1229 disp.has_dynamic_filename = False |
|
1230 return disp |
|
1231 |
|
1232 |
|
1233 def _disposition_debug_msg(disp): |
|
1234 """Make a nice debug message of what the FileDisposition is doing.""" |
|
1235 if disp.trace: |
|
1236 msg = "Tracing %r" % (disp.original_filename,) |
|
1237 if disp.file_tracer: |
|
1238 msg += ": will be traced by %r" % disp.file_tracer |
|
1239 else: |
|
1240 msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason) |
|
1241 return msg |
|
1242 |
1043 |
1243 |
1044 |
1244 def process_startup(): |
1045 def process_startup(): |
1245 """Call this at Python start-up to perhaps measure coverage. |
1046 """Call this at Python start-up to perhaps measure coverage. |
1246 |
1047 |