Coverage for coverage / control.py: 89.645%
477 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Central control stuff for coverage.py."""
6from __future__ import annotations 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
8import atexit 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
9import collections 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
10import contextlib 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
11import datetime 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
12import functools 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
13import os 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
14import os.path 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
15import platform 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
16import signal 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
17import sys 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
18import threading 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
19import time 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
20import warnings 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
21from collections.abc import Iterable, Iterator 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
22from types import FrameType 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
23from typing import IO, Any, Callable, cast 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
25from coverage import env 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
26from coverage.annotate import AnnotateReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
27from coverage.collector import Collector 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
28from coverage.config import CoverageConfig, read_coverage_config 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
29from coverage.context import combine_context_switchers, should_start_context_test_function 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
30from coverage.core import CTRACER_FILE, Core 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
31from coverage.data import CoverageData, combine_parallel_data 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
32from coverage.debug import ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
33 DebugControl,
34 NoDebugging,
35 relevant_environment_display,
36 short_stack,
37 write_formatted_info,
38)
39from coverage.disposition import disposition_debug_msg 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
40from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
41from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
42from coverage.html import HtmlReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
43from coverage.inorout import InOrOut 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
44from coverage.jsonreport import JsonReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
45from coverage.lcovreport import LcovReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
46from coverage.misc import ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
47 DefaultValue,
48 bool_or_none,
49 ensure_dir_for_file,
50 isolate_module,
51 join_regex,
52)
53from coverage.multiproc import patch_multiprocessing 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
54from coverage.patch import apply_patches 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
55from coverage.plugin import FileReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
56from coverage.plugin_support import Plugins, TCoverageInit 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
57from coverage.python import PythonFileReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
58from coverage.report import SummaryReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
59from coverage.report_core import render_report 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
60from coverage.results import Analysis, analysis_from_file_reporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
61from coverage.types import ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
62 FilePath,
63 TConfigSectionIn,
64 TConfigurable,
65 TConfigValueIn,
66 TConfigValueOut,
67 TFileDisposition,
68 TLineNo,
69 TMorf,
70)
71from coverage.version import __url__ 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
72from coverage.xmlreport import XmlReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
74os = isolate_module(os) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
77@contextlib.contextmanager 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
78def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
79 """Temporarily tweak the configuration of `cov`.
81 The arguments are applied to `cov.config` with the `from_args` method.
82 At the end of the with-statement, the old configuration is restored.
83 """
84 original_config = cov.config 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
85 cov.config = cov.config.copy() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
86 try: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
87 cov.config.from_args(**kwargs) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
88 yield 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
89 finally:
90 cov.config = original_config 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
93DEFAULT_DATAFILE = DefaultValue("MISSING") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
94_DEFAULT_DATAFILE = DEFAULT_DATAFILE # Just in case, for backwards compatibility 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
95CONFIG_DATA_PREFIX = ":data:" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
98class Coverage(TConfigurable): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
99 """Programmatic access to coverage.py. 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
101 To use::
103 from coverage import Coverage
105 cov = Coverage()
106 cov.start()
107 #.. call your code ..
108 cov.stop()
109 cov.html_report(directory="covhtml")
111 A context manager is available to do the same thing::
113 cov = Coverage()
114 with cov.collect():
115 #.. call your code ..
116 cov.html_report(directory="covhtml")
118 Note: in keeping with Python custom, names starting with underscore are
119 not part of the public API. They might stop working at any point. Please
120 limit yourself to documented methods to avoid problems.
122 Methods can raise any of the exceptions described in :ref:`api_exceptions`.
124 """
126 # The stack of started Coverage instances.
127 _instances: list[Coverage] = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
129 @classmethod 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
130 def current(cls) -> Coverage | None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
131 """Get the latest started `Coverage` instance, if any.
133 Returns: a `Coverage` instance, or None.
135 .. versionadded:: 5.0
137 """
138 if cls._instances: 138 ↛ 141line 138 didn't jump to line 141 because the condition on line 138 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
139 return cls._instances[-1] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
140 else:
141 return None
143 def __init__( # pylint: disable=too-many-arguments 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
144 self,
145 data_file: FilePath | DefaultValue | None = DEFAULT_DATAFILE,
146 data_suffix: str | bool | None = None,
147 cover_pylib: bool | None = None,
148 auto_data: bool = False,
149 timid: bool | None = None,
150 branch: bool | None = None,
151 config_file: FilePath | bool = True,
152 source: Iterable[str] | None = None,
153 source_pkgs: Iterable[str] | None = None,
154 source_dirs: Iterable[str] | None = None,
155 omit: str | Iterable[str] | None = None,
156 include: str | Iterable[str] | None = None,
157 debug: Iterable[str] | None = None,
158 concurrency: str | Iterable[str] | None = None,
159 check_preimported: bool = False,
160 context: str | None = None,
161 messages: bool = False,
162 plugins: Iterable[Callable[..., None]] | None = None,
163 ) -> None:
164 """
165 Many of these arguments duplicate and override values that can be
166 provided in a configuration file. Parameters that are missing here
167 will use values from the config file.
169 `data_file` is the base name of the data file to use. The config value
170 defaults to ".coverage". None can be provided to prevent writing a data
171 file. `data_suffix` is appended (with a dot) to `data_file` to create
172 the final file name. If `data_suffix` is simply True, then a suffix is
173 created with the machine and process identity included.
175 `cover_pylib` is a boolean determining whether Python code installed
176 with the Python interpreter is measured. This includes the Python
177 standard library and any packages installed with the interpreter.
179 If `auto_data` is true, then any existing data file will be read when
180 coverage measurement starts, and data will be saved automatically when
181 measurement stops.
183 If `timid` is true, then a slower and simpler trace function will be
184 used. This is important for some environments where manipulation of
185 tracing functions breaks the faster trace function.
187 If `branch` is true, then branch coverage will be measured in addition
188 to the usual statement coverage.
190 `config_file` determines what configuration file to read:
192 * If it is ".coveragerc", it is interpreted as if it were True,
193 for backward compatibility.
195 * If it is a string, it is the name of the file to read. If the
196 file can't be read, it is an error.
198 * If it is True, then a few standard files names are tried
199 (".coveragerc", "setup.cfg", "tox.ini"). It is not an error for
200 these files to not be found.
202 * If it is False, then no configuration file is read.
204 `source` is a list of file paths or package names. Only code located
205 in the trees indicated by the file paths or package names will be
206 measured.
208 `source_pkgs` is a list of package names. It works the same as
209 `source`, but can be used to name packages where the name can also be
210 interpreted as a file path.
212 `source_dirs` is a list of file paths. It works the same as
213 `source`, but raises an error if the path doesn't exist, rather
214 than being treated as a package name.
216 `include` and `omit` are lists of file name patterns. Files that match
217 `include` will be measured, files that match `omit` will not. Each
218 will also accept a single string argument.
220 `debug` is a list of strings indicating what debugging information is
221 desired.
223 `concurrency` is a string indicating the concurrency library being used
224 in the measured code. Without this, coverage.py will get incorrect
225 results if these libraries are in use. Valid strings are "greenlet",
226 "eventlet", "gevent", "multiprocessing", or "thread" (the default).
227 This can also be a list of these strings.
229 If `check_preimported` is true, then when coverage is started, the
230 already-imported files will be checked to see if they should be
231 measured by coverage. Importing measured files before coverage is
232 started can mean that code is missed.
234 `context` is a string to use as the :ref:`static context
235 <static_contexts>` label for collected data.
237 If `messages` is true, some messages will be printed to stdout
238 indicating what is happening.
240 If `plugins` are passed, they are an iterable of function objects
241 accepting a `reg` object to register plugins, as described in
242 :ref:`api_plugin`. When they are provided, they will override the
243 plugins found in the coverage configuration file.
245 .. versionadded:: 4.0
246 The `concurrency` parameter.
248 .. versionadded:: 4.2
249 The `concurrency` parameter can now be a list of strings.
251 .. versionadded:: 5.0
252 The `check_preimported` and `context` parameters.
254 .. versionadded:: 5.3
255 The `source_pkgs` parameter.
257 .. versionadded:: 6.0
258 The `messages` parameter.
260 .. versionadded:: 7.7
261 The `plugins` parameter.
263 .. versionadded:: 7.8
264 The `source_dirs` parameter.
265 """
266 # Start self.config as a usable default configuration. It will soon be
267 # replaced with the real configuration.
268 self.config = CoverageConfig() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
270 # data_file=None means no disk file at all. data_file missing means
271 # use the value from the config file.
272 self._no_disk = data_file is None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
273 if isinstance(data_file, DefaultValue): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
274 data_file = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
275 if data_file is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
276 data_file = os.fspath(data_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YPEm4Fn9ZQGo$Hp!0RIq6Jr#1S235
278 # This is injectable by tests.
279 self._debug_file: IO[str] | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
281 self._auto_load = self._auto_save = auto_data 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
282 self._data_suffix_specified = data_suffix 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
284 # Is it ok for no data to be collected?
285 self._warn_no_data = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
286 self._warn_unimported_source = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
287 self._warn_preimported_source = check_preimported 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
288 self._no_warn_slugs: set[str] = set() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
289 self._messages = messages 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
291 # A record of all the warnings that have been issued.
292 self._warnings: list[str] = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
294 # Other instance attributes, set with placebos or placeholders.
295 # More useful objects will be created later.
296 self._debug: DebugControl = NoDebugging() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
297 self._inorout: InOrOut | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
298 self._plugins: Plugins = Plugins() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
299 self._plugin_override = cast(Iterable[TCoverageInit] | None, plugins) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
300 self._data: CoverageData | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
301 self._data_to_close: list[CoverageData] = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
302 self._core: Core | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
303 self._collector: Collector | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
304 self._metacov = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
306 self._file_mapper: Callable[[str], str] = abs_file 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
307 self._data_suffix = self._run_suffix = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
308 self._exclude_re: dict[str, str] = {} 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
309 self._old_sigterm: Callable[[int, FrameType | None], Any] | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
311 # State machine variables:
312 # Have we initialized everything?
313 self._inited = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
314 self._inited_for_start = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
315 # Have we started collecting and not stopped it?
316 self._started = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
317 # Should we write the debug output?
318 self._should_write_debug = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
320 # Build our configuration from a number of sources.
321 if isinstance(config_file, str) and config_file.startswith(CONFIG_DATA_PREFIX): 321 ↛ 322line 321 didn't jump to line 322 because the condition on line 321 was never true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
322 self.config = CoverageConfig.deserialize(config_file[len(CONFIG_DATA_PREFIX) :])
323 else:
324 if not isinstance(config_file, bool): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
325 config_file = os.fspath(config_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
326 self.config = read_coverage_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
327 config_file=config_file,
328 warn=self._warn,
329 data_file=data_file,
330 cover_pylib=cover_pylib,
331 timid=timid,
332 branch=branch,
333 parallel=bool_or_none(data_suffix),
334 source=source,
335 source_pkgs=source_pkgs,
336 source_dirs=source_dirs,
337 run_omit=omit,
338 run_include=include,
339 debug=debug,
340 report_omit=omit,
341 report_include=include,
342 concurrency=concurrency,
343 context=context,
344 )
346 # If we have subprocess measurement happening automatically, then we
347 # want any explicit creation of a Coverage object to mean, this process
348 # is already coverage-aware, so don't auto-measure it. By now, the
349 # auto-creation of a Coverage object has already happened. But we can
350 # find it and tell it not to save its data.
351 if not env.METACOV: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
352 _prevent_sub_process_measurement()
354 def __repr__(self) -> str: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
355 core_name = self._core.tracer_class.__name__ if self._core is not None else "-none-" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
356 data_file = repr(self._data._filename) if self._data is not None else "-none-" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
357 return ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
358 "<Coverage" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
359 + f" @0x{id(self):x}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
360 + f" core={core_name}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
361 + f" data_file={data_file}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
362 + ">" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
363 )
365 def _init(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
366 """Set all the initial state.
368 This is called by the public methods to initialize state. This lets us
369 construct a :class:`Coverage` object, then tweak its state before this
370 function is called.
372 """
373 if self._inited: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
374 return 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
376 self._inited = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
378 # Create and configure the debugging controller.
379 self._debug = DebugControl(self.config.debug, self._debug_file, self.config.debug_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
380 if self._debug.should("process"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
381 self._debug.write("Coverage._init") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YPEm4Fn9ZQGo$Hp!0RIq6Jr#1S235
383 if "multiprocessing" in (self.config.concurrency or ()): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
384 # Multi-processing uses parallel for the subprocesses, so also use
385 # it for the main process.
386 self.config.parallel = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
388 # _exclude_re is a dict that maps exclusion list names to compiled regexes.
389 self._exclude_re = {} 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
391 set_relative_directory() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
392 if self.config.relative_files: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
393 self._file_mapper = relative_filename 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp!0RIqJr1S235
395 # Load plugins
396 self._plugins = Plugins(self._debug) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
397 if self._plugin_override: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
398 self._plugins.load_from_callables(self._plugin_override) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
399 else:
400 self._plugins.load_from_config(self.config.plugins, self.config) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
402 # Run configuring plugins.
403 for plugin in self._plugins.configurers: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
404 # We need an object with set_option and get_option. Either self or
405 # self.config will do. Choosing randomly stops people from doing
406 # other things with those objects, against the public API. Yes,
407 # this is a bit childish. :)
408 plugin.configure([self, self.config][int(time.time()) % 2]) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
410 def _post_init(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
411 """Stuff to do after everything is initialized."""
412 if self._should_write_debug: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
413 self._should_write_debug = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
414 self._write_startup_debug() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
416 # "[run] _crash" will raise an exception if the value is close by in
417 # the call stack, for testing error handling.
418 if self.config._crash and self.config._crash in short_stack(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
419 raise RuntimeError(f"Crashing because called by {self.config._crash}") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7DlYP%Em4FnZQ'Go$Hp0R(Iq6Jr1S)235
421 def _write_startup_debug(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
422 """Write out debug info at startup if needed."""
423 wrote_any = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
424 with self._debug.without_callers(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
425 if self._debug.should("config"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
426 write_formatted_info(self._debug.write, "config", self.config.debug_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
427 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
429 if self._debug.should("sys"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
430 write_formatted_info(self._debug.write, "sys", self.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
431 for plugin in self._plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
432 header = "sys: " + plugin._coverage_plugin_name 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
433 write_formatted_info(self._debug.write, header, plugin.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
434 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
436 if self._debug.should("pybehave"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
437 write_formatted_info(self._debug.write, "pybehave", env.debug_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
438 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
440 if self._debug.should("sqlite"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
441 write_formatted_info(self._debug.write, "sqlite", CoverageData.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
442 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
444 if wrote_any: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
445 write_formatted_info(self._debug.write, "end", ()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
447 def _should_trace(self, filename: str, frame: FrameType) -> TFileDisposition: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
448 """Decide whether to trace execution in `filename`.
450 Calls `_should_trace_internal`, and returns the FileDisposition.
452 """
453 assert self._inorout is not None
454 disp = self._inorout.should_trace(filename, frame)
455 if self._debug.should("trace"):
456 self._debug.write(disposition_debug_msg(disp))
457 return disp
459 def _check_include_omit_etc(self, filename: str, frame: FrameType) -> bool: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
460 """Check a file name against the include/omit/etc, rules, verbosely.
462 Returns a boolean: True if the file should be traced, False if not.
464 """
465 assert self._inorout is not None
466 reason = self._inorout.check_include_omit_etc(filename, frame)
467 if self._debug.should("trace"):
468 if not reason:
469 msg = f"Including {filename!r}"
470 else:
471 msg = f"Not including {filename!r}: {reason}"
472 self._debug.write(msg)
474 return not reason
476 def _warn(self, msg: str, slug: str | None = None, once: bool = False) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
477 """Use `msg` as a warning.
479 For warning suppression, use `slug` as the shorthand.
481 If `once` is true, only show this warning once (determined by the
482 slug.)
484 """
485 if not self._no_warn_slugs: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
486 self._no_warn_slugs = set(self.config.disable_warnings) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
488 if slug in self._no_warn_slugs: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
489 # Don't issue the warning
490 return 1satbTKucvdULwexfVMygzhWNAiBjXOCkDl8YP%EmFn9ZQ'GoHp!0R(IqJr#1S)235
492 self._warnings.append(msg) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
493 if slug: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
494 msg = f"{msg} ({slug}); see {__url__}/messages.html#warning-{slug}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
495 if self._debug.should("pid"): 495 ↛ 496line 495 didn't jump to line 496 because the condition on line 495 was never true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
496 msg = f"[{os.getpid()}] {msg}"
497 warnings.warn(msg, category=CoverageWarning, stacklevel=2) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
499 if once: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
500 assert slug is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'GoHp!0R(IqJr#1S235
501 self._no_warn_slugs.add(slug) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'GoHp!0R(IqJr#1S235
503 def _message(self, msg: str) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
504 """Write a message to the user, if configured to do so."""
505 if self._messages: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
506 print(msg) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
508 def get_option(self, option_name: str) -> TConfigValueOut | None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
509 """Get an option from the configuration.
511 `option_name` is a colon-separated string indicating the section and
512 option name. For example, the ``branch`` option in the ``[run]``
513 section of the config file would be indicated with `"run:branch"`.
515 Returns the value of the option. The type depends on the option
516 selected.
518 As a special case, an `option_name` of ``"paths"`` will return an
519 dictionary with the entire ``[paths]`` section value.
521 .. versionadded:: 4.0
523 """
524 return self.config.get_option(option_name) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
526 def set_option(self, option_name: str, value: TConfigValueIn | TConfigSectionIn) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
527 """Set an option in the configuration.
529 `option_name` is a colon-separated string indicating the section and
530 option name. For example, the ``branch`` option in the ``[run]``
531 section of the config file would be indicated with ``"run:branch"``.
533 `value` is the new value for the option. This should be an
534 appropriate Python value. For example, use True for booleans, not the
535 string ``"True"``.
537 As an example, calling:
539 .. code-block:: python
541 cov.set_option("run:branch", True)
543 has the same effect as this configuration file:
545 .. code-block:: ini
547 [run]
548 branch = True
550 As a special case, an `option_name` of ``"paths"`` will replace the
551 entire ``[paths]`` section. The value should be a dictionary.
553 .. versionadded:: 4.0
555 """
556 self.config.set_option(option_name, value) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
558 def load(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
559 """Load previously-collected coverage data from the data file."""
560 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
561 if self._collector is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
562 self._collector.reset() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
563 should_skip = self.config.parallel and not os.path.exists(self.config.data_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
564 if not should_skip: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
565 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
566 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
567 if not should_skip: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
568 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
569 self._data.read() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
571 def _init_for_start(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
572 """Initialization for start()"""
573 # Construct the collector.
574 concurrency: list[str] = self.config.concurrency 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
575 if "multiprocessing" in concurrency: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
576 if self.config.config_file is None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
577 raise ConfigError("multiprocessing requires a configuration file") 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
578 patch_multiprocessing(rcfile=self.config.config_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
580 dycon = self.config.dynamic_context 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
581 if not dycon or dycon == "none": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
582 context_switchers = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
583 elif dycon == "test_function": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
584 context_switchers = [should_start_context_test_function] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
585 else:
586 raise ConfigError(f"Don't understand dynamic_context setting: {dycon!r}") 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
588 context_switchers.extend( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
589 plugin.dynamic_context for plugin in self._plugins.context_switchers
590 )
592 should_start_context = combine_context_switchers(context_switchers) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
594 self._core = Core( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
595 warn=self._warn,
596 debug=(self._debug if self._debug.should("core") else None),
597 config=self.config,
598 dynamic_contexts=(should_start_context is not None),
599 metacov=self._metacov,
600 )
601 self._collector = Collector( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
602 core=self._core,
603 should_trace=self._should_trace,
604 check_include=self._check_include_omit_etc,
605 should_start_context=should_start_context,
606 file_mapper=self._file_mapper,
607 branch=self.config.branch,
608 warn=self._warn,
609 concurrency=concurrency,
610 )
612 suffix = self._data_suffix_specified 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
613 if suffix: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
614 if not isinstance(suffix, str): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
615 # if data_suffix=True, use .machinename.pid.random
616 suffix = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
617 elif self.config.parallel: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
618 if suffix is None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
619 suffix = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
620 elif not isinstance(suffix, str): 620 ↛ 625line 620 didn't jump to line 625 because the condition on line 620 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
621 suffix = bool(suffix) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
622 else:
623 suffix = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
625 self._init_data(suffix) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
627 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
628 self._collector.use_data(self._data, self.config.context) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
630 # Early warning if we aren't going to be able to support plugins.
631 if self._plugins.file_tracers and not self._core.supports_plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
632 self._warn( 1abKcdLefMghNijOk7l8P%m4n9Q'o$p!R(q6r#S)235
633 "Plugin file tracers ({}) aren't supported with {}".format(
634 ", ".join(
635 plugin._coverage_plugin_name for plugin in self._plugins.file_tracers
636 ),
637 self._collector.tracer_name(),
638 ),
639 )
640 for plugin in self._plugins.file_tracers: 1abKcdLefMghNijOklPmnQopRqrS235
641 plugin._coverage_enabled = False 1abKcdLefMghNijOklPmnQopRqrS235
643 # Create the file classifying substructure.
644 self._inorout = InOrOut( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
645 config=self.config,
646 warn=self._warn,
647 debug=(self._debug if self._debug.should("trace") else None),
648 include_namespace_packages=self.config.include_namespace_packages,
649 )
650 self._inorout.plugins = self._plugins 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
651 self._inorout.disp_class = self._core.file_disposition_class 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
653 # It's useful to write debug info after initing for start.
654 self._should_write_debug = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
656 # Register our clean-up handlers.
657 atexit.register(self._atexit) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
658 if self.config.sigterm: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
659 is_main = (threading.current_thread() == threading.main_thread()) # fmt: skip 1satbucvdwexfygzhAiBjCk7Dl8Em4Fn9Go$Hp!Iq6Jr#23
660 if is_main and not env.WINDOWS: 660 ↛ exitline 660 didn't return from function '_init_for_start' because the condition on line 660 was always true1satbucvdwexfygzhAiBjCk7Dl8Em4Fn9Go$Hp!Iq6Jr#23
661 # The Python docs seem to imply that SIGTERM works uniformly even
662 # on Windows, but that's not my experience, and this agrees:
663 # https://stackoverflow.com/questions/35772001/x/35792192#35792192
664 self._old_sigterm = signal.signal( # type: ignore[assignment] 1satbucvdwexfygzhAiBjCk7Dl8Em4Fn9Go$Hp!Iq6Jr#23
665 signal.SIGTERM,
666 self._on_sigterm,
667 )
669 def _init_data(self, suffix: str | bool | None) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
670 """Create a data file if we don't have one yet."""
671 if self._data is None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
672 # Create the data file. We do this at construction time so that the
673 # data file will be written into the directory where the process
674 # started rather than wherever the process eventually chdir'd to.
675 ensure_dir_for_file(self.config.data_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
676 self._data = CoverageData( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
677 basename=self.config.data_file,
678 suffix=suffix,
679 warn=self._warn,
680 debug=self._debug,
681 no_disk=self._no_disk,
682 )
683 self._data_to_close.append(self._data) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
685 def start(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
686 """Start measuring code coverage.
688 Coverage measurement is only collected in functions called after
689 :meth:`start` is invoked. Statements in the same scope as
690 :meth:`start` won't be measured.
692 Once you invoke :meth:`start`, you must also call :meth:`stop`
693 eventually, or your process might not shut down cleanly.
695 The :meth:`collect` method is a context manager to handle both
696 starting and stopping collection.
698 """
699 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
700 if not self._inited_for_start: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
701 self._inited_for_start = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
702 self._init_for_start() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
703 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
705 assert self._collector is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
706 assert self._inorout is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
708 # Issue warnings for possible problems.
709 self._inorout.warn_conflicting_settings() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
711 # See if we think some code that would eventually be measured has
712 # already been imported.
713 if self._warn_preimported_source: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
714 self._inorout.warn_already_imported_files() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
716 if self._auto_load: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
717 self.load() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
719 apply_patches(self, self.config, self._debug) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
721 self._collector.start() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
722 self._started = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
723 self._instances.append(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
725 def stop(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
726 """Stop measuring code coverage."""
727 if self._instances: 727 ↛ 730line 727 didn't jump to line 730 because the condition on line 727 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
728 if self._instances[-1] is self: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
729 self._instances.pop() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
730 if self._started: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
731 assert self._collector is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
732 self._collector.stop() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
733 self._started = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
735 @contextlib.contextmanager 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
736 def collect(self) -> Iterator[None]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
737 """A context manager to start/stop coverage measurement collection.
739 .. versionadded:: 7.3
741 """
742 self.start() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
743 try: 1abKcdLefMghNijOklPmnQopRqrS235
744 yield 1abKcdLefMghNijOklPmnQopRqrS235
745 finally:
746 self.stop() # pragma: nested
748 def _atexit(self, event: str = "atexit") -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
749 """Clean up on process shutdown."""
750 if self._debug.should("process"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
751 self._debug.write(f"{event}: pid: {os.getpid()}, instance: {self!r}") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
752 if self._started: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
753 self.stop() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
754 if self._auto_save or event == "sigterm": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
755 self.save() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
756 for d in self._data_to_close: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
757 d.close(force=True) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
759 def _on_sigterm(self, signum_unused: int, frame_unused: FrameType | None) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
760 """A handler for signal.SIGTERM."""
761 self._atexit("sigterm")
762 # Statements after here won't be seen by metacov because we just wrote
763 # the data, and are about to kill the process.
764 signal.signal(signal.SIGTERM, self._old_sigterm) # pragma: not covered
765 os.kill(os.getpid(), signal.SIGTERM) # pragma: not covered
767 def erase(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
768 """Erase previously collected coverage data.
770 This removes the in-memory data collected in this session as well as
771 discarding the data file.
773 """
774 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
775 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
776 if self._collector is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
777 self._collector.reset() 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
778 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
779 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
780 self._data.erase(parallel=self.config.parallel) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
781 self._data = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
782 self._inited_for_start = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
784 def switch_context(self, new_context: str) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
785 """Switch to a new dynamic context.
787 `new_context` is a string to use as the :ref:`dynamic context
788 <dynamic_contexts>` label for collected data. If a :ref:`static
789 context <static_contexts>` is in use, the static and dynamic context
790 labels will be joined together with a pipe character.
792 Coverage collection must be started already.
794 .. versionadded:: 5.0
796 """
797 if not self._started: # pragma: part started 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
798 raise CoverageException("Cannot switch context, coverage is not started") 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
800 assert self._collector is not None
801 if self._collector.should_start_context:
802 self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
804 self._collector.switch_context(new_context)
806 def clear_exclude(self, which: str = "exclude") -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
807 """Clear the exclude list."""
808 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
809 setattr(self.config, f"{which}_list", []) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
810 self._exclude_regex_stale() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
812 def exclude(self, regex: str, which: str = "exclude") -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
813 """Exclude source lines from execution consideration.
815 A number of lists of regular expressions are maintained. Each list
816 selects lines that are treated differently during reporting.
818 `which` determines which list is modified. The "exclude" list selects
819 lines that are not considered executable at all. The "partial" list
820 indicates lines with branches that are not taken.
822 `regex` is a regular expression. The regex is added to the specified
823 list. If any of the regexes in the list is found in a line, the line
824 is marked for special treatment during reporting.
826 """
827 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
828 excl_list = getattr(self.config, f"{which}_list") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
829 excl_list.append(regex) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
830 self._exclude_regex_stale() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
832 def _exclude_regex_stale(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
833 """Drop all the compiled exclusion regexes, a list was modified."""
834 self._exclude_re.clear() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
836 def _exclude_regex(self, which: str) -> str: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
837 """Return a regex string for the given exclusion list."""
838 if which not in self._exclude_re: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
839 excl_list = getattr(self.config, f"{which}_list") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
840 self._exclude_re[which] = join_regex(excl_list) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
841 return self._exclude_re[which] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
843 def get_exclude_list(self, which: str = "exclude") -> list[str]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
844 """Return a list of excluded regex strings.
846 `which` indicates which list is desired. See :meth:`exclude` for the
847 lists that are available, and their meaning.
849 """
850 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
851 return cast(list[str], getattr(self.config, f"{which}_list")) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
853 def save(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
854 """Save the collected coverage data to the data file."""
855 data = self.get_data() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
856 data.write() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
858 def _make_aliases(self) -> PathAliases: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
859 """Create a PathAliases from our configuration."""
860 aliases = PathAliases( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
861 debugfn=(self._debug.write if self._debug.should("pathmap") else None),
862 relative=self.config.relative_files,
863 )
864 for paths in self.config.paths.values(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
865 result = paths[0] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
866 for pattern in paths[1:]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
867 aliases.add(pattern, result) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
868 return aliases 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
870 def combine( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
871 self,
872 data_paths: Iterable[str] | None = None,
873 strict: bool = False,
874 keep: bool = False,
875 ) -> None:
876 """Combine together a number of similarly-named coverage data files.
878 All coverage data files whose name starts with `data_file` (from the
879 coverage() constructor) will be read, and combined together into the
880 current measurements.
882 `data_paths` is a list of files or directories from which data should
883 be combined. If no list is passed, then the data files from the
884 directory indicated by the current data file (probably the current
885 directory) will be combined.
887 If `strict` is true, then it is an error to attempt to combine when
888 there are no data files to combine.
890 If `keep` is true, then original input data files won't be deleted.
892 .. versionadded:: 4.0
893 The `data_paths` parameter.
895 .. versionadded:: 4.3
896 The `strict` parameter.
898 .. versionadded: 5.5
899 The `keep` parameter.
900 """
901 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
902 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
903 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
904 self.get_data() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
906 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
907 combine_parallel_data( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
908 self._data,
909 aliases=self._make_aliases(),
910 data_paths=data_paths,
911 strict=strict,
912 keep=keep,
913 message=self._message,
914 )
916 def get_data(self) -> CoverageData: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
917 """Get the collected data.
919 Also warn about various problems collecting data.
921 Returns a :class:`coverage.CoverageData`, the collected coverage data.
923 .. versionadded:: 4.0
925 """
926 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
927 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
928 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
930 if self._collector is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
931 for plugin in self._plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
932 if not plugin._coverage_enabled: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
933 self._collector.plugin_was_disabled(plugin) 1stTuvUwxVyzWABXCDYEFZGH0IJ1
935 if self._collector.flush_data(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
936 self._post_save_work() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
938 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
939 return self._data 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
941 def _post_save_work(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
942 """After saving data, look for warnings, post-work, etc.
944 Warn about things that should have happened but didn't.
945 Look for un-executed files.
947 """
948 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
949 assert self._inorout is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
951 # If there are still entries in the source_pkgs_unmatched list,
952 # then we never encountered those packages.
953 if self._warn_unimported_source: 953 ↛ 957line 953 didn't jump to line 957 because the condition on line 953 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
954 self._inorout.warn_unimported_source() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
956 # Find out if we got any data.
957 if not self._data and self._warn_no_data: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
958 self._warn("No data was collected.", slug="no-data-collected") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7DlYP%Em4FnZQ'Go$Hp0R(Iq6Jr1S)235
960 # Touch all the files that could have executed, so that we can
961 # mark completely un-executed files as 0% covered.
962 file_paths = collections.defaultdict(list) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
963 for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
964 file_path = self._file_mapper(file_path) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
965 file_paths[plugin_name].append(file_path) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
966 for plugin_name, paths in file_paths.items(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
967 self._data.touch_files(paths, plugin_name) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
969 # Backward compatibility with version 1.
970 def analysis(self, morf: TMorf) -> tuple[str, list[TLineNo], list[TLineNo], str]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
971 """Like `analysis2` but doesn't return excluded line numbers."""
972 f, s, _, m, mf = self.analysis2(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
973 return f, s, m, mf 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
975 def analysis2( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
976 self,
977 morf: TMorf,
978 ) -> tuple[str, list[TLineNo], list[TLineNo], list[TLineNo], str]:
979 """Analyze a module.
981 `morf` is a module or a file name. It will be analyzed to determine
982 its coverage statistics. The return value is a 5-tuple:
984 * The file name for the module.
985 * A list of line numbers of executable statements.
986 * A list of line numbers of excluded statements.
987 * A list of line numbers of statements not run (missing from
988 execution).
989 * A readable formatted string of the missing line numbers.
991 The analysis uses the source file itself and the current measured
992 coverage data.
994 """
995 analysis = self._analyze(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
996 return ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
997 analysis.filename,
998 sorted(analysis.statements),
999 sorted(analysis.excluded),
1000 sorted(analysis.missing),
1001 analysis.missing_formatted(),
1002 )
1004 @functools.lru_cache(maxsize=1) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1005 def _analyze(self, morf: TMorf) -> Analysis: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1006 """Analyze a module or file. Private for now."""
1007 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1008 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1010 data = self.get_data() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1011 file_reporter = self._get_file_reporter(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1012 filename = self._file_mapper(file_reporter.filename) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1013 return analysis_from_file_reporter(data, self.config.precision, file_reporter, filename) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1015 def branch_stats(self, morf: TMorf) -> dict[TLineNo, tuple[int, int]]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1016 """Get branch statistics about a module.
1018 `morf` is a module or a file name.
1020 Returns a dict mapping line numbers to a tuple:
1021 (total_exits, taken_exits).
1023 .. versionadded:: 7.7
1025 """
1026 analysis = self._analyze(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1027 return analysis.branch_stats() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1029 @functools.lru_cache(maxsize=1) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1030 def _get_file_reporter(self, morf: TMorf) -> FileReporter: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1031 """Get a FileReporter for a module or file name."""
1032 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1033 plugin = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1034 file_reporter: str | FileReporter = "python" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1036 if isinstance(morf, str): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1037 mapped_morf = self._file_mapper(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1038 plugin_name = self._data.file_tracer(mapped_morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1039 if plugin_name: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1040 plugin = self._plugins.get(plugin_name) 1stTuvUwxVyzWABXCDYEFZGH0IJ1
1042 if plugin: 1042 ↛ 1052line 1042 didn't jump to line 1052 because the condition on line 1042 was always true1stTuvUwxVyzWABXCDYEFZGH0IJ1
1043 file_reporter = plugin.file_reporter(mapped_morf) 1stTuvUwxVyzWABXCDYEFZGH0IJ1
1044 if file_reporter is None: 1044 ↛ 1045line 1044 didn't jump to line 1045 because the condition on line 1044 was never true1stTuvUwxVyzWABXCDYEFZGH0IJ1
1045 raise PluginError(
1046 "Plugin {!r} did not provide a file reporter for {!r}.".format(
1047 plugin._coverage_plugin_name,
1048 morf,
1049 ),
1050 )
1052 if file_reporter == "python": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1053 file_reporter = PythonFileReporter(morf, self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1055 assert isinstance(file_reporter, FileReporter) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1056 return file_reporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1058 def _get_file_reporters( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1059 self,
1060 morfs: Iterable[TMorf] | None = None,
1061 ) -> list[tuple[FileReporter, TMorf]]:
1062 """Get FileReporters for a list of modules or file names.
1064 For each module or file name in `morfs`, find a FileReporter. Return
1065 a list pairing FileReporters with the morfs.
1067 If `morfs` is a single module or file name, this returns a list of one
1068 FileReporter. If `morfs` is empty or None, then the list of all files
1069 measured is used to find the FileReporters.
1071 """
1072 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1073 if not morfs: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1074 morfs = self._data.measured_files() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1076 # Be sure we have a collection.
1077 if not isinstance(morfs, (list, tuple, set)): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1078 morfs = [morfs] # type: ignore[list-item] 1satbTKucvdULwexfVMygzhWNAiBjXOCkDl8YPEm4Fn9ZQGoHp0RIqJr1S235
1080 morfs = sorted(morfs, key=lambda m: m if isinstance(m, str) else m.__name__) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1081 return [(self._get_file_reporter(morf), morf) for morf in morfs] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1083 def _prepare_data_for_reporting(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1084 """Re-map data before reporting, to get implicit "combine" behavior."""
1085 if self.config.paths: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1086 mapped_data = CoverageData(warn=self._warn, debug=self._debug, no_disk=True) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
1087 if self._data is not None: 1087 ↛ 1089line 1087 didn't jump to line 1089 because the condition on line 1087 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
1088 mapped_data.update(self._data, map_path=self._make_aliases().map) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
1089 self._data = mapped_data 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
1090 self._data_to_close.append(mapped_data) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S235
1092 def report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1093 self,
1094 morfs: Iterable[TMorf] | None = None,
1095 show_missing: bool | None = None,
1096 ignore_errors: bool | None = None,
1097 file: IO[str] | None = None,
1098 omit: str | list[str] | None = None,
1099 include: str | list[str] | None = None,
1100 skip_covered: bool | None = None,
1101 contexts: list[str] | None = None,
1102 skip_empty: bool | None = None,
1103 precision: int | None = None,
1104 sort: str | None = None,
1105 output_format: str | None = None,
1106 ) -> float:
1107 """Write a textual summary report to `file`.
1109 Each module in `morfs` is listed, with counts of statements, executed
1110 statements, missing statements, and a list of lines missed.
1112 If `show_missing` is true, then details of which lines or branches are
1113 missing will be included in the report. If `ignore_errors` is true,
1114 then a failure while reporting a single file will not stop the entire
1115 report.
1117 `file` is a file-like object, suitable for writing.
1119 `output_format` determines the format, either "text" (the default),
1120 "markdown", or "total".
1122 `include` is a list of file name patterns. Files that match will be
1123 included in the report. Files matching `omit` will not be included in
1124 the report.
1126 If `skip_covered` is true, don't report on files with 100% coverage.
1128 If `skip_empty` is true, don't report on empty files (those that have
1129 no statements).
1131 `contexts` is a list of regular expression strings. Only data from
1132 :ref:`dynamic contexts <dynamic_contexts>` that match one of those
1133 expressions (using :func:`re.search <python:re.search>`) will be
1134 included in the report.
1136 `precision` is the number of digits to display after the decimal
1137 point for percentages.
1139 All of the arguments default to the settings read from the
1140 :ref:`configuration file <config>`.
1142 Returns a float, the total percentage covered.
1144 .. versionadded:: 4.0
1145 The `skip_covered` parameter.
1147 .. versionadded:: 5.0
1148 The `contexts` and `skip_empty` parameters.
1150 .. versionadded:: 5.2
1151 The `precision` parameter.
1153 .. versionadded:: 7.0
1154 The `format` parameter.
1156 """
1157 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1158 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1159 self,
1160 ignore_errors=ignore_errors,
1161 report_omit=omit,
1162 report_include=include,
1163 show_missing=show_missing,
1164 skip_covered=skip_covered,
1165 report_contexts=contexts,
1166 skip_empty=skip_empty,
1167 precision=precision,
1168 sort=sort,
1169 format=output_format,
1170 ):
1171 reporter = SummaryReporter(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1172 return reporter.report(morfs, outfile=file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1174 def annotate( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1175 self,
1176 morfs: Iterable[TMorf] | None = None,
1177 directory: str | None = None,
1178 ignore_errors: bool | None = None,
1179 omit: str | list[str] | None = None,
1180 include: str | list[str] | None = None,
1181 contexts: list[str] | None = None,
1182 ) -> None:
1183 """Annotate a list of modules.
1185 Each module in `morfs` is annotated. The source is written to a new
1186 file, named with a ",cover" suffix, with each line prefixed with a
1187 marker to indicate the coverage of the line. Covered lines have ">",
1188 excluded lines have "-", and missing lines have "!".
1190 See :meth:`report` for other arguments.
1192 """
1193 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1194 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1195 self,
1196 ignore_errors=ignore_errors,
1197 report_omit=omit,
1198 report_include=include,
1199 report_contexts=contexts,
1200 ):
1201 reporter = AnnotateReporter(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1202 reporter.report(morfs, directory=directory) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1204 def html_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1205 self,
1206 morfs: Iterable[TMorf] | None = None,
1207 directory: str | None = None,
1208 ignore_errors: bool | None = None,
1209 omit: str | list[str] | None = None,
1210 include: str | list[str] | None = None,
1211 extra_css: str | None = None,
1212 title: str | None = None,
1213 skip_covered: bool | None = None,
1214 show_contexts: bool | None = None,
1215 contexts: list[str] | None = None,
1216 skip_empty: bool | None = None,
1217 precision: int | None = None,
1218 ) -> float:
1219 """Generate an HTML report.
1221 The HTML is written to `directory`. The file "index.html" is the
1222 overview starting point, with links to more detailed pages for
1223 individual modules.
1225 `extra_css` is a path to a file of other CSS to apply on the page.
1226 It will be copied into the HTML directory.
1228 `title` is a text string (not HTML) to use as the title of the HTML
1229 report.
1231 See :meth:`report` for other arguments.
1233 Returns a float, the total percentage covered.
1235 .. note::
1237 The HTML report files are generated incrementally based on the
1238 source files and coverage results. If you modify the report files,
1239 the changes will not be considered. You should be careful about
1240 changing the files in the report folder.
1242 """
1243 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1244 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1245 self,
1246 ignore_errors=ignore_errors,
1247 report_omit=omit,
1248 report_include=include,
1249 html_dir=directory,
1250 extra_css=extra_css,
1251 html_title=title,
1252 html_skip_covered=skip_covered,
1253 show_contexts=show_contexts,
1254 report_contexts=contexts,
1255 html_skip_empty=skip_empty,
1256 precision=precision,
1257 ):
1258 reporter = HtmlReporter(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1259 return reporter.report(morfs) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1261 def xml_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1262 self,
1263 morfs: Iterable[TMorf] | None = None,
1264 outfile: str | None = None,
1265 ignore_errors: bool | None = None,
1266 omit: str | list[str] | None = None,
1267 include: str | list[str] | None = None,
1268 contexts: list[str] | None = None,
1269 skip_empty: bool | None = None,
1270 ) -> float:
1271 """Generate an XML report of coverage results.
1273 The report is compatible with Cobertura reports.
1275 Each module in `morfs` is included in the report. `outfile` is the
1276 path to write the file to, "-" will write to stdout.
1278 See :meth:`report` for other arguments.
1280 Returns a float, the total percentage covered.
1282 """
1283 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1284 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1285 self,
1286 ignore_errors=ignore_errors,
1287 report_omit=omit,
1288 report_include=include,
1289 xml_output=outfile,
1290 report_contexts=contexts,
1291 skip_empty=skip_empty,
1292 ):
1293 return render_report(self.config.xml_output, XmlReporter(self), morfs, self._message) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1295 def json_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1296 self,
1297 morfs: Iterable[TMorf] | None = None,
1298 outfile: str | None = None,
1299 ignore_errors: bool | None = None,
1300 omit: str | list[str] | None = None,
1301 include: str | list[str] | None = None,
1302 contexts: list[str] | None = None,
1303 pretty_print: bool | None = None,
1304 show_contexts: bool | None = None,
1305 ) -> float:
1306 """Generate a JSON report of coverage results.
1308 Each module in `morfs` is included in the report. `outfile` is the
1309 path to write the file to, "-" will write to stdout.
1311 `pretty_print` is a boolean, whether to pretty-print the JSON output or not.
1313 See :meth:`report` for other arguments.
1315 Returns a float, the total percentage covered.
1317 .. versionadded:: 5.0
1319 """
1320 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1321 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1322 self,
1323 ignore_errors=ignore_errors,
1324 report_omit=omit,
1325 report_include=include,
1326 json_output=outfile,
1327 report_contexts=contexts,
1328 json_pretty_print=pretty_print,
1329 json_show_contexts=show_contexts,
1330 ):
1331 return render_report(self.config.json_output, JsonReporter(self), morfs, self._message) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1333 def lcov_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)
1334 self,
1335 morfs: Iterable[TMorf] | None = None,
1336 outfile: str | None = None,
1337 ignore_errors: bool | None = None,
1338 omit: str | list[str] | None = None,
1339 include: str | list[str] | None = None,
1340 contexts: list[str] | None = None,
1341 ) -> float:
1342 """Generate an LCOV report of coverage results.
1344 Each module in `morfs` is included in the report. `outfile` is the
1345 path to write the file to, "-" will write to stdout.
1347 See :meth:`report` for other arguments.
1349 .. versionadded:: 6.3
1350 """
1351 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1352 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1353 self,
1354 ignore_errors=ignore_errors,
1355 report_omit=omit,
1356 report_include=include,
1357 lcov_output=outfile,
1358 report_contexts=contexts,
1359 ):
1360 return render_report(self.config.lcov_output, LcovReporter(self), morfs, self._message) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1362 def sys_info(self) -> Iterable[tuple[str, Any]]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1363 """Return a list of (key, value) pairs showing internal information."""
1365 import coverage as covmod 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1367 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1368 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1370 def plugin_info(plugins: list[Any]) -> list[str]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1371 """Make an entry for the sys_info from a list of plug-ins."""
1372 entries = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1373 for plugin in plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1374 entry = plugin._coverage_plugin_name 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEm4FnZQGoHp0RIq6Jr1S235
1375 if not plugin._coverage_enabled: 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEm4FnZQGoHp0RIq6Jr1S235
1376 entry += " (disabled)" 1abKcdLefMghNijOklPm4nQopRq6rS235
1377 entries.append(entry) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEm4FnZQGoHp0RIq6Jr1S235
1378 return entries 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1380 info = [ 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1381 ("coverage_version", covmod.__version__),
1382 ("coverage_module", covmod.__file__),
1383 ("core", self._collector.tracer_name() if self._collector is not None else "-none-"),
1384 ("CTracer", f"available from {CTRACER_FILE}" if CTRACER_FILE else "unavailable"),
1385 ("plugins.file_tracers", plugin_info(self._plugins.file_tracers)),
1386 ("plugins.configurers", plugin_info(self._plugins.configurers)),
1387 ("plugins.context_switchers", plugin_info(self._plugins.context_switchers)),
1388 ("configs_attempted", self.config.config_files_attempted),
1389 ("configs_read", self.config.config_files_read),
1390 ("config_file", self.config.config_file),
1391 (
1392 "config_contents",
1393 repr(self.config._config_contents) if self.config._config_contents else "-none-",
1394 ),
1395 ("data_file", self._data.data_filename() if self._data is not None else "-none-"),
1396 ("python", sys.version.replace("\n", "")),
1397 ("platform", platform.platform()),
1398 ("implementation", platform.python_implementation()),
1399 ("build", platform.python_build()),
1400 ("gil_enabled", getattr(sys, "_is_gil_enabled", lambda: True)()),
1401 ("executable", sys.executable),
1402 ("def_encoding", sys.getdefaultencoding()),
1403 ("fs_encoding", sys.getfilesystemencoding()),
1404 ("pid", os.getpid()),
1405 ("cwd", os.getcwd()),
1406 ("path", sys.path),
1407 ("environment", [f"{k} = {v}" for k, v in relevant_environment_display(os.environ)]),
1408 ("command_line", " ".join(getattr(sys, "argv", ["-none-"]))),
1409 ("time", f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S}"),
1410 ]
1412 if self._inorout is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1413 info.extend(self._inorout.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1415 return info 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1418# Mega debugging...
1419# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
1420if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1421 from coverage.debug import decorate_methods, show_calls
1423 Coverage = decorate_methods( # type: ignore[misc]
1424 show_calls(show_args=True),
1425 butnot=["get_data"],
1426 )(Coverage)
1429def process_startup( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1430 *,
1431 force: bool = False,
1432 slug: str = "default", # pylint: disable=unused-argument
1433) -> Coverage | None:
1434 """Call this at Python start-up to perhaps measure coverage.
1436 Coverage is started if one of these environment variables is defined:
1438 - COVERAGE_PROCESS_START: the config file to use.
1439 - COVERAGE_PROCESS_CONFIG: the config data to use, a string produced by
1440 CoverageConfig.serialize, prefixed by ":data:".
1442 If one of these is defined, it's used to get the coverage configuration,
1443 and coverage is started.
1445 For details, see https://coverage.readthedocs.io/en/latest/subprocess.html.
1447 Returns the :class:`Coverage` instance that was started, or None if it was
1448 not started by this call.
1450 """
1451 # This function can be called more than once in a process, for a few
1452 # reasons.
1453 #
1454 # 1) We install a .pth file in multiple places reported by the site module,
1455 # so this function can be called more than once even in simple
1456 # situations.
1457 #
1458 # 2) In some virtualenv configurations the same directory is visible twice
1459 # in sys.path. This means that the .pth file will be found twice and
1460 # executed twice, executing this function twice.
1461 # https://github.com/coveragepy/coveragepy/issues/340 has more details.
1462 #
1463 # We set a global flag (an attribute on this function) to indicate that
1464 # coverage.py has already been started, so we can avoid starting it twice.
1466 if not force and hasattr(process_startup, "coverage"): 1466 ↛ 1474line 1466 didn't jump to line 1474 because the condition on line 1466 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1467 # We've annotated this function before, so we must have already
1468 # auto-started coverage.py in this process. Nothing to do.
1469 return None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1471 # Now check for the environment variables that request coverage. If they
1472 # aren't set, do nothing.
1474 config_data = os.getenv("COVERAGE_PROCESS_CONFIG")
1475 cps = os.getenv("COVERAGE_PROCESS_START")
1476 if config_data is not None:
1477 config_file = CONFIG_DATA_PREFIX + config_data
1478 elif cps is not None:
1479 config_file = cps
1480 else:
1481 # No request for coverage, nothing to do.
1482 return None
1484 cov = Coverage(config_file=config_file)
1485 process_startup.coverage = cov # type: ignore[attr-defined]
1486 cov._warn_no_data = False
1487 cov._warn_unimported_source = False
1488 cov._warn_preimported_source = False
1489 cov._auto_save = True
1490 cov.start()
1492 return cov
1495def _after_fork_in_child() -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1496 """Used by patch=fork in the child process to restart coverage."""
1497 if cov := Coverage.current():
1498 cov.stop()
1499 process_startup(force=True, slug="fork")
1502def _prevent_sub_process_measurement() -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em4Fn9ZQ'Go$Hp!0R(Iq6Jr#1S)235
1503 """Stop any subprocess auto-measurement from writing data."""
1504 auto_created_coverage = getattr(process_startup, "coverage", None)
1505 if auto_created_coverage is not None:
1506 auto_created_coverage._auto_save = False