coverage 7.6.7__cp311-cp311-win_amd64.whl → 7.11.1__cp311-cp311-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coverage/__init__.py +2 -0
- coverage/__main__.py +2 -0
- coverage/annotate.py +1 -2
- coverage/bytecode.py +177 -3
- coverage/cmdline.py +329 -154
- coverage/collector.py +31 -42
- coverage/config.py +166 -62
- coverage/context.py +4 -5
- coverage/control.py +164 -85
- coverage/core.py +70 -33
- coverage/data.py +3 -4
- coverage/debug.py +112 -56
- coverage/disposition.py +1 -0
- coverage/env.py +65 -55
- coverage/exceptions.py +35 -7
- coverage/execfile.py +18 -13
- coverage/files.py +23 -18
- coverage/html.py +134 -88
- coverage/htmlfiles/style.css +42 -2
- coverage/htmlfiles/style.scss +65 -1
- coverage/inorout.py +61 -44
- coverage/jsonreport.py +17 -8
- coverage/lcovreport.py +16 -20
- coverage/misc.py +50 -46
- coverage/multiproc.py +12 -7
- coverage/numbits.py +3 -4
- coverage/parser.py +193 -269
- coverage/patch.py +166 -0
- coverage/phystokens.py +24 -25
- coverage/plugin.py +13 -13
- coverage/plugin_support.py +36 -35
- coverage/python.py +9 -13
- coverage/pytracer.py +40 -33
- coverage/regions.py +2 -1
- coverage/report.py +59 -43
- coverage/report_core.py +6 -9
- coverage/results.py +118 -66
- coverage/sqldata.py +260 -210
- coverage/sqlitedb.py +33 -25
- coverage/sysmon.py +195 -157
- coverage/templite.py +6 -6
- coverage/tomlconfig.py +12 -12
- coverage/tracer.cp311-win_amd64.pyd +0 -0
- coverage/tracer.pyi +2 -0
- coverage/types.py +25 -22
- coverage/version.py +3 -18
- coverage/xmlreport.py +16 -13
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/METADATA +40 -18
- coverage-7.11.1.dist-info/RECORD +59 -0
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/WHEEL +1 -1
- coverage-7.6.7.dist-info/RECORD +0 -58
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/entry_points.txt +0 -0
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info/licenses}/LICENSE.txt +0 -0
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/top_level.txt +0 -0
coverage/control.py
CHANGED
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
import atexit
|
|
9
9
|
import collections
|
|
10
10
|
import contextlib
|
|
11
|
+
import datetime
|
|
11
12
|
import functools
|
|
12
13
|
import os
|
|
13
14
|
import os.path
|
|
@@ -17,20 +18,23 @@ import sys
|
|
|
17
18
|
import threading
|
|
18
19
|
import time
|
|
19
20
|
import warnings
|
|
20
|
-
|
|
21
|
-
from types import FrameType
|
|
22
|
-
from typing import cast, Any, Callable, IO
|
|
23
21
|
from collections.abc import Iterable, Iterator
|
|
22
|
+
from types import FrameType
|
|
23
|
+
from typing import IO, Any, Callable, cast
|
|
24
24
|
|
|
25
25
|
from coverage import env
|
|
26
26
|
from coverage.annotate import AnnotateReporter
|
|
27
27
|
from coverage.collector import Collector
|
|
28
28
|
from coverage.config import CoverageConfig, read_coverage_config
|
|
29
|
-
from coverage.context import
|
|
30
|
-
from coverage.core import
|
|
29
|
+
from coverage.context import combine_context_switchers, should_start_context_test_function
|
|
30
|
+
from coverage.core import CTRACER_FILE, Core
|
|
31
31
|
from coverage.data import CoverageData, combine_parallel_data
|
|
32
32
|
from coverage.debug import (
|
|
33
|
-
DebugControl,
|
|
33
|
+
DebugControl,
|
|
34
|
+
NoDebugging,
|
|
35
|
+
relevant_environment_display,
|
|
36
|
+
short_stack,
|
|
37
|
+
write_formatted_info,
|
|
34
38
|
)
|
|
35
39
|
from coverage.disposition import disposition_debug_msg
|
|
36
40
|
from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError
|
|
@@ -39,23 +43,37 @@ from coverage.html import HtmlReporter
|
|
|
39
43
|
from coverage.inorout import InOrOut
|
|
40
44
|
from coverage.jsonreport import JsonReporter
|
|
41
45
|
from coverage.lcovreport import LcovReporter
|
|
42
|
-
from coverage.misc import
|
|
43
|
-
|
|
46
|
+
from coverage.misc import (
|
|
47
|
+
DefaultValue,
|
|
48
|
+
bool_or_none,
|
|
49
|
+
ensure_dir_for_file,
|
|
50
|
+
isolate_module,
|
|
51
|
+
join_regex,
|
|
52
|
+
)
|
|
44
53
|
from coverage.multiproc import patch_multiprocessing
|
|
54
|
+
from coverage.patch import apply_patches
|
|
45
55
|
from coverage.plugin import FileReporter
|
|
46
|
-
from coverage.plugin_support import Plugins
|
|
56
|
+
from coverage.plugin_support import Plugins, TCoverageInit
|
|
47
57
|
from coverage.python import PythonFileReporter
|
|
48
58
|
from coverage.report import SummaryReporter
|
|
49
59
|
from coverage.report_core import render_report
|
|
50
60
|
from coverage.results import Analysis, analysis_from_file_reporter
|
|
51
61
|
from coverage.types import (
|
|
52
|
-
FilePath,
|
|
53
|
-
|
|
62
|
+
FilePath,
|
|
63
|
+
TConfigSectionIn,
|
|
64
|
+
TConfigurable,
|
|
65
|
+
TConfigValueIn,
|
|
66
|
+
TConfigValueOut,
|
|
67
|
+
TFileDisposition,
|
|
68
|
+
TLineNo,
|
|
69
|
+
TMorf,
|
|
54
70
|
)
|
|
71
|
+
from coverage.version import __url__
|
|
55
72
|
from coverage.xmlreport import XmlReporter
|
|
56
73
|
|
|
57
74
|
os = isolate_module(os)
|
|
58
75
|
|
|
76
|
+
|
|
59
77
|
@contextlib.contextmanager
|
|
60
78
|
def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]:
|
|
61
79
|
"""Temporarily tweak the configuration of `cov`.
|
|
@@ -74,6 +92,8 @@ def override_config(cov: Coverage, **kwargs: TConfigValueIn) -> Iterator[None]:
|
|
|
74
92
|
|
|
75
93
|
DEFAULT_DATAFILE = DefaultValue("MISSING")
|
|
76
94
|
_DEFAULT_DATAFILE = DEFAULT_DATAFILE # Just in case, for backwards compatibility
|
|
95
|
+
CONFIG_DATA_PREFIX = ":data:"
|
|
96
|
+
|
|
77
97
|
|
|
78
98
|
class Coverage(TConfigurable):
|
|
79
99
|
"""Programmatic access to coverage.py.
|
|
@@ -120,7 +140,7 @@ class Coverage(TConfigurable):
|
|
|
120
140
|
else:
|
|
121
141
|
return None
|
|
122
142
|
|
|
123
|
-
def __init__(
|
|
143
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
124
144
|
self,
|
|
125
145
|
data_file: FilePath | DefaultValue | None = DEFAULT_DATAFILE,
|
|
126
146
|
data_suffix: str | bool | None = None,
|
|
@@ -131,6 +151,7 @@ class Coverage(TConfigurable):
|
|
|
131
151
|
config_file: FilePath | bool = True,
|
|
132
152
|
source: Iterable[str] | None = None,
|
|
133
153
|
source_pkgs: Iterable[str] | None = None,
|
|
154
|
+
source_dirs: Iterable[str] | None = None,
|
|
134
155
|
omit: str | Iterable[str] | None = None,
|
|
135
156
|
include: str | Iterable[str] | None = None,
|
|
136
157
|
debug: Iterable[str] | None = None,
|
|
@@ -138,6 +159,7 @@ class Coverage(TConfigurable):
|
|
|
138
159
|
check_preimported: bool = False,
|
|
139
160
|
context: str | None = None,
|
|
140
161
|
messages: bool = False,
|
|
162
|
+
plugins: Iterable[Callable[..., None]] | None = None,
|
|
141
163
|
) -> None:
|
|
142
164
|
"""
|
|
143
165
|
Many of these arguments duplicate and override values that can be
|
|
@@ -187,6 +209,10 @@ class Coverage(TConfigurable):
|
|
|
187
209
|
`source`, but can be used to name packages where the name can also be
|
|
188
210
|
interpreted as a file path.
|
|
189
211
|
|
|
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.
|
|
215
|
+
|
|
190
216
|
`include` and `omit` are lists of file name patterns. Files that match
|
|
191
217
|
`include` will be measured, files that match `omit` will not. Each
|
|
192
218
|
will also accept a single string argument.
|
|
@@ -211,6 +237,11 @@ class Coverage(TConfigurable):
|
|
|
211
237
|
If `messages` is true, some messages will be printed to stdout
|
|
212
238
|
indicating what is happening.
|
|
213
239
|
|
|
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.
|
|
244
|
+
|
|
214
245
|
.. versionadded:: 4.0
|
|
215
246
|
The `concurrency` parameter.
|
|
216
247
|
|
|
@@ -226,6 +257,11 @@ class Coverage(TConfigurable):
|
|
|
226
257
|
.. versionadded:: 6.0
|
|
227
258
|
The `messages` parameter.
|
|
228
259
|
|
|
260
|
+
.. versionadded:: 7.7
|
|
261
|
+
The `plugins` parameter.
|
|
262
|
+
|
|
263
|
+
.. versionadded:: 7.8
|
|
264
|
+
The `source_dirs` parameter.
|
|
229
265
|
"""
|
|
230
266
|
# Start self.config as a usable default configuration. It will soon be
|
|
231
267
|
# replaced with the real configuration.
|
|
@@ -249,9 +285,12 @@ class Coverage(TConfigurable):
|
|
|
249
285
|
self._warn_no_data = True
|
|
250
286
|
self._warn_unimported_source = True
|
|
251
287
|
self._warn_preimported_source = check_preimported
|
|
252
|
-
self._no_warn_slugs:
|
|
288
|
+
self._no_warn_slugs: set[str] = set()
|
|
253
289
|
self._messages = messages
|
|
254
290
|
|
|
291
|
+
# If we're invoked from a .pth file, we shouldn't try to make another one.
|
|
292
|
+
self._make_pth_file = True
|
|
293
|
+
|
|
255
294
|
# A record of all the warnings that have been issued.
|
|
256
295
|
self._warnings: list[str] = []
|
|
257
296
|
|
|
@@ -260,7 +299,9 @@ class Coverage(TConfigurable):
|
|
|
260
299
|
self._debug: DebugControl = NoDebugging()
|
|
261
300
|
self._inorout: InOrOut | None = None
|
|
262
301
|
self._plugins: Plugins = Plugins()
|
|
302
|
+
self._plugin_override = cast(Iterable[TCoverageInit] | None, plugins)
|
|
263
303
|
self._data: CoverageData | None = None
|
|
304
|
+
self._data_to_close: list[CoverageData] = []
|
|
264
305
|
self._core: Core | None = None
|
|
265
306
|
self._collector: Collector | None = None
|
|
266
307
|
self._metacov = False
|
|
@@ -280,28 +321,32 @@ class Coverage(TConfigurable):
|
|
|
280
321
|
self._should_write_debug = True
|
|
281
322
|
|
|
282
323
|
# Build our configuration from a number of sources.
|
|
283
|
-
if
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
config_file
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
324
|
+
if isinstance(config_file, str) and config_file.startswith(CONFIG_DATA_PREFIX):
|
|
325
|
+
self.config = CoverageConfig.deserialize(config_file[len(CONFIG_DATA_PREFIX) :])
|
|
326
|
+
else:
|
|
327
|
+
if not isinstance(config_file, bool):
|
|
328
|
+
config_file = os.fspath(config_file)
|
|
329
|
+
self.config = read_coverage_config(
|
|
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
|
+
)
|
|
303
348
|
|
|
304
|
-
# If we have
|
|
349
|
+
# If we have subprocess measurement happening automatically, then we
|
|
305
350
|
# want any explicit creation of a Coverage object to mean, this process
|
|
306
351
|
# is already coverage-aware, so don't auto-measure it. By now, the
|
|
307
352
|
# auto-creation of a Coverage object has already happened. But we can
|
|
@@ -340,7 +385,11 @@ class Coverage(TConfigurable):
|
|
|
340
385
|
self._file_mapper = relative_filename
|
|
341
386
|
|
|
342
387
|
# Load plugins
|
|
343
|
-
self._plugins = Plugins
|
|
388
|
+
self._plugins = Plugins(self._debug)
|
|
389
|
+
if self._plugin_override:
|
|
390
|
+
self._plugins.load_from_callables(self._plugin_override)
|
|
391
|
+
else:
|
|
392
|
+
self._plugins.load_from_config(self.config.plugins, self.config)
|
|
344
393
|
|
|
345
394
|
# Run configuring plugins.
|
|
346
395
|
for plugin in self._plugins.configurers:
|
|
@@ -366,22 +415,24 @@ class Coverage(TConfigurable):
|
|
|
366
415
|
wrote_any = False
|
|
367
416
|
with self._debug.without_callers():
|
|
368
417
|
if self._debug.should("config"):
|
|
369
|
-
|
|
370
|
-
write_formatted_info(self._debug.write, "config", config_info)
|
|
418
|
+
write_formatted_info(self._debug.write, "config", self.config.debug_info())
|
|
371
419
|
wrote_any = True
|
|
372
420
|
|
|
373
421
|
if self._debug.should("sys"):
|
|
374
422
|
write_formatted_info(self._debug.write, "sys", self.sys_info())
|
|
375
423
|
for plugin in self._plugins:
|
|
376
424
|
header = "sys: " + plugin._coverage_plugin_name
|
|
377
|
-
|
|
378
|
-
write_formatted_info(self._debug.write, header, info)
|
|
425
|
+
write_formatted_info(self._debug.write, header, plugin.sys_info())
|
|
379
426
|
wrote_any = True
|
|
380
427
|
|
|
381
428
|
if self._debug.should("pybehave"):
|
|
382
429
|
write_formatted_info(self._debug.write, "pybehave", env.debug_info())
|
|
383
430
|
wrote_any = True
|
|
384
431
|
|
|
432
|
+
if self._debug.should("sqlite"):
|
|
433
|
+
write_formatted_info(self._debug.write, "sqlite", CoverageData.sys_info())
|
|
434
|
+
wrote_any = True
|
|
435
|
+
|
|
385
436
|
if wrote_any:
|
|
386
437
|
write_formatted_info(self._debug.write, "end", ())
|
|
387
438
|
|
|
@@ -424,7 +475,7 @@ class Coverage(TConfigurable):
|
|
|
424
475
|
|
|
425
476
|
"""
|
|
426
477
|
if not self._no_warn_slugs:
|
|
427
|
-
self._no_warn_slugs =
|
|
478
|
+
self._no_warn_slugs = set(self.config.disable_warnings)
|
|
428
479
|
|
|
429
480
|
if slug in self._no_warn_slugs:
|
|
430
481
|
# Don't issue the warning
|
|
@@ -432,14 +483,14 @@ class Coverage(TConfigurable):
|
|
|
432
483
|
|
|
433
484
|
self._warnings.append(msg)
|
|
434
485
|
if slug:
|
|
435
|
-
msg = f"{msg} ({slug})"
|
|
486
|
+
msg = f"{msg} ({slug}); see {__url__}/messages.html#warning-{slug}"
|
|
436
487
|
if self._debug.should("pid"):
|
|
437
488
|
msg = f"[{os.getpid()}] {msg}"
|
|
438
489
|
warnings.warn(msg, category=CoverageWarning, stacklevel=2)
|
|
439
490
|
|
|
440
491
|
if once:
|
|
441
492
|
assert slug is not None
|
|
442
|
-
self._no_warn_slugs.
|
|
493
|
+
self._no_warn_slugs.add(slug)
|
|
443
494
|
|
|
444
495
|
def _message(self, msg: str) -> None:
|
|
445
496
|
"""Write a message to the user, if configured to do so."""
|
|
@@ -512,7 +563,7 @@ class Coverage(TConfigurable):
|
|
|
512
563
|
def _init_for_start(self) -> None:
|
|
513
564
|
"""Initialization for start()"""
|
|
514
565
|
# Construct the collector.
|
|
515
|
-
concurrency: list[str] = self.config.concurrency
|
|
566
|
+
concurrency: list[str] = self.config.concurrency
|
|
516
567
|
if "multiprocessing" in concurrency:
|
|
517
568
|
if self.config.config_file is None:
|
|
518
569
|
raise ConfigError("multiprocessing requires a configuration file")
|
|
@@ -534,7 +585,9 @@ class Coverage(TConfigurable):
|
|
|
534
585
|
|
|
535
586
|
self._core = Core(
|
|
536
587
|
warn=self._warn,
|
|
537
|
-
|
|
588
|
+
debug=(self._debug if self._debug.should("core") else None),
|
|
589
|
+
config=self.config,
|
|
590
|
+
dynamic_contexts=(should_start_context is not None),
|
|
538
591
|
metacov=self._metacov,
|
|
539
592
|
)
|
|
540
593
|
self._collector = Collector(
|
|
@@ -571,8 +624,7 @@ class Coverage(TConfigurable):
|
|
|
571
624
|
self._warn(
|
|
572
625
|
"Plugin file tracers ({}) aren't supported with {}".format(
|
|
573
626
|
", ".join(
|
|
574
|
-
plugin._coverage_plugin_name
|
|
575
|
-
for plugin in self._plugins.file_tracers
|
|
627
|
+
plugin._coverage_plugin_name for plugin in self._plugins.file_tracers
|
|
576
628
|
),
|
|
577
629
|
self._collector.tracer_name(),
|
|
578
630
|
),
|
|
@@ -596,13 +648,14 @@ class Coverage(TConfigurable):
|
|
|
596
648
|
# Register our clean-up handlers.
|
|
597
649
|
atexit.register(self._atexit)
|
|
598
650
|
if self.config.sigterm:
|
|
599
|
-
is_main = (threading.current_thread() == threading.main_thread())
|
|
651
|
+
is_main = (threading.current_thread() == threading.main_thread()) # fmt: skip
|
|
600
652
|
if is_main and not env.WINDOWS:
|
|
601
653
|
# The Python docs seem to imply that SIGTERM works uniformly even
|
|
602
654
|
# on Windows, but that's not my experience, and this agrees:
|
|
603
655
|
# https://stackoverflow.com/questions/35772001/x/35792192#35792192
|
|
604
|
-
self._old_sigterm = signal.signal(
|
|
605
|
-
signal.SIGTERM,
|
|
656
|
+
self._old_sigterm = signal.signal( # type: ignore[assignment]
|
|
657
|
+
signal.SIGTERM,
|
|
658
|
+
self._on_sigterm,
|
|
606
659
|
)
|
|
607
660
|
|
|
608
661
|
def _init_data(self, suffix: str | bool | None) -> None:
|
|
@@ -619,6 +672,7 @@ class Coverage(TConfigurable):
|
|
|
619
672
|
debug=self._debug,
|
|
620
673
|
no_disk=self._no_disk,
|
|
621
674
|
)
|
|
675
|
+
self._data_to_close.append(self._data)
|
|
622
676
|
|
|
623
677
|
def start(self) -> None:
|
|
624
678
|
"""Start measuring code coverage.
|
|
@@ -654,6 +708,8 @@ class Coverage(TConfigurable):
|
|
|
654
708
|
if self._auto_load:
|
|
655
709
|
self.load()
|
|
656
710
|
|
|
711
|
+
apply_patches(self, self.config, self._debug, make_pth_file=self._make_pth_file)
|
|
712
|
+
|
|
657
713
|
self._collector.start()
|
|
658
714
|
self._started = True
|
|
659
715
|
self._instances.append(self)
|
|
@@ -679,7 +735,7 @@ class Coverage(TConfigurable):
|
|
|
679
735
|
try:
|
|
680
736
|
yield
|
|
681
737
|
finally:
|
|
682
|
-
self.stop()
|
|
738
|
+
self.stop() # pragma: nested
|
|
683
739
|
|
|
684
740
|
def _atexit(self, event: str = "atexit") -> None:
|
|
685
741
|
"""Clean up on process shutdown."""
|
|
@@ -689,14 +745,16 @@ class Coverage(TConfigurable):
|
|
|
689
745
|
self.stop()
|
|
690
746
|
if self._auto_save or event == "sigterm":
|
|
691
747
|
self.save()
|
|
748
|
+
for d in self._data_to_close:
|
|
749
|
+
d.close(force=True)
|
|
692
750
|
|
|
693
751
|
def _on_sigterm(self, signum_unused: int, frame_unused: FrameType | None) -> None:
|
|
694
752
|
"""A handler for signal.SIGTERM."""
|
|
695
753
|
self._atexit("sigterm")
|
|
696
754
|
# Statements after here won't be seen by metacov because we just wrote
|
|
697
755
|
# the data, and are about to kill the process.
|
|
698
|
-
signal.signal(signal.SIGTERM, self._old_sigterm)
|
|
699
|
-
os.kill(os.getpid(), signal.SIGTERM)
|
|
756
|
+
signal.signal(signal.SIGTERM, self._old_sigterm) # pragma: not covered
|
|
757
|
+
os.kill(os.getpid(), signal.SIGTERM) # pragma: not covered
|
|
700
758
|
|
|
701
759
|
def erase(self) -> None:
|
|
702
760
|
"""Erase previously collected coverage data.
|
|
@@ -728,7 +786,7 @@ class Coverage(TConfigurable):
|
|
|
728
786
|
.. versionadded:: 5.0
|
|
729
787
|
|
|
730
788
|
"""
|
|
731
|
-
if not self._started:
|
|
789
|
+
if not self._started: # pragma: part started
|
|
732
790
|
raise CoverageException("Cannot switch context, coverage is not started")
|
|
733
791
|
|
|
734
792
|
assert self._collector is not None
|
|
@@ -740,7 +798,7 @@ class Coverage(TConfigurable):
|
|
|
740
798
|
def clear_exclude(self, which: str = "exclude") -> None:
|
|
741
799
|
"""Clear the exclude list."""
|
|
742
800
|
self._init()
|
|
743
|
-
setattr(self.config, which
|
|
801
|
+
setattr(self.config, f"{which}_list", [])
|
|
744
802
|
self._exclude_regex_stale()
|
|
745
803
|
|
|
746
804
|
def exclude(self, regex: str, which: str = "exclude") -> None:
|
|
@@ -759,7 +817,7 @@ class Coverage(TConfigurable):
|
|
|
759
817
|
|
|
760
818
|
"""
|
|
761
819
|
self._init()
|
|
762
|
-
excl_list = getattr(self.config, which
|
|
820
|
+
excl_list = getattr(self.config, f"{which}_list")
|
|
763
821
|
excl_list.append(regex)
|
|
764
822
|
self._exclude_regex_stale()
|
|
765
823
|
|
|
@@ -770,7 +828,7 @@ class Coverage(TConfigurable):
|
|
|
770
828
|
def _exclude_regex(self, which: str) -> str:
|
|
771
829
|
"""Return a regex string for the given exclusion list."""
|
|
772
830
|
if which not in self._exclude_re:
|
|
773
|
-
excl_list = getattr(self.config, which
|
|
831
|
+
excl_list = getattr(self.config, f"{which}_list")
|
|
774
832
|
self._exclude_re[which] = join_regex(excl_list)
|
|
775
833
|
return self._exclude_re[which]
|
|
776
834
|
|
|
@@ -782,7 +840,7 @@ class Coverage(TConfigurable):
|
|
|
782
840
|
|
|
783
841
|
"""
|
|
784
842
|
self._init()
|
|
785
|
-
return cast(list[str], getattr(self.config, which
|
|
843
|
+
return cast(list[str], getattr(self.config, f"{which}_list"))
|
|
786
844
|
|
|
787
845
|
def save(self) -> None:
|
|
788
846
|
"""Save the collected coverage data to the data file."""
|
|
@@ -935,6 +993,7 @@ class Coverage(TConfigurable):
|
|
|
935
993
|
analysis.missing_formatted(),
|
|
936
994
|
)
|
|
937
995
|
|
|
996
|
+
@functools.lru_cache(maxsize=1)
|
|
938
997
|
def _analyze(self, morf: TMorf) -> Analysis:
|
|
939
998
|
"""Analyze a module or file. Private for now."""
|
|
940
999
|
self._init()
|
|
@@ -945,6 +1004,20 @@ class Coverage(TConfigurable):
|
|
|
945
1004
|
filename = self._file_mapper(file_reporter.filename)
|
|
946
1005
|
return analysis_from_file_reporter(data, self.config.precision, file_reporter, filename)
|
|
947
1006
|
|
|
1007
|
+
def branch_stats(self, morf: TMorf) -> dict[TLineNo, tuple[int, int]]:
|
|
1008
|
+
"""Get branch statistics about a module.
|
|
1009
|
+
|
|
1010
|
+
`morf` is a module or a file name.
|
|
1011
|
+
|
|
1012
|
+
Returns a dict mapping line numbers to a tuple:
|
|
1013
|
+
(total_exits, taken_exits).
|
|
1014
|
+
|
|
1015
|
+
.. versionadded:: 7.7
|
|
1016
|
+
|
|
1017
|
+
"""
|
|
1018
|
+
analysis = self._analyze(morf)
|
|
1019
|
+
return analysis.branch_stats()
|
|
1020
|
+
|
|
948
1021
|
@functools.lru_cache(maxsize=1)
|
|
949
1022
|
def _get_file_reporter(self, morf: TMorf) -> FileReporter:
|
|
950
1023
|
"""Get a FileReporter for a module or file name."""
|
|
@@ -963,7 +1036,8 @@ class Coverage(TConfigurable):
|
|
|
963
1036
|
if file_reporter is None:
|
|
964
1037
|
raise PluginError(
|
|
965
1038
|
"Plugin {!r} did not provide a file reporter for {!r}.".format(
|
|
966
|
-
plugin._coverage_plugin_name,
|
|
1039
|
+
plugin._coverage_plugin_name,
|
|
1040
|
+
morf,
|
|
967
1041
|
),
|
|
968
1042
|
)
|
|
969
1043
|
|
|
@@ -993,8 +1067,9 @@ class Coverage(TConfigurable):
|
|
|
993
1067
|
|
|
994
1068
|
# Be sure we have a collection.
|
|
995
1069
|
if not isinstance(morfs, (list, tuple, set)):
|
|
996
|
-
morfs = [morfs]
|
|
1070
|
+
morfs = [morfs] # type: ignore[list-item]
|
|
997
1071
|
|
|
1072
|
+
morfs = sorted(morfs, key=lambda m: m if isinstance(m, str) else m.__name__)
|
|
998
1073
|
return [(self._get_file_reporter(morf), morf) for morf in morfs]
|
|
999
1074
|
|
|
1000
1075
|
def _prepare_data_for_reporting(self) -> None:
|
|
@@ -1004,6 +1079,7 @@ class Coverage(TConfigurable):
|
|
|
1004
1079
|
if self._data is not None:
|
|
1005
1080
|
mapped_data.update(self._data, map_path=self._make_aliases().map)
|
|
1006
1081
|
self._data = mapped_data
|
|
1082
|
+
self._data_to_close.append(mapped_data)
|
|
1007
1083
|
|
|
1008
1084
|
def report(
|
|
1009
1085
|
self,
|
|
@@ -1172,8 +1248,7 @@ class Coverage(TConfigurable):
|
|
|
1172
1248
|
precision=precision,
|
|
1173
1249
|
):
|
|
1174
1250
|
reporter = HtmlReporter(self)
|
|
1175
|
-
|
|
1176
|
-
return ret
|
|
1251
|
+
return reporter.report(morfs)
|
|
1177
1252
|
|
|
1178
1253
|
def xml_report(
|
|
1179
1254
|
self,
|
|
@@ -1298,21 +1373,23 @@ class Coverage(TConfigurable):
|
|
|
1298
1373
|
("coverage_version", covmod.__version__),
|
|
1299
1374
|
("coverage_module", covmod.__file__),
|
|
1300
1375
|
("core", self._collector.tracer_name() if self._collector is not None else "-none-"),
|
|
1301
|
-
("CTracer", "available" if
|
|
1376
|
+
("CTracer", f"available from {CTRACER_FILE}" if CTRACER_FILE else "unavailable"),
|
|
1302
1377
|
("plugins.file_tracers", plugin_info(self._plugins.file_tracers)),
|
|
1303
1378
|
("plugins.configurers", plugin_info(self._plugins.configurers)),
|
|
1304
1379
|
("plugins.context_switchers", plugin_info(self._plugins.context_switchers)),
|
|
1305
1380
|
("configs_attempted", self.config.config_files_attempted),
|
|
1306
1381
|
("configs_read", self.config.config_files_read),
|
|
1307
1382
|
("config_file", self.config.config_file),
|
|
1308
|
-
(
|
|
1383
|
+
(
|
|
1384
|
+
"config_contents",
|
|
1309
1385
|
repr(self.config._config_contents) if self.config._config_contents else "-none-",
|
|
1310
1386
|
),
|
|
1311
1387
|
("data_file", self._data.data_filename() if self._data is not None else "-none-"),
|
|
1312
1388
|
("python", sys.version.replace("\n", "")),
|
|
1313
1389
|
("platform", platform.platform()),
|
|
1314
1390
|
("implementation", platform.python_implementation()),
|
|
1315
|
-
("
|
|
1391
|
+
("build", platform.python_build()),
|
|
1392
|
+
("gil_enabled", getattr(sys, "_is_gil_enabled", lambda: True)()),
|
|
1316
1393
|
("executable", sys.executable),
|
|
1317
1394
|
("def_encoding", sys.getdefaultencoding()),
|
|
1318
1395
|
("fs_encoding", sys.getfilesystemencoding()),
|
|
@@ -1321,52 +1398,46 @@ class Coverage(TConfigurable):
|
|
|
1321
1398
|
("path", sys.path),
|
|
1322
1399
|
("environment", [f"{k} = {v}" for k, v in relevant_environment_display(os.environ)]),
|
|
1323
1400
|
("command_line", " ".join(getattr(sys, "argv", ["-none-"]))),
|
|
1401
|
+
("time", f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S}"),
|
|
1324
1402
|
]
|
|
1325
1403
|
|
|
1326
1404
|
if self._inorout is not None:
|
|
1327
1405
|
info.extend(self._inorout.sys_info())
|
|
1328
1406
|
|
|
1329
|
-
info.extend(CoverageData.sys_info())
|
|
1330
|
-
|
|
1331
1407
|
return info
|
|
1332
1408
|
|
|
1333
1409
|
|
|
1334
1410
|
# Mega debugging...
|
|
1335
1411
|
# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
|
|
1336
|
-
if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)):
|
|
1412
|
+
if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging
|
|
1337
1413
|
from coverage.debug import decorate_methods, show_calls
|
|
1338
1414
|
|
|
1339
|
-
Coverage = decorate_methods(
|
|
1415
|
+
Coverage = decorate_methods( # type: ignore[misc]
|
|
1340
1416
|
show_calls(show_args=True),
|
|
1341
1417
|
butnot=["get_data"],
|
|
1342
1418
|
)(Coverage)
|
|
1343
1419
|
|
|
1344
1420
|
|
|
1345
|
-
def process_startup() -> Coverage | None:
|
|
1421
|
+
def process_startup(*, force: bool = False) -> Coverage | None:
|
|
1346
1422
|
"""Call this at Python start-up to perhaps measure coverage.
|
|
1347
1423
|
|
|
1348
1424
|
If the environment variable COVERAGE_PROCESS_START is defined, coverage
|
|
1349
1425
|
measurement is started. The value of the variable is the config file
|
|
1350
1426
|
to use.
|
|
1351
1427
|
|
|
1352
|
-
|
|
1353
|
-
function when Python starts:
|
|
1354
|
-
|
|
1355
|
-
#. Create or append to sitecustomize.py to add these lines::
|
|
1356
|
-
|
|
1357
|
-
import coverage
|
|
1358
|
-
coverage.process_startup()
|
|
1359
|
-
|
|
1360
|
-
#. Create a .pth file in your Python installation containing::
|
|
1361
|
-
|
|
1362
|
-
import coverage; coverage.process_startup()
|
|
1428
|
+
For details, see https://coverage.readthedocs.io/en/latest/subprocess.html.
|
|
1363
1429
|
|
|
1364
1430
|
Returns the :class:`Coverage` instance that was started, or None if it was
|
|
1365
1431
|
not started by this call.
|
|
1366
1432
|
|
|
1367
1433
|
"""
|
|
1434
|
+
config_data = os.getenv("COVERAGE_PROCESS_CONFIG")
|
|
1368
1435
|
cps = os.getenv("COVERAGE_PROCESS_START")
|
|
1369
|
-
if not
|
|
1436
|
+
if config_data is not None:
|
|
1437
|
+
config_file = CONFIG_DATA_PREFIX + config_data
|
|
1438
|
+
elif cps is not None:
|
|
1439
|
+
config_file = cps
|
|
1440
|
+
else:
|
|
1370
1441
|
# No request for coverage, nothing to do.
|
|
1371
1442
|
return None
|
|
1372
1443
|
|
|
@@ -1379,22 +1450,30 @@ def process_startup() -> Coverage | None:
|
|
|
1379
1450
|
#
|
|
1380
1451
|
# https://github.com/nedbat/coveragepy/issues/340 has more details.
|
|
1381
1452
|
|
|
1382
|
-
if hasattr(process_startup, "coverage"):
|
|
1453
|
+
if not force and hasattr(process_startup, "coverage"):
|
|
1383
1454
|
# We've annotated this function before, so we must have already
|
|
1384
|
-
# started coverage.py in this process. Nothing to do.
|
|
1455
|
+
# auto-started coverage.py in this process. Nothing to do.
|
|
1385
1456
|
return None
|
|
1386
1457
|
|
|
1387
|
-
cov = Coverage(config_file=
|
|
1388
|
-
process_startup.coverage = cov
|
|
1458
|
+
cov = Coverage(config_file=config_file)
|
|
1459
|
+
process_startup.coverage = cov # type: ignore[attr-defined]
|
|
1389
1460
|
cov._warn_no_data = False
|
|
1390
1461
|
cov._warn_unimported_source = False
|
|
1391
1462
|
cov._warn_preimported_source = False
|
|
1392
1463
|
cov._auto_save = True
|
|
1464
|
+
cov._make_pth_file = False
|
|
1393
1465
|
cov.start()
|
|
1394
1466
|
|
|
1395
1467
|
return cov
|
|
1396
1468
|
|
|
1397
1469
|
|
|
1470
|
+
def _after_fork_in_child() -> None:
|
|
1471
|
+
"""Used by patch=fork in the child process to restart coverage."""
|
|
1472
|
+
if cov := Coverage.current():
|
|
1473
|
+
cov.stop()
|
|
1474
|
+
process_startup(force=True)
|
|
1475
|
+
|
|
1476
|
+
|
|
1398
1477
|
def _prevent_sub_process_measurement() -> None:
|
|
1399
1478
|
"""Stop any subprocess auto-measurement from writing data."""
|
|
1400
1479
|
auto_created_coverage = getattr(process_startup, "coverage", None)
|