Coverage for coverage / control.py: 90.601%
479 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-29 20:34 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-29 20:34 +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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
8import atexit 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
9import collections 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
10import contextlib 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
11import datetime 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
12import functools 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
13import os 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
14import os.path 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
15import platform 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
16import signal 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
17import sys 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
18import threading 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
19import time 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
20import warnings 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
21from collections.abc import Iterable, Iterator 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
22from types import FrameType 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
23from typing import IO, Any, Callable, cast 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
25from coverage import env 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
26from coverage.annotate import AnnotateReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
27from coverage.collector import Collector 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
28from coverage.config import CoverageConfig, read_coverage_config 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
29from coverage.context import combine_context_switchers, should_start_context_test_function 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
30from coverage.core import CTRACER_FILE, Core 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
31from coverage.data import CoverageData, combine_parallel_data 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
32from coverage.debug import ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
33 DebugControl,
34 NoDebugging,
35 relevant_environment_display,
36 short_stack,
37 write_formatted_info,
38)
39from coverage.disposition import disposition_debug_msg 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
40from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
41from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
42from coverage.html import HtmlReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
43from coverage.inorout import InOrOut 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
44from coverage.jsonreport import JsonReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
45from coverage.lcovreport import LcovReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
46from coverage.misc import ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
54from coverage.patch import apply_patches 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
55from coverage.plugin import FileReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
56from coverage.plugin_support import Plugins, TCoverageInit 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
57from coverage.python import PythonFileReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
58from coverage.report import SummaryReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
59from coverage.report_core import render_report 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
60from coverage.results import Analysis, analysis_from_file_reporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
61from coverage.types import ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
62 FilePath,
63 TConfigSectionIn,
64 TConfigurable,
65 TConfigValueIn,
66 TConfigValueOut,
67 TFileDisposition,
68 TLineNo,
69 TMorf,
70)
71from coverage.version import __url__ 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
72from coverage.xmlreport import XmlReporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
74os = isolate_module(os) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
77@contextlib.contextmanager 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
78def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
85 cov.config = cov.config.copy() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
86 try: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
87 cov.config.from_args(**kwargs) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
88 yield 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
89 finally:
90 cov.config = original_config 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
93DEFAULT_DATAFILE = DefaultValue("MISSING") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
94_DEFAULT_DATAFILE = DEFAULT_DATAFILE # Just in case, for backwards compatibility 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
95CONFIG_DATA_PREFIX = ":data:" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
98class Coverage(TConfigurable): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
99 """Programmatic access to coverage.py. 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
129 @classmethod 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
130 def current(cls) -> Coverage | None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
139 return cls._instances[-1] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
140 else:
141 return None
143 def __init__( # pylint: disable=too-many-arguments 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
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%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
273 if isinstance(data_file, DefaultValue): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
274 data_file = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
275 if data_file is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
276 data_file = os.fspath(data_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDl8YPEm5Fn9ZQGoHp#0RIq$Jr61S234
278 # This is injectable by tests.
279 self._debug_file: IO[str] | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
281 self._auto_load = self._auto_save = auto_data 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
282 self._data_suffix_specified = data_suffix 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
284 # Is it ok for no data to be collected?
285 self._warn_no_data = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
286 self._warn_unimported_source = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
287 self._warn_preimported_source = check_preimported 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
288 self._no_warn_slugs: set[str] = set() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
289 self._messages = messages 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
291 # If we're invoked from a .pth file, we shouldn't try to make another one.
292 self._make_pth_file = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
294 # A record of all the warnings that have been issued.
295 self._warnings: list[str] = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
297 # Other instance attributes, set with placebos or placeholders.
298 # More useful objects will be created later.
299 self._debug: DebugControl = NoDebugging() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
300 self._inorout: InOrOut | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
301 self._plugins: Plugins = Plugins() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
302 self._plugin_override = cast(Iterable[TCoverageInit] | None, plugins) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
303 self._data: CoverageData | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
304 self._data_to_close: list[CoverageData] = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
305 self._core: Core | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
306 self._collector: Collector | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
307 self._metacov = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
309 self._file_mapper: Callable[[str], str] = abs_file 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
310 self._data_suffix = self._run_suffix = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
311 self._exclude_re: dict[str, str] = {} 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
312 self._old_sigterm: Callable[[int, FrameType | None], Any] | None = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
314 # State machine variables:
315 # Have we initialized everything?
316 self._inited = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
317 self._inited_for_start = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
318 # Have we started collecting and not stopped it?
319 self._started = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
320 # Should we write the debug output?
321 self._should_write_debug = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
323 # Build our configuration from a number of sources.
324 if isinstance(config_file, str) and config_file.startswith(CONFIG_DATA_PREFIX): 324 ↛ 325line 324 didn't jump to line 325 because the condition on line 324 was never true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
325 self.config = CoverageConfig.deserialize(config_file[len(CONFIG_DATA_PREFIX) :])
326 else:
327 if not isinstance(config_file, bool): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
328 config_file = os.fspath(config_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
329 self.config = read_coverage_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
330 config_file=config_file,
331 warn=self._warn,
332 data_file=data_file,
333 cover_pylib=cover_pylib,
334 timid=timid,
335 branch=branch,
336 parallel=bool_or_none(data_suffix),
337 source=source,
338 source_pkgs=source_pkgs,
339 source_dirs=source_dirs,
340 run_omit=omit,
341 run_include=include,
342 debug=debug,
343 report_omit=omit,
344 report_include=include,
345 concurrency=concurrency,
346 context=context,
347 )
349 # If we have subprocess measurement happening automatically, then we
350 # want any explicit creation of a Coverage object to mean, this process
351 # is already coverage-aware, so don't auto-measure it. By now, the
352 # auto-creation of a Coverage object has already happened. But we can
353 # find it and tell it not to save its data.
354 if not env.METACOV: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
355 _prevent_sub_process_measurement()
357 def __repr__(self) -> str: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
358 assert self._core is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
359 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
360 return ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
361 "<Coverage" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
362 + f" @0x{id(self):x}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
363 + f" core={self._core.tracer_class.__name__}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
364 + f" data_file={self._data._filename!r}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
365 + ">" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
366 )
368 def _init(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
369 """Set all the initial state.
371 This is called by the public methods to initialize state. This lets us
372 construct a :class:`Coverage` object, then tweak its state before this
373 function is called.
375 """
376 if self._inited: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
377 return 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
379 self._inited = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
381 # Create and configure the debugging controller.
382 self._debug = DebugControl(self.config.debug, self._debug_file, self.config.debug_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
383 if self._debug.should("process"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
384 self._debug.write("Coverage._init") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YPEm5Fn9ZQGo!Hp#0RIq$Jr61S234
386 if "multiprocessing" in (self.config.concurrency or ()): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
387 # Multi-processing uses parallel for the subprocesses, so also use
388 # it for the main process.
389 self.config.parallel = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
391 # _exclude_re is a dict that maps exclusion list names to compiled regexes.
392 self._exclude_re = {} 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
394 set_relative_directory() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
395 if self.config.relative_files: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
396 self._file_mapper = relative_filename 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYP%EmFnZQGoHp0RIqJr1S234
398 # Load plugins
399 self._plugins = Plugins(self._debug) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
400 if self._plugin_override: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
401 self._plugins.load_from_callables(self._plugin_override) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
402 else:
403 self._plugins.load_from_config(self.config.plugins, self.config) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
405 # Run configuring plugins.
406 for plugin in self._plugins.configurers: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
407 # We need an object with set_option and get_option. Either self or
408 # self.config will do. Choosing randomly stops people from doing
409 # other things with those objects, against the public API. Yes,
410 # this is a bit childish. :)
411 plugin.configure([self, self.config][int(time.time()) % 2]) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
413 def _post_init(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
414 """Stuff to do after everything is initialized."""
415 if self._should_write_debug: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
416 self._should_write_debug = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
417 self._write_startup_debug() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
419 # "[run] _crash" will raise an exception if the value is close by in
420 # the call stack, for testing error handling.
421 if self.config._crash and self.config._crash in short_stack(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
422 raise RuntimeError(f"Crashing because called by {self.config._crash}") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7DlYP%Em5FnZQ'Go!Hp0R(Iq$Jr1S)234
424 def _write_startup_debug(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
425 """Write out debug info at startup if needed."""
426 wrote_any = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
427 with self._debug.without_callers(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
428 if self._debug.should("config"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
429 write_formatted_info(self._debug.write, "config", self.config.debug_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
430 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
432 if self._debug.should("sys"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
433 write_formatted_info(self._debug.write, "sys", self.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
434 for plugin in self._plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
435 header = "sys: " + plugin._coverage_plugin_name 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
436 write_formatted_info(self._debug.write, header, plugin.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
437 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
439 if self._debug.should("pybehave"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
440 write_formatted_info(self._debug.write, "pybehave", env.debug_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
441 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
443 if self._debug.should("sqlite"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
444 write_formatted_info(self._debug.write, "sqlite", CoverageData.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
445 wrote_any = True 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
447 if wrote_any: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
448 write_formatted_info(self._debug.write, "end", ()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
450 def _should_trace(self, filename: str, frame: FrameType) -> TFileDisposition: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
451 """Decide whether to trace execution in `filename`.
453 Calls `_should_trace_internal`, and returns the FileDisposition.
455 """
456 assert self._inorout is not None
457 disp = self._inorout.should_trace(filename, frame)
458 if self._debug.should("trace"):
459 self._debug.write(disposition_debug_msg(disp))
460 return disp
462 def _check_include_omit_etc(self, filename: str, frame: FrameType) -> bool: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
463 """Check a file name against the include/omit/etc, rules, verbosely.
465 Returns a boolean: True if the file should be traced, False if not.
467 """
468 assert self._inorout is not None
469 reason = self._inorout.check_include_omit_etc(filename, frame)
470 if self._debug.should("trace"):
471 if not reason:
472 msg = f"Including {filename!r}"
473 else:
474 msg = f"Not including {filename!r}: {reason}"
475 self._debug.write(msg)
477 return not reason
479 def _warn(self, msg: str, slug: str | None = None, once: bool = False) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
480 """Use `msg` as a warning.
482 For warning suppression, use `slug` as the shorthand.
484 If `once` is true, only show this warning once (determined by the
485 slug.)
487 """
488 if not self._no_warn_slugs: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
489 self._no_warn_slugs = set(self.config.disable_warnings) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
491 if slug in self._no_warn_slugs: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
492 # Don't issue the warning
493 return 1satbTKucvdULwexfVMygzhWNAiBjXOCkDl8YP%EmFn9ZQ'GoHp#0R(IqJr61S)234
495 self._warnings.append(msg) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
496 if slug: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
497 msg = f"{msg} ({slug}); see {__url__}/messages.html#warning-{slug}" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
498 if self._debug.should("pid"): 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
499 msg = f"[{os.getpid()}] {msg}"
500 warnings.warn(msg, category=CoverageWarning, stacklevel=2) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
502 if once: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
503 assert slug is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'GoHp#0R(IqJr1S)234
504 self._no_warn_slugs.add(slug) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'GoHp#0R(IqJr1S)234
506 def _message(self, msg: str) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
507 """Write a message to the user, if configured to do so."""
508 if self._messages: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
509 print(msg) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
511 def get_option(self, option_name: str) -> TConfigValueOut | None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
512 """Get an option from the configuration.
514 `option_name` is a colon-separated string indicating the section and
515 option name. For example, the ``branch`` option in the ``[run]``
516 section of the config file would be indicated with `"run:branch"`.
518 Returns the value of the option. The type depends on the option
519 selected.
521 As a special case, an `option_name` of ``"paths"`` will return an
522 dictionary with the entire ``[paths]`` section value.
524 .. versionadded:: 4.0
526 """
527 return self.config.get_option(option_name) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
529 def set_option(self, option_name: str, value: TConfigValueIn | TConfigSectionIn) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
530 """Set an option in the configuration.
532 `option_name` is a colon-separated string indicating the section and
533 option name. For example, the ``branch`` option in the ``[run]``
534 section of the config file would be indicated with ``"run:branch"``.
536 `value` is the new value for the option. This should be an
537 appropriate Python value. For example, use True for booleans, not the
538 string ``"True"``.
540 As an example, calling:
542 .. code-block:: python
544 cov.set_option("run:branch", True)
546 has the same effect as this configuration file:
548 .. code-block:: ini
550 [run]
551 branch = True
553 As a special case, an `option_name` of ``"paths"`` will replace the
554 entire ``[paths]`` section. The value should be a dictionary.
556 .. versionadded:: 4.0
558 """
559 self.config.set_option(option_name, value) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
561 def load(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
562 """Load previously-collected coverage data from the data file."""
563 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
564 if self._collector is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
565 self._collector.reset() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
566 should_skip = self.config.parallel and not os.path.exists(self.config.data_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
567 if not should_skip: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
568 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
569 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
570 if not should_skip: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
571 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
572 self._data.read() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
574 def _init_for_start(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
575 """Initialization for start()"""
576 # Construct the collector.
577 concurrency: list[str] = self.config.concurrency 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
578 if "multiprocessing" in concurrency: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
579 if self.config.config_file is None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
580 raise ConfigError("multiprocessing requires a configuration file") 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
581 patch_multiprocessing(rcfile=self.config.config_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
583 dycon = self.config.dynamic_context 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
584 if not dycon or dycon == "none": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
585 context_switchers = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
586 elif dycon == "test_function": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
587 context_switchers = [should_start_context_test_function] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
588 else:
589 raise ConfigError(f"Don't understand dynamic_context setting: {dycon!r}") 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
591 context_switchers.extend( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
592 plugin.dynamic_context for plugin in self._plugins.context_switchers
593 )
595 should_start_context = combine_context_switchers(context_switchers) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
597 self._core = Core( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
598 warn=self._warn,
599 debug=(self._debug if self._debug.should("core") else None),
600 config=self.config,
601 dynamic_contexts=(should_start_context is not None),
602 metacov=self._metacov,
603 )
604 self._collector = Collector( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
605 core=self._core,
606 should_trace=self._should_trace,
607 check_include=self._check_include_omit_etc,
608 should_start_context=should_start_context,
609 file_mapper=self._file_mapper,
610 branch=self.config.branch,
611 warn=self._warn,
612 concurrency=concurrency,
613 )
615 suffix = self._data_suffix_specified 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
616 if suffix: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
617 if not isinstance(suffix, str): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
618 # if data_suffix=True, use .machinename.pid.random
619 suffix = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
620 elif self.config.parallel: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
621 if suffix is None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
622 suffix = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
623 elif not isinstance(suffix, str): 623 ↛ 628line 623 didn't jump to line 628 because the condition on line 623 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
624 suffix = bool(suffix) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
625 else:
626 suffix = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
628 self._init_data(suffix) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
630 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
631 self._collector.use_data(self._data, self.config.context) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
633 # Early warning if we aren't going to be able to support plugins.
634 if self._plugins.file_tracers and not self._core.supports_plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
635 self._warn( 1abKcdLefMghNijOk7l8P%m5n9Q'o!p#R(q$r6S)234
636 "Plugin file tracers ({}) aren't supported with {}".format(
637 ", ".join(
638 plugin._coverage_plugin_name for plugin in self._plugins.file_tracers
639 ),
640 self._collector.tracer_name(),
641 ),
642 )
643 for plugin in self._plugins.file_tracers: 1abKcdLefMghNijOklPmnQopRqrS234
644 plugin._coverage_enabled = False 1abKcdLefMghNijOklPmnQopRqrS234
646 # Create the file classifying substructure.
647 self._inorout = InOrOut( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
648 config=self.config,
649 warn=self._warn,
650 debug=(self._debug if self._debug.should("trace") else None),
651 include_namespace_packages=self.config.include_namespace_packages,
652 )
653 self._inorout.plugins = self._plugins 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
654 self._inorout.disp_class = self._core.file_disposition_class 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
656 # It's useful to write debug info after initing for start.
657 self._should_write_debug = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
659 # Register our clean-up handlers.
660 atexit.register(self._atexit) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
661 if self.config.sigterm: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
662 is_main = (threading.current_thread() == threading.main_thread()) # fmt: skip 1satbucvdwexfygzhAiBjCk7Dl8Em5Fn9Go!Hp#Iq$Jr623
663 if is_main and not env.WINDOWS: 663 ↛ exitline 663 didn't return from function '_init_for_start' because the condition on line 663 was always true1satbucvdwexfygzhAiBjCk7Dl8Em5Fn9Go!Hp#Iq$Jr623
664 # The Python docs seem to imply that SIGTERM works uniformly even
665 # on Windows, but that's not my experience, and this agrees:
666 # https://stackoverflow.com/questions/35772001/x/35792192#35792192
667 self._old_sigterm = signal.signal( # type: ignore[assignment] 1satbucvdwexfygzhAiBjCk7Dl8Em5Fn9Go!Hp#Iq$Jr623
668 signal.SIGTERM,
669 self._on_sigterm,
670 )
672 def _init_data(self, suffix: str | bool | None) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
673 """Create a data file if we don't have one yet."""
674 if self._data is None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
675 # Create the data file. We do this at construction time so that the
676 # data file will be written into the directory where the process
677 # started rather than wherever the process eventually chdir'd to.
678 ensure_dir_for_file(self.config.data_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
679 self._data = CoverageData( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
680 basename=self.config.data_file,
681 suffix=suffix,
682 warn=self._warn,
683 debug=self._debug,
684 no_disk=self._no_disk,
685 )
686 self._data_to_close.append(self._data) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
688 def start(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
689 """Start measuring code coverage.
691 Coverage measurement is only collected in functions called after
692 :meth:`start` is invoked. Statements in the same scope as
693 :meth:`start` won't be measured.
695 Once you invoke :meth:`start`, you must also call :meth:`stop`
696 eventually, or your process might not shut down cleanly.
698 The :meth:`collect` method is a context manager to handle both
699 starting and stopping collection.
701 """
702 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
703 if not self._inited_for_start: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
704 self._inited_for_start = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
705 self._init_for_start() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
706 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
708 assert self._collector is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
709 assert self._inorout is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
711 # Issue warnings for possible problems.
712 self._inorout.warn_conflicting_settings() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
714 # See if we think some code that would eventually be measured has
715 # already been imported.
716 if self._warn_preimported_source: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
717 self._inorout.warn_already_imported_files() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
719 if self._auto_load: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
720 self.load() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
722 apply_patches(self, self.config, self._debug, make_pth_file=self._make_pth_file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
724 self._collector.start() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
725 self._started = True 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
726 self._instances.append(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
728 def stop(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
729 """Stop measuring code coverage."""
730 if self._instances: 730 ↛ 733line 730 didn't jump to line 733 because the condition on line 730 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
731 if self._instances[-1] is self: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
732 self._instances.pop() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
733 if self._started: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
734 assert self._collector is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
735 self._collector.stop() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
736 self._started = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
738 @contextlib.contextmanager 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
739 def collect(self) -> Iterator[None]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
740 """A context manager to start/stop coverage measurement collection.
742 .. versionadded:: 7.3
744 """
745 self.start() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
746 try: 1abKcdLefMghNijOklPmnQopRqrS234
747 yield 1abKcdLefMghNijOklPmnQopRqrS234
748 finally:
749 self.stop() # pragma: nested
751 def _atexit(self, event: str = "atexit") -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
752 """Clean up on process shutdown."""
753 if self._debug.should("process"): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
754 self._debug.write(f"{event}: pid: {os.getpid()}, instance: {self!r}") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
755 if self._started: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
756 self.stop() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
757 if self._auto_save or event == "sigterm": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
758 self.save() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
759 for d in self._data_to_close: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
760 d.close(force=True) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
762 def _on_sigterm(self, signum_unused: int, frame_unused: FrameType | None) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
763 """A handler for signal.SIGTERM."""
764 self._atexit("sigterm")
765 # Statements after here won't be seen by metacov because we just wrote
766 # the data, and are about to kill the process.
767 signal.signal(signal.SIGTERM, self._old_sigterm) # pragma: not covered
768 os.kill(os.getpid(), signal.SIGTERM) # pragma: not covered
770 def erase(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
771 """Erase previously collected coverage data.
773 This removes the in-memory data collected in this session as well as
774 discarding the data file.
776 """
777 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
778 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
779 if self._collector is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
780 self._collector.reset() 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
781 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
782 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
783 self._data.erase(parallel=self.config.parallel) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
784 self._data = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
785 self._inited_for_start = False 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
787 def switch_context(self, new_context: str) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
788 """Switch to a new dynamic context.
790 `new_context` is a string to use as the :ref:`dynamic context
791 <dynamic_contexts>` label for collected data. If a :ref:`static
792 context <static_contexts>` is in use, the static and dynamic context
793 labels will be joined together with a pipe character.
795 Coverage collection must be started already.
797 .. versionadded:: 5.0
799 """
800 if not self._started: # pragma: part started 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
801 raise CoverageException("Cannot switch context, coverage is not started") 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
803 assert self._collector is not None
804 if self._collector.should_start_context:
805 self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
807 self._collector.switch_context(new_context)
809 def clear_exclude(self, which: str = "exclude") -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
810 """Clear the exclude list."""
811 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
812 setattr(self.config, f"{which}_list", []) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
813 self._exclude_regex_stale() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
815 def exclude(self, regex: str, which: str = "exclude") -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
816 """Exclude source lines from execution consideration.
818 A number of lists of regular expressions are maintained. Each list
819 selects lines that are treated differently during reporting.
821 `which` determines which list is modified. The "exclude" list selects
822 lines that are not considered executable at all. The "partial" list
823 indicates lines with branches that are not taken.
825 `regex` is a regular expression. The regex is added to the specified
826 list. If any of the regexes in the list is found in a line, the line
827 is marked for special treatment during reporting.
829 """
830 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
831 excl_list = getattr(self.config, f"{which}_list") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
832 excl_list.append(regex) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
833 self._exclude_regex_stale() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
835 def _exclude_regex_stale(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
836 """Drop all the compiled exclusion regexes, a list was modified."""
837 self._exclude_re.clear() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
839 def _exclude_regex(self, which: str) -> str: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
840 """Return a regex string for the given exclusion list."""
841 if which not in self._exclude_re: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
842 excl_list = getattr(self.config, f"{which}_list") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
843 self._exclude_re[which] = join_regex(excl_list) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
844 return self._exclude_re[which] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
846 def get_exclude_list(self, which: str = "exclude") -> list[str]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
847 """Return a list of excluded regex strings.
849 `which` indicates which list is desired. See :meth:`exclude` for the
850 lists that are available, and their meaning.
852 """
853 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
854 return cast(list[str], getattr(self.config, f"{which}_list")) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
856 def save(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
857 """Save the collected coverage data to the data file."""
858 data = self.get_data() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
859 data.write() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
861 def _make_aliases(self) -> PathAliases: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
862 """Create a PathAliases from our configuration."""
863 aliases = PathAliases( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
864 debugfn=(self._debug.write if self._debug.should("pathmap") else None),
865 relative=self.config.relative_files,
866 )
867 for paths in self.config.paths.values(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
868 result = paths[0] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
869 for pattern in paths[1:]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
870 aliases.add(pattern, result) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
871 return aliases 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
873 def combine( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
874 self,
875 data_paths: Iterable[str] | None = None,
876 strict: bool = False,
877 keep: bool = False,
878 ) -> None:
879 """Combine together a number of similarly-named coverage data files.
881 All coverage data files whose name starts with `data_file` (from the
882 coverage() constructor) will be read, and combined together into the
883 current measurements.
885 `data_paths` is a list of files or directories from which data should
886 be combined. If no list is passed, then the data files from the
887 directory indicated by the current data file (probably the current
888 directory) will be combined.
890 If `strict` is true, then it is an error to attempt to combine when
891 there are no data files to combine.
893 If `keep` is true, then original input data files won't be deleted.
895 .. versionadded:: 4.0
896 The `data_paths` parameter.
898 .. versionadded:: 4.3
899 The `strict` parameter.
901 .. versionadded: 5.5
902 The `keep` parameter.
903 """
904 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
905 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
906 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
907 self.get_data() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
909 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
910 combine_parallel_data( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
911 self._data,
912 aliases=self._make_aliases(),
913 data_paths=data_paths,
914 strict=strict,
915 keep=keep,
916 message=self._message,
917 )
919 def get_data(self) -> CoverageData: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
920 """Get the collected data.
922 Also warn about various problems collecting data.
924 Returns a :class:`coverage.CoverageData`, the collected coverage data.
926 .. versionadded:: 4.0
928 """
929 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
930 self._init_data(suffix=None) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
931 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
933 if self._collector is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
934 for plugin in self._plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
935 if not plugin._coverage_enabled: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
936 self._collector.plugin_was_disabled(plugin) 1stTuvUwxVyzWABXCDYEFZGH0IJ1
938 if self._collector.flush_data(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
939 self._post_save_work() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
941 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
942 return self._data 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
944 def _post_save_work(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
945 """After saving data, look for warnings, post-work, etc.
947 Warn about things that should have happened but didn't.
948 Look for un-executed files.
950 """
951 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
952 assert self._inorout is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
954 # If there are still entries in the source_pkgs_unmatched list,
955 # then we never encountered those packages.
956 if self._warn_unimported_source: 956 ↛ 960line 956 didn't jump to line 960 because the condition on line 956 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
957 self._inorout.warn_unimported_source() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
959 # Find out if we got any data.
960 if not self._data and self._warn_no_data: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
961 self._warn("No data was collected.", slug="no-data-collected") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7DlYP%Em5FnZQ'Go!Hp0R(Iq$Jr1S)234
963 # Touch all the files that could have executed, so that we can
964 # mark completely un-executed files as 0% covered.
965 file_paths = collections.defaultdict(list) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
966 for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
967 file_path = self._file_mapper(file_path) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
968 file_paths[plugin_name].append(file_path) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
969 for plugin_name, paths in file_paths.items(): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
970 self._data.touch_files(paths, plugin_name) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
972 # Backward compatibility with version 1.
973 def analysis(self, morf: TMorf) -> tuple[str, list[TLineNo], list[TLineNo], str]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
974 """Like `analysis2` but doesn't return excluded line numbers."""
975 f, s, _, m, mf = self.analysis2(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
976 return f, s, m, mf 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
978 def analysis2( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
979 self,
980 morf: TMorf,
981 ) -> tuple[str, list[TLineNo], list[TLineNo], list[TLineNo], str]:
982 """Analyze a module.
984 `morf` is a module or a file name. It will be analyzed to determine
985 its coverage statistics. The return value is a 5-tuple:
987 * The file name for the module.
988 * A list of line numbers of executable statements.
989 * A list of line numbers of excluded statements.
990 * A list of line numbers of statements not run (missing from
991 execution).
992 * A readable formatted string of the missing line numbers.
994 The analysis uses the source file itself and the current measured
995 coverage data.
997 """
998 analysis = self._analyze(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
999 return ( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1000 analysis.filename,
1001 sorted(analysis.statements),
1002 sorted(analysis.excluded),
1003 sorted(analysis.missing),
1004 analysis.missing_formatted(),
1005 )
1007 @functools.lru_cache(maxsize=1) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1008 def _analyze(self, morf: TMorf) -> Analysis: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1009 """Analyze a module or file. Private for now."""
1010 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1011 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1013 data = self.get_data() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1014 file_reporter = self._get_file_reporter(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1015 filename = self._file_mapper(file_reporter.filename) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1016 return analysis_from_file_reporter(data, self.config.precision, file_reporter, filename) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1018 def branch_stats(self, morf: TMorf) -> dict[TLineNo, tuple[int, int]]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1019 """Get branch statistics about a module.
1021 `morf` is a module or a file name.
1023 Returns a dict mapping line numbers to a tuple:
1024 (total_exits, taken_exits).
1026 .. versionadded:: 7.7
1028 """
1029 analysis = self._analyze(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1030 return analysis.branch_stats() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1032 @functools.lru_cache(maxsize=1) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1033 def _get_file_reporter(self, morf: TMorf) -> FileReporter: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1034 """Get a FileReporter for a module or file name."""
1035 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1036 plugin = None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1037 file_reporter: str | FileReporter = "python" 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1039 if isinstance(morf, str): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1040 mapped_morf = self._file_mapper(morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1041 plugin_name = self._data.file_tracer(mapped_morf) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1042 if plugin_name: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1043 plugin = self._plugins.get(plugin_name) 1stTuvUwxVyzWABXCDYEFZGH0IJ1
1045 if plugin: 1045 ↛ 1055line 1045 didn't jump to line 1055 because the condition on line 1045 was always true1stTuvUwxVyzWABXCDYEFZGH0IJ1
1046 file_reporter = plugin.file_reporter(mapped_morf) 1stTuvUwxVyzWABXCDYEFZGH0IJ1
1047 if file_reporter is None: 1047 ↛ 1048line 1047 didn't jump to line 1048 because the condition on line 1047 was never true1stTuvUwxVyzWABXCDYEFZGH0IJ1
1048 raise PluginError(
1049 "Plugin {!r} did not provide a file reporter for {!r}.".format(
1050 plugin._coverage_plugin_name,
1051 morf,
1052 ),
1053 )
1055 if file_reporter == "python": 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1056 file_reporter = PythonFileReporter(morf, self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1058 assert isinstance(file_reporter, FileReporter) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1059 return file_reporter 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1061 def _get_file_reporters( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1062 self,
1063 morfs: Iterable[TMorf] | None = None,
1064 ) -> list[tuple[FileReporter, TMorf]]:
1065 """Get FileReporters for a list of modules or file names.
1067 For each module or file name in `morfs`, find a FileReporter. Return
1068 a list pairing FileReporters with the morfs.
1070 If `morfs` is a single module or file name, this returns a list of one
1071 FileReporter. If `morfs` is empty or None, then the list of all files
1072 measured is used to find the FileReporters.
1074 """
1075 assert self._data is not None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1076 if not morfs: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1077 morfs = self._data.measured_files() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1079 # Be sure we have a collection.
1080 if not isinstance(morfs, (list, tuple, set)): 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1081 morfs = [morfs] # type: ignore[list-item] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YPEm5Fn9ZQGoHp0RIq$Jr1S234
1083 morfs = sorted(morfs, key=lambda m: m if isinstance(m, str) else m.__name__) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1084 return [(self._get_file_reporter(morf), morf) for morf in morfs] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1086 def _prepare_data_for_reporting(self) -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1087 """Re-map data before reporting, to get implicit "combine" behavior."""
1088 if self.config.paths: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1089 mapped_data = CoverageData(warn=self._warn, debug=self._debug, no_disk=True) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
1090 if self._data is not None: 1090 ↛ 1092line 1090 didn't jump to line 1092 because the condition on line 1090 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
1091 mapped_data.update(self._data, map_path=self._make_aliases().map) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
1092 self._data = mapped_data 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
1093 self._data_to_close.append(mapped_data) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGoHp0RIqJr1S234
1095 def report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1096 self,
1097 morfs: Iterable[TMorf] | None = None,
1098 show_missing: bool | None = None,
1099 ignore_errors: bool | None = None,
1100 file: IO[str] | None = None,
1101 omit: str | list[str] | None = None,
1102 include: str | list[str] | None = None,
1103 skip_covered: bool | None = None,
1104 contexts: list[str] | None = None,
1105 skip_empty: bool | None = None,
1106 precision: int | None = None,
1107 sort: str | None = None,
1108 output_format: str | None = None,
1109 ) -> float:
1110 """Write a textual summary report to `file`.
1112 Each module in `morfs` is listed, with counts of statements, executed
1113 statements, missing statements, and a list of lines missed.
1115 If `show_missing` is true, then details of which lines or branches are
1116 missing will be included in the report. If `ignore_errors` is true,
1117 then a failure while reporting a single file will not stop the entire
1118 report.
1120 `file` is a file-like object, suitable for writing.
1122 `output_format` determines the format, either "text" (the default),
1123 "markdown", or "total".
1125 `include` is a list of file name patterns. Files that match will be
1126 included in the report. Files matching `omit` will not be included in
1127 the report.
1129 If `skip_covered` is true, don't report on files with 100% coverage.
1131 If `skip_empty` is true, don't report on empty files (those that have
1132 no statements).
1134 `contexts` is a list of regular expression strings. Only data from
1135 :ref:`dynamic contexts <dynamic_contexts>` that match one of those
1136 expressions (using :func:`re.search <python:re.search>`) will be
1137 included in the report.
1139 `precision` is the number of digits to display after the decimal
1140 point for percentages.
1142 All of the arguments default to the settings read from the
1143 :ref:`configuration file <config>`.
1145 Returns a float, the total percentage covered.
1147 .. versionadded:: 4.0
1148 The `skip_covered` parameter.
1150 .. versionadded:: 5.0
1151 The `contexts` and `skip_empty` parameters.
1153 .. versionadded:: 5.2
1154 The `precision` parameter.
1156 .. versionadded:: 7.0
1157 The `format` parameter.
1159 """
1160 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1161 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1162 self,
1163 ignore_errors=ignore_errors,
1164 report_omit=omit,
1165 report_include=include,
1166 show_missing=show_missing,
1167 skip_covered=skip_covered,
1168 report_contexts=contexts,
1169 skip_empty=skip_empty,
1170 precision=precision,
1171 sort=sort,
1172 format=output_format,
1173 ):
1174 reporter = SummaryReporter(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1175 return reporter.report(morfs, outfile=file) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1177 def annotate( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1178 self,
1179 morfs: Iterable[TMorf] | None = None,
1180 directory: str | None = None,
1181 ignore_errors: bool | None = None,
1182 omit: str | list[str] | None = None,
1183 include: str | list[str] | None = None,
1184 contexts: list[str] | None = None,
1185 ) -> None:
1186 """Annotate a list of modules.
1188 Each module in `morfs` is annotated. The source is written to a new
1189 file, named with a ",cover" suffix, with each line prefixed with a
1190 marker to indicate the coverage of the line. Covered lines have ">",
1191 excluded lines have "-", and missing lines have "!".
1193 See :meth:`report` for other arguments.
1195 """
1196 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1197 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1198 self,
1199 ignore_errors=ignore_errors,
1200 report_omit=omit,
1201 report_include=include,
1202 report_contexts=contexts,
1203 ):
1204 reporter = AnnotateReporter(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1205 reporter.report(morfs, directory=directory) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1207 def html_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1208 self,
1209 morfs: Iterable[TMorf] | None = None,
1210 directory: str | None = None,
1211 ignore_errors: bool | None = None,
1212 omit: str | list[str] | None = None,
1213 include: str | list[str] | None = None,
1214 extra_css: str | None = None,
1215 title: str | None = None,
1216 skip_covered: bool | None = None,
1217 show_contexts: bool | None = None,
1218 contexts: list[str] | None = None,
1219 skip_empty: bool | None = None,
1220 precision: int | None = None,
1221 ) -> float:
1222 """Generate an HTML report.
1224 The HTML is written to `directory`. The file "index.html" is the
1225 overview starting point, with links to more detailed pages for
1226 individual modules.
1228 `extra_css` is a path to a file of other CSS to apply on the page.
1229 It will be copied into the HTML directory.
1231 `title` is a text string (not HTML) to use as the title of the HTML
1232 report.
1234 See :meth:`report` for other arguments.
1236 Returns a float, the total percentage covered.
1238 .. note::
1240 The HTML report files are generated incrementally based on the
1241 source files and coverage results. If you modify the report files,
1242 the changes will not be considered. You should be careful about
1243 changing the files in the report folder.
1245 """
1246 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1247 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1248 self,
1249 ignore_errors=ignore_errors,
1250 report_omit=omit,
1251 report_include=include,
1252 html_dir=directory,
1253 extra_css=extra_css,
1254 html_title=title,
1255 html_skip_covered=skip_covered,
1256 show_contexts=show_contexts,
1257 report_contexts=contexts,
1258 html_skip_empty=skip_empty,
1259 precision=precision,
1260 ):
1261 reporter = HtmlReporter(self) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1262 return reporter.report(morfs) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1264 def xml_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1265 self,
1266 morfs: Iterable[TMorf] | None = None,
1267 outfile: str | None = None,
1268 ignore_errors: bool | None = None,
1269 omit: str | list[str] | None = None,
1270 include: str | list[str] | None = None,
1271 contexts: list[str] | None = None,
1272 skip_empty: bool | None = None,
1273 ) -> float:
1274 """Generate an XML report of coverage results.
1276 The report is compatible with Cobertura reports.
1278 Each module in `morfs` is included in the report. `outfile` is the
1279 path to write the file to, "-" will write to stdout.
1281 See :meth:`report` for other arguments.
1283 Returns a float, the total percentage covered.
1285 """
1286 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1287 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1288 self,
1289 ignore_errors=ignore_errors,
1290 report_omit=omit,
1291 report_include=include,
1292 xml_output=outfile,
1293 report_contexts=contexts,
1294 skip_empty=skip_empty,
1295 ):
1296 return render_report(self.config.xml_output, XmlReporter(self), morfs, self._message) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1298 def json_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1299 self,
1300 morfs: Iterable[TMorf] | None = None,
1301 outfile: str | None = None,
1302 ignore_errors: bool | None = None,
1303 omit: str | list[str] | None = None,
1304 include: str | list[str] | None = None,
1305 contexts: list[str] | None = None,
1306 pretty_print: bool | None = None,
1307 show_contexts: bool | None = None,
1308 ) -> float:
1309 """Generate a JSON report of coverage results.
1311 Each module in `morfs` is included in the report. `outfile` is the
1312 path to write the file to, "-" will write to stdout.
1314 `pretty_print` is a boolean, whether to pretty-print the JSON output or not.
1316 See :meth:`report` for other arguments.
1318 Returns a float, the total percentage covered.
1320 .. versionadded:: 5.0
1322 """
1323 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1324 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1325 self,
1326 ignore_errors=ignore_errors,
1327 report_omit=omit,
1328 report_include=include,
1329 json_output=outfile,
1330 report_contexts=contexts,
1331 json_pretty_print=pretty_print,
1332 json_show_contexts=show_contexts,
1333 ):
1334 return render_report(self.config.json_output, JsonReporter(self), morfs, self._message) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1336 def lcov_report( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)
1337 self,
1338 morfs: Iterable[TMorf] | None = None,
1339 outfile: str | None = None,
1340 ignore_errors: bool | None = None,
1341 omit: str | list[str] | None = None,
1342 include: str | list[str] | None = None,
1343 contexts: list[str] | None = None,
1344 ) -> float:
1345 """Generate an LCOV report of coverage results.
1347 Each module in `morfs` is included in the report. `outfile` is the
1348 path to write the file to, "-" will write to stdout.
1350 See :meth:`report` for other arguments.
1352 .. versionadded:: 6.3
1353 """
1354 self._prepare_data_for_reporting() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1355 with override_config( 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1356 self,
1357 ignore_errors=ignore_errors,
1358 report_omit=omit,
1359 report_include=include,
1360 lcov_output=outfile,
1361 report_contexts=contexts,
1362 ):
1363 return render_report(self.config.lcov_output, LcovReporter(self), morfs, self._message) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1365 def sys_info(self) -> Iterable[tuple[str, Any]]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1366 """Return a list of (key, value) pairs showing internal information."""
1368 import coverage as covmod 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1370 self._init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1371 self._post_init() 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1373 def plugin_info(plugins: list[Any]) -> list[str]: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1374 """Make an entry for the sys_info from a list of plug-ins."""
1375 entries = [] 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1376 for plugin in plugins: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1377 entry = plugin._coverage_plugin_name 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGo!Hp0RIqJr61S234
1378 if not plugin._coverage_enabled: 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGo!Hp0RIqJr61S234
1379 entry += " (disabled)" 1abKcdLefMghNijOklPmnQopRqr6S234
1380 entries.append(entry) 1satbTKucvdULwexfVMygzhWNAiBjXOCkDlYPEmFnZQGo!Hp0RIqJr61S234
1381 return entries 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1383 info = [ 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1384 ("coverage_version", covmod.__version__),
1385 ("coverage_module", covmod.__file__),
1386 ("core", self._collector.tracer_name() if self._collector is not None else "-none-"),
1387 ("CTracer", f"available from {CTRACER_FILE}" if CTRACER_FILE else "unavailable"),
1388 ("plugins.file_tracers", plugin_info(self._plugins.file_tracers)),
1389 ("plugins.configurers", plugin_info(self._plugins.configurers)),
1390 ("plugins.context_switchers", plugin_info(self._plugins.context_switchers)),
1391 ("configs_attempted", self.config.config_files_attempted),
1392 ("configs_read", self.config.config_files_read),
1393 ("config_file", self.config.config_file),
1394 (
1395 "config_contents",
1396 repr(self.config._config_contents) if self.config._config_contents else "-none-",
1397 ),
1398 ("data_file", self._data.data_filename() if self._data is not None else "-none-"),
1399 ("python", sys.version.replace("\n", "")),
1400 ("platform", platform.platform()),
1401 ("implementation", platform.python_implementation()),
1402 ("build", platform.python_build()),
1403 ("gil_enabled", getattr(sys, "_is_gil_enabled", lambda: True)()),
1404 ("executable", sys.executable),
1405 ("def_encoding", sys.getdefaultencoding()),
1406 ("fs_encoding", sys.getfilesystemencoding()),
1407 ("pid", os.getpid()),
1408 ("cwd", os.getcwd()),
1409 ("path", sys.path),
1410 ("environment", [f"{k} = {v}" for k, v in relevant_environment_display(os.environ)]),
1411 ("command_line", " ".join(getattr(sys, "argv", ["-none-"]))),
1412 ("time", f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S}"),
1413 ]
1415 if self._inorout is not None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1416 info.extend(self._inorout.sys_info()) 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1418 return info 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1421# Mega debugging...
1422# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
1423if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1424 from coverage.debug import decorate_methods, show_calls
1426 Coverage = decorate_methods( # type: ignore[misc]
1427 show_calls(show_args=True),
1428 butnot=["get_data"],
1429 )(Coverage)
1432def process_startup(*, force: bool = False) -> Coverage | None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1433 """Call this at Python start-up to perhaps measure coverage.
1435 If the environment variable COVERAGE_PROCESS_START is defined, coverage
1436 measurement is started. The value of the variable is the config file
1437 to use.
1439 For details, see https://coverage.readthedocs.io/en/latest/subprocess.html.
1441 Returns the :class:`Coverage` instance that was started, or None if it was
1442 not started by this call.
1444 """
1445 config_data = os.getenv("COVERAGE_PROCESS_CONFIG") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1446 cps = os.getenv("COVERAGE_PROCESS_START") 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1447 if config_data is not None: 1447 ↛ 1448line 1447 didn't jump to line 1448 because the condition on line 1447 was never true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1448 config_file = CONFIG_DATA_PREFIX + config_data
1449 elif cps is not None: 1449 ↛ 1453line 1449 didn't jump to line 1453 because the condition on line 1449 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1450 config_file = cps 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1451 else:
1452 # No request for coverage, nothing to do.
1453 return None
1455 # This function can be called more than once in a process. This happens
1456 # because some virtualenv configurations make the same directory visible
1457 # twice in sys.path. This means that the .pth file will be found twice,
1458 # and executed twice, executing this function twice. We set a global
1459 # flag (an attribute on this function) to indicate that coverage.py has
1460 # already been started, so we can avoid doing it twice.
1461 #
1462 # https://github.com/coveragepy/coveragepy/issues/340 has more details.
1464 if not force and hasattr(process_startup, "coverage"): 1464 ↛ 1469line 1464 didn't jump to line 1469 because the condition on line 1464 was always true1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1465 # We've annotated this function before, so we must have already
1466 # auto-started coverage.py in this process. Nothing to do.
1467 return None 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1469 cov = Coverage(config_file=config_file)
1470 process_startup.coverage = cov # type: ignore[attr-defined]
1471 cov._warn_no_data = False
1472 cov._warn_unimported_source = False
1473 cov._warn_preimported_source = False
1474 cov._auto_save = True
1475 cov._make_pth_file = False
1476 cov.start()
1478 return cov
1481def _after_fork_in_child() -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1482 """Used by patch=fork in the child process to restart coverage."""
1483 if cov := Coverage.current():
1484 cov.stop()
1485 process_startup(force=True)
1488def _prevent_sub_process_measurement() -> None: 1satbTKucvdULwexfVMygzhWNAiBjXOCk7Dl8YP%Em5Fn9ZQ'Go!Hp#0R(Iq$Jr61S)234
1489 """Stop any subprocess auto-measurement from writing data."""
1490 auto_created_coverage = getattr(process_startup, "coverage", None)
1491 if auto_created_coverage is not None:
1492 auto_created_coverage._auto_save = False