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/core.py
CHANGED
|
@@ -10,27 +10,28 @@ import sys
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
from coverage import env
|
|
13
|
+
from coverage.config import CoverageConfig
|
|
13
14
|
from coverage.disposition import FileDisposition
|
|
14
15
|
from coverage.exceptions import ConfigError
|
|
15
16
|
from coverage.misc import isolate_module
|
|
16
17
|
from coverage.pytracer import PyTracer
|
|
17
18
|
from coverage.sysmon import SysMonitor
|
|
18
|
-
from coverage.types import
|
|
19
|
-
TFileDisposition,
|
|
20
|
-
Tracer,
|
|
21
|
-
TWarnFn,
|
|
22
|
-
)
|
|
23
|
-
|
|
19
|
+
from coverage.types import TDebugCtl, TFileDisposition, Tracer, TWarnFn
|
|
24
20
|
|
|
25
21
|
os = isolate_module(os)
|
|
26
22
|
|
|
23
|
+
IMPORT_ERROR: str = ""
|
|
24
|
+
|
|
27
25
|
try:
|
|
28
26
|
# Use the C extension code when we can, for speed.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
import coverage.tracer
|
|
28
|
+
|
|
29
|
+
CTRACER_FILE: str | None = getattr(coverage.tracer, "__file__", "unknown")
|
|
30
|
+
except ImportError as imp_err:
|
|
32
31
|
# Couldn't import the C extension, maybe it isn't built.
|
|
33
|
-
|
|
32
|
+
# We still need to check the environment variable directly here,
|
|
33
|
+
# as this code runs before configuration is loaded.
|
|
34
|
+
if os.getenv("COVERAGE_CORE") == "ctrace": # pragma: part covered
|
|
34
35
|
# During testing, we use the COVERAGE_CORE environment variable
|
|
35
36
|
# to indicate that we've fiddled with the environment to test this
|
|
36
37
|
# fallback code. If we thought we had a C tracer, but couldn't import
|
|
@@ -39,7 +40,8 @@ except ImportError:
|
|
|
39
40
|
# exception here causes all sorts of other noise in unittest.
|
|
40
41
|
sys.stderr.write("*** COVERAGE_CORE is 'ctrace' but can't import CTracer!\n")
|
|
41
42
|
sys.exit(1)
|
|
42
|
-
|
|
43
|
+
IMPORT_ERROR = str(imp_err)
|
|
44
|
+
CTRACER_FILE = None
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
class Core:
|
|
@@ -52,43 +54,78 @@ class Core:
|
|
|
52
54
|
packed_arcs: bool
|
|
53
55
|
systrace: bool
|
|
54
56
|
|
|
55
|
-
def __init__(
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
*,
|
|
56
60
|
warn: TWarnFn,
|
|
57
|
-
|
|
61
|
+
debug: TDebugCtl | None,
|
|
62
|
+
config: CoverageConfig,
|
|
63
|
+
dynamic_contexts: bool,
|
|
58
64
|
metacov: bool,
|
|
59
65
|
) -> None:
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
def _debug(msg: str) -> None:
|
|
67
|
+
if debug:
|
|
68
|
+
debug.write(msg)
|
|
69
|
+
|
|
70
|
+
_debug("in core.py")
|
|
71
|
+
|
|
72
|
+
# Check the conditions that preclude us from using sys.monitoring.
|
|
73
|
+
reason_no_sysmon = ""
|
|
74
|
+
if not env.PYBEHAVIOR.pep669:
|
|
75
|
+
reason_no_sysmon = "sys.monitoring isn't available in this version"
|
|
76
|
+
elif config.branch and not env.PYBEHAVIOR.branch_right_left:
|
|
77
|
+
reason_no_sysmon = "sys.monitoring can't measure branches in this version"
|
|
78
|
+
elif dynamic_contexts:
|
|
79
|
+
reason_no_sysmon = "it doesn't yet support dynamic contexts"
|
|
80
|
+
elif any((bad := c) in config.concurrency for c in ["greenlet", "eventlet", "gevent"]):
|
|
81
|
+
reason_no_sysmon = f"it doesn't support concurrency={bad}"
|
|
62
82
|
|
|
63
|
-
core_name: str | None
|
|
64
|
-
if timid:
|
|
83
|
+
core_name: str | None = None
|
|
84
|
+
if config.timid:
|
|
65
85
|
core_name = "pytrace"
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
_debug("core.py: Using pytrace because timid=True")
|
|
87
|
+
elif core_name is None:
|
|
88
|
+
# This could still leave core_name as None.
|
|
89
|
+
core_name = config.core
|
|
90
|
+
_debug(f"core.py: core from config is {core_name!r}")
|
|
91
|
+
|
|
92
|
+
if core_name == "sysmon" and reason_no_sysmon:
|
|
93
|
+
_debug(f"core.py: raising ConfigError because sysmon not usable: {reason_no_sysmon}")
|
|
94
|
+
raise ConfigError(
|
|
95
|
+
f"Can't use core=sysmon: {reason_no_sysmon}",
|
|
96
|
+
skip_tests=True,
|
|
97
|
+
slug="no-sysmon",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if core_name is None:
|
|
101
|
+
if env.SYSMON_DEFAULT and not reason_no_sysmon:
|
|
102
|
+
core_name = "sysmon"
|
|
103
|
+
_debug("core.py: Using sysmon because SYSMON_DEFAULT is set")
|
|
104
|
+
else:
|
|
105
|
+
core_name = "ctrace"
|
|
106
|
+
_debug("core.py: Defaulting to ctrace core")
|
|
68
107
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
108
|
+
if core_name == "ctrace":
|
|
109
|
+
if not CTRACER_FILE:
|
|
110
|
+
if IMPORT_ERROR and env.SHIPPING_WHEELS:
|
|
111
|
+
warn(f"Couldn't import C tracer: {IMPORT_ERROR}", slug="no-ctracer", once=True)
|
|
112
|
+
core_name = "pytrace"
|
|
113
|
+
_debug("core.py: Falling back to pytrace because C tracer not available")
|
|
72
114
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# core_name = "sysmon"
|
|
77
|
-
if HAS_CTRACER:
|
|
78
|
-
core_name = "ctrace"
|
|
79
|
-
else:
|
|
80
|
-
core_name = "pytrace"
|
|
115
|
+
_debug(f"core.py: Using core={core_name}")
|
|
116
|
+
|
|
117
|
+
self.tracer_kwargs = {}
|
|
81
118
|
|
|
82
119
|
if core_name == "sysmon":
|
|
83
120
|
self.tracer_class = SysMonitor
|
|
84
|
-
self.tracer_kwargs
|
|
121
|
+
self.tracer_kwargs["tool_id"] = 3 if metacov else 1
|
|
85
122
|
self.file_disposition_class = FileDisposition
|
|
86
123
|
self.supports_plugins = False
|
|
87
124
|
self.packed_arcs = False
|
|
88
125
|
self.systrace = False
|
|
89
126
|
elif core_name == "ctrace":
|
|
90
|
-
self.tracer_class = CTracer
|
|
91
|
-
self.file_disposition_class = CFileDisposition
|
|
127
|
+
self.tracer_class = coverage.tracer.CTracer
|
|
128
|
+
self.file_disposition_class = coverage.tracer.CFileDisposition
|
|
92
129
|
self.supports_plugins = True
|
|
93
130
|
self.packed_arcs = True
|
|
94
131
|
self.systrace = True
|
coverage/data.py
CHANGED
|
@@ -16,14 +16,13 @@ import functools
|
|
|
16
16
|
import glob
|
|
17
17
|
import hashlib
|
|
18
18
|
import os.path
|
|
19
|
-
|
|
20
|
-
from typing import Callable
|
|
21
19
|
from collections.abc import Iterable
|
|
20
|
+
from typing import Callable
|
|
22
21
|
|
|
23
22
|
from coverage.exceptions import CoverageException, NoDataError
|
|
24
23
|
from coverage.files import PathAliases
|
|
25
24
|
from coverage.misc import Hasher, file_be_gone, human_sorted, plural
|
|
26
|
-
from coverage.sqldata import CoverageData
|
|
25
|
+
from coverage.sqldata import CoverageData as CoverageData # pylint: disable=useless-import-alias
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
def line_counts(data: CoverageData, fullpath: bool = False) -> dict[str, int]:
|
|
@@ -81,7 +80,7 @@ def combinable_files(data_file: str, data_paths: Iterable[str] | None = None) ->
|
|
|
81
80
|
if os.path.isfile(p):
|
|
82
81
|
files_to_combine.append(os.path.abspath(p))
|
|
83
82
|
elif os.path.isdir(p):
|
|
84
|
-
pattern = glob.escape(os.path.join(os.path.abspath(p), local)) +".*"
|
|
83
|
+
pattern = glob.escape(os.path.join(os.path.abspath(p), local)) + ".*"
|
|
85
84
|
files_to_combine.extend(glob.glob(pattern))
|
|
86
85
|
else:
|
|
87
86
|
raise NoDataError(f"Couldn't combine from non-existent path '{p}'")
|
coverage/debug.py
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import _thread
|
|
8
9
|
import atexit
|
|
9
10
|
import contextlib
|
|
11
|
+
import datetime
|
|
10
12
|
import functools
|
|
11
13
|
import inspect
|
|
12
14
|
import itertools
|
|
@@ -17,13 +19,8 @@ import reprlib
|
|
|
17
19
|
import sys
|
|
18
20
|
import traceback
|
|
19
21
|
import types
|
|
20
|
-
import _thread
|
|
21
|
-
|
|
22
|
-
from typing import (
|
|
23
|
-
overload,
|
|
24
|
-
Any, Callable, IO,
|
|
25
|
-
)
|
|
26
22
|
from collections.abc import Iterable, Iterator, Mapping
|
|
23
|
+
from typing import IO, Any, Callable, Final, overload
|
|
27
24
|
|
|
28
25
|
from coverage.misc import human_sorted_items, isolate_module
|
|
29
26
|
from coverage.types import AnyCallable, TWritable
|
|
@@ -41,7 +38,7 @@ FORCED_DEBUG_FILE = None
|
|
|
41
38
|
class DebugControl:
|
|
42
39
|
"""Control and output for debugging."""
|
|
43
40
|
|
|
44
|
-
show_repr_attr = False
|
|
41
|
+
show_repr_attr = False # For auto_repr
|
|
45
42
|
|
|
46
43
|
def __init__(
|
|
47
44
|
self,
|
|
@@ -76,7 +73,7 @@ class DebugControl:
|
|
|
76
73
|
"""Decide whether to output debug information in category `option`."""
|
|
77
74
|
if option == "callers" and self.suppress_callers:
|
|
78
75
|
return False
|
|
79
|
-
return
|
|
76
|
+
return option in self.options
|
|
80
77
|
|
|
81
78
|
@contextlib.contextmanager
|
|
82
79
|
def without_callers(self) -> Iterator[None]:
|
|
@@ -111,25 +108,38 @@ class DebugControl:
|
|
|
111
108
|
|
|
112
109
|
class NoDebugging(DebugControl):
|
|
113
110
|
"""A replacement for DebugControl that will never try to do anything."""
|
|
111
|
+
|
|
114
112
|
def __init__(self) -> None:
|
|
115
113
|
# pylint: disable=super-init-not-called
|
|
116
|
-
|
|
114
|
+
pass
|
|
117
115
|
|
|
118
116
|
def should(self, option: str) -> bool:
|
|
119
117
|
"""Should we write debug messages? Never."""
|
|
120
118
|
return False
|
|
121
119
|
|
|
120
|
+
@contextlib.contextmanager
|
|
121
|
+
def without_callers(self) -> Iterator[None]:
|
|
122
|
+
"""A dummy context manager to satisfy the api."""
|
|
123
|
+
yield # pragma: never called
|
|
124
|
+
|
|
122
125
|
def write(self, msg: str, *, exc: BaseException | None = None) -> None:
|
|
123
126
|
"""This will never be called."""
|
|
124
127
|
raise AssertionError("NoDebugging.write should never be called.")
|
|
125
128
|
|
|
126
129
|
|
|
130
|
+
class DevNullDebug(NoDebugging):
|
|
131
|
+
"""A DebugControl that won't write anywhere."""
|
|
132
|
+
|
|
133
|
+
def write(self, msg: str, *, exc: BaseException | None = None) -> None:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
127
137
|
def info_header(label: str) -> str:
|
|
128
138
|
"""Make a nice header string."""
|
|
129
|
-
return "--{:-<60s}".format(" "+label+" ")
|
|
139
|
+
return "--{:-<60s}".format(" " + label + " ")
|
|
130
140
|
|
|
131
141
|
|
|
132
|
-
def info_formatter(info: Iterable[tuple[str, Any]]) ->
|
|
142
|
+
def info_formatter(info: Iterable[tuple[str, Any]]) -> Iterable[str]:
|
|
133
143
|
"""Produce a sequence of formatted lines from info.
|
|
134
144
|
|
|
135
145
|
`info` is a sequence of pairs (label, data). The produced lines are
|
|
@@ -139,21 +149,21 @@ def info_formatter(info: Iterable[tuple[str, Any]]) -> Iterator[str]:
|
|
|
139
149
|
info = list(info)
|
|
140
150
|
if not info:
|
|
141
151
|
return
|
|
142
|
-
|
|
143
|
-
assert all(len(l) <
|
|
152
|
+
LABEL_LEN = 30
|
|
153
|
+
assert all(len(l) < LABEL_LEN for l, _ in info)
|
|
144
154
|
for label, data in info:
|
|
145
155
|
if data == []:
|
|
146
156
|
data = "-none-"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
prefix = f"{label:>{LABEL_LEN}}: "
|
|
158
|
+
match data:
|
|
159
|
+
case tuple() if len(str(data)) < 30:
|
|
160
|
+
yield f"{prefix}{data}"
|
|
161
|
+
case tuple() | list() | set():
|
|
162
|
+
for e in data:
|
|
163
|
+
yield f"{prefix}{e}"
|
|
164
|
+
prefix = " " * (LABEL_LEN + 2)
|
|
165
|
+
case _:
|
|
166
|
+
yield f"{prefix}{data}"
|
|
157
167
|
|
|
158
168
|
|
|
159
169
|
def write_formatted_info(
|
|
@@ -185,20 +195,24 @@ _FILENAME_REGEXES: list[tuple[str, str]] = [
|
|
|
185
195
|
]
|
|
186
196
|
_FILENAME_SUBS: list[tuple[str, str]] = []
|
|
187
197
|
|
|
198
|
+
|
|
188
199
|
@overload
|
|
189
200
|
def short_filename(filename: str) -> str:
|
|
190
201
|
pass
|
|
191
202
|
|
|
203
|
+
|
|
192
204
|
@overload
|
|
193
205
|
def short_filename(filename: None) -> None:
|
|
194
206
|
pass
|
|
195
207
|
|
|
208
|
+
|
|
196
209
|
def short_filename(filename: str | None) -> str | None:
|
|
197
210
|
"""Shorten a file name. Directories are replaced by prefixes like 'syspath:'"""
|
|
198
211
|
if not _FILENAME_SUBS:
|
|
199
212
|
for pathdir in sys.path:
|
|
200
213
|
_FILENAME_SUBS.append((pathdir, "syspath:"))
|
|
201
214
|
import coverage
|
|
215
|
+
|
|
202
216
|
_FILENAME_SUBS.append((os.path.dirname(coverage.__file__), "cov:"))
|
|
203
217
|
_FILENAME_SUBS.sort(key=(lambda pair: len(pair[0])), reverse=True)
|
|
204
218
|
if filename is not None:
|
|
@@ -209,6 +223,20 @@ def short_filename(filename: str | None) -> str | None:
|
|
|
209
223
|
return filename
|
|
210
224
|
|
|
211
225
|
|
|
226
|
+
def file_summary(filename: str) -> str:
|
|
227
|
+
"""A one-line summary of a file, for log messages."""
|
|
228
|
+
try:
|
|
229
|
+
s = os.stat(filename)
|
|
230
|
+
except FileNotFoundError:
|
|
231
|
+
summary = "does not exist"
|
|
232
|
+
except Exception as e:
|
|
233
|
+
summary = f"error: {e}"
|
|
234
|
+
else:
|
|
235
|
+
mod = datetime.datetime.fromtimestamp(s.st_mtime)
|
|
236
|
+
summary = f"{s.st_size} bytes, modified {mod}"
|
|
237
|
+
return summary
|
|
238
|
+
|
|
239
|
+
|
|
212
240
|
def short_stack(
|
|
213
241
|
skip: int = 0,
|
|
214
242
|
full: bool = False,
|
|
@@ -237,11 +265,13 @@ def short_stack(
|
|
|
237
265
|
|
|
238
266
|
"""
|
|
239
267
|
# Regexes in initial frames that we don't care about.
|
|
268
|
+
# fmt: off
|
|
240
269
|
BORING_PRELUDE = [
|
|
241
270
|
"<string>", # pytest-xdist has string execution.
|
|
242
271
|
r"\bigor.py$", # Our test runner.
|
|
243
272
|
r"\bsite-packages\b", # pytest etc getting to our tests.
|
|
244
273
|
]
|
|
274
|
+
# fmt: on
|
|
245
275
|
|
|
246
276
|
stack: Iterable[inspect.FrameInfo] = inspect.stack()[:skip:-1]
|
|
247
277
|
if not full:
|
|
@@ -265,7 +295,7 @@ def short_stack(
|
|
|
265
295
|
|
|
266
296
|
def dump_stack_frames(out: TWritable, skip: int = 0) -> None:
|
|
267
297
|
"""Print a summary of the stack to `out`."""
|
|
268
|
-
out.write(short_stack(skip=skip+1) + "\n")
|
|
298
|
+
out.write(short_stack(skip=skip + 1) + "\n")
|
|
269
299
|
|
|
270
300
|
|
|
271
301
|
def clipped_repr(text: str, numchars: int = 50) -> str:
|
|
@@ -293,10 +323,12 @@ def add_pid_and_tid(text: str) -> str:
|
|
|
293
323
|
|
|
294
324
|
AUTO_REPR_IGNORE = {"$coverage.object_id"}
|
|
295
325
|
|
|
326
|
+
|
|
296
327
|
def auto_repr(self: Any) -> str:
|
|
297
328
|
"""A function implementing an automatic __repr__ for debugging."""
|
|
298
329
|
show_attrs = (
|
|
299
|
-
(k, v)
|
|
330
|
+
(k, v)
|
|
331
|
+
for k, v in self.__dict__.items()
|
|
300
332
|
if getattr(v, "show_repr_attr", True)
|
|
301
333
|
and not inspect.ismethod(v)
|
|
302
334
|
and k not in AUTO_REPR_IGNORE
|
|
@@ -308,22 +340,26 @@ def auto_repr(self: Any) -> str:
|
|
|
308
340
|
)
|
|
309
341
|
|
|
310
342
|
|
|
311
|
-
def simplify(v: Any) -> Any:
|
|
343
|
+
def simplify(v: Any) -> Any: # pragma: debugging
|
|
312
344
|
"""Turn things which are nearly dict/list/etc into dict/list/etc."""
|
|
313
345
|
if isinstance(v, dict):
|
|
314
|
-
return {k:simplify(vv) for k, vv in v.items()}
|
|
346
|
+
return {k: simplify(vv) for k, vv in v.items()}
|
|
315
347
|
elif isinstance(v, (list, tuple)):
|
|
316
348
|
return type(v)(simplify(vv) for vv in v)
|
|
317
349
|
elif hasattr(v, "__dict__"):
|
|
318
|
-
return simplify({"."+k: v for k, v in v.__dict__.items()})
|
|
350
|
+
return simplify({"." + k: v for k, v in v.__dict__.items()})
|
|
319
351
|
else:
|
|
320
352
|
return v
|
|
321
353
|
|
|
322
354
|
|
|
323
|
-
def
|
|
355
|
+
def ppformat(v: Any) -> str: # pragma: debugging
|
|
356
|
+
"""Debug helper to pretty-print data, including SimpleNamespace objects."""
|
|
357
|
+
return pprint.pformat(simplify(v), indent=4, compact=True, sort_dicts=True, width=140)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def pp(v: Any) -> None: # pragma: debugging
|
|
324
361
|
"""Debug helper to pretty-print data, including SimpleNamespace objects."""
|
|
325
|
-
|
|
326
|
-
pprint.pprint(simplify(v))
|
|
362
|
+
print(ppformat(v))
|
|
327
363
|
|
|
328
364
|
|
|
329
365
|
def filter_text(text: str, filters: Iterable[Callable[[str], str]]) -> str:
|
|
@@ -338,7 +374,7 @@ def filter_text(text: str, filters: Iterable[Callable[[str], str]]) -> str:
|
|
|
338
374
|
|
|
339
375
|
"""
|
|
340
376
|
clean_text = text.rstrip()
|
|
341
|
-
ending = text[len(clean_text):]
|
|
377
|
+
ending = text[len(clean_text) :]
|
|
342
378
|
text = clean_text
|
|
343
379
|
for filter_fn in filters:
|
|
344
380
|
lines = []
|
|
@@ -350,6 +386,7 @@ def filter_text(text: str, filters: Iterable[Callable[[str], str]]) -> str:
|
|
|
350
386
|
|
|
351
387
|
class CwdTracker:
|
|
352
388
|
"""A class to add cwd info to debug messages."""
|
|
389
|
+
|
|
353
390
|
def __init__(self) -> None:
|
|
354
391
|
self.cwd: str | None = None
|
|
355
392
|
|
|
@@ -357,13 +394,14 @@ class CwdTracker:
|
|
|
357
394
|
"""Add a cwd message for each new cwd."""
|
|
358
395
|
cwd = os.getcwd()
|
|
359
396
|
if cwd != self.cwd:
|
|
360
|
-
text = f"cwd is now {cwd!r}\n"
|
|
397
|
+
text = f"cwd is now {cwd!r}\n{text}"
|
|
361
398
|
self.cwd = cwd
|
|
362
399
|
return text
|
|
363
400
|
|
|
364
401
|
|
|
365
402
|
class ProcessTracker:
|
|
366
403
|
"""Track process creation for debug logging."""
|
|
404
|
+
|
|
367
405
|
def __init__(self) -> None:
|
|
368
406
|
self.pid: int = os.getpid()
|
|
369
407
|
self.did_welcome = False
|
|
@@ -392,6 +430,7 @@ class ProcessTracker:
|
|
|
392
430
|
|
|
393
431
|
class PytestTracker:
|
|
394
432
|
"""Track the current pytest test name to add to debug messages."""
|
|
433
|
+
|
|
395
434
|
def __init__(self) -> None:
|
|
396
435
|
self.test_name: str | None = None
|
|
397
436
|
|
|
@@ -399,13 +438,14 @@ class PytestTracker:
|
|
|
399
438
|
"""Add a message when the pytest test changes."""
|
|
400
439
|
test_name = os.getenv("PYTEST_CURRENT_TEST")
|
|
401
440
|
if test_name != self.test_name:
|
|
402
|
-
text = f"Pytest context: {test_name}\n"
|
|
441
|
+
text = f"Pytest context: {test_name}\n{text}"
|
|
403
442
|
self.test_name = test_name
|
|
404
443
|
return text
|
|
405
444
|
|
|
406
445
|
|
|
407
446
|
class DebugOutputFile:
|
|
408
447
|
"""A file-like object that includes pid and cwd information."""
|
|
448
|
+
|
|
409
449
|
def __init__(
|
|
410
450
|
self,
|
|
411
451
|
outfile: IO[str] | None,
|
|
@@ -448,7 +488,7 @@ class DebugOutputFile:
|
|
|
448
488
|
else:
|
|
449
489
|
# $set_env.py: COVERAGE_DEBUG_FILE - Where to write debug output
|
|
450
490
|
file_name = os.getenv("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
|
|
451
|
-
if file_name in
|
|
491
|
+
if file_name in ["stdout", "stderr"]:
|
|
452
492
|
fileobj = getattr(sys, file_name)
|
|
453
493
|
elif file_name:
|
|
454
494
|
fileobj = open(file_name, "a", encoding="utf-8")
|
|
@@ -458,7 +498,7 @@ class DebugOutputFile:
|
|
|
458
498
|
the_one = cls(fileobj, filters)
|
|
459
499
|
cls._set_singleton_data(the_one, interim)
|
|
460
500
|
|
|
461
|
-
if not(the_one.filters):
|
|
501
|
+
if not (the_one.filters):
|
|
462
502
|
the_one.filters = list(filters)
|
|
463
503
|
return the_one
|
|
464
504
|
|
|
@@ -467,8 +507,8 @@ class DebugOutputFile:
|
|
|
467
507
|
# a process-wide singleton. So stash it in sys.modules instead of
|
|
468
508
|
# on a class attribute. Yes, this is aggressively gross.
|
|
469
509
|
|
|
470
|
-
SYS_MOD_NAME = "$coverage.debug.DebugOutputFile.the_one"
|
|
471
|
-
SINGLETON_ATTR = "the_one_and_is_interim"
|
|
510
|
+
SYS_MOD_NAME: Final[str] = "$coverage.debug.DebugOutputFile.the_one"
|
|
511
|
+
SINGLETON_ATTR: Final[str] = "the_one_and_is_interim"
|
|
472
512
|
|
|
473
513
|
@classmethod
|
|
474
514
|
def _set_singleton_data(cls, the_one: DebugOutputFile, interim: bool) -> None:
|
|
@@ -492,19 +532,21 @@ class DebugOutputFile:
|
|
|
492
532
|
def write(self, text: str) -> None:
|
|
493
533
|
"""Just like file.write, but filter through all our filters."""
|
|
494
534
|
assert self.outfile is not None
|
|
495
|
-
self.outfile.
|
|
496
|
-
|
|
535
|
+
if not self.outfile.closed:
|
|
536
|
+
self.outfile.write(filter_text(text, self.filters))
|
|
537
|
+
self.outfile.flush()
|
|
497
538
|
|
|
498
539
|
def flush(self) -> None:
|
|
499
540
|
"""Flush our file."""
|
|
500
541
|
assert self.outfile is not None
|
|
501
|
-
self.outfile.
|
|
542
|
+
if not self.outfile.closed:
|
|
543
|
+
self.outfile.flush()
|
|
502
544
|
|
|
503
545
|
|
|
504
|
-
def log(msg: str, stack: bool = False) -> None:
|
|
546
|
+
def log(msg: str, stack: bool = False) -> None: # pragma: debugging
|
|
505
547
|
"""Write a log message as forcefully as possible."""
|
|
506
548
|
out = DebugOutputFile.get_one(interim=True)
|
|
507
|
-
out.write(msg+"\n")
|
|
549
|
+
out.write(msg + "\n")
|
|
508
550
|
if stack:
|
|
509
551
|
dump_stack_frames(out=out, skip=1)
|
|
510
552
|
|
|
@@ -513,9 +555,10 @@ def decorate_methods(
|
|
|
513
555
|
decorator: Callable[..., Any],
|
|
514
556
|
butnot: Iterable[str] = (),
|
|
515
557
|
private: bool = False,
|
|
516
|
-
) -> Callable[..., Any]:
|
|
558
|
+
) -> Callable[..., Any]: # pragma: debugging
|
|
517
559
|
"""A class decorator to apply a decorator to methods."""
|
|
518
|
-
|
|
560
|
+
|
|
561
|
+
def _decorator(cls): # type: ignore[no-untyped-def]
|
|
519
562
|
for name, meth in inspect.getmembers(cls, inspect.isroutine):
|
|
520
563
|
if name not in cls.__dict__:
|
|
521
564
|
continue
|
|
@@ -526,17 +569,21 @@ def decorate_methods(
|
|
|
526
569
|
continue
|
|
527
570
|
setattr(cls, name, decorator(meth))
|
|
528
571
|
return cls
|
|
572
|
+
|
|
529
573
|
return _decorator
|
|
530
574
|
|
|
531
575
|
|
|
532
576
|
def break_in_pudb(func: AnyCallable) -> AnyCallable: # pragma: debugging
|
|
533
577
|
"""A function decorator to stop in the debugger for each call."""
|
|
578
|
+
|
|
534
579
|
@functools.wraps(func)
|
|
535
580
|
def _wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
536
581
|
import pudb
|
|
582
|
+
|
|
537
583
|
sys.stdout = sys.__stdout__
|
|
538
584
|
pudb.set_trace()
|
|
539
585
|
return func(*args, **kwargs)
|
|
586
|
+
|
|
540
587
|
return _wrapper
|
|
541
588
|
|
|
542
589
|
|
|
@@ -544,12 +591,14 @@ OBJ_IDS = itertools.count()
|
|
|
544
591
|
CALLS = itertools.count()
|
|
545
592
|
OBJ_ID_ATTR = "$coverage.object_id"
|
|
546
593
|
|
|
594
|
+
|
|
547
595
|
def show_calls(
|
|
548
596
|
show_args: bool = True,
|
|
549
597
|
show_stack: bool = False,
|
|
550
598
|
show_return: bool = False,
|
|
551
|
-
) -> Callable[..., Any]:
|
|
599
|
+
) -> Callable[..., Any]: # pragma: debugging
|
|
552
600
|
"""A method decorator to debug-log each call to the function."""
|
|
601
|
+
|
|
553
602
|
def _decorator(func: AnyCallable) -> AnyCallable:
|
|
554
603
|
@functools.wraps(func)
|
|
555
604
|
def _wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
|
|
@@ -578,7 +627,9 @@ def show_calls(
|
|
|
578
627
|
msg = f"{oid} {callid:04d} {func.__name__} return {ret!r}\n"
|
|
579
628
|
DebugOutputFile.get_one(interim=True).write(msg)
|
|
580
629
|
return ret
|
|
630
|
+
|
|
581
631
|
return _wrapper
|
|
632
|
+
|
|
582
633
|
return _decorator
|
|
583
634
|
|
|
584
635
|
|
|
@@ -595,19 +646,24 @@ def relevant_environment_display(env: Mapping[str, str]) -> list[tuple[str, str]
|
|
|
595
646
|
A list of pairs (name, value) to show.
|
|
596
647
|
|
|
597
648
|
"""
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
649
|
+
SLUGS = {"COV", "PY"}
|
|
650
|
+
INCLUDE = {"HOME", "TEMP", "TMP"}
|
|
651
|
+
CLOAK = {"API", "TOKEN", "KEY", "SECRET", "PASS", "SIGNATURE"}
|
|
652
|
+
TRUNCATE = {"COVERAGE_PROCESS_CONFIG"}
|
|
653
|
+
TRUNCATE_LEN = 60
|
|
601
654
|
|
|
602
655
|
to_show = []
|
|
603
656
|
for name, val in env.items():
|
|
604
|
-
|
|
605
|
-
if name in
|
|
606
|
-
|
|
607
|
-
elif any(slug in name for slug in
|
|
608
|
-
|
|
609
|
-
if
|
|
610
|
-
if any(slug in name for slug in
|
|
657
|
+
show = False
|
|
658
|
+
if name in INCLUDE:
|
|
659
|
+
show = True
|
|
660
|
+
elif any(slug in name for slug in SLUGS):
|
|
661
|
+
show = True
|
|
662
|
+
if show:
|
|
663
|
+
if any(slug in name for slug in CLOAK):
|
|
611
664
|
val = re.sub(r"\w", "*", val)
|
|
665
|
+
if name in TRUNCATE:
|
|
666
|
+
if len(val) > TRUNCATE_LEN:
|
|
667
|
+
val = val[: TRUNCATE_LEN - 3] + "..."
|
|
612
668
|
to_show.append((name, val))
|
|
613
669
|
return human_sorted_items(to_show)
|
coverage/disposition.py
CHANGED
|
@@ -32,6 +32,7 @@ class FileDisposition:
|
|
|
32
32
|
# be implemented in either C or Python. Acting on them is done with these
|
|
33
33
|
# functions.
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
def disposition_init(cls: type[TFileDisposition], original_filename: str) -> TFileDisposition:
|
|
36
37
|
"""Construct and initialize a new FileDisposition object."""
|
|
37
38
|
disp = cls()
|