eric6/DebugClients/Python/coverage/control.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
child 7702
f8b97639deb5
equal deleted inserted replaced
7426:dc171b1d8261 7427:362cd1b6f81a
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt 2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3 3
4 """Core control stuff for coverage.py.""" 4 """Core control stuff for coverage.py."""
5 5
6
7 import atexit 6 import atexit
8 import inspect 7 import contextlib
9 import itertools
10 import os 8 import os
9 import os.path
11 import platform 10 import platform
12 import re
13 import sys 11 import sys
14 import time 12 import time
15 import traceback
16 13
17 from coverage import env 14 from coverage import env
18 from coverage.annotate import AnnotateReporter 15 from coverage.annotate import AnnotateReporter
19 from coverage.backward import string_class, iitems 16 from coverage.backward import string_class, iitems
20 from coverage.collector import Collector 17 from coverage.collector import Collector, CTracer
21 from coverage.config import read_coverage_config 18 from coverage.config import read_coverage_config
22 from coverage.data import CoverageData, CoverageDataFiles 19 from coverage.context import should_start_context_test_function, combine_context_switchers
23 from coverage.debug import DebugControl, write_formatted_info 20 from coverage.data import CoverageData, combine_parallel_data
24 from coverage.files import TreeMatcher, FnmatchMatcher 21 from coverage.debug import DebugControl, short_stack, write_formatted_info
25 from coverage.files import PathAliases, find_python_files, prep_patterns 22 from coverage.disposition import disposition_debug_msg
26 from coverage.files import canonical_filename, set_relative_directory 23 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
27 from coverage.files import ModuleMatcher, abs_file
28 from coverage.html import HtmlReporter 24 from coverage.html import HtmlReporter
25 from coverage.inorout import InOrOut
26 from coverage.jsonreport import JsonReporter
29 from coverage.misc import CoverageException, bool_or_none, join_regex 27 from coverage.misc import CoverageException, bool_or_none, join_regex
30 from coverage.misc import file_be_gone, isolate_module 28 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
31 from coverage.plugin import FileReporter 29 from coverage.plugin import FileReporter
32 from coverage.plugin_support import Plugins 30 from coverage.plugin_support import Plugins
33 from coverage.python import PythonFileReporter, source_for_file 31 from coverage.python import PythonFileReporter
32 from coverage.report import render_report
34 from coverage.results import Analysis, Numbers 33 from coverage.results import Analysis, Numbers
35 from coverage.summary import SummaryReporter 34 from coverage.summary import SummaryReporter
36 from coverage.xmlreport import XmlReporter 35 from coverage.xmlreport import XmlReporter
37 36
38 try: 37 try:
41 # Jython has no multiprocessing module. 40 # Jython has no multiprocessing module.
42 patch_multiprocessing = None 41 patch_multiprocessing = None
43 42
44 os = isolate_module(os) 43 os = isolate_module(os)
45 44
46 # Pypy has some unusual stuff in the "stdlib". Consider those locations 45 @contextlib.contextmanager
47 # when deciding where the stdlib is. These modules are not used for anything, 46 def override_config(cov, **kwargs):
48 # they are modules importable from the pypy lib directories, so that we can 47 """Temporarily tweak the configuration of `cov`.
49 # find those directories. 48
50 _structseq = _pypy_irc_topic = None 49 The arguments are applied to `cov.config` with the `from_args` method.
51 if env.PYPY: 50 At the end of the with-statement, the old configuration is restored.
51 """
52 original_config = cov.config
53 cov.config = cov.config.copy()
52 try: 54 try:
53 import _structseq 55 cov.config.from_args(**kwargs)
54 except ImportError: 56 yield
55 pass 57 finally:
56 58 cov.config = original_config
57 try: 59
58 import _pypy_irc_topic 60
59 except ImportError: 61 _DEFAULT_DATAFILE = DefaultValue("MISSING")
60 pass
61
62 62
63 class Coverage(object): 63 class Coverage(object):
64 """Programmatic access to coverage.py. 64 """Programmatic access to coverage.py.
65 65
66 To use:: 66 To use::
71 cov.start() 71 cov.start()
72 #.. call your code .. 72 #.. call your code ..
73 cov.stop() 73 cov.stop()
74 cov.html_report(directory='covhtml') 74 cov.html_report(directory='covhtml')
75 75
76 Note: in keeping with Python custom, names starting with underscore are
77 not part of the public API. They might stop working at any point. Please
78 limit yourself to documented methods to avoid problems.
79
76 """ 80 """
81
82 # The stack of started Coverage instances.
83 _instances = []
84
85 @classmethod
86 def current(cls):
87 """Get the latest started `Coverage` instance, if any.
88
89 Returns: a `Coverage` instance, or None.
90
91 .. versionadded:: 5.0
92
93 """
94 if cls._instances:
95 return cls._instances[-1]
96 else:
97 return None
98
77 def __init__( 99 def __init__(
78 self, data_file=None, data_suffix=None, cover_pylib=None, 100 self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None,
79 auto_data=False, timid=None, branch=None, config_file=True, 101 auto_data=False, timid=None, branch=None, config_file=True,
80 source=None, omit=None, include=None, debug=None, 102 source=None, omit=None, include=None, debug=None,
81 concurrency=None, 103 concurrency=None, check_preimported=False, context=None,
82 ): 104 ):
83 """ 105 """
84 `data_file` is the base name of the data file to use, defaulting to 106 Many of these arguments duplicate and override values that can be
85 ".coverage". `data_suffix` is appended (with a dot) to `data_file` to 107 provided in a configuration file. Parameters that are missing here
86 create the final file name. If `data_suffix` is simply True, then a 108 will use values from the config file.
87 suffix is created with the machine and process identity included. 109
110 `data_file` is the base name of the data file to use. The config value
111 defaults to ".coverage". None can be provided to prevent writing a data
112 file. `data_suffix` is appended (with a dot) to `data_file` to create
113 the final file name. If `data_suffix` is simply True, then a suffix is
114 created with the machine and process identity included.
88 115
89 `cover_pylib` is a boolean determining whether Python code installed 116 `cover_pylib` is a boolean determining whether Python code installed
90 with the Python interpreter is measured. This includes the Python 117 with the Python interpreter is measured. This includes the Python
91 standard library and any packages installed with the interpreter. 118 standard library and any packages installed with the interpreter.
92 119
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
686 Once you invoke :meth:`start`, you must also call :meth:`stop` 511 Once you invoke :meth:`start`, you must also call :meth:`stop`
687 eventually, or your process might not shut down cleanly. 512 eventually, or your process might not shut down cleanly.
688 513
689 """ 514 """
690 self._init() 515 self._init()
691 if self.include: 516 if not self._inited_for_start:
692 if self.source or self.source_pkgs: 517 self._inited_for_start = True
693 self._warn("--include is ignored because --source is set", slug="include-ignored") 518 self._init_for_start()
694 if self.run_suffix: 519 self._post_init()
695 # Calling start() means we're running code, so use the run_suffix 520
696 # as the data_suffix when we eventually save the data. 521 # Issue warnings for possible problems.
697 self.data_suffix = self.run_suffix 522 self._inorout.warn_conflicting_settings()
523
524 # See if we think some code that would eventually be measured has
525 # already been imported.
526 if self._warn_preimported_source:
527 self._inorout.warn_already_imported_files()
528
698 if self._auto_load: 529 if self._auto_load:
699 self.load() 530 self.load()
700 531
701 self.collector.start() 532 self._collector.start()
702 self._started = True 533 self._started = True
534 self._instances.append(self)
703 535
704 def stop(self): 536 def stop(self):
705 """Stop measuring code coverage.""" 537 """Stop measuring code coverage."""
538 if self._instances:
539 if self._instances[-1] is self:
540 self._instances.pop()
706 if self._started: 541 if self._started:
707 self.collector.stop() 542 self._collector.stop()
708 self._started = False 543 self._started = False
709 544
710 def _atexit(self): 545 def _atexit(self):
711 """Clean up on process shutdown.""" 546 """Clean up on process shutdown."""
712 if self.debug.should("process"): 547 if self._debug.should("process"):
713 self.debug.write("atexit: {0!r}".format(self)) 548 self._debug.write("atexit: {!r}".format(self))
714 if self._started: 549 if self._started:
715 self.stop() 550 self.stop()
716 if self._auto_save: 551 if self._auto_save:
717 self.save() 552 self.save()
718 553
719 def erase(self): 554 def erase(self):
720 """Erase previously-collected coverage data. 555 """Erase previously collected coverage data.
721 556
722 This removes the in-memory data collected in this session as well as 557 This removes the in-memory data collected in this session as well as
723 discarding the data file. 558 discarding the data file.
724 559
725 """ 560 """
726 self._init() 561 self._init()
727 self.collector.reset() 562 self._post_init()
728 self.data.erase() 563 if self._collector:
729 self.data_files.erase(parallel=self.config.parallel) 564 self._collector.reset()
565 self._init_data(suffix=None)
566 self._data.erase(parallel=self.config.parallel)
567 self._data = None
568 self._inited_for_start = False
569
570 def switch_context(self, new_context):
571 """Switch to a new dynamic context.
572
573 `new_context` is a string to use as the :ref:`dynamic context
574 <dynamic_contexts>` label for collected data. If a :ref:`static
575 context <static_contexts>` is in use, the static and dynamic context
576 labels will be joined together with a pipe character.
577
578 Coverage collection must be started already.
579
580 .. versionadded:: 5.0
581
582 """
583 if not self._started: # pragma: part started
584 raise CoverageException(
585 "Cannot switch context, coverage is not started"
586 )
587
588 if self._collector.should_start_context:
589 self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
590
591 self._collector.switch_context(new_context)
730 592
731 def clear_exclude(self, which='exclude'): 593 def clear_exclude(self, which='exclude'):
732 """Clear the exclude list.""" 594 """Clear the exclude list."""
733 self._init() 595 self._init()
734 setattr(self.config, which + "_list", []) 596 setattr(self.config, which + "_list", [])
775 self._init() 637 self._init()
776 return getattr(self.config, which + "_list") 638 return getattr(self.config, which + "_list")
777 639
778 def save(self): 640 def save(self):
779 """Save the collected coverage data to the data file.""" 641 """Save the collected coverage data to the data file."""
780 self._init() 642 data = self.get_data()
781 self.get_data() 643 data.write()
782 self.data_files.write(self.data, suffix=self.data_suffix)
783 644
784 def combine(self, data_paths=None, strict=False): 645 def combine(self, data_paths=None, strict=False):
785 """Combine together a number of similarly-named coverage data files. 646 """Combine together a number of similarly-named coverage data files.
786 647
787 All coverage data files whose name starts with `data_file` (from the 648 All coverage data files whose name starts with `data_file` (from the
802 .. versionadded:: 4.3 663 .. versionadded:: 4.3
803 The `strict` parameter. 664 The `strict` parameter.
804 665
805 """ 666 """
806 self._init() 667 self._init()
668 self._init_data(suffix=None)
669 self._post_init()
807 self.get_data() 670 self.get_data()
808 671
809 aliases = None 672 aliases = None
810 if self.config.paths: 673 if self.config.paths:
811 aliases = PathAliases() 674 aliases = PathAliases()
812 for paths in self.config.paths.values(): 675 for paths in self.config.paths.values():
813 result = paths[0] 676 result = paths[0]
814 for pattern in paths[1:]: 677 for pattern in paths[1:]:
815 aliases.add(pattern, result) 678 aliases.add(pattern, result)
816 679
817 self.data_files.combine_parallel_data( 680 combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict)
818 self.data, aliases=aliases, data_paths=data_paths, strict=strict,
819 )
820 681
821 def get_data(self): 682 def get_data(self):
822 """Get the collected data. 683 """Get the collected data.
823 684
824 Also warn about various problems collecting data. 685 Also warn about various problems collecting data.
827 688
828 .. versionadded:: 4.0 689 .. versionadded:: 4.0
829 690
830 """ 691 """
831 self._init() 692 self._init()
832 693 self._init_data(suffix=None)
833 if self.collector.save_data(self.data): 694 self._post_init()
695
696 if self._collector and self._collector.flush_data():
834 self._post_save_work() 697 self._post_save_work()
835 698
836 return self.data 699 return self._data
837 700
838 def _post_save_work(self): 701 def _post_save_work(self):
839 """After saving data, look for warnings, post-work, etc. 702 """After saving data, look for warnings, post-work, etc.
840 703
841 Warn about things that should have happened but didn't. 704 Warn about things that should have happened but didn't.
843 706
844 """ 707 """
845 # If there are still entries in the source_pkgs_unmatched list, 708 # If there are still entries in the source_pkgs_unmatched list,
846 # then we never encountered those packages. 709 # then we never encountered those packages.
847 if self._warn_unimported_source: 710 if self._warn_unimported_source:
848 for pkg in self.source_pkgs_unmatched: 711 self._inorout.warn_unimported_source()
849 self._warn_about_unmeasured_code(pkg)
850 712
851 # Find out if we got any data. 713 # Find out if we got any data.
852 if not self.data and self._warn_no_data: 714 if not self._data and self._warn_no_data:
853 self._warn("No data was collected.", slug="no-data-collected") 715 self._warn("No data was collected.", slug="no-data-collected")
854 716
855 # Find files that were never executed at all. 717 # Touch all the files that could have executed, so that we can
856 for pkg in self.source_pkgs: 718 # mark completely unexecuted files as 0% covered.
857 if (not pkg in sys.modules or 719 if self._data is not None:
858 not module_has_file(sys.modules[pkg])): 720 for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files():
859 continue 721 file_path = self._file_mapper(file_path)
860 pkg_file = source_for_file(sys.modules[pkg].__file__) 722 self._data.touch_file(file_path, plugin_name)
861 self._find_unexecuted_files(self._canonical_path(pkg_file))
862
863 for src in self.source:
864 self._find_unexecuted_files(src)
865 723
866 if self.config.note: 724 if self.config.note:
867 self.data.add_run_info(note=self.config.note) 725 self._warn("The '[run] note' setting is no longer supported.")
868
869 def _warn_about_unmeasured_code(self, pkg):
870 """Warn about a package or module that we never traced.
871
872 `pkg` is a string, the name of the package or module.
873
874 """
875 mod = sys.modules.get(pkg)
876 if mod is None:
877 self._warn("Module %s was never imported." % pkg, slug="module-not-imported")
878 return
879
880 if module_is_namespace(mod):
881 # A namespace package. It's OK for this not to have been traced,
882 # since there is no code directly in it.
883 return
884
885 if not module_has_file(mod):
886 self._warn("Module %s has no Python source." % pkg, slug="module-not-python")
887 return
888
889 # The module was in sys.modules, and seems like a module with code, but
890 # we never measured it. I guess that means it was imported before
891 # coverage even started.
892 self._warn(
893 "Module %s was previously imported, but not measured" % pkg,
894 slug="module-not-measured",
895 )
896
897 def _find_plugin_files(self, src_dir):
898 """Get executable files from the plugins."""
899 for plugin in self.plugins.file_tracers:
900 for x_file in plugin.find_executable_files(src_dir):
901 yield x_file, plugin._coverage_plugin_name
902
903 def _find_unexecuted_files(self, src_dir):
904 """Find unexecuted files in `src_dir`.
905
906 Search for files in `src_dir` that are probably importable,
907 and add them as unexecuted files in `self.data`.
908
909 """
910 py_files = ((py_file, None) for py_file in find_python_files(src_dir))
911 plugin_files = self._find_plugin_files(src_dir)
912
913 for file_path, plugin_name in itertools.chain(py_files, plugin_files):
914 file_path = canonical_filename(file_path)
915 if self.omit_match and self.omit_match.match(file_path):
916 # Turns out this file was omitted, so don't pull it back
917 # in as unexecuted.
918 continue
919 self.data.touch_file(file_path, plugin_name)
920 726
921 # Backward compatibility with version 1. 727 # Backward compatibility with version 1.
922 def analysis(self, morf): 728 def analysis(self, morf):
923 """Like `analysis2` but doesn't return excluded line numbers.""" 729 """Like `analysis2` but doesn't return excluded line numbers."""
924 f, s, _, m, mf = self.analysis2(morf) 730 f, s, _, m, mf = self.analysis2(morf)
939 745
940 The analysis uses the source file itself and the current measured 746 The analysis uses the source file itself and the current measured
941 coverage data. 747 coverage data.
942 748
943 """ 749 """
944 self._init()
945 analysis = self._analyze(morf) 750 analysis = self._analyze(morf)
946 return ( 751 return (
947 analysis.filename, 752 analysis.filename,
948 sorted(analysis.statements), 753 sorted(analysis.statements),
949 sorted(analysis.excluded), 754 sorted(analysis.excluded),
955 """Analyze a single morf or code unit. 760 """Analyze a single morf or code unit.
956 761
957 Returns an `Analysis` object. 762 Returns an `Analysis` object.
958 763
959 """ 764 """
960 self.get_data() 765 # All reporting comes through here, so do reporting initialization.
766 self._init()
767 Numbers.set_precision(self.config.precision)
768 self._post_init()
769
770 data = self.get_data()
961 if not isinstance(it, FileReporter): 771 if not isinstance(it, FileReporter):
962 it = self._get_file_reporter(it) 772 it = self._get_file_reporter(it)
963 773
964 return Analysis(self.data, it) 774 return Analysis(data, it, self._file_mapper)
965 775
966 def _get_file_reporter(self, morf): 776 def _get_file_reporter(self, morf):
967 """Get a FileReporter for a module or file name.""" 777 """Get a FileReporter for a module or file name."""
968 plugin = None 778 plugin = None
969 file_reporter = "python" 779 file_reporter = "python"
970 780
971 if isinstance(morf, string_class): 781 if isinstance(morf, string_class):
972 abs_morf = abs_file(morf) 782 mapped_morf = self._file_mapper(morf)
973 plugin_name = self.data.file_tracer(abs_morf) 783 plugin_name = self._data.file_tracer(mapped_morf)
974 if plugin_name: 784 if plugin_name:
975 plugin = self.plugins.get(plugin_name) 785 plugin = self._plugins.get(plugin_name)
976 786
977 if plugin: 787 if plugin:
978 file_reporter = plugin.file_reporter(abs_morf) 788 file_reporter = plugin.file_reporter(mapped_morf)
979 if file_reporter is None: 789 if file_reporter is None:
980 raise CoverageException( 790 raise CoverageException(
981 "Plugin %r did not provide a file reporter for %r." % ( 791 "Plugin %r did not provide a file reporter for %r." % (
982 plugin._coverage_plugin_name, morf 792 plugin._coverage_plugin_name, morf
983 ) 793 )
984 ) 794 )
985 795
986 if file_reporter == "python": 796 if file_reporter == "python":
987 file_reporter = PythonFileReporter(morf, self) 797 file_reporter = PythonFileReporter(morf, self)
988 798
989 return file_reporter 799 return file_reporter
998 FileReporter. If `morfs` is empty or None, then the list of all files 808 FileReporter. If `morfs` is empty or None, then the list of all files
999 measured is used to find the FileReporters. 809 measured is used to find the FileReporters.
1000 810
1001 """ 811 """
1002 if not morfs: 812 if not morfs:
1003 morfs = self.data.measured_files() 813 morfs = self._data.measured_files()
1004 814
1005 # Be sure we have a list. 815 # Be sure we have a collection.
1006 if not isinstance(morfs, (list, tuple)): 816 if not isinstance(morfs, (list, tuple, set)):
1007 morfs = [morfs] 817 morfs = [morfs]
1008 818
1009 file_reporters = [] 819 file_reporters = [self._get_file_reporter(morf) for morf in morfs]
1010 for morf in morfs:
1011 file_reporter = self._get_file_reporter(morf)
1012 file_reporters.append(file_reporter)
1013
1014 return file_reporters 820 return file_reporters
1015 821
1016 def report( 822 def report(
1017 self, morfs=None, show_missing=None, ignore_errors=None, 823 self, morfs=None, show_missing=None, ignore_errors=None,
1018 file=None, # pylint: disable=redefined-builtin 824 file=None, omit=None, include=None, skip_covered=None,
1019 omit=None, include=None, skip_covered=None, 825 contexts=None, skip_empty=None,
1020 ): 826 ):
1021 """Write a summary report to `file`. 827 """Write a textual summary report to `file`.
1022 828
1023 Each module in `morfs` is listed, with counts of statements, executed 829 Each module in `morfs` is listed, with counts of statements, executed
1024 statements, missing statements, and a list of lines missed. 830 statements, missing statements, and a list of lines missed.
831
832 If `show_missing` is true, then details of which lines or branches are
833 missing will be included in the report. If `ignore_errors` is true,
834 then a failure while reporting a single file will not stop the entire
835 report.
836
837 `file` is a file-like object, suitable for writing.
1025 838
1026 `include` is a list of file name patterns. Files that match will be 839 `include` is a list of file name patterns. Files that match will be
1027 included in the report. Files matching `omit` will not be included in 840 included in the report. Files matching `omit` will not be included in
1028 the report. 841 the report.
1029 842
1030 If `skip_covered` is True, don't report on files with 100% coverage. 843 If `skip_covered` is true, don't report on files with 100% coverage.
844
845 If `skip_empty` is true, don't report on empty files (those that have
846 no statements).
847
848 `contexts` is a list of regular expressions. Only data from
849 :ref:`dynamic contexts <dynamic_contexts>` that match one of those
850 expressions (using :func:`re.search <python:re.search>`) will be
851 included in the report.
852
853 All of the arguments default to the settings read from the
854 :ref:`configuration file <config>`.
1031 855
1032 Returns a float, the total percentage covered. 856 Returns a float, the total percentage covered.
1033 857
1034 """ 858 .. versionadded:: 4.0
1035 self.get_data() 859 The `skip_covered` parameter.
1036 self.config.from_args( 860
861 .. versionadded:: 5.0
862 The `contexts` and `skip_empty` parameters.
863
864 """
865 with override_config(
866 self,
1037 ignore_errors=ignore_errors, report_omit=omit, report_include=include, 867 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1038 show_missing=show_missing, skip_covered=skip_covered, 868 show_missing=show_missing, skip_covered=skip_covered,
1039 ) 869 report_contexts=contexts, skip_empty=skip_empty,
1040 reporter = SummaryReporter(self, self.config) 870 ):
1041 return reporter.report(morfs, outfile=file) 871 reporter = SummaryReporter(self)
872 return reporter.report(morfs, outfile=file)
1042 873
1043 def annotate( 874 def annotate(
1044 self, morfs=None, directory=None, ignore_errors=None, 875 self, morfs=None, directory=None, ignore_errors=None,
1045 omit=None, include=None, 876 omit=None, include=None, contexts=None,
1046 ): 877 ):
1047 """Annotate a list of modules. 878 """Annotate a list of modules.
1048 879
1049 Each module in `morfs` is annotated. The source is written to a new 880 Each module in `morfs` is annotated. The source is written to a new
1050 file, named with a ",cover" suffix, with each line prefixed with a 881 file, named with a ",cover" suffix, with each line prefixed with a
1052 excluded lines have "-", and missing lines have "!". 883 excluded lines have "-", and missing lines have "!".
1053 884
1054 See :meth:`report` for other arguments. 885 See :meth:`report` for other arguments.
1055 886
1056 """ 887 """
1057 self.get_data() 888 with override_config(self,
1058 self.config.from_args( 889 ignore_errors=ignore_errors, report_omit=omit,
1059 ignore_errors=ignore_errors, report_omit=omit, report_include=include 890 report_include=include, report_contexts=contexts,
1060 ) 891 ):
1061 reporter = AnnotateReporter(self, self.config) 892 reporter = AnnotateReporter(self)
1062 reporter.report(morfs, directory=directory) 893 reporter.report(morfs, directory=directory)
1063 894
1064 def html_report(self, morfs=None, directory=None, ignore_errors=None, 895 def html_report(self, morfs=None, directory=None, ignore_errors=None,
1065 omit=None, include=None, extra_css=None, title=None, 896 omit=None, include=None, extra_css=None, title=None,
1066 skip_covered=None): 897 skip_covered=None, show_contexts=None, contexts=None,
898 skip_empty=None):
1067 """Generate an HTML report. 899 """Generate an HTML report.
1068 900
1069 The HTML is written to `directory`. The file "index.html" is the 901 The HTML is written to `directory`. The file "index.html" is the
1070 overview starting point, with links to more detailed pages for 902 overview starting point, with links to more detailed pages for
1071 individual modules. 903 individual modules.
1078 910
1079 See :meth:`report` for other arguments. 911 See :meth:`report` for other arguments.
1080 912
1081 Returns a float, the total percentage covered. 913 Returns a float, the total percentage covered.
1082 914
1083 """ 915 .. note::
1084 self.get_data() 916 The HTML report files are generated incrementally based on the
1085 self.config.from_args( 917 source files and coverage results. If you modify the report files,
918 the changes will not be considered. You should be careful about
919 changing the files in the report folder.
920
921 """
922 with override_config(self,
1086 ignore_errors=ignore_errors, report_omit=omit, report_include=include, 923 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1087 html_dir=directory, extra_css=extra_css, html_title=title, 924 html_dir=directory, extra_css=extra_css, html_title=title,
1088 skip_covered=skip_covered, 925 skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts,
1089 ) 926 skip_empty=skip_empty,
1090 reporter = HtmlReporter(self, self.config) 927 ):
1091 return reporter.report(morfs) 928 reporter = HtmlReporter(self)
929 return reporter.report(morfs)
1092 930
1093 def xml_report( 931 def xml_report(
1094 self, morfs=None, outfile=None, ignore_errors=None, 932 self, morfs=None, outfile=None, ignore_errors=None,
1095 omit=None, include=None, 933 omit=None, include=None, contexts=None,
1096 ): 934 ):
1097 """Generate an XML report of coverage results. 935 """Generate an XML report of coverage results.
1098 936
1099 The report is compatible with Cobertura reports. 937 The report is compatible with Cobertura reports.
1100 938
1104 See :meth:`report` for other arguments. 942 See :meth:`report` for other arguments.
1105 943
1106 Returns a float, the total percentage covered. 944 Returns a float, the total percentage covered.
1107 945
1108 """ 946 """
1109 self.get_data() 947 with override_config(self,
1110 self.config.from_args(
1111 ignore_errors=ignore_errors, report_omit=omit, report_include=include, 948 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1112 xml_output=outfile, 949 xml_output=outfile, report_contexts=contexts,
1113 ) 950 ):
1114 file_to_close = None 951 return render_report(self.config.xml_output, XmlReporter(self), morfs)
1115 delete_file = False 952
1116 if self.config.xml_output: 953 def json_report(
1117 if self.config.xml_output == '-': 954 self, morfs=None, outfile=None, ignore_errors=None,
1118 outfile = sys.stdout 955 omit=None, include=None, contexts=None, pretty_print=None,
1119 else: 956 show_contexts=None
1120 # Ensure that the output directory is created; done here 957 ):
1121 # because this report pre-opens the output file. 958 """Generate a JSON report of coverage results.
1122 # HTMLReport does this using the Report plumbing because 959
1123 # its task is more complex, being multiple files. 960 Each module in `morfs` is included in the report. `outfile` is the
1124 output_dir = os.path.dirname(self.config.xml_output) 961 path to write the file to, "-" will write to stdout.
1125 if output_dir and not os.path.isdir(output_dir): 962
1126 os.makedirs(output_dir) 963 See :meth:`report` for other arguments.
1127 open_kwargs = {} 964
1128 if env.PY3: 965 Returns a float, the total percentage covered.
1129 open_kwargs['encoding'] = 'utf8' 966
1130 outfile = open(self.config.xml_output, "w", **open_kwargs) 967 .. versionadded:: 5.0
1131 file_to_close = outfile 968
1132 try: 969 """
1133 reporter = XmlReporter(self, self.config) 970 with override_config(self,
1134 return reporter.report(morfs, outfile=outfile) 971 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1135 except CoverageException: 972 json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
1136 delete_file = True 973 json_show_contexts=show_contexts
1137 raise 974 ):
1138 finally: 975 return render_report(self.config.json_output, JsonReporter(self), morfs)
1139 if file_to_close:
1140 file_to_close.close()
1141 if delete_file:
1142 file_be_gone(self.config.xml_output)
1143 976
1144 def sys_info(self): 977 def sys_info(self):
1145 """Return a list of (key, value) pairs showing internal information.""" 978 """Return a list of (key, value) pairs showing internal information."""
1146 979
1147 import coverage as covmod 980 import coverage as covmod
1148 981
1149 self._init() 982 self._init()
983 self._post_init()
1150 984
1151 def plugin_info(plugins): 985 def plugin_info(plugins):
1152 """Make an entry for the sys_info from a list of plug-ins.""" 986 """Make an entry for the sys_info from a list of plug-ins."""
1153 entries = [] 987 entries = []
1154 for plugin in plugins: 988 for plugin in plugins:
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
1284 # started coverage.py in this process. Nothing to do. 1085 # started coverage.py in this process. Nothing to do.
1285 return None 1086 return None
1286 1087
1287 cov = Coverage(config_file=cps) 1088 cov = Coverage(config_file=cps)
1288 process_startup.coverage = cov 1089 process_startup.coverage = cov
1289 cov.start()
1290 cov._warn_no_data = False 1090 cov._warn_no_data = False
1291 cov._warn_unimported_source = False 1091 cov._warn_unimported_source = False
1092 cov._warn_preimported_source = False
1292 cov._auto_save = True 1093 cov._auto_save = True
1094 cov.start()
1293 1095
1294 return cov 1096 return cov
1295 1097
1296 1098
1297 def _prevent_sub_process_measurement(): 1099 def _prevent_sub_process_measurement():

eric ide

mercurial