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/patch.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Invasive patches for coverage.py."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import atexit
|
|
9
|
+
import contextlib
|
|
10
|
+
import os
|
|
11
|
+
import site
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Any, NoReturn
|
|
14
|
+
|
|
15
|
+
from coverage import env
|
|
16
|
+
from coverage.debug import NoDebugging, DevNullDebug
|
|
17
|
+
from coverage.exceptions import ConfigError, CoverageException
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from coverage import Coverage
|
|
21
|
+
from coverage.config import CoverageConfig
|
|
22
|
+
from coverage.types import TDebugCtl
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def apply_patches(
|
|
26
|
+
cov: Coverage,
|
|
27
|
+
config: CoverageConfig,
|
|
28
|
+
debug: TDebugCtl,
|
|
29
|
+
*,
|
|
30
|
+
make_pth_file: bool = True,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Apply invasive patches requested by `[run] patch=`."""
|
|
33
|
+
debug = debug if debug.should("patch") else DevNullDebug()
|
|
34
|
+
for patch in sorted(set(config.patch)):
|
|
35
|
+
match patch:
|
|
36
|
+
case "_exit":
|
|
37
|
+
_patch__exit(cov, debug)
|
|
38
|
+
|
|
39
|
+
case "execv":
|
|
40
|
+
_patch_execv(cov, config, debug)
|
|
41
|
+
|
|
42
|
+
case "fork":
|
|
43
|
+
_patch_fork(debug)
|
|
44
|
+
|
|
45
|
+
case "subprocess":
|
|
46
|
+
_patch_subprocess(config, debug, make_pth_file)
|
|
47
|
+
|
|
48
|
+
case _:
|
|
49
|
+
raise ConfigError(f"Unknown patch {patch!r}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _patch__exit(cov: Coverage, debug: TDebugCtl) -> None:
|
|
53
|
+
"""Patch os._exit."""
|
|
54
|
+
debug.write("Patching _exit")
|
|
55
|
+
|
|
56
|
+
old_exit = os._exit
|
|
57
|
+
|
|
58
|
+
def coverage_os_exit_patch(status: int) -> NoReturn:
|
|
59
|
+
with contextlib.suppress(Exception):
|
|
60
|
+
debug.write(f"Using _exit patch with {cov = }")
|
|
61
|
+
with contextlib.suppress(Exception):
|
|
62
|
+
cov.save()
|
|
63
|
+
old_exit(status)
|
|
64
|
+
|
|
65
|
+
os._exit = coverage_os_exit_patch
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _patch_execv(cov: Coverage, config: CoverageConfig, debug: TDebugCtl) -> None:
|
|
69
|
+
"""Patch the execv family of functions."""
|
|
70
|
+
if env.WINDOWS:
|
|
71
|
+
raise CoverageException("patch=execv isn't supported yet on Windows.")
|
|
72
|
+
|
|
73
|
+
debug.write("Patching execv")
|
|
74
|
+
|
|
75
|
+
def make_execv_patch(fname: str, old_execv: Any) -> Any:
|
|
76
|
+
def coverage_execv_patch(*args: Any, **kwargs: Any) -> Any:
|
|
77
|
+
with contextlib.suppress(Exception):
|
|
78
|
+
debug.write(f"Using execv patch for {fname} with {cov = }")
|
|
79
|
+
with contextlib.suppress(Exception):
|
|
80
|
+
cov.save()
|
|
81
|
+
|
|
82
|
+
if fname.endswith("e"):
|
|
83
|
+
# Assume the `env` argument is passed positionally.
|
|
84
|
+
new_env = args[-1]
|
|
85
|
+
# Pass our configuration in the new environment.
|
|
86
|
+
new_env["COVERAGE_PROCESS_CONFIG"] = config.serialize()
|
|
87
|
+
if env.TESTING:
|
|
88
|
+
# The subprocesses need to use the same core as the main process.
|
|
89
|
+
new_env["COVERAGE_CORE"] = os.getenv("COVERAGE_CORE")
|
|
90
|
+
|
|
91
|
+
# When testing locally, we need to honor the pyc file location
|
|
92
|
+
# or they get written to the .tox directories and pollute the
|
|
93
|
+
# next run with a different core.
|
|
94
|
+
if (cache_prefix := os.getenv("PYTHONPYCACHEPREFIX")) is not None:
|
|
95
|
+
new_env["PYTHONPYCACHEPREFIX"] = cache_prefix
|
|
96
|
+
|
|
97
|
+
# Without this, it fails on PyPy and Ubuntu.
|
|
98
|
+
new_env["PATH"] = os.getenv("PATH")
|
|
99
|
+
old_execv(*args, **kwargs)
|
|
100
|
+
|
|
101
|
+
return coverage_execv_patch
|
|
102
|
+
|
|
103
|
+
# All the exec* and spawn* functions eventually call execv or execve.
|
|
104
|
+
os.execv = make_execv_patch("execv", os.execv)
|
|
105
|
+
os.execve = make_execv_patch("execve", os.execve)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _patch_fork(debug: TDebugCtl) -> None:
|
|
109
|
+
"""Ensure Coverage is properly reset after a fork."""
|
|
110
|
+
from coverage.control import _after_fork_in_child
|
|
111
|
+
|
|
112
|
+
if env.WINDOWS:
|
|
113
|
+
raise CoverageException("patch=fork isn't supported yet on Windows.")
|
|
114
|
+
|
|
115
|
+
debug.write("Patching fork")
|
|
116
|
+
os.register_at_fork(after_in_child=_after_fork_in_child)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _patch_subprocess(config: CoverageConfig, debug: TDebugCtl, make_pth_file: bool) -> None:
|
|
120
|
+
"""Write .pth files and set environment vars to measure subprocesses."""
|
|
121
|
+
debug.write("Patching subprocess")
|
|
122
|
+
|
|
123
|
+
if make_pth_file:
|
|
124
|
+
pth_files = create_pth_files(debug)
|
|
125
|
+
|
|
126
|
+
def delete_pth_files() -> None:
|
|
127
|
+
for p in pth_files:
|
|
128
|
+
debug.write(f"Deleting subprocess .pth file: {str(p)!r}")
|
|
129
|
+
p.unlink(missing_ok=True)
|
|
130
|
+
|
|
131
|
+
atexit.register(delete_pth_files)
|
|
132
|
+
assert config.config_file is not None
|
|
133
|
+
os.environ["COVERAGE_PROCESS_CONFIG"] = config.serialize()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Writing .pth files is not obvious. On Windows, getsitepackages() returns two
|
|
137
|
+
# directories. A .pth file in the first will be run, but coverage isn't
|
|
138
|
+
# importable yet. We write into all the places we can, but with defensive
|
|
139
|
+
# import code.
|
|
140
|
+
|
|
141
|
+
PTH_CODE = """\
|
|
142
|
+
try:
|
|
143
|
+
import coverage
|
|
144
|
+
except:
|
|
145
|
+
pass
|
|
146
|
+
else:
|
|
147
|
+
coverage.process_startup()
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
PTH_TEXT = f"import sys; exec({PTH_CODE!r})\n"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def create_pth_files(debug: TDebugCtl = NoDebugging()) -> list[Path]:
|
|
154
|
+
"""Create .pth files for measuring subprocesses."""
|
|
155
|
+
pth_files = []
|
|
156
|
+
for pth_dir in site.getsitepackages():
|
|
157
|
+
pth_file = Path(pth_dir) / f"subcover_{os.getpid()}.pth"
|
|
158
|
+
try:
|
|
159
|
+
if debug.should("patch"):
|
|
160
|
+
debug.write(f"Writing subprocess .pth file: {str(pth_file)!r}")
|
|
161
|
+
pth_file.write_text(PTH_TEXT, encoding="utf-8")
|
|
162
|
+
except OSError: # pragma: cant happen
|
|
163
|
+
continue
|
|
164
|
+
else:
|
|
165
|
+
pth_files.append(pth_file)
|
|
166
|
+
return pth_files
|
coverage/phystokens.py
CHANGED
|
@@ -12,13 +12,11 @@ import re
|
|
|
12
12
|
import sys
|
|
13
13
|
import token
|
|
14
14
|
import tokenize
|
|
15
|
-
|
|
16
15
|
from collections.abc import Iterable
|
|
17
16
|
|
|
18
17
|
from coverage import env
|
|
19
18
|
from coverage.types import TLineNo, TSourceTokenLines
|
|
20
19
|
|
|
21
|
-
|
|
22
20
|
TokenInfos = Iterable[tokenize.TokenInfo]
|
|
23
21
|
|
|
24
22
|
|
|
@@ -57,8 +55,10 @@ def _phys_tokens(toks: TokenInfos) -> TokenInfos:
|
|
|
57
55
|
if last_ttext.endswith("\\"):
|
|
58
56
|
inject_backslash = False
|
|
59
57
|
elif ttype == token.STRING:
|
|
60
|
-
if (
|
|
61
|
-
last_line.
|
|
58
|
+
if ( # pylint: disable=simplifiable-if-statement
|
|
59
|
+
last_line.endswith("\\\n")
|
|
60
|
+
and last_line.rstrip(" \\\n").endswith(last_ttext)
|
|
61
|
+
):
|
|
62
62
|
# Deal with special cases like such code::
|
|
63
63
|
#
|
|
64
64
|
# a = ["aaa",\ # there may be zero or more blanks between "," and "\".
|
|
@@ -70,15 +70,17 @@ def _phys_tokens(toks: TokenInfos) -> TokenInfos:
|
|
|
70
70
|
# It's a multi-line string and the first line ends with
|
|
71
71
|
# a backslash, so we don't need to inject another.
|
|
72
72
|
inject_backslash = False
|
|
73
|
-
elif
|
|
73
|
+
elif env.PYBEHAVIOR.fstring_syntax and ttype == token.FSTRING_MIDDLE:
|
|
74
74
|
inject_backslash = False
|
|
75
75
|
if inject_backslash:
|
|
76
76
|
# Figure out what column the backslash is in.
|
|
77
77
|
ccol = len(last_line.split("\n")[-2]) - 1
|
|
78
78
|
# Yield the token, with a fake token type.
|
|
79
79
|
yield tokenize.TokenInfo(
|
|
80
|
-
99999,
|
|
81
|
-
|
|
80
|
+
99999,
|
|
81
|
+
"\\\n",
|
|
82
|
+
(slineno, ccol),
|
|
83
|
+
(slineno, ccol + 2),
|
|
82
84
|
last_line,
|
|
83
85
|
)
|
|
84
86
|
last_line = ltext
|
|
@@ -93,7 +95,7 @@ def find_soft_key_lines(source: str) -> set[TLineNo]:
|
|
|
93
95
|
soft_key_lines: set[TLineNo] = set()
|
|
94
96
|
|
|
95
97
|
for node in ast.walk(ast.parse(source)):
|
|
96
|
-
if
|
|
98
|
+
if isinstance(node, ast.Match):
|
|
97
99
|
soft_key_lines.add(node.lineno)
|
|
98
100
|
for case in node.cases:
|
|
99
101
|
soft_key_lines.add(case.pattern.lineno)
|
|
@@ -126,10 +128,7 @@ def source_token_lines(source: str) -> TSourceTokenLines:
|
|
|
126
128
|
source = source.expandtabs(8).replace("\r\n", "\n")
|
|
127
129
|
tokgen = generate_tokens(source)
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
soft_key_lines = find_soft_key_lines(source)
|
|
131
|
-
else:
|
|
132
|
-
soft_key_lines = set()
|
|
131
|
+
soft_key_lines = find_soft_key_lines(source)
|
|
133
132
|
|
|
134
133
|
for ttype, ttext, (sline, scol), (_, ecol), _ in _phys_tokens(tokgen):
|
|
135
134
|
mark_start = True
|
|
@@ -144,6 +143,9 @@ def source_token_lines(source: str) -> TSourceTokenLines:
|
|
|
144
143
|
elif ttype in ws_tokens:
|
|
145
144
|
mark_end = False
|
|
146
145
|
else:
|
|
146
|
+
if env.PYBEHAVIOR.fstring_syntax and ttype == token.FSTRING_MIDDLE:
|
|
147
|
+
part = part.replace("{", "{{").replace("}", "}}")
|
|
148
|
+
ecol = scol + len(part)
|
|
147
149
|
if mark_start and scol > col:
|
|
148
150
|
line.append(("ws", " " * (scol - col)))
|
|
149
151
|
mark_start = False
|
|
@@ -152,19 +154,16 @@ def source_token_lines(source: str) -> TSourceTokenLines:
|
|
|
152
154
|
if keyword.iskeyword(ttext):
|
|
153
155
|
# Hard keywords are always keywords.
|
|
154
156
|
tok_class = "key"
|
|
155
|
-
elif
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
is_start_of_line = False
|
|
166
|
-
if is_start_of_line and sline in soft_key_lines:
|
|
167
|
-
tok_class = "key"
|
|
157
|
+
elif keyword.issoftkeyword(ttext):
|
|
158
|
+
# Soft keywords appear at the start of their line.
|
|
159
|
+
if len(line) == 0:
|
|
160
|
+
is_start_of_line = True
|
|
161
|
+
elif (len(line) == 1) and line[0][0] == "ws":
|
|
162
|
+
is_start_of_line = True
|
|
163
|
+
else:
|
|
164
|
+
is_start_of_line = False
|
|
165
|
+
if is_start_of_line and sline in soft_key_lines:
|
|
166
|
+
tok_class = "key"
|
|
168
167
|
line.append((tok_class, part))
|
|
169
168
|
mark_end = True
|
|
170
169
|
scol = 0
|
coverage/plugin.py
CHANGED
|
@@ -116,10 +116,9 @@ from __future__ import annotations
|
|
|
116
116
|
|
|
117
117
|
import dataclasses
|
|
118
118
|
import functools
|
|
119
|
-
|
|
119
|
+
from collections.abc import Iterable
|
|
120
120
|
from types import FrameType
|
|
121
121
|
from typing import Any
|
|
122
|
-
from collections.abc import Iterable
|
|
123
122
|
|
|
124
123
|
from coverage import files
|
|
125
124
|
from coverage.misc import _needs_to_implement
|
|
@@ -132,7 +131,7 @@ class CoveragePlugin:
|
|
|
132
131
|
_coverage_plugin_name: str
|
|
133
132
|
_coverage_enabled: bool
|
|
134
133
|
|
|
135
|
-
def file_tracer(self, filename: str) -> FileTracer | None:
|
|
134
|
+
def file_tracer(self, filename: str) -> FileTracer | None: # pylint: disable=unused-argument
|
|
136
135
|
"""Get a :class:`FileTracer` object for a file.
|
|
137
136
|
|
|
138
137
|
Plug-in type: file tracer.
|
|
@@ -174,8 +173,8 @@ class CoveragePlugin:
|
|
|
174
173
|
|
|
175
174
|
def file_reporter(
|
|
176
175
|
self,
|
|
177
|
-
filename: str,
|
|
178
|
-
) -> FileReporter | str:
|
|
176
|
+
filename: str, # pylint: disable=unused-argument
|
|
177
|
+
) -> FileReporter | str: # str should be Literal["python"]
|
|
179
178
|
"""Get the :class:`FileReporter` class to use for a file.
|
|
180
179
|
|
|
181
180
|
Plug-in type: file tracer.
|
|
@@ -191,7 +190,7 @@ class CoveragePlugin:
|
|
|
191
190
|
|
|
192
191
|
def dynamic_context(
|
|
193
192
|
self,
|
|
194
|
-
frame: FrameType,
|
|
193
|
+
frame: FrameType, # pylint: disable=unused-argument
|
|
195
194
|
) -> str | None:
|
|
196
195
|
"""Get the dynamically computed context label for `frame`.
|
|
197
196
|
|
|
@@ -210,7 +209,7 @@ class CoveragePlugin:
|
|
|
210
209
|
|
|
211
210
|
def find_executable_files(
|
|
212
211
|
self,
|
|
213
|
-
src_dir: str,
|
|
212
|
+
src_dir: str, # pylint: disable=unused-argument
|
|
214
213
|
) -> Iterable[str]:
|
|
215
214
|
"""Yield all of the executable files in `src_dir`, recursively.
|
|
216
215
|
|
|
@@ -256,6 +255,7 @@ class CoveragePlugin:
|
|
|
256
255
|
|
|
257
256
|
class CoveragePluginBase:
|
|
258
257
|
"""Plugins produce specialized objects, which point back to the original plugin."""
|
|
258
|
+
|
|
259
259
|
_coverage_plugin: CoveragePlugin
|
|
260
260
|
|
|
261
261
|
|
|
@@ -311,8 +311,8 @@ class FileTracer(CoveragePluginBase):
|
|
|
311
311
|
|
|
312
312
|
def dynamic_source_filename(
|
|
313
313
|
self,
|
|
314
|
-
filename: str,
|
|
315
|
-
frame: FrameType,
|
|
314
|
+
filename: str, # pylint: disable=unused-argument
|
|
315
|
+
frame: FrameType, # pylint: disable=unused-argument
|
|
316
316
|
) -> str | None:
|
|
317
317
|
"""Get a dynamically computed source file name.
|
|
318
318
|
|
|
@@ -527,7 +527,7 @@ class FileReporter(CoveragePluginBase):
|
|
|
527
527
|
self,
|
|
528
528
|
start: TLineNo,
|
|
529
529
|
end: TLineNo,
|
|
530
|
-
executed_arcs: Iterable[TArc] | None = None,
|
|
530
|
+
executed_arcs: Iterable[TArc] | None = None, # pylint: disable=unused-argument
|
|
531
531
|
) -> str:
|
|
532
532
|
"""Provide an English sentence describing a missing arc.
|
|
533
533
|
|
|
@@ -545,8 +545,8 @@ class FileReporter(CoveragePluginBase):
|
|
|
545
545
|
|
|
546
546
|
def arc_description(
|
|
547
547
|
self,
|
|
548
|
-
start: TLineNo,
|
|
549
|
-
end: TLineNo
|
|
548
|
+
start: TLineNo, # pylint: disable=unused-argument
|
|
549
|
+
end: TLineNo,
|
|
550
550
|
) -> str:
|
|
551
551
|
"""Provide an English description of an arc's effect."""
|
|
552
552
|
return f"jump to line {end}"
|
|
@@ -614,4 +614,4 @@ class FileReporter(CoveragePluginBase):
|
|
|
614
614
|
return isinstance(other, FileReporter) and self.filename < other.filename
|
|
615
615
|
|
|
616
616
|
# This object doesn't need to be hashed.
|
|
617
|
-
__hash__ = None
|
|
617
|
+
__hash__ = None # type: ignore[assignment]
|
coverage/plugin_support.py
CHANGED
|
@@ -8,17 +8,14 @@ from __future__ import annotations
|
|
|
8
8
|
import os
|
|
9
9
|
import os.path
|
|
10
10
|
import sys
|
|
11
|
-
|
|
12
|
-
from types import FrameType
|
|
13
|
-
from typing import Any
|
|
14
11
|
from collections.abc import Iterable, Iterator
|
|
12
|
+
from types import FrameType
|
|
13
|
+
from typing import Any, Callable
|
|
15
14
|
|
|
16
15
|
from coverage.exceptions import PluginError
|
|
17
16
|
from coverage.misc import isolate_module
|
|
18
|
-
from coverage.plugin import CoveragePlugin,
|
|
19
|
-
from coverage.types import
|
|
20
|
-
TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines,
|
|
21
|
-
)
|
|
17
|
+
from coverage.plugin import CoveragePlugin, FileReporter, FileTracer
|
|
18
|
+
from coverage.types import TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines
|
|
22
19
|
|
|
23
20
|
os = isolate_module(os)
|
|
24
21
|
|
|
@@ -26,7 +23,7 @@ os = isolate_module(os)
|
|
|
26
23
|
class Plugins:
|
|
27
24
|
"""The currently loaded collection of coverage.py plugins."""
|
|
28
25
|
|
|
29
|
-
def __init__(self) -> None:
|
|
26
|
+
def __init__(self, debug: TDebugCtl | None = None) -> None:
|
|
30
27
|
self.order: list[CoveragePlugin] = []
|
|
31
28
|
self.names: dict[str, CoveragePlugin] = {}
|
|
32
29
|
self.file_tracers: list[CoveragePlugin] = []
|
|
@@ -34,25 +31,17 @@ class Plugins:
|
|
|
34
31
|
self.context_switchers: list[CoveragePlugin] = []
|
|
35
32
|
|
|
36
33
|
self.current_module: str | None = None
|
|
37
|
-
self.debug
|
|
34
|
+
self.debug = debug
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
cls,
|
|
36
|
+
def load_from_config(
|
|
37
|
+
self,
|
|
42
38
|
modules: Iterable[str],
|
|
43
39
|
config: TPluginConfig,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"""Load plugins from `modules`.
|
|
47
|
-
|
|
48
|
-
Returns a Plugins object with the loaded and configured plugins.
|
|
49
|
-
|
|
50
|
-
"""
|
|
51
|
-
plugins = cls()
|
|
52
|
-
plugins.debug = debug
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Load plugin modules, and read their settings from configuration."""
|
|
53
42
|
|
|
54
43
|
for module in modules:
|
|
55
|
-
|
|
44
|
+
self.current_module = module
|
|
56
45
|
__import__(module)
|
|
57
46
|
mod = sys.modules[module]
|
|
58
47
|
|
|
@@ -63,10 +52,17 @@ class Plugins:
|
|
|
63
52
|
)
|
|
64
53
|
|
|
65
54
|
options = config.get_plugin_options(module)
|
|
66
|
-
coverage_init(
|
|
55
|
+
coverage_init(self, options)
|
|
56
|
+
|
|
57
|
+
self.current_module = None
|
|
67
58
|
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
def load_from_callables(
|
|
60
|
+
self,
|
|
61
|
+
plugin_inits: Iterable[TCoverageInit],
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Load plugins from callables provided."""
|
|
64
|
+
for fn in plugin_inits:
|
|
65
|
+
fn(self)
|
|
70
66
|
|
|
71
67
|
def add_file_tracer(self, plugin: CoveragePlugin) -> None:
|
|
72
68
|
"""Add a file tracer plugin.
|
|
@@ -138,6 +134,9 @@ class Plugins:
|
|
|
138
134
|
return self.names[plugin_name]
|
|
139
135
|
|
|
140
136
|
|
|
137
|
+
TCoverageInit = Callable[[Plugins], None]
|
|
138
|
+
|
|
139
|
+
|
|
141
140
|
class LabelledDebug:
|
|
142
141
|
"""A Debug writer, but with labels for prepending to the messages."""
|
|
143
142
|
|
|
@@ -152,7 +151,7 @@ class LabelledDebug:
|
|
|
152
151
|
def message_prefix(self) -> str:
|
|
153
152
|
"""The prefix to use on messages, combining the labels."""
|
|
154
153
|
prefixes = self.labels + [""]
|
|
155
|
-
return ":\n".join(" "*i+label for i, label in enumerate(prefixes))
|
|
154
|
+
return ":\n".join(" " * i + label for i, label in enumerate(prefixes))
|
|
156
155
|
|
|
157
156
|
def write(self, message: str) -> None:
|
|
158
157
|
"""Write `message`, but with the labels prepended."""
|
|
@@ -211,10 +210,8 @@ class DebugFileTracerWrapper(FileTracer):
|
|
|
211
210
|
|
|
212
211
|
def _show_frame(self, frame: FrameType) -> str:
|
|
213
212
|
"""A short string identifying a frame, for debug messages."""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
frame.f_lineno,
|
|
217
|
-
)
|
|
213
|
+
filename = os.path.basename(frame.f_code.co_filename)
|
|
214
|
+
return f"{filename}@{frame.f_lineno}"
|
|
218
215
|
|
|
219
216
|
def source_filename(self) -> str:
|
|
220
217
|
sfilename = self.tracer.source_filename()
|
|
@@ -228,9 +225,13 @@ class DebugFileTracerWrapper(FileTracer):
|
|
|
228
225
|
|
|
229
226
|
def dynamic_source_filename(self, filename: str, frame: FrameType) -> str | None:
|
|
230
227
|
dyn = self.tracer.dynamic_source_filename(filename, frame)
|
|
231
|
-
self.debug.write(
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
self.debug.write(
|
|
229
|
+
"dynamic_source_filename({!r}, {}) --> {!r}".format(
|
|
230
|
+
filename,
|
|
231
|
+
self._show_frame(frame),
|
|
232
|
+
dyn,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
234
235
|
return dyn
|
|
235
236
|
|
|
236
237
|
def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]:
|
|
@@ -289,10 +290,10 @@ class DebugFileReporterWrapper(FileReporter):
|
|
|
289
290
|
|
|
290
291
|
def source(self) -> str:
|
|
291
292
|
ret = self.reporter.source()
|
|
292
|
-
self.debug.write("source() -->
|
|
293
|
+
self.debug.write(f"source() --> {len(ret)} chars")
|
|
293
294
|
return ret
|
|
294
295
|
|
|
295
296
|
def source_token_lines(self) -> TSourceTokenLines:
|
|
296
297
|
ret = list(self.reporter.source_token_lines())
|
|
297
|
-
self.debug.write("source_token_lines() -->
|
|
298
|
+
self.debug.write(f"source_token_lines() --> {len(ret)} tokens")
|
|
298
299
|
return ret
|
coverage/python.py
CHANGED
|
@@ -8,16 +8,15 @@ from __future__ import annotations
|
|
|
8
8
|
import os.path
|
|
9
9
|
import types
|
|
10
10
|
import zipimport
|
|
11
|
-
|
|
12
|
-
from typing import TYPE_CHECKING
|
|
13
11
|
from collections.abc import Iterable
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
14
13
|
|
|
15
14
|
from coverage import env
|
|
16
15
|
from coverage.exceptions import CoverageException, NoSource
|
|
17
16
|
from coverage.files import canonical_filename, relative_filename, zip_location
|
|
18
17
|
from coverage.misc import isolate_module, join_regex
|
|
19
18
|
from coverage.parser import PythonParser
|
|
20
|
-
from coverage.phystokens import
|
|
19
|
+
from coverage.phystokens import source_encoding, source_token_lines
|
|
21
20
|
from coverage.plugin import CodeRegion, FileReporter
|
|
22
21
|
from coverage.regions import code_regions
|
|
23
22
|
from coverage.types import TArc, TLineNo, TMorf, TSourceTokenLines
|
|
@@ -62,7 +61,7 @@ def get_python_source(filename: str) -> str:
|
|
|
62
61
|
break
|
|
63
62
|
else:
|
|
64
63
|
# Couldn't find source.
|
|
65
|
-
raise NoSource(f"No source for code: '{filename}'.")
|
|
64
|
+
raise NoSource(f"No source for code: '{filename}'.", slug="no-source")
|
|
66
65
|
|
|
67
66
|
# Replace \f because of http://bugs.python.org/issue19035
|
|
68
67
|
source_bytes = source_bytes.replace(b"\f", b" ")
|
|
@@ -194,6 +193,10 @@ class PythonFileReporter(FileReporter):
|
|
|
194
193
|
"""Return the line numbers of statements in the file."""
|
|
195
194
|
return self.parser.statements
|
|
196
195
|
|
|
196
|
+
def multiline_map(self) -> dict[TLineNo, TLineNo]:
|
|
197
|
+
"""A map of line numbers to first-line in a multi-line statement."""
|
|
198
|
+
return self.parser.multiline_map
|
|
199
|
+
|
|
197
200
|
def excluded_lines(self) -> set[TLineNo]:
|
|
198
201
|
"""Return the line numbers of statements in the file."""
|
|
199
202
|
return self.parser.excluded
|
|
@@ -207,10 +210,7 @@ class PythonFileReporter(FileReporter):
|
|
|
207
210
|
def no_branch_lines(self) -> set[TLineNo]:
|
|
208
211
|
assert self.coverage is not None
|
|
209
212
|
no_branch = self.parser.lines_matching(
|
|
210
|
-
join_regex(
|
|
211
|
-
self.coverage.config.partial_list
|
|
212
|
-
+ self.coverage.config.partial_always_list
|
|
213
|
-
)
|
|
213
|
+
join_regex(self.coverage.config.partial_list + self.coverage.config.partial_always_list)
|
|
214
214
|
)
|
|
215
215
|
return no_branch
|
|
216
216
|
|
|
@@ -228,11 +228,7 @@ class PythonFileReporter(FileReporter):
|
|
|
228
228
|
) -> str:
|
|
229
229
|
return self.parser.missing_arc_description(start, end)
|
|
230
230
|
|
|
231
|
-
def arc_description(
|
|
232
|
-
self,
|
|
233
|
-
start: TLineNo,
|
|
234
|
-
end: TLineNo
|
|
235
|
-
) -> str:
|
|
231
|
+
def arc_description(self, start: TLineNo, end: TLineNo) -> str:
|
|
236
232
|
return self.parser.arc_description(start, end)
|
|
237
233
|
|
|
238
234
|
def source(self) -> str:
|